后端技术面试 38 讲
李智慧
同程艺龙交通首席架构师,前 Intel& 阿里架构师,《大型网站技术架构》作者
37373 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 46 讲
不定期加餐 (1讲)
后端技术面试 38 讲
15
15
1.0x
00:00/00:00
登录|注册

14 | 软件设计的单一职责原则:为什么说一个类文件打开最好不要超过一屏?

从Web应用架构演进看单一职责原则
正方形类Rectangle
一个类文件打开后,最好不要超过屏幕的一屏
一个类,应该只有一个引起它变化的原因
低耦合和高内聚
小结
一个违反单一职责原则的例子
单一职责原则
为什么说一个类文件打开最好不要超过一屏?
思考题
软件设计的单一职责原则
参考文章

该思维导图由 AI 生成,仅供参考

我在 Intel 工作期间,曾经接手过一个大数据 SQL 引擎的开发工作(如何自己开发一个大数据 SQL 引擎?)。我接手的时候,这个项目已经完成了早期的技术验证和架构设计,能够处理较为简单的标准 SQL 语句。后续公司打算成立一个专门的小组,开发支持完整的标准 SQL 语法的大数据引擎,然后进一步将这个产品商业化。
我接手后打开项目一看,吓出一身冷汗,这个项目只有几个类组成,其中最大的一个类,负责 SQL 语法的处理,有近万行代码。代码中充斥着大量的 switch/case,if/else 代码,而且方法之间互相调用,各种全局变量传递。
只有输入测试 SQL 语句的时候,在 debug 状态下才能理解每一行代码的意思。而这样的代码有 1 万行,现在只实现了不到 10% 的 SQL 语法特性。如果将 SQL 的全部语法特性都实现了,那么这个类该有多么大!逻辑有多么复杂!维护有多么困难!而且还要准备一个团队来合作开发!想想看,几个人在这样一个大文件里提交代码,想想都酸爽。
这是当时这个 SQL 语法处理类中的一个方法,而这样的方法有上百个。
/**
* Digest all Not Op and merge into subq or normal filter semantics
* After this process there should not be any NOT FB in the FB tree.
*/
private void digestNotOp(FilterBlockBase fb, FBPrepContext ctx) {
// recursively digest the not op in a top down manner
if (fb.getType() == FilterBlockBase.Type.LOGIC_NOT) {
FilterBlockBase child = fb.getOnlyChild();
FilterBlockBase newOp = null;
switch (child.getType()) {
case LOGIC_AND:
case LOGIC_OR: {
// not (a and b) -> (not a) or (not b)
newOp = (child.getType() == Type.LOGIC_AND) ? new OpORFilterBlock()
: new OpANDFilterBlock();
FilterBlockBase lhsNot = new OpNOTFilterBlock();
FilterBlockBase rhsNot = new OpNOTFilterBlock();
lhsNot.setOnlyChild(child.getLeftChild());
rhsNot.setOnlyChild(child.getRightChild());
newOp.setLeftChild(lhsNot);
newOp.setRightChild(rhsNot);
break;
}
case LOGIC_NOT:
newOp = child.getOnlyChild();
break;
case SUBQ: {
switch (((SubQFilterBlock) child).getOpType()) {
case ALL: {
((SubQFilterBlock) child).setOpType(OPType.SOMEANY);
SqlASTNode op = ((SubQFilterBlock) child).getOp();
// Note: here we directly change the original SqlASTNode
revertRelationalOp(op);
break;
}
case SOMEANY: {
((SubQFilterBlock) child).setOpType(OPType.ALL);
SqlASTNode op = ((SubQFilterBlock) child).getOp();
// Note: here we directly change the original SqlASTNode
revertRelationalOp(op);
break;
}
case RELATIONAL: {
SqlASTNode op = ((SubQFilterBlock) child).getOp();
// Note: here we directly change the original SqlASTNode
revertRelationalOp(op);
break;
}
case EXISTS:
((SubQFilterBlock) child).setOpType(OPType.NOTEXISTS);
break;
case NOTEXISTS:
((SubQFilterBlock) child).setOpType(OPType.EXISTS);
break;
case IN:
((SubQFilterBlock) child).setOpType(OPType.NOTIN);
break;
case NOTIN:
((SubQFilterBlock) child).setOpType(OPType.IN);
break;
case ISNULL:
((SubQFilterBlock) child).setOpType(OPType.ISNOTNULL);
break;
case ISNOTNULL:
((SubQFilterBlock) child).setOpType(OPType.ISNULL);
break;
default:
// should not come here
assert (false);
}
newOp = child;
break;
}
case NORMAL:
// we know all normal filters are either UnCorrelated or
// correlated, don't have both case at present
NormalFilterBlock nf = (NormalFilterBlock) child;
assert (nf.getCorrelatedFilter() == null || nf.getUnCorrelatedFilter() == null);
CorrelatedFilter cf = nf.getCorrelatedFilter();
UnCorrelatedFilter ucf = nf.getUnCorrelatedFilter();
// It's not likely to result in chaining SqlASTNode
// as any chaining NOT FB has been collapsed from top down
if (cf != null) {
cf.setRawFilterExpr(
SqlXlateUtil.revertFilter(cf.getRawFilterExpr(), false));
}
if (ucf != null) {
ucf.setRawFilterExpr(
SqlXlateUtil.revertFilter(ucf.getRawFilterExpr(), false));
}
newOp = child;
break;
default:
}
fb.getParent().replaceChildTree(fb, newOp);
}
if (fb.hasLeftChild()) {
digestNotOp(fb.getLeftChild(), ctx);
}
if (fb.hasRightChild()) {
digestNotOp(fb.getRightChild(), ctx);
}
}
我当时就觉得,我太难了。

单一职责原则

软件设计有两个基本准则:低耦合和高内聚。我在前面讲到过的设计原则和后面将要讲的设计模式大多数都是关于如何进行低耦合设计的。而内聚性主要研究组成一个模块或者类的内部元素的功能相关性。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文通过作者在处理大数据SQL引擎的开发工作中的经历,以及一个违反单一职责原则的例子,阐述了单一职责原则的重要性。作者分享了在接手一个项目时,发现一个负责SQL语法处理的类有近万行代码,充斥着大量的switch/case,if/else代码,以及方法之间的互相调用,导致维护困难和代码脆弱。文章还举例说明了一个违反单一职责原则的设计,即Rectangle类既负责绘图又负责计算面积,导致耦合性高,不利于程序的扩展和维护。最后,文章强调了单一职责原则的重要性,指出遵循该原则可以使类更易于复用和扩展,更符合开闭原则,同时也更易于阅读和维护。文章通过实际案例生动地阐述了单一职责原则的重要性,对于软件设计人员具有一定的借鉴意义。文章还从Web应用架构演进的角度,阐述了单一职责原则在实际开发中的应用,以及在处理大数据SQL引擎开发中如何通过职责拆分来改进设计。通过实例和经验分享,读者可以深入了解单一职责原则在软件设计中的重要性和应用方法。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《后端技术面试 38 讲》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(18)

  • 最新
  • 精选
  • 张希音
    以前编码的时候喜欢写一大段逻辑,然后出现bug要进行debug的时候发现真的很痛苦。后来,把特定功能的逻辑抽出一个方法,每个方法只干一件事,刚开始感觉很麻烦,后来发现看代码的时候比以前清晰多了,有时候不用debug都能大概判断问题出在哪个具体方法中。

    作者回复: 👍

    2019-12-23
    2
    12
  • Jinyun
    在维护前同事写的代码的时候,之前总是在其方法内部做增量,吃了几次亏后,果断写个方法把需要的业务处理好,然后勾到需要的地方,不光调试贼爽,还很清晰。老师讲的比较宏大,不知道我这个算不算😂

    作者回复: 算😁

    2019-12-23
    6
  • learn more
    即使一个类职责单一,也不一定就好维护吧,个人觉得还要考虑他所依赖和他被依赖的关系,如果你看到的方法被很多类依赖使用,还敢这么轻松修改吗?

    作者回复: 是的,要综合权衡。原则不是教条。

    2020-03-26
    5
  • geek_arong2048
    智慧老师当年还需要亲手写代码吗hhh,一直以为这是交给下属做的事

    作者回复: 现在也要写

    2021-07-17
  • escray
    很努力的看了一下那个 103 行的方法(包括注释),其实看不太懂,上来就是一个 if,然后有两层的 switch 嵌套,第二层 case 里面还有 if ,很奇怪,居然没有 else。 可能自己以前也是这么写代码的,感觉就像一篇流水账,好在还没有到面条(Spaghetti code)的程度。 有点好奇,李老师当年是如何重构这段代码的,似乎可以再开一个专栏了。 如果是我的话,可能会把 Rectangle 类拆分成 GeometricRectangle 和 GUIRectangle,这样似乎更明确一些,否则总感觉 GeometricRectangle 是 Rectangle 的子类。 看到 Web 应用架构演进那一段,还真是亲切,我在学编程的时候,似乎还是 Servlet 的时代(暴露年龄),后来写代码的时候已经是 JSP 了(其实调试起来也很痛苦),再后来就是 MVC 了。 看到文末的截图,不过似乎 project-panthera-ase 已经停止更新了。 感觉单一职责原则其实有点考察程序员对于业务的掌握情况,就是能否想清楚引起类变化的原因。 最后,讲个段子。 因为要求一个类不超过一屏,越来越多的程序员买宽屏甚至带鱼屏,然后竖着放。
    2020-09-25
    3
  • 丁乐洪
    看到这,我要重构我的代码
    2020-03-18
    3
  • 俊伟
    我觉得从测试的角度写代码也有助于代码逻辑结构的模块化划分。每次写之前想想写完了这段代码能不能测试。
    2019-12-23
    3
  • Seven.Lin澤耿
    老师,从我工作以来,Java工程基本都是Controller->Service->DAO,以表来区分,这样子算违背单一责任原则吗?还是要看所谓的"责任"的界限?
    2020-06-08
    1
  • 木风
    如此众多的类,它们之间的关系就相对复杂了,请问这如何管理呢?
    2020-01-28
    1
  • Paul Shan
    职责单一原则其实挺难定义,有些资源文件虽然很大,但是其职责是单一的,这个文件就是维护一个映射。我个人认为,一个类与其包含的所有方法和属性,如果只有包含关系,可认为单一职责,例如用户和用户的名字。如果用户名字还有其他用途,例如和密码一道提供认证功能而用户类中还有其他属性和认证无关,就不再是单一职责,可以让用户名和密码单独成类。
    2019-12-29
    1
收起评论
显示
设置
留言
18
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部