In the article Clean Code with Exception, I discussed how using Exception can make code cleaner. However, it was missing a complete practical example and guidance on using custom Exception to make exceptions more meaningful and informative.
The requirements are identical to the Clean Code with Exception article. I’ll summarize them here for convenience so you don’t have to switch back and forth ;))
Write a program that calculates and transfers salaries to company employees. From this requirement, we need to accomplish 3 tasks:
- Calculate salary for employees
- Transfer salary to employees
- Coordinate salary calculation and transfer
We already implemented #1 in Clean Code with Exception, but in a broader context, #3 needs to distinguish between the error types from #1 and #2. For example, #1 requires asking the user to re-enter data and halting execution, while #2 can be retried (the company transfers salaries through two banks, VCB and VTB; if VCB fails, retry with VTB) ;))
1. Calculate salary for employees
From the above analysis, a generic Exception will not satisfy our needs. We need to create a custom Exception:
| |
With an enum defining the error types:
| |
From here, we update the salary calculation method slightly:
| |
2. Transfer salary to employees
Now let’s write classes supporting money transfer. Suppose we integrate with two banks: VietcomBank and VietinBank. Both banks will implement a common interface:
| |
I also implemented parameter validation for money transfers via the checkTransferParams default method.
Implementation for VietcomBank:
| |
To simulate a bank failure, I make VietcomBank throw an error when the amount exceeds 10M.
Similarly for VietinBank, but without errors:
| |
3. Coordinate salary calculation and transfer
And finally, #3 coordinates salary calculation and transfer:
| |
Here I wrote the main method directly so we can run and test right away. You can view the full example in this repo.
4. Build and test
Run the following command to build:
./gradlew clean build
and test:
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
Conclusion: By using custom Exception, our code becomes clearer and more consistent when handling errors:
- In the standard case, when an
Exceptionis thrown, the flow is broken as incalcSalaryandsalaryTransfer - In cases where we have a specific recovery strategy for an
Exception, we cantry/catchand handle it, as inMoneyTransfer.moneyTransfer