开发工具可观测性后端【免费下载链接】vjtoolsThe vip.coms java coding standard, libraries and tools项目地址https://gitcode.com/gh_mirrors/vj/vjtools点击查看免费下载导读本文以《唯品会Java开发手册》vjtools 仓库 docs/standard 章节中的“异常处理”一章为骨架系统讲解 Java 异常创建成本、静态异常复用、异常抛出/捕获/处理及 finally 块等 8 大规约并逐一对应到仓库中 vjkit 工具库ExceptionUtil、CloneableException、IOUtil 等与 sonar-vj 定制规则的源码实现。读完本文你将掌握一套可直接落地的异常编码规范并理解其底层原理与配套的静态检查落地方式。一、异常处理规范总览本章docs/standard/chapter10.md是《唯品会Java开发手册》docs/standard/README.md的第十章共 8 条规则其中【强制】4 条、【推荐】4 条覆盖从异常“产生”到“消亡”的完整生命周期规则关注点级别Rule 1创建异常的消耗大只用在真正异常的场景强制Rule 2特定场景避免每次构造异常静态异常复用推荐Rule 3自定义异常建议继承 RuntimeException推荐Rule 4异常日志应包含排查问题的足够信息推荐Rule 5异常抛出的原则标准异常优先、按需定义推荐Rule 6异常捕获的原则按需捕获、多异常合并推荐Rule 7异常处理的原则不可吞异常、不捕获则处理强制Rule 8finally 块的处理原则资源关闭、禁止 return强制值得注意的是本章在阿里手册基础上做了“增补与删减”完整对照见 docs/standard/ali.md其核心思路是异常是昂贵的控制流能规避就规避能复用就复用捕获后必须负责到底。二、Rule 1强制创建异常的消耗大只用在真正异常的场景构造异常对象时JVM 需要获得整个调用栈fillInStackTrace这是一笔不小的开销。因此异常不应被用来做流程控制或条件控制——条件判断的效率远高于异常处理。典型反例用捕获NullPointerException来做空值判断//WRONG try { return obj.method(); } catch (NullPointerException e) { return false; }正确做法对发生概率较高的条件先做检查规避//RIGHT if (obj null) { return false; }如果代码里频繁捕获IndexOutOfBoundsException、NullPointerException这类“本可预防”的异常通常意味着存在坏味道。这条规则对应 Sonar 规则 RSPEC-1696: NullPointerException should not be caught。三、Rule 2推荐在特定场景避免每次构造异常承接 Rule 1如果异常频繁发生且不需要打印完整调用栈可以考虑绕过异常构造函数。文档给出三种手段1message 不变将异常定义为静态成员变量private static RuntimeException TIMEOUT_EXCEPTION ExceptionUtil.setStackTrace(new RuntimeException(Timeout), MyClass.class, mymethod); ... throw TIMEOUT_EXCEPTION;这里的ExceptionUtil即 vjkit 工具库中的 ExceptionUtil.java。其setStackTrace(Throwable, Class, String)方法参考 Netty 的做法为静态异常设置仅一层的 StackTrace抛出点类名 方法名替代完整调用栈public static T extends Throwable T setStackTrace(NotNull T throwable, Class? throwClass, String throwClazz) { throwable.setStackTrace( new StackTraceElement[] { new StackTraceElement(throwClass.getName(), throwClazz, null, -1) }); return throwable; }该方法返回异常对象本身便于静态初始化时链式赋值。对应的测试见 ExceptionUtilTest.java 的staticException()用例它断言静态异常的 StackTrace 文本只有 2 行且指向ExceptionUtilTest.hello这个自定义抛出点证明“单层 StackTrace”效果确实生效。补充说明若异常可能在多个地方抛出应使用setStackTrace显式指定抛出类与方法而 ExceptionUtil.clearStackTrace() 则用于“无法控制生成端但能控制打印端”的场景——它沿 Cause 链逐层清空 StackTrace注意 Cause 链本身无法清除。2message 会变化对静态异常实例 clone() 后再修改 messageprivate static CloneableException TIMEOUT_EXCEPTION new CloneableException(Timeout) .setStackTrace(My.class, hello); ... throw TIMEOUT_EXCEPTION.clone(Timeout for 40ms);Java 默认异常并不实现 Cloneablevjkit 为此提供了 CloneableException.java继承了Exceptionclone()基于super.clone()复制实例不经过构造函数也就避免了重新获得 StackTraceclone(String message)克隆后直接设置新 messagesetStackTrace(Class, String)内部委托给ExceptionUtil.setStackTrace用于静态初始化。Override public CloneableException clone() { // NOSONAR try { return (CloneableException) super.clone(); } catch (CloneNotSupportedException e) {// NOSONAR return null; } }测试用例同样验证了该行为TIMEOUT_EXCEPTION2.clone(Timeout for 30ms)后打印的 StackTrace 仍只有 2 行消息变为新值抛出点保持ExceptionUtilTest.hello即克隆没有重新生成完整调用栈。如果希望异常直接继承RuntimeException契合 Rule 3仓库还提供了对应的 CloneableRuntimeException.javaAPI 与 CloneableException 完全一致可按需选用。3重载 fillInStackTrace() 为空函数自定义异常也可以重载fillInStackTrace()为空函数来跳过调用栈生成但相对不够灵活——无法像方案 1/2 那样按场景指定一层 StackTrace。四、Rule 3推荐自定义异常建议继承 RuntimeException详见《Clean Code》该争论已经结束不再推荐初衷很好的 CheckedException。原因在于CheckedException 需要在“抛出异常的地方”与“捕获处理异常的地方”之间层层定义throws XXX来传递底层代码一旦改动将影响所有上层函数的签名导致编译出错对封装的破坏严重对 CheckedException 的处理也给上层程序员带来额外负担其他主流语言都没有 CheckedException 的设计。这与 vjkit 的设计一脉相承CloneableRuntimeException、UncheckedException见 UncheckedException.java均继承RuntimeException。其中UncheckedException是 CheckedException 的包装器ExceptionUtil.unchecked(t)会把 CheckedException 包装后重新抛出减少函数签名中的 CheckedException 定义其测试用例ExceptionUtilTest.java 的unchecked()确认RuntimeException 与 Error 原样抛出仅普通 Exception 被包装。五、Rule 4推荐异常日志应包含排查问题的足够信息异常信息应包含排查问题时足够的上下文捕获并记录异常日志的地方还需要记录“未包含在异常信息中、但排查问题需要的信息”比如捕获处的上下文。//WRONG new TimeoutException(timeout); logger.error(e.getMessage(), e); //RIGHT new TimeoutException(timeout: eclapsedTime , configuration: configTime); logger.error(user[ userId ] expired: e.getMessage(), e);这条规则对应 Facebook-Contrib 的 Style 规则Method throws exception with static message string。要点异常 message 携带业务度量耗时、配置值日志再补充调用上下文用户 ID二者配合才能快速定位问题。六、Rule 5推荐异常抛出的原则5.1 尽量使用 JDK 标准异常与项目标准异常优先使用 JDK 标准的 RuntimeException如IllegalArgumentException、IllegalStateException、UnsupportedOperationException业务上使用项目定义的ServiceException。标准异常语义清晰调用方无需额外学习成本。5.2 根据调用者的需要来定义异常类是否定义独立的异常类关键看调用者会如何处理这个异常。如果没有特殊处理需求直接抛出RuntimeException也是允许的。这避免了为异常而异常、无谓地扩充异常类层级。七、Rule 6推荐异常捕获的原则6.1 按需要捕获异常捕获 Exception 或 Throwable 是允许的如果无特殊处理逻辑统一捕获Exception统一处理是允许的。捕获Throwable则用于捕获Error类异常包括其实无法处理的OOM、StackOverflow、ThreadDeath以及类加载/反射时可能抛出的NoSuchMethodError、NoClassDefFoundError等。6.2 多个异常处理逻辑一致时使用 JDK7 的多 catch 语法try { ... } catch (AException | BException | CException ex) { handleException(ex); }对应 Sonar 规则 RSPEC-2147: Catches should be combined避免重复代码。八、Rule 7强制异常处理的原则7.1 捕获异常一定要处理故意忽略须注释写明原因空 catch 块是典型的坏味道。若确实需要忽略比如循环中的单项失败必须用注释说明原因方便阅读者确认“此处不是漏了处理”//WRONG try { } catch(Exception e) { } //RIGHT try { } catch(Exception ignoredExcetpion) { //continue the loop }vjtools 在 Sonar 落地时进一步放宽了这一条定制规则 CatchUsesExceptionWithContextCheck.java 在实现 Sonar S1166 时忽略异常变量名含 ignore 字样的检查catch(Exception ignore)视为有意忽略不再报警见 sonar-vj/README.md 规则表第 1166 行。7.2 不能吞掉原异常要么打日志要么在重新抛出的异常里包含原异常//WRONG throw new MyException(message); //RIGHT 记录日志后抛出新异常向上次调用者屏蔽底层异常 logger.error(message, ex); throw new MyException(message); //RIGHT 传递底层异常 throw new MyException(message, ex);对应 Sonar 规则 RSPEC-1166: Exception handlers should preserve the original exceptions。该规则默认包含若干例外InterruptedException、NumberFormatException、NoSuchMethodException等。在 CatchUsesExceptionWithContextCheck.java 中这些例外类型被显式配置为exceptions属性默认值同时额外加入了ParseException、MalformedURLException、DateTimeParseException等解析类异常——这些场景捕获后不处理通常可接受。7.3 最外层业务使用者必须处理异常如果不想处理异常可以不捕获让异常向上传播但最外层的业务使用者必须处理异常将其转化为用户可以理解的内容而不是把堆栈直接抛给用户。九、Rule 8强制finally 块的处理原则8.1 必须关闭资源对象/流对象或使用 try-with-resource关闭动作必须放在 finally 块不能放在 try 块或 catch 块这是经典错误。更推荐直接使用 JDK7 的 try-with-resource 语法自动关闭 Closeable 资源try (Writer writer ...) { writer.append(content); }8.2 处理过程中如有抛出异常的可能也要 try-catch防止 finally 中的异常顶替原异常//WRONG try { ... throw new TimeoutException(); } finally { file.close();//如果file.close()抛出IOException, 将代替TimeoutException } //RIGHT, 在finally块中trycatch try { ... throw new TimeoutException(); } finally { IOUtil.closeQuietly(file); //该方法中对所有异常进行了捕获 }规则背后的原理finally 块中抛出的异常会顶替try 块中尚未抛出的异常。对应 Sonar 规则 RSPEC-1163: Exceptions should not be thrown in finally blocks。文档示例中的IOUtil.closeQuietly(file)正是 vjkit 的 IOUtil.java 中提供的“安静关闭”方法内部捕获 IOException 并仅打 warn 日志保证不干扰原有异常流public static void closeQuietly(Closeable closeable) { if (closeable null) { return; } try { closeable.close(); } catch (IOException e) { logger.warn(IOException thrown while closing Closeable., e); } }它还兼容closeable null的情况资源可能未实际创建可以放心地在 finally 中使用。8.3 禁止在 finally 块中使用 returnfinally 块中的 return 将代替try 块中的 return 及 throw Exception//WRONG try { ... return 1; } finally { return 2; //实际return 2 而不是1 } try { ... throw TimeoutException(); } finally { return 2; //实际return 2 而不是TimeoutException }对应 Sonar 规则 RSPEC-1143: Jump statements should not occur in finally blocks。十、规范落地Sonar 定制规则如何支撑本章《唯品会Java开发手册》的落地主要依赖代码格式模板与 Sonar 代码规则检查见 docs/standard/README.md 的“规范落地”一节。由于官方 Sonar 规则存在误报vjtools 在 standard/sonar-vj 中定制了与本章直接相关的若干规则编号规则描述与本章的对应修改S1166Exception handlers should preserve the original exceptions对应 Rule 7.2忽略异常变量名含 ignore 字样的检查S1163Exceptions should not be thrown in finally blocks对应 Rule 8.2S1143Jump statements should not occur in finally blocks对应 Rule 8.3S2147Catches should be combined对应 Rule 6.2S1696NullPointerException should not be caught对应 Rule 1这些规则以源码形式存放在 standard/sonar-vj/src/main/java/com/vip/vjkit/sonarvj/checks/ 目录下编译后放入 Sonar 的 lib 目录并重启即可用“带 VJ 字样”的规则替代官方同编号规则具体步骤见 sonar-vj/README.md。此外CatchUsesExceptionWithContextCheck.java 还额外处理了Enum.valueOf()捕获 IllegalArgumentException 的合法场景可见落地时对真实业务场景的细致考量。十一、总结异常处理的八条心法异常创建昂贵只用于真正异常的场景可预防的条件先检查规避Rule 1高频异常可静态复用message 不变用ExceptionUtil.setStackTracemessage 变化用CloneableException.clone()Rule 2自定义异常继承 RuntimeException避免 CheckedException 污染签名Rule 3异常信息与日志都要包含足够的排查上下文Rule 4抛出优先 JDK 标准异常按调用者需要定义异常类Rule 5捕获按需捕获逻辑一致的多异常用多 catch 合并Rule 6处理捕获必处理忽略需注释永不吞异常最外层必须面向用户转化Rule 7finally资源必关优先 try-with-resource、不抛异常、不 returnRule 8。以上八条配合 vjtools 仓库中 ExceptionUtil.java、CloneableException.java、IOUtil.java 等工具类以及 sonar-vj 的定制规则即可在日常开发中实现从“编写规范”到“自动检查”的完整闭环。赞分享开发工具可观测性后端【免费下载链接】vjtoolsThe vip.coms java coding standard, libraries and tools项目地址https://gitcode.com/gh_mirrors/vj/vjtools点击查看免费下载相关推荐唯品会 Java 开发手册vjtools之集合处理12 条规约与 vjkit 源码级实践唯品会 Java 开发手册vjtools之集合处理12 条规约与 vjkit 源码级实践 导读 本文基于《唯品会 Java 开发手册》vjtools 仓开发工具可观测性后端唯品会Java开发手册vjtools命名规约全解13条强制与推荐规则及Sonar落地实践唯品会Java开发手册vjtools命名规约全解13条强制与推荐规则及Sonar落地实践 《唯品会Java开发手册》1.0.3版本仓库 vjtools开发工具可观测性后端唯品会 Java 开发手册之注释规约10 条规则详解与落地实践唯品会 Java 开发手册之注释规约10 条规则详解与落地实践 导读 本文基于《唯品会Java开发手册》vjtools 仓库 docs/standard/c开发工具可观测性后端上一篇微信读书笔记助手你的数字阅读效率提升神器下一篇更快更私密Thorium浏览器快速上手3步开始使用创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考