优秀软件方法学“漫游记”

news2025/1/11 14:24:29

你好,我是东(在极客时间的 ID 是 Fredo)。大学的时候我读的是计算机专业,现在是一名工作了近3年的程序员,很高兴能和你分享我的学习体会。

我是怎样学习课程的?

首先聊聊我是怎么学习这门课的。

DDD 是一个很老的概念,但在我身边的技术圈,这仍然是个常常被提起的词。不过很多人在谈论这个概念,但很少有人说清楚,并给出详细具体的实践指导。

我就是抱着试试的心态,想看看作者到底是要怎么“手把手”教我 DDD 。我急迫地寻找课程里一些 DDD 的概念名词,试图验证一下作者讲的和我之前理解的是不是一回事,稍后我再专门说说对我启发最大的几点。

课程案例以迭代的形式展开,我试着投入其中,想象自己是敏捷团队中的一员,正在参与这个迭代。

迭代一按照“捕获需求、建立领域模型、模型实现”的顺序串了一遍,这大概还是一个三段论的模式,即发现问题(收集需求)、分析问题(建立模型)、解决问题(编码实现)。

由于我之前有一些 UML 的基础,课程里的分析图看起来没有多大障碍。每看完一节课,我都会将其中涉及的 DDD 中的术语记录在导图中。同时,跟随老师的分析,我结合自己的理解,提炼了一些重要的区分点和有助于回忆内容的关键词,把它们体现在了导图里。

第二遍阅读课程(正在进行中),我的关注点主要放在了DDD代码实现层面,主要会对比迭代中的代码,与我当前的项目结构以及代码组织方式有什么异同。

之后,我打算再跟着迭代案例分析过程,将其中的领域模型图再好好画一遍,进一步体会“领域模型与业务需求一致,系统实现与领域模型一致”的原则。

我从课里学到了什么

课程里有很多值得反复咀嚼的内容,这里我就抛砖引玉,分享一些印象最深刻的点。

编程范式的选择不是非黑即白

印象里在上大学的时候,我曾经在一门系统分析与设计类的课程,趁着课间,跑上讲台跟老师问过这样一个问题:“面向对象强调对象的属性与行为在一起,而我现在的编程方式自然地将逻辑都放在 Service 里面了。虽然使用的是面向对象的编程语言,但感觉自己还是在用面向过程的思路,该怎么办呢?”。

老师的回答已淡出了记忆,然而这个问题始终萦绕心头。不排除是大一的时候就学了 C 语言的缘故,过于习惯面向过程的处理方式。

后来工作了,编程范式上的“纠结”还是困扰着我。第10节课《要“贫血”模型还是要“充血”》讲的就是这个问题,编程范式在一个发展的过程中,开发者的认知也在成长,编程范式的选择不是一个非黑即白的问题,需要我们根据所开发的软件、编程语言、团队编程习惯等去找到一个平衡点。

模块划分方法

第11节课《怎么创建领域对象、实现领域逻辑》中,老师提到了一种指导模块划分的方法——打横(分层)与打竖(耦合关系)

打横是站在按性质的角度对代码进行分层,如适配器层、应用服务层、领域服务层、基础Common层。

打竖是按耦合性,我们可以在已经分层的包下,再根据耦合关系来分包。按我之前的分包方式,通常是按照代码的性质分包的,也就是仅仅做了打横这一步。

对比之后不难发现,按打横与打竖的分包方式,后面的包结构在各层下面会更加细粒度。虽然包结构变多了,但是Domain层的各聚合内,代码显得更加紧凑、高内聚。

以聚合为单位进行持久化

我之前对仓库的认知是一个数据库表对应一个 DAO,就算有单独拎出一个 Repository,也仅仅做了对 DAO 的一个委派,然后在业务代码层面将数据取出来处理。

而第16课《怎么实现不变规则》中讲到,Repository 是针对整个聚合的,而不是从数据库表的角度。

这个说法打破了我的惯性认识,现在想起来仍然记忆犹新。我原先的想法缺乏领域意识,将应用服务和领域逻辑混杂在了一起,变成了基于 CRUD 的面向过程编程。

而一个 Repository 负责一个聚合的持久化,聚合内部紧密相关的对象生命周期就得到了统一管理。如果我们需要测试领域服务,只需要mock聚合对应的仓库即可,这有利于我们对领域服务代码的测试。

值对象和实体的本质区别

值对象和实体虽然是DDD里“人尽皆知”的知识点了,但想要说清楚两者的本质区别还是有难度的。

第20节课里,从认识论的角度去讲解,让我觉得很新颖。

这两者的区分,有一个有趣的方式可供参考判断,我们可以向自己提问:存在F的属性/属性值可以改变吗,如果属性/属性值改变了,存在F还是存在F吗?

如果得到的答案是“是”,则F为实体,否则就是值对象。

还有一些需要注意的点,需要根据上下文结合判断(这也是 DDD 原书中提到的),比如在邮政软件或地图软件中重新划分了行政区域,那么所有使用邮政软件或地图软件的地址都将改变,这个上下文里面,地址就是实体。而对于某个用户的购物软件中的收件地址,仅表示这个用户实体的收件地址,这里就应该当成值对象来处理。

我还尝试用数学语言和图解描述了一下(可能不准确,欢迎批评指正)。

图片

泛化的表设计策略

软件架构或设计里经常需要我们做取舍,没有唯一的答案,具体问题具体分析,权衡利弊之后,才能得到当前问题域下的一个相对最优解。这种不确定性很有意思,同时也很有挑战。

就拿泛化的数据库表设计来说,我之前的常用做法是整个泛化体系一张表。可能我碰到的大多数情况,子类没有太多的不同属性,即便有几个字段的空间浪费,也可以接受。

而第25节课里,不但提到了泛化的三种数据库表设计策略。更重要的是给了我一个指导方针,在空间效率、时间效率、系统的可维护性这些维度去考量。相比原先凭直觉行事,知其所以然的衡量判断,才更有可能在设计上给出有说服力的方案。

品读经典与刻意练习

工作之后,我们常常被一些糟糕的代码“折磨”,但心中仍追求着整洁代码、优雅的分析设计,希望实现“良质”的软件开发(“良质”这个词来自《禅与摩托车维修艺术》这本书,你有兴趣的话可以去翻一下)。

在我看来,计算机领域里,“良质”和软件大师们探索、归纳出的分析方法、原则、模式是异曲同工的。沿着他们的思路,配合自己的实践,我们才更有可能实现一个具备“良质”的软件系统。

而想要了解这些经验模式,很重要的一个途径就是阅读经典、学习原著。老师经常在课程中指出知识点在 DDD 原书中的出处,正好我旁边也放着两本书《领域驱动设计》和《实现领域驱动设计》,可以随时看一下对应的章节。

课程里还特意用两节选学的内容介绍了相关的经典书籍,让我燃起了重读经典的兴趣,算是意外惊喜。

之前我曾细读了《实现领域驱动设计》,可惜因畏难情绪而将原书《领域驱动设计》放置一旁,《分析模式》更是从未问津。老师有个“经”与“传”的提法,将原书比作“经”,一手资料的知识密度及重要性可见一斑。

我尝试了在课程学习过程中去阅读一些相关图书,感觉对巩固知识很有帮助。在迭代一第12节课中,老师的案例用到了不少重构手法,我们可以在《重构》原书中找到对应的章节。

比方说有两个技能点,我现在已经有意识地用在了日常工作里,分别是提高封装性的“表意接口”,还有发现和消除“特性依恋”。你可以在《重构》的3.9节找到它们,这两招能让我们的代码更加 OO done right (把面向对象做对)。

通过命名表达意图将函数写短的方法也很实用,如果命名已经清晰地说明了一切,就没有注释的必要(《重构》3.24节)。

另外,再补充一个我在实际开发时发现的问题,也和封装性有关。项目开发中很多小伙伴喜欢用的 lombok 插件,虽然能够为我们节约一些时间,但不假思索地使用 @Data 注解会破坏封装性,需要收敛着用

经典仰之弥高,课程里的迭代案例亦循循善诱。在阅读经典的过程中,我时常会惊讶于我所遇到的问题,前人早已就碰到了,并且分析问题直捣本质,给人以启发。

不过,软件工程没有银弹,这些经典提供了各式各样的思路和方案,能培养我们对“好”的认识。但想要把高手的心法招式化为己用,还离不开刻意练习。

就像王小波说的那样,“生活就是个缓慢受锤的过程”,编程技艺的学习旅程也是同样的体验。希望我们面对各种锤炼仍然能生猛下去,热爱雕琢技艺,也热爱滚烫生活。

文章来源:极客时间《手把手教你落地 DDD》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/436473.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

智网工程师培训一些心得

智网工程师培训一些心得 MYSQLApache Flask开发小程序 MYSQL MYSQL的日期如果定义为datetime类型,比较的时候可以使用 entry_form.exercise_date between 2022-1-1 and 2023-4-19 日期可以是非2位宽对齐方式 日期比较也可以使用大于或者小于号MYSQL支持定时备份数…

学习数据结构第6天(栈的基本概念)

栈的基本概念 栈的定义栈的基本操作栈的存储结构 栈的定义 栈(Stack)是一种基于先进后出(FILO)或者后进先出(LIFO)的数据结构,是一种只允许在一端进行插入和删除操作的特殊线性表。 栈按照先进后出的原则存储数据,先进入的数据被压入栈底,最…

数据结构初阶(链表)

文章目录 一、链表的基础概念1.1 什么是链表1.2 分类1.3 链表的底层代码1.4 例题1.5 LinkedList 的实现(1)什么是LInkedList(2)底层代码(3)LinkedLIst的使用 1.6 ArrayList和LinkedList的区别 一、链表的基…

Spring底层架构核心概念

文章目录 Spring底层架构核心概念BeanDefinitionBeanDefinitionReaderAnnotatedBeanDefinitionReaderXmlBeanDefinitionReaderClassPathBeanDefinitionScanner BeanFactoryApplicationContext国际化资源加载获取运行时环境事件发布 类型转换PropertyEditorConversionServiceTyp…

20行Python代码获取 心碎榜单文件保存本地,准备开始emo......

人生苦短 我用python(emo版) (语气充满悲伤…) 今天咱们试试只用20行代码来实现 批量获取 某某云 文件保存本地,炒鸡简单! 悄悄的告诉你,其实不到20行代码~ 事前准备 软件环境 环境Python3.8编辑器是pycharm 模块…

轻松掌握k8s的kubectl使用命令行操作01知识点

程序员使用的kubectl,只能在主节点使用kubectl命令 1、查看集群所有节点 kubectl get nodes 2、根据配置文件,给集群创建资源 kubectl apply -f xxxx.yaml 3、查看集群部署了哪些应用 kubectl get pods -A 4、指定查看命名空间部署了哪些应用 不指…

[DSCoding2] 反转链表——迭代法

题目描述 核心思路 观察上图可以发现,将链表反转后,原有的结点间的引用关系发生了改变,比如反转前1指向2,反转后2指向1, 所以我们可以从修改节点间的引用关系下手。 在遍历链表时,将当前节点的next指针指向…

ReentrantLock原理

实现了Lock接口 内部也维护了一个同步器Sync继承自AQS,Sync是抽象的,两个实现NonFairSync和FairSync public ReentrantLock() {sync new NonfairSync(); } public ReentrantLock(boolean fair) {sync fair ? new FairSync() : new NonfairSync(); }非…

算法训练Day30:332.重新安排行程 51. N皇后 37. 解数独

文章目录 重新安排行程题解 [N 皇后](https://leetcode.cn/problems/n-queens/description/)题解 解数独题解 重新安排行程 CategoryDifficultyLikesDislikesContestSlugProblemIndexScorealgorithmsHard (47.57%)7650--0 Tags Companies 给你一份航线列表 tickets &#xf…

微服务学习——微服务

认识微服务 单体架构 将业务的所有功能集中在一个项目中开发,打成一个包部署。 优点: 架构简单部署成本低 缺点: 耦合度高 分布式架构 根据业务功能对系统进行拆分,每个业务模块作为独立项目开发,称为一个服务。 优点: 降低服务耦合有利…

【LeetCode】剑指 Offer 58. 反转字符串 p284 -- Java Version

1. 题目介绍(58. 反转字符串) 面试题58:翻转字符串, 一共分为两小题: 题目一:翻转单词顺序题目二:左旋转字符串 2. 题目1:翻转单词顺序 题目链接:https://leetcode.cn/p…

使用 ip2region 获取用户的 IP 归属地

目录 1. ip2region 简介2. 使用步骤2.1 下载资源2.2 引入依赖2.3 编写工具类2.3.1 获取 IP 地址2.3.2 根据 IP 地址获取 IP 归属地2.3.3 完整代码 2.4 结果测试 1. ip2region 简介 ip2region 是一个离线IP地址定位库和IP定位数据管理框架,10微秒级别的查询效率&…

部署zabbix代理服务器和snmp监控

目录 zabbix代理服务器 分布式监控的作用 部署zabbix代理服务器 在 Web 页面配置 agent 代理 snmp监控 SNMP简介 部署zabbix-snmp 服务端安装snmp监控程序 在 Web 页面配置 snmp 方式监控 zabbix代理服务器 分布式监控的作用 分担 server 的集中式压力 解决多机房之…

HTTP | 强缓存与协商缓存

缓存,开发绕不开的环节。 web缓存分为很多种,比如数据库缓存、代理服务器缓存、CDN缓存,以及浏览器缓存(localStorage, sessionstorage, cookie)。 一个web应用,需要各式各样的资源(html/css/…

【C++】C++11 右值引用和移动语义

文章目录 一、左值与左值引用二、右值与右值引用三、左值引用和右值引用的比较四、右值引用的使用场景和意义1、左值引用的短板2、移动构造和移动赋值3、STL 容器的变化 五、万能引用与完美转发1、万能引用2、完美转发 六、新增默认成员函数七、成员变量的缺省值八、default 和…

【Blender建模】newbird从零开始学+新手常见问题处理

目标 第一阶段:在跟着教程下,熟悉如何使用blender 教程地址:https://www.youtube.com/watch?vnIoXOplUvAw 一、移动、旋转、扩展各视角下的物体,熟悉各个窗口 鼠标中键(Shift)控制视角的方向 ~键快速选择…

Redis --- 入门、数据类型

一、前言 1.1、什么是Redis Redis是一个基于内存的key-value结构数据库。Redis 是互联网技术领域使用最为广泛的存储中间件,它是「Remote Dictionary Service」的首字母缩写,也就是「远程字典服务」。 基于内存存储,读写性能高适合存储热点…

Pytorch基础 - 5. torch.cat() 和 torch.stack()

目录 1. torch.cat(tensors, dim) 2. torch.stack(tensors, dim) 3. 两者不同 torch.cat() 和 torch.stack()常用来进行张量的拼接,在神经网络里经常用到。且前段时间有一个面试官也问到了这个知识点,虽然内容很小很细,但需要了解。 1. t…

Spring(10. 面试问题简析)学习笔记

上一篇:9. Spring 底层原理 文章目录 1. 对Spring的IOC机制的理解2. 对spring的AOP机制的理解3. 了解过cglib动态代理吗?他跟jdk动态代理的区别是什么?4. 能说说Spring中的Bean是线程安全的吗?5. Spring的事务实现原理是什么&…

Leetcode-二叉树

1.中序-后序构建二叉树 106. 从中序与后序遍历序列构造二叉树 - 力扣(LeetCode) 1. 首先根据后序(左右中)确定顶点元素; 2. 根据顶点元素划分中序序列; 3. 根据划分中序序列中-左子树的长度,进…