糟糕代码的优势是什么?

发布于: 3 小时前
糟糕代码的优势是什么?

作者是谁?

最近, 花了几个小时读了一本阐述游戏编程过程中如何使用设计模式的书籍: <游戏编程模式>.

这本书是开源电子书, 可以免费阅读(中文版地址). 当然也有中文纸质版. (豆瓣地址)

书籍作者叫做: Bob Nystrom

2001年作为软件工程师, 加入EA.

EA是什么公司? 我举它旗下两个工作室, 你就知道它的实力了.

在1998年EA公司收购西木工作室, 该工作室开发了当年火遍全中国的游戏<红警>.

Maxis工作室, 《模拟人生》《模拟城市》的开发团队. 同时开发出了口碑爆棚、IGN打出8.8高分的《孢子》.

现在, 根据他 Github 上的信息, 就职于 Google Dart 语言项目组.

豁然开朗

阅读这本书的过程, 仿佛不经意间获得某个宗师的武林秘籍. 秘籍之中充满了真知灼见, 我潜心修炼. 甚至于过于精彩, 达到了忘我之境, 饭都顾不上吃. 然后一些长久以来的疑惑都被解开了, 筋脉被疏通了, 我感觉功力大涨.

<桃花源记> 里面一段话, 准确地描绘了我的心情:

林尽水源,便得一山。山有小口,髣髴若有光,便舍船,从口入。初极狭,才通人。复行数十步,豁然开朗。

豁然开朗, 没错, 就是豁然开朗的感觉.

序言中我学到了什么?

这本书打开序言就是高潮, 提出一些我之前从未想过的观点, 拔高一点, 是颠覆.

比如其中一个标题: 糟糕代码的优势. 我当时心头一惊, 什么? 一堆的文章, 书籍, 科普作者都在竭尽全力告诉软件工程师要高内聚, 低耦合, 要写灵活的, 可扩展的代码. 糟糕代码也会有优势吗? 把所有代码写在 main 函数里面也有优势?

然后, 读完整个章节, 你定会赞赏作者的真知灼见, 点头称是.

作者其实论证的是: 真实项目开发过程中, 软件工程师面对的是 长期开发的速度,游戏运行的速度和短期开发的速度 三者之间的角力, 应该怎么办? 应该如何做好权衡?

高度优化的代码考虑了游戏的运行速度, 但是它不灵活, 难以改动. 就影响开发速度.

糟糕的高耦合的代码, 能够快速出原型, 照顾了开发速度, 但是难以维护.

高度架构设计的代码, 需要花很多时间去思考, 呵护. 每当你添加了抽象或者扩展支持,你就是在赌以后这里需要灵活性。模块化如果最终无益,那就有害。 毕竟,你得处理更多的代码。照顾了长期开发的速度, 但同时损失了游戏运行速度和短期开发速度, 同时会陷入过度设计的风险.

在序的最后, 作者提了几条建议, 我认为应该成为每一个软件工程师的指导方针:

  • 抽象和解耦让扩展代码更快更容易,但除非确信需要灵活性,否则不要在这上面浪费时间。

  • 在整个开发周期中为性能考虑并做好设计,但是尽可能推迟那些底层的,基于假设的优化,那会锁死代码。

相信我,发布前两个月不是开始思考“游戏运行只有1FPS”这种问题的时候。

  • 快速地探索游戏的设计空间,但不要跑得太快,在身后留下烂摊子。毕竟你总得回来打扫。

  • 如果打算抛弃这段代码,就不要尝试将其写完美。摇滚明星将旅店房间弄得一团糟,因为他们知道明天就走人了。

  • 但最重要的是,如果你想要做出让人享受的东西,那就享受做它的过程

摘录

以下是我关于序言的摘录.

文中出现 我的闲言碎语 的段落是个人的读书笔记.

国际知名的游戏可能也无特别的架构设计

首先, 作者介绍了自己在 2001 年加入 EA 公司的经历. 他看到很多关于实现各种功能和视觉效果的杰出代码, 还知道专家如何榨干 CPU 的最后一个循环并好好利用

但是这些杰出代码依赖的架构通常是事后设计他们太注重功能而忽视了架构。耦合充斥在模块间。 新功能被塞到任何能塞进去的地方。在梦想幻灭的我看来,这和其他程序员没什么不同, 如果他们阅读过《设计模式》,最多也就用用单例.

这段话透漏出了一些信息 : 即使像实况足球这样世界知名的游戏项目, 设计模式最多用到单例. 他们的专家和程序员们关注功能多于架构设计.

而后, 作者解释一下现实与理想的区别, 这个也符合我近几年真实参与公司项目后的感悟:

我曾幻想游戏程序员坐在白板包围的象牙塔里,为架构冷静地讨论上几周。 而实际情况是,我看到的代码是努力应对紧张截止期限的人赶工完成的。 他们已经竭尽全力,而且就像我慢慢意识到的那样,他们全力以赴的结果通常很好。

什么是好的架构设计 解耦的定义

每个程序都有一定架构,哪怕这架构是“将所有东西都塞到main()中看看如何”.

我的闲言碎语 : 别以为将所有代码放在一个文件, 塞进 main 函数的软件都是新手干的事情, 最近我发现一款知名的 roguelike 游戏源码, 10万行左右的代码放在一个文件中. 拆分多个文件, 模块化的目的是易于维护, 但是如果一个游戏代码的维护人员从头到尾就一个人, 这个人清楚知道每一行代码是什么意思, 并且能高效开发维护, 即使一个文件10万行也没问题.

好的设计意味着当我作出改动,整个程序就好像正等着这种改动。 我可以仅调用几个函数就完成任务,而代码库本身无需改动。

这听起来很棒,但实际上不可行。“代码库即使改动不会影响其表面上的和谐。”就好。

架构是关于改动的。 总会有人改动代码。如果没人碰代码,那么它的架构设计就无关紧要.

评价架构设计的好坏就是评价它应对改动有多么轻松。

解耦的两种定义方式:

可以用多种方式定义“解耦”,但我认为如果有两块代码是耦合的, 那就意味着无法只理解其中一个。 如果解耦了它们俩,就可以单独地理解某一块。 这当然很好,因为只有一块与问题相关, 只需将这一块加载到你的大脑中而不需要加载另外一块。

解耦的另一种定义是:当一块代码有改动时,不需要修改另一块代码。 肯定也得修改一些东西,但耦合程度越小,改动会波及的范围就越小。

软件架构的关键目标: 最小化在编写代码前需要了解的信息。

解耦(抽象, 模块化)的代价

每当你添加了抽象或者扩展支持,你就是在赌以后这里需要灵活性。 你向游戏中添加的代码和复杂性是需要时间来开发、调试和维护的。

如果你赌对了,后来使用了这些代码,那么功夫不负有心人。 但预测未来很难,模块化如果最终无益,那就有害。 毕竟,你得处理更多的代码。

当你过分关注这点时,代码库就失控了。 接口和抽象无处不在。插件系统,抽象基类,虚方法,还有各种各样的扩展点,它们遍地都是。

你要消耗无尽的时间回溯所有的脚手架,去找真正做事的代码。...... 理论上,解耦意味着在修改代码之前需要了解更少的代码, 但抽象层本身也会填满大脑。

像这样的代码库会使得人们反对软件架构,特别是设计模式。 人们很容易沉浸在代码中,忽略了目标是要发布游戏。 对可扩展性的过分强调使得无数的开发者花费多年时间制作“引擎”, 却没有搞清楚做引擎是为了什么。

我的闲言碎语 : 所有的软件工程师都在被告知解耦的重要性, 却没人讨论解耦的代价. 作者的观点颠覆了我之前的认知: 解耦增加了代码量和复杂性, 如果解耦, 抽象, 模块化没有增加灵活性, 最终无益, 就是有害.

性能与灵活性发生矛盾了怎么办

但性能与假设相关。实现优化需要基于确定的限制。 敌人永远不会超过256个?好,可以将敌人ID编码为一个字节。 只在这种类型上调用方法吗?好,可以做静态调度或内联。 所有实体都是同一类?太好了,可以使用 连续数组存储它们。

但这并不意味着灵活性不好!它可以让我们快速改进游戏, 开发速度对创造更好的游戏体验来说是很重要的。 没有人能在纸面上构建一个平衡的游戏,哪怕是Will Wright。这需要迭代和实验。

这里没有普适的答案。 要么在损失一点点性能的前提下,让你的程序更加灵活以便更快地做出原型; 要么就优化性能,损失一些灵活性。

就我个人经验而言,让有趣的游戏变得高效比让高效的游戏变有趣简单得多。 一种折中的办法是保持代码灵活直到确定设计,再去除抽象层来提高性能。

我的闲言碎语 : 作者提出的观点十分有借鉴意义. 先保证灵活性和开发速度, 然后再设计基本确定之后, 再去抠性能的细节.

糟糕代码的优势

我的闲言碎语 : 什么? 糟糕代码也有优势? 又一个颠覆我的认知的观点.

编写架构良好的代码需要仔细地思考,这会消耗时间。 在项目的整个周期中保持良好的架构需要花费大量的努力。 你需要像露营者处理营地一样小心处理代码库:总是让它比之前更好些。

如果只想试试游戏的某些点子是否可行, 良好的架构就意味着在屏幕上看到和获取反馈之前要消耗很长时间。 如果最后证明这点子不对,那么删除代码时,那些让代码更优雅的工夫就付诸东流了。

原型——一坨勉强拼凑在一起,只能完成某个点子的简单代码——是个完全合理的编程实践。 虽然当你写一次性代码时,必须保证将来可以扔掉它。

你得让人们清楚,可抛弃的代码即使看上去能工作,也不能被维护,必须重写。 如果有可能要维护这段代码,就得防御性地好好编写它。

我的闲言碎语 : 原型是一堆不能维护, 不能工作, 必须重写的代码. 你必须让 老板或者经理人 事先知道这点. 如果实在无法沟通, 作者提供一个小技巧.

一个小技巧能保证原型代码不会变成真正用的代码:使用和游戏实现不同的编程语言。 这样,在将其实际应用于游戏中之前必须重写。

三种速度的角力和平衡

有些因素在相互角力:

1. 为了在项目的整个生命周期保持其可读性,需要好的架构。

2. 需要更好的运行时性能。

3. 需要让现在想要的特性更快地实现。

有趣的是,这些都是速度:长期开发的速度,游戏运行的速度,和短期开发的速度。

好的架构长期来看提高了生产力, 也意味着每个改动都需要消耗更多努力保持代码整洁。

草就的代码很少是运行时最快的。 相反,提升性能需要很多的开发时间。 一旦完成,它就会污染代码库:高度优化的代码不灵活,很难改动。

但是如果尽可能快地实现特性, 代码库就会充满黑魔法,漏洞和混乱,阻碍未来的产出。

没有简单的答案,只有权衡。...... 这似乎是在恐吓,“没有正确的答案,只有不同的错误。”

但对我而言,这让人兴奋!看看任何人们从事的领域, 你总能发现某些相互抵触的限制。无论如何,如果有简单的答案,每个人都会那么做。 一周就能掌握的领域是很无聊的。

我的闲言碎语 : 快速掌握意味着无聊, 有挑战性和难度让人兴奋.

如果有什么能简化这些限制,那就是简单。 在我现在的代码中,我努力去写最简单,最直接的解决方案。 你读过这种代码后,完全理解了它在做什么,想不到其他完成的方法。

我的目标是正确获得数据结构和算法(大致是这样的先后),然后再从那里开始。 我发现如果能让事物变得简单,最终的代码就更少, 就意味着改动时有更少的代码载入脑海。

最节约心血的方法是为每段用况编写一段代码。...... 但这一点也不优雅,那种风格的代码遇到一点点没想到的输入就会崩溃。 当我们想象优雅的代码时,想的是通用的那一个: 只需要很少的逻辑就可以覆盖整个用况。

找到这样的方法有点像模式识别或者解决谜题。 需要努力去识别散乱的用例下隐藏的规律。 完成时你会感觉好得不能再好。

我的闲言碎语 : 作者为了阐述简单的含义引用了两句名言, 很有启发意义.

Blaise Pascal有句著名的信件结尾,“我没时间写得更短。”

Antoine de Saint-Exupery:“臻于完美之时,不是加无可加,而是减无可减。”

发布于: 3 小时前 阅读数: 15
用户头像

lmymirror

关注

I am what i am. 2019.10.12 加入

我是一个程序员. 微信公众号 : shaohuogun2019

评论

发布
暂无评论
糟糕代码的优势是什么?