为什么遵循原则
如果我没写过这部分代码,那么我会直接看前面的人怎么写的,然后仿照前面的人的格式,接着写代码。这也许就是破窗效应
- 如果前面的人写得好,那我也能写得好
- 如果前面的人写💩,那我就是在堆💩了
破窗效应
以一幢有少许破窗的建筑为例,如果那些窗没修理好,可能将会有破坏者破坏更多的窗户。最终他们甚至会闯入建筑内,如果发现无人居住,也许就在那里占领、定居或者纵火。又或想像一条人行道有些许纸屑,如果无人清理,不久后就会有更多垃圾,最终人们会视为理所当然地将垃圾顺手丢弃在地上。
破窗效应本是犯罪心理学的范畴,但也可以用于其它方面,比如代码设计。糟糕的代码设计如果不被及时改正,后面的人处理类似问题时可能会依然采取糟糕的设计,最终影响整体的代码质量和设计。
- “别人也是这么写的”
- “没办法处理,只能先苟着了”
说实话,我也先砸过窗户,也跟着前人一起砸窗户。这里的经验告诉我,不要贸然将一笔质量不合规的代码合入到主分支当中去,否则很快就有人开始跟着堆💩,毕竟堆💩可比写遵循原则的代码简单多了。
SOLID原则
SOLID包含单一职责、开闭原则、里氏替换、迪米特法则、接口隔离以及依赖反转,这些原则指导开发进行代码设计与重构,从而使得代码清晰可读以及可扩展。
单一职责原则 Single Responsibility Principle, SRP
定义:类、接口或者方法应该具备单一的职责,保持功能简单。职责是业务逻辑上进行划分的。\
例如使用MVC或者MVVM,Model层负责数据的读写、View层负责根据数据显示UI并且响应用户的操作,C/VM层是Model和View之间的桥梁,负责处理业务逻辑的处理。
其实这个准则没得说,只有函数的职责比较清晰,函数和类等的命名才比较简单,后面也会比较好维护。举个简单的例子
func dealForOperation(num1: Int, num2: Int, op: Operation) -> Int {
if op == .add {
return num1 + num2
} else if op == .sub {
return num1- num2
} else {
return 0
}
}
当然目前分支很简单,其实没有拆分的必要。
但是后续有同事会不断的在三个if分支下堆代码,最终会把三个函数堆的巨大无比。(当然会有遵循原则的同事会选择把这些代码根据用途拆成不同的函数,但是不要寄希望于此,你自己都不愿意处理,别人怎么可能会处理,最好可以把他们在实现最初就拆开)
不要觉得拆开会到后续会影响代码的性能,大部分编译器都会有内联的功能,并不会让你的代码跳转的次数太过频繁。
开闭原则 Open Closed Principle, OCP
定义:类、模块、方法应该对扩展开放,对修改关闭。
里氏替换原则 Liskov Substitution Principle, LSP
定义:
如果对于一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在左右对象o1都带换成o2时,程序P的行为没有发生变化,那么类型S是类型T的子类型。
所有引用基类的地方必须能透明地使用其子类的对象.
我们提倡多组合少继承,但是当不得不继承的时候,里氏替换原则是使用继承的时候必须要遵守的一个原则。
- 子类不应当改变父类的行为
- 程序中使用到父类对象的地方,替换成任意的子类对象,依然能正常工作
迪米特法则 Law of Demeter/The Least Knowledge Principle
定义:最少知识原则,一个对象应该对其他对象有最少的了解。
核心观念是降低系统间耦合,提高健壮性。
接口隔离原则 Interface Segregation Principle, ISP
定义:
- 一个类不应该依赖它不需要的接口
- 类间的依赖关系应该建立在最小的接口上
可以理解,感觉这个准则可以理解为最小依赖原则,类不要依赖一些不需要用的接口,也就是说一些冗余的接口、实现要尽早删掉,否则后续维护的同学就懵逼了,不知道有什么用,但是又不敢删除。
依赖反转原则 Dependence Inversion Principle, DIP
定义:
- 高层模块不应该依赖低层模块,两者都应该依赖其抽象
- 抽象不应该依赖细节
- 细节应该依赖抽象
核心是面向接口编程,本质是通过接口或者抽象类使各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合,这个原则其实还是实现开闭原则的重要途径。
依赖反转并不是说低层会反过来依赖高层,而是高层从依赖低层的实现变为依赖低层的抽象。在小项目中可能难以体现出来优点,对于中大型项目特别有用
可以看到 Expample 是依赖 Member 的,而且依赖了具体实现。(小项目中,这样实现很清晰,上手也会更加容易)
class Expample {
var member: Member
}
class Member {
func function() { }
}
大项目中最好是要依赖接口,可以这样实现:
class Expample {
var member: Member
}
protocol Member {
func function() { }
}
class Implement:Member {
func function() { }
}
这样后续如果实现修改了,对于业务调用方不会有任何影响。而且依赖于接口,可以解耦合 业务方 & 调用方