第 1 章 优质代码的外部特征
张刚
讲述:Alloy大小:7.81M时长:34:09
软件工程师是专业人士。人们很容易意识到,成为专业人士需要非常多的专业知识和经验,却往往忽略了决定专业人士产出的另一个重要方面——专业人士的鉴赏力。
像卓越的建筑设计师、音乐家这类专业人士,在创造高质量的作品之前,往往花费了数年的时间参观或揣摩卓越的作品,去鉴赏其中的美妙。鉴赏培养的是专业人士的“品味”。
品味非常重要——要做出卓越的工作,首先要能看出哪些工作是卓越的,哪些是拙劣的。优秀的建筑设计师一定能欣赏到建筑的美妙,优秀的软件工程师也能够快速而精准地判断代码和设计的优劣。学会判断和欣赏美,是成为优秀软件工程师的第一步。
本章和第 2 章聚焦什么是好的设计和什么是好的代码这两个核心问题。我把好的代码特征分为外部特征和内部特征。其中,外部特征是高质量代码应有的外在表现,从结果角度衡量。对这些特征的判断无须深入代码,即使是一个不懂软件的人,也能从外部感知到。内部特征则体现了代码是否“专业”,从代码的内部质量角度衡量。经验丰富的软件工程师,只需要大致读一下代码,就能感知到代码的大致质量。
本章介绍优质代码的外部特征,这些特征可以概括为以下 5 条。
实现了期望的功能。
缺陷尽量少。
易于理解。
易于演进。
易于复用。
其中,前两条和代码的外部质量有关,关注当下的效益;后 3 条和代码的演进有关,关注长期的价值。
1.1 实现了期望的功能
软件理应实现期望的功能,这无须多言。如果开发的软件不能给业务相关人带来价值,那对它所做的一切开发活动自然都是浪费。
不过,碍于软件所解决问题的复杂性和人类沟通的复杂性,实现期望的功能并不那么容易。在实际工作中,我们经常会遇到下面这些情况。
用户心目中的目标和用户能描述出的内容并不一致。
产品经理所理解的用户表述,和用户描述的内容存在偏差。
产品经理编写了需求文档,但未能进行精确的需求表述。
即使需求文档表述准确,开发者也有可能产生误解。
即使开发者的理解是正确的,也可能存在用户没有描述出来的隐含需求。
……
历史悠久的秋千漫画
相信有不少读者见过图 1.1 的这组漫画。不过,很少有人考证过它的历史。如果知道这幅漫画的历史有多长,或许你会大吃一惊:原来在软件领域,过了这么多年,这组漫画描绘的问题一直都在,并没有随着技术发展产生根本的改变。
![](https://static001.geekbang.org/files/resource/ebook/100011/image00275.jpeg)
图 1.1 历史悠久的秋千漫画
据考证[1],这幅漫画的最早版本至少在 1968 年就出现在公开出版物上了,迄今已有 50 多年的历史。它形象地反映了我们刚刚描述的情况:用户真正需要的和用户描述的往往并不一致,经过层层加工,信息更是进一步失真,最终不仅成本大量超出预算,所开发的东西也不能真正满足用户的需要。
1.1.1 为什么需求问题如此普遍
没有谁愿意把宝贵的人生耗费在毫无价值的错误需求上。不过仅仅“不愿意”是不够的,只有在理解了为什么会有这样的问题之后,才能找到恰当的解决方案。
软件解决的是现实世界的复杂问题
需求问题如此普遍的最根本原因是软件解决的是现实世界的问题。现实世界有多复杂,软件就有可能多复杂。从图 1.1 中的第一幅画和最后一幅画就可以看出:即使是用户自己,其表达出来的需求也和自身真正的期望相去甚远。
开发软件的过程是一个持续建立认知的过程。我们不能寄希望于业务人员一开始就对问题有直达本质的认知,随着开发过程的展开,渐渐地弄明白问题是很正常的。同样,我们也不能寄希望于开发人员一开始就能有直达本质的解决方案,随着开发过程的展开,渐渐地弄明白方案也是正常的。
要真正产出有价值的软件,需要关注以下两个重要的方面。
加快认知的过程。
增加设计的弹性,在出现问题时能较快调整。
高质量沟通是困难的,也是容易被忽略的
秋千漫画还反映了导致需求问题产生的另一个重要维度:沟通。在现实世界中,每一次信息传递都意味着一次信息损耗。在综艺节目中有一个常见的“拷贝不走样”游戏。这个游戏之所以有趣,是因为信息在前后传递的过程中,很可能会产生偏差。
在软件开发中,情况也非常类似,即使一开始的认知是正确的,可由于信息传递过程中不可避免的偏差,也常常导致最终的产出和预期相去甚远。如何降低信息在传递过程中的失真,也是软件开发人员不得不面对的一个问题。
实现高质量的沟通非常困难,身在其中的人往往并不自知。一个经常发生的现象是:业务人员觉得自己已经交代得很清楚了,开发人员也觉得自己理解得很清楚了——结果是表面上一致,事实上却谬之千里。
此外,“实现正确的需求”并不仅仅局限在系统层次。系统的某个局部可能是由多个开发者协作完成的,这个局部也存在需求问题,并且越是局部问题,细节就越多,这些问题也就更加值得重视。
1.1.2 解决问题的方向
优秀的开发者会关注自己开发的软件的真正价值,而不只是盲目地接收到手的需求。实践表明,开发者的积极投入是高效理解需求、提升设计质量的关键。没有来自开发者的积极沟通,需求设计的质量就很难提升,开发工作的结果自然也不可能太好。
结构化的探索
软件开发从本质上讲是“从无到有”的过程,在这整个过程中,一个客户或业务方脑海中的想法,逐步变为真实运行的软件系统。
“从无到有”意味着探索,而探索是需要结构化的方法的。如果没有清晰的探索方法,探索效率就不可能高。一件事情的确定性越弱,所需要的结构化思维能力就越强。例如,面对需求的不确定性问题,本书第 3 章介绍的需求分析金字塔就是有效探索需求的方法。此外,第 4 章介绍的领域模型,则是增强认知、加强沟通的重要工具。
注重沟通
优秀的软件工程师往往也是沟通的高手。不得不承认,许多人可能对此有偏见。如果认为编程就只是和机器打交道,那就忽略了软件其实是解决现实问题的工具这一本质。软件工程师不一定都开朗活泼、妙语连珠,但是在尊重他人、认真理解对方意图、准确达成一致方面,优秀软件工程师的表现至少和其他行业的沟通高手相差无几,甚至远远超出后者。
强调设计契约
契约不是让客户“签字画押”。在认知不足的时候“签字画押”只能是一种双输行为。契约的本质是信息明确、以终为始。只有尽可能地强调明确,才可以发现需求的模糊性,提升在早期发现问题的概率。
本书中有大量围绕设计契约展开的内容。例如,第 6 章会讲解如何把接口表述为清晰的设计契约,第 7 章会讨论如何通过测试前置的方式,促成需求或者设计契约的明确化,并对理解一致性展开早期验证。
做到演进式设计
如果软件设计得足够好,那么完全可以在用户需求发生变化时随机应变。与此相对的是惧怕变化。设计有“刚性”和“柔性”之分。刚性的设计无论考虑得如何周全,也仅能适应预先认知的场景。柔性的设计恰恰相反,它可以灵活地适应环境的变化。好的设计应该是柔性的。
做到演进式设计极其重要但是并不容易,它需要坚实的设计基础和卓越的设计实践。这就是第 11 章将要探讨的核心内容。
1.2 缺陷尽量少
优质的软件应该只有极少的缺陷。缺陷意味着客户满意度降低和修复成本增加,并且远远不限于此。特别是在今天这种竞争剧烈的业务环境中,如果缺陷太多,修复缺陷不仅需要花费金钱,还会耽误宝贵的时间,继而影响业务竞争,这可能造成潜在的商业损失。
1.2.1 关于软件缺陷的两个事实
关于软件缺陷的第一个事实是:缺陷不可能完全避免。要做到低缺陷率非常不容易,但这并不意味着无法减少缺陷带来的损失。这是关于软件缺陷的第二个事实:缺陷带来的影响和发现缺陷的时机密切相关。要规避缺陷造成的影响,最重要的原则是尽量早地发现缺陷。
缺陷不可能完全避免
缺陷不可能完全避免意味着对待缺陷的正确态度:谨慎而专业。
常常把“我的代码没有缺陷,不需要测试”挂在嘴边的程序员,非但大概率不是高手,其编写的代码也往往会在后续测试阶段错误频出。
真正的软件工程师懂得尊重软件的复杂性,在编程时“如履薄冰”,时刻把产出高质量的代码放在思考的第一优先级。不过,“如履薄冰”和“战战兢兢”完全是两码事。正是因为“如履薄冰”,才会非常审慎地对待自己接收到的需求、自己编写的代码、团队的产出。例如,专业的软件工程师会非常重视自动化测试,而且会做到测试先行(详见 7.5 节)。通过使用恰当的工具和思考方法,软件设计质量可以得到大幅提升。
专业的软件工程师,可以完美地融合专业能力、信心、勇气和谨慎为一体,产出低缺陷率的代码。
尽量早地发现缺陷
缺陷并不可怕,真正可怕的是没有能力及时发现缺陷。优秀的软件工程师都知道:软件是非常复杂的,人类思维又是有局限的。苛求代码在刚被写出来的一瞬间就一定是对的完全不现实。因此,我们要做的不是要规避缺陷,而是要通过加快反馈,在缺陷造成实质性影响之前,就把它消灭于无形。
当我们说缺陷率的时候,更多是在讨论那些没有被及时发现的缺陷,特别是出现在后期,如系统测试、用户接收测试、正式发布这些阶段的缺陷。而对那些刚有出现苗头、就立即被修复的缺陷,如软件工程师在编码过程中发现的刚刚犯下的错误,往往没有什么人会关心。只关注那些延迟发现的缺陷是非常合理的策略,其背后的理论基础是缺陷成本递增原理。
图 1.2 是 McConnell 在其经典著作《代码大全》[2] 中给出的缺陷成本递增曲线。我们可以发现,无论是哪个阶段注入的缺陷,只要我们能在当前阶段立即发现,缺陷成本都是非常低的。发现阶段越往后移,问题的复现、定位越困难,影响面也越宽,缺陷导致的成本就越高。
![](https://static001.geekbang.org/files/resource/ebook/100011/image00276.jpeg)
1.2.2 解决问题的方向
虽然不能完全避免缺陷,但是提前发现缺陷可以大幅降低它带来的不良影响。基于这样的事实,我们就可以得到如下的应对策略。
缩短缺陷的发现周期。
降低缺陷的发现成本和修复成本。
缩小缺陷的影响面。
缩短缺陷的发现周期
保证缺陷能被及时发现的关键点在于缩短问题的反馈周期。本书 7.2 节会介绍测试前置的策略,即“I 模型”。测试前置保障了需求以及外部接口描述和理解的清晰,并且通过测试先行,可以及时发现缺陷,在源头上降低发生功能性缺陷的可能性。
降低缺陷的发现成本和修复成本
发现、调查和修复缺陷往往会消耗较高的成本。如果能降低缺陷在各个环节的成本,缺陷带来的影响也就相应地减小了。全面的自动化测试、更小的迭代是降低发现、调查和修复缺陷成本的有效方法。自动化测试是贯穿本书多个章节的核心内容,而通过让设计持续演进(第 11 章)和持续集成(11.5 节),可以把发现和修复缺陷的周期降到小时,甚至分钟这种级别。
缩小缺陷的影响面
在大型商场等公共场所中都设置有防火墙,它可以在一个区域着火的时候紧急阻断火势,避免影响其他区域。软件设计也一样:有没有什么办法能及时阻断缺陷问题的传播?通过把软件划分为更合理的设计单元,定义清楚设计单元之间的依赖、接口和契约,并采取契约式设计等手段,就可以起到防火墙的效果,从而降低缺陷带来的影响。
1.3 易于理解
代码是一种很特殊的产品。一旦它被写出来,就会被一遍遍地阅读。代码被反复阅读的原因是多样的,有时候是为了修复缺陷,有时候是为了理解其背后的原理或者实现了什么功能,还有时候是为了复用或者是在原来的基础上增加新的功能。
研究数据表明,代码在其生命周期中被阅读的时间,是编写代码所用时间的 10 倍。所以,如果代码编写得不容易理解,那么即使它实现了所需的功能,也很难被称为好的代码。《计算机程序的构造和解释》[3]的作者 Harold Abelson 有一个著名的观点:
计算机程序首先是用来给人读的,只是顺便用于机器执行。
——Harold Abelson
1.3.1 为什么代码难以理解
写出易于理解的代码不容易,写出不容易让别人理解的代码却是再容易不过。这是代码的天性使然。代码天生充满各种细节,每一行代码都有它的意义。尽管如此,高质量的软件设计一定会刻意且安全地隐藏细节,从而提升代码的可理解性。
不良代码充斥着细节和意外
虽然代码充满各种细节,但这绝对不意味着没有理解某一行代码,就不能理解整体代码的具体工作。这样的代码是一种灾难,因为它挑战的是人类的记忆能力和认知能力。好的代码一定会隐藏一切细节,并且是安全地隐藏这些细节,即没有“意外”。
我们的理解力依赖于抽象、层次化、刻意地忽略这些认知技巧。如果代码缺少封装,导致内部状态可以被任意修改,就必然会带来意外,影响抽象。我们还会“望文生义”:如果一个类的名字叫作订单,那我们一般不会从里面寻找和用户管理相关的信息,所以如果哪个工程师把用户管理相关的职责混进了订单类中,就给以后维护代码的人留下了陷阱。
不要有意外,不要强迫他人为细节阅读代码。这是优质代码结构的力量,也是优秀工程师的素养。换句话说,代码的设计结构应该最大化地降低理解负担、尽量减少阅读代码的必要性。例如,让工程师:
能通过阅读 API 声明去理解代码,就不要去阅读 API 是如何实现的;
能通过观察代码结构(如类名、包名、方法名)去理解代码,就不需要去阅读代码的内部实现;
能通过阅读直接理解代码,就不需要去阅读文档和注释。
范式或概念不一致
相信不少读者有过这样的经历:初学面向对象编程时,看到代码中有数百个类会觉得无从下手。同样,刚从面向对象编程转为函数式编程时,也会觉得比较别扭。这是因为不同编程范式之间的话语体系不一样。不仅缺乏语言范式的共识会影响代码的可理解性,在编程规范、实现惯例、架构模式或设计模式,以及技术框架的应用等方面也同样需要共识。
业务概念的共识也是一个重要方面。例如,在维护一个订餐系统时,代码维护人可能需要了解菜单这个功能是如何实现的,但他可能找不到代码,因为代码中出现了一个不同的名字:餐品列表。这就是陷阱了。菜单和餐品列表或许并无二致,也不存在哪个概念更为精确的问题,但是如果大家使用的是两种语言,彼此语言不通,那么代码的可理解性就会受到影响。
扩展阅读:面条代码和馄饨代码
面向对象编程在今天已是主流。但是,在面向对象刚刚兴起的时候,开发者社区中有些人认为面向对象并不容易理解,因为“类实在是太多了”。多态也让代码变得更复杂,如果不运行,都不知道代码走到哪里了。在这些人看来,反倒是面向过程的代码更容易理解,因为不论函数有多长,只要耐心阅读就能了解一切细节。产生这种感觉的原因就是他们试图用一种编程范式来解读另一种编程范式。
面条代码(spaghetti code)[4]描述的是一种不良的代码设计风格,常常出现在缺乏封装的过程式设计中。不良的设计中总会出现一些很长的函数,它们如同彼此“缠绕”的面条一样,所以称为面条代码。
面条代码把所有的业务逻辑都放在一起,尽管复杂,但是只要你有足够的耐心,一行一行地读下去,总能理解代码实现了什么,这是它的优势。而如果你习惯了面条代码,自然会养成一行一行阅读代码的习惯,带着这种思维模式去阅读面向对象的代码,往往会觉得找不到线索。
馄饨代码(ravioli code)[5]是面条代码的反面,它认为结构是编程世界中的主导因素。在面向对象程序中,程序的本质是对象和对象的协作。一般来说,好的对象式设计包含许多小而独立的类,每个类分别实现一个比较有限的功能,通过组合这些功能有限的类,就可以实现丰富多彩的功能。
面向对象代码强调的是“概念”,是结构和协作。如果把关注点从关心“如何实现”的流程,转移到关心“做什么”的对象结构和对象协作上,那么理解面向对象的代码时就会更加快捷。打个比方,你打开本书的目录,发现对“由外而内的设计”这一章特别感兴趣,就直接定位到这一章开始阅读,这便是面向对象的理解方法。如果你不关心目录,而是从第一页开始逐行阅读,逐行找到对代码质量的介绍,就更类似于面向过程的理解方法。
1.3.2 提升代码可理解性的关键
降低代码的复杂性,是提升代码可理解性的关键。正如著名的计算机科学家 Tony Hoare 所说:“我相信设计软件的方式有两种:一种是使软件足够简单而明显没有缺陷;另一种是使它足够复杂,以至于没有明显的(可被轻易发现的)缺陷。”Hoare 表面上说的是缺陷,其本质就是代码的可理解性。
降低复杂性和许多编程实践密切相关,如更好的命名、一致的业务概念、更好的设计结构、尽量减少不必要的设计元素、减少重复、增加设计契约和测试的描述能力等。在本书的后续章节中,我们将依次展开这些概念。
此外,我想特别提一下与可理解性密切相关的非技术因素。经验表明,这比更多的技术技巧更有力量:一个程序员在编写代码的时候,是否思考过别人会如何阅读这段代码?又应该如何做,才能尽可能减少别人理解这段代码的成本?
我常常发现,凡是能真正把他人怎么阅读代码放在心上的软件工程师,即使一开始不具备非常好的设计技巧,随着时间的推移,也能很快学会这些技巧。心中有他人是非常重要的意识和素养。
有一些手段可以增强这种意识,例如,在极限编程中有一个结对编程的实践。在结对编程实践中,由于是两个人一起编程,所以他们需要随时考虑对方能否理解当前的代码。这会不断增强“代码将要被其他人阅读”的意识,迫使自己选择更合适的命名、简化设计结构、增加必要的注释等,从而有效提升代码的可理解性。
1.4 易于演进
软件之所以叫作“软”件,是因为它天生就应该是便于修改的。演进是软件的最本质特征。
1.4.1 演进是软件的最本质特征
软件的演进特征是软件和人类创作的其他东西的根本区别所在。例如,我们生产一个汽车零件,在生产完成之后它就是预期的样子。如果对这个零件的要求发生了变化,会去生产一个新的型号,而不太会在原来的基础上重新加工。但是,软件是不一样的。在实现了早期功能之后,软件并不会停止“生长”,而是会在后续的业务发展中被持续注入新的功能或者放入新的使用场景中。软件应该是“软”的。
要做到软件是“软”的并不容易。设计不良的软件,在经过多次修改之后,往往会变得混乱不堪,看起来就像是补丁摞补丁的衣服。甚至对于初始设计良好的软件,如果缺乏有效的演进策略,结果也同样是糟糕的。这也就是人们常说的,代码在演进过程中容易变得腐化。
1.4.2 为演进而设计
为了让软件具有好的演进能力,我们需要让它能方便地演进、安全地演进。良好的设计结构、自动化测试和简单设计的理念都是实现易于演进的设计的重要方面。
良好的设计结构
一个软件中有许多不同的关注点。例如,在电商系统中,订单的支付行为意味着成交,而成交可能带来积分。如果改动一个关注点,如支付,还会影响到用户积分,就会让问题变得更加复杂。好的设计,能够把那些似乎藕断丝连的逻辑巧妙地分解开,形成正交的设计,让它们互不影响。正交设计是增强代码演进能力最重要的手段,如图 1.3 所示。
![](https://static001.geekbang.org/files/resource/ebook/100011/image00277.jpeg)
有一些好的设计原则有助于形成正交设计。例如,观察设计中相关因素的变化频率,把容易变化和不容易变化的部分分离开,就能减少变化影响。再比如,根据变化的方向来识别设计对象的职责,这就是面向对象设计中的单一职责原则。此外,通过恰当的抽象,让代码在面临新的业务场景时更容易扩展,甚至完全无须修改原来的代码,这就是面向对象设计中的开放 - 封闭原则。本书在第 5 章将会讨论这些和演进能力密切相关的设计原则。
自动化测试
设计必须安全地演进,增加的新功能不可以破坏既有的功能。当业务场景变得越来越复杂,靠人记住所有的业务场景,或者靠人进行回归测试,显然是一个不可能完成的任务。因此,具有自动化测试的代码,往往在演进方面的表现也更好。本书的第 7 章和第 10 章将会介绍如何使用自动化测试保障设计的演进能力。
简单设计
要警惕一种可能会伤害到设计演进能力的“前瞻性设计”做法——为未来所做的“预留设计”。有时候,有些开发者喜欢在设计中留下某些“前瞻性”,如在数据库中加入几个叫作“预留 1、预留 2、预留 3”的字段,在代码中加入一些想象出的未来需要的扩展点等。可惜事与愿违,它们往往不会增强软件设计的适应性,还可能适得其反。其道理在于:今天你所做的任何决定,都是软件未来变化的约束。预留设计当然也是一样。只有那些恰好产生在预留方向上的新需求,才可以方便地演进,对于其他的变化方向,则无能为力。这是把“软”件当成“硬”件的一种思维模式。这种错误的设计方式,也被称为过度设计或大规模预先设计。本书的 11.2 节 将会讨论与此相对的简单设计原则。
1.5 易于复用
复用是人类文明发展的重要推动力。很难想象,一家制造汽车的企业需要从最基本的 JK、RS 等逻辑电路开始构造控制系统,更不可能自己去制造晶体硅。但是,在软件行业,复用(特别是业务层面的软件复用)并不像想象起来那么容易。
1.5.1 设计质量决定了复用能力
软件是信息制品。信息制品的典型特点是复用的边际成本极低。例如,一个设计良好的登录模块,既可以用于学生的学籍管理,也可以用于购物网站,还有你能想到的各种需要身份认证的场景。
尽管软件行业在框架层面的复用已经取得了突出的进步,如平台级的 k8s、框架级的 Spring等,但是在业务层面,还有许多业务组件都必须从头写起,很难在不同的场景下复用。业务的丰富性、多样性固然是一个原因,但是设计边界和设计职责的不合理、过度复杂的依赖等,也是阻碍复用的重要因素。
1.5.2 提升复用能力的手段
通过提升设计质量,软件的复用能力可以得到极大的提升。概括来说,选择合适的复用粒度,定义清晰的设计职责和设计契约并很好地管理依赖,是提升代码复用能力的重要手段。
选择合适的复用粒度
在多大粒度上复用是一个有挑战性的问题。复用粒度越大,复用价值也就越大,不过复用的机会往往更小。所以,我们看到标准函数库很容易被复用,但是没有太多人提及这是一种“复用”——因为大家对这个操作已经习以为常,它给效率带来的提升是有限的。业务模块的复用价值很大,但很多时候难以被复用,因为总是会有那么一点看起来不明显的区别阻碍对业务模块的复用。
时至今日,业务模块的复用已经有了更好的理论基础,而且经过了实践的检验。这就是以领域为中心的设计。通过恰当的确定问题域的边界,如把一个订餐系统切分为用户、订单、支付、配送、消息通知等子域,并保持各个子域边界之间的抽象和隔离,就可以大幅提升问题的通用性,从而增加复用机会。通过这样的方式,可以发展出一大批专门的业务服务,已经很好地实现了商业化的短信发送、地图服务、聊天消息等就是其中的典型例子。本书的第 4 章和第 8 章将分别讨论如何进行领域建模及如何基于领域模型指导软件实现。
需要特别提及,代码的“复制 - 粘贴”不是复用,它是复用的反面。复用是一种几乎零成本的、在新场景下可安全使用既有设计资产的活动。尽管复制 - 粘贴代码似乎也节省了一点编码成本,却为以后的维护埋下了隐患。例如,如果后续代码在某些方面有能力增强,那么其他的副本要不要一起跟着修改?如果两段代码看起来很相似,仅有一点点不同,会不会增加阅读者的负担?一般来说,需要对代码进行复制 - 粘贴才能进行的所谓“复用”,是复用能力不足的表征。
清晰的设计职责和设计契约
可靠的复用必须满足两个条件。第一,被复用模块的职责必须清晰,这样别人才可以知道该不该复用、能不能复用。第二,被复用模块的实际行为必须和承诺的职责相一致,这样才能被可靠地复用。这就是清晰的设计职责和设计契约。本书第 6 章和第 7 章将深入讨论设计职责,第 10 章将讨论更为严谨而有效的契约式设计。
很好地管理依赖
在大多数情况下,软件模块需要依赖其他模块才能正常工作。拔出萝卜带出泥,是影响复用的一个很常见的问题。
我曾经见过一个软件系统,它早期基于微软的技术栈开发,代码中各处都充满了对微软的某个编程框架的依赖,如随处可见的 AfxMessageBox。后来,当尝试把这个系统迁移到 Linux 环境中时,就不得不在各处做改动。
在软件设计中,很好地管理依赖是优秀软件工程师的基本功。例如,尽量依赖抽象的接口而不是具体的实现、依赖设计小而聚焦的接口而不是大而全的接口等。本书的第 6 章将会介绍依赖倒置、接口分离等设计原则,它们都是提高复用能力的有效手段。
1.6 小结
软件设计和编码首先服务于业务价值和业务目标。好的设计和代码,其最终的评价标准还是要回到外部特征上来。
本章介绍了优质代码的 5 个重要外部特征,其中前两个特征是关于当前价值的:需要满足当前的业务诉求,同时尽量减少缺陷,或减少缺陷造成的影响。后面 3 个特征聚焦长期价值,即通过提升可理解、可演进和可复用能力,让代码未来的维护成本、演进成本尽量小,并通过高效复用提升其作为软件资产的能力。
图 1.4 总结了本章的核心内容。
![](https://static001.geekbang.org/files/resource/ebook/100011/image00278.jpeg)
公开
同步至部落
取消
完成
0/2000
笔记
复制
AI
- 深入了解
- 翻译
- 解释
- 总结
![](https://static001.geekbang.org/resource/image/d0/e4/d0880927e2a522a038be2d04bf19dfe4.png)
本文深入探讨了软件工程师需要具备的专业鉴赏力和对优质代码外部特征的重视。强调了软件开发是一个持续建立认知的过程,需要加快认知的过程和增加设计的弹性。高质量沟通的重要性以及优秀软件工程师需要注重沟通和结构化的探索也得到了强调。文章重点讨论了实现了期望的功能这一外部特征,指出了需求问题的普遍性,并探讨了需求问题产生的原因和解决方向。另外,文章还强调了缺陷尽量少的重要性,以及提出了解决缺陷问题的方向。文章还提到了降低代码的复杂性是提升代码可理解性的关键,以及软件的演进特征和为演进而设计的重要性。整体而言,本文对软件工程师在实际工作中需要关注的技术特点和重要概念进行了深入的探讨,为读者提供了有益的技术指导和思路。
2024-01-04给文章提建议
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《软件设计:从专业到卓越》
《软件设计:从专业到卓越》
立即购买
登录 后留言
精选留言
由作者筛选后的优质留言将会公开显示,欢迎踊跃留言。
收起评论
[1]:k8s(Kubernetes)是一种开源的容器编排引擎。
[2]:Spring 是一种著名的 Java 开发编程框架。
[3]:AfxMessageBox 是早期 Windows 编程框架提供的一个消息提示 API。