
俗话说,如果你不为自己以前写的代码感到羞愧,说明你作为程序员没有进步——我对此深表赞同。我四十多年前开始出于兴趣编程,三十多年前开始从事编程工作,所以犯过不少错误。 非常作为一名计算机科学教授,我教导学生从错误中学习——包括他们自己的错误、我的错误以及他人的错误。我认为现在是时候分享我自己的错误了,以免失去谦逊。我希望这些经验能对某些人有所帮助。
第三名是微软的 C 编译器。
我的老师认为《罗密欧与朱丽叶》不能算作悲剧,因为剧中的人物并没有犯下真正的罪行——他们只是像青少年一样做了蠢事。当时我不同意他的观点,但现在我发现他的观点也有几分道理——尤其是在编程方面。
在麻省理工学院读完二年级时,我年轻且缺乏经验,无论是在生活还是编程方面。那年夏天,我在微软的C编译器团队实习。起初,我做一些常规工作,比如性能分析支持,但后来我被分配到我认为编译器中最有趣的部分——后端优化。具体来说,我的任务是改进x86代码中分支语句的处理。
为了编写出适用于每一种可能情况的最优机器代码,我义无反顾地投入其中。如果数值分布密度很高,我就把它们输入进去。 如果它们有公约数,我就用它来使表格更密集(但前提是除法可以用以下方式完成): 当所有值都是 2 的幂时,我进行了另一次优化。如果这组值不满足我的条件,我将其拆分成几个优化后的情况,并使用优化后的代码。
那简直是一场噩梦。多年后,我才知道接手我代码的程序员恨透了我。

吸取的教训
正如 David Patterson 和 John Hennessy 在《计算机体系结构与计算机系统设计》一书中写道,体系结构和设计的主要原则之一是使事物尽可能快地运行。
加速常见情况比优化罕见情况更能有效地提升性能。讽刺的是,常见情况往往比罕见情况更简单。这条合乎逻辑的建议的前提是,你知道哪些情况属于常见情况——而这只有通过仔细的测试和测量才能实现。
为自己辩解一下,我当时试图理解分支语句在实践中是如何运作的(例如,有多少个分支以及常量是如何分布的),但这些信息在 1988 年是无法获得的。然而,我不应该每次当实际编译器无法为我的人为示例生成最优代码时就添加特殊情况。
我需要请一位经验丰富的开发人员来帮忙,共同找出常见的使用场景并针对性地解决它们。这样一来,我编写的代码量会减少,但这其实是件好事。正如 Stack Overflow 的创始人 Jeff Atwood 所说,程序员最大的敌人就是他们自己:
我知道你出发点是好的,我们所有人都是如此。我们编写程序,热爱写代码。我们就是这样的人。我们总觉得任何问题都能用胶带、自制的临时方案和一点点代码解决。虽然程序员们很难承认,但最好的代码就是不存在的代码。每一行新代码都需要调试和维护;它需要被理解。当你添加新代码时,你应该心存不舍和厌恶,因为所有其他选择都已经用尽。许多程序员编写了过多的代码,这反而成了我们的敌人。
如果我当初写的是更简洁、涵盖常见情况的代码,那么以后需要更新的时候就会容易得多。但我却留下了一堆没人愿意收拾的烂摊子。

第二名:社交媒体广告
我在谷歌从事社交媒体广告工作时(还记得 Myspace 吗?),用 C++ 写过类似这样的代码:
for (int i = 0; i < user->interests->length(); i++) {
for (int j = 0; j < user->interests(i)->keywords.length(); j++) {
keywords->add(user->interests(i)->keywords(i)) {
}
}程序员或许能立刻发现这个错误:最后一个参数应该是 j,而不是 i。单元测试没能检测到这个错误,我的代码审查员也没注意到。程序发布后,一天晚上我的代码攻击了服务器,导致数据中心里的所有计算机都崩溃了。
什么可怕的事情都没发生。由于代码在全球发布前已在单个数据中心进行了测试,所以没有人搞砸任何东西。SRE工程师们暂停了打台球,进行了一次小规模的回滚。第二天早上,我收到了一封包含崩溃转储文件的邮件,修复了代码,并添加了可以捕获该错误的单元测试。因为我遵循了既定流程——否则我的代码根本无法发布——所以没有出现其他问题。

吸取的教训
许多人认为,如此重大的错误必然会导致责任人被解雇,但事实并非如此:首先,所有程序员都会犯错;其次,他们很少会犯同样的错误两次。
事实上,我认识一位程序员——一位才华横溢的工程师——他因为一个错误而被解雇。后来他被谷歌录用(并且很快得到了晋升)——他在面试中坦诚地承认了这个错误,而且这个错误并没有被视为致命的。
这就是 关于IBM传奇总裁托马斯·沃森:
一份价值约一百万美元的政府合同宣布开标。IBM——更确切地说是托马斯·沃森先生本人——渴望拿下这份合同。不幸的是,销售代表未能成功,IBM最终落选。第二天,这位员工来到沃森先生的办公室,把一个信封放在他的桌上。沃森先生甚至都没看一眼——他早就料到这位员工会来,知道这是一封辞职信。
沃森问出了什么问题。
销售代表详细汇报了投标过程。他指出了过程中出现的错误以及本可以避免的错误。最后,他说:“沃森先生,谢谢您让我解释。我知道我们多么需要这份订单,我知道它有多么重要。”说完,他转身离开了。
沃森走到门口,看着他的眼睛,把信封递还给他,说:“我怎么能让你走呢?我刚刚在你身上投资了一百万美元的教育。”
我有一件T恤,上面写着:“如果你真的能从错误中吸取教训,那我已经是硕士学位了。” 事实上,说到犯错,我可是博士。
第一名:App Inventor API
真正灾难性的错误会影响大量用户,成为公开信息,需要很长时间才能弥补,而且往往是那些本可以避免错误的人犯下的。我犯的最大错误符合所有这些标准。
越差越好
我读 我在上世纪90年代读研究生时就写过这种方法,我很喜欢它,所以也把它作为学生的必读书目。如果你记不太清了,不妨复习一下;文章很短。在这篇文章中,作者从多个层面对比了“力求完美”和“宁可差也好”这两种理念,其中就包括简洁性。
正确的方法:设计应该易于实现且易于使用。界面简洁性比实现便捷性更重要。
越糟糕越好:设计应该简单易行,易于使用。易于实施比易于使用更重要。
我们暂且把这件事忘掉吧。可惜的是,我把它忘了很多年。
应用发明家
在谷歌工作期间,我曾是团队的一员。 一个面向初学者的在线拖放式开发环境 Android——开发人员。那是2009年,我们正赶着按时发布alpha版本,以便在夏季举办教师研讨会,让教师们能够在秋季将这个环境应用到课堂上。我自告奋勇地负责实现精灵(sprite)功能,怀念当年在TI-99/4计算器上编写游戏的日子。对于不熟悉精灵的人来说,它是一种可以移动并与其他软件元素交互的二维图形对象。精灵的例子包括宇宙飞船、小行星、球和球拍。
我们使用 Java 实现了面向对象的 App Inventor,因此对象数量庞大。由于球和精灵的行为非常相似,我创建了一个抽象的精灵类,其中包含 X、Y、速度和方向属性(字段)。它们共享相同的碰撞检测、屏幕边缘反弹等方法。
球体和精灵的主要区别在于实际绘制的内容——一个是实心圆,一个是栅格图像。由于我先实现了精灵,因此指定图像左上角位置的 x 和 y 坐标是合理的。

精灵图正常工作后,我决定用极少的代码来实现球体对象。唯一的问题是,我选择了最简单的方法(从实现者的角度来看),即直接指定球体轮廓左上角的 x 和 y 坐标。

事实上,必须标明圆心的 x 坐标和 y 坐标,正如任何数学教科书和任何其他提到圆的资料中所教导的那样。

与我之前的错误不同,这次的错误不仅影响了我的同事,还影响了数百万 App Inventor 用户。他们当中许多人是孩子,或者完全是编程新手。在开发任何涉及球体的应用时,他们都不得不执行许多不必要的步骤。虽然我常常拿其他错误开玩笑,但这次的错误至今仍让我感到懊恼。
直到最近,也就是十年后,我才最终修复了这个漏洞。“修复”而非“打补丁”,因为正如 Joshua Bloch 所说,API 是永恒的。由于无法进行会影响现有程序的更改,我们添加了 OriginAtCenter 属性,旧程序中该属性的值为 false,所有后续程序中该属性的值为 true。用户自然会问,究竟是谁想出把原点放在中心以外的位置的?是谁?十年前,一个懒得编写完善 API 的程序员。
吸取的教训
在开发 API 时(几乎每个程序员迟早都要开发 API),你应该遵循 Joshua Bloch 的视频中概述的最佳建议。“ 要么 :
- API既能给你带来巨大的好处,也能带来巨大的危害。好的API能带来忠实的客户,糟糕的API则会成为你挥之不去的噩梦。
- 公共 API 就像钻石一样,永存不朽。全力以赴:你不会再有第二次机会把它做好。
- API 概述应简洁明了。 — 仅包含类和方法签名及描述的单页文档,每项内容不超过一行。这样,如果第一次 API 设计得不够完美,您可以轻松地进行重构。
- 描述用例在实现 API 甚至制定其规范之前,您应该避免实现和指定一个完全没有功能的 API。
如果我事先写个简短的提纲,模拟一下场景,我可能就能发现错误并改正。即便没有,我的同事也肯定能发现。任何影响深远的决定都需要至少一天的考虑(这不仅适用于编程)。
理查德·加布里埃尔的文章标题《越糟糕越好》暗示了抢占市场先机所带来的优势——即使产品并不完美——而其他人却可能花费无穷的时间去追求完美。反思精灵代码,我意识到我甚至不需要编写更多代码就能把它做好。最终,我错了。
结论
程序员每天都会犯错——无论是编写有缺陷的代码,还是未能尝试一些能够提升技能和效率的方法。当然,你可以成为一名程序员,而不会犯我曾经犯过的那种严重错误。但是,如果你不认识到自己的错误并从中吸取教训,就不可能成为一名优秀的程序员。
我经常遇到一些学生,他们觉得自己犯的错误太多,因此不适合编程。我知道在IT行业,“冒名顶替综合症”非常普遍。我希望你们能吸取我总结的经验教训,但请记住最重要的一点:每个人都会犯错——尴尬的、可笑的、糟糕的。如果以后我没有足够的素材继续写这篇文章,我会感到惊讶和失望。
来源: habr.com
