Skip to main content
  1. internet/

如何写没有bug的代码

·3450 words·7 mins·

作为一名程序员,bug就是我们生活的一部分。

一听到有bug,绝大分程序员的血压会立马上升,紧接着心脏跳动加快,然后带着一丝侥幸的期待着这是个不那么美丽的误会。

我对bug可谓是又爱又恨!

因为bug就是另一个我!

他是是业务逻辑没有被严谨考虑的我!

他是代码被马虎地编写的我!

他是单元测试被过于自信地省略的我!

bug就是满是缺点的我!每一个bug都似乎都是在一脸鄙夷(或者嫌弃?)得看着他的作者——我!

所以bug的存在,根源就在我自己!

所以要如何写没有bug的代码呢?

一想到这个话题就会在头脑中涌现非常多的想法,让我来一一记录下来!

不写bug清单 #

1. 要有不写bug的态度 #

总有些人认为项目延期是正常的,代码有bug也是正常的。一旦持有这种态度,那么项目就一定会延期、代码一定会有bug!

因此作为一个“心怀大教堂”的程序员,一定要有不写bug的态度!

2. 全面的了解业务逻辑 #

作为开发的第一步,不是画流程图、写文档,更不是写代码,而是了解业务逻辑。

如果对业务逻辑一知半解,那么在产品验收的阶段就会有很多问题,有些是“想得太多”导致费力不讨好,有些是“想的太少”导致需求没满足,更严重的是与产品的想法偏差太大。。。

在这一阶段,最忌感觉如此!如果有不确定的地方,一定要找产品问清楚!

3. 不要局限于当前的业务逻辑 #

如果你在每次增加/修改功能时都很痛苦,那么说明当前的功能实现仅仅满足了需求,而不是基于业务规则。

深入了解业务规则,并构建业务规则。产品提出的功能需求一定有其底层逻辑而不是随意提出需求,因此,构建底层规则能够保证每次的功能添加/修改更顺畅。

3. 严谨的思考功能流程 #

了解完业务逻辑后,就要思考如何实现了。

刚进入职场的年轻程序猿往往不知“天高地厚”,总是急于展示自己的十八般武艺,还没了解完业务需求就在摩拳擦掌,了解完需求后就直接开搞。

如果没有十分严谨的考虑功能逻辑,那么在写代码的时候总会不断去修改之前的代码逻辑。牵一发而动全身, bug自然就容易产生。

我在这个年纪的时候也是流下了不少悔恨的泪水。

4. 将流程先描述出来 #

我们常说沟通是有成本的,这个成本是什么呢?

  • 表达能力不足导致没能让其他人了解到自己的真正想法
  • 沟通能力不足导致沟通所需时间太长(有些同事真的会说一堆没什么用的话,或者去讨论一堆跟主题不符的问题)
  • 不懂装懂(有些刚入职的员工害怕别人否定自己,会在没懂的时候也会说自己懂了。。。)
  • 。。。

总之,沟通成本是实实在在存在的,也是难以避免的。为了避免沟通导致的理解不一致,我习惯在遇到复杂的场景时,先将流程画出来或者写出来,然后交给leader或者其他人查看,避免吭哧吭哧写了一堆代码,但是没啥用的情况产生。

在于非开发同事(如产品经历)沟通时,一定要注意不仅要把逻辑描述出来,还要把会产生哪些影响讲出来,否则他们可能不清楚会有哪些地方改变!

5. 写业务逻辑而不是功能实现 #

每当派我我协助其他项目组赶进度时,我总会跟他们说:你们这是在写功能实现而不是业务逻辑。

如何区分业务逻辑还是功能实现?很简单,如果不能一眼就能看懂他们的代码逻辑,那他们就是写的功能实现!

前几天带着一个刚入职的小兄弟写一个项目,测试同事反馈有一个bug,我去看了下,实现上好像也没什么问题,但是不好理解,于是按照业务逻辑重新写了那块代码,提交上去后bug就消失了。

对于复杂的场景,如果只专注于如何去实现,那么写出来的代码一定是难以理解的,对于难以理解的代码,即使遇到问题也很难排查!

那么如何写业务逻辑呢?我的习惯是按照人类的理解逻辑,将步骤先写出来,然后去实现每个步骤,并且把每个步骤都封装成函数来保持逻辑函数的整洁!再进一步,可以利用“领域”来封装规则,这样能让业务逻辑更简洁!推荐各位程序员一定要了解DDD(领域驱动设计)!

6. 先写逻辑,后写实现 #

我在刚入行的时候,导师告诉我们,要么从前往后写(先写路由层,再写控制层、逻辑层、数据层),要么从后往前写(先写数据层,再写逻辑层、控制层、路由层)。当时我就想有没有先写中间的,现在还真是先写中间的逻辑层。。。

为什么要先写逻辑层呢?因为逻辑层往往是一个项目最复杂的地方,解决了这个地方,整个项目在我看来就完成了三分之二了。

以前不管从后往前写还是从前往后写,总会在写逻辑层时碰到问题,然后要去修改对应的控制层或者数据层,修改了这些地方,对应的逻辑层也要做调整,于是一个逻辑有问题就会导致很多地方需要修改!

所以我现在写代码都是先写逻辑层,把逻辑搞定再写数据层和控制层就很轻松了。

至于写逻辑层时,需要调用数据层的方法怎么办?简单,用一个接口来定义这些方法,写完逻辑层在数据层去实现这些接口即可!

那为什么很多人都是先写两边再写中间?因为人总是倾向于先做简单的事情啊!

7. 回顾代码 #

在写完代码后不要急于进入测试阶段,应该先通读一遍代码,这是因为:

  1. 测试通过只能说明当前的测试场景能够正常运行,不代表代码没问题(比如说代码中很多冗余逻辑)
  2. 一定要尽快做“可以做也可以不做”的重要事情。阅读自己的代码能够提高自己的代码质量、完善代码逻辑,所以它是重要的,但对于大部分人来说也是非必须的。如果拖到后边去做,很可能就不会做
  3. 如果先测试再回顾代码,那么当发现某些代码存在问题后,还要再进行测试流程

8. 自测 #

测试分很多种:单元测试、集成测试、组件测试等等,这些都是开发要掌握的并且也是要做的。

如此多的测试自然也都有其对应的技巧,这个可以另作文章!

国内的开发环境一向忽略测试流程,部分原因是没时间,部分原因是不想做。

作为一个合格的开发,一定要知道哪些是必须要做的,即使时间再紧也要做!

9. 不断重构 #

写代码的流程就是在不断重构的过程。

当遇到不符合当前需求的代码时,一定要对这块代码进行重构而不是打补丁。

打补丁一时爽,维护如进火葬场!

10. 维护好文档 #

我习惯在READMINE中记录:

  1. 哪些事情还没有做(todo list)。避免造成遗漏。
  2. 哪些复杂的场景的流程是怎样的。方便自己和他人熟悉业务逻辑(我不喜欢记这些东西)
  3. 为什么要这么写。有些地方需要做取舍,如果不记下来,自己和他人都很难理解为什么这么写。

11. 将逻辑封装成对象 #

以更新用户信息为例。

在一开始,这可能仅仅是更新下数据库,所以在代码里直接调用对应的update方法是没问题的。

但是后来加了个功能:更新后发送事件,如果时间很赶的话,年轻的工程师可能就找到所有调用update方法的代码,然后在下边加上事件通知代码。

然后需求又增加了:更新后要按照某种规则判断是否需要发送事件。这时候还要重复上个步骤吗?

好的代码应该把更新用户信息的逻辑封装成一个逻辑对象,这样就能避免每次增加需求都要改多个地方的代码。

封装成逻辑对象和封装为方法有什么区别?本质上是没有区别,封装成逻辑对象是为了告诉读者,这是一种逻辑对象,不要将其他代码掺入进来,同时也不要将其内部代码外泄。大部分工程师对于方法的使用总是很随意,封装成对象能让其谨慎的写代码!

12. 合理的估时 #

项目启动前往往需要参与人员对于自己的工作量进行估时,合理的估时能够保证工作的正常进行。

对于开发工程师来说,开发时间包括:

  1. 开发文档(方案、API文档、关键功能的流程图)等准备性工作
  2. 代码开发
  3. 代码review
  4. 自测
  5. 联调

一般来说,上述时间的估算方法为 W = codingTime * α , 即整体开发时间为纯粹的代码开发时间乘以α,α一般为3~5,具体视情况而定(联调方越多、代码复杂性越高,α越大),每个开发工程师都应该按照实际情况不断调整自己的α。

13. 拒绝拖延 #

以前有个项目很急,于是我们决定先把功能堆上去,后续再去优化项目结构(代码更复杂,但是逻辑更聚合)。

上线后再去修改整体性的设计是非常困难的,也是具有非常大的风险的。

因为这意味着需要全量的回归测试。

当团队不是很重视质量的时候,工程师是推不动整体项目的优化的——不进行全量回归测试意味着非常有可能存在BUG,因此就无法上线。

因此,对于整体性的设计是不能拖延的,理清思路就去做,不要寄希望于未来的优化。

14. 保持积极的情绪 #

情绪对代码质量的影响很大,因为开发者总会不时的蹦出一些不好的想法或者回忆,这会使开发者分心以至于代码存在bug。

另一方面,开发者在心情差时也不会去想去花时间精力去构建更好的代码。

最后 #

温伯格说代码是个体的延伸,一个人写的代码有问题,往往是因为这个人存在某些问题。

一般人都不会喜欢别人指出自己的缺点,所以工程师也不希望别人指出自己代码的bug,但是能做到这一点的只有不写bug。

愿我们都能少写bug,愿我们都能成为更好的自己!