时至今日, 笔者已经有十年以上的写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无穷匮也。

还是要继续修炼也。

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