如何写没有bug的代码
Table of Contents
作为一名程序员,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. 回顾代码 #
在写完代码后不要急于进入测试阶段,应该先通读一遍代码,这是因为:
- 测试通过只能说明当前的测试场景能够正常运行,不代表代码没问题(比如说代码中很多冗余逻辑)
- 一定要尽快做“可以做也可以不做”的重要事情。阅读自己的代码能够提高自己的代码质量、完善代码逻辑,所以它是重要的,但对于大部分人来说也是非必须的。如果拖到后边去做,很可能就不会做
- 如果先测试再回顾代码,那么当发现某些代码存在问题后,还要再进行测试流程
8. 自测 #
测试分很多种:单元测试、集成测试、组件测试等等,这些都是开发要掌握的并且也是要做的。
如此多的测试自然也都有其对应的技巧,这个可以另作文章!
国内的开发环境一向忽略测试流程,部分原因是没时间,部分原因是不想做。
作为一个合格的开发,一定要知道哪些是必须要做的,即使时间再紧也要做!
9. 不断重构 #
写代码的流程就是在不断重构的过程。
当遇到不符合当前需求的代码时,一定要对这块代码进行重构而不是打补丁。
打补丁一时爽,维护如进火葬场!
10. 维护好文档 #
我习惯在READMINE中记录:
- 哪些事情还没有做(todo list)。避免造成遗漏。
- 哪些复杂的场景的流程是怎样的。方便自己和他人熟悉业务逻辑(我不喜欢记这些东西)
- 为什么要这么写。有些地方需要做取舍,如果不记下来,自己和他人都很难理解为什么这么写。
11. 将逻辑封装成对象 #
以更新用户信息为例。
在一开始,这可能仅仅是更新下数据库,所以在代码里直接调用对应的update方法是没问题的。
但是后来加了个功能:更新后发送事件,如果时间很赶的话,年轻的工程师可能就找到所有调用update方法的代码,然后在下边加上事件通知代码。
然后需求又增加了:更新后要按照某种规则判断是否需要发送事件。这时候还要重复上个步骤吗?
好的代码应该把更新用户信息的逻辑封装成一个逻辑对象,这样就能避免每次增加需求都要改多个地方的代码。
那封装成逻辑对象和封装为方法有什么区别?本质上是没有区别,封装成逻辑对象是为了告诉读者,这是一种逻辑对象,不要将其他代码掺入进来,同时也不要将其内部代码外泄。大部分工程师对于方法的使用总是很随意,封装成对象能让其谨慎的写代码!
12. 合理的估时 #
项目启动前往往需要参与人员对于自己的工作量进行估时,合理的估时能够保证工作的正常进行。
对于开发工程师来说,开发时间包括:
- 开发文档(方案、API文档、关键功能的流程图)等准备性工作
- 代码开发
- 代码review
- 自测
- 联调
一般来说,上述时间的估算方法为 W = codingTime * α
, 即整体开发时间为纯粹的代码开发时间乘以α,α一般为3~5,具体视情况而定(联调方越多、代码复杂性越高,α越大),每个开发工程师都应该按照实际情况不断调整自己的α。
13. 拒绝拖延 #
以前有个项目很急,于是我们决定先把功能堆上去,后续再去优化项目结构(代码更复杂,但是逻辑更聚合)。
上线后再去修改整体性的设计是非常困难的,也是具有非常大的风险的。
因为这意味着需要全量的回归测试。
当团队不是很重视质量的时候,工程师是推不动整体项目的优化的——不进行全量回归测试意味着非常有可能存在BUG,因此就无法上线。
因此,对于整体性的设计是不能拖延的,理清思路就去做,不要寄希望于未来的优化。
14. 保持积极的情绪 #
情绪对代码质量的影响很大,因为开发者总会不时的蹦出一些不好的想法或者回忆,这会使开发者分心以至于代码存在bug。
另一方面,开发者在心情差时也不会去想去花时间精力去构建更好的代码。
最后 #
温伯格说代码是个体的延伸,一个人写的代码有问题,往往是因为这个人存在某些问题。
一般人都不会喜欢别人指出自己的缺点,所以工程师也不希望别人指出自己代码的bug,但是能做到这一点的只有不写bug。
愿我们都能少写bug,愿我们都能成为更好的自己!