Assertions + Exception Mechanism = Clear and Concise Code
Introduction
Assertions + Exception Mechanism = Clear and Concise Code
The business framework supports an exception mechanism, which makes business code clearer. It is exactly this exception mechanism that enables near-zero learning cost (turning ordinary Java methods into business actions).
If there is a business exception, throw it directly. Developers do not need extra handling. The business framework knows how to process business exceptions, and these thrown exceptions can always be returned to the requester.
Exception Examples
ErrorCode
We define an enum ErrorCode to store all error codes.
The enum implements framework interface ErrorInformation,
which provides a series of assert_xxx methods.
@Getter
public enum ErrorCode implements ErrorInformation {
nameChecked(100, "The name must be Jackson");
final int code;
final String message;
ErrorCode(int code, String message) {
this.code = code;
this.message = message;
}
}
Assertion Example
When name is not "Jackson", an exception is thrown.
When assertion is triggered, framework sends the exception message to the requester.
@ActionController(DemoCmd.cmd)
public class DemoAction {
@ActionMethod(DemoCmd.jackson)
private HelloMessage jackson(HelloMessage helloMessage) {
// Example exception mechanism demonstration
// cn: 示例 异常机制演示
ErrorCode.nameChecked.assertTrue("Jackson".equals(helloMessage.name));
// custom error info. cn: 自定义错误信息
// ErrorCode.nameChecked.assertTrue("Jackson".equals(helloMessage.name), "This name is wrong!");
helloMessage.name = "Hello, " + helloMessage.name;
return helloMessage;
}
}
@ProtobufClass
public class HelloMessage {
public String name;
}
As you can see, enum-based assertion coding keeps code very clear.
If there is a new exception business case, add a new enum value in ErrorCode.
When exceptions are thrown in actions, framework sends exception messages to the requester.
Print
When assertion is triggered, console prints information like below.
┏━━ Error.(DemoAction.java:43) ━━ [HelloMessage jackson(HelloMessage helloMessage)] ━━ [1-1] ━━ [tag:DemoLogicServer, serverId:5934564] ━━━━
┣ userId: 1
┣ RequestParam: HelloMessage{name='1'}
┣ ErrorCode: 100
┣ ErrorMessage: The name must be Jackson
┣ ExecutionTime: 0 ms
┗━━ [ionetVersion] ━━ [Thread:User-8-1] ━━ [ConnectionWay:WebSocket] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Will Assertions + Exception Mechanism Affect Performance?
Q: Assertions + exception mechanism make business code very clear. Will heavy use impact performance?
A: In real-world applications, there is no practical performance impact. But this answer may seem too simple, so we break it down by connecting theory with practice.
Code Snippet
First look at two snippets.
// snippet 1
int i = 1 + 1;
// snippet 2
try {
int i = 1 + 1;
} catch(Throwable e) {
}
With identical logic and no exception thrown, performance difference is negligible, and can be benchmarked. When exceptions are thrown, there may be slight overhead, usually negligible.
Short Story
When analyzing problems, avoid rigid bookish thinking.
Here is a common fallacy example: "Repeatedly boiled water causes cancer." Why is this fallacious? It states conclusions without discussing quantity. Only quantitative change leads to qualitative change. Think of repeatedly boiled broth in braised-food cooking; this does not by itself prove such a conclusion.
The point is: when analyzing problems, include quantity in comprehensive analysis.
Framework Exception Mechanism
The business framework wraps action business calls with a try block to catch all exceptions. To reduce exceptions triggered at this layer, the framework provides JSR380 validation support. When JSR380 is triggered, it does not enter action business methods and does not enter this framework try logic.
Connecting theory and practice
From snippets above, when no exception is triggered, there is no performance impact. When exceptions are triggered, impact is slight and can be ignored, but why can it be ignored?
Because we must combine theory with practice. Consider this scenario: buying an item requires 500 gold. Pseudo code:
@ActionController(1)
public class DemoAction {
@ActionMethod(0)
public boolean buyGoods(FlowContext flowContext) {
long userId = flowContext.getUserId();
long gold = getGoldByUserId(userId);
// When the assertion is true, an exception is thrown
// cn: 断言是 true, 就抛出异常
ErrorCode.gold.assertTrueThrows(gold < 500);
...
}
}
This pseudo code means exceptions are thrown when player gold is below 500. That is true.
But do not forget you also have frontend teammates. If frontend also writes robust code, pre-validation is usually done before purchase, so exceptions are usually not triggered. If frontend does not have this awareness, you still do not need to panic. It is not solely your issue, since good products require multi-team effort.
In practice, you may see three developer styles: lazy, perfunctory, proactive.
- Lazy: some frontend developers do no validation and just throw requests to server.
- Perfunctory: fix only what is explicitly pointed out.
- Proactive: do as much validation as possible.
If occasional missed validations happen, how to improve?
You can extend framework plugin mechanism. Use plugins to count which action methods trigger exceptions more frequently. Then share statistics with frontend developers so they can optimize validations.
A side joke: even with statistics, some frontend developers may still not improve. They might quickly generate many reasons not to change. At that moment, accept reality.
Traditional Framework Error Handling
In some traditional frameworks, validation logic is mixed directly into business logic. When validation fails, error code is manually pushed to frontend. This is called traditional/legacy style. Pseudo code:
public void hello() {
if (gold < 500) {
xxx.send(userId, new TheErrorCode(goldCode));
log.error(goldCode + "-" + userId)
return;
}
if (level < 20) {
xxx.send(userId, new TheErrorCode(levelCode));
log.error(levelCode + "-" + userId)
return;
}
if (age < 18) {
xxx.send(userId, new TheErrorCode(agelCode));
log.error(agelCode + "-" + userId)
return;
}
}
When validation logic is small, it may seem fine. In reality, validations are usually many,
leading to excessive if/else blocks and messy business code.
With framework JSR380 and Assertion + Exception mechanism, code stays clearer and more readable.
ErrorCode.gold.assertTrueThrows(gold < 500);
ErrorCode.level.assertTrueThrows(level < 20);
ErrorCode.age.assertTrueThrows(age < 18);
With traditional style, counting route exception trigger frequency is difficult. With framework plugin mechanism, this is simple. As mentioned in plugin docs, this mechanism can do many things, depending on your imagination.
Bridging Theory and Practice Summary
- Building a good product needs cross-team effort. If frontend also performs validation well, these requests often never reach backend.
- Framework provides JSR380 validation. When JSR380 triggers, it does not enter action business methods and does not enter framework try logic.
- From quantitative/qualitative change perspective, exception trigger counts are not high in practice, so there is no practical performance impact. This is supported by actual statistics.
- Stay away from purely bookish development.
The conclusion from theory + practice is the same as above: in real scenarios there is no practical performance impact. So use this coding style confidently. It keeps business code clear and as easy to read as an article.
Code is read and maintained by people. Please do not make it painful.