【译文】好代码的11个方面

关于代码质量的课程始于学习编程的最初几周,那时新手会学到变量命名的基础知识,并被告知为什么编程语言有注释。在无数的博客文章和每一次关于拉取请求的辩论中,这些课程都在继续。

无论你是回避还是接受,代码质量培训都会贯穿你的整个职业生涯。

但是,我们很容易忽视其中的原因。

持怀疑态度的人说:漂亮的代码会分散注意力,就像大教堂上的石像鬼或巴洛克式建筑的曲线一样。

极致主义者说:你是否有过这样的经历:在调试过程中,你需要翻阅大量的资料,感觉整个世界都在你脚下坍塌。要达到 10 倍的速度,不是靠调试速度提高 10 倍,而是靠编写根本不需要调试的代码。

这位大师说:那些只会让人分心的代码并不是优质代码。

我一直致力于澄清软件工程中所有模糊的术语。学生在第一堂课上就学会了代码知识和耦合的定义。我的一篇研究论文给出了 “依赖性 “的严格定义

今天,我将这一探索进行到底:什么是高质量代码?

对于这个问题,不可能有简短详尽的答案。问 “什么是好代码 “就好比问 “化学反应是如何发生的?这是整个领域的主题。

但我们可以更容易地问,追求代码质量的目的是什么,即使实现代码质量是一门值得终生钻研的技艺。要认识高质量代码,我们首先要问:高质量代码应具备哪些外部属性内部属性

外部属性

好代码就是好代码

我们从老生常谈开始。所有关于质量的讨论都以设计对象的最终目的为基础。绝大多数代码的目的都是作为软件执行,以实现某些目标,无论是娱乐大众、帮助纳税、传输数据还是测试其他代码。也有少数代码是为了其他目的而编写的:实验是否可行、解释库或算法的示例,以及做一些小把戏(如打印自己的源代码)的代码。

就所有这些而言,未能实现其目的的代码不可能具有外在质量,就像一个废弃的建筑工地不可能是一个有用的建筑一样。

但这并不能证明一心只想让程序运行的做法是正确的。还有一些非功能性要求,如性能。如果迟缓会让用户回到纸笔时代,那么软件就不可能有质量。

尽管整个企业可能都致力于帮助软件产品实现其目的,但这并不能使所有其他功能从属于 “代码质量”。代码是否优质不能完全取决于工程领域之外的因素。一个软件包没有在市场上销售并不能说明它不好,而自上而下要求人们使用它的指令也不能说明它好。

因此,好的代码就是已完成的代码。

但我们不能就此止步。这并不是故事的结束。这只是开始。

因为如果你说 “我们已经完成了工作并为客户创造了价值”,当你向老板解释为什么添加一个祝用户 “生日快乐 “的功能需要几年时间时,这并不能成为你的借口。当你花了 4 天时间调试一个原来只是错别字的问题时,这也不是你自己的借口。完成的代码不是好代码。

好代码是可以理解的

根据一种定义,工程师是深入理解系统的人。

由此可见,代码要具有良好的工程性,就必须是易懂的。

有时,比如教学代码,这就是它的全部目的。

因此,你希望代码是易懂的。但谁能看懂呢?

你自己和需要阅读它的人。

或者更具体地说:在这些人需要阅读代码的时候。

愚蠢的工程师得到了一项新技能,可以将他的一段代码缩减 10 倍。他一边说 “别人不会懂”,一边拒绝学习。由此可见,他对自己的期望很低,而对他人的期望也很低。他躲在自己的技能舒适区内。

傲慢的工程师拥有一技之长,并知道它将会有效。”任何关心这段代码的人都应该能够学会我所使用的技术”。如果读者有适当的雄心壮志,那么这个选择就是正确的;但如果读者关心的是其他问题,那么这个选择就不正确了。但是,不知情的决定不可能是好的决定。他躲进了自己的舒适区,无法产生共鸣。

任何一个人都可以通过走出自己的舒适区,了解哪些墙需要攀爬,如何攀爬,并通过搭建和放置梯子来帮助其他人来提高自己。

但是,工程师中的工程师打破了舒适区本身。他们不需要攀爬,也不需要梯子,因为追随他们的人将会突然发现自己身处高山之巅。

好代码是可进化的

软件不是一个时间点,而是一个系统。

只有极少数程序像博物馆的展品一样矗立在玻璃柜中,只为自己而存在,或展示一段凝固的过去。其余的程序则与其他程序、平台、成长中的企业和不断变化的客户紧密相连。它们与不断变化的世界息息相关。

而现在,当我们的生活被来自机器人工厂、通过卫星控制的船只抵达的屏幕所包围时,软件就是不断变化的世界。

不改变软件,就无法改变世界。每个软件工程师都肩负着构建易于改变的软件的职业重任。

具体来说,就是从一种理想状态转变为另一种理想状态。

我们必须避免编写难以改变的僵化代码。

同时,我们也必须防止出现脆性代码,因为这些代码很容易就会被修改得支离破碎。

好的代码易于扩展,难以破坏。设计的力量不在于它能做什么,而在于它不能做什么。

但为什么要为可能永远不会到来的未来做准备呢?我们不可能准确预测代码的变化方式。

然而,预测代码的变化却往往很容易。甚至是在哪里发生变化。而这正是创建可进化代码所需要的一切。

是的,如果在设计时假定随着生活轨迹的变化而需要做出某些改变,那是一种愚蠢的做法。但如果在设计时假定根本不会发生任何变化,则是更大的愚蠢。

内部属性

程序必须是正确的、可理解的和可发展的,这是一个崇高的目标。

如果说单个函数应通过测试、有少量分支并使用抽象类型,这是一个可以实现的目标。

然而,后者的总和就是前者。外在质量来自内在质量。下文将介绍这些特性:

好代码可以模块化分析理解

程序由文件组成。文件由声明组成。声明由行组成。

也就是说,程序是由一个个片段组成的。

每一块都有它的用途。

每执行一行,就会发生一次变化,而关于每一次变化,都可以有许多真实的陈述。这些语句中的大多数都无关紧要,而有些语句则对后面的行实现其目的至关重要。

但在某些程序中,还有第三类语句。有些关于程序状态的事实在某些行中变成了真,但直到某个更远的行要求它们为真时,这些事实就不再重要了。大多数程序行只与它们的邻居有关,而这两条程序行却通过虫洞握住了手,它们的命运纠缠在一起。一个地方的变化会导致宇宙另一端的断裂。

在优秀的代码中,每一行的目的都可以简单陈述。每一行都可以单独理解。对于每一行,我们都可以这样推理:如果程序状态的某个简单事实为真,那么运行这一行后,其他某个简单事实也将为真。每一行的假设和保证就像乐高积木一样组合在一起,形成简单而正确的函数,进而组合成简单的模块和简单的程序。

在糟糕的代码中,你读一个函数,问它是否有效,然后再读十几个函数才能得到答案。必须像玩 Jenga 一样小心翼翼地进行修改,以免塔倒塌。

好代码是设计出来的。坏代码是巧合。

好代码让程序员的意图更容易恢复

程序员梦想着一个新的实体。她的思维逐渐将梦想转化为机制,将机制转化为代码,梦想中的实体被赋予了生命。

一个新程序员走进来,看到的只是代码。但在他的脑海中,随着阅读和理解,模式逐渐浮现。在他的脑海中,代码将自己塑造成机制,机制将自己塑造成梦想。只有这样,他才能工作。因为事实上,对代码的修改就是对梦想的修改。

程序员的大部分工作是恢复创造者头脑中已有的信息。因此,创造者的工作就是尽可能地简化这一切。

但要做到这一点,却是一场持久战。

每一个命名决定都是一次探索,既要在读者脑海中勾勒出命名的真正目的,又要避免误解。

每一个功能,都是将行为雕刻成有意义的东西。

每一个模块,都是创造新词,赋予使用者新力量的探索。

通过每一个这样的步骤,我们向着理想攀登,使程序不是用机器语言编写,而是用梦想的语言编写。

据说,对于那些已经达到顶峰的人来说,他们只需在梦中对程序进行修改,程序就会立即生效。但是,即使是那些只完成了一部分的人,也会拥有强大的力量。

好代码能在单一地方表达意图

但是,从代码到设计仅仅简单是不够的。还必须易于将设计转换为新代码。

原子设计师走进一间正在施工的房间,房间宽敞明亮。”不!”他喊道。”我希望它是黑暗的,私密的。”在他面前的是一个庞大的工作计划,因为这一个指令要求他用画笔描绘成千上万次,并为其中的每一个物体做出新的选择。

位面设计师走进一个网站,说她想要一个暗色模式。在好的代码中,她会说出组成暗色模式的新颜色,然后就会有了。在优秀的代码中,她只需说出 “深色模式”,颜色就会被推断出来。

然而,这样的变化虽然简单,却往往需要在成千上万个地方进行调整,就像用画笔在千百个地方协调地描绘一样。

比特应该比原子更容易改变,因为它们生活在机器内部。

然而,它们可能更难,因为它们的数量实在太多了。

好代码是健壮的

如果每一行都有作用,那么每一行都必须正确。

也就是说,每一行都是一个新的机会,让错误在不经意间溜进来。

而犯错的容易程度是软件设计者所能控制的。

有些代码库非常险恶,在其中工作就像在大峡谷中走钢丝。有些函数需要查阅巨著才能正确调用。写入数据结构可能会产生无意义的结果。从数据结构中读取数据可能只能得到部分信息。

还有一些代码库更像是在乘坐电梯,甚至连刻意的努力都不会产生意外。在这样的代码中,应用程序接口有防护栏,任何滥用行为要么被禁止,要么只能通过喷涂红旗来实现。无论你怎么努力,对数据结构的任何写入都不会产生任何错误。只要能编译,就可能是有效的。

正如托尼-霍尔(Tony Hoare)所说,我们可以写出简单到明显没有错误的代码,也可以写出复杂到没有明显错误的代码。

如果你必须拼命思考才能检查出程序是否能正常运行,那么它很可能不能正常运行。

但对于好的代码,你几乎不需要思考。

好代码隐藏秘密

软件不是一个时间点,而是一个系统。

它不是一个系统,而是许多相互作用的系统。

每个系统都在不断变化。

但是,如果它的外表和行为都是一样的,就不会有人知道。

对于驾驶员来说,汽车工程师更改线路并不重要。除非汽车使用手册已经详细地告诉她,汽车的电气系统会有什么变化,而且她已经开始依赖这个系统。了解到汽车电池可以为 5 部手机充电整整 433 分钟才会熄火的人,才是能够发挥最大性能的人。但是,在一个瞬息万变的世界里,利用这些禁忌知识的人将会濒临毁灭。

当子系统的创造者的思想结合在一起时,子系统就会结合在一起,进行不该进行的对话,分享不该分享的细节。或者,当唯一的主人未能在自己的头脑中筑起一道防火墙时。

炙手可热者自诩无所不知。她创造的软件只能由她的 “全知全能 “同伴来开发。

大师的优点是什么都不知道。而这足以维护他的软件。

好代码隔离假设

最大限度地减少知识的使用是实现可发展性的途径。秘密只是极端,只有其拥有者才知道。每一次知识的使用都会将程序与 “过去的世界 “联系在一起,阻碍 “可能的世界 “的创造。

对于每一个数据,都有创造它的组件、使用它的组件以及介于两者之间仅仅传递它的组件。这些组成部分是像密封包裹一样传递数据呢?还是那些快递员在窥探其中的内容?

一个值从程序的一端传递到另一端。途中每一个将数值称为 “int “的函数都是将其变为浮点数的另一个障碍。而途中每一个调用该值的函数都是将该值转化为两个数字的另一个障碍。

物理世界充满了不可逆的变化。建造一座建筑,城市就会围绕着它成形;再把它烧掉,风中就会永远沾满它的灰烬。但在重塑比特世界时,程序员唯一的障碍就是自己。

好代码是开放的

程序处理的是一个领域,而程序和领域都可以通过无数种方式切分成多个集合。选项集!字段集格式集代表选项的字段格式集!

随着程序和领域的变化,这样的集合也会增大或缩小。优秀的代码在处理此类集合时,会尽可能不考虑集合的大小。

简单的人看到一个实体有两个可能的值,就会使用布尔值来构建程序。第二天,可能性增加到了三个。这就需要重写。

有了三种实体的集合,编写的程序可以处理每一种实体。然后有一天,其中一种实体被废弃了。如果程序在这一组中是开放的,那么相关代码就已经在一个可以丢弃的盒子里了。如果程序是关闭的,那么所有分支都将成为需要埋葬的骸骨。

思想开放的人会接受新事物。开放式程序也是如此。

好代码能充分发挥程序员的智慧

熟练的程序员会找到一份包含 10 条良好代码原则的清单。她逐一研究,经过多年的努力,终于掌握了这些原则。在她面前,令人费解的复杂程序就像石蜡一样矗立在那里;而在她的注视下,这些复杂程序现在已经融化,分离成一桶桶的水。

这条道路充满艰辛。每当直觉告诉她代码可以更简单时,她就会想办法。每一个难以调试的问题,她都在寻找如何避免。

然后她宣布 “就这样吧”。她表面上的精通为她带来了尊重,她的技能让其他人望尘莫及的问题迎刃而解。她接受了自己的巅峰位置,然后休息。

但是,有潜力成为大师的人并没有休息。他们注意到了掉落在桶外的蜡渣,并从中看到了找到新解释的机会。随着他们不断深入探寻,蜡桶逐渐溶解,展现出相互关联的整体。就像程序员学习代码库一样,他们已经走进了概念背后的梦想,现在已经准备好自己做梦了。

这份清单来自多年的观察、反思、研究、教学和完善。现在,你可以对它进行研究、批评、宣讲、嘲笑和扩展。

本文文字及图片出自 The 11 Aspects of Good Code

余下全文(1/3)
分享这篇文章:

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注