在Java中进行输入验证时用错误通知替换异常
在我以前的文章中,我寫了一個(gè)輸入驗(yàn)證設(shè)計(jì),該設(shè)計(jì)取代了難以維護(hù)和測(cè)試的 if-else塊。 但是,正如某些讀者指出的那樣,它有一個(gè)缺點(diǎn)–如果輸入數(shù)據(jù)有多個(gè)驗(yàn)證錯(cuò)誤,則用戶將不得不多次提交請(qǐng)求以查找所有錯(cuò)誤。 從可用性的角度來看,這不是一個(gè)好的設(shè)計(jì)。
當(dāng)我們發(fā)現(xiàn)驗(yàn)證錯(cuò)誤時(shí),拋出異常的另一種方法是返回一個(gè)包含錯(cuò)誤的Notification對(duì)象。 這將使我們能夠在用戶輸入上運(yùn)行所有驗(yàn)證規(guī)則,并同時(shí)捕獲所有違規(guī)行為。 Martin Fowler 撰寫了一篇文章,詳細(xì)介紹了該方法。 我強(qiáng)烈建議您繼續(xù)閱讀,如果您還沒有讀過的話。
在本文中,我將重構(gòu)以前的實(shí)現(xiàn),以使用錯(cuò)誤通知對(duì)象來驗(yàn)證用戶輸入。
第一步,我將創(chuàng)建一個(gè)ErrorNotification對(duì)象,該對(duì)象封裝了我的應(yīng)用程序錯(cuò)誤-
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對(duì)象–
public interface OrderItemValidator {ErrorNotification validate(OrderItem orderItem); }然后更改所有實(shí)現(xiàn)以適應(yīng)新的返回類型。
最初,我將更改所有實(shí)現(xiàn)以返回一個(gè)空的錯(cuò)誤對(duì)象,以便擺脫編譯錯(cuò)誤。 例如,我將通過以下方式更改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;} }修復(fù)編譯錯(cuò)誤之后,我現(xiàn)在將開始用每個(gè)驗(yàn)證器中的通知消息替換異常。 為此,我將首先修改相關(guān)測(cè)試以反映我的意圖,然后修改驗(yàn)證器以通過測(cè)試。
讓我們從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();} }當(dāng)我運(yùn)行這些測(cè)試時(shí),只有其中一項(xiàng)通過,而其中兩項(xiàng)失敗,這是預(yù)料之中的。 我現(xiàn)在將修改驗(yàn)證器代碼以通過測(cè)試–
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;} }我對(duì)上面的ifPresentOrElse方法的使用感到不舒服。 我在這里使用它的主要原因是因?yàn)镺ptionals沒有ifNotPresent方法之類的東西,該方法允許我僅在不存在該值的情況下才采取措施(請(qǐng)向讀者提出要求-如果您知道一種更好的方法,為此,請(qǐng)發(fā)表評(píng)論!)。
進(jìn)行此重構(gòu)后, ItemValidatorTest類中的所有測(cè)試均通過測(cè)試。 大!
現(xiàn)在,讓我們重構(gòu)MenuValidatorTest類中的測(cè)試–
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));}};} }等等。
修改了各個(gè)驗(yàn)證器之后,我現(xiàn)在將修改Composite以收集單個(gè)訂單商品的所有錯(cuò)誤–
@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類中添加了一個(gè)名為addAll的新方法,該方法基本上從另一個(gè)ErrorNotification對(duì)象復(fù)制所有錯(cuò)誤。
最后,我現(xiàn)在將修改服務(wù)方法以收集訂單中所有訂單項(xiàng)的所有錯(cuò)誤消息–
@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);} }進(jìn)行此更改會(huì)導(dǎo)致OrderServiceIT中的測(cè)試之一失敗,因?yàn)楫?dāng)價(jià)格無效時(shí),它專門查找原因設(shè)置為NumberFormatException的異常。 重構(gòu)后,我們可以安全地刪除此檢查,因?yàn)樗辉傧嚓P(guān)。
本文的完整源代碼已推送到GitHub (特定的提交URL在此處 )。
翻譯自: https://www.javacodegeeks.com/2017/11/replacing-exceptions-error-notifications-input-validation-java.html
總結(jié)
以上是生活随笔為你收集整理的在Java中进行输入验证时用错误通知替换异常的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 手机如何刷机如何在电脑上刷机
- 下一篇: 《马力欧 vs. 咚奇刚》将于 2024