如何写好代码?(转载自阿里技术)
前言
写了多年的代码,始终觉得如何写出干净优雅的代码并不是一件容易的事情。按10000小时刻意训练的定理,假设每天8小时,一个月20天,一年12个月,大概也需要5年左右的时间成为大师。其实我们每天的工作中真正用于写代码的时间不可能有8个小时,并且很多时候是在完成任务,在业务压力很大的时候,可能想要达到的目标是如何尽快的使得功能work起来,代码是否干净优雅非常可能没有能放在第一优先级上,而是怎么快怎么来。在这样的情况下是非常容易欠下技术债的,时间长了,这样的代码基本上无法维护,只能推倒重来,这个成本是非常高的。欠债要还,只是迟早的问题,并且等到要还的时候还要赔上额外的不菲的利息。还债的有可能是自己,也有可能是后来的继任者,但都是团队在还债。所以从团队的角度来看,写好代码是一件非常有必要的事情。如何写出干净优雅的代码是个很困难的课题,我没有找到万能的solution,更多的是一些trade off,可以稍微讨论一下。
代码是写给人看的还是写给机器看的?
在大部分的情况下我会认为代码是写给人看的。虽然代码最后的执行者是机器,但是实际上代码更多的时候是给人看的。
我们来看看一段代码的生命周期:开发 --> 单元测试 --> Code Review --> 功能测试 --> 性能测试 --> 上线 --> 运维、Bug修复 --> 测试上线 --> 退休下线。
开发到上线的时间也许是几周或者几个月,但是线上运维、bug修复的周期可以是几年。在这几年的时间里面,几乎不可能还是原来的作者在维护了。继任者如何能理解之前的代码逻辑是极其关键的,如果不能维护,只能自己重新做一套。
所以在项目中我们经常能见到的情况就是,看到了前任的代码,都觉得这是什么垃圾,写的乱七八糟,还是我自己重写一遍吧。就算是在开发的过程中,需要别人来Code Review,如果他们都看不懂这个代码,怎么来做Review呢。所以代码主要还是写给人看的,是我们的交流的途径。那些非常好的开源的项目虽然有文档,但是更多的我们其实还是看他的源码,如果开源项目里面的代码写的很难读,这个项目也基本上不会火。因为代码是我们开发人员交流的基本途径,甚至可能口头讨论不清楚的事情,我们可以通过代码来说清楚。
代码的可读性我觉得是第一位的。各个公司估计都有自己的代码规范,遵循相关的规范保持代码风格的统一是第一步(推荐谷歌代码规范[1]和微软代码规范[2])。规范里一般都包括了如何进行变量、类、函数的命名,函数要尽量短并且保持原子性,不要做多件事情,类的基本设计的原则等等。另外一个建议是可以多参考学习一下开源项目中的代码。
DRY (Don't repeat yourself)
为了快速地实现一个功能,知道之前有类似的,把代码copy过来修改一下就用,可能是最快的方法。但是copy代码经常是很多问题和bug的根源。有一类问题就是copy过来的代码包含了一些其他的逻辑,可能并不是这部分需要的,所以可能有冗余甚至一些额外的风险。
另外一类问题就是在维护的时候,我们其实不知道修复了一个地方之后,还有多少其他的地方还需要修复。在我过去的项目中就出现过这样的问题,有个问题明明之前做了修复,过几天另外一个客户又提了类似的问题出现的另外的路径上。相同的逻辑要尽量只出现在一个地方,这样有问题的时候也就可以一次性地修复。这也是一种抽象,对于相同的逻辑,抽象到一个类或者一个函数中去,这样也有利于代码的可读性。
是否要写注释
个人的观点是大部分的代码尽量不要注释。代码本身就是一种交流语言,并且一般来说编程语言比我们日常使用的口语更加的精确。在保持代码逻辑简单的情况下,使用良好的命名规范,代码本身就很清晰并且可能读起来就已经是一篇良好的文章。
特别是OO的语言的话,本身object(名词)加operation(一般用动词)就已经可以说明是在做什么了。重复一下把这个操作的名词放入注释并不会增加代码的可读性。并且在后续的维护中,会出现修改了代码,却并不修改注释的情况出现。
在我做的很多Code Review中我都看到过这样的情况。尽量把代码写的可以理解,而不是通过注释来理解。当然我并不是反对所有的注释,在公开的API上是需要注释的,应该列出API的前置和后置条件,解释该如何使用这个API,这样也可以用于自动产品API的文档。在一些特殊优化逻辑和负责算法的地方加上这些逻辑和算法的解释还是非常有必要的。
一次做对,不要相信以后会Refactoring
通常来说在代码中写上TODO,等着以后再来refactoring或者改进,基本上就不会再有以后了。尽量一次就做对,不要相信以后还会回来把代码refactoring好。人都是有惰性的,一旦完成了当前的事情,move on之后再回来处理这些概率就非常小了,除非下次真的需要修改这些代码。如果说不会再回来,那么这个TODO也没有什么意义。如果真的需要,就不要留下这个问题。我见过有的人留下了一个TODO,throw了一个not implemented的exception,然后几天之后其他同学把这个代码带上线了,直接挂掉的情况。尽量不要TODO, 一次做好。
是否要写单元测试?
个人的观点是必须,除非你只是做prototype或者快速迭代扔掉的代码。
Unit tests are typically automated tests written and run by software developers to ensure that a section of an application (known as the "unit") meets its design and behaves as intended. In procedural programming, a unit could be an entire module, but it is more commonly an individual function or procedure. In object-oriented programming, a unit is often an entire interface, such as a class, but could be an individual method.
From Wikipedia
单元测试是为了保证我们写出的代码确实是我们想要表达的逻辑。当我们的代码被集成到大项目中的时候,之后的集成测试、功能测试甚至e2e的测试,都不可能覆盖到每一行的代码了。如果单元测试做的不够,其实就是在代码里面留下一些自己都不知道的黑洞,哪天调用方改了一些东西,走到了一个不常用的分支可能就挂掉了。我之前带的项目中就出现过类似的情况,代码已经上线几年了,有一次稍微改了一下调用方的参数,觉得是个小改动,但是上线就挂了,就是因为遇到了之前根本没有人测试过的分支。单元测试就是要保证我们自己写的代码是按照我们希望的逻辑实现的,需要尽量的做到比较高的覆盖,确保我们自己的代码里面没有留下什么黑洞。关于测试,我想单独开一篇讨论,所以就先简单聊到这里。
要写好代码确实是已经非常不容易的事情,需要考虑正确性、可读性、鲁棒性、可测试性、可以扩展性、可以移植性、性能。前面讨论的只是个人觉得比较重要的入门的一些点,想要写好代码需要经过刻意地考虑和练习才能真正达到目标!