立即注册找回密码

QQ登录

只需一步,快速开始

查看: 8|回复: 0

《架构整洁之道》推荐序:软件开发的上古智慧2018/12/6 21:30:43

[复制链接]

6万

主题

6万

帖子

18万

积分

版主

Rank: 7Rank: 7Rank: 7

积分
187234
发表于 2018-12-6 21:30:02 | 显示全部楼层 |阅读模式

余晟  关注odoo,有帮助!

  让你接手一套不稳定但要紧的在线系统,这套系统还有各种问题:变量命名非常随意,依赖逻辑错综复杂,层次结构乱七八糟,部署流程一塌糊涂,监控系统一片空白 你该怎么办?
  前几年我就遇到了这种问题,我冒着频发的故障仔细观察,发现了最关键的问题:如果放着不动,这套系统的核心功能还是相对稳定的,但经常会有一些外围需求要开发,原有的依赖逻辑和层次结构不够清楚,就会牵一发而动全身,加上测试不完善,所以几乎每次外围功能上线更新,核心功能都会受影响,然后又要重复好几次调试、改正、上线的流程。
  怎么办?大家说了很多办法:开发态度更认真一些,把单元测试都补全,重构代码拆分核心功能和非核心功能,跟业务方谈暂停需求 这些办法都很对,但是,都需要时间才能见效,而我们最缺的就是时间。
  我提了个很笨的办法:把所有共享变量都抽到 R 中读写,消灭本地副本,然后把稳定版本程序多部署几份,就可以多启动几个实例,这些实例标记为 AB 两组。同时,在访问链路前部搭建代理服务用于分流请求,核心功能请求分配到A组(程序基本不更新),外围功能请求分配到B组(程序按业务需求更新)。这样看起来有点多此一举,AB 两组都只有部分代码提供服务,而且要通过 R 共享状态,但是无论B组的程序如何更新,都不会影响到A组所承载的核心服务。
  虽然当时不少人疑惑怎么能这样玩呢?,但它确实有效。当天部署当天生效,在线服务迅速就稳定了,即便新开发的外围功能有问题,核心服务也不受任何影响。这样,业务人员满意了,开发人员也可以安心对系统做改造。
  后来有不少人问我:你怎么会想到这个办法呢?答案是:因为我是个老程序员,成长在面向对象的年代,SOC(关注点分离)、SRP(单一责任原则)、OCP(开放-闭合原则)这些东西对我来说,就好像刷牙洗脸一样是本能。具体到这个例子,无非就是识别关注点,隔离责任,保持核心关注点的封闭而已。
  后来我才知道,这办法有个专门的名字叫蓝绿部署。当然,我是个老程序员,不懂这些新鲜概念,我不太在乎。不过,如今不少程序员确实已经不认识 SOC、SRP、OCP、LSP 等等古老的玩意儿了,大家熟悉的是各种语言、类库、框架、代码托管站。互联开发场景千变万化,技术一日千里,而面向对象在不少人的脑海里,早就是弃之不用的老古董了。只有老一辈的老程序员,还记得那些古老的教诲,守着那些古拙的技巧。但是这些东西,总有一天会被时代淘汰吗?
  实际上,这也是我初读 C A 的疑惑。虽然 U B 的名字对我们这些老程序员如雷贯耳,之前针对一般性软件开发的 C C 和 C C 也确实很受欢迎,但如今写架构,还从结构化编程、面向对象编程、函数式编程写起,还花时间去解释 SRP、OCP、LSP 等等原则,实在难掩古老的感觉拜托,它们和如今的架构有什么关系吗?
  不过如果你耐心读下去就会发现,还真有关系。
  按照 U B 的说法,所谓架构,就是用最小的人力成本来满足构建和维护系统的需求的设计行为。以前的面向对象系统,和如今的分布式系统,在这一点上是完全一致的。听取久远的教诲,尊重古老的智慧,如今的架构师也会从中受益的。
  不信?我们就拿经典的三种编程范式来举例,看看这些老掉牙的玩意儿和如今的架构设计有什么关联。
  结构化编程
  一般理解,结构化编程是由 -, - 之类的语句组织程序代码的方式,而杜绝了  导致的混乱。但是从更深的层次上看,它也是一种设计范式,避免随意使用 ,使用 -, - 之类控制语句和函数、子函数组织起来的程序代码,可以保证程序的结构是清楚的,自顶向下层层细化,消灭了杂错,杜绝了混淆。
  联系到如今的分布式系统,我们在设计的时候,真的能够做到自顶向下层层细化、结构清晰吗?有多少次,我看到的系统设计图里,根本没有层次的概念,各个模块没有一致的层次划分,子系统交互的不是子系统,而是一盘散沙式的接口,甚至接口之间随意互调、关系乱成一团麻的情况也时常出现,结果就是维护和调试的噩梦。
  吹散历史的迷雾,不正是古老的  陷阱的再现吗?
  面向对象编程
  一般理解,面向对象编程是由封装、继承、多态三种特性支持的,包含类、接口等等若干概念的编程方式。但是从更深的层次上看,它也是一种设计范式。多态大概算其中最神奇的特性了,程序员在确定接口时做好抽象,代码就可以很灵活,遇到新情况,新写一个实现就可以无缝对接。
  联系到如今的分布式系统,我们在设计的时候,真的能够做到接口足够抽象,新模块能无缝对接吗?有多少次,我看到接口的设计非常随意,接口不是基于行为而是基于特定场景的实现,没有做适当的抽象,也没有为未来预留空间,直接导致契约僵硬死板。新增一种终端呈现形式,整个内容生产流程就要大动干戈,这样的例子并不罕见。
  许多人都熟悉那个经典的例子:三角形、圆形、正方形都是形状,都必须提供  方法,只是各自实现不同。这个道理很容易讲通,但它对如今的开发真的有什么用吗?要我说,还真有用。比如某在线教学系统,有各种类型的学习计划,也使用分布式架构。但是不管这些学习计划分布在哪里,用什么语言实现的,都必须提供完成百分比计算的方法,虽然具体的计算逻辑各异。照这样做了,教学情况统计就简单异常,不照这样做,就会麻烦异常。
  抹去历史的尘埃,这类设计背后的逻辑,不正是多态的思想吗?
  函数式编程
  一般理解,函数式编程是以函数为基本单元,没有变量(更准确地说是不能重复赋值)也没有副作用的编程方式。但是从更深的层次上看,它彻底隔离了可变性,变量或者状态默认就是不可变的,如果要变化,则必须经过合理设计的专门机制。所以,它也避免了死锁、状态冲突等众多麻烦。
  联系到如今的分布式系统,我们在设计的时候,真的能够彻底隔离可变性,避免状态冲突吗?有多少次,我看到状态或变量的修改接口大方暴露,被不经意(或者恶意)修改,导致奇怪的故障。U B 举了个相当有趣的例子,如果又要保证操作原子性又要能精确还原各时刻的状态,有个办法是这样的:只提供 CR 操作,而不提供完整的 CRUD 操作(就像 MSQL 的  那样)。平时只要追加操作记录即可,各时刻的状态永远通过重放之前的操作记录得出,这样就彻底避免了状态的错乱。这个办法看起来古怪,但我真的在之前的开发中用过(当然是在程序生命周期有限的场景下),而且真的从没出过错。
  坦白说,看完 U B 的书,我心里好过点儿了。因为我发现,我们这些这些老程序员的知识其实没有过时,如今不少光鲜的架构,其实骨子里还是那些古老的问题。只是多亏了 U B 的妙手点拨,我才能穿越时空,享受到重新发现智慧的愉悦。

  当然,架构设计是一门复杂的学问,要综合考虑编码、质量、部署、发布、运维、排障、升级等等各种因素,作出权衡。好消息是,U B 的这本书覆盖广泛,各方面都有涉及,认真读完全书一定会有不小的收获。唯一的问题是,你要适应这个老程序员的口味和节奏:他当然也会拿如今流行的打车系统做例子,但他更熟悉的,还是链接器、C语言、UML 图等等。这些玩意儿对如今的程序员来说,确实透出一股古老的感觉。
  不过我觉得,这都不是大问题。看得出类之间的依赖关系不合理,自然容易发现子系统之间的依赖不合理;搞得懂 U 如何巧妙定义通用的 IO 设备,自然容易想到对 PC W、M W、A 内页面做合适抽象;认得清各线程、进程、链接库的职责,自然容易明白微服务也需要避免跨边界调用。更妙的是,掌握这种古老的视角,往往更能摆脱细节的困扰,把握问题的核心。就像老子说的那样:治大国如烹小鲜。
  噢,对了,治大国如烹小鲜,这也是久远的教诲,也包含着古老的智慧。
  这是我为《架构整洁之道》写的推荐序。 2018/12/6 21:30:43
回复

使用道具 举报

发表回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则