摆脱这些坏习惯可以带来意想不到的效果:编写出更优质精简的代码。
作者 | Peter Wayner
译者 | 谭开朗,责编 | 郭芮
出品 | CSDN(ID:CSDNnews)
以下为译文:
我们都有过这样的经历:在妈妈不注意的时候偷吃一块饼干;聚餐时过量饮酒;把车停在超时停车的停车位上——我们甚至在死亡边缘疯狂试探。是的,我们违反了许多编程的基本规则,那些大家都认为是不好的规则,而我们却偷偷地使用着。
我们对好的编程规则嗤之以鼻,输出的代码也一塌糊涂——但我们做出来了。没有来自编程之神的五雷轰顶,我们的桌面也没有爆炸。事实上,只要我们的代码可以编译并交付,客户似乎就很满意了。
这是因为糟糕的编程不像安装电路或者摸老虎屁股那样有直接的危害性。大多数情况下,它也是可以运行的。规则通常是作为一种指导或格式上的建议,并没有硬性规定一定要遵守,也不会导致代码出现问题。当然,你的代码可能会被人耻笑,甚至可能大家公开嘲笑你。不过,这种挑战惯例的行为可以让人增加一点颠覆传统的快感,哪怕是在不经意间。
让事情变得更复杂的是,有时候违反规则反而更好。(嘘!)输出的代码会更干净,甚至可能会更快更简单。规则通常显得太过于宽泛,有技巧的程序员可以通过打破这些规则来优化代码。不要告诉你的老板,这对你的编码生涯会很有意义。
下面这十条编码习惯常常是被驳斥的,但我们很多人就是会不由自主地使用它们,觉得好用且实用。
在学校里,拷贝是不对的。而在工作中,规则不是很明确。当然有一些代码块不应该被窃取。如果它来自私有代码,不要将它拷贝到你的堆栈中,特别是标记了版权信息的。请编写自己的版本,这是有报酬的。
更棘手的问题出现在原创者想要分享的时候。可能是在一个在线编程论坛上;也可能是具有许可证的开放源代码(BSD、MIT),它允许窃取其中一到三个函数。你无需承担法律责任。你的工作是解决问题,而不是重新发明轮子。
大多数情况下,拷贝的优点是不可抗拒的,而缺点可以稍加限制。从一个可靠来源处获得的代码已经至少经过一轮思考与实践。最初的作者寻找解决方案并找到新思路,给出了循环不变量和数据流。
拷贝的棘手问题是,是否存在一些未发现的bug,或者关于角色或底层数据的一些不同假设。也许你的代码混合了空指针,而原始代码却从未检查出来。如果你能解决这些问题,就好比你的老板能从两个程序员那里得到输入一样。这是结对编程,没有华丽的铺陈。
在过去的十余年里,功能范式一直在提升。研究表明,在调用嵌套函数的基础上构建程序,代码会比老式的变量和循环更安全、更少bug,所有这些都足以使程序员满意。狂热者义愤填膺地谴责代码审查和拉取请求中的非功能性请求。这可能是真正的优势。
但有时你只需要复制粘贴。精心设计和优雅规划的代码不仅需要时间来想象,还需要时间来构建和导航。所有这些层都增加了复杂性,而复杂性就是金钱。写出漂亮函数代码的开发人员需要提前规划,并确保所有数据都沿着正确的路径传递。有时候改变一个变量更容易,这也许可以写个评论来解释一下。即使在评论中向未来几代人注上一长串真诚的道歉,也比以正确的方式来重构整个系统要快得多。
软件中的大多数空格对程序的执行没有影响。除了像Python等少数使用空格表示代码块的语言外,大多数空格对程序的行为没有影响。尽管如此,仍有一些执迷于此的程序员,他们计空格数并坚持认为这很重要。曾有人义正言辞的向我老板控诉我在写“非标准代码”,且他一眼就看出来了。我的错咯?没有在等号两边都加上空格,违反了ESLint space-infix-ops规则。
有时你必须考虑比空间位置更深层次的东西。或许你担心数据库超载,又或许你担心一个空指针会使你的代码崩溃。几乎代码的任一部分都比空格更重要,即使挑剔专横的标准委员会已经制定了诸多关于空格或制表符位置的规则。
令人惊奇的是,有几个很好的工具可以自动地重新格式化代码,以遵循任何规定的linting规则。我们不需要花时间去思考这个问题,如果它如此重要,我们可以通过运行工具来清理问题。
禁止使用goto可以追溯到许多结构化编程工具尚未面世的时代。如果程序员想创建一个循环或跳转到另一个程序中,他们需要输入GOTO再在后面加上一个行号。几年后,编译器团队允许程序员使用字符串标签而不是行号。这在当时被认为是一个热门的新功能。
有些人认为这会导致“意大利面式的代码”。代码会变得不可读,并且很难理解代码的执行路径。那是一团乱麻,永远缠绕在一起。Edsger Dijkstra用一篇名为“Goto语言害人不浅”的滑稽手稿建议禁止goto命令。
但绝对分支是没有问题的。这就让人纠结了。通常,巧妙的break或return将提供一个非常清晰的声明,说明代码在该位置正在做什么。有时候,将goto添加到case语句中会更容易,而不是一组结构更合理的层叠if-then-else块。
也有反例,苹果SSL协议栈中的“goto fail”安全漏洞就是最好的例子之一。但是,如果我们小心避免case语句和循环的一些棘手问题,我们可以插入良好的绝对跳转,让读者更容易理解。我们可以插入一个break或return,这对每个人来说都更干净简明——可能除了那些goto厌恶者。
那些热爱类型化语言的人认为,如果为每个变量添加明确的数据类型声明,就可以写出更好、没有bug的代码。在代码运行前,花一段时间来拼写出类型,可以帮助编译器标记出愚蠢的错误。这可能不容易做到,但它是有帮助的。这是编程中阻止bug的一种有备无患的方法。
但是时代变了。许多较新的编译器足够智能,可以通过查看代码来推断类型。它们会前后反复的查看代码,直到确定变量是string,int或其他类型。如果这些推断的类型不成队列,那么编译器就会抛出一个错误标志。因此就不再需要我们输入变量了。
这意味着现在可以通过省略一些最简单的声明来简化代码。代码变得更简洁了,而且读者通常能够猜到在for循环中名为i的变量是一个整数。
程序员喜欢称之为“溜溜球代码”。一开始先将值存储为字符串,然后又解析成整数,接着又转换回字符串。这是非常低效的。你几乎可以感觉到CPU在所有额外负载下的挣扎。聪明的程序员之所以能快速的编码,是因为他们事先会设计架构,以尽量减少转换。因为他们的良好规划而使得代码更快的运行。
但是不管你信不信,有时候这种溜溜球代码是有道理的。比如你有一个很棒的库,在其专有的黑盒子里能做无数智能的事情。又比如,老板会开一张七位数的支票,赋予黑盒子所有杰出功能。如果库需要字符串形式的数据,那你就给它字符串,即使你刚将其转换为整数。
当然,你也可以重写所有代码以尽量减少转换,但这需要时间。有时,代码多运行一分钟、一小时、一天甚至一周都没有问题,因为重写代码会花费更多的时间。有时候,积累技术债务比一开始就把它建好要便宜。
有时,这个库不是私有代码,而是你很久以前自己编写的代码。有时,将数据转换一次比重写库中的所有内容更快。所以,就继续写溜溜球代码吧,没关系的,我们都经历过。
其中一个标准规则是,程序员在大二时修完数据结构课程后,绝不应该编写用于存储数据的代码。其他人已经编写了我们需要的所有数据结构,他们的代码经过了多年的测试和重新测试。它与语言捆绑在一起,而且可能是免费的。而你写的代码可能只有bug。
但有的时候,你会发现数据结构库有点慢。有时它们迫使我们进入一个可能是标准的但对我们的代码来说是错误的结构。有时,在使用结构之前,库会要求我们重新配置数据。有时库包含一些所谓有备无患的保护功能,而我们的代码不需要它们。
如果遇到这种情况时,我们就该编写自己的数据结构了。这可能会更快更好。有时它使我们的代码更简洁,因为我们没有包含所有额外的代码来精确地重新格式化数据。
很久以前,有人创建了C语言,希望将所有抽象的可能性封装在一个简单的结构中。某些事情在开始时就要做,某些在每次循环时要做,还有一些方法可以告诉你什么时候完成。当时,它似乎是一种捕捉无限可能性的完美语法。
然后,现在一些现代的责骂者只看到其不便性。太繁琐了。所有这些好的可能性也同样可能是坏的。这使得阅读和拓展变得更加困难。他们喜欢功能范型,没有循环,只有应用于列表的函数,计算模板映射到一些数据。
有时,无查找方式更简洁,特别是只有一个整洁的函数和一个数组时。但有时老式的循环要简单得多,因为它可以做更多的事情。例如,如果您可以在找到第一个匹配项时立即停止,那么搜索它就会变得更简单。
此外,当需要对数据执行多个操作时,映射函数会鼓励更草率的编码。假设你想取绝对值然后取每个数的平方根。最快的解决方案是映射第一个函数,然后映射第二个函数,对数据进行两次循环。
规则制定小组宣称,在代码行中的某个地方,每个循环都应该有一个“常量”,即在整个循环中逻辑语句为真。当该常量不再为真时,循环结束。这是考虑复杂循环的一种好方法,但它会导致愚蠢的禁令——比如禁止我们在循环中间使用return或break。这也包含在禁止goto语句规则中。
这个理论很好,但是它通常会导致更复杂的代码。考虑这种简单的情况,遍历数组,将找到的元素传给test函数,并将该元素返回:
while (i<a.length){
...
if (test(a[i]) then return a[i];
...
}
循环常量爱好者会要求我们添加另一个布尔变量,命名为notFound,然后这样使用:
while ((notFound) && (i<a.length){
...
if (test(a[i])) then notFound=false;
...
}
如果这个布尔值命名正确,它就是一段很好的自文档化代码,更易于大家理解。但它也增加了复杂性。它还意味着分配另一个局部变量并阻塞寄存器,而编译器可能不够智能,无法修复这些问题。
一些最有趣的语言可以让你做一些尤为曲折的事情,比如重新定义看起来应该是常量的元素的值。例如,Python允许输入TRUE=FALSE,至少在2.7版之前是这样的。这并没有造成某种逻辑的崩溃和宇宙的终结;它只是交换了TRUE与FALSE的意义。你也可以用C预处理器和其他一些语言玩类似的危险游戏。还有一些语言允许你重新定义运算符,比如加号。
这是一种延伸,但是当要更快速的重新定义一个或多个所谓的常量时,其在代码块中意义非凡。有时候,老板希望代码做一些完全不同的事情。当然,你可以遍历代码并更改每个事件,或者重新定义。它会让你看起来像个天才。不需要重写一个庞大的库,只需稍微翻转一下,它就会执行相反的操作。
也许在这里划清界限比较好。你不应该在家里尝试做这个,不管它有多聪明和有趣。这太危险了——真的......
原文:https://www.infoworld.com/article/2992566/10-bad-programming-habits-we-secretly-love.html
热 文 推 荐
☞
点击阅读原文,即刻参加!