俗话说“文无第一,武无第二”,软件开发归类于“文”,还是“武”?这个问题本身就有争议。想痛快地吵上一吵吗?进来看看吧。

大家都来吵

软件涉及到的领域太广,以至于程序员之间、程序之间很多时候难分伯仲。你可能也经常会听见一些牛逼的程序员们互相吵来吵去,很多时候并不是自己拥有一个完美的解决方案,而是觉得对方的解决方案在特定情况下不好。当然没有十全十美的方案。举个简单的例子,我们奉行的DRY(Don’t Repeat Yourself)原则,要求我们不要WET(Write Everything Twice或者We Enjoy Typing)。可即便是这么通用的原则,也可以被质疑:这样就失去了两边各自变化的能力了。听起来似乎有些道理,但是回头想想,两边各自变化的可能性有多大?这样的可能性在每个人的眼中是不一样的。因为大家的背景不一样,也许某人知道更多的未来需求,也许某人预见业务增长将会很快,也许某人曾经在这上面吃过亏……不一而足。

像DRY这样,但是更加令人难以决断的例子还有很多,列了一些常见的如下:

  • 缩进用空格还是TAB?
    据说用空格的程序员收入比用TAB的多,你还打算继续用TAB吗?
  • Java的变量名要不要默认加final?
    加上去是有它的好处,但是很烦不是吗?它的价值是否能抵过你的烦恼?这有一个最佳实践,但是否应该采用?
  • 应该测试先行地去TDD,还是补上单元测试就好了?
    测试驱动开发当然是测试先行,但现实中往往有许多不容易写第一个测试的情况。怎么破?
  • 要不要写注释?
    重构告诉我们注释很可能是坏味道,应该先尝试重构以让注释变得多余。但就有极端人士认为所有代码中的注释都是多余的。我们的观点呢?
  • 用不用设计模式?
    你有没有这样的经历,学完设计模式以后,编程时总想着套到什么地方去?到底应该何时用、怎么用、用多少?
  • 要不要尽量内联(inline)?
    有一种编程风格是尽量inline所有的变量,除非很不好懂,需要用变量名来解释。例如:
    String name = someService.getName();
    String result = otherService.getResult(name);
    return result;
    
    需要重构为:
    return otherService.getResult(someService.getName());
    
    因为比较简洁嘛。我原来也倾向于使用这种风格。但是新团队的风格是尽量抽变量,理由是方便调试。比如一行中要是出错了呢?调试时要是想知道返回值呢?当然可以查看otherService.getResult(someService.getName()),但是这个操作要是不幂等呢?似乎也有几分道理。
  • 要不要用Java 8的Lambda?
    曾经有人对我说,最好别用Lambda,因为会的员工不多,不好维护。站在他当时的立场上看,这个理由确实成立。但如果总是这样,新技术岂不是永远都上不了台了?
  • 用异常还是返回值来处理验证逻辑?
    这也是个比较经典的问题,原来认为异常影响性能,但随着时代的演进,我们更倾向于使用异常了。当然争论还在继续
  • 代码覆盖率要不要100%?
    追求100%的代码覆盖率究竟有没有意义?上80%可能比较轻松,但是最后的20%可能需要付出额外80%的劳动,值得不值得?那么,代码覆盖率设置为多少比较合理?
  • 要不要结对编程?
    一人工作一人看,结对编程效率低。但是结对的关键不是效率,而是质量。这又涉及到下面这个问题:
  • 如何衡量软件质量?
    代码行数、代码覆盖率、缺陷率、圈复杂度……但是它们足够吗?应该如何看待这些值?换个问题,如何衡量软件复杂性?这个问题又涉及到下面的问题:
  • 如何衡量程序员的KPI?
    这很难。只能参考而无绝对。
  • 软件该如何收费?
    虽然现在看起来有点而离谱,但是很早以前曾经任职的公司确实是按行数收费的,行数=编写的代码行数+自动生成的代码×系数(如20%)。更加透明一点的是按工时收费。
  • 可以不用QA吗?
    曾任职的公司开展过一场“去QA化”运动,现在也不提了。这里的回答很有意思。
  • 是否把修复CI当做第一优先级的事情?
    这是测试别人是不是了解CI的三个问题之一。当然修复CI的优先级很高。但是有多高呢?我们在实践中是根据具体情况有所取舍的。线上生产环境的问题,才是第一优先级的事情。甚至就连上面这句话有时也不成立。
  • 要不要代码审查?如何审查?
    大部分人还是认同代码审查的,但是审查方式可以有很多种。最直接的是团队成员都围着电脑看代码,让一个没参与的人来讲解。但是可能费时很多,而且不是所有人都能进入状态,有的人喜欢一起看,有的人喜欢单独看。有一种方式是工具的支持,可以进行一对一的审查,这个可以挖个坑单写一片文章了。
  • 要不要鼓励项目中使用多种技术,比如多种测试框架,版本管理工具等?
    这里存在着工作效率与提升技能之间的小冲突。使用已经用过的技术当然开发起来更快,但是也是去了尝新的机会,或者说是乐趣。而过量的技术运用到一个项目中,也会带来沉重的负担。
  • PHP是不是世界上最好的语言?
    我还是不评论了吧,免得挨揍。

底线还得有

对于永远稳定不会变化的需求(尽管很少,但这样的需求确实存在)而言,软件开发也许就能够分出高下来。举个例子:一个确定不会被重用的小工具。在这种情况下,可以适用的原则是:越快越好。我们甚至可以适当允许一些bug的存在,因为修复它们所需的时间可能大于手动修复运行结果所需的时间。另外,永远稳定不会变化的需求真的就永远不会变化吗?未必。但是在开发的某个时间点上,它确实是被认为是永远不会再变化的了。唯一不变的是变化本身。

曾经有同事去印度当了几个月的程序员讲师,回来后告诉我,在回答学员们的问题时,讲师们说得最多的就是这句话:“It depends.”。这基本上是一个放之四海而皆准的原则:具体情况具体分析。那是不是所有的问题都直接无脑地“具体情况具体分析”就完了?当然可以,但这是一种思想上的懒,不是我这“懒程序员”的“懒”。因为这句话对解决问题并不能有太多实质上的帮助嘛。关键是,我们还得就着“具体情况”来“分析”。所以,我们可以在其上再构建一些原则,来覆盖特定的情况。

比如说设计模式,它是对特定问题的特定解决方案。不要一股脑儿就往上套,好的经验是在发现坏味道以后,重构到设计模式,甚至是重构了一半,就已经消除掉坏味道了。原则:越简单越好
比如说final,它并不能带来明显可观的价值,所以应该以大多数人的习惯为先。原则:贴近大多数人的习惯
比如说lambda,明显它是更加先进的生产力,所以上面的原则就不适用了,应该以先进的生产力为先。原则:采用先进的生产力
比如说代码覆盖率,高覆盖率自然是好,但是值得吗?比如Java可能就很难做到100%,但JS就能轻松一些。原则:采用性价比高的方案
从“代码审查”中我们也可以看到,每个人都是不一样的,我在人际风格与有效沟通实战中也曾提到不同风格的人。原则:因人而异
从“尽量内联”、“异常或返回值”中我们也可以看到,应该保持开放的心态来调整各原则。原则:原则需要与时俱进
比如说要不要写注释,大多数情况下,组织得当的方法名、变量名已经能够说明问题了,这时的注释就显得多余。但偶尔还是需要介绍一块代码的来龙去脉,这时的注释就是必要的。原则:具体情况具体分析

我们现在已经有了好几条原则了:

  • 越快越好
  • 越简单越好
  • 贴近大多数人的习惯
  • 采用先进的生产力
  • 采用性价比高的方案
  • 因人而异
  • 原则需要与时俱进
  • 具体情况具体分析

有些原则可能在特定的情况下是冲突的,需要自己思考究竟哪条原则更加符合现实情况。在适当的时候使用适当的原则(就像设计模式一样),而不是拿着锤子看见啥都像钉子(也像设计模式一样)。另外,需求是变化的,我们的原则也不必一成不变。有句话说“规则是用来打破的”。我想说的是,在充分理解规则之后,再来决定是不是打破它,并承担相应的后果。或者,考虑是不是用更高级的规则(如“具体情况具体分析”)来约束它,或是用更低级的规则来覆盖它吧。