- P7 把主要精力集中于构建活动,可以大大提高程序员的生产率。
在最近的一个项目中,对于这一点,我是深有体会。我们花了很长的时间做设计,结果接下来的许多工作都在愉快的心情下完成。我觉得 P28 的那个食物链的例子更有说服力,健康的生态环境中,海鸥吃新鲜的鲑鱼,鲑鱼吃新鲜的青鱼,青鱼吃新鲜的水蝽。这是一条健康的食物链。 如果环境被污染了,水蝽在污染的水域游泳,那么海鸥,食物链的最后一环吃下的不仅仅是是不健康的鲑鱼体内的垃圾,还有青鱼,水蝽体内的污染物。软件开发中,架构师吃掉需求,设计师吃掉架构,程序员,软件食物链的最后一环,消化掉设计。如果一开始就被污染了,我们就不要指望程序员快乐了。整个软件都会具有放射性,周身都是缺陷,绝对导致程序员脾气暴躁、营养失调。在我们规模不大的团队里,一个人身兼数职,伤害更大。所以,项目一开始就决定了它能否成功。
- P7 源代码——往往是对软件的唯一精确描述
其实我们不必为没有精确的文档沮丧,不是吗?
- P13 常见的软件隐喻
好的隐喻可以让我们思考更多的问题,并走上正确的道路。我们是在 Writing Code,还是 Growing a System 还是 System Accretion 或是 Building Software ? 做不同软件有不同的方法,不要拘泥。
- P24 避免使用错误的方法制造正确的产品
往往我们在软件开发中会很强调测试。的确、测试是质量的保证。但是测试只保证有质量的代码,却不保证有质量的设计。
- P42 需求的 checklist
其实我们不必去造本宣科的写需求分析书什么的,做需求分析即使是在大脑中,口头交流上完成,也是有这么一个过程。落下文字固然是好的,但并不是重点。关键在于做不做。是否详细定义了系统的全部输入,包括来源、精度、取值范围、出现频率。是否详细定义了系统全部输出,包括目的,精度,取值范围、出现频率,格式?是否定义了机器内存和剩余磁盘空间的最小值?是否详细定义了系统的可维护性,包括适应特定功能的变更、操作环境的变更、与其他软件的接口的变更能力? 书中列的远比我这里列出的多,非常值得一读。定下这些是很重要的,我觉得合理的游戏开发,是有一个相对稳定的策划方案,和一些已经完成完成的美术资源。大部分的变更都留在下一版本去做。策划和美术永远为下一个版本工作,而程序可以根据相对稳定的需求做设计。这样做,即使第一个版本是不可玩的,扔掉,也是能让游戏最终成功。
- P46 数据设计
我曾经很迷惑项目文档到底要写什么?这里列举的一些东西解开了我一些疑惑。如果你选择使用一个顺序访问的列表表示一组数据,就应该说明为什么顺序访问比随机访问更好。(往往随机访问更为高效)在构建期间,这些信息让你能洞察架构师的思想。在维护阶段,这种洞察力是无价之宝。 后面 P58 有个更为有趣的例子:Beth 想做丈夫 Adbul 家祖传的炖肉。Adbul 说,先撒上胡椒和盐,然后去头去尾,最后放在锅里盖上盖子炖就好了。Beth 就问了,“为什么要去头去尾呢?” Abdul 回答说,我不知道,我一直这么做,这要问我妈。他打电话回家一问,母亲也说不知道,她一直这么做,这个问题要问奶奶。母亲就打了个电话给奶奶,奶奶回答说,“我不知道你为什么要去头去尾,我这么做是因为我的锅太小了装不下”:D 架构应该描述所有主要决策的动机。
- P48 国际化和本地化
国际化常常被称为 i18n 是因为 Internationalization 这个单词太长了,I 和n 之间有 18 个字母。 同理,通常本地化简写为 l10n 。这个工作一定要在构架期想好啊,到底我们需不需要 i18n 或者支持 l10n 就够了,到底用 utf16 还是 utf8 还是 ascii 串就可以。是代码中嵌入字串,还是封装到一个类,还是可以放去一个配置文件。我们现在的项目,一开始考虑岔了,结果后来花了很多精力重构。好在发现问题不算晚,否则代价更高。引申的想一想,发现问题我们应该最早的解决,不要怕做出错误的决定,因为更可怕的是,意识到错误后,因为害怕修正错误的代价过大,而一拖再拖。代码的 bugfix 大家都知道应该立刻做,但是设计失误却容易被放过,将就着做下去。
- P51 过度工程
这个问题把握好并不容易。一方面,我们希望系统健壮,如果组成系统的各个部分只在最低限度满足健壮性要求,那么整体通常是达不到要求的。软件健壮性不取决于最薄弱的一环,而是等于所有薄弱环节的乘积。构架应该指出每个部分,程序员为了谨慎而宁可做过度工程,还是做出简单的能工作的东西就够了。有些东西是不应该过分花精力的,这个错误我们也犯过,尤其一些一开始就知道以后很可能要重构的部分,大量的精力花在里面很浪费。
- P62 选择编程语言
我曾经也觉得 C++ 是万能的,这种想法很多 C++ 程序员也有。但是无可否认,每种语言的表达力是不同的。书在这页有一张表,如果 C 的表达能力是 1 的话,C++ 和 Java 就是 2.5 。而 perl 和 python 却有 6 。这就是我们选择游戏逻辑脚本编写的原因之一。另外对语言的熟悉程度是很影响程序员的效率的,所以我们不能独立的看语言本身的表达能力。P63 有个例子,用一群 Fortran 背景的程序员去用 C++ 编写一个新系统,结果他们编写出的是伪装成 C++ 的 Fortran 代码。他们扭曲 C++ 来模拟 Fortran 的不良特性并且忽略了 C++ 丰富的面向对象能力。我们这里有个现成的例子,一个 C++ 程序员用 C++/C 的方式写 Lua ,结果可想而知。到现在我还在叮嘱他,一定要理解,再理解 Lua 。lua 不是 C 。
- P68 Programming into a Language
注意,这里是 into 而不是 in 。书这里用了一个 vb 的例子来说明,恰好我也有个例子。我们现在用 C++ 构建系统,C++ 里有个相当麻烦的东西,就是单件的生存期问题。一个 singleton 到底什么时候创建出来,什么是否析构,相信很多 C++ 程序员在构建大系统的时候都头痛过。据我所知,我们公司别的项目的同事到现在还在头痛这个问题。这次我做了一个约定,禁止任何模块的代码构造静态对象,也就是说,任何在 main 函数前自动的对象构造过程和 main 函数之后的自动析构过程都是不允许的。然后我们有一整套管理单件的方法供使用,这个问题被很好的解决了。我们再也没有为某个单件什么时候构造出来的,或是为什么他提前析构了的问题烦恼过。
- P78 管理复杂度的重要性
我们做软件,就是在和问题的复杂度做斗争。有三个问题需要注意:用复杂的方法解决简单的问题;用简单但错误的方法解决复杂的问题;用不恰当的复杂方法解决复杂的问题。
- P80 high fan-in 和 high fan-out
高内聚,低耦合很容易被重视。但是高扇入低扇出有时候会被忽略。这里是说,我们应该尽量的大量的使用某个低层次上给定的类(high fan-in) 而每个类都应该尽量少使用其他的类(控制在7个之下)。
- P83 子系统间尽量减少联系
书里的例子很贴切。一个通用的规则是,如果 A 系统用到 B ,B 又用到 C ,那么 C 就不要再用到 A 了。系统层的设计图应该是无环图。
- P91 封装不仅仅是有简化过的模型看到复杂的概念,而且同时还不能让你看到复杂概念的任何细节
隐藏信息的重要性毋庸质疑。所以我们现在不仅用 C++ 的 private 隐藏信息。还用接口的方法,不在头文件暴露任何设计细节。另外,任何一个不满足现状的程序员,对自己以前的代码一定不会满意。但是复用老的不满意的代码并非坏事。我们需要做的是,重用的时候,把老的东西隐藏起来。
- P99 预料不同程度的变化
好的设计人员应该对以后可能变化的部分很敏感。这点很有体会,我自己就是这样一步步过来的。做一个决定之前,想想如果他做错了的后果。估计未来改变的成本,以作出合适的设计,非常的重要。系统为了适应每种变化而做过度设计又是不合适的。到底怎样设计,需要良好的感觉。
- P101 耦合的种类
松散耦合是每个系统设计人员所追求的东西。但是其标准往往把握不准。举个简单的例子,不一定恰当。在我最早的设计里,系统把坐标这个东西封装成一个叫做 point ,以后参数传递都传 point * ,而不是 x,y 。这看使很合理。但是,这的确增加了耦合度。因为每个类都需要知道 point 的细节。很多情况下,用简单类型做参数传递反而更合适。(到底传 point * 还是 x,y 依旧要根据实际情况靠量) 参数过多也会导致耦合度的增加,从这个角度看,x,y 是两个参数, point * 是一个参数。关于耦合程度的问题,没有绝对唯一的标准。书里的阐述和总结非常值得一看。
- P103 查阅常用的设计模式
设计模式这个东西,给程序员带来的最大的好处就是增加了交流的便捷。一个人可以思考的深度取决于他用于思考的语言掌握的词汇量。设计模式也可以给程序员带来这种便捷。其实常用的设计模式,即使你没有看过《设计模式》这本书,只要你是一个经验丰富的程序员,这些估计大多考虑过。但是,给设计模式起个名字,却可以加快伙伴间的交流。不过必须警惕一个陷阱,那就是为模式而模式。强迫代码去使用某个模式是很危险的。
- P106 避免失误
失败的经验比成功的经验重要。如今网游开始尤其。我们的游戏能成功,是因为我们在失败后吸取教训,小心谨慎。我们的代码未必质量高很多,但是很多业内同任做出的东西吸取了许多人家成功的经验却失败了,正是因为他们缺少对失败教训的学习。
- P111 自上而下和自下而上的设计方法
很多人都赞同自上而下的设计方法,把问题逐步分解,再分而治之。而我喜欢至下而上的设计方法。先把绝对要做的模块做了,再考虑怎么把他们搭起来。但是我在实际操作的时候往往是,自下而上的做,自上而下的思考。书这里对两种方法的优缺点的总结非常精辟。我特别同意最后的结论,这两种方法并不是互相排斥的——你会受益于二者的相互协作。
- P114 开发人员不把原型代码当作可以抛弃的代码。
这个问题很严重,我多次看到过了。做原型真的是一个好方法,但一定要明白,有些代码写出来就是为了以后扔掉的。
- P118 使用数码相机
设计文档不一定要详细的写成规范的文档,重要的是我们做了设计并保留下来,而不是写出漂亮的文档(去蒙投资?)数码相机似乎是一个好东西,这样可以把草稿纸上的乱涂乱画保留下来。我一直觉得铅笔是设计师最好的伙伴。开会的白板也是,虽然现在已经有电子化的白板,但是,昂贵的东西不是所有开发人员都需要的。
- P131 不要让 ADT 依赖于储存介质
这点早就意识的到,类里面最好不要有 readfromfile , writetofile 这样的方法。但是为了方便,往往又会加上这些方法。最终的结果是,依赖文件带来的不便总是比便利要多。同理,依赖文件名也是不恰当的。可悲的是,有些错误犯一次往往不够。意识到这样做的不好是很容易的,真正杜绝它是另一件事。
- P143 在万不得已时通过 private 继承来实现“has a”的关系
private 继承的主要原因是让外层的类能够访问内层被包含类的 protected 成员。根据我自己的经验,让类有 protected 成员本身就不是一件好事。我刚学 C++ 的时候,很多类都有大量的 protected ,但现在,只剩下了 private 和 public 。虽然偶有 protected ,但是我相信,那些都是可以通过改良设计然后去掉的。我不喜欢教条,如果有教条说,不准用 protected ,我会很反感。而实现上,我的感觉会排斥 protected 的使用。
- P174 对于超过 200 行代码的子程序来说,没有哪项研究发现它更够降低成本和/或降低出错率
这是个谁都懂的道理,但是错误我自己也犯过。早几年,我为大话西游编写了一个图文混排的函数,可以在聊天或是其他屏幕文字中同时显示不同的文字,有不同的颜色,状态,效果,还可以显示动画图标。当时图一时方便,还有一些效率原因,我写了一个几百行的函数。再接下来的几年里,我为这个函数多次头痛过,出过不只三次的 bug 。直到在梦幻西游的项目里,这些模块得到全部重写才舒了一口气。长达千行的子程序我也在一些地方见过,那也是非常可怕的。并非所有程序员可以真正认识到这样做的可怕程度吧。
- P176 使用 C++ 中的 const 关键字来定义输入参数
能够正确充分的使用 const 是合格的 c++ 程序员评判标准之一。
- P176 不要把子程序的参数用做工作变量
不要因为想当然的效率因素使用输入参数做工作变量,编译器会帮你优化的。如果这样做,至少在我们公司内部的 codeview 上是要被严重警告的。
- P210 确认留在代码中的错误消息是友好的
读这段的时候想到同事给我讲的一则逸事:他以前的同事因为敲出脏话被开掉了,因为他的老板(也是一个程序员)在调试代码的时候被某个模块的出错信息骂了一通 :D