Custom Exception

bài Clean Code with Exception mình có nói về cách dùng Exception để giúp code được đẹp hơn nhưng quả thật thiếu sót khi không có một ví dụ hoàn chỉnh cũng như hướng dẫn sử dụng custom Exception để giúp cho Exception rõ nghĩa và cung cấp nhiều thông tin hơn.

Yêu cầu cũng giống như bài Clean Code with Exception. Mình copy qua đây cho tiện khỏi phải xem lại ;))

Viết chương trình tính và chuyển lương cho nhân viên công ty. Từ yêu cầu này cần làm 3 việc sau:

  1. Tính lương cho nhân viên
  2. Chuyển lương tới nhân viên
  3. Điều phối việc tính và chuyển lương

#1 mình đã làm rồi ở bài Clean Code with Exception nhưng nếu xét tổng quát thì #3 cần biết loại lỗi ở #1 và #2. Giả sử #1 cần yêu cần người dùng nhập lại và dừng chương trình, còn #2 thì có thể retry (công ty chuyển lương qua 2 ngân hàng VCB và VTB, VCB lỗi thì thử VTB) ;))

1. Tính lương cho nhân viên

Từ phân tích trên Exception sẽ không thể đáp ứng yêu cầu. Ta cần custom lại Exception

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class SalaryException extends Exception {

  private ErrorType codeType;

  public SalaryException(ErrorType errorType, String message) {
    super(message);
    this.codeType = errorType;
  }

  public ErrorType getCodeType() {
    return codeType;
  }
}

Với enum define các loại lỗi:

1
2
3
4
5
6
public enum ErrorType {
  UNKNOWN_ERROR,
  CALC_SALARY_PARAMS_INVALID,
  TRANSFER_MONEY_PARAMS_INVALID,
  TRANSFER_MONEY_NOT_ENOUGH_MONEY,
}

Từ đây ta sửa lại hàm tính lương một chút:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class SalaryCalculator {

  public int calcSalary(int workingDay, int salaryPerDay) throws SalaryException {

    if (workingDay <= 0)
      throw new SalaryException(CALC_SALARY_PARAMS_INVALID, "WorkingDay less than or equal zero");

    if (salaryPerDay <= 0)
      throw new SalaryException(CALC_SALARY_PARAMS_INVALID, "SalaryPerDay less than or equal zero");

    return workingDay * salaryPerDay;
  }
}

2. Chuyển lương tới nhân viên

Bây giờ ta viết những class hỗ trợ chuyển tiền. Giả sử ta tích hợp với 2 ngân hàng VietcomBank và VietinBank. 2 ngân hàng này sẽ phải implement bởi interface

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public interface Bank {

  void transferMoney(String accountNo, int amount) throws SalaryException;

  default void checkTransferParams(String accountNo, int amount) throws SalaryException {

    if (accountNo == null || accountNo.length() == 0)
      throw new SalaryException(TRANSFER_MONEY_PARAMS_INVALID, "AccountNo null or empty");

    if (amount <= 0)
      throw new SalaryException(TRANSFER_MONEY_PARAMS_INVALID, "Amount less than or equal zero");
  }
}

Mình có implement thêm hành kiểm tra tham số cho việc chuyển tiền qua hàm checkTransferParams

Implement cho VietcomBank

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class VietcomBank implements Bank {

  @Override
  public void transferMoney(String accountNo, int amount) throws SalaryException {
    checkTransferParams(accountNo, amount);

    if (amount > 10_000_000)
      throw new SalaryException(TRANSFER_MONEY_NOT_ENOUGH_MONEY, "VCB only support transfer amount less than 10M");

    System.out.println("VCB transfer success");
  }
}

Để giả lập lỗi ngân hàng. Mình sẽ cho VietcomBank bị lỗi khi số tiền lớn hơn 10M

Tương tự với VietinBank nhưng không lỗi

1
2
3
4
5
6
7
8
9
public class VietinBank implements Bank {

  @Override
  public void transferMoney(String accountNo, int amount) throws SalaryException {
    checkTransferParams(accountNo, amount);

    System.out.println("VTB transfer success");
  }
}

3. Điều phối việc tính và chuyển lương

Và cuối cùng là #3 điều phối việc tính và chuyển lương

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class SalaryTransfer {

  private final MoneyTransfer moneyTransfer = new MoneyTransfer();
  private final SalaryCalculator salaryCalculator = new SalaryCalculator();

  public void salaryTransfer(int workingDay, int salaryPerDay, String accountNo) throws SalaryException {

    int salary = salaryCalculator.calcSalary(workingDay, salaryPerDay);
    moneyTransfer.transferMoney(accountNo, salary);
    System.out.println("Transfer Salary success");

  }

  public static void main(String[] args) {
    SalaryTransfer salaryTransfer = new SalaryTransfer();

    try {
      salaryTransfer.salaryTransfer(Integer.parseInt(args[0]), Integer.parseInt(args[1]), args[2]);
    } catch (NumberFormatException e) {
      System.out.println("Parse int error " + e.getMessage());
    } catch (SalaryException e) {
      System.out.println(e.getMessage());
    }
  }
}

Ở đây mình viết luôn hàm main để thử chạy luôn. Các bạn có thể xem full ví dụ ở repo này

4. Build và test

Ta chạy command sau để build

./gradlew clean build

và test thử:

java -cp build/libs/*.jar dev.trile.customexception.SalaryTransfer 11 1000000 EM1
Transfer 11000000 to EM1
VCB only support transfer amount less than 10M
Retry with VTB
VTB transfer success
Transfer Salary success
java -cp build/libs/*.jar dev.trile.customexception.SalaryTransfer -1 1000000 EM1
WorkingDay less than or equal zero
java -cp build/libs/*.jar dev.trile.customexception.SalaryTransfer 10 -1000000 EM1
SalaryPerDay less than or equal zero
java -cp build/libs/*.jar dev.trile.customexception.SalaryTransfer 15 1000000 EM1
Transfer 15000000 to EM1
VCB only support transfer amount less than 10M
Retry with VTB
VTB transfer success
Transfer Salary success

Kết luận: bằng việc sử dụng custom Exception giúp code trở nên rõ ràng và nhất quán trong việc xử lý nếu có lỗi xảy ra

  • Trường hợp thông thường khi có 1 Exception được throw ra thì flow sẽ bị break như ở hàm calcSalarysalaryTransfer
  • Trường hợp ta có hướng xử lý cho một Exception cụ thể thì ta sẽ thực hiện try/catch và handle như ở MoneyTransfer.moneyTransfer
updatedupdated2020-11-252020-11-25
Load Comments?