查手表价格的网站,简洁个人博客网站模板下载,邦利博客网站怎么做的,搜索引擎优化的主要特征从战中清除代码–验证 让我们直接从一个例子开始。 考虑一个简单的Web服务#xff0c;该服务允许客户向商店下订单。 订单控制器的非常简化的版本可能如下所示– RestController
RequestMapping(value /,consumes MediaType.APPLICATION_JSON_VALUE,produces … 从战中清除代码–验证 让我们直接从一个例子开始。 考虑一个简单的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;NotNullSize(min 1)private ListOrderItem orderItems;GetterSetterToStringpublic 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 ListOrderItemValidator validators;OrderService(ListOrderItemValidator 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 ListOrderItemValidator validators;OrderItemValidatorComposite(ListOrderItemValidator 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