引言
设计模式被认为是软件设计的“规范”,但是在互联网快速发展的过程中,也暴露了一些问题。相比过程式代码的简单与易于修改,设计模式常常导致代码复杂,增加理解与修改的成本,我们称之为 “过度设计”。
过度设计是认知提升过程中的必经阶段,用什么语言都一样。一般情况下,我们跟着公司内部的开发规范走,就能在写代码的时候避免大部分坑点,减少代码故障和设计缺陷,但这也无法完全解决过度设计的问题,那么过度设计是怎么产生的,又需要怎么减少呢?
本文将探索以下问题:
- 为什么要做代码设计
- 设计模式是如何衍生出来的
- 过度设计的的常见形式
- 如何避免过度设计
1.为什么要做代码设计
我认为做代码设计的根本原因只有一个:提高代码质量;
而代码质量的提升怎么体现出来呢?两个方面:1、让代码读起来更容易;2、让代码写起来更简便。
1.1 让代码读起来更容易
首先,对于一个程序员来说,我们既要开发自己的项目,也会维护别人交接过来的项目。我们接手的代码风格各式各样,理解成本也各不相同。
不否认一些同学技术能力非常出众,理解能力超群,什么样的代码都能读懂,并能很快的在其基础上进行优化、开发、重构。但对于绝大多数心态平和的同学而言,接手一个 设计凌乱,无标准开发规范 的项目绝对能让其心态崩溃甚至开发时满嘴脏话。我们开发时遵守的“编码规范”、“设计规范”,无一不是在为了让项目更好理解,更好维护。
举几个例子:编码的分层、分模块,对象和接口设计时的单一职责原则、开闭原则、策略模式等,都能让我们的项目更有层级,更好理解其结构,让代码读起来更容易。
1.2 让代码写起来更简单
说到让代码写起来更简单,就不得不说到代码的复用。如果代码不复用,每增加一个需求就只能往上堆代码,写的代码越来越多,代码风格与功能就不会呈收敛态。
那么怎么能使代码更简单呢?
尽量相同的功能不写第二遍,抽对象相同属性走继承、抽相同方法做工具类、抽象类;按模块抽、根据路由条件抽、横着抽、竖着抽,总之代码复用性越高,写的代码就越少。但我们又不能不按条理抽,因为这样会影响代码的可读性也会使依赖变得复杂,这时候设计模式就登场了,先辈们经过多年的代码设计经验总结了一些常用代码的写法,用于解决 不同场景下如何能将代码在有条理的条件下写的更“偷懒” 的问题。(这里只是举例,并不是说这些模式就是全部的,也不代表这些就是最好的)。
创建型模式:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。结构型模式:适配器模式、代理模式、桥接模式、装饰器模式、外观模式、享元模式、组合模式。行为型模式:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
学习这些东西能让我们的代码复用性更高,扩展起来更容易,也会让我们代码看起来高端、大气上档次。
简单介绍几种设计模式
- 工厂模式能让我们更简单地创建基础相同而局部不同的相似对象;
- 适配器模式能让我们在实现接口时更简单地只实现特异性方法;
- 策略模式则能够让我们调用接口时根据不同的条件路由到不同的实现策略上去。
换个角度看到设计模式
让我们换个角度来看上面这些设计模式,如果我们将接口的实现方法当做对象类的属性,
适配器模式是否就成了一个适用于 class 类的工厂模式;工厂模式通过不同的属性参数创建不同的对象;
适配器模式通过不同的场景创建不同的接口实现类,甚至更向上抽象一层,策略模式通过不同的业务条件路由到不同的接口实现时也有些同样的味道。
我想说的是不同的设计好多时候他们的基本思考方式是相通的,它们只是一些问题的常用解决方法模板,并不是各不相同的死定义。
2.设计模式是如何衍生出来的
上面说了那么多因为所以,大家可能看文字看的头痛,这里我先举例来说明下常规代码接口设计的发展形式:
产品说我要做个天体系统,先来个月亮
需求一:要有功能:创建个月亮(新产品,产品功能可能给不明确,方向也不明确,先简单实现其功能)
一月后,再来个地球
需求二:要有功能创建一个月亮,一个地球(有两个星球了,后续可能会有多个,抽象星球父对象,将相同的东西放父类中)
再一月后,要上星球管理系统了
需求 3:星球创建的时候要加过程,创建地球的时候要顺便在星球上造山川、河流,还都要通知到星球管理局(针对不同星球有差异化需求了,考虑到现在星球还只有两种,段时间内也不会增加特别多,结合开发时间,将星球类当做适配器来用,同时使用模板方法模式将特异性方法抽取成公共方法)
再一年后,要丰富星系了
需求 4:一次创建一个星球,要支持太阳系的 8 种行星,并且之后会扩展为无数种星球,除了地球外初始化的数据和月亮一样(由于星球数大量扩张,再用 java 类来定义星球需要大量创建 java 类,而且之后要支持无数种星球,现有模式要被打破了,但现阶段稍微改改还能平滑过度,结合星球拥有的公共属性方法,将其合为一个类,先对地球做特殊处理,等要无限支持的时候,做成数据库配置或者配置中心配置实现)
再一月后:完善产品,支持无限种星球
需求 5:需要支持无限种星球了,可以创建的星球需要由运营去配置,而且创建后需要按照顺序分别执行不同的动作,现定义 3 种动作,将来还可能支持更多种动作,动作 1:初始化初始化物体,具体是什么可以由运营配置;动作 2:通知星球管理局;动作 3:遭到撞击,毁灭;(到处都要支持配置,开放度太高,由于每种星球的差异化需求,之前的星球模型的模板方法明显已经不支持现有的场景了,需要用一种可以通过配置来执行方法的模型,这里 DemoService 类在一张图里截不完了,和场景 4 的完全一样,就先省去吧)
上述过程是一个功能从简单到复杂,然后使用的设计模式也逐渐改变,最终适用于业务发展的一种过程,如果在需求 1 的时候直接就上需求 5 的责任链模式,那么这个就属于设计过度了。
关于设计过度不同同学往往有不同的容忍度,有时候在某种情况下也可能会成为有前瞻性的设计,关于这块有时候还是不好评判的。
3.过度设计的的常见形式
什么叫过度设计?
只要团队里内没有足够能力把控未来三年的架构发展,便只需要把代码重构到恰好满足当下的最佳状态,那么我们在开发的时候总是要考虑一下未来的需求到底会往哪个方向走。你蒙中了,就叫正交分解。你没蒙中,就叫过度设计。
过度设计的场景很多,这里列举下常见的几种情况:
有时相同的功能使用不同的设计模式都可以实现,设计模式的引入常常导致代码复杂,增加理解与修改的成本,我们作为开发者必须要把控理解成本、代码实现复杂度、减少的代码量,代码扩展性之间的平衡,一旦思考不到位、理解不到位,打破了其中的平衡,过度设计就产生了。
而我们在日常学习和开发中,甚至在发或者技术方案评审的时候也不可能开对技术、业务全部理解到位,即使对自己负责的项目理解到位了也没有条件对跨项目协作中的直接或间接依赖项目的来龙去脉完全清楚,这时候当你想搞个“高级”东西的时候,就很难把控会不会搞出过度设计,这也就是我说的过度设计是认知提升过程中的必经阶段。
4.如何避免过度设计
上面说了过度设计的来龙去脉和很难避免性,那么如何能最大限度的避免过度设计呢?
根据我过往经验大概从这几个方面着手:
- 按照公司的编码规范写代码、遵守公司内的约定是很重要的事情,如果公司内部没有自己的编码规范,还可以参考业内认可的编码规范(这些都是公司前辈们总结的最适用于自己公司的经验)
- 熟悉设计模式,对于不熟悉的设计模式宁愿不用也不瞎用,保持代码简单易懂(不做一知半解,不炫技)
- 充分理解需求并有一定前瞻能力,根据业务场景复杂度选择自己熟悉的设计模式(一切抛开场景的设计都是搅屎棍)
- 编码时有点洁癖,习惯抽取和改动不合理的代码,对代码质量有追求(要认真哦,出了 bug 是要背的,没把握的话可以暂时不改,但要打上标记后续一点点改)
- 小步快跑,简单点可能也挺好:不要一开始就想着做出完美的方案,很多时候优秀的方案不是设计出来的,而是逐渐演变出来的,一点点优化已有的设计方案比一开始就设计出一个完美的方案容易得多。
5.总结
上面或许有些东西说的比较主观,有些细节点说的不到位,但我觉得当你真正发现并领悟所有的设计架构都是围绕业务服务和后期维护考虑时,过度设计的问题大体上就解决了。
以上是我对于过度设计的理解,随着我工作年限的增加说不定过两年我会再有其他感悟,到时候再分享给大家。