简介: 从团队的角度来看,写好代码是一件很是有必要的事情。如何写出干净优雅的代码是个很困难的课题,我没有找到万能的 solution,更多的是一些 trade off,能够稍微讨论一下。算法
写了多年的代码,始终以为如何写出干净优雅的代码并非一件容易的事情。按 10000 小时刻意训练的定理,假设天天 8 小时,一个月 20 天,一年 12 个月,大概也须要 5 年左右的时间成为大师。其实咱们天天的工做中真正用于写代码的时间不可能有 8 个小时,而且不少时候是在完成任务,在业务压力很大的时候,可能想要达到的目标是如何尽快的使得功能 work 起来,代码是否干净优雅很是可能没有能放在第一优先级上,而是怎么快怎么来。编程
在这样的状况下是很是容易欠下技术债的,时间长了,这样的代码基本上没法维护,只能推倒重来,这个成本是很是高的。欠债要还,只是早晚的问题,而且等到要还的时候还要赔上额外的不菲的利息。还债的有多是本身,也有多是后来的继任者,但都是团队在还债。因此从团队的角度来看,写好代码是一件很是有必要的事情。如何写出干净优雅的代码是个很困难的课题,我没有找到万能的 solution,更多的是一些 trade off,能够稍微讨论一下。网络
在大部分的状况下我会认为代码是写给人看的。虽然代码最后的执行者是机器,可是实际上代码更多的时候是给人看的。咱们来看看一段代码的生命周期:开发 --> 单元测试 --> Code Review --> 功能测试 --> 性能测试 --> 上线 --> 运维、Bug 修复 --> 测试上线 --> 退休下线。开发到上线的时间也许是几周或者几个月,可是线上运维、bug 修复的周期能够是几年。app
在这几年的时间里面,几乎不可能仍是原来的做者在维护了。继任者如何能理解以前的代码逻辑是极其关键的,若是不能维护,只能本身从新作一套。因此在项目中咱们常常能见到的状况就是,看到了前任的代码,都以为这是什么垃圾,写的乱七八糟,仍是我本身重写一遍吧。就算是在开发的过程当中,须要别人来 Code Review,若是他们都看不懂这个代码,怎么来作 Review 呢。还有你也不但愿在休假的时候,由于其余人看不懂你的代码,只好打电话求助你。这个我印象极其深入,记得我在工做不久的时候,一次回到了老家休假中,忽然同事打电话来了,出现了一个问题,问我该如何解决,当时电话还要收漫游费的,很是贵,可是我还不得不支持直到耗光个人电话费。运维
因此代码主要仍是写给人看的,是咱们的交流的途径。那些很是好的开源的项目虽然有文档,可是更多的咱们其实仍是看他的源码,若是开源项目里面的代码写的很难读,这个项目也基本上不会火。由于代码是咱们开发人员交流的基本途径,甚至可能口头讨论不清楚的事情,咱们能够经过代码来讲清楚。代码的可读性我以为是第一位的。各个公司估计都有本身的代码规范,遵循相关的规范保持代码风格的统一是第一步(推荐谷歌代码规范和微软代码规范)。规范里通常都包括了如何进行变量、类、函数的命名,函数要尽可能短而且保持原子性,不要作多件事情,类的基本设计的原则等等。另一个建议是能够多参考学习一下开源项目中的代码。编程语言
通常大脑工做记忆的容量就是 5-9 个,若是事情过多或者过于复杂,对于大部分人来讲是没法直接理解和处理的。一般咱们须要一些辅助手段来处理复杂的问题,好比作笔记、画图,有点相似于在内存不够用的状况下咱们借用了外存。ide
学 CS 的同窗都知道,外存的访问速度确定不如内存访问速度。另一般来讲在逻辑复杂的状况下出错的可能要远大于在简单的状况下,在复杂的状况下,代码的分支可能有不少,咱们是否可以对每种状况都考虑到位,这些都有困难。为了使得代码更加可靠,而且容易理解,最好的办法仍是保持代码的简单,在处理一个问题的时候尽可能使用简单的逻辑,不要有过多的变量。函数
可是现实的问题并不会老是那么简单,那么如何来处理复杂的问题呢?与其借用外存,我更加倾向于对复杂的问题进行分层抽象。网络的通讯是一个很是复杂的事情,中间使用的设备能够有无数种(手机,各类 IOT 设备,台式机,laptop,路由器,交换机...), OSI 协议对各层作了抽象,每一层须要处理的状况就都大大地简化了。经过对复杂问题的分解、抽象,那么咱们在每一个层次上要解决处理的问题就简化了。其实也相似于算法中的 divide-and-conquer, 复杂的问题,要先拆解掉变成小的问题,从而来简化解决的方法。性能
KISS 还有另一层含义,“如无必要,勿增实体” (奥卡姆剃刀原理)。CS 中有一句 "All problems in computer science can be solved by another level of indirection", 为了系统的扩展性,支持未来的一些可能存在的变化,咱们常常会引入一层间接层,或者增长中间的 interface。在作这些决定的时候,咱们要多考虑一下是否真的有必要。增长额外的一层给咱们的好处就是易于扩展,可是同时也增长了复杂度,使得系统变得更加不可理解。对于代码来讲,极可能是我这里调用了一个 API,不知道实际的触发在哪里,对于理解和调试均可能增长困难。单元测试
KISS 自己就是一个 trade off,要把复杂的问题经过抽象和分拆来简单化,可是是否须要为了保留变化作更多的 indirection 的抽象,这些都是须要仔细考虑的。
为了快速地实现一个功能,知道以前有相似的,把代码 copy 过来修改一下就用,多是最快的方法。可是 copy 代码常常是不少问题和 bug 的根源。有一类问题就是 copy 过来的代码包含了一些其余的逻辑,可能并非这部分须要的,因此可能有冗余甚至一些额外的风险。
另一类问题就是在维护的时候,咱们其实不知道修复了一个地方以后,还有多少其余的地方还须要修复。在我过去的项目中就出现过这样的问题,有个问题明明以前作了修复,过几天另一个客户又提了相似的问题出现的另外的路径上。相同的逻辑要尽可能只出如今一个地方,这样有问题的时候也就能够一次性地修复。这也是一种抽象,对于相同的逻辑,抽象到一个类或者一个函数中去,这样也有利于代码的可读性。
我的的观点是大部分的代码尽可能不要注释。代码自己就是一种交流语言,而且通常来讲编程语言比咱们平常使用的口语更加的精确。在保持代码逻辑简单的状况下,使用良好的命名规范,代码自己就很清晰而且可能读起来就已是一篇良好的文章。特别是 OO 的语言的话,自己 object(名词)加 operation(通常用动词)就已经能够说明是在作什么了。重复一下把这个操做的名词放入注释并不会增长代码的可读性。而且在后续的维护中,会出现修改了代码,却并不修改注释的状况出现。在我作的不少 Code Review 中我都看到过这样的状况。尽可能把代码写的能够理解,而不是经过注释来理解。
固然我并非反对全部的注释,在公开的 API 上是须要注释的,应该列出 API 的前置和后置条件,解释该如何使用这个 API,这样也能够用于自动产品 API 的文档。在一些特殊优化逻辑和负责算法的地方加上这些逻辑和算法的解释仍是很是有必要的。
一般来讲在代码中写上 TODO,等着之后再来 refactoring 或者改进,基本上就不会再有之后了。咱们能够去咱们的代码库里面搜索一下 TODO,看看有多少,而且有多少是多少年前的,我相信这个结果会让你很惊讶(欢迎你们留言分享你查找以后的结果)。
尽可能一次就作对,不要相信之后还会回来把代码 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 的测试,都不可能覆盖到每一行的代码了。若是单元测试作的不够,其实就是在代码里面留下一些本身都不知道的黑洞,哪天调用方改了一些东西,走到了一个不经常使用的分支可能就挂掉了。我以前带的项目中就出现过相似的状况,代码已经上线几年了,有一次稍微改了一下调用方的参数,以为是个小改动,可是上线就挂了,就是由于遇到了以前根本没有人测试过的分支。单元测试就是要保证咱们本身写的代码是按照咱们但愿的逻辑实现的,须要尽可能的作到比较高的覆盖,确保咱们本身的代码里面没有留下什么黑洞。关于测试,我想单独开一篇讨论,因此就先简单聊到这里。
要写好代码确实是已经很是不容易的事情,须要考虑正确性、可读性、鲁棒性、可测试性、能够扩展性、能够移植性、性能。前面讨论的只是我的以为比较重要的入门的一些点,想要写好代码须要通过刻意地考虑和练习才能真正达到目标!