日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

从战中清理代码

發布時間:2023/12/3 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 从战中清理代码 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

從戰中清除代碼–驗證

讓我們直接從一個例子開始。 考慮一個簡單的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

總結

以上是生活随笔為你收集整理的从战中清理代码的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。