不能忍受的十个编程习惯

这几天在搞项目的重构,发现了几个不能忍受的编程习惯,总结在这里,也提醒自己不要犯同样的错误。

1. 函数过长 + 过多的本地变量

有时在项目中会看到超过200行的代码,而且在函数开头有大量的变量声明。有的变量在函数里多次被修改,有的在函数末尾才会被用到。在重构一书中写道,坏代码的重要特征就是函数过长,会使逻辑不清,拆分困难,也会不可避免的违背单一职责的原则。

对于Java项目来说,一般认为理想的函数长度在20行左右。实际情况中,不可避免的会有一些较复杂的逻辑,所以我觉得50行以内的函数都是可以接受的。在写函数时,我主要考虑两个因素:

  1. 代码逻辑是否在同一抽象层面
  2. 是否对单元测试友好

如果代码深入了一层,那么就单独抽出一个函数来。另外,能写成private的函数,就不要public。能写成static,就不要instance method

2. 过多的if else嵌套

这个问题经常和第一条同时出现。如果if else过多,说明class设计的不好。因为在Java里大部分的if else都可以用多态来实现。解决方法如问题1,每一层if else都是逻辑上的加深,所以都应该抽象成新的函数。

3. 没用的代码变成注释

有的人在重构时,旧代码不删除,而是注释掉。实在忍不了。。。又不是没有版本管理,没必要把之前的代码留下。

4. 编程之前不设计,编程之后不重构

一般开始一项新功能前,都要好好设计一番,画一下UML图,把各个类的职责和抽象关系定义好。可有的人拿到任务就开始写代码,这样就好像盖房子没有图纸,想到哪儿盖到哪儿,最终得到的肯定毫无扩展性和可维护性。如果能持续重构还好,但是大多数人都是任务完成就好,根本不管代码是否优雅易读,结构是否清晰。代码应该是活的,因为需求会变,代码也应该持续变化。

5. 编程不符合标准习惯

在编程时,除了实现功能外,符合标准和一般习惯也很重要。大一些的项目都会有coding style,定义了命名,格式等规范。除了这种代码文本上的规范,功能上符合规范也同样重要。尤其是对于接口来说,规范十分重要。拿RestAPI来说吧,参数格式(尤其是时间格式),http方法(GET vs POST),接口设计,错误返回码都有一定的标准。每个公司也都应该定义一个内部标准并严格执行。这样在前后端链接和不同app整合时能节省大量时间。

6. 不写custom exception

好多人在处理异常时,不根据使用场景抛出自己的异常,而是直接throws一个 generic的Exception类。问题是Exception类为checked exception,这样导致上级函数不能根据各个不同的错误进行针对性的处理,而是只能也必须也同样的try..catch这个exception。这样引发连锁反应,整个程序链都会被污染。正确的做法应该是定义一个继承于Runtime Exception的子类。这样上级函数可以自行决定是否捕获异常,同时重要的exception也要在函数声明里加入throws,以提醒caller要适当进行处理。

7. 对需求一知半解

拿到一个任务后,有的人会详细阅读需求文档,把脑海中的问题记下来,和产品交流。等所有疑问解决了,才开始程序的设计,最后再编写。还有的人,大概看一眼文档就开始写程序,但其实对需求内部之间的联系没有理解透彻,更不知道这项功能为什么要做,之后是否有扩展的需要,各项边界条件还有非功能性需求是什么。很多时候,MOA想问题是不全面的,只有程序员才能踩到所有的坑和逻辑分支。这个时候,就需要不断反馈,并完善文档。最怕的就是文档写的不全面,程序员也不反馈,而是臆测可能需要的处理,或者自行发明。由于程序员没有相关的业务知识,这种臆测往往不全面,有时甚至会产生严重的漏洞。只有全面理解需求,才能编写出符合预期,并且易于扩展的程序。

8. 重复代码

对于一个已经存在一段时间的项目,一定有很大一部分代码是共用的。在开始开发之前,一定要认真分析已有代码,了解程序结构和逻辑,进行合理的重构和添加。否则,只会使重复代码段在程序各处游走,使抽象变得越来越难。另外,很多时候重复代码的出现是由于对业务知识理解不足。因为如果业务有全面的认识,会很容易联想到另一个功能也许会有同样的逻辑,从而简化程序的分析过程。

另一个产生重复代码的原因则是纯粹因为懒。有的人不认真思考进行重构和抽象,而是直接复制粘贴已有代码,而只改几个变量参数。这么做当时节省了时间,长远看百害而无一利。

9. 单元测试写成了整合测试

单元测试是针对一个单独的函数来进行的。有时甚至要针对每一个判断分支和边界条件进行测试。然而在实际开发中,很多人把单元测试写成了整合测试。给一个输入,判断最后的输出。这样做虽然测试的覆盖率上去了,但是一旦重构起来,会很复杂。首先这样的测试通常会很长,因为要判断比较的结果有很多。其次,程序牵一发而动全身,一个小的修改便会使结果发生变化,导致必须重新分析测试的逻辑,向前找到应该修改的输入值,向后分析需要得到的结果,非常耽误时间。真正的单元测试,应该短小精悍,一个测试方法只验证一个函数的一个行为。然后一层一层的向上,多用mock。这样的测试才能保证效率,才是可维护的,这样提高的代码覆盖率才是健康的。

10. 不注意性能优化

一个优秀的程序员应该时刻考虑代码的性能,而不是在产生问题时才考虑优化。应该时刻保持警惕,每写一段函数就分析一下时间和空间复杂度,问自己是否有更优化的算法。正确使用数据结构,用高效的库函数而不是自己造轮子。大部分时候编写出高效的代码并不一定要采用多高深的算法,而是从点滴做起,保证每个函数都是最优的,整体性能自然就上去了。

以上就是我工作两年以来遇到的各种难以忍受的编程习惯。其中很多都是静态工具(如sonar)的分析内容(比如1,2,6,8),很多优秀的IDE(如IntelliJ)也可以给出相关的提示。剩下的一些,则需要从主观意识上做起,不断地提醒自己,逐渐养成好的习惯。