一贯以来,设计(Design)和架构(Architecture)这两个观点让大多数人十分迷惑--什么是设计?什么是架构?二者究竟有什么差异?二者没有差异。
一丁点差异都没有!
"架构"这个词每每适用于"高层级"的谈论中,这类谈论一样平常都把"底层"的实现细节打消在外。
而"设计"一词,每每用来指代详细的系统底层组织构造和实现的细节。
但是,从一个真正的系统架构师的日常事情来看,这些区分是根本不成立的。
以给我设计新居子的建筑设计师要做的事情为例。
新居子当然是存在着既定架构的,但这个架构详细包含哪些内容呢?首先,它该当包括房屋的形状、外不雅观设计、垂直高度、房间的布局,等等。

但是,如果查看建筑设计师利用的图纸,会创造个中也充斥着大量的设计细节。
譬如,我们可以看到每个插座、开关以及每个电灯详细的安装位置,同时也可以看到某个开关与所掌握的电灯的详细连接信息;我们也能看到壁炉的详细位置,热水器的大小和位置信息,乃至是污水泵的位置;同时也可以看到关于墙体、屋顶和地基所有非常详细的建造解释。
总的来说,架构图里实际上包含了所有的底层设计细节,这些细节信息共同支撑了顶层的架构设计,底层设计信息和顶层架构设计共同组成了全体房屋的架构文档。

软件设计也是如此。
底层设计细节和高层架构信息是不可分割的。
他们组合在一起,共同定义了全体软件系统,缺一不可。
所谓的底层和高层本身便是一系列决策组成的连续体,并没有清晰的分边界。

android和php开发最佳实践法式员进阶指南文档团队Golang最佳实践和CR案例集分享 SQL

我们编写、review 细节代码,便是在做架构设计的一部分。
我们编写的细节代码构成了全体系统。
我们就该当在细节 review 中,总是带着所有架构原则去核阅。
你会创造,你已经写下了无数让整体变得丑陋的细节,它们背后,都有古人总结过的架构原则。

把代码和文档绑在一起(自阐明原则)

写文档是个好习气。
但是写一个别人须要咨询老开拓者才能找到的文档,是个坏习气。
这个坏习气乃至会给工程师们带来侵害。
比如,当初始开拓者写的文档在一个犄角旮旯(在 wiki 里,但是阅读代码的时候没有在明显的位置看到链接),后续代码被修正了,文档已经由时,有人再找出文档来获取到过期、缺点的知识的时候,阅读文档这个同学的开拓效率一定受到侵害。
以是,犹如 golang 的 godoc 工具能把代码里'按规范来'的注释自动天生一个文档页面一样,我们该当:

按照 godoc 的哀求好好写代码的注释。
代码首先要自阐明,当阐明不了的时候,须要就近、合理地写注释。
当小段的注释不能阐明清楚的时候,该当有 doc.go 来阐明,或者,在同级目录的 ReadMe.md 里注释讲解。
文档须要强大的富文本编辑能力,Down 无法知足,可以写到 wiki 里,同时必须把 wiki 的大略描述和链接放在代码里得当的位置。
让阅读和掩护代码的同学一眼就看到,能做到及时的掩护。

以上,总结起来便是,阐明信息必须离被阐明的东西,越近越好。
代码能做到自阐明,是最棒的。

让目录构造自阐明

ETC 代价不雅观(easy to change)

ETC 是一种代价不雅观念,不是一条原则。
代价不雅观念是帮助你做决定的: 我该当做这个,还是做那个?当你在软件领域思考时,ETC 是个引导,它能帮助你在不同的路线中选出一条。
就像其他一些代价不雅观念一样,你该当让它漂浮在意识思维之下,让它奇妙地将你推向精确的方向。

敏捷软件工程,所谓敏捷,便是要能快速变更,并且在变更中保持代码的质量。
以是,持有 ETC 代价不雅观看待代码细节、技能方案,我们将能更好地编写出适宜敏捷项目的代码。
这是一个大的代价不雅观,不是一个根本微不雅观的原则,以是没有例子。
本文提到的所有原则,或者接,或间接,都要为 ETC 做事。

DRY 原则(don not repeat yourself)

在《Code Review 我都 CR 些什么》里面,我已经就 DRY 原则做了深入阐述,这里不再赘述。
我认为 DRY 原则是编码原则中最主要的编码原则,没有之一(ETC 是个不雅观念)。
不要重复!
不要重复!
不要重复!

正交性原则(全局变量的危害)

'正交性'是几何学中的术语。
我们的代码该当肃清不干系事物之间的影响。
这是一给大略的道理。
我们写代码要'高内聚、低耦合',这是大家都在提的。

但是,你有为了利用某个 class 一堆能力中的某个能力而去派生它么?你有写过一个 helper 工具,它什么都做么?在腾讯,我相信你是做过的。
你自己说,你这是不是为了复用一点点代码,而让两大块乃至多块代码耦合在一起,不再正交了?大家可能并不是不明白正交性的代价,只是不知道怎么去正交。
手段有很多,但是首先我就要批驳一下 OOP。
它的核心是多态,多态须要通过派生/继续来实现。
继续树一旦写出来,就变得很难 change,你不得不为了利用一小段代码而去做继续,让代码耦合。

你该当多利用组合,而不是继续。
以及,该当多利用 DIP(Dependence Inversion Principle),依赖颠倒原则。
换个说法,便是面向 interface 编程,面向左券编程,面向切面编程,他们都是 DIP 的一种衍生。
写 golang 的同学就更不陌生了,我们要把一个 struct 作为一个 interface 来利用,不须要显式 implement/extend,仅仅须要持有对应 interface 定义了的函数。
这种 duck interface 的做法,让 DIP 来得更大略。
AB 两个模块可以独立编码,他们仅仅须要一个依赖一个 interface 署名,一个刚好实现该 interface 署名。
并不须要显式知道对方 interface 署名的两个模块就可以在须要的模块、场景下被组合起来利用。
代码在须要被组合利用的时候才产生了一点关系,同时,它们依然保持着独立。

说个正交性的范例案例。
全局变量是不正交的!
没有充分的情由,禁止利用全局变量。
全局变量让依赖了该全局变量的代码段相互耦合,不再正交。
特殊是一个 pkg 供应一个全局变量给其他模块修正,这个做法会让 pkg 之间的耦合变得繁芜、隐秘、难以定位。

全局 map case

单例便是全局变量

这个不须要我阐明,大家自己品一品。
后面有'共享状态便是禁绝确的状态'原则,会进一步讲到。
我先给出办理方案,可以通过管道、机制来替代共享状态/利用全局变量/利用单例。
仅仅能获取此刻最新的状态,通过变更状态。
要拿到最新的状态,须要重新获取。
在必要的时候,引入锁机制。

可逆性原则

可逆性原则是很少被提及的一个原则。
可逆性,便是你做出的判断,最好都是可以被逆转的。
再换一个随意马虎懂的说法,你最好只管即便少认为什么东西是一定的、不变的。
比如,你认为你的系统永久做事于,用 32 位无符号整数(比如 QQ 号)作为用户标识的系统。
你认为,你的持久化存储,就选型 SQL 存储了。
当这些一开始你认为一定的东西,被推翻的时候,你的代码却很难去 change,那么,你的代码便是可逆性做得很差。
书里有一个例证,我以为很好,直接引用过来。

与其认为决定是被刻在石头上的,还不如把它们想像成写在沙滩的沙子上。
一个大浪随时都可能袭来,卷走统统。
腾讯也确实在 20 年内经历了'大铁块'到'云虚拟机换成容器'的几个阶段。
几次变革都是伤筋动骨,摧残浪费蹂躏大量的韶光。
乃至总会有一些上一个时期残留的做事。
就机器数量而论,还不小。
一到裁撤季,就很难熬痛苦。
就最近,我看到某个 trpc 插件,直接从环境变量里读取本机 IP,仅仅由于 STKE(Tencent Kubernetes Engine)供应了这个能力。
这个细节设计便是不可逆的,将来会有人为它买单,可能价格还不便宜。

我本日才想起一个事儿。
当年 SNG 的很多部门对付 metrics 监控的利用。
就潜意识地认为,我们将一贯利用'模块间调用监控'组件。
利用它的 API 是直接把上报通道 DCLog 的 API 袒露在业务代码里的。
本日(2020.12.01),该组件该当已经完备没有人掩护、完备下线了,这些核心业务代码要怎么办?有人能对它做出修正么?那,这些部门现在还有 metrics 监控么?答案,可能是悲观的。
有人已经已经尝到了可逆性之痛。

依赖颠倒原则(DIP)

DIP 原则太主要了,我这里单独列一节来讲解。
我这里只是大略的讲解,讲解它最原始和大略的形态。
依赖颠倒原则,全称是 Dependence Inversion Principle,简称 DIP。
考虑下面这几段代码:

packagedippackagediptypeBottoninterface{TurnOn()TurnOff()}typeUIstruct{bottonBotton}funcNewUI(bBotton)UI{return&UI{botton:b}}func(uUI)Poll(){u.botton.TurnOn()u.botton.TurnOff()u.botton.TurnOn()}

packagejavaimplimport"fmt"typeLampstruct{}funcNewLamp()Lamp{return&Lamp{}}func(Lamp)TurnOn(){fmt.Println("turnonjavalamp")}func(Lamp)TurnOff(){fmt.Println("turnoffjavalamp")}

packagepythonimplimport"fmt"typeLampstruct{}funcNewLamp()Lamp{return&Lamp{}}func(Lamp)TurnOn(){fmt.Println("turnonpythonlamp")}func(Lamp)TurnOff(){fmt.Println("turnoffpythonlamp")}

packagemainimport("javaimpl""pythonimpl""dip")funcrunPoll(bdip.Botton){ui:=NewUI(b)ui.Poll()}funcmain(){runPoll(pythonimpl.NewLamp())runPoll(javaimpl.NewLamp())}

看代码,main pkg 里的 runPoll 函数仅仅面向 Botton interface 编码,main pkg 不再关心 Botton interface 里定义的 TurnOn、TurnOff 的实现细节。
实现理解耦。
这里,我们能看到 struct UI 须要被注入(inject)一个 Botton interface 才能逻辑完全。
以是,DIP 常常换一个名字涌现,叫做依赖注入(Dependency Injection)。

从这个依赖图不雅观察。
我们创造,一样平常来说,UI struct 的实现是要该当依赖于详细的 pythonLamp、javaLamp、其他各种 Lamp,才能让自己的逻辑完全。
那便是 UI struct 依赖于各种 Lamp 的实现,才能逻辑完全。
但是,我们看上面的代码,却是反过来了。
pythonLamp、javaLamp、其他各种 Lamp 是依赖 Botton interface 的定义,才能用来和 UI struct 组合起来拼接成完全的业务逻辑。
变成了,Lamp 的实现细节,依赖于 UI struct 对付 Botton interface 的定义。
这个时候,你创造,这种依赖关系被颠倒了!
依赖颠倒原则里的'颠倒',便是这么来的。
在 golang 里,'pythonLamp、javaLamp、其他各种 Lamp 是依赖 Botton interface 的定义',这个依赖是隐性的,没有显式的 implement 和 extend 关键字。
代码层面,pkg dip 和 pkg pythonimpl、javaimpl 没有任何依赖关系。
他们仅仅须要被你在 main pkg 里组合起来利用。

在 J2EE 里,用户的业务逻辑不再依赖低详细低层的各种存储细节,而仅仅依赖一套配置化的 Java Bean 接口。
Object 落地存储的详细细节,被做成了 Java Bean 配置,注入到框架里。
这便是 J2EE 的核心科技,并不繁芜,实在也没有多么'高不可攀'。
反而,在'动态代码'优于'配置'的本日,这种通过配置实现的依赖注入,反而有点过期了。

将知识用纯文本来保存

这也是一个生僻的原则。
指代码操作的数据和方案设计文稿,如果没有充分的必要利用特定的方案,就该当利用人类可读的文本来保存、交互。
对付方案设计文稿,你能不该用 office 格式,就不该用(office 能极大提升效率,才用),最好是原始 text。
这是《Unix 编程艺术》也提到了的 Unix 系产生的设计信条。
简而言之一句话,当须要确保有一个所有各方都能利用的公共标准,才能实现交互沟通时,纯文本便是这个标准。
它是一个接管度最高的通畅标准。
如果没有必要的情由,我们就该当利用纯文本。

左券式设计

如果你对左券式设计(Design by Contract, DBC)还很陌生,我相信,你和其他真个同学(web、client、后端)联调需求该当是一件很花费韶光的事情。
你自己编写接口自动化,也会是一件很耗费精力的事情。
你先看看它的wiki 阐明吧。
grpc + grpc-gateway + swagger 是个很喷鼻香的东西。

代码是否不多不少刚好完成它流传宣传要做的事情,可以利用左券加以校验和文档化。
TDD 便是全程在不断调度和履行着左券。
TDD(Test-Driven Development)是自底向上地编码过程,实在会耗费大量的精力,并且对付一个良好的层级架构没有帮助。
TDD 不是强推的规范,但是同学们可以用一用,感想熏染一下。
TDD 方法论实现的接口、函数,自我阐明能力一样平常来说比较强,由于它便是一个实现左券的过程。

抛开 TDD 不谈。
我们的函数、api,你能快速捉住它描述的核心左券么?它的左券大略么?如果不能、不大略,那你该当哀求被 review 的代码做出调度。
如果你在辅导一个子弟,你该当帮他思考一下,给出至少一个可能的简化、拆解方向。

尽早崩溃

Erlang 和 Elixir 措辞信奉这种哲学。
乔-阿姆斯特朗,Erlang 的发明者,《Erlang 程序设计》的作者,有一句反复被引用的话: "防御式编程是在摧残浪费蹂躏韶光,让它崩溃"。

尽早崩溃不是说不容错,而是程序该当被设计成许可出故障,有适当的故障监管程序和代码,及时告警,奉告工程师,哪里出问题了,而不是考试测验粉饰问题,不让程序员知道。
当末了程序员知道程序出故障的时候,已经找不到问题涌如今哪里了。

特殊是一些 recover 之后什么都不做的代码,这种代码切实其实是毒瘤!
当然,崩溃,可以是早一些向上通报 error,不一定便是 panic。
同时,我哀求大家不要在没有充分的必要性的时候 panic,该当更多地利用向上通报 error,做好 metrics 监控。
合格的 golang 程序员,都不会在没有必要的时候忽略 error,会妥善地做好 error 处理、向上通报、监控。
一个去世掉的程序,常日比一个瘫痪的程序,造成的危害要小得多。

崩溃但是不告警,或者没有补救的办法,不可取.尽早崩溃的题外话是,要在问题涌现的时候做合理的告警,有预案,不能粉饰,不能没有预案:

解耦代码让改变随意马虎

这个原则,显而易见,大家自己也常常提,其他原则或多或少都和它有关系。
但是我也再提一提。
我紧张是描述一下它的症状,让同学们更好地警示自己'我这两块代码是不是耦合太重,须要额外引入解耦的设计了'。
症状如下:

不干系的 pkg 之间古怪的依赖关系对一个模块进行的'大略'修正,会传播到系统中不干系的模块里,或是毁坏了系统中的其他部分开拓职员害怕修正代码,由于他们不愿定会造成什么影响会议哀求每个人都必须参加,由于没有人能确定谁会受到变革的影响只管命令不要讯问

看看如下三段代码:

funcapplyDiscount(customerCustomer,orderIDstring,discountfloat32){customer.Orders.Find(orderID).GetTotals().ApplyDiscount(discount)}

funcapplyDiscount(customerCustomer,orderIDstring,discountfloat32){customer.FindOrder(orderID).GetTotals().ApplyDiscount(discount)}

funcapplyDiscount(customerCustomer,orderIDstring,discountfloat32){customer.FindOrder(orderID).ApplyDiscount(discount)}

明显,末了一段代码最简洁。
不关心 Orders 成员、总价的存在,直接命令 customer 找到 Order 并对其进行打折。
当我们调度 Orders 成员、GetTotals()方法的时候,这段代码不用修正。
还有一种更吓人的写法:

funcapplyDiscount(customerCustomer,orderIDstring,discountfloat32){total:=customer.FindOrder(orderID).GetTotals()customer.FindOrder(orderID).SetTotal(totaldiscount)}

它做了更多的查询,关心了更多的细节,变得更加 hard to change 了。
我相信,大家写过类似的代码也不少。
特殊是客户端同学。

最好的那一段代码,便是只管给每个 struct 发送命令,哀求大家干事儿。
怎么做,就内聚在和 struct 关联的方法里,其他人不要去操心。
一旦其他人操心了,当须要做修正的时候,就要操心了这个细节的人都一起参与学习正过程。

不要链式调用方法

看下面的例子:

funcamount(customerCustomer)float32{returncustomer.Orders.Last().Totals().Amount}

funcamount(totalsTotals)float32{returntotals.Amount}

第二个例子明显优于第一个,它变得更大略、通用、ETC。
我们该当给函数传入它关心的最小凑集作为参数。
而不是,我有一个 struct,当某个函数须要这个 struct 的成员的时候,我们把全体 struct 都作为参数通报进去。
该当仅仅通报函数关心的最小凑集。
传进去的一整条调用链对函数来说,都是无关的耦合,只会让代码更 hard to change,让工程师畏惧去修正。
这一条原则,和上一条关系很紧密,问题常常同时涌现。
还是,特殊是在客户端代码里。

继续税(多用组合)

继续便是耦合。
不仅子类耦合到父类,以及父类的父类等,而且利用子类的代码也耦合到所有先人类。
有些人认为继续是定义新类型的一种办法。
他们喜好设计图表,会展示出类的层次构造。
他们看待问题的办法,与维多利亚时期的名流科学家们看待自然的办法是一样的,即将自然视为须分解到不同类别的综合体。
不幸的是,这些图表很快就会为了表示类之间的细微差别而逐层添加,终极恐怖地爬满墙壁。
由此增加的繁芜性,可能使运用程序更加薄弱,由于变更可能在许多层次之间高下颠簸。
由于一些值得商榷的词义消歧方面的缘故原由,C++在20世纪90年代玷污了多重继续的名声。
结果,许多当下的OO措辞都没有供应这种功能。

因此,纵然你很喜好繁芜的类型树,也完备无法为你的领域准确地建模。

Java 下统统都是类。
C++里不该用类还不如利用 C。
写 Python、PHP,我们也肯定要时髦地写一些类。
写类可以,当你要去继续,你就得考虑清楚了。
继续树一旦形成,就是非常 hard to change 的,在敏捷项目里,你要想清楚'代价是什么',有必要么?这个设计'可逆'么?对付边界清晰的 UI 框架、游戏引擎,利用繁芜的继续树,挺好的。
对付 UI 逻辑、后台逻辑,可能,你仅仅须要组合、DIP(依赖反转)技能、左券式编程(接口与协议)就够了。
写出继续树不是'就该当这么做',它是本钱,继续是要收税的!

在 golang 下,继续税的烦恼被减轻了,golang 从来说自己不是 OO 的措辞,但是你 OO 的事情,我都能轻松地做到。
更进一步,OO 和过程式编程的差异到底是什么?

面向过程,面向工具,函数式编程。
三种编程构造的核心差异,是在不同的方向限定程序员,来做到好的代码构造(引自《架构整洁之道》):

构造化编程是对程序掌握权的直接转移的限定。
面向工具是对程序掌握权的间接转移的限定。
函数式编程是对程序中赋值操作的限定。

SOLID 原则(单一功能、开闭原则、里氏更换、接口隔离、依赖反转,后面会讲到)是 OOP 编程的最经典的原则。
个中 D 是指依赖颠倒原则(Dependence Inversion Principle),我认为,是 SOLID 里最主要的原则。
J2EE 的 container 便是环绕 DIP 原则设计的。
DIP 能用于避免构建繁芜的继续树,DIP 便是'限定掌握权的间接转移'能连续发挥积极浸染的最大保障。
合理利用 DIP 的 OOP 代码才可能是高质量的代码。

golang 的 interface 是 duck interface,把 DIP 原则更进一步,不须要显式 implement/extend interface,就能做到 DIP。
golang 利用构造化编程范式,却有面向工具编程范式的核心优点,乃至简化了。
这是一个基于高度抽象理解的极度风雅的设计。
google 把 abstraction 这个设计理念发挥到了极致。
曾经,J2EE 的 container(EJB, Java Bean)设计是海内 Java 程序员引以为傲'架构设计'、'厉害的设计'。

在 golang 里,它被剖析、解构,以更大略、灵巧、统一、易懂的办法呈现出来。
写了多年垃圾 C++代码的腾讯后端工程师们,是你们再次核阅 OOP 的时候了。
我大学一年级的时候看的 C++教材,终归给我描述了一个美好却无法抵达的天下。
目标我没有放弃,但我不再用 OOP,而是更多地利用组合(Mixin)。
写 golang 的同学,该当对 DIP 和组合都不陌生,这里我不再赘述。
如果有人自傲地说他在 golang 下搞起了继续,我只能说,'同道,你现在站在了广大 gopher 的对立面'。
现在,你站在哲学的云端,鸟瞰了 Structured Programming 和 OOP。
你还乐意再连续支付继续税么?

共享状态是禁绝确的状态

你坐在最喜好的餐厅。
吃完主菜,问男做事员还有没有苹果派。
他转头一看-陈设柜里还有一个,就见告你"还有"。
点到了苹果派,你心满意足地长出了一口气。
与此同时,在餐厅的另一边,还有一个顾客也问了女做事员同样的问题。
她也看了看,确认有一个,让顾客点了单。
总有一个顾客会失落望的。

问题出在共享状态。
餐厅里的每一个做事员都查看了陈设柜,却没有考虑到其他做事员。
你们可以通过加互斥锁来办理精确性的问题,但是,两个顾客有一个会失落望或者良久都得不到答案,这是肯定的。

所谓共享状态,换个说法,便是: 由多个人查看和修正状态。
这么一说,更好的办理方案就浮出水面了: 将状态改为集中掌握。
预定苹果派,不再是先查询,再下单。
而是有一个餐厅经理卖力和做事员沟通,做事员只管发送下单的命令/,经理看情形能不能知足做事员的命令。

这种办理方案,换一个说法,也可以说成"用角色实现并发性时不必共享状态"。
对,上面,我们引入了餐厅经理这个角色,授予了他职责。
当然,我们仅仅该当给这个角色发送命令,不应该去讯问他。
前面讲过了,'只管命令不要讯问',你还记得么。

同时,这个原则便是 golang 里大家耳熟能详的谚语: "不要通过共享内存来通信,而该当通过通信来共享内存"。
作为并发性问题的根源,内存的共享备受关注。
但实际上,在运用程序代码共享可变资源(文件、数据库、外部做事)的任何地方,问题都有可能冒出来。
当代码的两个或多个实例可以同时访问某些资源时,就会涌现潜在的问题。

缄默原则

如果一个程序没什么好说,就保持沉默。
过多的正平日记,会粉饰缺点信息。
过多的信息,会让人根本不再关注新涌现的信息,'更多信息'变成了'没有信息'。
每人添加一点信息,就变成了输出很多信息,末了即是没有任何信息。

不要在正常 case 下打印日志。
不要在单元测试里利用 fmt 标准输出,至少不要提交到 master。
不打不必要的日志。
当缺点涌现的时候,会非常明显,我们能第一韶光反应过来并处理。
让调试的日志勾留在调试阶段,或者利用较低的日志级别,你的调试信息,对其他人根本没有代价。
纵然低级别日志,也不能泛滥。
不然,日志打开与否都没有差别,日志变得毫无代价。

缄默

缺点通报原则

我不喜好 Java 和 C++的 exception 特性,它随意马虎被滥用,它具有传染性(如果代码 throw 了 excepttion, 你就得 handle 它,不 handle 它,你就崩溃了。
可能你不肯望崩溃,你仅仅希望报警)。
但是 exception(在 golang 下是 panic)是有代价的,参考微软的文章:

ExceptionsarepreferredinmodernC++forthefollowingreasons:Anexceptionforcescallingcodetorecognizeanerrorconditionandhandleit.Unhandledexceptionsstopprogramexecution.Anexceptionjumpstothepointinthecallstackthatcanhandletheerror.Intermediatefunctionscanlettheexceptionpropagate.Theydon'thavetocoordinatewithotherlayers.Theexceptionstack-unwindingmechanismdestroysallobjectsinscopeafteranexceptionisthrown,accordingtowell-definedrules.Anexceptionenablesacleanseparationbetweenthecodethatdetectstheerrorandthecodethathandlestheerror.

Google 的 C++规范在常规情形禁用 exception,情由包含如下内容:

BecausemostexistingC++codeatGoogleisnotpreparedtodealwithexceptions,itiscomparativelydifficulttoadoptnewcodethatgeneratesexceptions.

从 google 和微软的文章中,我们不难总结出以下几点衍生的结论:

在必要的时候抛出 exception。
利用者必须具备'必要性'的判断能力。
exception 能一起把底层的非常往上通报到高函数层级,信息被向上通报,并且在上级被妥善处理。
可以让非常和关心详细非常的处理函数在高层级和低层级遥相呼应,中间层级什么都不须要做,仅仅向上通报。
exception 传染性很强。
当代码由多人协作,利用 A 模块的代码都必须要理解它可能抛出的非常,做出合理的处理。
不然,就都写一个丑陋的 catch,catch 所有非常,然后做一个没有针对性的处理。
每次 catch 都须要加深一个代码层级,代码常常写得很丑。

我们看到了非常的优缺陷。
上面第二点提到的信息通报,是很有代价的一点。
golang 在 1.13 版本中拓展了标准库,支持了Error Wrapping也是承认了 error 通报的代价。

以是,我们认为缺点处理,该当具备跨层级的缺点信息通报能力,中间层级如果不关心,就把 error 加上本层的信息向上透传(有时候可以直接透传),该当利用 Error Wrapping。
exception/panic 具有传染性。
大量利用,会让代码变得丑陋,同时随意马虎滋长可读性问题。
我们该当多利用 Error Wrapping,在必要的时候,才利用 exception/panic。
每一次利用 exception/panic,都该当被负责审核。
须要 panic 的地方,不去 panic,也是有问题的。
参考本文的'尽早崩溃'。

额外说一点,把稳不要把全体链路的缺点信息带到公司外,带到用户的浏览器、native 客户端。
至少不能直接展示给用户看到。

缺点链

SOLID

SOLID 原则,是由以下几个原则的凑集体:

SRP: 单一职责原则OCP: 开闭原则LSP: 里氏更换原则ISP: 接口隔离原则DIP: 依赖反转原则

这些年来,这几个设计原则在很多不同的出版物里都有过详细描述。
它们太出名了,我这里就不更多地做详解了。
我这里想说的是,这 5 个原则环环相扣,前 4 个原则,要么便是同时做到,要么便是都没做到,很少有说,做到个中一点其他三点都不知足。
ISP 便是做到 LSP 的常用手段。
ISP 也是做到 DIP 的根本。
只是,它刚被提出来的时候,是紧张针对'设计继续树'这个目的的。
现在,它们已经被更广泛地利用在模块、领域、组件这种更大的观点上。

SOLI 都显而易见,DIP 原则是最值得把稳的一点,我在其他原则里也多次提到了它。
如果你还不清楚什么是 DIP,一定去看明白。
这是工程师最根本、必备的知识点之一了。

要做到 OCP 开闭原则,实在,便是要大家要通过后面讲到的'不要面向需求编程'才能做好。
如果你还是面向需求、面向 UI、交互编程,你永久做不到开闭,并且不知道如何才能做到开闭。

如果你对这些原则确实不理解,建议读一读《架构整洁之道》。
该书的作者 Bob 大叔,便是第一个提出 SOLID 这个凑集体的人(20 世纪 80 年代末,在 USENET 新闻组)。

一个函数不要涌现多个层级的代码

//IrisFriends拉取好友funcIrisFriends(ctxiris.Context,appapp.App){varrspsdc.FriendsRspdeferfunc(){varbufbytes.Buffer_=(&jsonpb.Marshaler{EmitDefaults:true}).Marshal(&buf,&rsp)_,_=ctx.Write(buf.Bytes())}()common.AdjustCookie(ctx)if!checkCookie(ctx){return}//从cookie中拿到关键的上岸态等有效信息varsessioncommon.BaseSessioncommon.GetBaseSessionFromCookie(ctx,&session)//校验上岸态err:=common.CheckLoginSig(session,app.ConfigStore.Get().OIDBCmdSetting.PTLogin)iferr!=nil{_=common.ErrorResponse(ctx,errors.PTSigErr,0,"checkloginsigerror")return}iferr=getRelationship(ctx,app.ConfigStore.Get().OIDBCmdSetting,NewAPI(),&rsp);err!=nil{//TODO:日志}return}

上面这一段代码,是我随意找的一段代码。
逻辑非常清晰,由于除了最上面 defer 写回包的代码,其他部分都是顶层函数组合出来的。
阅读代码,我们不会掉到细节里出不来,反而忽略了全体业务流程。
同时,我们能明显创造它没写完,以及 common.ErrorResponse 和 defer func 两个地方都写了回包,可能涌现发起两次 http 回包。
TODO 也会非常显眼。

想象一下,我们没有把细节收归进 checkCookie()、getRelationship()等函数,而是展开在这里,但是总函数行数没有到 80 行,表面上符合规范。
但是实际上,阅读代码的同学不再能轻松节制业务逻辑,而是同时在阅读功能细节和业务流程。
阅读代码变成了每个时候心智包袱都很重的事情。

显而易见,单个函数里该当只保留某一个层级(layer)的代码,更细化的细节该当被抽象到下一个 layer 去,成为子函数。

Unix 哲学根本

《Code Review 我都 CR 些什么》讲解了很多 Unix 的设计哲学。
这里不再赘述,仅仅列举一下。
大家自行阅读和参悟,并且利用到编码、review 活动中。

模块原则: 利用简洁的接口拼合大略的部件清晰原则: 清晰胜于技巧组合原则: 设计时考虑拼接组合分离原则: 策略同机制分离,接口同引擎分离简洁原则: 设计要简洁,繁芜度能低则低吝啬原则: 除非确无它法,不要编写弘大的程序透明性原则: 设计要可见,以便审查和调试健壮原则: 健壮源于透明与简洁表示原则: 把知识叠入数据以求逻辑朴实而健壮普通原则: 接口设计避免标新创新缄默原则: 如果一个程序没什么好说,就保持沉默补救原则: 涌现非常时,立时退出并给出足量缺点信息经济原则: 宁花机器一分,不花程序员一秒天生原则: 避免手工 hack,只管即便编写程序去天生程序优化原则: 雕琢前先得有原型,跑之前先学会走多样原则: 绝不相信所谓"不二法门"的断言扩展原则: 设计着眼未来,未来总比预想快工程师的自我教化

下面,是一些在 review 细节中不能直策应用的原则。
更像是一种信念和自我约束。
带着这些信念去编写、review 代码,把这些信念在实践中通报下去,将是极有代价的。

偏执

对代码细节偏执的不雅观念,是我自己提出的新不雅观点。
在当下研发质量不高的腾讯,是很有必要普遍存在的一个不雅观念。
在一个别系不完善、韶光安排荒谬、工具可笑、需求不可能实现的天下里,让我们安全行事吧。
就像伍迪-艾伦说的:"当所有人都真的在给你找麻烦的时候,偏执便是一个好主张。
"

对付一个方案,一个实现,请不要说出"彷佛这样也可以"。
你一定要选出一个更好的做法,并且一贯坚持这个做法,并且哀求别人也这样做。
既然他来让你 review 了,你就要有自己的偏执,你一定要他按照你以为得当的办法去做。
当然,你得有说服得了自己,也说服得了他人的情由。
纵然,只有一点点。
偏执会让你的天下变得大略,你的团队的协作变得大略。
特殊当你身处一个编码质量低下的团队的时候。
你至少能说,我是一个务实的程序员。

掌握软件的熵是软件工程的主要任务之一

熵是个物理学观点,大家可能看过诺兰的电影《信条》。
大略来说,熵可以理解为'混乱程度'。
我们的项目,在刚开始的几千行代码,是很简洁的。
但是,为什么到了 100w 行,我们常常就觉得'太繁芜了'?比如 QQ 客户端,最近终于在做大面积重构,但是创造无数 crash。
个中一个主要缘故原由,便是'混乱程度'太高了。
'混乱程度',理解起来还是比较抽象,它有很多其他名字。
'hard code 很多'、'分外逻辑很多'、'定制化逻辑很多'。
再换另一个抽象的说法,'我们面对一类问题,采纳了过多的范式和分外逻辑细节去实现它'。

熵,是一点点堆叠起来的,在一个需求的 2000 行代码变动中,你可能就引入了一个不同的范式,冲破了之前的通用范式。
在微不雅观来看,你以为你的代码是'整洁干净'的。
就像一个已经穿着好看的赤色风衣的人,你隔一天让他接着穿上一条绿色的裤子,这还干净整洁么?熵,在不断增加,我们须要做到以下几点,不然你的团队将在希望通过重构来降落项目的熵的时候尝到恶果,乃至放弃重构,让熵不断增长下去。

如果没有充分的情由,始终利用项目规范的范式对每一类问题做出办理方案。
如果业务发展创造老的办理方案不再精良,做整体重构。
项目级主干开拓,对重构很友好,让重构变得可行。
(客户端很随意马虎实现主干开拓)。
务实地讲,重构已经不可能了。
那么,你们可以谨慎地提出新的一整套范式。
重修它。
禁止 hardcode,分外逻辑。
如果你创造分外逻辑随意马虎实现需求,否则很难。
那么,你的架构已经涌现问题了,你和你的团队该当深入思考这个问题,而不是轻易加上一个分外逻辑。

为测试做设计

现在我们在做'测试左移',让工程师编写自动化测试来担保质量。
测试工程师的事情更多的是类似 google SET(Software Engineer In Test, 参考《google 软件测试之道》)的事情。
事情重心在于测试编码规范、测试编码流程、测试编码工具、测试平台的思考和培植。
测试代码,还是得工程师来做。

为方法写一个测试的考虑过程,使我们得以从外部看待这个方法,这让我们看起来是代码的客户,而不是代码的作者。
很多同学,就觉得很难熬痛苦。
对,这是一定的。
由于你的代码设计的时候,并没有把'随意马虎测试'考虑进去,可测试性不强。
如果工程师在开拓逻辑的过程中,就同时思考着这段代码若何才能轻松地被测试。
那么,这段写就的代码,同时可读性、大略性都会得到保障,经由了良好的设计,而不仅仅是'能事情'。

我以为,测试得到的紧张好处发生在你考虑测试及编写测试的时候,而不是在运行测试的时候!
在编码的时候同时让思考怎么测试的思维存在,会让编写高质量的代码变得大略,在编码时就更多地考虑边界条件、非常条件,并且妥善处理。
仅仅是抱有这个思维,不去真地编写自动化测试,就能让代码的质量上升,代码架构的能力得到提升。

硬件工程出 bug 很难查,bug 造成的本钱很高,每次都要重新做一套模具、做模具的工具。
以是硬件工程每每有层层测试,极早创造问题,只管即便担保大略且质量高。
我们可以在软件上做同样的事情。
与硬件工程师一样,从一开始就在软件中构建可测试性,并且考试测验将每个部分连接在一起之前,对他们进行彻底的测试。

这个时候,有人就说,TDD 便是这样,让你同时思考编码架构和测试架构。
我对 TDD 的态度是: 它不一定便是最好的。
测试对开拓的驱动,绝对有帮助。
但是,就像每次驱动汽车一样,除非心里有一个目的地,否则就可能会兜圈子。
TDD 是一种自底向上的编程方法。
但是,适当的时候利用自顶向下设计,才能得到一个最好的整体架构。
很多人处理不好自顶向下和自底向上的关系,结果在利用 TDD 的时候创造举步维艰、奏效甚微。

以及,如果没有强大的外部驱动力,"往后再测"实际上意味着"永久不测"。
大家,务实一点,在编码时就考虑怎么测试。
不然,你永久没有机会考虑了。
当面对着测试性低的代码,须要编写自动化测试的时候,你会觉得很难熬痛苦。

尽早测试, 常常测试, 自动测试

一旦代码写出来,就要尽早开始测试。
这些小鱼的恶心之处在于,它们很快就会变成巨大的食人鲨,而捕捉鲨鱼则相称困难。
以是我们要写单元测试,写很多单元测试。

事实上,好项目的测试代码可能会比产品代码更多。
天生这些测试代码所花费的韶光是值得的。
从长远来看,终极的本钱会低得多,而且你实际上有机会生产出险些没有缺陷的产品。

其余,知道通过了测试,可以让你对代码已经"完成"产生高度信心。

项目中利用统一的术语

如果用户和开拓者利用不同的名称来称呼相同的事物,或者更糟糕的是,利用相同的名称来代指不同的事物,那么项目就很难取获胜利。

DDD(Domain-Driven Design)把'项目中利用统一的术语'做到了极致,哀求项目把目标系统分解为不同的领域(也可以称作高下文)。
在不同的高下文中,同一个术语名字意义可能不同,但是要项目内统一认识。
比如证券这个词,是个多种经济权柄凭据的统称,在股票、债券、权证市场,意义和规则是完备不同的。
当你第一次听说'涡轮(港股特有金融衍生品,是一种股权)'的时候,是不是瞬间蒙圈,搞不清它和证券的关系了。
买'涡轮'是在买什么鬼证劵?

在软件领域是一样的。
你须要对股票、债券、权证市场建模,你就得有不同的领域,在每个领域里有一套词汇表(实体、值工具),在不同的领域之间,同一个观点可能会换一个名字,须要映射。
如果你们既不区分领域,乃至在同一个领域还对同一个实体给出不同的名字。
那,你们怎么确保自己沟通到位了?写成代码,别人如何知道你现在写的'证券'这个 struct 详细是指的什么?

不要面向需求编程

需求不是架构;需求无关设计,也非用户界面;需求便是须要的东西。
须要的东西是常常变革的,是不断被探索,不断被加深认识的。
产品经理的说辞是常常变革的。
当你面向需求编程,你便是在依赖一个认识每一秒都在改变的女/男朋友。
你将身心俱疲。

我们该当面向业务模型编程。
我在《Code Review 我都 CR 些什么》里也提到了这一点,但是我当时并没有给出该当怎么去设计业务模型的辅导。
我的潜台词便是,你还是仅仅能凭借自己的智力和履历,没有很多方法论工具。

现在,我给你推举一个工具,DDD(Domain-Driven Design),面向领域驱动设计。
它能让你对业务更好地建模,让对业务建模变成一个可拆解的实行步骤,仅仅须要少得多的智力和履历。
区分好领域高下文,思考明白它们之间的关系,找到领域下的实体和值工具,找到和模型贴合的架构方案。
这些任务,让业务建模变得大略。

当我们面向业务模型编程,变更的需求就变成了--供应给用户他所须要的业务模型的不同部分。
我们不再是在不断地 change 代码,而是在不断地 extend 代码,逐渐做完一个业务模型的填空题。

写代码要有对付'美'的追求

google 的很多同学说(至少 hankzheng 这么说),软件工程=科学+艺术。
当前腾讯,很多人,不讲科学。
工程学,打算机科学,都不管。
就喜好搞'巧合式编程'。
刚好能事情了,打完收工,交付需求。
绝大多数人,根本不追求编码、设计的艺术。
对细节的好看,毫无觉得。
对付一个空格、空行的利用,毫无逻辑,毫无美感。
用代码和其他人沟通,连基本的整洁、合理都不讲。
根本没想过,别人会看我的代码,我要给代码'装扮打扮'一下,整洁大方,俏丽动人,还极有内涵。
'窈窕淑女,君子好逑',我们该当对别人年夜方一点,你总是得阅读别人的代码的。
大家都对美有一点追求,便是相互都年夜方一些。

很无奈,我把对美的追求说得这么'卑微'。
必须要由'务实的须要'来构建必要性。
而不是每个工程师发自内心的,像对待俊秀的异性、好的音乐、好的电影一样的发自内心的须要它。
认为代码也是媚谄别人、媚谄自己的东西。

如果我们想做一个有肃静、有格调的工程师,我们就该当把自己的代码、劳动的产物,当成一件艺术品去雕琢。
务实地追求效率,同时也追求美感。
效率产出代价,美感媚谄自己。
不仅仅是为了一口饭,同时也把工程师的事情当本钱身一个快乐的源头。
事情不再是 overhead,而是 happiness。
此刻,你做不到,但是该当有这样的追求。
当我们都有了这样的追求,有一天,我们会能像 google 一样做到的 。

换行

换行

换行

运用程序框架是实现细节

以下是《整洁架构之道》的原文摘抄:

对,DIP 大发神威。
我以为核心做法便是:

核心代码该当通过 DIP 来让它不要和详细框架绑定!
它该当利用 DIP(比如代理类),抽象出一个防腐层,让自己的核心代码免于腐坏。
选择一个框架,你不去做防腐层(紧张通过 DIP),你便是单方面领了却婚证,你只有责任,没有权利。
同学们要想明白。
同学们该当对框架本身是否精良,是否足够组件化,它本身能否在项目里做到可插拔,做出思考和设计。

trpc-go 对付插件化这事儿,做得还不错,大家会积极地利用它。
trpc-cpp 离插件化非常远,它自己根本就成不了一个插件,而是有一种要霸道你的觉得,你能凭直觉明显地觉得到不愿意和它订终生。
例如,trpc-cpp 乃至霸道了你构建、编译项目的办法。
当然,这很多时候是 c++措辞本身的问题。

‘解耦’、'插件化’便是 golang 措辞的关键词。
大家开玩笑说,c++已经被委员会玩坏了,加入了太多特性。
less is more, more means nothing。
c++从来都是让别的工具来办理自己的问题,trpc-cpp 可能把自己松绑定到 bazel 等精良的构建方案。
寻求精良的组件去软绑定,供应办理方案,是可行的出路。
我个人喜好 rust。
但是大家还是熟习 cpp,我们确实须要一个投入更多人力做得更好的 trpc-cpp。

统统都该当是代码(通过代码去显式组合)

Unix 编程哲学见告我们: 如果有一些参数是可变的,我们该当利用配置,而不是把参数写去世在代码里。
在腾讯,这一点做得很好。
但是,大人,现在时期又变了。

J2EE 框架让我们看到,组件也可以是通过配置 Java Bean 的形式注入到框架里的。
J2EE 实现了把组件也配置化的壮举。
但是,时期变了!
你下载一个 golang 编译器,你进入你下载的文件里去看,会创造你找不到任何配置文件。
这是为什么?两个大略,但是很多年都被人们忽略的道理:

配置即隐性耦合。
配置只有和利用配置的代码组合利用,它才能完成它的事情。
它是通过把'一个事情分开两个步骤'来换取动态性。
换句话说,它让两个相隔十万八千里的地方产生了耦合!
作为工程师,你一开始就要理解双倍的繁芜度。
配置如何利用、配置的处理程序会如何解读配置。
代码能够有很强的自阐明能力,工程师们更乐意阅读可读性强的代码,而不是编写得很烂的配置文档。
配置只能通过厚重的配置解释书去阐明。
当你缺少完备的配置解释书,配置变成了地狱。

golang 的编译器是怎么做的呢?它会在代码里给你设定一个通用性较强的默认配置项。
同时,配置项都是集中管理的,就像管理配置文件一样。
你可以通过额外配置一个配置文件或者命令行参数,来改变编译器的行为。
这就变成了,代码阐明了每一个配置项是用来做什么的。
只有当你须要的时候,你会先看懂代码,然后,当你有需求的时候,通过额外的配置去改变一个你有预期的行为。

逻辑变成了。
一开始,所有事情都是解耦的。
一件事情都只看一块代码就能明白。
代码有较好的自阐明性和表明,不再须要费劲地编写撇脚的文档。
当你明白之后,你须要不一样的行为,就通过额外的配置来实现。
关于怎么配置,代码里也讲明白了。

对付 trpc-go 框架,以及一众插件,优先考虑配置,然后才是代码去指定,部分功能还只能通过配置去指定,我就很难熬痛苦。
我接管它,就得把一个事情放在两个地方去完成:

须要在代码里 import 插件包。
须要在配置文件里配置插件参数。

既然不能消灭第一步,为什么不能是显式 import,同时通过代码+其他自定义配置管理方案去完成插件的配置?当然,插件,直接不须要任何配置,供应代码 Option 去改变插件的行为,是最喷鼻香的。
这个时候,我就真的能把 trpc 框架本身也当成一个插件来利用了。

封装不一定是好的组织形式

封装(Encapsulation),是我上学时刚打仗 OOP,惊为天人的思想方法。
但是,我事情了一些年头了,看过了不知道多少糜烂的代码。
个中一部分还须要我来掩护。
我看到了很多莫名其妙的封装,让我难熬痛苦至极。
封装,常常被滥用。
封装的时候,我们一定要让自己的代码,自己就能阐明自己是按照下面的哪一种套路在做封装:

按层封装按功能封装按领域封装按组件封装

或者,其他能被命名到某种有代价的类型的封装。
你要能说出为什么你的封装是必要的,有代价的。
必要的时候,你必须要封装。
比如,当你的 golang 函数达到了 80 行,你就该当对逻辑分组,或者把一块过于细节化却功能单一的较长的代码独立到一个函数。
同时,你又不能胡乱封装,或者过度封装。
是否过度,取决于大家的共识,要 reviwer 能认可你这个封装是有代价的。
当然,你也会成为 reviewer,别人也须要得到你的认可。
缺少意图设计的封装,是毁坏性的。
这会使其他人在面对这段代码时,畏首畏尾,不敢修正它。
形成一个糜烂的肉块,并且,这种糜烂会逐渐蔓延开来。

以是,所有细节都是关键的。
每一块砖头都被精心设计,才能构建一个俊秀的项目!

所有细节都该当被显式处理

这是一个显而易见的道理。
但是很多同学却毫无知觉。
我为须要深入阅读他们编写的代码的同学默哀一秒。
当有一个函数 func F() error,我仅仅是用 F(),没有用变量吸收它的返回值。
你阅读代码的时候,你就会想,第一开拓者是忘却了 error handling 了,还是他思考过了,他决定不关注这个返回值?他是设计如此,还是这里是个 bug?他人即地狱,掩护代码的苦难又多了一分。

我们对付自己的代码可能会给别人带来困扰的地方,都该当显式地去处理。
就像写了一篇不会有歧义的文章。
如果便是想要忽略缺点,'_ = F()'搞定。
我将来再处理缺点逻辑,'_ = F() // TODO 这里须要更好地处理缺点'。
在代码里,把事情讲明白,所有人都能快速理解他人的代码,就能快速做出修正的决策。
'预测他人代码的逻辑用意'是很难熬痛苦且困难的,他人的代码也会在这种场景下,产生被误读。

不能上升到原则的一些常见案例合理注释一些并不'普通'的逻辑和数值

和'所有细节都该当被显式处理'一脉相承。
所有他人可能要花较多韶光预测缘故原由的细节,都该当在代码里提前清楚地讲明白。
请年夜方一点。
也可能,三个月后的将来,是你回来 eat your own dog food。

习气留下 TODO

要这么做的道理很大略。
便于所有人能接着你开拓。
极有可能便是你自己接着自己开拓。
如果没有标注 TODO 把没有做完的事情标示出来。
可能,你自己都会搞忘自己有事儿没做完了。
留下 TODO 是很大略的事情,我们为什么不做呢?

不要丢弃缺点信息

即'缺点通报原则'。
这里给它换个名字--你不应该主动把很多有用的信息给丢弃了。

自动化测试要快

在 google,自动化测试是硬性哀求在限定韶光内跑完的。
这从细节上保障了自动化测试的速率,进而保障了自动化测试的代价和可用性。
你真的须要 sleep 这么久?该当负责考量。
考量清楚了把缘故原由写下来。
昔时夜家创造总时长太长的时候,可以选择个中最不必要的部分做优化。

历史有问题的代码, 创造了问题要及时 push 干系人主动办理

这是'掌握软件的熵是软件工程的主要任务之一'的表现之一。
我们是团队作战,不是无组织无记录的部队。
创造了问题,就及时抛前程争决。
让伤痛更少,跑得更快。

less is more

less is more. 《Code Review 我都 CR 些什么》强调过了,这里不再强调。

less is more

如果打了缺点日志, 有效信息必须充足, 且不过多

和'less is more'一脉相承。
同时,必须有的时候,就得有,不能漏。

日志

注释要把问题讲清楚, 讲不清楚的日志即是没有

是个大略的道理,和'所有细节都该当被显式处理'一脉相承。

日志

MR 要自己先 review, 不要摧残浪费蹂躏 reviewer 的韶光

你也会成为 reviewer,节省他人的韶光,他人也节省你的韶光。
缩短交互次数,提升 review 的愉悦感。
让他人提的 comment 都是'言之有物'的东西,而不是一些反反复复的最根本的细节。
会让他人更愉悦,自己在看 comment 的时候,也更愉悦,更乐意去谈论、沟通。
让 code review 成为一个技能互换的平台。

韶光

要探求得当的定语

这个显而易见。
但是,同学们便是爱放肆自己?

定语

不要涌现特定 IP,或者把什么可变的东西写去世

这个和'ETC'一脉相承,我以为也是显而易见的东西。
但是很多同学还是喜好放肆自己?

写去世

利用定语, 不要 1、2、3、4

这个存粹便是放肆自己了。
当然,也会有只能用 1、2、3、4 的时候。
但是,你这里,是么?多数时候,都不会是。

数字

有必要才利用 init

这,也显而易见。
init 很方便,但是,它也会带来心智包袱。

init

要关注 shadow write

这个很主要,看例子就知道了。
但是大家常常忽略,特此提一下。

shadow

能不耦合吸收器就别耦合

减少耦合是我们保障代码质量的主要手段。
请把 ETC 原则放在自己的头上漂浮着,时候带着它思考,不要

吸收器

空实现须要注明空实现便是实现

这个和'所有细节都该当被显式处理'一脉相承。
这个理念,我见过无数种形式表现出来。
这里便是个中一种。
列举这个 case,让你印象再深刻一点。

空实现

看错题集没多少有用, 我们须要教练和传承

上面我列了很多例子。
是我能列出来的例子中的九牛一毫。
但是,我列一个非常弘大的错题集没有任何用。
我也不再例举更多。
只有昔时夜家书仰了敏捷工程的美。
认可好的代码架构对付业务的代价,才能真正地做到举一反三,理解无数例子,能对更多的 case 自己做出合理的判断。
同时,把好的判断传播起来,做到"群体免疫",终极做好 review,做好代码质量。

展望

希望本文能帮助到须要做好 CR、做好编码,须要培养更多 reviwer 的团队。
让你门看到很多原则,接管这些原则和理念。
去理解、相信这些理念。
在 CR 中把这些理念、原则传播出去。
成为别人的临时教练,让大家都成为合格的 reviwer。
加强对付代码的互换,飞轮效应,让团队构建好的人才梯度和工程文化。

写到末了,我创造,我上面写的这些东西都不那么主要了。
你有想把代码写得更利于团队协作的代价不雅观和态度,反而是最主要的事情。
上面讲的都仅仅是写高质量代码的手段和思想方法。
当你认可了'该当编写利于团队协作的高质量代码',并且拥有对'不利于团队代码质量的代码'嫉恶如仇的态度。
你总能找到高质量代码的写法。
没有我帮你总结,你也总会节制!

拾遗

如果你深入理解 DDD,就会理解到'六边形架构'、'CQRS(Command Query Responsibility Segregation,查询职责分离)架构'、'事宜驱动架构'等关键词。
这是 DDD 构建自己体系的基石,这些架构及是细节又是顶层设计,也值得理解一下。