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

在这里插入图片描述

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

原文链接: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 项目就是过渡优化。

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容