代码之美——Doom3源代码赏析1
http://www.csdn.net/article/2013-01-17/2813778-the-beauty-of-doom3-source-code/1
摘要:Dyad作者、资深C++工程师Shawn McGrathz在空闲时翻看了Doom3的源代码,发出了这样的惊叹:“这是我见过的最整洁、最优美的代码!”“Doom 3的源代码让我对那些优秀的程序员刮目相看。”因此有了本文。
背景介绍:
Doom3是id Software于2004年开发的第一人称射击游戏,目前以GPL v3协议开源。其采用游戏引擎的是id Tech 4,由id Software创始人、首席程序员John Carmack领导开发。
再做个简单的对比:作者刚刚完成的Dyad有193k行纯C++代码,Doom3是601k(2004),Quake3是229k(1999),Quake2是136k(1997)。
以下是CSDN译文,做了部分删减:
关于代码,什么才能被称为“好看”——或者说“优美”?在和几个程序员朋友讨论后,我得出了结论:
- 代码应该局部连贯而且功能单一:一个函数解决一个问题。而且应该很清晰。
- 局部代码应该能够解释,至少暗示整体的系统设计。
- 代码应该“自文档”,尽可能地避免注释。因为无论是在读还是写代码时,注释都是一项冗余工作。如果你需要添加注释才能帮别人理解,那么那段代码可能需要重写。
这里是idTech4引擎的编码标准,绝对值得一读。
统一的语法与词法分析
我在Doom源代码中所见最聪明之处在于其词法分析器和解释器。所有的资源文件都是语法统一的ASCII文件:脚本、动画文件、配置文件,等等,所有东西都遵循相同的规则。因此一大块代码就可以阅读并处理所有的文件。这个解析器非常健壮,支持一个C++的主要子集。通过一个统一的词法分析、解释器,引擎所有组件都不必担心序列化数据的问题,因为已经准备好了相应的代码,这保证其它地方的代码更加整洁。
参数严格和const化
Doom的代码非常严格,尽管在我看来,const方面还不够严格。可能很多程序员都没注意到const的多种种作用。我的看法是“任何东西只要可以都应该设定为const”,我希望C++中所有的变量都默认是const。Doom参数几乎完全遵守“no in-out”规则,这意味着所有函数都参数都不能既是输入参数也是输出参数。这样,在当你向函数传入参数时,更容易理解他身上发生了什么。比如:
从这几个const中我就看出来:
- 这个函数不会修改作为参数传入的idPlane。我无需坚持idPlane是否被修改就可以安全地使用它。
- 函数中的epsilon也不会被修改。
- front, back, frontOnPlaneEdges and backOnPlaceEdges是输出变量,是值的写入目标。
- 参数列表后面的const是我最赞赏的地方。它表明idSurface::Split()不会去修改surface。这是我最喜欢的C++独有功能,因为我可以这样使用:
- void f(const idSurface &s) {
- s.Split(....);
- }
如果Split没有被定义为 Split(...) const,这段代码将无法编译。无论被谁所调用,f()都不会去修改外表,即使f()将surface传递给另一个函数,或者调用一些Surface::method()。const能够透露出很多关于函数甚至整个系统设计的信息,仅仅通过阅读这里的函数声明,我就明白了surface可以被plane动态地split()。这个函数不会修改surface,而是返回新的surface、front、back数据,可选地返回frontOnPlaneEdges和backOnPlaneEdges。
const规则,以及无input/output参数对我来说也许是最重要的原则,也是区分好的代码跟优美代码的关键,它能简化整个系统的理解、编辑和重构。
最少注释原则
这是一个“格式问题”,但Doom基本不会过度注释,这很漂亮!我经常会看到这样的代码:
这太让人恼火了,我通过名字就可以知道它的作用!如果这个函数名不能体现出其功能,毫无疑问应该重新命名;如果名字描述得过多,那么去简化它。除非实在不能通过重构、重命名内描述它唯一的功能,那么注释才是合理的。我本以为程序员在学校已经学会注释的重要性,但实际上没有。注释很有必要,但它经常没必要。Doom在这方面做得非常合格,以idSurface::Split()为例,我们看看它是如何注释的:
- // splits the surface into a front and back surface, the surface itself stays unchanged
- // frontOnPlaneEdges and backOnPlaneEdges optionally store the indexes to the edges that lay on the split plane
- // returns a SIDE_?
第一行有点多余,从函数定义中我们已经能明白所有的信息了;但第二、第三行很有价值,虽然我们已经可以推断出第二行的属性,但注释消除了歧义。
Doom的代码加上合理的注释,阅读非常方便。也许很多人把它归为格式问题,但我认为,格式也有正确与否。如果有人修改了函数,并且删除了最后的const;这样surface可以直接被函数修改,于是注释与代码不再同步;这样注释反过来会导致误解,导致代码更加难以阅读。
纵向空间
Doom从不浪费纵向空间。我们以t_stencilShadow::R_ChopWinding()为例:
整个算法只占了我1/4个屏幕,剩下的3/4可以用来观看其周围的相关代码块。实际上,我经常看到这样的代码:
这可以归为格式问题,我有10年编程经历都是像后者那样,大概在6年前才强行转换为紧凑风格的。
两者的代码行数比是11:18,同样的代码后者行数几乎是前者的两倍,所以可能导致看不到后面的代码块,就像这样:
如果没有前面的for循环,仅仅上面这段代码毫无意义,如果id没有纵向紧凑的风格,代码可能更难阅读、更难写、更难维护、也就远离了优美代码的定义。
另外一个我认同的格式是:id永远尽可能地使用{},没有括号会很糟糕,比如我看过这段代码:
这非常丑陋,甚至比把{}放在同一行还要糟糕,我在id的代码中从未发现省略{}的情况。省略{}会导致while代码块解析的时间大幅增加,而且编辑起来也非常痛苦:如果我希望往else if(c > d)分支中再插入一个if分支怎么办?
代码之美——Doom3源代码赏析1的更多相关文章
- 代码之美——Doom3源代码赏析2
http://www.csdn.net/article/2013-01-17/2813778-the-beauty-of-doom3-source-code/2 摘要:Dyad作者.资深C++工程师S ...
- java代码之美(14)---Java8 函数式接口
Java8 函数式接口 之前写了有关JDK8的Lambda表达式:java代码之美(1)---Java8 Lambda 函数式接口可以理解就是为Lambda服务的,它们组合在一起可以让你的代码看去更加 ...
- java代码之美(15)---Java8 Function、Consumer、Supplier
Java8 Function.Consumer.Supplier 有关JDK8新特性之前写了三篇博客: 1.java代码之美(1)---Java8 Lambda 2.java代码之美(2)---Jav ...
- 【OpenCV入门教程之二】OPENCV3 开源之美 — 编译源代码、配置opencv_contrib
为什么要配置opencv_contrib? opencv3.0版本 功能更加模块块,一些功能模块不够完善,等足够完善在merge到主分支中,而我们图像识别中要用到的SIFT等算法被封装在xfeactu ...
- java代码之美(11)---java代码的优化
java代码的优化 随着自己做开发时间的增长,越来越理解雷布斯说的: 敲代码要像写诗一样美.也能理解有一次面试官问我你对代码有洁癖吗? 一段好的代码会让人看就像诗一样,也像一个干净房间会让人看去很舒服 ...
- java代码之美(12)---CollectionUtils工具类
java代码之美(12)---CollectionUtils工具类 这篇讲的CollectionUtils工具类是在apache下的, 而不是springframework下的CollectionUt ...
- java代码之美(10)---Java8 Map中的computeIfAbsent方法
Map中的computeIfAbsent方法 Map接口的实现类如HashMap,ConcurrentHashMap,HashTable等继承了此方法,通过此方法可以在特定需求下,让你的代码更加简洁. ...
- java代码之美(9)---guava之Lists、Maps
guava之Lists.Maps 谷歌提供了guava包里面有很多的工具类,Lists和Maps集合工具,集合操作做了些优化提升. 1.概述 1.静态工厂方法 (1)Guava提供了能够推断范型的静态 ...
- java代码之美(8)---guava字符串工具
guava字符串工具 在java开发过程中对字符串的处理是非常频繁的,google的guava工具对字符串的一些处理进行优化,使我们开发过程中让自己的代码看去更加美观,清爽. 一.Joiner 根据给 ...
随机推荐
- 在Sql Server 2005中将主子表关系的XML文档转换成主子表“Join”形式的表
本文转载:http://www.cnblogs.com/Ricky81317/archive/2010/01/06/1640434.html 最近这段时间在Sql Server 2005下做了很多根据 ...
- android中选择控件与选择界面自然过度效果的实现--一种新的交互设计
转载请标明出处: http://blog.csdn.net/jianghejie123/article/details/40648931 在安卓中经常遇到须要选择一个东西的功能,比方选择日期.选择文件 ...
- hdu 2457 DNA repair
AC自动机+DP.按着自动机跑,(其实是生成新的满足题目要求的串,然后找改变最少的.)但是不能跑到是单词的地方,如果跑到单词的话那么说明改变后的串含有病毒了,不满足题意.然后就是应该怎么跑的问题了,现 ...
- [RxJS] Filtering operators: takeLast, last
Operators take(), skip(), and first() all refer to values emitted in the beginning of an Observable ...
- 重复记录(duplicate records)相关运营数据
MySQL 中查找反复数据,删除反复数据 创建表和測试数据 /* 表结构 */ DROPTABLEIFEXISTS `t1`; CREATETABLEIFNOTEXISTS `t1`( `id` IN ...
- iOS--inputView和inputAccessoryView
iOS–inputView和inputAccessoryView 什么是inputView和inputAccessoryView? 如果是UITextField和UITextView,下面是声明文件源 ...
- PHP中的循环while、do...while、for、foreach四种循环。
php中的while循环,循环执行代码块制定的次数,或者当指定的条件为真时循环执行代码块. 在我们编写代码是时候,我们经常需要一块代码块重复执行多次.我们就可以使用while循环语句来完成这个任务. ...
- HTML5 <Audio/>标签Api整理(二)
1.实例2: 相对较完整 Html代码: <style> #volumeSlider .slider-selection { background:#bababa; } </styl ...
- The windows PowerShell snap-in 'Microsoft.Crm.PowerShell' is not installed on this computer
加载PowerShell插件时出现以下错误: The windows PowerShell snap-in 'Microsoft.Crm.PowerShell' is not installed on ...
- ORACLE 中ROWNUM用法总结!(转)
对于 Oracle 的 rownum 问题,很多资料都说不支持>,>=,=,between...and,只能用以上符号(<.<=.!=),并非说用>,>=,=,be ...