遗留系统现代化实战
姚琪琳
Thoughtworks 资深咨询师
5615 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 30 讲
用户故事 (1讲)
遗留系统现代化实战
15
15
1.0x
00:00/00:00
登录|注册

10 | 代码现代化 :代码的分层重构

你好,我是姚琪琳。
上节课,我带你学习了重构遗留代码的倚天剑和屠龙刀,也就是拆分阶段方法对象。面对遗留代码,它们是披荆斩棘的利器。
不过,单块逻辑的代码重构好了之后,我们还要迎接新的挑战。今天我们就来看看如何重构整体的代码,也就是如何对代码分层。

遗留系统中常见的模式

我还记得大学时做的编程作业,用 VB6 做一个学校图书馆的借书系统。当时的做法十分“朴素”,在点击“借阅”按钮的事件处理器中,我直接读取借书列表中的书籍 ID,然后连接数据库,执行一条 update 语句,把这些书籍的借阅者字段改成当前的学生 ID。
后来,我看到了 Eric Evans 的《领域驱动设计》这本书,才发现这种做法就是书中介绍的 Smart UI 模式。它虽然简单好理解,但归根结底还是一种面向过程的编程思想。一旦逻辑变得更复杂,这种模式的问题就会凸显出来。
举个最简单的例子,比如借书前需要校验学生的类型,本科生最多可以借 3 本,而研究生最多可以借 10 本。如果本科生借阅了 5 本书,在点击按钮的时候就会弹出错误消息。我们用伪代码来表示就是:
var bookCount = bookDataTable.count
var studentType = DB.query("SELECT TYPE FROM STUDENTS WHERE ID = " + studentId)
if (studentType = "本科生" && bookCount > 3)
MessageBox.error("本科生一次最多借阅3本图书")
if (studentType = "研究生" && bookCount > 10)
MessageBox.error("研究生一次最多借阅10本图书")
for(var book in bookDataTable.values)
DB.update("UPDATE BOOKS SET BORROWER_ID = " + studentId + " WHERE BOOK_ID = " + book.id)
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文介绍了面对遗留代码时的代码分层重构方法。作者首先回顾了遗留系统中常见的模式,指出它们在面对复杂逻辑时的局限性和问题。随后,文章提出了对代码分层的重构方法,即将界面逻辑、业务逻辑和数据库访问分离开来,形成UI、Service、Dao这样的三层结构。通过伪代码和Java代码的对比,展示了重构后的代码结构和逻辑。作者指出,虽然这种分层模式在Service层向Dao层传递数据时使用了对象,但仍然存在贫血模型和代码臃肿等问题。最后,文章强调了在逻辑变得复杂时,服务层的代码会变得越来越臃肿,不同的服务之间也很难相互调用和复用逻辑,每一个服务类都将变成上帝类(God Class)的问题。文章通过实例分析和对比,深入浅出地介绍了代码的分层重构方法及其局限性,对于需要进行代码现代化和重构的开发人员具有一定的参考价值。文章还介绍了应用服务、领域模型、数据映射器、仓库等多种模式,强调了重构遗留代码的重要性和挑战。读者可以通过本文了解到如何应对遗留代码的复杂逻辑,以及如何利用分层架构和领域驱动设计等技术手段进行代码重构和现代化。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《遗留系统现代化实战》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(15)

  • 最新
  • 精选
  • Michael
    老师能不能再讲讲DDD里面的 Application Service, Domain Service之间的区别啊?

    作者回复: 它们解决的问题是不同的,Domain Service解决的领域逻辑,Application Service解决的是应用逻辑,这两种逻辑的区别就是,领域逻辑是和具体的软件系统无关的,是线下操作的时候就有的逻辑,比如去菜市场买菜,一手交钱一手交菜,而应用逻辑是因为有了软件系统才附加上去的,比如在线支付的安全问题,数据一致性问题。 可以参考我以前写的一篇文章以及后面和同事的讨论:https://mp.weixin.qq.com/s/ZkpVELpKVmmwyG9kP7wvxw

    2022-05-30
    9
  • 下弦の月
    public class UserRepository { public void add(User user) { } public void save(User user) { } public User findById(long userId) { } public boolean isBorrowedWith(long bookId) { } } UserRepository.findById(1000L).isBorrowed() UserRepository.isBorrowedWith(1000L) isBorrowed逻辑如果刚好是检查对象上的某个字段的值,那么第一种领域驱动的查询方式会将对象上所有不会使用到的数据都加载到内存。而第二种面相过程的方式则看起来性能更优。 这两种方式如何取舍呢?第二种查询到底属于领域模式,还是传统的事物脚本模式呢?

    作者回复: 第二种属于事务脚本模式。Repository要么操作领域模型,要么返回领域模型,想这种既不操作也不返回的,实际上是吧Repository当Dao用了。 用第二种带来的所谓性能提升其实微乎其微,没有必要为了它而放弃面向对象。一旦你用了第二种,未来任何类似的方法都将加到Repository上,User很快就变回了贫血模型。

    2022-05-02
    2
    4
  • 雨落~紫竹
    现在一直是服务于业务 项目都是倒排期 能自己回头看代码的时间很少 难搞喔

    作者回复: 要给技术债建卡

    2022-06-22
    3
  • favorlm
    请问老师,应用中最常用的多条件查询功能,例如 ,根据书籍类型 关键字查询书籍,那么使用仓储模式,会把其他无关书籍也查询到内存里 这种情况下,我会被直接写sql吸引,老师认为要如何取舍呢?

    作者回复: 这种查询业界一般有两种方案: 1. 读写分离(CQRS)。领域模型处理的是领域状态的更改,更适合写操作。读操作,特别是查询,有时候跟模型并不是十分匹配,比如管理员在查询书籍的时候,还想知道是谁借阅了这个书籍,这个模型是从Book到Borrowing的,和我们模型中User到Borrowing不匹配,如果为了查询去修改模型显然是不合适的。读写分离的思想是,在进行复杂查询时不走领域模型那一套写模型,而是重新写一套读模型,在读模型中不适用repo,而是可以直接写SQL,因为有些查询很复杂。 2. 仍然使用原来的模型,只是模型可能会稍作一些修改。 我建议能用原来的写模型就用写模型,实在不行的时候,也可以考虑CQRS。但CQRS的认知负载偏高,很多团队应对不了,所以要慎重。

    2022-05-04
    3
    2
  • FelixFly
    老师,业务逻辑与应用逻辑如何判别?借阅到期发送通知,借阅到期理解为业务逻辑,发送通知为应用逻辑。假如说借阅成功就需要发送通知,这个发送通知作为事件发送,这个应该理解为是业务逻辑还是应用逻辑,是否也应该是应用逻辑?

    作者回复: 对于用户验证、日志记录、事务、性能等非功能需求,显然是应用逻辑,这个自不用说。 对于一些难以说清楚的逻辑,我是这么区分的(不一定正确,但你可以参考):对于传统行业来说,将原来的手动流程变为信息化流程的,都属于业务逻辑;而由信息化带来的增值服务(比如自动发短信通知),就属于应用逻辑,也就是软件系统给我们带来的那些逻辑。 对于你的场景,如果事件发送给另一个上下文,我认为发送事件是属于应用逻辑。但也有不少人认为这是领域层的某些业务触发的领域事件,是领域逻辑。

    2022-05-07
    2
    1
  • hzecool
    老师,上面提到仓库接口放在领域层,仓库接口里涉及到的输入输出参数应该有领域对象吧? 而仓库的实现类是要放在更下层的(算基础设施层吧),这样岂不是下层的实现类也要引用到上层的领域对象了,感觉不合适呀。

    作者回复: 好问题。基础设施层并不属于领域层的下层,它不是一个层,而是属于能力提供商模式,是可以依赖任何层的。注意看最后那幅图中的箭头。

    2022-06-17
  • hzecool
    老师,一个领域模型能直接同步调用另外一个领域模型吗?还是需要在domainService里对这两个领域模型进行集成?

    作者回复: 当然可以,一个订单包含多个商品,订单的总价就依赖了商品的价格。

    2022-06-17
  • Marx
    领域对象和数据库的映射是怎么解决的呢?

    作者回复: 参考这节课的数据映射器和仓库模式。更多内容可以看看《企业应用架构模式》。

    2022-06-13
  • 飞翔
    老师业务代码用ddd 但是写infra 框架 比如zookeeper等用什么指导呀 也用ddd嘛?

    作者回复: 没太理解你的意思。你是指业务代码用DDD的时候,类似使用zookeeper的代码怎样写到infra层?还是你的工作是写zookeeper这样的工具,应该怎么开发? 如果前者,只是在infra层封装就好了,没什么特别的。如果是后者,也是可以用DDD的,你要分析一下zookeeper这样的工具,它的核心业务是什么,并为此建立模型。

    2022-05-10
  • 刘大明
    如果从领域中获取其他信息,是否会存在过长的消息链的坏味道呢? 例如这样 user.getTravel().getFlights().getCabin().getCode(); 之前项目中,总是会有get值报空指针的问题。 请问下老师,这种代码要怎么处理?

    作者回复: 这的确是Message Chains坏味道,解决方案可以按照《重构》中说的,使用Hide Delegate手法,将委托关系隐藏到User或Flight里。但这样往往也会造成Middle Man坏味道,比如Code放到User里可能就不合适。所以还是要结合业务来综合对比。 一种可能的方案是,将Flight隐藏到User,将Code隐藏到Flight,得到一个还算OK的设计。 空指针可以在封装的get方法里去处理,不要放在客户端。

    2022-05-06
    2
收起评论
显示
设置
留言
15
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部