文/安博林在《The art of unix programming》中,复杂度的控制非常重要。一句话提到,一个编程项目的核心是对复杂度的控制。而简单的原理其实就是在讲这件事。我自己在2008年写过关于这个主题的文章:复杂性和习惯。七年过去了,经历了《天涯明月刀》这样的重工项目,也有了更多的感悟。复杂度的关键点复杂度的关键点是程序给大脑带来的负担,相当于程序员升级开发程序的难度。随着模块的复杂性,这种负担大致呈二次级数增长。如果负担很低,那么一个程序就很容易控制,程序员也很容易提高程序的质量(包括开发效率、运行稳定性、运行效率)。所以我们不需要在任何时候任何情况下都把复杂度降到最低。如果一个模块本身体积很小,那么我们就不需要花很多精力去进一步精简(当然,本着自强不息、精益求精的本能,在时间允许的情况下,当然是好的todothis)同时,低复杂度不等于代码行数最少,而是给大脑带来负担最少的代码,比如后面给出的代码示例,虽然另一种写法有代码行数较多,但由于符合更稳定的模式,所以精神和精神上的负担较小,可以认为是不太复杂的代码。复杂度控制的实际意义和价值首先从实际的角度来看:它关系到运行效率和开发效率(当然其他的可扩展性等等也会包括在内,但是在项目中的实际感受是这些两个特别明显)。其实7年前我是这么想的,但实践起来不是一回事。发展的原则真正形成是在几年前。开发效率最深刻的原则就是地形系统的开发,包括编辑器的底层(UI部分是另一个同事做的)和runtime部分,从材质到高度图。该系统庞大而复杂。在开发过程中,难免会遇到需求的变化(包括材质系统的能力,地图的大小,都是非常具有颠覆性的)。时间紧,任务重,总想尽快把事情做好。开发过程中,我没有做太多的代码组织和整体系统控制,其他组可以同步进行,再进行代码组织。但是对于一个大系统来说,这个策略并不好。在写程序的时候,最好的质量和效率就是对整个系统在代码层面始终保持一个非常清晰的状态。写什么自己心里有数,整体代码在写的过程中也是清晰合理的。心中的模样得到了印证,然后就可以静下心来写得很快,整个过程很享受。而如果在实施过程中,缺乏对制度的理解和组织,希望“随便做,做完了再整理”,小的时候还可以,但大的时候一个大系统,即使思路还很清晰,庞大的系统缺乏组织性,让它变得非常复杂。很多东西都是因为前后设计不一致导致的复杂到不合理的地步——需要死记硬背。这样做的结果是,即使你对整个系统的设计非常清晰,但在编程的过程中,由于系统的一定混乱,你无法在整个过程中非常清晰、冷静地进行。这很累人。所以在下半场,我停下来,改变了策略。我先是做了一个完整的组织,把不需要的部分去掉,然后再组织代码,直到完全可以为新代码的实现做好准备,才去做新的实现。它是最快的,而且写起来愉快而快速。运行效率和处理效率,基本套路是根据游戏情况剖析热点和关闭功能。但是这个能做的很有限。如果你想进一步提升性能,逼近性能的极限,你必须做的是:对各个模块有充分的了解,能够快速反复尝试迭代处理性能热点。早期优化是一种非常有效的方法。准确地说,热点处理是一种“在水分存在的情况下有效提高性能”的方法。但是在追求极致性能的过程中,热点优化还不够,某个模块的性能消耗是否超过了它应该有的,排名前10之外的模块是否不需要高频运行等。这些都是热点处理不了的事情。在对程序有了充分的了解后,可以进行更彻底的调整,将大量操作并行化,进行低频执行,或者直接优化。在实践中,这样的处理将使程序的性能提升到一个新的水平。这个道理可以说是说起来容易做起来难,难点在于如何完全理解和实现一个大系统(比如《天涯明月刀》,就是整个客户端,覆盖几十万行代码).易于彻底修改和优化。所以关键点又回到了复杂性上。只有完全控制了程序的复杂度,才能更好的完成这项工作。在实践中,优化过程中大约一半的时间花在了代码调整和重构上。合理的代码会让优化变得更加可行和高效。复杂度控制的方法与实践经过实践,复杂度控制的能力我认为可以从三个方面拆解:欲望、目标和时间积累。愿望:首先,最有效的方法是承担覆盖范围非常大的实际开发任务。在这种情况下,你会对复杂性有敏锐的理解,你会真正理解什么是复杂性,它是什么。什么是重要的,什么让你疯狂,什么是虚张声势,微不足道,欲望很足,那么后面的积累和修炼就会容易很多。目标:方法和做法有很多,但目标要简单得多,就是能够在代码层面让整个系统保持非常清晰。在进行设计和决策时,能够一帆风顺就足够了。所以在一定程度上可以说,复杂度的控制还是比较主观的,而且还要看火候。比如有时候项目比较小,即使复杂度控制的不是很好,但是很清晰,hold住,然后就可以把更多的精力放在其他方面。方法:在个人实践中,可以注意这几个方面:-任务切分+代码组织:当较小的任务结束后,开始做小规模的代码组织,始终保持代码干净-模式+性质:多积累patterns,比如一大片代码,里面其实做了pool的事情,那么这片大片的复杂度就是一个字:pool。让一切都变得更自然,符合良好的编程习惯,这样你需要记住和注意的东西就很少了,那么它就是一个很低的复杂度。例如下面的代码:inta[5];for(inti=0;i<5;i++){printf("%d",a[5]);}这在实际程序中不是一个好的做法,在看到这段代码的时候,你应该本能地注意到如果a[5]的大小改变了怎么办,就会有for访问越界的可能。#defineARRAY_NUM(a)(sizeof(a)/sizeof(a[0]))inta[5];for(inti=0;i 再看到这样的代码,心里踏实多了,一路pass过去,那这也算是complexity相对较低(需要注意或刻意记住的事情很少)。所以,保持总结积累是非常重要的。对于越来越多的编程模式或者算法的积累,那么在开发和思考的时候,就可以在更高的维度上去做,所以对于压缩复杂度,提高思考速度和质量是非常重要的。而且,在这个级别上,尝试回归基础是一种更强大的编程风格。“复杂性控制的敌人”没有意识到“复杂性”的重要性很多程序员(甚至大部分)对复杂性漠不关心,把一些算法和效率因素远远放在复杂性之上,甚至以写出非常复杂的程序为荣。在这方面交流并不容易。只有当你实际承担了大量的程序实现,对复杂性有了深刻的理解,你才能真正的理解。还有就是缺乏及时和项目组沟通,争取有足够的时间来处理复杂性问题和清理代码。相当多的程序员对复杂性没有充分的理解,要求项目经理有足够的理解似乎是不合理的。基本上比较可行的方法是程序员给予足够的沟通,在执行时间预估上留有足够的余量,而如果没有意识,沟通不足,甚至为了取悦管理者而忽略复杂性,那是抓狂的情况追求实现时间太糟糕了。进度问题和时间紧任务重的情况前面已经提到过,但是在实际项目中还是会反复出现。这实际上可以是一个很大的话题。首先,每个A程序员都需要建立一个代码实现的profile机制——我个人一直在使用worklog,然后对自己的开发效率有一个轨迹,从而知道哪种方法是正确的,更快。什么情况下磨刀才不会误切作为材料工,只有看了轮廓才知道。根据具体情况采取具体策略。根据个人经验,边组织边实施会比较快。快速、稳定地掌握编程的基本功。这需要长期有意识的积累。goodfortheprogrammer'ssoul低级编程对程序员的灵魂有好处。”——JohnCarmack非常赞同卡申的话。做底层代码实现,对硬件和系统有透彻的了解,对程序员来说是至关重要的清楚地了解整个程序是如何工作的,你会更好地用低级的思维去思考。同样的原理也可以用于高层的复杂度控制,更优秀的编程实践,更好地理解做什么,明白系统本身,最终实现最简洁的实现,整个设计和实现过程可以让人进入一种静止的状态,同样“有益于程序员的灵魂”
