`
maishj
  • 浏览: 84054 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

为什么你的代码如此难以理解(转)

 
阅读更多

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

 

凌晨1:30分,我正盯着不到一个月前我写的一段代码。当时它看起来像是件艺术品,全部是可理解的,优雅、简单、让人叹为观止。这一切都不再了,明天是我的最后期限,数小时前我发现了一个bug。当时看起来的简单和逻辑再也说不通了。可以肯定的是,如果我写代码,我应该足以聪明到理解代码?

 

经过了多次这种经历以后,我开始认真思考,为什么我的代码在我编写的时候很清楚、而当我数周或数月后回头看的时候,它们却那么费解。

 

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

 

为了理解当你间隔一段时间返回到你的代码、却发现代码难以理解的第一步,就是理解我们如何从心智上建立问题模型。你写的几乎所有代码都是尽量解决现实世界的问题。在你写代码之前,你需要理解你正试图解决的问题。这常常是编程里最难的一步。

 

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

 

你接下来的步骤是形成可能最简单的语义模型。这是容易搞错的第二步。如果你不花时间去真正理解你正试图解决的问题,你将在写代码时被绊倒在模型上。另一方面,如果你真正考虑了你正尽量做的事情,你经常得到一个非常简单的模型,这足以让你掌握最初的意图。 如果你想容易地维护简单的代码,就尽可能多些地消除意外的复杂性。我们正试图解决的问题是足够复杂的。如果你不必那么做,就不要把意外的复杂性增加进来。

 

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

 

一旦你尽全力形成了最好的语义模型,那么就到了把它转化为代码的时候了。我们称之为句法模型(syntactic model)。你正试图把你的语义模型的意义转化为计算机能够理解的句法。

 

如果你有非常不错的语义模型、而在转化为代码时搞砸了,那么在你需要在以后某个阶段回头修改代码时,你将比较痛苦。当你脑子里还有语义模型时,把你代码映射到语义模型是容易的。回忆起变量“x”实际上代表一条记录被创建的日期、而“y”代码记录被删除的日期,这是不难的。当你3个月后再回来看代码,你的脑子里将没有这个语义模型了,因此无法理解同样的变量名字。

 

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

 

好了,你该怎么做呢?

 

类结构和命名

 

如果你在使用面向对象语义,请尽量让你的类结构和命名靠近语义模型。领域驱动设计(Domain Driven Design)【注2】是在这种练习上投入了相当重要性的一种运动。即使你没有相信完全的DDD方法,你也应当非常小心地考虑类结构和命名。每个类都是你留给自己和其他人的线索,它帮助你在将来返回的时候重建你的心智模型。

 

变量、参数和方法命名

 

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

 

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

 

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

 

适当的注释

 

如果因为某种原因,你不能让代码变得清晰,你同情将来的自己,需要不得不做些事情,那就留下注释来说明你为什么不得不那样做。注释倾向于快速地变得陈旧,因此我宁愿尽可能让代码自描述,注释用来说明为什么你不得不那样做,而不是它如何做。

 

问题3,没有足够的组块

 

心理学上的组块被定义是,把信息组块定位为单一的实体。那么这该如何应用到编程上呢?作为一名开发者,在你积累经验时,你开始发现你解决方案里反复出现的模式。极具影响的设计模式:《可重用的面向对象软件》是第一本整理和解释一些模式的书。尽管如此,组块不仅仅用在设计模式和面向对象。在函数式编程(FP)里,存在大量的著名标准函数具备这同样的目的。算法是组块的另一种形式(后续会更多)。

 

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

 

如果你有兴趣了解更多FP里的函数,请移步到我的文章面向web开发者的函数式编程。

 

问题4,费解的用法

 

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

 

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

 

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

 

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

 

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

 

问题6,发明算法

 

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

 

总结

 

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

 

设想一下,当你走过你代码的被照亮的森林时,你在身后留了面包屑。相信我,当你需要找到回去的路时,森林将充满了黑暗、朦胧和不详的预感。

 

听起来容易,实际做起来是很难的。

 

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

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

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

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

分享到:
评论

相关推荐

    C语言常见几中图形打印代码

    其中循环是重点内容,对个人的逻辑分析以及程序执行顺序的理解都要求较高。大家都懂得:有学无习难以提高能力。软件编程更是如此,听十遍,想十遍,不如动手练一遍。下面利用循环结构实现了几个C语言入门的小实例,...

    重构Python代码的六个实例

    太多的嵌套会使代码难以理解,这在 Python 中尤为如此,因为 Python 没有括号来帮助区隔不同的嵌套级别。 阅读深度嵌套的代码容易让人烦躁,因为你必须理清哪些条件属于哪一级。因此,我们应尽可能减少嵌套,如果两...

    Eziriz .Net Reactor v4.2.8.4加密程序

    简单的混淆使你的源代码更加难以阅读(但是还能读,只不过多费点时间)。 混淆工程将有意义的类命,方法名,属性名混淆为无意义的变代码混淆量名字。 例如,它可能会将计数器变量counter混淆为“A4DF3CV89G”。 混淆...

    Effective+C+++3rd+chm+中文版(代码加亮)

    然而,如果不经训练就贸然使用,C++ 也会导致不可理解的,难以维护的,无法扩展的,低效率的,错误百出的代码。 本书的目的在于引导你如何高效使用 C++。我假设你已经熟悉了作为语言的 C++ 并有使用它的一些经验。...

    Ruby宝石,可帮助您重构遗留代码-Ruby开发

    Suture:hospital:Ruby的重构工具,...从本地开发到暂存环境,甚至在生产中,缝合线都为重构难以理解的代码的整个生命周期提供了帮助。 Video Suture在Ruby Kaigi 2016上首次亮相,这是一种我们可以减少重构的恐惧的方法

    R语言数据关系型图表绘制代码(含数据、代码以及介绍)

    在先前的很多回归分析相关的推文中,大多都在过程中提及了一些简单的可视化方法,以帮助理解回归中的变量响应关系。在这些作图方法中,有些是特定功能R包自带的可视化方法,有些通过ggplot2绘制,还有部分直接使用...

    C#微软培训资料

    18.2 在 C #代码中调用 C++和 VB 编写的组件 .240 18.3 版 本 控 制 .249 18.4 代 码 优 化 .252 18.5 小 结 .254 第五部分 附 录 .255 附录 A 关 键 字.255 附录 B 错 误 码.256 附录 C .Net 名字空间...

    CHAT GPT的这些知识你了解吗?

    ChatGPT是人工智能技术驱动的自然语言处理工具,它能够通过理解和学习人类的语言来进行对话,还能根据聊天的上下文进行互动,真正像人类一样来聊天交流,甚至能完成撰写邮件、视频脚本、文案、翻译、代码,写论文等...

    Effective C++.mobi kindle可用

    Effective C++.mobi ...然而,如果不经训练就贸然使用,C++ 也会导致不可理解的,难以维护的,无法扩展的,低效率的,错误百出的代码。 wizardforcel. Effective C++ (Kindle 位置 164-169). GitBook. Kindle 版本.

    Vim实用技巧

    匪浅,但却难以帮助你像 Vim 高手一样思考问题。 当我发现本书正是以这种“技巧”的方式组织章节时,你一定能理解我所持的怀 疑态度。这区区上百条技巧怎么能让我举一反三呢?但当我翻了几页本书之后,我才 意识到自己对...

    正则表达式30分钟入门教程

    当然,如果你看完了这篇教程之后,发现自己明白了很多,却又几乎什么都记不得,那也是很正常的——我认为,没接触过正则表达式的人在看完这篇教程后,能把提到过的语法记住80%以上的可能性为零。这里只是让你明白...

    设计模式自动化

    尽管维护每行代码的成本如此高昂,但我们依然编写了大量的样板代码,而这其中有很大一部分可以由更智能的编译器来替代完成。实际上,多数模板代码只是重复地实现设计模式,而其中一部分模式已被理解得十分透彻,只要...

    Maven2 的新特性.7z

    )如此大的变动到底换来了什么样的结果? 1. 更快、更简单 比起 Maven1 那不急不慢的运行速度,Maven2在速度上有了质的飞跃,甚至与Ant相比也毫不逊色(当然,下载不算)。除此之外,"简化工作,使用业界公认的最佳...

    过失和AI的人类用户-研究论文

    通过在人类决策者和该决策的后果之间插入一层难以理解,不直观且统计得出的代码,人工智能会破坏我们对错误选择责任的典型理解。 该文章认为,人工智能的独特性质导致过失导致四个复杂性:1)无法预见的人工智能将...

    飞鸽传书(IPMessenger) 源码

    这样循环操作,最终完成文件的传输,这个过程比较难以理解。  有了上边的知识,开发文件传输功能就变得简单多了,文件的接受也可以类推了,同样开启一个线程维护接受文件链表,逐次接受身下的文件,链表为空时。...

    [高性能MySQL(第3版)].Baron.Scbwartz等.扫描版

     纵观全书,作者推荐的工具、实战案例及经验过的诊断技术,可大大提高你的性能急救技能,以及加深对MySQL本质的理解。然而,本书最值得推崇的,还是其在探讨性能的同时,将数据库结构的客观方面纳入思考,这是其他...

    蒙特卡罗方法:使用蒙特卡罗方法计算 pi-matlab开发

    蒙特卡罗方法通常可用于解决物理和数学中难以应用分析方法的问题。 这些方法使用随机数并使用概率论来解决问题。 这个方法可以通过解决小问题来理解... 这段代码演示了一个如此简单的问题; 使用蒙特卡罗方法计算 pi。

    VC实现炫眩qq界面的模拟(附源码)

    上述代码功能是将预先在beautyQQ.bak皮肤里面定义好的对话框图像资源与对话框绑定,资源内部ID号为103(注意,该资源编号是编辑皮肤的时候就是由用户定义的,一般从101开始)。从上面的BindRes2CtrlbyHWND()函数...

    操作系统(内存管理)

    为什么必须管理内存 内存管理是计算机编程最为基本的领域之一。在很多脚本语言中,您不必担心内存是如何管理的,这并不能使得内存管理的重要性有一点点降低。对实际编程来说,理解您的内存管理器的能力与局限性至关...

Global site tag (gtag.js) - Google Analytics