烂代码还有很多种类型,沿着功能-性能-可读-可测试-可扩展这条路线走下去,还能看到很多匪夷所思的例子。
那么什么是烂代码?个人认为,烂代码包含了几个层次:
所以,当一个团队里的底层代码难以阅读、耦合了上层的逻辑导致难以测试、或者对使用场景做了过多的假设导致难以复用时,虽然完成了功能,它依然是坨翔一样的代码。
而相对的,如果一个工程的代码难以阅读,能不能说这个是烂代码?很难下定义,可能算不上好,但是能说它烂吗?如果这个工程自始至终只有一个人维护,那个人也维护的很好,那它似乎就成了“够用的代码”。
很多工程刚开始可能只是一个人负责的小项目,大家关心的重点只是代码能不能顺利的实现功能、按时完工。
过上一段时间,其他人参与时才发现代码写的有问题,看不懂,不敢动。需求方又开始催着上线了,怎么办?只好小心翼翼的只改逻辑而不动结构,然后在注释里写上这么实现很ugly,以后明白内部逻辑了再重构。
再过上一段时间,有个相似的需求,想要复用里面的逻辑,这时才意识到代码里做了各种特定场景的专用逻辑,复用非常麻烦。为了赶进度只好拷代码然后改一改。问题解决了,问题也加倍了。
几乎所有的烂代码都是从“够用的代码”演化来的,代码没变,使用代码的场景发生变了,原本够用的代码不符合新的场景,那么它就成了烂代码。
程序员最喜欢跟程序员说的谎话之一就是:现在进度比较紧,等X个月之后项目进度宽松一些再去做重构。
不能否认在某些(极其有限的)场景下重构是解决问题的手段之一,但是写了不少代码之后发现,重构往往是程序开发过程中最复杂的工作。花一个月写的烂代码,要花更长的时间、更高的风险去重构。
曾经经历过几次忍无可忍的大规模重构,每一次重构之前都是找齐了组里的高手,开了无数次分析会,把组内需求全部暂停之后才敢开工,而重构过程中往往哀嚎遍野,几乎每天都会出上很多意料之外的问题,上线时也几乎必然会出几个问题。
从技术上来说,重构复杂代码时,要做三件事:理解旧代码、分解旧代码、构建新代码。而待重构的旧代码往往难以理解;模块之间过度耦合导致牵一发而动全身,不易控制影响范围;旧代码不易测试导致无法保证新代码的正确性。
这里还有一个核心问题,重构的复杂度跟代码的复杂度不是线性相关的。比如有1000行烂代码,重构要花1个小时,那么5000行烂代码的重构可能要花2、3天。要对一个失去控制的工程做重构,往往还不如重写更有效率。
而抛开具体的重构方式,从受益上来说,重构也是一件很麻烦的事情:它很难带来直接受益,也很难量化。这里有个很有意思的现象,基本关于重构的书籍无一例外的都会有独立的章节介绍“如何向boss说明重构的必要性”。
重构之后能提升多少效率?能降低多少风险?很难答上来,烂代码本身就不是一个可以简单的标准化的东西。
举个例子,一个工程的代码可读性很差,那么它会影响多少开发效率?
你可以说:之前改一个模块要3天,重构之后1天就可以了。但是怎么应对“不就是做个数据库操作吗为什么要3天”这类问题?烂代码“烂”的因素有不确定性、开发效率也因人而异,想要证明这个东西“确实”会增加两天开发时间,往往反而会变成“我看了3天才看懂这个函数是做什么的”或者“我做这么简单的修改要花3天”这种神经病才会去证明的命题。
而另一面,许多技术负责人也意识到了代码质量和重构的必要性,“那就重构嘛”,或者“如果看到问题了,那就重构”。上一个问题解决了,但实际上关于重构的代价和收益仍然是一笔糊涂账,在没有分配给你更多资源、没有明确的目标、没有具体方法的情况下,很难想象除了有代码洁癖的人还有谁会去执行这种莫名其妙的任务。
于是往往就会形成这种局面:
与写出烂代码不同的是,想写出好代码有很多前提:
-
理解要开发的功能需求。
-
了解程序的运行原理。
-
做出合理的抽象。
-
组织复杂的逻辑。
-
对自己开发效率的正确估算。
-
持续不断的练习。
写出好代码的方法论很多,但我认为写出好代码的核心反而是听起来非常low的“持续不断的练习”。
很多程序员在写了几年代码之后并没有什么长进,代码仍然烂的让人不忍直视,原因有两个主要方面:
而工作几年之后的人很难再说服他们去提高代码质量,你只会反复不断的听到:“那又有什么用呢?”或者“以前就是这么做的啊?”之类的说法。
那么从源头入手,提高招人时对代码的质量的要求怎么样?
前一阵面试的时候增加了白板编程、最近又增加了上机编程的题目。发现了一个现象:一个人工作了几年、做过很多项目、带过团队、发了一些文章,不一定能代表他代码写的好;反之,一个人代码写的好,其它方面的能力一般不会太差。
举个例子,最近喜欢用“写一个代码行数统计工具”作为面试的上机编程题目。很多人看到题目之后第一反映是,这道题太简单了,这不就是写写代码嘛。
从实际效果来看,这道题识别度却还不错。
首先,题目足够简单,即使没有看过《面试宝典》之类书的人也不会吃亏。而题目的扩展性很好,即使提前知道题目,配合不同的条件,可以变成不同的题目。比如要求按文件类型统计行数、或者要求提高统计效率、或者统计的同时输出某些单词出现的次数,等等。
从考察点来看,首先是基本的树的遍历算法;其次有一定代码量,可以看出程序员对代码的组织能力、对问题的抽象能力;上机编码可以很简单的看出应聘者是不是很久没写程序了;还包括对于程序易用性和性能的理解。
最重要的是,最后的结果是一个完整的程序,我可以按照日常工作的标准去评价程序员的能力,而不是从十几行的函数里意淫这个人在日常工作中大概会有什么表现。
但即使这样,也很难拍着胸脯说,这个人写的代码质量没问题。毕竟面试只是代表他有写出好代码的能力,而不是他将来会写出好代码。
说了那么多,结论其实只有两条,作为程序员: