767 重构字符串_重构字符串型系统

767 重构字符串

去年,我加入了一个项目,该项目从另一个软件公司接手,但未能满足客户需求。 如您所知,在“继承”的项目及其代码库中,有许多事情可以并且应该加以改进。 可悲的是(但并不奇怪)领域模型就是这样一个孤零零,被遗忘已久的领域之一,它大声呼救。

我们知道我们需要动手,但是您如何在一个陌生的项目中改进领域模型,在该项目中,所有事情都是如此混杂,纠结和杂草丛生,并具有偶然的复杂性? 您设置边界(分而治之!),在一个区域中进行较小的改进,然后移至另一区域,同时了解景观,并发现隐藏在那些可怕的显而易见的事物背后的更大问题,这些事物乍一看会伤害您的眼睛。 您可能会感到惊讶,您可以通过进行一些小的改进并选择低挂的水果来取得多少成就,但同时您也会傻傻地认为它们可以解决由于缺少(或没有足够)从项目开始之初就开始进行建模工作。 但是,如果没有这些小的改进,将很难解决大多数主要的领域模型问题。

对我来说,通过引入简单的值对象将更多的表现力和类型安全性带入代码中,始终是挂在最下面的成果之一。 这是一个总能奏效的技巧,尤其是在处理散布着原始痴迷代码气味的代码库时,所提到的系统是一个字符串类型的系统。 到处都是这样的代码:

public void verifyAccountOwnership(String accountId, String customerId) {...}

虽然我敢打赌,每个人都希望它看起来像这样:

public void verifyAccountOwnership(AccountId accountId, CustomerId customerId) {...}

这不是火箭科学! 我会说这是不费吹灰之力的,这总是让我感到惊讶的是,找到在模糊,无上下文的BigDecimals而不是Amounts,Quantities或Percentages上运行的实现是多么容易。

使用域特定值对象而不是无上下文基元的代码是:

  • 更具表现力(您无需将字符串映射到脑海中的客户标识符,也不必担心这些字符串中的任何一个都是空字符串)
  • 更容易掌握(不变式被保护在一个地方,而不是分散在各处的if语句中的代码库中)
  • 越野车少(我是否将所有这些字符串按正确的顺序排列?)
  • 更容易开发(显式定义更明显,不变量在您期望的位置得到保护)
  • 开发速度更快(IDE提供了更多帮助,编译器提供了快速的反馈周期)

而这些只是您几乎免费获得的一些东西(您只需要使用常识^^)即可。

对价值对象的重构听起来简直是小菜一碟(这里没有考虑命名),您只需在这里提取类,在那儿迁移类型,没有什么特别的。 通常就是这么简单,尤其是当您要处理的代码位于单个代码存储库中并在单个进程中运行时。 但这一次并不那么琐碎。 并不是说它复杂得多,它只需要一点点思考(这使得描述一件不错的工作^^)。

这是一个分布式系统,其服务边界设置在错误的位置,并且在服务之间共享了过多的代码(包括模型)。 边界设置得如此糟糕,以至于系统中的许多关键操作都需要与多种服务进行多次交互(大多数情况下是同步的)。 在描述的上下文中应用提到的重构存在一个挑战(不是那么大),但这种挑战不会最终成为创建不必要的层并在服务边界引入意外复杂性的练习。 在跳到重构之前,我必须设置一些规则,或者甚至是一个关键规则:服务(包括后备服务)外部应该看不到任何更改。 简而言之,所有已发布的合同都保持不变,并且在支持服务方面不需要进行任何更改(例如,无需更改数据库架构)。 坦率地说,轻而易举地完成了一些枯燥的工作。

让我们以String accountId ,并演示必要的步骤。 我们要转这样的代码:

public class Account {private String accountId;// rest omitted for brevity
}

到这个:

public class Account {private AccountId accountId;// rest omitted for brevity
}

这可以通过引入AccountId值对象来实现:

@ToString
@EqualsAndHashCode
public class AccountId {private final String accountId;private AccountId(String accountId) {if (accountId == null || accountId.isEmpty()) {throw new IllegalArgumentException("accountId cannot be null nor empty");}// can account ID be 20 characters long?// are special characters allowed?// can I put a new line feed in the account ID?this.accountId = accountId;}public static AccountId of(String accountId) {return new AccountId(accountId);}public String asString() {return accountId;}
}

AccountId只是一个值对象,没有身份,不会随时间变化,因此是不可变的。 它在单个位置执行所有验证,并且由于无法实例化AccountId而在错误输入上快速失败,而不是随后在隐藏在调用堆栈下几层的if语句上失败。 如果需要保护任何不变式,您就会知道将它们放在哪里以及在哪里寻找它们。

到目前为止一切顺利,但是如果Account是一个实体怎么办? 好吧,您只需实现一个属性转换器:

public class AccountIdConverter implements AttributeConverter<AccountId, String> {@Overridepublic String convertToDatabaseColumn(AccountId accountId) {return accountId.asString();}@Overridepublic AccountId convertToEntityAttribute(String accountId) {return AccountId.of(accountId);}
}

然后,您可以通过直接在转换器实现上设置的@Converter(autoApply = true)或在实体字段上设置的@Convert(converter = AccountIdConverter.class)启用@Convert(converter = AccountIdConverter.class)

当然,并非所有事物都围绕数据库旋转,幸运的是,在提到的项目中应用的许多不太好的设计决策中,也有很多好的决策。 如此好的决定之一就是标准化用于进程外通信的数据格式。 在上述情况下,它是JSON,因此我需要使JSON有效负载不受执行的重构的影响。 最简单的方法(如果使用Jackson的话)是在实现中添加几个Jackson注释:

public class AccountId {@JsonCreatorpublic static AccountId of(@JsonProperty("accountId") String accountId) {return new AccountId(accountId);}@JsonValuepublic String asString() {return accountId;}// rest omitted for brevity
}

我从最简单的解决方案开始。 这不是理想的,但已经足够好了,那时我们还有更多重要的问题要处理。 在不到3小时的时间里,就完成了JSON序列化和数据库类型转换的工作,我已经将前两个服务从字符串类型的标识符移到了基于值对象的服务中,这些值是系统中最常用的标识符。 花了很长时间有两个原因。

第一个很明显:在此过程中,我必须检查是否无法使用null值(以及是否可以明确声明该值)。 没有这个,整个重构将仅仅是代码完善的练习。

第二个是我几乎想念的东西–您还记得从外部看不到更改的要求吗? 在将帐户ID转换为值对象后,草签定义也发生了变化,现在帐户ID不再是字符串而是对象。 这也很容易修复,只需要指定摇摇欲坠的模型替换即可。 对于swagger-maven-plugin,您需要做的只是将其包含模型替换映射的文件提供给它 :

com.example.AccountId: java.lang.String

重构的结果是否有明显的改善? 并非如此,但是您可以通过进行许多小的改进来改善很多。 尽管如此,这并不是一个小小的改进,它使代码更加清晰,并使进一步的改进变得更加容易。 值得付出努力–我肯定会说:是的。 一个很好的指标是其他团队也采用了这种方法。

快速完成一些冲刺,解决了一些更重要的问题,并开始将继承的,缠结得很乱的混乱变成一个基于六角形体系结构的更好的解决方案,现在是时候应对采用最简单方法进行支持的缺点了JSON序列化。 我们需要做的是将AccountId域对象与与该域无关的事物分离。 也就是说,我们必须移出定义如何序列化此值对象并删除耦合到Jackson的域的部分。 为了实现这一点,我们创建了处理AccountId序列化的Jackson模块:

class AccountIdSerializer extends StdSerializer<AccountId> {AccountIdSerializer() {super(AccountId.class);}@Overridepublic void serialize(AccountId accountId, JsonGenerator generator, SerializerProvider provider) throws IOException {generator.writeString(accountId.asString());}
}class AccountIdDeserializer extends StdDeserializer<AccountId> {AccountIdDeserializer() {super(AccountId.class);}@Overridepublic AccountId deserialize(JsonParser json, DeserializationContext cxt) throws IOException {String accountId = json.readValueAs(String.class);return AccountId.of(accountId);}
}class AccountIdSerializationModule extends Module {@Overridepublic void setupModule(SetupContext setupContext) {setupContext.addSerializers(createSerializers());setupContext.addDeserializers(createDeserializers());}private Serializers createSerializers() {SimpleSerializers serializers = new SimpleSerializers();serializers.addSerializer(new AccountIdSerializer());return serializers;}private Deserializers createDeserializers() {SimpleDeserializers deserializers = new SimpleDeserializers();deserializers.addDeserializer(AccountId.class, new AccountIdDeserializer());return deserializers;}// rest omitted for brevity
}

如果您正在使用Spring Boot进行配置,则只需在应用程序上下文中注册该模块即可:

@Configuration
class JacksonConfig {@BeanModule accountIdSerializationModule() {return new AccountIdSerializationModule();}
}

实现自定义序列化器也是我们所需要的,因为在所有改进中,我们发现了更多的价值对象,其中一些对象更加复杂-但这是另一篇文章。

翻译自: https://www.javacodegeeks.com/2018/01/refactoring-stringly-typed-systems.html

767 重构字符串

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

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

相关文章

fanuc roboguide_FANUC机器人虚拟仿真教程:Roboguide弧焊仿真工作站工装添加

Roboguide软件左侧浏览树中的“Fixture”节点专门用于添加工装资源&#xff0c;软件支持添加自带的库文件数模以及外部导入的CAD数模。软件支持CSB、DXF、STL、IGES、3DS等格式数模文件&#xff0c;一般情况下将将外部数模转换为IGES或CSB格式后进行导入效果会更好一些。本期&a…

如何在 Ubuntu 14.04 和 12.04 上测试 systemd

本来&#xff0c;Ubuntu决定从Ubuntu 16.04 LTS开始使用systemd来替换当前的引导过程。Ubuntu 16.04预计在2016年4月发布&#xff0c;但是考虑到systemd的流行和需求&#xff0c;刚刚发布的Ubuntu 15.04采用它作为默认引导程序。另外&#xff0c;Ubuntu 14.04 Trusty Tahr和Ubu…

oracle怎么读取表空间文件大小,oracle获取表空间文件大小

oracle通过查询DBA_SEGMENTS表获取表空间文件信息&#xff0c;包括表空间名称&#xff0c;大小等 ? 1 select tablespace_name,sum(max_size),sum(bytes) from dba_segments group by tablespace_name; max_size和bytes的单位是字节。将上面的sql语句放到存储过程中执行&#…

java 枚举内嵌枚举_高度有用的Java ChronoUnit枚举

java 枚举内嵌枚举几年前&#xff0c;我发表了博客文章“ The Highly有用的Java TimeUnit Enum ”&#xff0c;其中介绍了JDK 5引入的TimeUnit枚举。 JDK 8引入了一个更新的枚举ChronoUnit &#xff0c;它比TimeUnit更适合于并发以外的上下文&#xff08;例如日期/时间操作&…

Chrome for Mac OS 的 快捷键

文章目录页面滚动其它页面滚动 快捷键功能说明Command ↑滚动到网页的顶部Command ↓滚动到网页的底部 其它 快捷键功能说明Shift Command [ 或 Shift Command ]左右切换标签页&#xff0c;到最后一个会重新循环Option Command ← 或 Option Command →左右切换标签页…

oracle分页包,Oracle分页获取数据的实现 (包和存储过程)

本文档主要是介绍针对oracle数据库中利用包和存储过程来实现分页获取数据的内容。其实该存储过程的主题思想及主要实现我也是从网上找的&#xff0c;自己做了整合。包括包头声明和包体实现部分以及C#代码具体调用部分&#xff0c;共大家参考并提出改进意见。本包中包括两个分页…

python-opencv 最快的遍历颜色空间_居住空间设计的七种设计风格

生活中每家每户的家居样式都是不一样的&#xff0c;它们都是各有各的风格。在居住空间设计中&#xff0c;设计师会根据居住者的风格喜好来定制相应的设计方案&#xff0c;以及融入设计师的风格特点来创造出独特的设计风格&#xff0c;那么让我看看都有哪些设计风格吧。传统风格…

java更好的语言_五个使Java变得更好的功能

java更好的语言我偶然发现了Brian Goetz 提出的有关Java数据类的建议 &#xff0c;立即意识到我也对如何使Java更好地成为一种语言有一些想法。 我实际上有很多&#xff0c;但这只是五个最重要的简短列表。 专制&#xff08;2006&#xff09;&#xff0c;迈克贾奇&#xff08;…

Linux 初始化系统(系统服务管理和控制程序/Init System) -- System V init(SysV init) 的简单理解

文章目录SysV init 简介SysV init 的缺点运行级别主要文件和目录的截图SysV init 简介 System V init&#xff08;缩写 SysV init&#xff09;是类 Unix 操作系统中传统的也是首款初始化系统。 服务配置文件&#xff08;bash 脚本文件&#xff09;存放在目录 /etc/init.d 下 …

oracle查询file_name,Oracle DG环境下db_file_name_convert的实际意义

关于DG环境下备库数据文件重命名的问题&#xff1a;**前言:**主要想表明DG环境下备库数据文件重命名的问题&#xff0c;以及db_file_name_convert与log_file_name_convert的作用。**实验证明&#xff1a;**主库为备库备份一份控制文件RMAN> backup current controlfile for …

python收集数据程序_用一行Python代码进行数据收集探索!Python真牛逼!

简易的Pandas之路 任何使用P ython数据的人都会熟悉Pandas包。P andas是大多数行和列格式数据的go-to包。 如果你没有Pandas&#xff0c;请确保在终端中使用pip install安装&#xff1a; pip install pandas 现在&#xff0c;让我们看看Pandas包中的默认方法可以做些什么&#…

java使用缓冲区读取文件_在Java中使用Google的协议缓冲区

java使用缓冲区读取文件最近发布了 有效的Java第三版 &#xff0c;我一直对确定此类Java开发书籍的更新感兴趣&#xff0c;该书籍的最新版本仅通过Java 6进行了介绍 。 在此版本中&#xff0c;显然存在与Java 7 &#xff0c; Java 8和Java 9密切相关的全新项目&#xff0c;例如…

浅析 Linux 初始化系统(系统服务管理和控制程序/Init System) -- sysvinit/systemvinit(System V init)

文章目录从 sysvinit 到 systemd什么是 init 系统,init 系统的历史和现状sysvinit 概况运行级别sysvinit 运行顺序sysvinit 和系统关闭sysvinit 的管理和控制功能haltinitkillall5lastlastbmesgpidofpoweroffrebootrunlevelshutdownsulogintelinitutmpdumpwallsysvinit 的小结从…

php /usr/lib/libjpeg.so.62,linux PHP的装配

linux PHP的安装2. 红帽Linux PHP2.1. 安装linux PHP查看系统是否自带了php&#xff0c;若自带先卸载&#xff0c;否则安装后会出错。(1)下载php源文件php-5.3.6.tar.gz&#xff0c;地址为http://cn2.php.net/downloads.php(2)执行如下命令&#xff0c;解压源文件&#xff0c;以…

linux多用户运行同一程序_linux系统中CentOS有哪些优势,让它长盛不衰?

CentOS是目前评价和口碑都不错的linux系统&#xff0c;甚至很多公司安装的都是CentOS系统&#xff0c;对于初入门的小白可能不了解这个系统&#xff0c;今天我们就整理一下CentOS系统的七大优势。1. 开源、免费众所周知&#xff0c;不管是微软的 Windows 还是苹果的 macOS&…

代码注释掉还能执行_日志消息是可执行代码和注释

代码注释掉还能执行尽管在一个人的代码中应添加多少注释之间存在意见分歧&#xff0c;但我认为可以肯定地说&#xff0c;大多数开发人员都同意以下代码段中的注释是多余的&#xff1a; // increment the total total;在该示例中&#xff0c;代码很简单&#xff0c;而且实际上是…

浅析 Linux 初始化系统(系统服务管理和控制程序/init system) -- UpStart

文章目录一、Upstart 简介&#xff08;一&#xff09;开发 Upstart 的缘由&#xff08;二&#xff09;Upstart 的特点二、Upstart 概念和术语&#xff08;一&#xff09;Job&#xff08;二&#xff09;Job 生命周期&#xff08;三&#xff09;事件 Event1.Signals2.Methods3.Ho…

php如何在类的外部修改成员属性,php中如何在外部修改类的私有或受保护属性值...

php中怎么在外部修改类的私有或受保护属性值在做单元测试框架时&#xff0c;发现了个比较郁闷的问题&#xff1a;测试人员需要在类外修改类的private或protected成员变量的值&#xff0c;而这些变量没有抽象成public的属性&#xff0c;同时为了代码量的问题&#xff0c;也不可能…

telnet到设备里 php_金融行业思科设备典型网络故障案例:76系列典型案例(一)...

一、Cisco 7606主备引擎自动切换01故障现象某行上联路由器cisco7606 x月x日引擎自行切换&#xff0c;这种情况是第一次发生&#xff0c;至今仍是备引擎是active&#xff0c;主引擎是standby。02分析过程思科TAC提取了log日志和crashinfo信息&#xff1a;Previous engine detect…

白盒测试方法静态分析_静态分析的教育方面

白盒测试方法静态分析加入我们项目的新程序员经常问我们是否有自动格式化工具&#xff0c;以使Java代码看起来完全像Qulice期望的那样。 &#xff08;Quili是我们使用的静态分析器。&#xff09;我总是回答说&#xff0c;拥有这样一个自动代码抛光器只会有害&#xff0c;并且不…