代码大全是一本很厚重的书,一直望而生畏,误以为关于算法,实际上涉及的是关于写好代码的方方面面,软件工程、敏捷、匠艺、重构和性能调优的技巧,从怎么命名变量函数,到类和函数的结构以及注释。
真正读完这本书,还是需要广泛地阅读、学习在实践中应用、总结,才能真正地掌握。关于软件工程、匠艺,如何写好代码也是我自己最有兴趣的部分,一鼓作气就读完了这本厚重的代码工艺手册。
编程是介于艺术和科学之间的手艺,由艺术和技术协同融合而产生的一门工程学科。对编程来说,最有挑战的其实是将问题概念化,也就是建模,核心是管理和降低复杂度。
要构建高质量的软件,就需要高质量的实践,重视前期准备是有效编程的关键思想之一,哪怕是快速迭代试错验证商业模式,也应该尽可能少量预先设计,对接口精心设计。
相比单元测试,更有用的是代码审查,作者在书中举了大量例子,并且通过数据统计,分析了为什么从投入产出的角度,代码审查这种实践,对于团队和系统的演进,远比单元测试本身或者测试的覆盖率更有价值;另外一方面比起注释,自解释的、结构优雅的代码更有价值,作者也不断强调清晰命名的重要性。
我觉得对于软件的复杂性和写出更优质的代码,AI浪潮必然会带来极大的改进,大模型经过训练,对于优雅的代码,有更深刻的理解,我们只需要用自然语言定义好目标,很多工具程序,就可以自动的生成出来,这样的体验,已经是现实。
软件开发是从需求分析、产品设计、架构设计到编码,优化的整个过程,代码大全探讨的是如何构建的整个过程,构建占用项目时长的30-80%,就决定了构建必然对项目成败有巨大的影响,是软件开发中的核心活动。
从需求分析开始
充分详尽地描述需求是项目成功的关键,明确的需求有助于确保是用户而不是程序员在驾驭系统的功能,有助于避免争论,在前期重视清晰的定义需求,有助于尽可能减少开发阶段后的系统变更。
对于我们的启示是,无论开发什么规模的软件,都应该尽可能的在开发阶段开始之前,清晰的描述需求,交付更详细的产品规格说明(PRD),更清晰的设计稿;通过需求评审提前确认可能的技术问题。
就像作者说的,稳定的需求是软件开发的圣杯,一旦客户接受需求文档,就再也不能更改,只是一个美好的愿望而已;客户有清晰业务模式的开发尚且如何,何况是还需要进行市场验证的探索性项目。
增量式开发是更好的选择,对于任何不确定性的需求,尽可能做出简单但是能运行的软件系统版本。精心的对项目进行规划,将增量改变细节的能力,作为核心能力集成到从产品设计到构建的整个过程中,能够更好的应对变化。
这样的增量改变细节的能力,我觉得包括了特性开关、灰度发布、A/B测试、持续集成,在系统变得非常复杂之后,还需要微服务的反脆弱能力。
在代价最小的阶段,用最少的精力投入,捕获错误,能够减少软件缺陷在系统中存在的时间和产生的影响。
用建筑作为隐喻来类比,就是无论什么规模的项目,都要根据项目具体需要,在实施前认真做好准备工作。
管理复杂性
软件的首要技术使命是管理复杂性。
软件开发的本质复杂性在于,编程很难,即使有好工具支持。无论有什么工具可用,程序员都不得不同混乱的现实世界搏斗;我们不得不严谨的考虑时序、异常和依赖;不得不面对说不清自己想法的用户;还不得不面对接口定义模糊的其他软硬件;另外,还不得不处理法规、业务规则和其他复杂性的来源,这些都来自计算机编程以外的世界,我们需要有人去搞清楚真实世界要解决的问题和计算机假设要解决的问题。
封装和抽象是降低软件系统复杂性主要手段。将代码放入命名良好的子程序中是实现功能自文档化的最好方法,同时可以隐藏复杂的处理顺序;内聚的代码,便于修改和维护,有利于提高性能。
在实践中,我们会将特别复杂的处理逻辑,拆分成一个专门的对象,将所有操作聚合在一起,可以更好的管理控制流,还可以利用私有的局部变量,减少函数间参数传递。
好的子程序,应该通过名称就可以描述它所做的所有事情。
设计优美的软件结构
从命名到源代码的布局,审美都是很重要的,哪怕单纯代码看起来优美也是有价值的。但是代码更有价值的特点是清楚展现代码的逻辑结构,最好的代码应该是像散文一样结构优雅,而且自解释,代码本身就是基本的注释。
为什么值得精心设计、思考、测试之后再构建,关键是相信软件运行时间远比构建时间更长。哪怕是时间再紧张,开发人员也要养成预先设计的习惯,坚持做少量预先设计。至于应该设计多少才足够,是主观判断题,没有完美的答案,努力追求简单,迭代、迭代、再迭代。
作者苦口婆心的说明如何清晰命名的重要性,取名字是最重要的一件事情,然后通过分组、空行、锁进和括号,优化代码的布局,从视觉上凸显代码的逻辑结构。
这方面的实践,充满了这本书,作者从命名到函数的设计,类的结构,抽象和、组合和继承的关系,如何重构,怎么写注释都提出了很多有价值观点,阅读并且在实践中应用,总结自己的最佳实践才是真正值得鼓励的,而不是教条主义。
代码的结构里,我觉得就近原则是一个非常值得深刻地思考的,什么时候根据单一职责拆分代码,什么时候应该把相关操作放在一起,遵循就近原则,这是一个非常艺术的地方。
最小删除成本,应该是就近原则的一种延伸,在高度不确定的情况下,对于后期会是什么特性驱动某个模块代码演进方向的不清晰的时候,应该将相关模块的代码,就近的放在相对集中的地方,这样有助于模块的快速试错。
类似的实践原则还有,尽可能将变量局部化,缩小作用域,通过参数清晰地传递信息。
在代码的结构中建立一个控制点,把任务集中在一个地方控制,可以更内聚;就像工作台一样,在一个固定的工作台高效地拼装;依赖注入框架中,由容器集中初始化对象,我觉得也是这种理念的应用。也是艺术创作、外科手术这些协作场景的高效实践。
高质量开发的实践
软件质量必须从开发过程早期就内建,仔细地分析需求,良好的设计,并且高质量的编码实践。
高质量的实践,首先是程序员的个人品质,谦逊、好奇心、理性诚实、创造力、自律和高明的懒惰。重视前期准备,行动之前先分析和设计,学习成功项目的经验,做试验,阅读解决问题的方法论,阅读文档书籍。
构建高质量软件的程序员,有一个共同特征,都用的是高质量的实践,从项目开始、中间和结束,始终都强调质量;我觉得对于个人来说,就是有一套基准,一些肌肉记忆一样的习惯,在任何情况下,都会坚持不低于基准。
首先要学会如何避免缺陷,而不是如何修复;一次只改一个地方;一旦发现问题,要识别潜在的问题,彻底解决在修复表面问题,而不是用程序掩盖。
协同构建优于单元测试
在软件构建过程中,包括结对编程、正式审查、非正式技术评审和文档阅读在内的协同构建,能够培育公司文化并加强编程技能和知识的传递。
作者通过数据统计,总结出审查是比测试成本更低,能够更高效减少缺陷的实践,审查可以一举检查缺陷的现象和原因,而测试只能发现表面现象。正如作者列出的数据,无论单元测试、集成还是beta测试,在单独使用时效果都很有限,远低于设计和代码审查,测试代码本身也是有维护成本,而且会出错的,这是常常被忽略的,随着产品迭代,写出来就要有足够时间维护保养,除了就近删除方便,效果并不好,这也是单元测试的局限性所在。
另外一方面,如果基本功扎实,充分设计和思考,代码本身是可测试的,并不一定非要写出来,可测试的设计思考才是本质,当然在至关重要,不易变、副作用小、范围可控的时候,测试驱动值得鼓励,是信心的来源,但不是银弹。代码审查或者Mob编程能够促进团队中的知识传递、有审查者可以带来的写更好代码的压力,都是积极的作用。我觉得AI副驾驶,能够对改善单元测试效率有很大帮助。
注释也是一样,最好的代码还是活文档,鼓励但是不要抱有太多幻想,好的注释是对已经充分自解释代码易图的说明,传达代码无法表述的信息,应该针对接口、影响深远、不易变或者复杂的逻辑,针对高层次设计易图。
从哪里开始写代码
这本书,叫做代码大全,是名副其实的,教我们如何写代码的百科全书。
说到代码设计,完全自顶向下设计的缺点是,设计成本高昂,如果经验不足或者对领域理解不充分,或者时间不足,设计出的方案就有可能不够深思熟虑。另外还有一点是,如果考虑性能,底层的细节,也会反过来影响上层结构,纯粹自顶向下,有可能因为信息不足而导致设计被推翻。
我自己的经验跟作者介绍的方法差不多,根据具体的场景,风险、复杂度和时间的紧急程度,还有性能要求,具体的决定先从顶部开始向下,还是从底部开始向上,先设计好上层抽象,在路由接口层、类接口层、包接口层创建抽象;写好下层细节,对具体的实现进行封装,最后在中间集成,是比较好的实践。
作者提出的三明治方法,首先在系统层次结构顶部集成高层级业务的抽象;然后在层次结构底部集成设备接口类以及广泛使用的通用功能类。
软件的设计是持续迭代的过程,需要同时从高层和底层的角度看问题。自上而下和自下而上相辅相成。
性能、方法论、还有更多
掌握编写高效代码的艺术是成为一名真正程序员的成人礼。书中,也讨论过,如何在可读性和写好代码的同时,写出高性能的代码。
作者列举了很多我们平时在开发中也会用到的实践,但是最重要的我觉得是两个核心理念,首先是不要在视角有限的情况下过早优化,软件运行起来以后,真正的性能瓶颈,很有可能不是我们一开始预见到的地方;然后是性能不存在“可能”,要精准的思考,形成假设、收集数据、分析假设,然后有条不紊的证伪,经常度量软件的性能,根据度量的结果来做判断和优化,而不是根据假设。
在小型的项目中,构建活动占据主导地位,开发过程的重点是把产品设计出来,编码实现,并且运行起来;而较大的项目,需要更多的架构、集成工作和系统测试才能成功。
随着项目规模变大,要精心设计方法论,在流程、设计和架构上下功夫,才能驾驭复杂性。
如果您喜欢这篇文章,欢迎关注我的公众号【软件十书】,获取更多精彩内容,点击阅读原文。