前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >设计原则 \ 单一职责,让你的代码变的清爽

设计原则 \ 单一职责,让你的代码变的清爽

原创
作者头像
霍格沃兹测试开发
修改2021-03-17 17:51:29
1.2K0
修改2021-03-17 17:51:29
举报
文章被收录于专栏:测吧测试开发测吧测试开发

原文链接:https://ceshiren.com/t/topic/10481

单一职责

在这里插入图片描述
在这里插入图片描述

单一职责是设计原则 SOLD 中的 S ,英文是 Single Responsibility Principle。从名字上看,单一职责字面意思是任务专一,举个例子,如果一位后端程序员只开发后端,就可以说这个人职责单一,但后端程序员既做前端,又开发后端,还要维护服务器,那程序员的职责就不够单一。

单一职责表示类或者模块的责任应该单一。这里的类和模块就像大圆和小圆,类是小圆,代表细粒度的代码,比如函数,类,甚至是变量名等等。职责不够单一表现是:

  • 函数设计的大而全:数据读取函数既包括路径解析,又包括目录查找和数据检测。
  • 类内的方法多而杂:动物类包括猫行走方法,猫奔跑方法,狗行走方法...。
  • 变量名或函数名含义模糊:变量名或函数名全取 util , commmon 等通用名字。

模块是大圆,代表粗粒度的代码,比如一个文件(包含多个类)。职责不够单一表现是:

  • 资源模块包含与资源无关的类:比如 resource 模块中包含日志提取类。
    在这里插入图片描述
    在这里插入图片描述

可以看出,不论大圆还是小圆,都在强调一件事:不要设计大而全(粗糙),要设计功能单一(精细)代码,只要设计的不够专一,就可以说违反了单一职责原则。但现实开发中,单一职责很难有清晰的判定标准,比如猫类,类内有关于猫的各种方法:小跑,快跑,吃鱼干,吃猫粮,喝白开水,喝纯净水,站立,躺着,睡觉。如果把与猫相关的动作都加进来,这个类显得有些臃肿。你可以思考一下如何修改,才能让猫类职责更单一:

在这里插入图片描述
在这里插入图片描述

比较合理的方法是拆分。可以把猫类拆分成三个小类,分而治之,分别是猫-动作,猫-吃喝和猫-静止。拆分后的三个类功能更单一,但这样拆分是否符合所有需求?

在这里插入图片描述
在这里插入图片描述

很遗憾,拆分后的类不能满足所有需求。宠物医院只关心猫的健康状态,他们会把猫类按照健康和非健康进行拆分。相对而言,经常运动是健康的,比如小跑,快跑,而吃猫粮,喝纯净水也是健康的。那宠物医院对单一职责的理解就像下图:

注意:以上关于猫的观点纯属虚构,没有任何参考价值!

在这里插入图片描述
在这里插入图片描述

从上面得出结论:需求不同,对单一职责的理解也不同。单一职责就这么抽象?有没有一个放之四海而皆准的方法,来帮我们判断单一职责?很遗憾,并没有,这就是设计原则易懂难实施的原因。同一个方法,在 A 项目适用,在 B 项目就是过渡优化。下面介绍自己遇到的问题和解决方案,你可以作为参考来看,但是不要盲目模仿。

在编写测试框架时,需要加载指定目录下的 yaml 文件。为了让大家看懂,省略了代码细节(代码不可运行):

代码语言:txt
复制
def load(path):
    # 使用 yaml 读取函数,读取流中的 yaml 数据
    data = yaml.load(path)
    # 返回数据
    return data


# 打印加载后的内容
load("tmp.yaml")

这段代码很简单,只需要用 yaml 库加载 path 路径传进来的 yaml 即可。随着调用次数增加,发现每次都要传路径,现加入新功能:自动遍历目录,找出符合要求的 yaml 文件

代码语言:txt
复制
def load(path, yaml_name="tmp.yaml"):

    if os.path.isdir(path):
        list = os.listdir(path)  # 列出文件夹下所有的目录与文件
        for i in range(0, len(list)):
            # 递归遍历目录文件,找出和 yaml_name 相同的 yaml 文件      
    else:
        # 使用 yaml 读取函数,读取流中的 yaml 数据
        data = yaml.load(path)
    # 返回数据
    return data


# 打印加载后的内容,下列两种方式写法等价
load("c:/a/b/tmp/tmp.yaml")
load("c:/a", "tmp.yaml")

这段代码加入了目录遍历功能,通过 os.listdir 找出所有目录和文件,再进行递归遍历,直到遇到和 yaml_name 匹配的 yaml 文件为止。由于手动传入 path 还是不方便,现要求加入新的遍历方式:如果设置了环境变量 BASE_DIR ,那么 load 函数中的 path 参数就作废,按照环境变量 BASE_DIR 目录开始遍历,寻找 yaml_name 匹配的 yaml 文件

代码语言:txt
复制
def load(path=None, yaml_name=""):
    if path is None:
        # 从环境变量中找目录
        path = os.getenv("BASE_DIR", default = "")
    if os.path.isdir(path):
        list = os.listdir(path)  # 列出文件夹下所有的目录与文件
        for i in range(0, len(list)):
            # 递归遍历目录文件,找出和 yaml_name 相同的 yaml 文件      
    else:
        # 使用 yaml 读取函数,读取流中的 yaml 数据
        data = yaml.load(path)
    # 返回数据
    return data


# 打印加载后的内容,下列两种方式写法等价
load("c:/a/b/tmp/tmp.yaml")
load("c:/a", "tmp.yaml")
load(yaml_name="tmp.yaml")

上述代码加入了环境变量获取,目前还算简单。随着需求增加, load 函数会越来越复杂,未来可能支持正则匹配,目录黑名单,限制查找子目录的层级...。 load 函数会变得大而全,违反了单一职责,可以对 load 进行拆分:

代码语言:txt
复制
def load_by_data(yaml_name):
    # 使用 yaml 读取函数,读取流中的 yaml 数据
    data = yaml.load(path)
    return data


def load_by_dir(path=None, yaml_name=""):
    if not os.path.isdir(path):
        raise ValueError()
    list = os.listdir(path)  # 列出文件夹下所有的目录与文件
    data = None
    for i in range(0, len(list)):
        # 递归遍历目录文件,找出和 yaml_name 相同的 yaml 文件
        data = load_by_data(xxx)   
    return data

def load_by_env(yaml_name):
    # 从环境变量中找目录
    path = os.getenv("BASE_DIR")
    # 复用 load_by_dir 
    return load_by_dir(path, yaml_name)


def load(path=None, yaml_name=""):
    if path is None:
        return load_by_env(yaml_name, default = "")
    elif os.path.isdir(path):
        return load_by_dir(path, yaml_name)
    else:
        return load_by_data(yaml_name)


# 打印加载后的内容,下列两种方式写法等价
load("c:/a/b/tmp/tmp.yaml")
load("c:/a", "tmp.yaml")
load(yaml_name="tmp.yaml")

load 函数被拆分成多个小函数,这些小函数功能更单一:

  • load_by_data:直接查找
  • load_by_dir:从目录中查找
  • oad_by_env:从环境变量中查找
  • oad:组合上述功能

上述拆分只是一个例子,并不是最优解。单一职责有多种理解方式,最核心的还是程序编写者如何衡量,编写代码要多思考职责设计是否合理,拆分或重构后会不会增加复杂度,从而得不偿失。比如上述例子,如果 load 函数需求很简单,完全没有必要进行拆分。

本文章不是银弹,如果你在编写代码时,能够主动思考职责问题,那我的目的就达到了。衡量单一职责需要对业务足够了解,只有不断思考,不满足现状以及持续重构才能找到自己的尺子。

总结

  • 单一职责表示类或者模块的责任应该单一。要注意类和模块就像大圆和小圆,类是小圆,代表细粒度的代码,比如函数,类,甚至是变量命;模块是大圆,代表粗粒度的代码,比如一个文件(包含多个类)。
  • 不同需求有不同的拆分方式。没有一个放之四海而皆准的方法帮我们判断单一职责,同一个方法,在 A 项目适用,在 B 项目就是过渡优化。
  • 衡量单一职责需要对业务足够了解,只有不断思考,不满足现状以及持续重构才能找到自己的尺子。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 单一职责
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档