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 nulltools
UI/UX 与布局设计——做界面布局/结构/导航/组件/交互的设计决策。触发:做UI/UX/布局/排版/导航/组件/交互/栅格/响应式/图表选型/字体配对。按媒介路由 HTML/Web、原生 App(iOS/Android/桌面)、CLI、TUI。需后端动态系统不适用;配色/主题/色板走姊妹 skill design-color。
tools
主题与配色设计——做颜色搭配/调色板/主题/品牌色阶/暗模式的设计决策。触发:选配色/调色/主题/色板/品牌色/暗模式/对比度/色盲/UI风格。按媒介路由 HTML/Web(CSS变量)、原生App(平台token)、CLI(ANSI)、TUI(真彩/256/16降级)。保证可访问性(对比度/色盲安全)。需后端动态系统不适用;UI/UX 布局/组件/交互走姊妹 skill design-uiux。
tools
跨任意组件(plugin/skill/agent/command)的验证驱动优化循环纪律 skill。当用户要优化某个已有组件却无明确方向、或要防止改了反而更差(自评乐观偏差 / 多维同改归因失效 / 为凑分加废话膨胀)、或要把一套通用「评分→单变量改→改后验证严格更好才留否则回滚→触顶停」的纪律套到任意组件上时使用。管优化过程本身的纪律(validation gate / ratchet / 独立验证 / 触顶停),不评单组件深度(交 skill-dev),不查插件接线(交 plugin-dev)。仅手动 /optimize-any 触发。
data-ai
两层规则记忆 (基于 .skein/spec)。planning 时 recall 召回相关规则、task finish 后 sediment 沉淀学习 + prune 自动精简过期/重复/断链规则。core 常驻硬规 + recall 按需召回, 经判定门自动写盘 (不逐次问用户)。产出 .skein/spec 下 core/recall 规则文件 + index。另支持空仓 bootstrap 播种规则基线、记忆大面积失效 (大重构/换栈) 时 reconstruct 可逆归档后按项目类型分型重建、maintain 手动体检 (超预算/stale/断链/重复/废弃, --apply 自动修复)、auto-fix (Stop hook 写 .pending-fix 标记 → main 派 skein-specer bg 跑 maintain --apply 全自动修, 断链只报告)。