好代码的定义
1,定义
让我们来谈谈代码。
代码重要吗?当然,代码就是设计(Jack W.Reeves, 1992);代码是最有价值的交付物。
我们需要好代码吗?在给“好代码”下个定义之前,这个问题无法回答。
那么,究竟什么是好代码?
闻到硝烟味了吗?哦不,战争从来不是好东西。
对我而言,好代码就是 “整洁可用” 的代码。
好代码首先必须是“可用”的代码,“可用” 是指代码做了它应该做的事情,而且做得不错。 如果让你写求绝对值的代码,你就不能写成求平方根的;如果让你做一个文本编辑器,OK,你做出来了,它不是一个图片编辑器,它确确实实就是一个文本编辑 器。但是用户输入一个字要一分钟,这也不能称之为“可用”,因为它没达到“不错”的标准,当然,如果这个文本编辑器是给“慢星人”用的,你有理由认为它是 “不错”的。那么,究竟怎样才叫“应该做”,怎样才叫“不错”呢?也许客户(用户)的反应(评价)是唯一的标准答案。
其次它需要整洁。
整洁是一个相对的词,在我看来,它唯一的作用就是令维护简单。如果你写的代码不需维护(没有 BUG、完成之后永远不会做功能改动、没有任何其它代码基于这些代码编写等等,显然,如果满足了这些条件,没人“有必要”来阅读你的代码),比如用完即抛 的很简单的一次性用品,那么只要“可用”就行了,不需要“整洁”。值得注意的是, 这里隐含了一个假设的前提条件:不保持代码整洁的情况下,你能够写出“可用”的代码。
现实生活中相当一部分(也许我可以说大部分)代码是需要维护的,也就意味着它们如果想成为好代码,必须要整洁。
在继续探讨“整洁”话题之前,也许有必要先谈谈“复杂度”。
2,复杂度
什么是复杂度? 在本文中,我们所谈及的复杂度是指软件开发中的复杂度,很难给出精确的数学定义,虽然业界已经有了各种相对严格的测量方法,但根据本文需要,这里只简单的给出自己的定义:复杂度是事物复杂程度的量化描述,其大概等价于使软件达到可用所需耗费的劳动(智力+体力)的总和。
当然,上述定义又引出了对“劳动”的量化需求,本文更多的只需要对“复杂度“做相对的评估,不需要绝对的量化,所以这里简单地用通用的行业描述:劳动 = 人月。
无疑,(现有事实证明)软件开发是复杂度很高的活动,我们有各种方法论、工具、最佳实践等等等,其本质都是为了降低软件开发的复杂度,也就是:第一,使软件达到可用的标准;第二,尽可能地减少所需劳动。
那么,软件开发为什么这么复杂呢?《没有银弹》给出了它的回答:所有软件活动包括根本任务——打造由抽象软件实体构成的复杂(现实)概念结构,次要任务——使用编程语言表达这些抽象实体,在时间和空间限制内将它们映射成机器语言。相应的,软件开发的复杂度由两部分构成:
1) 来自根本任务:根本困难——对复杂现实情况的抽象,这是软件开发中固有的困难。
2) 来自次要任务:次要困难——通过特定表达方式让计算机理解。这是受限于目前生产(方法、工具)的并非与生俱来的困难。
更具体一些,软件的复杂度来自这些:
1) 规模:软件实体可能比人类有史以来创造的其他任何实体都要复杂,计算机本身就比人类建造的大多数东西 复杂,它拥有大量状态,这使得构思、描述和测试都很困难,而软件系统状态又比计算机状态多几个数量级。同时,软件没有两个部分是相同的,至少在语句级别 上,如果有,我们会将它合并成一个子函数,在这个方面,软件系统和建筑、汽车大不相同,后者存在大量重复的部分。另外它不仅仅导致技术上的困难,还引发了 许多管理上的问题,它使全面理解变得很困难,从而妨碍了概念上的完整性;它使所有离散出口难以寻找和控制;它引发了大量学习和理解上的负担,使开发慢慢地 演变成一场灾难。
2)(容易)变化:软件天生就是易变的,第一,因为人们的想法本身容易产生变化;第二,人们可能有这样的错 觉:软件很容易变化——不需要太高的代价,相对其他产品来说;第三,软件必须演变才能成功。软件实体的扩展不是简单元素的重复添加,而必须是不同元素实体 的添加,大多数情况下,这些元素以非线性递增的方式交互,因此整个软件的复杂度以更大的非线性级数增长。
3)(缺乏)一致性:物理学家和数学家都坚信本源的存在,所有复杂的表象之下都必有简单的一致的本源存在, 如基本粒子,如通用原理。软件工程师可能缺乏这种信念,他必须处理不同用户习惯以及随时间推移而变化的接口,这些变化是无规律的,仅仅由于不同的人——而 不是上帝——设计的结果;另外他们还需要处理各种历史遗留系统的兼容性所带来的问题,这往往需要保持接口和历史接口的一致性。从本质上来说,这些都是不必 要的,但软件工程师必须处理它们——以及它们带来的复杂度。
4) 不可见性:几何抽象是强大的工具,建筑平面图、机械制图、分子模型都帮助相关工作人员更好的理解及工 作,软件的客观存在不具有空间的形体特性,因此没有已有的表达方式能恰如其分的描述软件。当我们试图用图形描述软件结构时,我们发现它不止包含一个,而是 很多相互关联、重叠在一起的图形,现有的描述方式都是强制将关联分割,直到可以层次化一个或多个图形(形成某种扁平结构)。这种缺憾限制了个人的设计过 程,同时也严重阻碍了相互之间的交流。
3,整洁
日常生活中我们谈起整洁,头脑中大概会浮现出这样的场景:每样物品都有序地摆放在它应该在的地方,一目了然,并且一尘不染,非常干净,令人愉快;同时,不那么明显的,整洁往往暗示着没有多余的东西,东西越少,越容易保持整洁。
整洁的代码有同样的特征:
1) 有序,各得其所,模块的归模块,接口的归接口,实现的归实现。
处理对于人脑来说过于复杂的东西,自古以来有效的办法就是分解,将大的分解成小的,使人脑在某一时刻只需要思考小的部分。要做到这点,除了分 解,还需要保持模块和模块之间联系尽可能少,只有这样你才能专注思考眼前这一块,而不必过于担心它和其它模块的相互影响。同样,只有这样,当软件发生变动 的时候,你才不至于陷入焦油坑。
相关术语:结构化、分层、抽象、解耦、正交、降低依赖、大量原则(SRP、OCP、LSP…)
2) 一目了然。
流畅,没有障碍,它应该就是这个样子,而不是别的样子。任何维护工作的第一件事是什么?读代码。
相关术语:可读、文字化编程(Kunth)、自解释。
3) 一尘不染。
重点是保持。如果一直保持干净,一旦出现污点,将会显得非常刺眼,自然会被清除。相反,一扇窗户破了,若无人关心,最终整条街道都会腐化。
相关术语:重构、温水煮青蛙。
4) 只做必要的事,保持简单。
从奥卡姆开始,到建筑,到飞机制造。“完成”不是指不能再往里塞东西,而是指不能再往外拿任何东西。
相关术语:KISS、敏捷。
5) 令人愉快。
成功永远令人愉快,美永远令人愉快。
相关术语:诗歌
4,其它
也许你已经发现了,如果保持代码整洁,似乎就可以应对多种复杂度(但不是所有),这也是为什么好代码除了可用,还需要整洁。
本文只是描述我心目中的好代码,并不打算说明如何编写好代码,那需要太多的篇幅(和太多的争议)。所以,至此为止。
疯狂的孩子你慢一点,
把电话摘下然后消失一会儿,
对了,你可以放松一两天,
你何时意识到……维也纳在等着你?
——《陌生人》(The Stranger) Bill Joel