时至今日,
笔者已经有十年以上的写Bug经验了。
是时候给各位想写Bug但还不够熟练的同学们,
分享一些写Bug的心得了。

概述

诚实地说,
写Bug本身并不是一件很光荣的事情。
但是写了一个很隐蔽/影响很大/看起来像是Feature的Bug,
然后再修复它,
就是一件伟大的事情了。
往往在你的行云流水般的bugfix代码下,
还能收获围观群众的666666惊呼

总的来说,
写Bug的技巧可以分为下面几个维度:

  • 开发功能
    • 测试是懦夫的行为
    • 不要让外界发现Bug
    • Warning? 不存在的
    • 复制别人的代码
    • 遵循历史规律,不轻易改变
    • 用中间状态完成功能
  • 培养习惯
    • 所有情况下都拥抱变化
    • 配置个性化的开发环境
    • 少做自动化的无用功
    • 同时开展多个工作
    • 信任他人的实现
    • 永远充满自信
  • 与人交流
    • 永远只实现90%的方案
    • 永远只实现100%的方案
    • Code Review时也不解释自己的代码
    • 为Bug的合理性辩护
    • 适当指责别人写的不好
  • 后期维护
    • 当权责分明的人
    • 信奉无知即无罪
    • 分担权责到用户和运营上
    • 多写代码,生产力=破坏力

下面就让笔者依次为你讲解具体的写Bug技巧。

开发功能

测试是懦夫的行为

Bug的天敌之一就是完善的测试,
为了写更多的Bug,
我们需要减少测试的量。
但是不写测试道义上说不过去,
所以我们要合理用一些话术来解释:

  • 这个功能很简单,不用写测试。
  • 这个功能太复杂,不想写测试。
  • 这个功能是临时上的,不写测试了。
  • 这个功能我们写不了测试。
  • 放心吧,我写的代码稳得一笔,包准没有Bug。

更高级的做法是自己开发功能的时候,
先手动测试保证完善了。
但是几个月以后,
别人再改动相关代码的时候,
就神不知鬼不觉地写了几个Bug进去了。

不要让外界发现Bug

Bug是十分引人讨厌的,
基本上被发现了都会被修复。
那针对这个,
我们有两种操作可以做:

  1. 让Bug不被发现
    编码过程中,
    多用catch Exception { ignore(); }这样的操作,
    或者就把Bug的信息记到一个没人看的日志文件里面。
    这样前一个操作做错了,
    后面的操作还能接着做下去,
    说不定就像小学数学题一样:
    过程是错的,结果却是对的呢?

  2. 让Bug不被修复
    这个其实很简单,
    只要我们写一个基于概率性假设的Feature就行了。
    比如经典的race condition
    在高并发的情况下同时改某一个变量,
    或者是npm的包不锁死版本,
    然后不同时间依次部署在多个环境上…

Warning? 不存在的

代码里面有Warning是极其正常的事情,
我们只要保证代码能编译运行就行了。
IDE的大部分灰色/黄色/下划线都是一些强迫症程序员做的功能,
我们应当关掉IDE的这些警告。

还有Lint工具,
更多是用来检查拼写的。
每个人的代码习惯都不一样,
我们应当尊重个人差异,
只要有习惯不一致的地方,
就应当关掉Linter对应的检查。

多复制别人的代码

程序员业界有一句流传两百多年的俗话,叫:
“不要重复造轮子”。

所以我们应当多复制现成的代码,
这样一个Bug就会被复制成了两个。
而且以后新版本里,那部分代码的Bug被修复了,
但肯定没人会发现我们也复制了一份代码,
这里还有一个Bug。

当我们要定制化部分第三方依赖库里的代码时,
少用继承和组合,
多用复制和粘贴,
然后在我们需要的地方加上定制化的改动就行了。

遵循历史规律,不轻易改变

很多时候以前的代码写成这样是有原因的。
中国有句老话叫“萧规曹随”,
写程序里的意思就是“之前的代码就是这么实现的,我也应该要这么写”。

这样虽然我们只能在新的业务里引进新的Bug,
但是旧的业务的Bug我们可以继承过来呀。
而且把这一条技巧和上一条技巧结合起来就更佳了,
“复制以前的历史代码”,
是大大减少写Bug的工作量的一条捷径。

用中间状态完成功能

工作中写的代码大体上会分为技术代码和业务代码两部分。
业务代码很不好玩,
而且会有一个明确的deadline。

这个时候我们就要心中冥想“这个我要快点写完”,
口中默念“这个很容易实现”,
一边解释“先猥琐一点实现了,后面再整理一下”,
再依靠我们的编程素养三下五除二地解决问题。

虽然这样写出来的Bug生命周期不会太长,
但是万一后面大家都很忙,忘了这里有个中间状态,
那一些Bug的生命就会很长了!超棒!

培养习惯

所有情况下都拥抱变化

现代软件开发世界里有一个真理,
“需求一直是变化的”
根据逻辑学来看:

  • 因为需求一直是变化的,
  • 又因为代码是要实现需求的,
  • 所以代码也一直是变化的。

我们写出一个又一个新的Bug就有了坚实的理论基础。

而且这里还有一个酷炫小技巧就是,
最开始的系统设计一般都是简单的,
最开始的时间也是很充裕的,
但是往往变化的时间会比较急促。
所以只要我们大胆拥抱变化,承诺“这周内都能实现”,
这里就会有一个很小但充裕的时间让我们发挥,
写出一些别人根本设计不出来的Bug。

配置个性化的开发环境

开发环境永远,永远不要跟生产环境保持一致:
因为生产环境一般来说是Linux,Linux太无趣了。
我们应当用一些像Windows/MacOS这样不区分大小写的宽松的系统,
来当我们的开发环境。

用自己的电脑开发的时候,为了自己心情愉悦,
可以在全局装一些好看的字体、很好用的第三方库,
然后适当地在代码中引用这些定制化的很棒的工具。
同时测试时永远用最新版的软件,
比如浏览器、客户端、代码版本等。

当别人疑惑地报了个疑似Bug的问题时,
你可以自豪地回复:“我这是好的。”

少做自动化的无用功

假如一个自动化程序有Bug,
那这个Bug被修复以后就很难被重新引入。

但是假如我们每次做一些数据上的操作,
都直接调用代码,
或者是写一些一次性的脚本来“自动化”这个操作,
那我们每次都有机会引入全新的Bug。

虽然手动操作会耗费我们更多的时间,
但是它能带来更多Bug的可能性呀~

同时开展多个工作

厉害的人永远不会只在一个项目上留下他的Bug。

我们也要当这样的人,
所以就像“能力越强,责任越大”说的一样,
在同一时刻我们要敢于承担多个任务。
这里的任务可大可小,
但是每个任务都要是不同方面的,
假如不仅是紧急的任务,
还时不时会有利益相关的人来催一下就更好了。

这样我们在完成多个任务的时候,
还可以充分体验“上下文切换”的快感,
然后在多个项目上都留下了自己的印记。

信任他人的实现

正如小学、初中、高中每几年都要学一遍的《珍珠鸟》讲的那样:

信赖,往往创造出美好的境界。

作为单纯可爱的程序员,
我们也要信任他人。
信任用户这么聪明,肯定能看得懂报错,
信任产品经理给的需求,肯定简单,
信任设计师给的图,肯定容易实现;
信任前端传入的数据,肯定是合法的,
信任传入合法数据时,后端的接口肯定不会报错;
信任测试过的代码,上线了肯定没问题,
信任第三方的库,小版本肯定不会改兼容性,
信任这么贵的服务,效果肯定比MySQL好。

而且就算这种时候出了Bug,
我们也可以解释呀~
又不是我们的问题,
这个Bug,写的精妙吧。

永远充满自信,不用写注释

世界上20%的程序员
写出了80%的程序Bug。
一般来说,看到这篇文章的程序员,
应该都是属于top 20%。

虽然说三年前的代码我不一定想的起来,
但是一年前的代码自己应该看得懂,
所以一般情况知道是自己维护的话,
就不用写多少注释了。

而且像我们这种英文很好的人,
写注释肯定是用英文的。
不过又担心看代码的人英文不一定好,
所以还是不写注释了。

基于自信的基础上,
我们还可以觉得所有自己写的Bug都是可以修复的,
所以一些暂时的Bug不用太重视,
所谓When there's a bug, there's a fix.

与人交流

永远只实现90%的方案

老程序员之间会口口相传的一句道理叫:
“相比于覆盖100%的复杂设计,我更喜欢覆盖90%的简单设计。”

虽然这样子写不出来100%的Bug了,
但其实根据这个原则,
我们可以把放弃掉的10%用户都看成是一个巨大Bug。

这么看起来,
我们在更少的工作量情况下,
完成了更大的Bug,
其实效率更高了呢。

永远实现100%的方案

中国的智慧长者们会讲一句东方谚语,叫:
“行百里者半九十。”

当我们做一个系统设计/业务实现时,
假如不能做到最好,
不考虑完所有的情况,
那其实相当于整个功能没有实现。

软件合理的流程应该是一开始设计好整个系统,
然后再开发,一次性全部上线,
之后再整理上一次的Bug,
伙同下一次的所有设计、功能再上线。
这样旧的Bug也可以活得更久,
新的Bug也可以持续上线。

在100%的方案里面,
Bug虽然会减少一部分,
但是我们可以分散写Bug的权责,
最终设计、实现、测试、上线整个流程的所有人都是Bug的父母。

Code Review时也不解释自己的代码

随着现代软件开发流程的普及,
大部分程序员用上了Git等版本控制工具。
但是这些只不过是上传代码的工具,
即使是版本控制跟项目流程工具结合时,
我们也应当相信个人的力量。

发Code Review时利用好自动发邮件功能,
少解释代码逻辑,多催别人“快approve我的代码”。
因为小的Code Review比较容易看,
写的Bug会被一眼看穿,
所以更棒的做法是发一次改动几百上千行的大的PR。
这样不仅能显示我们的工作量,
还能让别人对Review我们的代码望而生怯。

久而久之,
别人只能Review我们的代码风格,
然后Bug们就能隐藏在代码逻辑里,
在开发环境、测试环境、生产环境之间遨游了。

适当指责别人写的不好

假如以上的技巧你都掌握了的话,
那恭喜你,成为了一个写Bug中级工程师。
此时你不仅拥有了写Bug的权利,
你还拥有了指责别人的权利。

当然,我们不能上升到人身攻击,
所以我们应该主要攻击别人的代码:
“你这么写不好”
“我觉得这样写不行”
“你这么写以后会有Bug”

为了保持神秘,我们也要少跟别人解释为什么/什么是好代码什么是坏代码。
心态好的人被指责了,可能会去查谷歌,从而写更少的Bug;
心态不好的人被指责了,他可能会怀疑自己不适合干这一行。

总的来说,跟我们写Bug竞争的人会变少。
这样我们下次敞开写Bug的时候,
还能反驳“你也写了一个Bug”。

为Bug的合理性辩护

Bug也分很多种,
不同的场景会催生不同的Bug,
严重性、时效性、后果也各有不同。
写Bug的确是再正常不过的日常了。

根据“存在即合理”的说法,
我们要勇于为自己的Bug辩护,
主要的话术可以聚集以上的技巧,
总结精华如下:

  • 怎么重现的?我电脑上是好的,你确定你操作没问题?
  • 这种是边缘情况,不要这么操作就行,可以忽略。
  • 以前就是这么实现的,这不是Bug,这是Feature。
  • 最开始是好的,是改需求改挂的,假如需求不改就不会挂了。
  • 跑脚本跑错了,改一下重跑就行。数据的问题,怎么能叫Bug呢?
  • 这个功能不是我写的。
  • 这个Code Review的时候,A君没有看出来。
  • 这个测试的时候,B君没有测出来。

牢记以上话术,
适当时候抛出,
写Bug时心里的底气就会更足了。

后期维护

当权责分明的人

一个五千多年历史的文明古国里流传着这么一句话:
“不在其位,不谋其政。”

意思是“别人写的代码,我们不用管。别人写的Bug,我们不用修”。
现代工具提供了很便利的历史记录功能,
比如Git的话可以使用git blame
这样的功能可以让我们快速定位某个Bug是谁写的。

平常跟别人讨论代码的时候,
多用“你的代码”、“我的代码”、“他的代码”等指代副词加强代码归属感,
这样出了Bug以后,别人是甩不了锅的。
虽然这样的操作弊端是我们自己写的Bug,
也得我们自己修复。
但是依照上文的操作,
我们肯定可以把锅给甩掉。

总而言之就是一句话:
Bug都是人写的,
不是你写的,就是我写的。

当权责分明的人,
这样别人就不会对我们的代码感兴趣,
我们自己的代码,
想写多少Bug就写多少Bug。

信奉无知即无罪

无知者究竟无罪还是有罪是吃瓜群众们很喜欢争论的话题,
但在我们写Bug的程序员业界,
很显然:无知者无罪。

比如我们要用一个第三方库,怎么用呢?
百度一下,找到的第一段代码复制粘贴下来就行了。
我们不一定要去理解所有的代码原理是什么的,
因为要实现功能的需求更紧迫嘛。
就算出Bug了怎么办?
无知者无罪,
我又不知道这样会有Bug。

不太清楚这里要怎么操作,
求A君写一段代码发我。
虽然我还是不太清楚具体操作,
但是这段代码是A君写的,
这样有Bug了有罪的也是他。
而且我之后调用出了问题,
也可以写出不少blame到他头上的Bug呢。

无知即无罪,就是:
我们写程序的时候,
不需要懂得每一行程序的原理,
能用就行了,
这样程序出问题了,
也怪不到我头上。
毕竟谁没个菜鸟的时候呢?

分担权责到用户和运营上

技术肯定是不能解决一切问题的,
而且技术肯定不能解决人的问题。

所以很多时候的Bug,
不是我们实现的不对,
而是用户的操作不对。

这个工具是写给程序员用的,
他们都很聪明,
就不用写那么完善了,
适当报个NullPointerException,
他们肯定能读懂trace,
报个segmentation fault,
他们肯定会用gdb逐步调试的吧。

这个工具最终是运营用的,
end user可以培训的,
所以就不用太好用了,
功能做出来就行。
适当报个400,
教一下用户用console看一下就行了。
不是所有情况的500都是用不了的bug,
有些情况只要那么操作就可以解决了。

用户怎么不懂呢?
这,也是另一种意义上的Bug吧。

多写代码,生产力=破坏力

好了,孙子兵法三十五计都看完了,
最后一步,那当然是提升生产力了。

只要你熟读本文,
掌握了Bug生产之术,
那你勤于加班,
多写代码
肯定能掌握Bug大生产之术。

就像中国神话里一位叫“愚公”的人说的,
写Bug也一样:

虽我之死,无子存焉;
但100行代码里有51个Bug,
修了1个,还有78个Bug;
虽码不加增,Bug无穷匮也。

还是要继续修炼也。

本文思想仿《如何写出无法维护的代码》