Skip to main content

断言 + 异常机制 = 清晰简洁的代码

介绍

tip

断言 + 异常机制 = 清晰简洁的代码

业务框架支持异常机制,有了异常机制可以使得业务代码更加的清晰。 也正是有了异常机制,才能做到零学习成本(让普通的 java 方法成为一个业务动作 action )。

如果有业务上的异常,请直接抛出去,不需要开发者做过多的处理,业务框架会知道如何处理这个业务异常,这些抛出去的业务异常总是能给到请求端的。

异常示例

ErrorCode

我们定义了一个枚举类 ErrorCode,用于存放所有的错误码。

枚举类实现了框架提供的 ErrorInformation 接口, 该接口提供了一系列的 assert_xxx 方法。

@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;
}
}

断言示例

当 name 不是 "Jackson" 时,会抛出异常。 当触发断言时,框架会将异常消息发送到请求方。

@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;
}
tip

可以看出,使用枚举(断言)的编码方式,使得代码非常的清晰。 如果有新的异常业务,就在 ErrorCode 枚举中新增变量。 在 action 中抛出的异常时,框架会将异常消息发送到请求方。

打印

当触发断言时,控制台会打印如下信息。

┏━━ 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] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

断言 + 异常机制对性能会有影响吗?

Q: 断言 + 异常机制让业务代码表现得很清晰,问下异常机制使用过多对性能会有影响吗?

A: 在实际应用中,不会有性能上的影响。 但似乎这么回答又过于简单,所以这里会拆解分析,将问题理论联系实际。

代码片段

我们先看两个代码片段

// 片段1
int i = 1 + 1;

// 片段2
try {
int i = 1 + 1;
} catch(Throwable e) {
}

代码逻辑相同时,在不触发异常的情况下,两者之间的性能差异是可忽略的,这点可以通过 benchmark 来测试验证。 在触发异常时会略有微微的影响,基本可以忽略。

小故事

我们在分析问题时,不能本本主义,这样容易谬误。 这里举个谬误的小故事,可能你之前听过或在小视频中刷到过《饮用反复烧开的水会致癌》这样的内容,这是谬误之一。 为什么说这个内容是谬误呢?因为内容中只谈结论而没有谈量的问题,要知道只有量变才会引起质变。

卤味吃过吧,无限循环锅里反复煮,也没见吃卤味的人怎么样。 还有那句怎么说来的,拋开什么谈毒性来着。 这里不要杠,杠就你赢

这个故事想表达的是,在分析问题时,要将这个方面纳入到综合分析中。

框架的异常机制

业务框架会在 action 的业务上层做一个 try Throwable 的工作,目的是捕获所有异常。

了为防止触发异常机制,框架提供了 JSR380 验证支持,当触发 JSR380 时。 不会进入 action 业务方法,也不会进入框架 try 的这部分逻辑。


理论联系实际

从上面的代码片段中我们可以知道在没有触发异常时,是不会有性能方面的影响的。 在触发异常时会有微微的影响,但也可以忽略,那么忽略是理由是什么?

忽略的理由是理论联系实际,为了说明这点我们先设定一个业务场景,玩家购买一个商品需要 500 金币。伪代码如下

@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);
...
}
}

通过这段伪代码是不是感觉只要玩家金币小于 500 时就会拋异常。不用感觉,事实的确是这样的。

但别忘了,你还有前端的队友。 假设前端也是编写健壮型代码的开发者,通常会在玩家购买商品时做一个购买前的验证逻辑,所以通常情况下是不会触发的异常机制。 如果前端不具备这样的编程意识,你也不需要担心,这不是你的问题,因为你已经做了你该做的了,做好一个产品需要多方的努力与配合。

在实际中,会碰到三类开发者:懒惰型、敷衍型、积极型

  • 懒惰型:比如部分前端开发者不做任何验证就把请求往服务器扔,这样做似乎可以减少一些工作量。
  • 敷衍型:你说一处,他就改一处。
  • 积极型:尽可能的将验证做好。

也许会有偶尔忘记验证的情况,如果出现这种类似的情况我们该如何改进呢?

这里也顺便分享一下,我们可以扩展业务框架提供的插件机制。 通过插件来实现统计哪些 action 业务方法触发的异常次数较多, 得到统计数据后,将这些统计数据给到前端开发者,让其根据统计数据来做对应的验证优化。

顺便开个玩笑,也会有这么一种情况,即使你给出了统计数据,有部分前端开发者也不会对此做改进。 或许此时他在拿到统计数据时,大脑也在飞速的运转,数秒之后将多条不做改进的理由砸向你。 此时,请认命。

传统框架的错误处理

在一些传统框架中,会将验证逻辑放到实际的业务逻辑代码中。 当有触发验证逻辑时,将错误码主动的推送到前端,这里称为传统写法或祖传代码写法。伪代码如下

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;
}
}

在验证逻辑少时还好,但在实际中验证逻辑往往是有多条的, 这将会不断的在业务代码中疯狂使用 if else 输出,使得业务代码混乱。

当使用框架提供的 JSR380断言 + 异常机制时,你会发现代码是清晰的,可读性也更强。

ErrorCode.gold.assertTrueThrows(gold < 500);
ErrorCode.level.assertTrueThrows(level < 20);
ErrorCode.age.assertTrueThrows(age < 18);

如果使用传统的写法,你会发现想做对应路由异常触发次数的统计是困难的, 而在框架的插件机制中实现这些是简单的。 在插件文档就也提到过,你可以通过该机制做很多事情,这要看你的想象力有多丰富了。

理论联系实际小结

  1. 做好一个产品需要多方的努力,当前端也将数据验证做好了,此类请求也就不会进入到服务端。
  2. 框架提供了 JSR380 验证支持,当触发 JSR380 时。不会进入 action 业务方法,也不会进入框架 try 的这部分逻辑。
  3. 量变质变规律,实际中触发异常机制的次数并不会很多,所以并不会有性能上的影响,这是根据实际统计数据得出的结论。
  4. 远离纯本本主义开发者。

通过理论联系实际得出的结论是与开头的回答一样,在实际中并不会有性能上的影响。 所以请放心的使用这种编码方式,这将使得项目中业务代码更加的清晰,就像阅读文章一样轻松。

tip

代码是给人读的,维护代码的也是人,请不要伤害他人