【软件开发】代码设计指南
本文最后更新于 2026年6月21日 凌晨
【软件开发】代码设计指南
写代码最费时间的是查 API,最难的则是设计代码结构。如今,查 API 已经可以被 AI 替代,但代码设计始终需要手工操作。这非常难,难到大部分人类都无法实现这一点。
检查代码设计是否有误
是否存在数量不定的条件判断
检查代码是否需要对实现进行抽象
如果你的代码实现里用到很多if、switch之类的判断,且你无法确定他们的最大数量,可以预见性的,他们未来还会继续增加。那你不应该使用条件分支实现(违反开闭原则),而是改用委托、抽象类等(依赖倒转原则),由外部调用者实现原本的条件分支代码。
1 | |
是否存在结果恒定的条件判断
检查代码是否存在多余的实现耦合
一段代码被分支,但部分场景下又完全不执行,说明该实现在某些功能中需要,但在其他的一些功能中则完全多余。这意味着代码没有实现单一职责、接口隔离等原则,存在功能耦合。你应该将其进一步拆分,确保功能中的每段代码都是必要代码。
1 | |
是否存在复制粘贴的编码方式
检查代码是否需要对功能封装复用
如果你写代码时出现了成段的复制粘贴行为,那说明你的代码需要优化。你应该将你复制的代码进行封装,考虑改用可以共用的函数实现,这样后期修改才不会出现“一次调整,到处要改”的情况。
1 | |
是否无法轻易迁移单个功能
检查多个功能间的依赖关系是否正常
当有需求让你把项目中某一小功能快速迁移到其他项目,你却无法做到或得扯上一堆毫不相干的其他功能,那你的代码就存在依赖问题。最佳的功能依赖应该是单向树状的,且有明显的层级隔离(最少知道原则的体现),一个枝杈不可能影响到另一个枝杈。所以你应该确保你的项目内任意单一功能都能随时迁移。
1 | |
正确的设计思路:

学习良好的代码设计理念
基本原则:通过“高内聚低耦合”实现“模块化”
很多人把自己的功能称为模块,但事实上他们并没有实现模块化。“模块化”在《软件工程》中有专门的定义,仅逻辑上的功能独立并不能说明你的代码是模块化的,只有做到真正的随拆随删,才能叫模块化。
无论多复杂的编码技巧,本质都是为了实现“模块化”,实现“高内聚低耦合”。
指导思想:设计模式
市面上有很多类似的技巧和规范教我们如何高效管理项目,例如《设计模式》就是其中一种:
开发框架:MVP与DDD
从整体关系上设计软件
MVP框架(局部视角)
如果你的代码实现了模块化,那必然就会自动使用上 MVP 框架,因为 MVP 就代表了三个不同的模块:
- M(模型):核心功能,类似于 API 的存在。
- V(视图):用户界面,与 M 分离,用于向用户呈现特定的数据交互形式。
- P(主持人):驱动游戏逻辑,将各个模块(M、C)桥接,激活。
而且 Unity 天生对 MVP 有非常好的适配性,因为其自带编辑器界面(V)和游戏生命周期(P),使得可以轻松分离出功能核心部分(M)实现快速分开发。
当然,为了偷懒,有时会对 MVP 简化,例如改为 MV-P、M-PV。本质都是对 V 的退化实现(不过这种不利于自动化测试,因为V只能人工测试),但 M 和 P 一定是分离的,因为一个应用说到底就是由一堆功能模块和驱动模块构成的。
补充:
MVP 目前进一步发展到 MVVM,其中 VM (视图模型)是借助数据绑定技术,对原本 V 和 P 的进一步解耦,也是现在主流的前端框架。目前 Unity 的新 UI 系统 UIElements 已实现对绑定技术的支持,但该 UI 系统本身还无法替代 UGUI,UGUI 只能实现 V 到 P 的单向数据流,所以任需要 P 去控制 V。
DDD框架(宏观视角)
MVP 解决的是单个模块或少数模块间的设计方法,但从整个项目几十上百的模块视角来看,则需要利用 DDD 框架(领域驱动设计)。DDD 框架的思想简单来讲就是分层与隔离,相比传统分层它还进一步将业务实现分为“应用层”和“领域层”:
- Interface(交互层):实现 UI 功能,负责实际功能的输入输出部分。
- Application(应用层):根据软件最终的实际需要,对业务功能的组合使用。
- Domain(领域层):基于业务需求,针对特定领域的一套通用功能实现。
- Infrastructure(基础层):实现基础功能,例如后台 API、存储,等跨项目的通用功能服务。
所有功能模块被细分为多层,依赖上仅允许上层调用下层,且不允许跨层调用。这种分层思想在很多大型软件设施中都有体现,例如 OSI参考模型、来自GAMES104的现代游戏引擎架构分层。
当一个软件庞大到一定程度,没有人能记住它的所有功能实现,再加上人员变动等,很快项目就会变成黑盒代码,而分层使得功能细节可以被隔离。从协作角度看,人员分配调度会变的更加方便;从个人角度看,上手门槛降低,实现时不用在意旧功能的细节,开发新功能压力小,旧代码也更安全。
实现细节:函数化
从具体实现上设计软件
我认为世界上的任何现象,都可以用函数进行归纳,这和人们试图用数学抽象现实的想法是一致的。同理,模块的实现也可以参考函数的实现。函数的组成非常简单,只有“输入”、“输出”、“实现”三个部分组成。
单模块实现(实现函数)
先不要关心其他模块,只关心当前模块本身的功能,其函数三核心在代码规范中的区域分布中就能体现出来。而且由于 Unity 本身的特性,一个 MonoBehaviour 自身就可以完成一套完整的 MVP 架构,因此它确实是一个独立且功能完整的存在,确实能被称为是一个模块。
1 | |
多模块依赖(调用函数)
函数化的思想适用于每个模块,包括模块间的依赖调用和控制调用。整个模块使用就如同函数一样,填入参数,执行,取出参数,再将参数填入另一个函数,执行,直到输出到用户界面。
1 | |
优势分析
- 函数化的思考方式非常简单,对外只用考虑本身功能的输入输出,对外细节完全封装,自动形成层级隔离。
- 实现完全基于 MonoBehaviour 的原生设计,没有任何学习成本,理解运用非常轻松。
- 主动暴露输入条件于面板,使得用户可以借助编辑器自由调节参数,并且前置依赖一眼便知。
- 支持基于 Unity 事件的自动激活,在前期快速开发和单元测试场景中非常方便。
- 模块内代码分区明了,输出放顶,细节放尾,实现了代码文档化,查阅起来非常方便。
依赖注入
https://github.com/modesttree/Zenject
https://docs.unity3d.com/Packages/com.unity.dt.app-ui@2.0/manual/mvvm-di.html
没有框架是最好的框架
就在刚刚的实现细节中,其实我们已经实现了一系列 MVC、模块化、封装分层的思想。这是很难的操作吗?在我看来,用 Unity 面板编辑参数,用 Unity 事件执行功能,这明明就是 Unity 最基本的用法。
实际上,在写该文章前,我不知道什么是 MVP(我一直以为我用的是MVC),也不知道什么是 DDD,设计方法我也是懒得看的那种。直到现在,我才了解到,原来这些对我来说很自然的代码实现,原来是有名称的。
这种后知后觉的现象很正常,因为不是框架让你的代码变好,而是你为了让代码变好,自然的写出了框架。实际上写代码只要遵循以下原则,那写出来的代码一定是最优代码:
- 不要没事找事:写代码永远只把注意力放在当下的事,不要妄加揣测尚未提及的需求,也不要试图定制一个完美的框架。(如果你经验不足,你的小巧思,通常都会成为废物甚至拖慢项目的枷锁,不如只把当前的事做好)
- 不要瞻前顾后:写一个模块就只关注这个模块要做的事,不要同时考虑多个模块,那样写出来的全是耦合代码。模块在之后可以扩展,可以按需提供更多服务,但它本身一定是独立的,绝不是为了特定于为某物而写的。
- 遵循设计原则:检查你的代码是否符合设计模式中的原则要求,以及上文的代码问题检查,如果违反,那一定是代码存在问题,必须要重新设计你的实现。如果你不知道如何修改,那么你就可以进一步参考设计模式中的设计方法。
- 及时修改代码:没有人是先知,无论是开发者还是项目经理,需求总是会变的,代码结构总是会改的,所以要以不变应万变。代码调整很正常,但只要做好了隔离,再大的火都只会烧在一个模块内,然后根据需求随时调整你的代码,确保每次迭代都始终遵守原则,不要偷懒,否则它将成为屎山的开始。
很多时候开发和项目经理会存在需求上的矛盾,但对我来说项目经理的需求我一向都会接受,我甚至会感觉他们思考太多。对他们来说可能是为了开发好,但当他们主动试图去理解开发的工作内容,来决定成品的效果时,我只感觉到了本末倒置,感觉到了单一职责原则的违背。
我从不怕提需求,也从来没有过大改过代码结构,在我看来适用需求本身就是软件开发的一项职责。而这一强大适应性的背后,我却从来不写什么 Demo 版,也从来不设计什么全局框架,我唯一做的事,就是“接到需求,实现功能、调整复用”,以此往复。
“没有框架是最好的框架”:我的项目没有框架,又或者说到处都是框架,每天都在变化。以不变应万变,这才是一个真正优秀的软件框架。