腾讯工程师,万字长文谈「程序员修练之道」
引子
软件工程师仍旧是「手工业者」
这一篇文字是《腾讯工程师,万字长文说 Code Review》的姊妹篇。
同样是 腾讯 12 级工程师 Cheaterlin 根据他在自己团队进行 Code Review时遇到的问题所写。
本文巨长,1.8万字,
阅读时间  > 30分钟,
容易受伤,谨读,
不如去刷短视频 happy!
正文如下:
综述
我写过一篇《Code Review 我都 CR 些什么》,讲解了 Code Review 对团队有什么价值,我认为 CR 最重要的原则有哪些。最近我在团队工作中还发现了:
原则不清晰。对于代码架构的原则,编码的追求,我的骨干员工对它的认识也不是很全面。当前还是在 review 过程中我对他们口口相传,总有遗漏。
从「知道」到「掌握」需要时间。我需要反复跟他们补充 review 他们漏掉的点,他们才能完成吸收、内化,在后续的 review 过程中,能自己提出这些 review 的点。
过度文档化是有害的,当过多的内容需要被阅读,工程师们最终就会选择不去读,读了也仅仅能吸收很少一部分。在 google,对于代码细节的理解,更多还是口口相传,在实践中去感受和理解。
但是,适当的文档、文字宣传,是必要的。特此,我就又输出了这一篇文章,尝试从'知名架构原则'、'工程师的自我修养'、'不能上升到原则的几个常见案例'三大模块,把我个人的经验系统地输出,供其他团队参考。
知名架构原则
web标准有哪三大部分后面原则主要受《程序员修炼之道: 通向务实的最高境界》、《架构整洁之道》、《Unix 编程艺术》启发。我不是第一个发明这些原则的人,甚至不是第一个总结出来的人,别人都已经写成书了!务实的程序员对于方法的总结,总是殊途同归。
细节即是架构
(下面是原文摘录, 我有类似观点, 但是原文就写得很好, 直接摘录)
一直以来,设计(Design)和架构(Architecture)这两个概念让大多数人十分迷惑--什么是设计?什么是架构?二者究竟有什么区别?二者没有区别。一丁点区别都没有!'架构'这个词往往适用于'高层级'的讨论中,这类讨论一般都把'底层'的实现细节排除在外。而'设计'一词,往往用来指代具体的系统底层组织结构和实现的细节。但是,从一个真正的系统架构师的日常工作来看,这些区分是根本不成立的。以给我设计新房子的建筑设计师要做的事情为例。新房子当然是存在着既定架构的,但这个架构具体包含哪些内容呢?首先,它应该包括房屋的形状、外观设计、垂直高度、房间的布局,等等。
但是,如果查看建筑设计师使用的图纸,会发现其中也充斥着大量的设计细节。譬如,我们可以看到每个插座、开关以及每个电灯具体的安装位置,同时也可以看到某个开关与所控制的电灯的具体连接信息;我们也能看到壁炉的具体位置,热水器的大小和位置信息,甚至是污水泵的位置;同时也可以看到关于墙体、屋顶和地基所有非常详细的建造说明。总的来说,架构图里实际上包含了所有的底层设计细节,这些细节信息共同支撑了顶层的架构设计,底层设计信息和顶层架构设计共同组成了整个房屋的架构文档。
软件设计也是如此。底层设计细节和高层架构信息是不可分割的。他们组合在一起,共同定义了整个软件系统,缺一不可。所谓的底层和高层本身就是一系列决策组成的连续体,并没有清晰的分界线。
我们编写、review 细节代码,就是在做架构设计的一部分。我们编写的细节代码构成了整个系统。我们就应该在细节 review 中,总是带着所有架构原则去审视。你会发现,你已经写下了无数让整体变得丑陋的细节,它们背后,都有前人总结过的架构原则。
把代码和文档绑在一起(自解释原则)
写文档是个好习惯。但是,写一个别人需要咨询老开发者才能到的文档,是个坏习惯。
这个坏习惯甚至会给工程师们带来伤害。比如,当初始开发者写的文档在一个犄角旮旯(在 wiki 里,但是阅读代码的时候没有在明显的位置看到链接),后续代码被修改了,文档已经过时,有人再出文档来获取到过时、错误的知识的时候,阅读文档这个同学的开发效率必然受到伤害。
所以,如同 golang 的 godoc 工具能把代码里'按规范来'的注释自动生成一个文档页面一样,我们应该:
按照 godoc 的要求好好写代码的注释。
代码首先要自解释,当解释不了的时候,需要就近、合理地写注释。
当小段的注释不能解释清楚的时候,应该有 来解释,或者,在同级目录的 ReadMe.md 里注释讲解。
文档需要强大的富文本编辑能力,MarkDown 无法满足,可以写到 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 监控么?答案,可能是悲观的。有人已经已经尝到了可逆性之痛。