【英译中】当我还是编程小白时,我不懂的那些事

作者:Alex Naraghi,游戏编程员。代码助推者。

原文链接:What I Didn’t Understand as a Junior Programmer

 

我还记得当我做实习生的时候,第一次看到一个超过百万行的代码库时的情景。那是一个大型的,服役超过十年的老系统,支持多国语言,包含数以千计的单元测试,被组织为好几个项目和动态链接库(dll)。想把它重新编译一遍,需要整整一夜。其中有些项目的构建过程相当复杂,需要大量的脚本,我们的代码控制系统甚至包含自定义的钩子,避免有人把违反编程风格指南的代码提交到代码库中。那时候看来,我需要花一星期的时间才能把所有的文档通读。而我的主管程序员告诉我,人们通常需要花一整年的时间才能深入理解这个项目,而我的实习期才仅仅 3 个月。

哇,当时我想,是不是只有这样,才能成为一位真正的程序员?

在那个时候,我以为自己有两把刷子。不错,我理解所有常见排序算法的复杂度。专业课对我而言是小菜一碟。我已经发布了好几个跟朋友们一起制作的游戏程序,而且我“知道”所有关于面向对象编程的东西。看到这个,我才感觉相形见绌。

写出糟糕的代码是可怕的,尤其是当项目涉及真正的金钱和人员时——它们可不是弱智的课程设计项目。更糟糕的是,你感觉你的同行前辈们能一眼看透你。他们知道。他们知道你只是漫无目标地瞎搞了一通,反复地删除自己 90% 的代码,直到你失去耐心,环顾左右之后,对代码做些疯狂的瞎改了事。如果你要做代码审查,他们会发现问题,他们皱起眉头并尝试说出问题,但你的代码如同一团乱麻,以至于你的主管程序员都感到迷惑。看过你无意义的部分接口和意大利面条一般的方法时,他甚至无法用一种合适的设计来明确表达你的代码。他一边皱着眉一边看着屏幕:“这段代码不在关键的代码路径上,所以我们将会对其运行质量保证代码,必要时我们再回头对它做检查和重构。”郁闷。

简直就是噩梦。当然我说的夸张了一些,但感觉就是那样。编程很难。你将被卷入工作空间,花费全天的时间摆弄代码风格,编程模式,运行时,调试工具,接口,搜索,规划,中断等,把这些东西放进你的思绪。别忘了,你还得确保在此期间把问题给解决掉。

尽管如此,你会克服的。

我经常会问自己同样一个问题:有那么一种知识,随着经验的增长不可避免地会接触到它们,但是这类知识是否真能被别人“教会”,还是说,只有通过一次又一次的犯错才能被真正领会?这是一个我目前还不能回答的疑问,但我有一些特别的战斗历程可以告诉你。

第一件作为新手开发者想破头都搞不懂的事情是:在弄懂执行状态之前,你无法解决因它而展现出来的 bug。执行状态,按我自己的定义,是指一组数据:但凡出现在与 bug 相临近的任意一个相关的作用域内,被程序指令读取或写入的数据都属于执行状态。

我很清楚地记得那段时间。我花了很多精力,浪费了大量时间,来回来去漫无目的地调整各个变量,希望能解决这个我并不理解的 bug。更要命的是,我曾认为自己谙熟于此并为之自豪。我知道,如果我把足够多的代码行都鼓捣一番,这个 bug 可能会发生变化,或许变得不那么明显,甚至看上去已被完全修复。

我能犯的最大罪过莫过于有关数学的错误,它们往往会涉及到用户界面或渲染方面的问题。比如当基线太高时,就把 y - box.height / 2 改为 y - box.height / 4 。这样就能修复数学上的错误吗?或许能?如果我在程序中放置一个断点,就可以查看一些变量的内容,但这样也无济于事,因为中间计算结果通常是一些看来像垃圾的数字。所以当我移动到下一代码行时,我写在程序里的数学公式就会变成一些神奇的黑盒子。

这样的 bug 修复显然是野蛮,反智,原始的白痴行为,但这非常常见。这种做法在课堂作业和周末作业项目里表现出色,因为那些作业的复杂度本来就很低,代码库很小,无需维护并且用户的覆盖度也有限。只要它能通过老师所写的单元测试,就能得到 A+ 的评分。

开发者们在他们不理解的问题上反复纠结所花费的时间,是惊人的长。

现在,我相信即使你为了修 bug 不惜四下摆弄一番,但很可能你没有就这样解决问题。被修改得最频繁的代码拥有最多的 bug,你为了修改 bug 刚刚触动了那个功能。接下来,不仅在第一个 bug 附近通常会有另一个 bug,而且你很难同时维护好你修改过的代码行和原始代码中未被修改的那部分代码,因为你并不理解当前功能得以运作的基础——执行状态。

更糟的是,现在你的版本控制日志现有误导其它程序员的危险,他们未来看代码时会根据你修复 bug 时的实现方式做出假设。就这样,这些问题会堆叠在彼此之上,因为有过切身体验,我对这种做法的潜在危险深信不疑。

幸运的是,一路过来我学到了一些东西,能够向你分享一些改进这类 Debug 工作的技巧。

如果代码非常令人费解,你可能不得不开始重构它,让它变为对人类更加可读的形式(在完成一次重构以后,一定要检查原来的 bug 是否仍然显现)。编写多个测试用例,在理论上预想它们会以怎样的方式改变数据,并验证你的假设是否正确。如果数据能够被可视化显示,比如它是几何类数据或者使用复杂的容器结构,不妨考虑以可视化方式展现它。如果有大量的中间数据,就拿出笔和纸或数字记事本,自己动手跟踪执行状态。向代码的原作者寻求支持。如果你在这过程中掉了链子,到外面散个步,抖擞一下精神再回来。深呼吸。

我深信任何代码,无论多么复杂,你都可以跟踪执行过程中的数据。代码可能是由已从公司离职的人编写的,并且实现的方式如意大利面条一般一塌糊涂,所有的变量都以他或她所喜好的英文字母命名…… 但只要经过足够的简化,可视化,通过,步进走查,验证和时间投入,你将会获得你所需的洞察力。

当然,如果能够一致地使用良好的编程原则和设计模式,执行状态的理解难度将会呈指数级下降。如果你接受我的灌输,开始评估代码背后究竟发生了什么,你将会很快被说服,开始编写强壮的、易于维护的代码。本站还会推出更多关于这个主题的文章!

你没有任何借口不去理解自己写过的代码或正在调试的程序。理解代码没有捷径,即使你感觉好像有。现在,我甚至在已经修复了一个 bug 之后,花时间逐步走查代码,以确保我完全理解。如果有必要,我还会为了将来的易用性把它重构为更好的代码,或者把我通过分析学到的东西添加到代码注释中。只有这时我才认为问题真的解决了。

你可能觉得跳过理解代码的步骤才能感觉自己像个天才,但这种走捷径的做法将会在面对真实用户,需要不定期维护的实际项目中吞噬掉你和你公司数十倍的时间。避免我曾经有的这种粗鄙的习惯吧。花时间学习解析程序复杂度,掌握调试工具,并且习惯于编写工具性代码帮自己看清 1 和 0 的代码之汤吧。这么做是值得的。没有更多的恶梦。

“大多数 bug 都源于程序的实际的执行状态与你所认为的不一样。”

我必须感谢约翰·卡马克的一篇博客文章,它让我的思想变得更具体,文章的链接在 这里

发表评论

电子邮件地址不会被公开。 必填项已用*标注