从战中清理代码

从战中清除代码–验证

让我们直接从一个例子开始。 考虑一个简单的Web服务,该服务允许客户向商店下订单。 订单控制器的非常简化的版本可能如下所示–

@RestController
@RequestMapping(value = "/",consumes = MediaType.APPLICATION_JSON_VALUE,produces = MediaType.APPLICATION_JSON_VALUE)
public class OrderController {private final OrderService orderService;public OrderController(OrderService orderService) {this.orderService = orderService;}@PostMappingpublic void doSomething(@Valid @RequestBody OrderDTO order) {orderService.createOrder(order);}
}

和相应的DTO类

@Getter
@Setter
@ToString
public class OrderDTO {@NotNullprivate String customerId;@NotNull@Size(min = 1)private List<OrderItem> orderItems;@Getter@Setter@ToStringpublic static class OrderItem {private String menuId;private String description;private String price;private Integer quantity;}
}

从此DTO创建订单的最常见方法是将其传递给服务,根据需要对其进行验证,然后将其保存在数据库中

@Service
@Slf4j
class OrderService {private final MenuRepository menuRepository;OrderService(MenuRepository menuRepository) {this.menuRepository = menuRepository;}void createOrder(OrderDTO orderDTO) {orderDTO.getOrderItems().forEach(this::validate);log.info("Order {} saved", orderDTO);}private void validate(OrderItem orderItem) {String menuId = orderItem.getMenuId();if (menuId == null || menuId.trim().isEmpty()) {throw new IllegalArgumentException("A menu item must be specified.");}if (!menuRepository.menuExists(menuId.trim())) {throw new IllegalArgumentException("Given menu " + menuId + " does not exist.");}String description = orderItem.getDescription();if (description == null || description.trim().isEmpty()) {throw new IllegalArgumentException("Item description should be provided");}String price = orderItem.getPrice();if (price == null || price.trim().isEmpty()) {throw new IllegalArgumentException("Price cannot be empty.");}try {new BigDecimal(price);} catch (NumberFormatException ex) {throw new IllegalArgumentException("Given price is not in valid format", ex);}if (orderItem.getQuantity() == null) {throw new IllegalArgumentException("Quantity must be given");}if (orderItem.getQuantity() <= 0) {throw new IllegalArgumentException("Given quantity "+ orderItem.getQuantity()+ " is not valid.");}}
}

validate方法写得不好。 很难测试。 将来引入新的验证规则也很困难,因此删除/修改任何现有的验证规则也很困难。 从我的经验中,我看到大多数人通常在集成测试类中针对这种类型的验证检查编写一些通用断言,仅涉及一个或两个(或更多,但不是全部)验证规则。 因此,将来只能在“ 编辑”和“祈祷”模式下进行重构。

如果使用多态来替换这些条件,我们可以改善代码结构。 我们创建一个通用的超级类型来表示一个验证规则

public interface OrderItemValidator {void validate(OrderItem orderItem);
}

下一步是创建验证规则实现,该实现将集中于DTO的单独验证区域。 让我们从菜单验证器开始

public class MenuValidator implements OrderItemValidator {private final MenuRepository menuRepository;public MenuValidator(MenuRepository menuRepository) {this.menuRepository = menuRepository;}@Overridepublic void validate(OrderItem orderItem) {String menuId = Optional.ofNullable(orderItem.getMenuId()).map(String::trim).filter(id -> !id.isEmpty()).orElseThrow(() -> new IllegalArgumentException("A menu item must be specified."));if (!menuRepository.menuExists(menuId)) {throw new IllegalArgumentException("Given menu [" + menuId + "] does not exist.");}}
}

然后商品说明验证器

public class ItemDescriptionValidator implements OrderItemValidator {@Overridepublic void validate(OrderItem orderItem) {Optional.ofNullable(orderItem).map(OrderItem::getDescription).map(String::trim).filter(description -> !description.isEmpty()).orElseThrow(() -> new IllegalArgumentException("Item description should be provided"));}
}

价格验证器

public class PriceValidator implements OrderItemValidator {@Overridepublic void validate(OrderItem orderItem) {String price = Optional.ofNullable(orderItem).map(OrderItem::getPrice).map(String::trim).filter(itemPrice -> !itemPrice.isEmpty()).orElseThrow(() -> new IllegalArgumentException("Price cannot be empty."));try {new BigDecimal(price);} catch (NumberFormatException ex) {throw new IllegalArgumentException("Given price [" + price + "] is not in valid format", ex);}}
}

最后,数量验证器

public class QuantityValidator implements OrderItemValidator {@Overridepublic void validate(OrderItem orderItem) {Integer quantity = Optional.ofNullable(orderItem).map(OrderItem::getQuantity).orElseThrow(() -> new IllegalArgumentException("Quantity must be given"));if (quantity <= 0) {throw new IllegalArgumentException("Given quantity " + quantity + " is not valid.");}}
}

现在,可以彼此独立地轻松地测试每个验证器实现。 关于它们每个的推理也变得更加容易。 将来的添加/修改/删除也是如此。

现在是接线部分。 我们如何将这些验证器与订单服务集成在一起?

一种方法是直接在OrderService构造函数中创建一个列表,并使用验证程序填充它。 或者我们可以使用Spring将List注入OrderService

@Service
@Slf4j
class OrderService {private final List<OrderItemValidator> validators;OrderService(List<OrderItemValidator> validators) {this.validators = validators;}void createOrder(OrderDTO orderDTO) {orderDTO.getOrderItems().forEach(this::validate);log.info("Order {} saved", orderDTO);}private void validate(OrderItem orderItem) {validators.forEach(validator -> validator.validate(orderItem));}
}

为了使它起作用,我们将必须将每个验证器实现声明为Spring Bean。

我们可以进一步改进抽象。 OrderService现在正在接受验证者列表。 但是,我们可以将其更改为仅了解OrderItemValidator类型,而无需其他任何更改。 这为我们提供了将来注入单个验证器或验证器的任何组合的灵活性。

因此,现在我们的目标是更改订单服务,以与单个验证器相同的方式来处理订单项验证器的组成。 有一个著名的设计模式称为
Composite让我们可以做到这一点。

让我们为验证器接口创建一个新的实现,它将是复合的

class OrderItemValidatorComposite implements OrderItemValidator {private final List<OrderItemValidator> validators;OrderItemValidatorComposite(List<OrderItemValidator> validators) {this.validators = validators;}@Overridepublic void validate(OrderItem orderItem) {validators.forEach(validators -> validators.validate(orderItem));}
}

然后,我们创建一个新的Spring配置类,该类将实例化并初始化此组合,然后将其公开为bean

@Configuration
class ValidatorConfiguration {@BeanOrderItemValidator orderItemValidator(MenuRepository menuRepository) {return new OrderItemValidatorComposite(Arrays.asList(new MenuValidator(menuRepository),new ItemDescriptionValidator(),new PriceValidator(),new QuantityValidator()));}
}

然后,我们通过以下方式更改OrderService类

@Service
@Slf4j
class OrderService {private final OrderItemValidator validator;OrderService(OrderItemValidator orderItemValidator) {this.validator = orderItemValidator;}void createOrder(OrderDTO orderDTO) {orderDTO.getOrderItems().forEach(validator::validate);log.info("Order {} saved", orderDTO);}
}

我们完成了!

这种方法的好处很多。 整个验证逻辑已完全从订购服务中抽象出来。 测试更容易。 将来的维护更加容易。 客户只知道一种验证器类型,而没有其他信息。

但是,上述所有方法也都存在一些问题。 有时人们对此设计不满意。 他们可能觉得这太抽象了,或者对于将来的维护他们将不需要太多的灵活性或可测试性。 我建议根据团队文化采用这种方法。 毕竟,在软件开发中没有唯一正确的方法。

请注意,为了本文的方便,我在这里也做了一些简化。 其中包括验证失败时引发通用IllegalArgumentException。 您可能希望生产级应用程序中有一个更特定/自定义的异常,以在不同情况之间进行标识。 十进制分析也很幼稚地完成,您可能要修复特定的格式,然后使用DecimalFormat对其进行解析。

完整的代码已上传到Github 。

翻译自: https://www.javacodegeeks.com/2017/05/clean-code-trenches.html

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

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

相关文章

python opencv屏幕找图_使用Python+OpenCV进行图像模板匹配(Match Template)实例-找到百度首页按钮并点击...

意图&#xff1a;准备一张小图&#xff0c;在电脑屏幕上找到小图坐标&#xff0c;并点击。1 安装 opencv 和 numpy&#xff1a;pip3 install opencv-python上述命令将 opencv 和 numpy都安装了&#xff0c;可以在类似D:\Python36\Lib\site-packages目录下看到2 准备小图&…

Eigen(6)快操作

1. 块操作 块是matrix或array中的矩形子部分。 2. 使用块 函数.block()&#xff0c;有两种形式 operation 构建一个动态尺寸的block 构建一个固定尺寸的block 起点(i,j)块大小(p,q) .block(i,j,p,q) .block< p,q >(i,j) Eigen中&#xff0c;索引从0开始。 两个版本…

11.【原创】chrom文件上传后,手动释放内存

最近在用google chrom测试大文件上传功能&#xff0c;上传的文件为4GB左右的。但是试了几次之后突然发现&#xff0c;我C盘的内存由原先的剩余的30多GB变为了15GB左右&#xff0c;猜想是chrom文件上传之后并没有把读取出的文件进行清理。网上找了很久也没有找到类似解决方法。于…

mapreduce排序算法_MapReduce算法–二级排序

mapreduce排序算法我们将继续执行有关实现MapReduce算法的系列文章&#xff0c;该系列可在使用MapReduce进行数据密集型文本处理中找到。 本系列的其他文章&#xff1a; 使用MapReduce进行数据密集型文本处理 使用MapReduce进行数据密集型文本处理-本地聚合第二部分 使用Had…

数据图表与分析图_几种可视化数据分析图表的使用

图表简洁大方、一目了然&#xff0c;利用图表工具就能轻松实现&#xff0c;是数据分析中常采用的方式。今天利用在雀书无代码平台搭建的图表来介绍几种常见数据分析图表的使用。1. 柱状图柱状图可以显示一段时间内的数据变化或显示各项之间的比较情况&#xff0c;主要使用颜色进…

java打包exe

配置如下&#xff1a; 1&#xff0c;复制运行环境jre&#xff1b; 2&#xff0c;复制项目配置文件config; 3&#xff0c;复制项目运行的lib包&#xff1b; 4&#xff0c;编写清单文件&#xff0c;放到系统目录&#xff1b; 5&#xff0c;将项目打包成xxx.jar&#xff0c;放入到…

Eigen(5)Array类和元素级操作

0. 为什么使用Array 相对于Matrix提供的线性代数运算&#xff0c;Array类提供了更为一般的数组功能。Array类为元素级的操作提供了有效途径&#xff0c;比如点加&#xff08;每个元素加值&#xff09;或两个数据相应元素的点乘。 1. Array Array是个类模板&#xff08;类似于M…

装饰信封

有时 很多时候&#xff0c;我需要一个类实现通过使其他类的实例的接口。 听起来很奇怪&#xff1f; 让我给你看一个例子。 在Takes框架中有很多此类&#xff0c;它们的名称都都类似于*Wrap 。 这是一个方便的设计概念&#xff0c;不幸的是&#xff0c;在Java中看起来相当冗长。…

进制转换c语言代码_奇怪的C语言代码,有些函数在变量前加上(void)是什么类型转换?...

C语言的语法极其简洁&#xff0c;即使是初次接触编程语言的初学者也能很快学完它的语法。不过&#xff0c;C语言也是一门“灵活得过了头”的编程语言&#xff0c;对于很多初学者来说&#xff0c;编写C语言程序就好像拿着一堆最基本的砖块&#xff0c;要修建一座大厦一样&#x…

Eigen(4)矩阵基本运算

矩阵和向量的运算 提供一些概述和细节&#xff1a;关于矩阵、向量以及标量的运算。 1. 介绍 Eigen提供了matrix/vector的运算操作&#xff0c;既包括重载了c的算术运算符/-/*&#xff0c;也引入了一些特殊的运算比如点乘dot、叉乘cross等。 对于Matrix类&#xff08;matrix和v…

beta冲刺(1/7)

作业格式 课程名称&#xff1a;软件工程1916|W&#xff08;福州大学&#xff09;作业要求&#xff1a;项目beta冲刺&#xff08;团队&#xff09;团队名称&#xff1a; 那周余嘉熊掌将得队作业目标&#xff1a;beta&#xff08;1/7&#xff09;队员学号队员姓名博客地址备注221…

qq纵横四海源码_【0基础】纵横中文网python爬虫实战

原文在此~【0基础】纵横中文网python爬虫实战​mp.weixin.qq.com大家好&#xff0c;我是你们的机房老哥&#xff01;在粉丝群的日常交流中&#xff0c;爬虫是比较常见的话题。python最强大的功能之一也是爬虫。考虑到很多0基础的小白想要入门爬虫。老哥今天就通过一个比较简单的…

Eigen(3)矩阵Matrix及其简单操作

1. Matrix类 在Eigen&#xff0c;所有的矩阵和向量都是Matrix模板类的对象&#xff0c;Vector只是一种特殊的矩阵&#xff08;一行或者一列&#xff09;。 Matrix有6个模板参数&#xff0c;主要使用前三个参数&#xff0c;剩下的有默认值。 Matrix<typename Scalar, int Ro…

java jquery_jQuery数据表和Java集成

java jqueryjQuery DataTables是一个开放源代码插件&#xff0c;用于在浏览器中创建表。 它具有许多功能&#xff0c;例如排序&#xff0c;服务器端处理&#xff0c; JQUERY UI主题滚动。 该插件的下载链接&#xff1a; http://www.datatables.net/download/ 在本演示中&am…

Eigen(2) 模块与头文件

Eigen库被分为一个Core模块和其他一些模块&#xff0c;每个模块有一些相应的头文件。 为了便于引用&#xff0c;Dense模块整合了一系列模块&#xff1b;Eigen模块整合了所有模块。一般情况下&#xff0c;include<Eigen/Dense> 就够了

白鹭龙骨异步加载

private armatureDisplay: dragonBones.EgretArmatureDisplay; /** * 加载龙骨动画 * Create scene interface */ private loadGragon(): void { let t this; t.removeGragon(); //默认男战士 let sex t.occupationSex.selectedValue ? t.occupationSex.selectedValue : 1; …

zip和unzip上的Java要点

压缩是编写文件时可以在我们的代码中发出的主要动作之一。 因此&#xff0c;我发现在zip和unzip上必不可少的简单Java代码段&#xff0c;并且必须易于访问。 要点是纯Java语言&#xff0c;并以zip格式存储两个文件。 完成后&#xff0c;打开打开的拉链并评估其内容。 import …

ppt图片丢失_041 职场PPT实战:做好的PPT换个电脑就丢字体?三招解决!

大家好&#xff0c;我是肥宅。这是《职场PPT实战》系列更文的第41篇。本期我要跟大家聊一个很实在的问题&#xff0c;字体嵌入。有小伙伴在后台提问&#xff0c;他是这么说的&#xff1a;“肥宅老师&#xff0c;我做好的PPT&#xff0c;换个电脑打开字体就变了&#xff0c;有没…

matlab sort对矩阵某一维进行排序并记录之前索引

sort函数默认升序排列&#xff0c;即‘ascend’&#xff1b; [B, index] sort(A&#xff0c;dim&#xff0c; model) B为排序后矩阵 index记录排序前的索引号 A需要进行排序操作的矩阵 dim对矩阵的维度&#xff08;一维不用指定&#xff0c;二维默认对列&#xff09; mode…

2018-2019-2 20165221 【网络对抗技术】-- Exp9 Web安全基础

2018-2019-2 20165221 【网络对抗技术】-- Exp9 Web安全基础 目录&#xff1a; 一 . 实验要求二 . 实验过程记录 1 . Webgoat安装2 . SQL注入的攻击 命令注入&#xff08;Command Injection&#xff09;数字型注入&#xff08;Numeric SQL Injection&#xff09;日志欺骗&#…