一、怎样将软件架构思想应用到代码中?
- 上篇说到,好的软件架构,是要花费最小的人力,实现软件的构建和维护。
- 那对应到代码中是什么样呢?
个人理解,就是需求变动的时候,需要改动的代码最少。
- 但是需求就是在不断的变,要改动的代码怎么能少呢?
需求的变化不是我们工程师能决定的。但是修改的代码量却是可以减少的。
使用合理的方法应对变化,是核心。
- 对于个人而言,如何就能算花费的能量最少了?
看《意志力》一书讲了,人在不断做决策的过程中,会消耗大量能量。
这也是我们工程师消耗能量最多的地方。工程师就是要不断分析当前条件,给出当前情况下我认为最正确的方法。不断决策。
因此,我们需要想办法设计,让我们在维护的过程中尽可能的少做决策。
二、关于软件设计的个人看法
- 计算机编程的本质是降低复杂度,没有万能药。
是我看《unix编程艺术》一书中看到的。
我认同,我认为工程师就是要不断分析当前条件,给出当前情况下我认为最正确的方法。
如果通过套用一下什么公式,就设计出完美的软件,那真是太无聊了。
- 架构的设计者需要是一线程序员。
因为一线的程序员,才能时刻体会到不良的设计带来的问题。
- 软件的设计需要从实际案例出发。
因为没有万能药,很难空想什么高大上的模式来解决问题。要从实际情况出发。
- 软件设计是要分离变化和不变的部分。
下面的案例将说明这个问题。
饮血剑是否被购买,饮血剑的参数,饮血剑的被动效果。都是可能被调整的。但是只要把这些可能变化的部分分离出去,代码就更可靠。
三、实际案例
- 介绍一下背景。
英雄联盟中,买了饮血剑之后,打别人是可以将造成的伤害,转换成自己的HP增加的。
如果让你来实现一下饮血剑的吸血功能,如何实现? - 考虑两种方案。
- 第一种:
A 购买饮血剑之后,每次攻击,就判断自己是否有饮血剑。
消息传递给B(告诉B我有饮血剑)(B被攻击)。
B收到伤害之后,判断A给自己的消息,是否有饮血剑,if (withEatFloodSword == 1) ,然后根据自身受到的伤害给A回血。
- 第二种:
A购买饮血剑之后,设置一下攻击到别人的时候的回调函数。比如,onAttackOther 回调。
消息发送给B,(B被攻击)
B受到伤害之后,尝试调用onAttackOther回调,如果回调非空,就调用。否则就不调用。
- 然后评估一下两种方案。
- 采用第一种方案,饮血剑的作用范围。
从购买到被攻击,都需要A有饮血剑这个信息的参与。
- 采用第二种方案,饮血剑的作用范围。
只有购买成功的时候作用一下。
卖掉饮血剑的时候,可能把这个回调置空就可以。
- 如果有bug.发现吸血吸不上来。
- 首先,使用第二种方案出现bug的可能性就低,因为只要购买的时候给函数指针赋值了,饮血剑回调功能是对的,基本就是OK的。
- 其次,第二种方案有bug,基本也就是集中在饮血剑 这个类或者说 模块中。不会影响其它。
- 但是第一种方案实现,你需要跟踪整个流程去排查。而且,别的什么修改也可能在流程中干扰饮血剑的工作。工作量就非常大。
- 第一种方案,你需要做大量的决策来解决问题。第二种方案要少的多。
- 扩展。
- 事实上,就算是别的什么功能的装备也可以使用这个机制。比如,你买破败+饮血+九头蛇,那就把对应的回调加到链表中一个个执行就可以。
- 如果使用第一种方案,那你要整个流程都加一遍。工作量就非常大。
- 第一种方案,你需要大量的决策去调试新功能。第二种就不是。基本一个饮血剑好使,其它的就也好使了。
- 总结
1.购买的装备是什么,装备的属性,装备的被动,是可能被调整,可能变化的。但是使用这种方法,可以在需求变化的时候,我们少改一些代码。
2.使用第二种方法,饮血剑和整个攻击流程的节点,没有耦合。
3.实际的设计中,就是面对这些形形色色的问题,不断地思考方法,让要修改的部分,尽可能的少。
4.我们不能消灭变化,但是可以把变化的部分尽可能放到一小部分代码中。让不变的代码,永远不要变。每次只修改变化的小部分代码,我们的工作量,就会降低。
5.如果真的和上一条所说的这样,架构就是好的架构。
6.这就是 《unix编程艺术》中说的,分离原则,策略与机制分离,接口和引擎分离。机制往往是不变的。策略是随时变化的。我们要做的就是每次维护只改变策略来满足需求。
7.尽可能设计让维护的工程师做更少决策的代码。
四、参考文献
- 《unix编程艺术》
- 《架构整洁之道》
- 《意志力》