V2CE – 程序员应该多写“坏”代码

多写些坏代码,然后学会更快地识别它们。

告诫程序员们 “不要这样做”的文章比比皆是,隔三差五网上就会冒出一篇。例如,不要使用继承,永远不要写单例,scrum项目管理已经过时,等等。但是我们真的应该摒弃一切吗?“if 语句”真的有那么糟糕吗?我们如何判断哪些建议值得听取?

世界上不存在一种完美的编程语言,也没有所谓的正确编码方式。然而,各种指南和已知的陷阱早已铺天盖地。如今在网上浏览 “编程建议” 是件很可怕的事情,因为每个人都在告诉你 “千万不要这样做或那样做” 。如此发展下去,很快我们就会没有命令可用了。一切都会是bug的来源。

思考一下这个比喻:你不能把一辆卡车从木桥上开下去,然后指望它能完好无损地到达另一端。但这并不意味着木桥已经彻底被淘汰了,也不意味着你应该停止使用卡车。

编程语言给我们提供了工具。我们需要知道何时、为何、以及如何使用这些工具。不要因为锤子砸到了手指就把锤子扔掉——应该借此机会提高“瞄准”能力。

Don’t throw your hammer away, improve your aim.

如何识别那些坏建议(Bad Advices)

好的建议有三个要素:信息本身、适用场景以及不适场景。糟糕的建议往往缺乏第二点和第三点,而常常自诩为百分百有效。

一个常见的观点是 “不惜一切代价都要避免使用继承”。如果没有明确适用场景或不适用的场景,你大概会盲目地遵循这一点,因而失去OO编程最基本的工具之一。相反,考虑一下这个说法:“继承本是一个不错的工具,但过深地继承层级往往对代码维护弊大于利”。从这种说法可以看出,问题是在于继承的深度。这种解释要精确得多,直接洞察问题本质,并提示我们相对浅的继承层级是完全可行的,不必过分担心。

另外需要注意的是语言表达。很多作者都被训练为 “大胆地写、绝不姑息”。这种论调在互联网的博客平台上尤其大行其道。但问题是,为了显得大胆而刻意夸张表达是有害的。作者常常会忘记加上:此建议并不通用 。为了避免听起来没有说服力和感染力,作者常有意无视那些建议的不适场景。

好的建议应该像“友情提示”一样的温暖,而不是如“严厉警告”一般的恐吓。 你可以回忆一下这些年来获得的真诚帮助,有哪次对方是愤怒地向你伸出了援手?

经验法则 (Rules of Thumb)

特别是在编码方面,有两条经验法则供大家参考:

一、语言的创建和维护成本很高,但如果一个特性总是被添加到新生的语言中,说明该特性仍然不可或缺。

这就是为什么全局作用域仍然很关键,继承和if语句也是如此。任何主张完全避免它们的文章,都忽略了这些特性的一个重要方面。

强制类型就是一个很好的例子。Python和JavaScript等语言创造的这类有些感性的无类型的世界吸引了很多开发者投入它们的怀抱,TA们随后便后悔用这种非结构化的语言写出了上万行代码。而在Java和C#的传统世界里,这些都不存在 。当然,这并不意味着Java和C#就是天堂。

这也难怪TypeScript会变得很受欢迎。强类型概念已经在那些所谓的类型松散型的语言中逐渐回归了——你输入差不多的信息就够了,只不过剩下的,如类型声明/注解等工作,则由编译器自动填充。这个想法非常成功,以至于它分别通过var和auto关键字进入了C#和C++世界。现在连Python都有了类型注解的功能。

二、现代编程语言在设计阶段已经规避了很多的曾经让人糟心的东西

这就是为什么我们再也看不到宏、goto语句或显式内存管理了。当年Java的垃圾回收机制有相当多的批评,但现在GC特性已经超越了JVM,几乎成为了所有的现代语言的标配之一。

最近被移除的功能是空指针异常。现代语言如Kotlin和Swift在设计上会强制执行null检查。C# 8也在走类似的路线。实现异步任务不论使用原生线程还是异步回调都会遇到类似的麻烦。好在现在,我们已经可以用更方便的async/await控制结构来更简洁地编写异步任务。

综上所述,我们可以提炼以下几点建议:

如果你想成为一个更好的编码者,请了解编程语言的历史。

尽管大多数语言最初都是由具备出色的工具意识(sense of tooling)的个体开发者创造的,但它们日后的发展都是由社区委员会来主导的。每当增加新的功能时,都会进行一系列的工作,专门讨论它们与社区的相关性和价值,并完善其设计。更改和删除功能时也是如此。Python3带来了许多难以忽视的突破性的变化,但这一切都取得了成功。

多写一点“坏”代码

如今,我们所使用的工具都是近几十年来的成功创新以及失败设计的产品。

只有当你潜心研究一些烦人的C/C++代码时,你才能真正领略到具备垃圾收集特性的语言的魅力。在那之前,你能做的就是想象一下当年的痛苦。对单例设计模式的恨意,只有那些曾经写过并面对与之相关的诸多问题(如编写测试用例)的人才能真正理解。

教材上的案例和现实中的经验相差甚远。前者不过是一种提示,真正改变你的编码方式的则是后者。

我们大多数人在初学之时,都是在没有Git或Unit Tests的情况下进行编码。这些项目往往有很多bug,而且经常不能运行。没有Git,你无法知道自己不小心改了什么。没有测试,你的项目可能会罢工几天,然后周而复始。这种经历驱使我们每天都使用这些工具。

要想真正了解如何写出好代码,你必须先写出“坏”代码。

有几种方法可以强迫自己写出“坏”代码,或者在你当前的代码中发现其丑陋的部分。归根结底:尝试用其他方式编码。这会让你知道你的解决方案有多好,或者你的解决方案曾经有多愚蠢。这里用“曾经”是因为当你意识到愚蠢时你会改变它,没错吧?

下面就为大家列举一下业余时间可以做的事情。

1. 学习一门前任语言(Parent Language): 比如Kotlin就是受到Scala的启发;除此之外,Swift试图解决Objective-C的问题;C#取代了Java。学习前任语言可以让你了解有多少“你现在拥有的东西”是当时没有的,以及它当时所解决的问题。这教会你更加欣赏很多你可能认为是垃圾的东西。

2. 学习一门后继语言(Successor Language): 如果你是一个C++开发者,你应该尝试Rust;Java粉们应该试试Go;Python用户可以试试Julia或Nim;JavaScript粉们应该尝试TypeScript或者Dart。与学习前任语言不同,这会让你知道你现在做的事情有多少是垃圾,以及如何更好地处理。

3. 学习LISP:这对很多人来说有点奇怪。LISP虽然没有变量,却是一种通用的编程语言,而且还比Haskell容易。你不需要对它精通,但可以试着写一些算法,比如斐波那契数列、快速排序或赫夫曼编码。如果你花时间去做,你会意识到很多时候变量是不必要的。
4. 用纯C编写一个文本处理器: 给定一个文本文件的路径,打开它,删除所有的换行符,并在每个句号(.)字符后添加新的换行符。然后,保持第一个和最后一个字符不变,对每个字进行重组。如果你能并行处理每一行,就能得到加分。这将快速地向你展示字符串处理是如何急剧发展的。
5.寻找设计模式: 拿一份设计模式的清单,然后打开一些你正在做或已经完成的项目。花点时间阅读每一种模式,并尝试找到可以从这种模式中受益的地方。对于你看到的每一项,试着想象一下,如果你使用了它,结果会有多简洁。当然如果你还能重构它,理应得到加分。这是将设计模式纳入你的技能库的最好方法。

这些技巧本质上都是想让你用不同的方式来编写代码,或者再看看你所做的一切。无论哪种方式,你都会发现,并不是所有的东西都像你曾经想象的那样光鲜亮丽。

此外,我不是在告诫你何为对错,也不是在教导如何编码。相反,我只是鼓励你去……编码。用一种新的语言来编码,或者尝试用两种不同的方式做同一件事。只有编码才能让你成为一个更好的编码者—— 而不是在一味地在网上搜寻编码建议。

正文完