引言:为什么要思考这个问题?
从编程角度,项目推进最大的问题就是代码随着更新变得越来越臃肿,难以维护。但是理想的情况下,一个项目应该随着开发的推进变得越来越好维护,功能越多,实现新需求应该越快,这其中的差异在哪里?我认为这是一个大问题。
抽象
抽象几乎是一个从入门开始就会接触到的词,但其中的内涵很多人一直也无法理解,也很难有一个统一的共识。我理解的抽象是对业务的解构,并重新建构。这一点该如何解释呢,比如一个背包系统,他需要存放一些物品,这是策划的需求。如果与现实直接对应,那么在写这块业务时很自然的会抽象出一个“容器”,一个“物品”,背包系统做的事就是将“物品“放在“容器”里,并且能“取”出来,能“看”得到里面有什么,事实上如果实现过这类系统,你做的就是这样的事情,但你很可能没有意识到,业务代码很直接的与现实逻辑进行了对应,所以你没意识到你将背包功能解构为了装放物品的容器,并且基于此实现了“看”和“取”的功能。
但是对于一个复杂系统呢?比如一个战斗功能,策划可能需要你实现一个“攻击”功能,那么需要怎么实现呢?如果没有更宏观的抽象,可能会这样想:攻击,无非就是一方发起攻击一方受到伤害而已,所以我实现一个战斗单位,他有受击方法,他有攻击方法,他有血量,这个功能就实现了,这里的思路是与之前制作背包时是相同的思路。但是继续的,策划可能要求攻击可能附带一个持续扣血效果,当然,你可以顺着与现实的对应关系去进行最基本的,你可能都没意识到的抽象行为,但是最终代码会变得越来越臃肿。但是如果我们在那之前,我们将攻击理解成一个瞬时的伤害行为,那么持续的扣血就是一个持续的伤害行为,这样两者之间的差异就只剩下触发时机,也就是你抽象了伤害行为这一概念,重新建构了攻击和持续扣血这两个动作。当然这里只是简单举例,只是需要论证解构与建构的意义是什么。
所以,抽象的核心是忽略次要细节,只聚焦本质,提取共性,建立通用的逻辑。我认为这是抽象的内涵。所以开发需要有意识的抽象,而不是无意识的对应现实。换句话就是不要用现实逻辑去写代码,而是用抽象逻辑写代码。这是对抽象的第一层理解,那么再往下呢?
如何抽象
之前已经提到了,抽象本质上就是解构与建构,但是这就像是一个潘多拉魔盒一样,对于具体业务有茫茫多的角度,选择哪种方式才能最大程度的提取共性呢?这时候,大家会提到一个概念,程序员需要理解业务,理解了业务不就可以找到最合适的角度了吗?但很明显,这是一种理想情况,事实上,没有人能真正理解业务,我认为这是一条理论可行,但现实无法走通的方向。意思是,你对业务的理解确确实实能体现在能将业务更好的拆分上,并且对业务的理解越深这个工作也做的越好,但是这需要大量的时间,并且永远没有那一天,能保证我总是对业务做出最合适的抽象与设计,也就是说,这只是一个“增幅”的手段,而不是解题的工具,抽象对于开发来说是一个高频操作,理解业务确实有很大帮助,但是不能依赖抽象解决所有问题。 这是我对抽象的第二层理解。
如何推进一个项目
基于这些理解,我应该怎么去推进一个项目呢?我觉得我应该承认业务是复杂的,我无法完全理解业务,这意味着我的抽象不可能完全正确,他在某个时刻会变成累赘,或者变成鸡肋。所以之前的文章提到不要过早的优化代码,不要有完美主义。这意味着我应该更加务实的推进业务。
务实具体体现在
- 只进行最基本的抽象,并且随时准备重构它
- 实现业务时,保持在现有抽象的基础下快速实现。
这是一个很有趣的角度,因为大部分的“屎山”就是在只追求快速实现的情况下堆出来的,而差异我认为在是否能足够理解项目当前的抽象,并基于此快速实现,并且随时准备推翻旧的抽象。问题在于这样的项目是建立在不完整的抽象的基础上开发的,这意味着他对参与项目的人员有着更高的要求,并且需要投入更多的时间去理解抽象,而在抽象不够用时,能进行权衡是修改还是推翻,还是继续顺着写。随着业务规模的增长,对于项目的抽象也越来越完整,架构也逐渐清晰,这样的节奏我认为项目是会变得越来越好维护的。
但这其中最关键的问题在于,软件开发是一个多人参与的工作,意味着大家水平参差不齐,也不能保证每个人对于项目中已有的抽象能完全理解,更不要说可能还存在完全不具备抽象思维的人存在。
所以实际的开发中,更常见的模式是对于开发已经有了一套完整的开发框架,开发人员遵循这套框架进行开发,所有开发都局限在业务层,并且遵循一定的开发规范,这样能保证开发人员能快速上手。而这个完美的可用的框架从哪来呢,笑。
而更更常见的模式是,大家都在一个不完整的框架下开发,项目自然就逐渐的堆成一座屎山。
而这一切,最现实的解法只能是用更靠谱的人,而不能期望用设计解决一切问题。
其他问题
抽象的时效性与重构的时机
前面有提到,抽象是会过时的,当他出现时不应该反思为什么当初没有那样做,而是应该权衡应该做出调整,那么什么时候应该重构呢?
这需要一个标准,我认为这个标准应该是:
- 认知升级时:比如最开始我理解角色之间是在执行一个个动作,而随着业务的加深,我的理解变成了角色是在推送一个个的伤害事件,那么这时候就应该重构
- 代码臃肿时:
- 在实现需求时,如果业务需要绕着抽象写,那么就应该重构。比如早期实现技能时使用了伤害事件+触发节点的逻辑,但是后来一个技能可能带有多个触发节点,需要将一个技能拆解成多个技能触发,这时候可以考虑重构
- 如果出现需要复制粘贴代码的情况时,应该考虑重构。
- 性能瓶颈时:这实际上是业务需求的部分,但是也可以囊括进来,因为对于性能的优化在某种情况下是极具破坏性的。他体现在两方面,一方面是意料之外,另一方面是刚性需求。所以对性能的考量是一个基础的内容。
多人协作
这一块的主要问题是如何达成共识,同步认知
- 可视化图标能帮助其他人快速理解
- 代码规范,直观的函数名,变量名,注释,文档
- 代码审查。
具体措施还有很多,可以参考相关书籍,比如《代码整洁之道》《人月神话》。
关于完美的框架
即使有一个很完美的,抽象很完整的框架,我也应该认为他是可拓展的,而非封闭僵硬的工具,前提是在修改时已经对框架中的现有的框架足够理解。真正可靠的框架都是迭代出来的,而不是一开始就完美的。这一点可以理解成框架的生长性。
团队管理
没干过呢,到时候在想吧 ^-^。应该就是围绕建立共识,互相帮助,互相检查展开吧。
代码质量
我始终认为编程中经验是最重要的,经验具体体现在什么样的抽象是“最基本的抽象”,比如当项目越早期,就越要求尽可能的理解业务,尽可能的预测,而这样的预测,有可能有效,有可能局限,当然,我上面提到的做法是不断的推翻调整,但真实的业务环境里,并不可能给你无限多的时间,你只能在有限的时间内做出最合适的抽象,并且要求这个抽象尽量不被推翻,这需要经验。
结语
这一篇应该是我对编程的理解的全部了,之前有记录过《不要过早维护代码》《编程时实际是在做什么》,实际上都能在这里得到体现。说到底,抽象也是一门玄学,他要求你快速实现业务时要考虑维护成本,他要求你在维护代码时要考虑时间成本。他要求你找到“快速交付”与“长期维护”间的动态平衡点。说到底,软件有软件自己的生命周期,根据他的生命周期来确定开发节奏,因为抽象的本质是复用,“快速交付”与“长期维护”的预期就是体现在对于复用规模的预期。事实上,项目的生命周期也是无法有一个准确答案的,虽然这个时间点可以大致准确的得到,但仍然需要带着这样的预期。