阿里巴巴java开发手册里面,在控制语句部分有一个推荐,如下:
【推荐】表达异常的分支时,少用 if-else 方式,这种方式可以改写成:
if (condition) {
...
return obj;
}
// 接着写 else 的业务逻辑代码;
【强制】说明:如果非得使用 if()...else if()...else...方式表达逻辑,避免后续代码维护困难,请勿超过 3 层。
正例:超过 3 层的 if-else 的逻辑判断代码可以使用卫语句、策略模式、状态模式等来实现, 其中卫语句示例如下:
public void today() {
if (isBusy()) {
System.out.println(“change time.”);
return;
}
if (isFree()) {
System.out.println(“go to travel.”);
return;
}
System.out.println(“stay at home to learn Alibaba Java Coding Guidelines.”);
}
以上是手册里面完整的说明,if else的嵌套,强制不让超过三层,超过了就是程序员代码写的有问题了,需要优化。
下面我通过一段代码来进行优化多层if else嵌套问题,问题场景是投票功能,需要调腾讯云的滑动验证码来进行防止刷票(这里开启滑动验证码防刷票是需要购买的),滑动验证码是前端直接调中台接口的,中台会返回一个ticket值,然后这这边需要判断ticket值是否正确,这是投票的时候进行滑动验证码判断的逻辑。
首先这个功能做的方式是,小程序一开始加载投票活动的时候,就会返回一个字段告诉是否开启了滑动验证码,前端根据这个字段,如果开启了那么调中台的滑动验证码拿ticket,调微信接口的滑动验证码,用户滑动之后访问接口,后端判断ticket等。(因为滑动验证码在很多地方都会需要用到,所以放在了中台),多层嵌套代码如下:
public void checkSlideCaptchaRemainTimes(String activityId, String ticket, UserIp userIp)
throws NoSuchAlgorithmException, InvalidKeyException, UnirestException {
Activity activity = activityService.findByid(activityId);
SlideCaptcha slideCaptcha = slideCaptchaService.findSlideCaptchaByBuserId(activity.getBUserId());
if (activity != null){
// 如果滑动验证码没有开启,那么不需要管滑动验证码逻辑;如果滑动验证码开启了,走如下逻辑
if (StringUtils.isNotEmpty(ticket)){
// 滑动验证码开启了
if (!activity.getDisablePicvcode()) {
if (slideCaptcha != null) {
// 剩余滑动验证码次数足够
if (slideCaptcha.getRemainTimes() > 0) {
// 滑动验证码次数减1
slideCaptchaService.updateSlideCaptchaByBUserId(activity.getBUserId(), -1);
// 调中台接口确定ticket是否正确
slideCaptchaManager.checkTicket(ticket);
// 剩余次数少于100次,给用户发短信
if (slideCaptcha.getRemainTimes() == 300){
BUser bUser = userService.getBUserPhone(slideCaptcha.getBUserId());
executor.execute(() -> smsManager.smsSendMessage(bUser.getPhone(), slideCaptcha.getRemainTimes().toString(), "2024"));
}
} else {
// 将此用户的所有投票活动全部关闭滑动验证码功能
activityRepositoryImpl.updateActivitySlideCaptchaByBUserId(activity.getBUserId());
throw new BaseException(ExceptionCode.SLIDE_CAPTCHA_NOT_ENOUGH);
}
} else {
throw new BaseException(ExceptionCode.SLIDE_CAPTCHA_NOT_ENOUGH);
}
} else {
// 滑动验证码关闭了
throw new BaseException(ExceptionCode.SLIDE_CAPTCHA_NOT_ENOUGH);
}
}
} else {
// 滑动验证码开启了但是没有传需要验证的ticket
throw new BaseException(ExceptionCode.VOTE_SLIDE_CAPTCHA_IS_OPEN);
}
}
看到这些代码,头都是大的,光是理解就需要半天。下面就来着手优化一下这段代码,
首先考虑问题需要考虑一些极端情况(这是一个非常好的习惯,一个人严不严谨往往就在这),比如:
1.活动一开始没有滑动验证码,然后用户投票之前,后端改成了需要滑动验证码;
这种情况为了防止用户刷票,肯定是不能让他正常投票了,刷票一般都是直接调接口的方式,所以后台需要返回一个错误码告诉前端,需要滑动验证码,不能投票,那么这个接口调用失败,前端拉取滑动验证码重新传ticket再调接口。这里可能会有人说接口调用很多次,但是没有关系,因为这个操作原本就是很少概率下的操作。
2.活动一开始就有滑动验证码,但是用户投票之前,后端改成了不需要滑动验证码;
这种情况,也许正常会想,给前端返回个错误码告诉前端:不需要滑动验证码啦!但是再仔细一想其实没有必要,因为从业务角度看,用户是需要正常投票的,不需要滑动验证码就没有了防止刷票功效,让他调用一次滑动验证码损失不大,原本滑动验证码就很便宜,所以此时直接让他正常投票就可以。
优化后的代码如下:
public void checkSlideCaptchaTicket(Activity activity, String ticket, UserIp userIp)
throws UnirestException {
// 如果滑动验证码没有开启,直接返回不需要做任何处理
if (activity.getDisablePicvcode()) {
return;
}
// 滑动验证码开启了,但是没有传ticket
if (StringUtils.isEmpty(ticket)){
throw new BaseException(ExceptionCode.VOTE_SLIDE_CAPTCHA_IS_OPEN);
}
SlideCaptcha slideCaptcha = slideCaptchaService.findSlideCaptchaByBuserId(activity.getBUserId());
// 防止户没有开启过滑动验证码,但是活动中数据有误
if (slideCaptcha == null){
throw new BaseException(ExceptionCode.SLIDE_CAPTCHA_NOT_ENOUGH);
}
// 剩余滑动验证码次数足够
if (slideCaptcha.getRemainTimes() > 0) {
slideCaptchaService.updateSlideCaptchaByBUserId(activity.getBUserId(), -1);
slideCaptchaManager.checkTicket(ticket);
// 剩余次数少于100次,给用户发短信
if (slideCaptcha.getRemainTimes() == 300){
BUser bUser = userService.getBUserPhone(slideCaptcha.getBUserId());
executor.execute(() -> smsManager.smsSendMessage(bUser.getPhone(), slideCaptcha.getRemainTimes().toString(), "2024"));
}
} else {
// 将此用户的所有投票活动全部关闭滑动验证码功能
activityRepositoryImpl.updateActivitySlideCaptchaByBUserId(activity.getBUserId());
// 并发进来可能会出现一个人投票失败,但是业务上允许他成功,所以没有必要抛出异常
// throw new BaseException(ExceptionCode.SLIDE_CAPTCHA_NOT_ENOUGH);
}
}
优化的地方:
1.嵌套语句改成了阿里巴巴推荐的方式,让人很容易看懂
2.减少了一次活动查询,以及减少了没有开启滑动验证码的情况下,对用户滑动验证码的查询
滑动验证码的调用思路如下:
先请求中台,中台返回数据,也就是腾讯云那边的滑动验证码
前端根据返回的地址,校验滑动验证码是否滑正确
这是图片没有对准的返回值,图片对准了ticket就会是一个值 如这种:往往是加密过的值
Xd0Wt2g7ZFa-MX5qE0dSA1j-SOkbJWLl7uLUNMEl2DUaAuPsGPT0mZGaaIS_Xv7X2dYTlgj00CQ*