plugins/languages/java/skills/error/SKILL.md
Java 错误处理规范 — sealed 异常层次、RFC 9457 Problem Details、Optional 空值安全、SLF4J 结构化日志、Try-With-Resources。当用户设计异常体系、处理错误、编写日志、调试堆栈,或讨论 "异常处理"、"Optional"、"null 安全"、"ControllerAdvice"、"Problem Details"、"日志规范" 时加载。
npx skillsauth add lazygophers/ccplugin java-errorInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Optional.get() 不检查;用 orElseThrow / map / flatMaplog.info("user={}", id),禁字符串拼接、禁 System.out.printlnthrow new AppException("msg", e)public sealed interface AppException permits
ResourceNotFoundException,
DuplicateResourceException,
ValidationException,
AuthorizationException {
String code();
String message();
}
public record ResourceNotFoundException(String type, String id) implements AppException {
public String code() { return "NOT_FOUND"; }
public String message() { return "%s not found: %s".formatted(type, id); }
}
public record DuplicateResourceException(String field, String value) implements AppException {
public String code() { return "DUPLICATE"; }
public String message() { return "Duplicate %s: %s".formatted(field, value); }
}
// 运行时载体(继承 RuntimeException 以便抛出)
public final class AppRuntimeException extends RuntimeException {
private final AppException detail;
public AppRuntimeException(AppException d) { super(d.message()); this.detail = d; }
public AppRuntimeException(AppException d, Throwable c) { super(d.message(), c); this.detail = d; }
public AppException detail() { return detail; }
}
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(AppRuntimeException.class)
public ProblemDetail handle(AppRuntimeException ex) {
return switch (ex.detail()) {
case ResourceNotFoundException e -> {
var pd = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, e.message());
pd.setTitle("Resource Not Found");
pd.setProperty("resourceType", e.type());
pd.setProperty("resourceId", e.id());
yield pd;
}
case DuplicateResourceException e -> {
var pd = ProblemDetail.forStatusAndDetail(HttpStatus.CONFLICT, e.message());
pd.setTitle("Duplicate Resource");
pd.setProperty("field", e.field());
yield pd;
}
case ValidationException e -> ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, e.message());
case AuthorizationException e -> ProblemDetail.forStatusAndDetail(HttpStatus.FORBIDDEN, e.message());
};
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ProblemDetail handleValidation(MethodArgumentNotValidException ex) {
var pd = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, "Validation failed");
pd.setTitle("Validation Error");
pd.setProperty("errors", ex.getFieldErrors().stream()
.map(e -> Map.of("field", e.getField(), "message", e.getDefaultMessage()))
.toList());
return pd;
}
}
application.yml:
spring.mvc.problemdetails.enabled: true
// Service 层
@Transactional(readOnly = true)
public Optional<UserResponse> findById(Long id) {
return userRepository.findById(id).map(UserResponse::from);
}
// Controller
@GetMapping("/{id}")
public ResponseEntity<UserResponse> get(@PathVariable Long id) {
return userService.findById(id)
.map(ResponseEntity::ok)
.orElseThrow(() -> new AppRuntimeException(
new ResourceNotFoundException("User", id.toString())));
}
// 链式
Optional<String> email = repo.findById(id)
.filter(User::isActive)
.map(User::getEmail);
禁用反模式:
optional.get() 无 isPresent 检查if (optional.isPresent()) optional.get() (改 orElse/map)return nullOptional<List<T>> (返回空 List 即可)private static final Logger log = LoggerFactory.getLogger(UserService.class);
log.info("Creating user: email={}", req.email());
try {
User u = repo.save(...);
log.info("User created: id={}, email={}", u.getId(), u.getEmail());
} catch (DataIntegrityViolationException e) {
log.warn("Duplicate email: email={}", req.email()); // 业务可预期
throw new AppRuntimeException(new DuplicateResourceException("email", req.email()), e);
} catch (Exception e) {
log.error("Failed to create user: email={}", req.email(), e); // 最后一个参数是 Throwable
throw e;
}
| 级别 | 用途 | |------|------| | ERROR | 系统错误,需立即处理 | | WARN | 业务异常,可预期但需关注 | | INFO | 业务关键节点 (创建/更新/删除/登录) | | DEBUG | 开发调试 | | TRACE | 详细跟踪 |
try (Connection c = ds.getConnection();
PreparedStatement ps = c.prepareStatement(sql);
ResultSet rs = ps.executeQuery()) {
while (rs.next()) process(rs);
}
// 自定义
public final class Session implements AutoCloseable {
@Override public void close() { /* release */ }
}
| AI 易犯解释 | 实际应核验 |
|---------|---------|
| "抛 RuntimeException 通用" | 是否 sealed 层次? |
| "返回 null 简单" | 是否 Optional? |
| "catch 先空着" | 是否至少 log + 包装重抛? |
| "System.out 调试" | 是否 SLF4J {}? |
| "HTTP 500 通用响应" | 是否 ProblemDetail? |
| "Optional.get() 直接取" | 是否 orElseThrow? |
| "拼字符串日志" | 是否 log.info("k={}", v)? |
@RestControllerAdvice 全局处理spring.mvc.problemdetails.enabled=true.get() 裸用return nulldevelopment
Go 数据库规范——GORM Model 命名 ModelXxx、表名单数、枚举 uint8 + 常量、索引 idx_ 前缀 + deleted_at leading column、禁 time.Time 统一 int64 unix、禁指针/nullable 字段、TEXT/BLOB/JSON 禁 default、AutoMigrate 禁改主键。设计 DB model、写 GORM tag、建索引、做 migration 审查时触发。
development
Go HTTP API 规范——响应始终 200 + body code 字段、路由 /api/* 全 POST 单段 <Action><Model>、中间件逐路由注册禁 Group(prefix,mw...)、handler 仅返回 (rsp,error)、认证走 header。设计 HTTP API、写路由/handler/中间件时触发。
development
Go 项目结构规范——三层架构(API → Impl → State)、全局状态模式、internal/ 私有包、cmd/ 仅 main.go、go.work 多模块、禁止 Repository 接口和 DI 容器、struct 公共字段开头全 omitempty、handler var rsp 顶声明、禁 legacy migration。设计项目骨架、新建目录、组织包、做架构评审时触发。
development
Go 命名规范——Id/Uid 字段(非 ID)、IsActive/HasMFA 布尔前缀、CreatedAt 时间字段、接收者统一用 p、包名全小写无下划线、泛型类型参数描述性命名、集合字段 xxx_list 禁 xxxs 复数、Enum 0 值 XxxNil 禁 Unknown、禁 Status 统一 State、Set/Update 语义区分。定义结构体字段、函数、变量、包、接收者名、泛型、枚举时触发。