导语:大家应该在工作中都有这样的体验吧!代码函数超长需要整改,发现整改后的代码不忍直视,不熟悉业务的童鞋根本不知道拆分出来的函数是用来干什么;需求变更需要改动一块逻辑发现该逻辑散落在各个模块最后辛苦改完后发现遗留一处导致现网故障;函数入参个数超长以及隐式改变入参值且又有出参 等等。在多个人接收一个模块且在快速的需求迭代的背景下以上问题出现的频率会更高。如何来解决这类问题呢?制定代码规范、CR阶段拦截 都不能很好的解决类似问题出现只能减少出现的概率,根因还是在于开发者本身。
一、设计原则
SOLID 原则
SOLID 是面向对象设计(OOD)的头五大基本原则的首字母缩写。
-
S:单一职责原则(Single Responsibility Principle,SRP): 一个类只负责一项功能或一类相似的功能
-
O:开放封闭原则(Open Close Principle,OCP):在一个软件产品的生命周期内,不可避免的会有一些业务和需求变化。我们在设计代码的时候应该尽可能地考虑这些变化,在增加一个功能时,应当尽可能地不去改动已有的代码;当修改一个模块时不应该影响到其他的模块。
-
L:里氏替换原则(Liskov Substitution Principle,LSP):只要父类能出现的地方子类就能出现(就可以用子类来替换他),反之,子类能出现的地方父类就不一定能出现(子类拥有父类的所有属性和行为,但子类拓展了更多的功能)。
-
I:接口隔离原则(Interface Segregation Principle,ISP):建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。也就是说,我们要为各个类建立专用的接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。
-
D:依赖倒置原则( Dependence Inversion Principle,DIP):把具有相同特征或相似功能的类,抽象成接口或抽象类,让具体的实现类继承这个抽象类(或实现对应的接口)。抽象类(接口)负责定义统一的方法,实现类负责实现具体功能的实现。
上述设计原则大家应该不会陌生,工作中可能也会用到但可能不会去留意和关注它们。本文不会着重去介绍它们(ps:网上资料很多,我也是copy的~)。我们应该关注的是下面几个实用设计原则,理解它们可能会让我们少走几年弯路。
LOD原则(Law of Demeter)
LOD原则(Law of Demeter)又称迪米特法则,简述的话:一个类对自己依赖的类知道的越少越好,只需要和直接的对象进行交互,而不用在乎这个对象的内部组成结构。 如类 A 中有类 B 的对象;类 B 中有类 C 的对象,调用方有一个类 A 的对象 a,这时要访问 C 对象的属性,不要采用类似下面的写法:
a.getB().getC().getProperties()
而应该是:
a.getCProperties()
至于 getCProperties 怎么实现是类 A 要负责的事情,我只和我直接的对象 a 进行交互,不访问我不了解的对象。
KISS原则(Keep It Simple and Stupid)
KISS原则(Keep It Simple and Stupid):要求程序能简单、快速的开发实现且设计简单到傻瓜都能理解用于应对组内成员开发水平良莠不齐的状况。设想一下组内代码水平低的人去理解水平高的人封装的代码可能会因为遗忘某一些特性导致开发理解成本变高进而导致需求交付延期和需求bug等问题。
DRY 原则(Don’t Repeat Yourself)
DRY 原则(Don’t Repeat Yourself):不要重复你的代码,即多次遇到同样的问题,应该抽象出一个共同的解决方法,不要重复开发同样的功能,也就要尽可能地提高代码的复用率。
YAGNI 原则(You Aren’t Gonna Need It)
YAGNI 原则(You Aren’t Gonna Need It): 避免过度去设计,只考虑和设计必须的功能。
Rule Of Three 原则
Rule Of Three 原则:事不过三的原则要求我们在程序中相似逻辑出现三次就应该去抽象重构,这里就可以避免相似改动改动多处
CQS 原则(Command-Query Separation)
查询: 当一个方法返回一个值来回应一个问题的时候,它就具有查询的性质;
命令: 当一个方法要改变对象的状态的时候,它就具有命令的性质; CQS原则要求我们接口设计时尽量功能单一,如果查询和命令两者混用就会造成清晰度降低,具体视业务情况而定。
二、设计模式
注:这里不会详细介绍每一种设计模式(ps:半路出家,文采有限,理解也不深刻);关于侧重点这栏属于个人理解,如有不同可以补充
主要分为三大类:创建型、结构型和行为型。
推荐学习资源:设计模式目录
创建型模式
目的 :提供创建对象的机制, 能够提升已有代码的灵活性和可复用性。说人话:约束我们如何初始化对象 侧重点 :单例模式、函数式选项模式、对象池
结构型模式
目的:如何将对象和类组装成较大的结构, 并同时保持结构的灵活和高效。说人话:如何使用不兼容类的能力 侧重点:感觉都很重要
行为型模式
目的:负责对象间的高效沟通和职责委派。 侧重点:责任链、策略、观察者、模板方法
三、代码之丑
列举了开发中遇到的一些问题以及解决方法;注以下仅代表作者个人观点,以下case是不全的,在实际开发中会遇到各种各样的问题,这里也欢迎大家补充一起学习,谢谢~
case1: 函数超长
产生原因:
- 平铺直述的方式写代码
- 需求堆砌,不断在原有方法中添加新功能,一点一点增长
- 以性能为由
解决方案: 重构 (完全在于个人对业务流程的理解)
注意事项: 根据函数的业务逻辑进行合理拆分,切不可随意截取中间代码来封装函数用于避免函数过长。尽量将函数写的越短越好;也注意函数参数问题。
case2: 相似代码逻辑分散到模块各处,没有统一
产生原因:
- 业务不熟悉:需求迭代开发中相似模块经过多人开发
- 怕影响已有模块功能:相似逻辑已识别怕改动影响已有模块而copy一份重新开发
- 需求截止时间影响
解决方案: 业务模块owner在技术评审阶段和CR阶段识别出来;业务开发owner尽量在本次需求中进行逻辑抽取封装;如果受需求截止时间影响应建技术单进行跟进(ps:可能也没时间处理但有存单)
注意事项: 测试用例的覆盖
case3: 函数入参过多
产生原因:
- 需求迭代中一点一点在开发中新增
- 需求中功能缺失快速补充开发
- 代码习惯问题
解决方案: 将长参数封装成对象
注意事项: 不要封装一个大而全的对象,可根据实际业务情况以及作用域来拆分多个参数对象
case4: 控制语句问题
现象:
if 条件1{
}else if 条件2{
} else {
}
探讨 关于else,我建议不要写满而是去掉else语句,使代码逻辑更加清晰,如下图;
if 条件1{
}
if 条件2{
}
异议: 关于性能问题,我承认会牺牲一点因为每个判断条件都会走但它能提高代码逻辑的清晰度我认为可以不需要else