在Java中进行输入验证时用错误通知替换异常

在我以前的文章中,我写了一个输入验证设计,该设计取代了难以维护和测试的 if-else块。 但是,正如某些读者指出的那样,它有一个缺点–如果输入数据有多个验证错误,则用户将不得不多次提交请求以查找所有错误。 从可用性的角度来看,这不是一个好的设计。

当我们发现验证错误时,抛出异常的另一种方法是返回一个包含错误的Notification对象。 这将使我们能够在用户输入上运行所有验证规则,并同时捕获所有违规行为。 Martin Fowler 撰写了一篇文章,详细介绍了该方法。 我强烈建议您继续阅读,如果您还没有读过的话。

在本文中,我将重构以前的实现,以使用错误通知对象来验证用户输入。

第一步,我将创建一个ErrorNotification对象,该对象封装了我的应用程序错误-

public class ErrorNotification {private List<String> errors = new ArrayList<>();public void addError(String message) {this.errors.add(message);}public boolean hasError() {return !this.errors.isEmpty();}public String getAllErrors() {return this.errors.stream().collect(joining(", "));}
}

然后,我将更改OrderItemValidator接口以返回ErrorNotification对象–

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

然后更改所有实现以适应新的返回类型。

最初,我将更改所有实现以返回一个空的错误对象,以便摆脱编译错误。 例如,我将通过以下方式更改ItemDescriptionValidator –

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

修复编译错误之后,我现在将开始用每个验证器中的通知消息替换异常。 为此,我将首先修改相关测试以反映我的意图,然后修改验证器以通过测试。

让我们从ItemDescriptionValidatorTest类开始–

public class ItemDescriptionValidatorTest {@Testpublic void validate_descriptionIsNull_invalid() {ItemDescriptionValidator validator = new ItemDescriptionValidator();ErrorNotification errorNotification = validator.validate(new OrderItem());assertThat(errorNotification.getAllErrors()).isEqualTo("Item description should be provided");}@Testpublic void validate_descriptionIsBlank_invalid() {OrderItem orderItem = new OrderItem();orderItem.setDescription("     ");ItemDescriptionValidator validator = new ItemDescriptionValidator();ErrorNotification errorNotification = validator.validate(new OrderItem());assertThat(errorNotification.getAllErrors()).isEqualTo("Item description should be provided");}@Testpublic void validate_descriptionGiven_valid() {OrderItem orderItem = new OrderItem();orderItem.setDescription("dummy description");ItemDescriptionValidator validator = new ItemDescriptionValidator();ErrorNotification errorNotification = validator.validate(orderItem);assertThat(errorNotification.getAllErrors()).isEmpty();}
}

当我运行这些测试时,只有其中一项通过,而其中两项失败,这是预料之中的。 我现在将修改验证器代码以通过测试–

class ItemDescriptionValidator implements OrderItemValidator {static final String MISSING_ITEM_DESCRIPTION = "Item description should be provided";@Overridepublic ErrorNotification validate(OrderItem orderItem) {ErrorNotification errorNotification = new ErrorNotification();Optional.ofNullable(orderItem).map(OrderItem::getDescription).map(String::trim).filter(description -> !description.isEmpty()).ifPresentOrElse(description -> {},() -> errorNotification.addError(MISSING_ITEM_DESCRIPTION));return errorNotification;}
}

我对上面的ifPresentOrElse方法的使用感到不舒服。 我在这里使用它的主要原因是因为Optionals没有ifNotPresent方法之类的东西,该方法允许我仅在不存在该值的情况下才采取措施(请向读者提出要求-如果您知道一种更好的方法,为此,请发表评论!)。

进行此重构后, ItemValidatorTest类中的所有测试均通过测试。 大!

现在,让我们重构MenuValidatorTest类中的测试–

public class MenuValidatorTest {@Testpublic void validate_menuIdInvalid_invalid() {OrderItem orderItem = new OrderItem();String menuId = "some menu id";orderItem.setMenuId(menuId);MenuRepository menuRepository = mock(MenuRepository.class);when(menuRepository.menuExists(any())).thenReturn(false);MenuValidator validator = new MenuValidator(menuRepository);ErrorNotification errorNotification = validator.validate(orderItem);assertThat(errorNotification.getAllErrors()).isEqualTo(String.format(MenuValidator.INVALID_MENU_ERROR_FORMAT, menuId));}@Testpublic void validate_menuIdNull_invalid() {MenuRepository menuRepository = mock(MenuRepository.class);when(menuRepository.menuExists(any())).thenReturn(true);MenuValidator validator = new MenuValidator(menuRepository);ErrorNotification errorNotification = validator.validate(new OrderItem());assertThat(errorNotification.getAllErrors()).isEqualTo(MenuValidator.MISSING_MENU_ERROR);}@Testpublic void validate_menuIdIsBlank_invalid() {OrderItem orderItem = new OrderItem();orderItem.setMenuId("       \t");MenuRepository menuRepository = mock(MenuRepository.class);when(menuRepository.menuExists(any())).thenReturn(true);MenuValidator validator = new MenuValidator(menuRepository);ErrorNotification errorNotification = validator.validate(orderItem);assertThat(errorNotification.getAllErrors()).isEqualTo(MenuValidator.MISSING_MENU_ERROR);}@Testpublic void validate_menuIdValid_validated() {OrderItem orderItem = new OrderItem();String menuId = "some menu id";orderItem.setMenuId(menuId);MenuRepository menuRepository = mock(MenuRepository.class);when(menuRepository.menuExists(menuId)).thenReturn(true);MenuValidator validator = new MenuValidator(menuRepository);ErrorNotification errorNotification = validator.validate(orderItem);assertThat(errorNotification.getAllErrors()).isEmpty();}
}

然后是MenuValidator类–

@RequiredArgsConstructor
class MenuValidator implements OrderItemValidator {private final MenuRepository menuRepository;static final String MISSING_MENU_ERROR = "A menu item must be specified.";static final String INVALID_MENU_ERROR_FORMAT = "Given menu [%s] does not exist.";@Overridepublic ErrorNotification validate(OrderItem orderItem) {ErrorNotification errorNotification = new ErrorNotification();Optional.ofNullable(orderItem.getMenuId()).map(String::trim).filter(menuId -> !menuId.isEmpty()).ifPresentOrElse(validateMenuExists(errorNotification),() -> errorNotification.addError(MISSING_MENU_ERROR));return errorNotification;}private Consumer<String> validateMenuExists(ErrorNotification errorNotification) {return menuId -> {if (!menuRepository.menuExists(menuId)) {errorNotification.addError(String.format(INVALID_MENU_ERROR_FORMAT, menuId));}};}
}

等等。

修改了各个验证器之后,我现在将修改Composite以收集单个订单商品的所有错误–

@RequiredArgsConstructor
class OrderItemValidatorComposite implements OrderItemValidator {private final List<OrderItemValidator> validators;@Overridepublic ErrorNotification validate(OrderItem orderItem) {ErrorNotification errorNotification = new ErrorNotification();validators.stream().map(validator -> validator.validate(orderItem)).forEach(errorNotification::addAll);return errorNotification;}
}

为此,我在ErrorNotification类中添加了一个名为addAll的新方法,该方法基本上从另一个ErrorNotification对象复制所有错误。

最后,我现在将修改服务方法以收集订单中所有订单项的所有错误消息–

@Service
@Slf4j
@RequiredArgsConstructor
class OrderService {private final OrderItemValidator validator;void createOrder(OrderDTO orderDTO) {ErrorNotification errorNotification = new ErrorNotification();orderDTO.getOrderItems().stream().map(validator::validate).forEach(errorNotification::addAll);if (errorNotification.hasError()) {throw new IllegalArgumentException(errorNotification.getAllErrors());}log.info("Order {} saved", orderDTO);}
}

进行此更改会导致OrderServiceIT中的测试之一失败,因为当价格无效时,它专门查找原因设置为NumberFormatException的异常。 重构后,我们可以安全地删除此检查,因为它不再相关。

本文的完整源代码已推送到GitHub (特定的提交URL在此处 )。

翻译自: https://www.javacodegeeks.com/2017/11/replacing-exceptions-error-notifications-input-validation-java.html

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

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

相关文章

Java构建子类对象时的顺序

先看一个这么的程序&#xff1a; //------------------------------------------------------------------------// //程序目的&#xff0c;创建一个父类与子类&#xff0c;分别创建构造方法 public abstract class usecase1 {abstract void testAbstract();usecase1(){//&…

关于Java中serialUID的序列化的问题

在我们平时处理JAVA类中的问题时&#xff0c;有时会碰见类似这样的一行代码&#xff1a; private static final long serialVersionUID 1L;这就是程序的序列化ID 所谓序列化&#xff0c;用一句通俗的话来说&#xff0c;就是将对象冻结了放入硬盘的过程&#xff0c;举一个例子…

java 绑定微信号开发_Java开发中的常见危险信号

java 绑定微信号开发在开发&#xff0c;阅读&#xff0c;复审和维护成千上万行Java代码的几年中&#xff0c;我已经习惯于看到Java代码中的某些“ 危险信号 ”&#xff0c;这些信号通常&#xff08;但可能并非总是&#xff09;暗示着代码有问题。 我不是在谈论总是错误的做法&a…

使用绝对布局的方法创建一个登录密码验证的简易窗口

绝对布局是指直接采用坐标指定的方式在容器中创建窗口&#xff0c;这种方式比较灵活 采用这种方法创建布局的方式主要有以下几步&#xff1a; 使用Container.setLayout(null)的方式取消布局管理器使用Compon.setBounds()的方式来设置每个组件的大小 下面先上代码 import ja…

Neo4j:Cypher –属性值只能是原始类型或其数组

本周初&#xff0c;当我尝试在我认为要共享的节点上创建数组属性时&#xff0c;遇到了一个有趣的Cypher错误消息。 这是我写的Cypher查询&#xff1a; CREATE (:Person {id: [1, "mark", 2.0]})导致此错误&#xff1a; Neo.ClientError.Statement.TypeError Prope…

通信工程制图期末考试大纲

通信工程建设的三个阶段 初步设计、技术设计、施工图设计 通信工程建设的参与方 1、建设单位&#xff1a;是建设工程的投资人&#xff0c;也称“业主"。 2、勘察单位&#xff1a;是指已通过建设行政主管部门的资质审查&#xff0c;从事工程测量、水文地质和岩土工程等工…

锁相环锁相原理简洁版

锁相环锁相原理 1.没有参考信号输入的时候&#xff0c;环路滤波器的输出为一个固定值&#xff0c;这是的VCO按照其固定的频率进行震荡 2.当有参考信号输入时&#xff0c;Ur和Uv同时加到鉴相器来鉴相&#xff0c;如果鉴相器fr和fv差别不大的话&#xff0c;这两个电压做差&#…

古诺模型_再见了古诺。 你好Drools工作台。

古诺模型Drools 6.0发生了许多变化。 随着功能和功能的更改&#xff0c;我们对Guvnor github存储库进行了重组&#xff0c;以更好地反映我们的新架构。 历史上&#xff0c;Guvnor一直是Drools的Web应用程序。 它由Drools专用的编辑器&#xff0c;后端存储库和简化的资产管理系统…

八千字复习一下天线与电磁波

1. 空间坐标系 空间直角坐标系&#xff08;常用&#xff09;极坐标系平面直角坐标系距离矢量就是空间向量坐标单位矢量就是给距离矢量xyz底下分别除以模长梯度就是xyz求偏导&#xff0c;记得写 ex⃗\vec{e_{x}}ex​​/ey⃗\vec{e_{y}}ey​​/ez⃗\vec{e_{z}}ez​​,最后方程左…

Apache Fluo:填充Google搜索索引的Percolator的实现

Apache Fluo是Apache Accumulo [3]的Percolator [2]&#xff08;填充Google的搜索索引&#xff09;的开源实现 。 使用Fluo&#xff0c;用户可以将新数据连续地合并到大型现有数据集中&#xff0c;而无需重新处理所有数据。 与批处理和流传输框架不同&#xff0c;Fluo提供了低得…

最通俗的语言讲解卷积码、码树图、状态图以及维特比译码

什么是卷积码&#xff1f; 卷积码是由伊利亚斯发明的一种非分组码&#xff0c;它更加倾向于纠错&#xff0c;对于实际的性能优于分组码&#xff0c;运算较为简单。 将卷积码记为(n,k,N),码率定义为k/n n是n个比特 k是k个信息位 N是N个信息段 卷积码编码器 组成&#xff1a…

通信系统的同步技术归纳

载波同步&#xff1a;在相干解调时&#xff0c;接收端的解调器要产生一个与接收到的载波同频同相的相干载波位同步&#xff1a;接收端产生一个与接收信号符号速率相同&#xff0c;相位与最佳判决时刻一致的定时脉冲序列信号群同步&#xff1a;群同步是指在接收端产生与“字、句…

c语言 在txt文件中搜索关键词_网络推广外包—网络推广外包如何在搜索引擎中体现关键词优化效果...

在当下的互联网时代中&#xff0c;没有企业愿意远离这种流量获取快、用户需求大、宣传推广能力见效快的搜索引擎&#xff0c;况且通过互联网的帮助下&#xff0c;企业网站能够通过搜索引擎进行关键词优化后获取更多的转化率&#xff0c;增加企业交易额度的提升&#xff0c;谁又…

Spring JPA数据+Hibernate+ MySQL + Maven

在Spring MVC的帮助下开发Web应用程序意味着要创建几个逻辑架构层。 层之一是DAO&#xff08;存储库&#xff09;层。 它负责与数据库进行通信。 如果您至少开发了DAO层一次&#xff0c;则应该知道它涉及许多样板代码。 Spring Data本身就是与DAO相关的日常工作的一部分。 在帖…

数字通信系统中的均衡技术

均衡技术是为了消除码间串扰的影响 自适应均衡器 可以自动调整抽头滤波系数的均衡器。包含两个工作模式&#xff0c;训练模式和跟踪模式&#xff1b; 训练模式&#xff1a;发射机发射一个已知的定长的序列&#xff0c;以便接收机处的均衡器可以做出正确的设置跟踪模式&#…

eclipse中birt插件使用_waves教学:如何使用通道条插件在DAW中创建模拟工作流

这篇Waves小文章将帮助你了解通道条插件如何来加快混音工作流程&#xff0c;同时还能在DAW中添加令人愉悦的模拟调音台染色——以及它们如何以前所未有的灵活性适应数字新时代。什么是通道条&#xff1f;第一次看到一台老式模拟调音台时&#xff0c;人们常常因为按键、旋钮和推…

X-Mas Musings –在Grails集成测试中不要使用随机服务器端口

对许多人来说&#xff0c;十二月是反思或思考的时期。 所以我决定在去年的事情和想法&#xff0c;以反映- 每一天 &#xff0c;直到圣诞节。 这是第四天 对于Grails集成测试&#xff0c;了解应用程序当前在哪个端口上运行非常有用。 Spring Boot以及因此建立在它上面的Grails都…

数字通信系统的分集技术

产生原因 无线信道的传输会被多个不同的因素干扰&#xff0c;增加辐射功率会对其他电台产生干扰&#xff0c;采用其他手段来处理数字信号 多径效应 直射波、反射波的传播路径不同&#xff0c;而且无线通信系统中接收端与发送端都是可以移动的&#xff0c;这就是多径效应。 …

主板螺丝是机箱配还是主板配_要配新电脑,A520主板和B450主板哪一个更加值得购买?...

Hello大家好&#xff0c;我是兼容机之家的小牛。上周AMD正式发布了最新的A520芯片组主板&#xff0c;这也补齐了500系列主板的高中低三档配置&#xff0c;现在500系列主板已经拥有X570、B550、A520三个芯片组。那么小牛为什么要拿上一代的B450和最新的A520来做对比呢&#xff1…

javafx 和swing_集成JavaFX和Swing(修订版)

javafx 和swing我刚刚完成了对使用Swing的应用程序组件的重写&#xff0c;现在正在使用JavaFX&#xff0c;最后得到了与更大的swing应用程序集成的JavaFX组件。 这是一个很大的应用程序&#xff0c;重写花了我一段时间&#xff0c;最后一切都很好&#xff0c;我很高兴自己做到了…