这两章主要站在软件设计的角度来分析如何写出好的代码。写出好的代码比写出代码更加重要,因为,代码必然会遇到后期的维护、扩展的问题,好的代码易于维护和扩展。怎么样区分什么是好的代码什么是差的代码呢?那就要看代码是否符合接下来要介绍的一般性的软件设计原则。
原则方面的是建议,不是一定要这么做,但是这么做了带来的好处多于坏处为什么不这么做呢?
SOLID
S: Single Responsibility Principle 单一职责原则
O: Open/Closed Principle 打开/关闭原则
L: Liskov's Substitution Principle 里氏替换原则
I: Interface Segregation Principle 接口隔离原则
D:Dependency Inversion Principle 依赖倒置原则
这里介绍书中提到的几个代码设计的原则。
一、契约式设计(Design by Contract, DbC)
1、前置条件。
在代码运行之前所要做的检查,比如验证数据库中的集合、文件、之前调用的另一个方法等等。这对调用方施加了约束。
比如我们在写api接口的时候,一般都会对输入进来的参数做校验,不符合预期的会报错或者给出提示。
2、后置条件
比如我们在写接口的时候,会对返回数据做统一的处理,都会带上msg,data 之类的,如果data里面的数据为空,那么就在msg里面给出“内容为空”这样的提示,一方面保证每个字段都有,另一方面保证字段内容符合客户端的预期。
3、不变量
4、副作用
我这里打的比方是针对前后端分离的接口开发,其实针对项目内部里面的函数之间的调用也是一样的需要遵守类似的契约设计模式。
二、防错性程序设计
1、异常抛出
try except 这类用法
2、默认值
保证在程序调用方未按照预期赋值的时候程序能够正确运行。
在读取配置文件,有的情况会给默认值,比如数据库配置,一般会给"localhost" "3306" 这样通用的默认值。
三、高内聚、低耦合
高内聚,一个命令只做一件事,而且做到很好
低耦合,多个对象项目依赖最小。
保证没有重复的代码,同样逻辑的代码写了多次,虽然可能有一些细节不一样,但是依然需要我们继续把核心的功能抽出来,一直抽到不能再抽为止,这样就保证了高内聚。
避免一个地方的代码修改,影响另外一个地方。其实我感觉这是一种思维训练,从具体的实现过程中抽离出来,往更高层次的设计看齐,重新组织原先实现功能的代码,把一段很长的代码拆分成若干个独立的最小功能单位的代码,然后通过相互调用来实现原来的功能。
这也算是单一职责原则,这样的代码就有更好的维护性。
四、避免过度设计(YAGNI You Ain't Gonna Need It)
编写处理当前的需求代码,而不是过度考虑未来的需求。
这种错误很多初级程序员都会犯,与产品经理沟通需求的时候,总是想着未来的需求会怎样怎样,我要怎样设计代码来兼容未来的需求,最后可能当前的需求都没有理解清楚。
软件开发应该是一个不断进化的过程,谁也不能一开始就把所有的需求想明白,过度设计的结果是让自己一旦开始动手就犯迷糊,左也不是右也不是,我这样设计了,他以后会不会增加一个什么功能,他这个功能要是加进来了,我现在这个函数改怎么写 ......
五、保持简单(Keep It Simple KIS)
实现正确解决问题的最小功能,并且不要使解决方案过于复杂。设计越简单,就越容易维护
六、 单一职责原则
类越小越好,分配责任给不同的类
七 、打开/关闭原则
当领域问题上出现新内容时,通过添加新的代码而不是修改原有代码。利用类的多态性实现
class Event:
def __init__(self, raw_data):
self.raw_data = raw_data
@staticmethod
def meets_condition(event_data: dict):
return False
class UnkonwnEvent(Event):
pass
class LoginEvent(Event):
@staticmethod
def meets_condition(event_data: dict):
if event_data['id'] == 1:
print("login event")
class LogoutEvent(Event):
@staticmethod
def meets_condition(event_data: dict):
if event_data['id'] == 2:
print("logout event")
class SystemMonitor:
def __init__(self, event_data):
self.event_data = event_data
def identify_event(self):
for event_cls in Event.__subclasses__(): # 遍历子类
try:
event = event_cls.meets_condition(self.event_data)
except KeyError:
continue
return UnkonwnEvent(self.event_data)
l1 = SystemMonitor({'id': 1})
l1.identify_event()
再增加一个Event,然后实现meets_condition 方法,对原先的代码没有任何侵入。
八、里氏替换原则
对象类型必须有一系列属性,才能保证其设计的可靠性
九、接口隔离原则
十、依赖倒置原则
细节(具体实现)应该依赖于抽象