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


    在这里插入图片描述

    更多技术文章分享及测试资料点此获取

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

    单一职责

    在这里插入图片描述

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

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

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

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

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

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

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

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

    注意:以上关于猫的观点纯属虚构,没有任何参考价值!
    在这里插入图片描述

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

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

    
    def load(path):
        # 使用 yaml 读取函数,读取流中的 yaml 数据
        data = yaml.load(path)
        # 返回数据
        return data
    
    
    # 打印加载后的内容
    load("tmp.yaml")
    
    

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

    
    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 文件

    
    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 进行拆分:

    
    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 项目就是过渡优化。

    • 衡量单一职责需要对业务足够了解,只有不断思考,不满足现状以及持续重构才能找到自己的尺子。

    更多技术文章分享及测试资料点此获取

  • 相关阅读:
    【36氪收录】观「招商银行」隐私计算布局的思考
    如何用cmake编译
    docker | Ubuntu16.04安装与卸载docker
    通过删除注册表重新获得软件试用期
    k8s | 重启Kubernetes Pod的几种方式
    2021年终总结(一)
    凡是过往、皆为序章20210917PPT分享
    多厂商容器平台开发系统性总结
    VS2022安装.NetFramework4.0目标包的方法
    C# Winform窗体继承过程中,TableLayoutPanel是不支持继承的
  • 原文地址:https://www.cnblogs.com/hogwarts/p/14549103.html
Copyright © 2020-2023  润新知