应根据错误是否可恢复选择:可恢复用错误码(如Result),不可恢复必须throw;RuntimeException用于编程错误,CheckedException用于外部依赖失败;避免原始数字错误码,优先用枚举或Result封装。
throw还是return错误码?看异常是否可恢复Java中抛异常和返回错误码不是风格偏好问题,而是语义分层问题。核心判断标准是:调用方能否在当前上下文合理处理并继续执行。如果能,用错误码;如果不能(比如数据库连接中断、空指针、非法参数),必须throw——否则会掩盖故障边界,让错误在调用栈深处突然爆发。
RuntimeException和CheckedException怎么选?看是否需要强制捕获Jav

IOException、SQLException)要求调用方显式处理,适合外部依赖失败(如文件读写、网络请求)。但滥用会导致大量try-catch或throws污染业务逻辑。相反,RuntimeException子类(如IllegalArgumentException、NullPointerException)应代表编程错误或不可恢复状态,不强制捕获,靠测试和日志暴露。
RuntimeException封装业务错误(如BusinessException),由全局异常处理器转为HTTP状态码+JSON错误体Optional或自定义结果类Result,而非抛异常NullPointerException当控制流用——这是bug,不是设计用int或String表示错误码看似简单,但很快会遇到问题:不同模块错误码冲突、含义模糊、无法携带上下文。比如return -1可能代表“未找到”“权限不足”或“超时”,调用方只能靠文档猜。
public class Result{ private final boolean success; private final T data; private final String errorCode; // 比int更易读,支持国际化 private final String message; public static Result success(T data) { ... } public static Result fail(String code, String msg) { ... } }
ErrorCode.USER_NOT_FOUND)或字符串常量
traceId字段,单纯返回码做不到抛Exception确实比return慢,因为要生成堆栈快照。但在99%的业务场景中,这个开销远小于一次DB查询或HTTP调用。真正要注意的是:不要在高频循环里抛异常(比如用NumberFormatException做字符串数字校验),这会让JVM无法优化,且堆内存碎片化加剧。
Integer.parseInt()前先matches("\\d+"),别依赖catch
list.get(i)前先判i ,而不是靠IndexOutOfBoundsException兜底
server.error.include-stacktrace=never,就是防止异常堆栈被误发给前端
异常设计最难的不是语法,是界定“什么是正常流程的失败”和“什么是系统级崩溃”。一个Result对象可能比五个自定义异常更清晰,也可能让错误处理散落在二十个if里——关键在团队对错误边界的共识,不在技术选型本身。