DOOM 之父 John Carmack 谈软件工程中的艺术与科学

2015-09-20 13:23


虽然我不是个游戏超级玩家,但却是因喜欢游戏而开始学习编程的(特别是图像渲染算法)。所以当我看到John Carmack在2012 Quake­Con上的发言时,我觉得自己该听听,学习游戏设计以及开发相关的知识。

不过我所听到的是一个黑客在谈论他的最近感悟——软件工程事实上是一门社会科学。其中的大约10分钟,他涉及到了人性的各个方面,比如开发人员犯的错误,编程语言设计,静态分析,代码评审,开发人员培训,成本/收益分析。文中强调的部分是我加的(文字为我转录,如有错误,还请海涵)。


在尝试让游戏跑得更快(我们的主要发展方向)的过程中,我们犯了不少《Doom 4》中已有的错误。其中的一些显然无法改变,但迫于提速的愿景,不得不做。

因为这些行不通,你懂的,两个游戏相隔了6年。

在软件开发方面,你们或许知道一件有趣的事,那是一个对我的访谈。那个访谈中我提到我是如何学习到那么多知识的,不过与一年前相比,现在我是个更好的开发者,当时主持人对此很是惊讶。你知道经过了20年,经历了所有的一切,最终一切都浮出水面。关于软件开发我确实学习到很多,既包括个人技能,也包括更多的关注团队积极性的一面。

而后者是过去多年来我所忽视的,因为过去我把自己标榜为一个软件工程的科学家,处理一些抽象、证明、正确相关的事情,真的很酷。

事实上在计算机科学中,真正属于科学的唯一事情就是算法,优化属于工程范畴。但是那些没有一直开发算法的人,大部分的时间都花在编程上。我们中确实有少数程序员在做一些优化以及挑选算法,但是90%以上的程序员在编写程序,只是为了实现一些功能。当我开始审视这一切时,发现工作中的绝大部分没有科学、工程、正确的一面。或许,其中某个程序员会说,他做了很多的巧妙编程工作,实现了一些功能。我们喜欢把自己想象成聪明的工程师,用正确的方式去开发好软件。但随着我观察的越来越多,发现这根本不是事实。

除了那些能够评估的东西,我们用来评估和重现的工具才是科学的精华所在,有了它们,才能评估、重现,做出预测并测试验证。我们会在做优化和算法时涉及,但所有其它事,跟科学无关。程序员之间的交流,甚至于与不同时期的自己交流。我们谈论函数式编程,lambda演算,monads,这些想想就很美,但对你在软件工程方面做的事情没有任何影响,这些仅仅是最佳实践而已,一些总结好的做法,但只是在阻止人们犯某些类型错误的时候它们才有意义。纯函数式的,用最严苛的科学的视角写出来的代码,最后还是会转化为汇编语言,事实上你也可以用诸如BASIC或者其它任何语言来实现。

另一件对我有启发意义的事情是大儿子开始学编程。我确实摇摆过是否应该让一个7岁的孩子学习Haskell,最终决定不要,我不认为自己是一个足够出色Haskell程序员以至于愿意去指导别人。当我思考人们到底如何开始从零学编程时,我豁然开朗,意识到我们从软件工程社区获取了如此之多,事实上是一些构建在一个核心基础之上的由多层组件组成。当你回退到结构化编程,不论while循环,还是for循环,在底层,你如何解释编程,计算机做些什么,最终都会归于流程图(flow chart)。这种条件执行这个,否则执行那个。甚至尝试解释在一个地方到底是应该用for循环还是while循环时,这仅仅关乎惯例,一个解决人们经常犯的错误的惯例。但是它们不是计算机在做些什么的核心。这些只是人们根据以往经验总结的让你少犯错误的方式。

另外一件严峻的事情是,程序员一直在不断的犯错。我去年谈论了很多关于静态分析方面的工作,并尝试静态分析我们的所有代码,希望它们都能够顺利通过,可惜扫出了数以千计的错误。其中一些问题,我的同伴们确实是Bug,并保证下次不会再犯,这种感觉不错。如果一些错误没有从语法上阻止,就可能发生。那正是我投入如此多精力到静态分析上的原因之一。我希望能够开启更多严格的语言限制,因为我们一直在犯错。

最近我开始做的一件事情是code review,查看每日提交的代码,找出一些典型的错误案例,给team成员讲讲。标注出一小段代码,说这是个静态分析工具扫出的Bug,推荐这样的写法,更简单清晰,也不容易犯错,刚开始时有些成员对这种公开文化有些反感,不过我觉得大家现在都接受这样的做法了。但有个问题,我看看大家都做了什么就会花费很多的时间,更别说逐一review。能够指出组内其他成员犯的错,并告诉大家当心类似的问题,那才是真正的价值所在。只要组员都接受这件事,就是有积极意义的。

当你在争论譬如是否应该在函数的参数之前加上const关键字时,想象一下会发生什么。这些很难客观地给出结论,大多数这种情况,我们称之为缓存失效,要耗费很大的精力来做说服工作。如果一个结论很客观,显而易见,就不会起争论。但是其它的很多事情涉及的只是代码风格问题,你或许会说,根据多年的经验,这会导致种种错误,但是很多人会回答说,我从来没有看到那种错误,对于我来说那不是问题,我从来没有犯过那种错。这个时候能够演示错误很重要,看,就是这个问题导致的错误。

随着这些事情我做的越来越多,并思考相关的问题,我觉得这些不是科学,而是解决人类弱点的过程,我希望有更好的办法来处理。我们都希望成为更好的程序员,开发出更好的产品,把工作做得更好,但是事实归结为去训练数十个人按一致的风格做事情。组内成员有进有出,不断更迭,新人进来,看着代码库,不了解惯例规则。有更好,也有更坏的方式做事情,不过这真的很难衡量。

这些是我正在花更多的时间关注的事。我阅读了NASA的软件工程报告,在大量资料中并没有找到任何有价值的东西。真正有价值的是自动化——无需人工参与,自动评估。我觉得那才是越来越大的项目的最终归宿。现在我们开发的一些项目代码量非常惊人。回头看看NASA的报告,他们认为3,4百万行代码是大项目。现在我们的游戏引擎的代码量远远超过那个数字。这样来评估游戏引擎很有意思,它比送人类到月球上并返回,控制宇宙飞船,操作太空舱,维护空间站还要更复杂,所有这些巨型项目都没有市面上的任何主要游戏引擎复杂。

我的答案并非到此为止。使用NASA风格的开发流程,他们能够交付Bug率非常低的产品,但这建立在极低的生产力基础上。你能够做的一件事情是,分析成本/收益比值,你会说自己能开发出很棒的产品,但推出实在是太晚了。或者我们可以做得很快,领先市场,不过可能做得不大精细,不过我们会让一些很酷的功能尽快推出。这是一个典型的例子,合适的工具,处理合适的任务。现实中发生的是,你会很快推出很酷的产品,但会在多年内忍受它的折磨(译者注,猜测是指维护产品)。而在这方面我觉得我们做的不是很好。

我们知道有些代码将会维护十年,我告诉同事这是个好机会——你写的代码,并未与特定的游戏强相关,或许会活跃10年,会有数以百计的程序员阅读、参考、调用它,责任重大。在软件的API设计层有很多问题,找出它们是项艺术,需要技艺精湛的工匠。我希望以后能够有更多关于软件工程艺术的内容与大家分享,我正投入很多精力钻研其中。