Skip to content

🔰 程序员大牛是如何编写程序的?

🕒 Published at:

程序员大牛是如何编写程序的?

好的程序员是怎么写代码的呢?点燃一根烟,一边吸一边进行周密的思考,待想法成熟了,一把操起键盘,一阵噼里啪啦敲击,一气呵成吗?

或者这样,使用具有步进调试功能的 IDE,例如 Visual Studio,一边编写代码,一边调试代码,一步一调试,直到完成所有需求?

你是哪种编写方式呢?那些计算机编程大牛们他们一般又是怎么写代码的呢?

肯·汤普森说:

我只通过 printf 语句(或类似的 console.log)进行调试,几乎从不使用单元测试或调试功能。通过先设计数据结构开始项目,然后自下而上逐步开展工作,可能会写一些一次性的测试示例。(其实很多大神都是这种编程方式。)

乔·阿姆斯特朗说:

在设计软件时,我更喜欢在开始编写代码之前,尽可能严格地记录文档,尤其是对于那些涉及实时网络协议的困难项目。我首先使用原型来解决关键问题,而对于调试,则只使用打印语句(这一点和肯·汤普森一致)。

杰米·扎温斯基说:

我也更喜欢只使用打印语句调试代码(大神的工作方式惊人一致)。我的流程是自顶向下或自底向上写代码,让代码自然进化,在必要时重构。在开发过程中,我几乎从不使用单元测试,我觉得它会减慢开发速度,破坏我的开发节奏。

这三位毫无疑问都是计算机世界的大牛。第一位是 C 语言的创建者,第二位是 Erlang 语言的创建者,第三位是 Mozilla 浏览器的创建者。有人说,他们开始编程的年代,还没有可以步进的调试器,还没有单元测试这些完备的开发理念,以致于他们没有养成现代「良好」的编程习惯。

对于这些话,我想说,TOO YOUNG TOO SIMPLE,肯·汤普森可是可以手撸 C 语言和 Unix 操作系统的人,他如果想写一个可以步进的调试器,或者设计一个完善的 TDD(测试驱动开发)工程思想方案,你觉得他做不到吗?肯·汤普森今天还在 Google 一线工作,与同事们一起创建了并维护着 Golang 语言,你觉得他没有接触过可以步进的现代调试器吗?还是他年纪大了学不会呢?

我觉得根本原因,在于扎温斯基说的那句话,使用步进调试功能和编写单元测试代码,会减慢开发速度,破坏开发节奏,这是根本原因。程序员写的程序是并发的、多线程的,但程序员写代码这件事却是单线程的,他们可不想被像 CPU 一样打断。

对程序员如何编程这个问题,一个有经验的程序员表示:

在我看来,编程是一门艺术。一个好的程序员是一个艺术家,是一个思想家,是一个问题解决者,是一个创造者和一个有远见卓识的人,所有优良品质都结合在了程序员身上。他们以简单的方式思考可以长期解决问题的方案,他们愿意遵守规则;如果还没有规则,他们也可以创建规则然后遵守。优秀的程序员也会读很多书,并且总是在技术上不断更新自己。

另一位有近 30 年编程经验的程序员尼古拉·米哈洛夫表示,编写程序并不想人们想象的那样酷,一点也不帅。下面内容来自他的分享。

在高中期间,我在全国编程比赛中名列前三,并且是国际比赛的候选人。我自己和团队也曾在国民赛中排名第一。作为下班后的爱好,我制作了一个绘画应用程序,最终该程序拥有了约 1000 万用户,并且与类似应用程序相比,运行时出现的问题非常少。它在 Windows Phone 的印度照片应用程序中排名第一,在西班牙排名第二。

这段经历说明他是一名出色的程序员,至少天赋很不错。

我认为没有「最好的」程序员,因为每个人的表现都不一样,即使是在同一个任务上也是如此。

在我从事软件开发的前 10 年(共 28 年)中,我每天编写大约 13 小时的程序(很厉害,每天写 13 个小时的代码,并不是我们想象的老外每天只工作 8 小时,只写 2、3 个小时的代码)。我感觉这很有趣,我去完成任何软件方面的事情都没有感到压力,都是基于兴趣驱动的。我不断向朋友学习,从书籍中学习,做项目,尝试做新的事情,我很少感到无聊,总有新东西要学。我从 12 岁左右开始,就一直是这样的状态。

下面是我对编码的看法:

  • 如果代码量很小,例如是程序的一部分,可能是一个 RESTFul API,或者一种小算法,这时候可能要考虑使用的数据结构是什么,这种情况下应该是直接上手就写了,没有什么提前的推演和规划。

  • 要尽可能多地考虑边缘情况,并针对它们一一处理和测试,确保处理所有可能发生的错误和已经发现的边缘情况。举一个简单的例子,假设需求是「反转一个字符串中的所有单词」,对于一些特殊的字符串,例如空字符串、空白字符串、一个单词的字符串、2 个单词的字符串、10000 亿个单词的字符串,我们应该如何处理呢?

    还有,什么是空白字符串,这涉及到系统中对空白字符的定义,对于不同的空白字符或其组合,例如空格、制表符、不可打印的空格、换行符等,当遇到这些符号时我们又如何处理呢?

    还有一些其他方面的特殊情况,例如单词之间,句首和句尾的多个空格如何处理?对于从右到左的语言和没有单词分隔符的语言,如果我们在没有分隔符的情况下,混合使用阿拉伯语+英语单词又会发生什么?等等,看似这是一个简单的小需求,背后却隐藏着很多需要考虑的边缘情况。它并不简单。

  • 我主要在代码中思考,而不是编码前准备,尤其是前 10 年。后来我开始重视编写注释,会在注释中解释更多内容,以便后续阅读和维护方便。但在一开始,我只要代码可以工作,很少使用或不用注释。

  • 几乎没有单元测试。我觉得它们就像道路上的侧护栏,侧护栏用于阻止可怕的汽车碰撞,任由车辆在护栏上刮擦。我认为生产中的大多数错误都在单元测试可以捕获的场景之外,所以觉得单元测试很鸡肋。(上面提到的边缘情况处理,是在代码中处理的,并非指在单元测试中覆盖。不同类型的项目,单元测试的作用和重要性是不一样的。还有,因人而异。)

  • 比单元测试更好的方法是,对于任何代码更改,通过分析当前函数的所有消费代码,分析它们触发的所有副作用,以及所有可能影响到的边缘情况,然后测试所有代码。这能让我们对整个代码库有更好的理解,可以消除对单元测试的「温暖」的依赖。将整个项目装在心中,做到熟悉每一行代码,这样做的好处是:1)首先便于发现新的错误和需要改进的地方;2)确实有助于帮助我们提高代码质量。

    事实上这件事并不困难,一旦有条不紊地进行全库的洞悉,这件事就会变得简单。我已经使用它取得了巨大成功,有一个项目,有数百万用户,作为一个高端嵌入式系统的软件解释器,和一个高科技研究公司桌面软件的一部分,在生产中运行服务 2 年,0 崩溃。另一个项目运行了大约 5 年,没有出现一个问题,直到服务器退役后我得到了一个 ping,发现它仍然被大约数百名未迁移到新系统的用户使用。

    我知道有很多错误或异常,是不会或很难被单元测试捕获的,这些异常通常是集成的、未考虑的边缘情况或类似的东西。通过洞悉项目,在代码变动时测试一切,并记录一切,不必进行单元测试。我知道这个观点有争议,可能会激怒很多人,有人可能会说,团队其他人怎么办,如果你忘记了测试代码怎么办,等等。好吧,这时候就是团队测试纪律、团队文化、最佳实践和编码规范要发挥作用的时候了。

    在研发中发现问题,而不是从现在起 6 个月后,那时候所有「单元测试」都已通过并且产品已投入生产运营,相比那个时候发现问题要好的多。并且,我发现,一旦完成多次全面检查,后面这件事也会容易很多。这看似浪费时间,其实在节省时间。

  • 对于更复杂的算法,我会写一篇关于如何工作的页面注释。(这也是为了方便回忆和他人阅读。)

  • 对于变量命名,总是使用富有表现力的描述词,例如 currIndex、row、col 等。即使 x、y 都比 i、j 要好。

  • 对于函数命名,尽可能实现自我记录。名称应该准确地说明函数的作用,要尽量避免函数中产生副作用。如果函数是有条件地执行任务,是一个动作,则可以命名为 UpdateUserIfNeeded(...),而不是 UpdateUser(),或者 GetInfoAndUpdateDb()而不是 GetInfo()。

  • 对于强类型,考虑使用 User、SignedInUser、ModeratorUser 这样的类型。如果匿名用户尝试访问需要登录的资源,可能会导致编译时错误。(将用户分成不同的类型,胜于在一个 User 类型中处理所有用户身份。)

  • 除了性能原因之外,尽量保持数据不可变。

  • 我每天使用的提示检查模板是:1) 始终检查所有边缘情况;2)解析没有验证的数据;3)简化 if else 语言,如果可以就提前退出;d)快速崩溃,总是在需要开发人员立即修复代码的地方马上抛出异常,不要静默它们;这里要与用户错误区分开,用户错误总是要处理的,而开发错误总是选择抛出。

另外,在遇到困难时请不要自暴自弃。我大约每 5-10 行代码就会产生 1 个错误,这很正常,有错误绝不是不足。我喜欢通过处理边缘情况来规避大部分错误,但我不知道我没有捕捉到全部。庆幸你发现的每个错误吧,这些错误可以避免成为生产问题。有时在代码审查中,有 40 多条评论也是很常见的,接受自己就好。

小结

这是一位大佬,他分享了自己接近 30 年的编程经验,很诚恳。对于调试和单元测试,大佬都不喜欢在编码中途停下来做这件事,他们更喜欢在一次性编写完成后集中做自动化测试。与其被自己的单元测试打断,在中国开放式的工作环境中,我想他们是不是更介意被没完没了且毫无意义的会议打断呢?

还有钉钉,你一直不回复,可以一直钉你,钉到会回复为止。我很想知道肯·汤普森如果在这样的环境中工作,他会是什么表情。

image-20221011121717446

参考链接