查看原文
其他

为什么你的代码如此难以理解?

2018-03-14 Stephen Young 程序人生

点击上方“程序人生”,选择“置顶公众号”

第一时间关注程序猿(媛)身边的故事

图片源自:电影《降临》

“我到底在想些什么?!?”


凌晨1:30分,我正盯着我不到一个月前写的一段代码。当时一看简直是件艺术品!不但完全说得通,而且优雅、简单、令人叹为观止。而此时再看到这段代码的时候,却和当初的情况完全不同。明天就到截止日期了,而我在刚才还发现了一个bug。现在再一看,当时所认为的简单和逻辑也都说不通了。可以肯定的是,这段代码是我写的,那么我应该有足以能够理解这段代码的智慧吧?


然而这种情况我已经经历过不止一次了,于是我便开始认真地去思考一个问题,那就是:为什么我的代码在我开始编写的时候很清楚,而当我几周或几个月后回过头再来看的时候,它们却变得如此让人费解。

留意下:这图会动

问题1,过度复杂的心智模型

想要理解为什么当你休息了一段时间后,再回头看自己写的代码,会令你感到费解的原因。首先第一步,是要理解心智模型的问题。你写的所有代码,几乎都是在试图解决现实世界的问题。在你写代码之前,你需要理解你所要试图解决的问题。这往往也是编程里最难的一步。


为了解决现实世界的问题,我们首先需要形成该问题的心智模型 ,并以此作为你的编程目的。接下来你需要形成实现编程目的的方案模型,我们姑且称为语义模型(semantic model)。不要混淆你的编程目的和此目的的解决方案。我们倾向于主要考虑解决方案方面的问题,而常常忽略构建目的的模型。


你下一步是尽可能地构建最简单的语义模型,这是第二容易搞错的事情。如果你不花时间去真正理解你将要解决的问题,那么你会在写代码时在模型这一块跌倒。另一方面,如果你真的考虑过你将要尽力做的事情的话,你通常只是想到一个十分简单的模型,但这足以帮你达到你最初的目的。


如果您想要易于维护的简单代码,那么尽可能消除会出现意外的复杂性尤为重要。一般我们试图解决的问题都很复杂。如果你不需要,那就不要增加。


问题2,语义模型到代码的糟糕转化

一旦你形成了最好的语义模型,你就可以将其转化为代码了。我们称之为句法模型(syntactic model)。你接下来就是将语义模型的含义转换成计算机能够理解的语法。


如果你有个非常不错的语义模型,但在转化为代码时搞砸了,那么你在之后某个时间段再回头修改代码时,你将会比较痛苦。当你脑子里还有语义模型时,把你的代码映射到语义模型上还是会比较容易的。例如,此时你回忆起变量“x”实际上代表一条记录被创建的日期、而“y”代码记录被删除的日期,这并非难事儿。然而当你3个月后脑海中已经没有这个语义模型,再回过头来看这段代码时,就很难理解同样的变量名了。


把语义模型转化为句法的任务就是尽可能多地留下线索,这将会让你在今后回查时,能够重建起当初的语义模型。


所以,你知道该怎么做了吧?


类结构和命名

如果你在使用面向对象语言,请尽量让你的类结构和命名靠近语义模型。领域驱动设计(Domain Driven Design) ❷ 是一种非常重视这种实践的运动。即使你对DDD方法并不买账,你也应当仔细考虑类结构和命名。每个类都是你留给自己和其他人的一条线索,它会有助于你在将来返回时重建你的心智模型。


变量、参数和方法命名

尽量避免普通的变量和方法命名。当“PaySalesCommision”更有意义时,就不要把方法命名为“Process”;当它应当是“currentContract”时,就不要把变量命名为“x”;当“outstandingInvoices“更好时,就不要把参数命名为“input”。


单一功能原则(Single responsibility principle,简称SRP)

SRP ❸ 是面对对象设计原则的核心之一,关联着好的类和变量命名。它认为,任何类或方法都应该完成一个单一的功能,只能是一个单一的功能。如果你想给类和方法一个有意义的名字,那么它们需要有一个唯一的明确的目的。如果一个单一类从数据库中读取和写入、计算销售税、通知交易客户并生成账单,那么你就可能无法给出一个合适的名字。我经常停滞在重构一个类上,因为我很难给它一个足够短的名字来描述它所做的一切。为了更多地讨论SRP和其它面向对象原则,可以参考我的博文《面向对象设计》。


适当的注释

如果你因为某些原因需要去做某件事,而此时还不能让代码变得清晰,为了不让你将来留下遗憾,那就留下个注释来说明你为什么不得不那样做。注释往往很快就会过时,所以我宁可让代码自描述,注释用来说明为什么你不得不那样做,而不是它如何做。


问题3,没有足够的组块

在心理学中,组块被定义为单个实体的信息分组。那么,这该如何应用到编程上呢?作为一名开发者,在你积累经验时,你会开始看到重复的模式在你的解决方案中反复出现。极具高度影响力的设计模式:《可重用的面向对象软件》(Elements of Reusable Object-Oriented Software)是第一本整理和解释一些模式的书。尽管如此,组块不仅仅用在设计模式和面向对象。在函数式编程(FP)里,存在大量的众所周知的标准函数,它们具有相同的用途。算法是组块的另一种形式(稍后会详细介绍)。


当你使用组块(设计模式、算法和标准函数)时,它会让你停下来思考,你编写的代码是如何运行的,而不是考虑它做了什么。这缩短了你的语义模型(你的代码)和句法模型(你脑中的模型)的距离。这个距离越短,在后期你就越容易重建你的心智模型。


图片源自:电影《降临》


问题4,费解的用法

目前,我们主要讨论了如何结构化你的类、方法和变量命名。心智模型的另一个重要部分是理解这些方法是如何被使用的。再次强调,当你最初形成心智模型时,这是很清楚的。当你后来返回时,就非常难以重建你的类和方法的所有有意图的用法了。通常这是因为不同的用法散布在你的程序其它地方。有时甚至跨许多不同的项目。


我就是在这种情况下发现测试用例是非常有用的。除了相应地知道一个修改是否破坏了代码的明显好处之外,测试还为你的代码提供了一整套的示例用例。你不必搜遍上百个文件,只需通过查看测试就能得到引用的全景。


注意为了达到这个目的,您需要有一组完整的测试用例。如果你的测试仅仅覆盖了一部分,而你认为测试是完整的,那么你之后将陷入困境。


问题5,不同模型之间没有清晰的路径

通常,你的代码从技术角度看,常常是非常好、非常优雅的,但是从程序意图到语义模型、再到代码会存在非常不自然的跳跃。考虑你选择的一堆模型的透明性是很重要的。从程序意图到语义模型,再到代码的过程需要尽可能平滑。你应该通过每个模型看到对应问题的每个模型的各个方面。多数情况下,最好选择一个特定的类结构或算法,而不是孤立地考虑它的优雅,而是能够连接各种模型下,并为重构意图留下一条自然的路径。当你从抽象的编程意图走到具体的代码时,你所做的选择应该被清晰的代码所驱动,这样你就可以在接下来表现出更抽象的模型。


问题6,发明算法

作为程序员,我们经常认为,我们为了解决问题而发明着算法。但这几乎是不可能的。在多数情况下,已经有现成的算法可以被组合在一起解决你的问题了。像最短路径搜索法、字符串相似度算法、粒子群算法等。大部分编程是以正确的组合、选择现存算法来解决你的问题。如果你正在发明新算法,那么,要么你不知道合适的算法、要么你正忙于你的博士论文。


图片源自:电影《降临》

最后总结

作为一名程序员,你的目标是构建一个最简单的语义模型来解决你的问题。将语义模型尽可能地转换为一个句法模型(代码),并提供尽可能多的线索,便于你之后无论哪个人看你的代码,都能重建像你最初脑子里的、相同的语义模型。


设想一下,当你走过被你的代码照亮的那片丛林时,也要记得在你的身后留下面包屑。相信我,当你需要找到回去的路时,丛林仍将充满着黑暗、朦胧和不详。


这听起来很简单,实际做起来是很难的。


特别感谢Nic Young和Ulvi Guliyev对本文的支持。


原文地址:https://medium.com/on-coding/why-your-code-is-so-hard-to-understand-83057c115a2b

❶ 心智模型是用于解释个体为现实世界中之某事所运作的内在认知历程。http://zh.wikipedia.org/wiki/心智模型

❷ 要通过创建领域模型来加速复杂的软件开发,就需要利用大量最佳实践和标准模式在开发团队中形成统一的交流语言;不仅重构代码,而且要重构代码底层的模型;同时采取反复迭代的敏捷开发方法,深入理解领域特点,促进领域专家与程序员的良好沟通。http://baike.baidu.com/view/3705331.htm

❸ 马丁把功能(职责)定义为:“改变的原因”,并且总结出一个类或者模块应该有且只有一个改变的原因。一个具体的例子就是,想象有一个用于编辑和打印报表的模块。这样的一个模块存在两个改变的原因。第一,报表的内容可以改变(编辑)。第二,报表的格式可以改变(打印)。这两方面会的改变因为完全不同的起因而发生:一个是本质的修改,一个是表面的修改。单一功能原则认为这两方面的问题事实上是两个分离的功能,因此他们应该分离在不同的类或者模块里。把有不同的改变原因的事物耦合在一起的设计是糟糕的。http://zh.wikipedia.org/wiki/单一功能原则

- THE END -

本文作者:Stephen Young

如果你有好的原创文章想与大家分享,欢迎投稿。


征稿要求:

①稿件字数以800~1500字左右为宜,多于2000字的文章在手机上阅读起来比较麻烦,少于800字的文章看起来不过瘾;

②你有自己拍的适合做文章插图的照片也可一并附上~如果不方便,程序和小七也会帮你配图~



加程序人生编辑们的微信,备注#投稿#:


程序 微信ID:druidlost  

小七 微信ID:duoshangshuang

点击图片get往期内容

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存