mcpField
J
java-coding-standards

java-coding-standards

github.com/midudev开发7.0k56 次安装1 周前更新
代码仓库

关于 java-coding-standards

面向构建或审查 Spring Boot 和 Quarkus 服务团队的 Java 17+ 编码规范技能。它根据构建文件识别框架,并结合通用规范提供相应指导。

主要功能

  1. 01Spring Boot 与 Quarkus 规范
  2. 02根据构建文件识别框架
  3. 03命名与包结构指导
  4. 04不可变性与类型安全实践
  5. 05测试和异常处理指导

适用场景

  1. 01审查 Java 服务代码
  2. 02组织 Spring Boot 项目结构
  3. 03应用 Quarkus 专属规范

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/midudev/autoskills --skill java-coding-standards

安装后在 Claude Code 中输入/java-coding-standards确认已加载。

详细介绍

Java 编码规范

适用于 Spring Boot 和 Quarkus 服务中可读、可维护的 Java(17+)代码的规范。

适用场景

  • 在 Spring Boot 或 Quarkus 项目中编写或审查 Java 代码
  • 规范命名、不可变性或异常处理约定
  • 使用记录类、密封类或模式匹配(Java 17+)
  • 审查 Optional、流或泛型的使用
  • 组织包和项目布局
  • [QUARKUS]:使用 CDI 作用域、Panache 实体或响应式管道

工作原理

框架检测

应用规范前,先从构建文件中确定框架:

  • 构建文件包含 quarkus → 应用 [QUARKUS] 约定
  • 构建文件包含 spring-boot → 应用 [SPRING] 约定
  • 两者均未检测到 → 仅应用通用约定

核心原则

  • 清晰优先于巧妙
  • 默认采用不可变设计;尽量减少共享可变状态
  • 通过有意义的异常快速失败
  • 保持命名和包结构一致
  • [QUARKUS]:优先采用构建时处理而非运行时处理;尽可能避免运行时反射

示例

以下各节展示 Spring Boot、Quarkus 和通用 Java 的具体示例, 涵盖命名、不可变性、依赖注入、响应式代码、异常、 项目布局、日志记录、配置和测试。

命名

// PASS: Classes/Records: PascalCase
public class MarketService {}
public record Money(BigDecimal amount, Currency currency) {}

// PASS: Methods/fields: camelCase
private final MarketRepository marketRepository;
public Market findBySlug(String slug) {}

// PASS: Constants: UPPER_SNAKE_CASE
private static final int MAX_PAGE_SIZE = 100;

// PASS: [QUARKUS] JAX-RS resources named as *Resource, not *Controller
public class MarketResource {}

// PASS: [SPRING] REST controllers named as *Controller
public class MarketController {}

不可变性

// PASS: Favor records and final fields
public record MarketDto(Long id, String name, MarketStatus status) {}

public class Market {
  private final Long id;
  private final String name;
  // getters only, no setters
}

// PASS: [QUARKUS] Panache active-record entities use public fields (Quarkus convention)
@Entity
public class Market extends PanacheEntity {
  public String name;
  public MarketStatus status;
  // Panache generates accessors at build time; public fields are idiomatic here
}

// PASS: [QUARKUS] Panache MongoDB entities
@MongoEntity(collection = "markets")
public class Market extends PanacheMongoEntity {
  public String name;
  public MarketStatus status;
}

Optional 的使用

// PASS: Return Optional from find* methods
// [SPRING]
Optional<Market> market = marketRepository.findBySlug(slug);

// [QUARKUS] Panache
Optional<Market> market = Market.find("slug", slug).firstResultOptional();

// PASS: Map/flatMap instead of get()
return market
    .map(MarketResponse::from)
    .orElseThrow(() -> new EntityNotFoundException("Market not found"));

流的最佳实践

// PASS: Use streams for transformations, keep pipelines short
List<String> names = markets.stream()
    .map(Market::name)
    .filter(Objects::nonNull)
    .toList();

// FAIL: Avoid complex nested streams; prefer loops for clarity

依赖注入

// PASS: [SPRING] Constructor injection (preferred over @Autowired on fields)
@Service
public class MarketService {
  private final MarketRepository marketRepository;

  public MarketService(MarketRepository marketRepository) {
    this.marketRepository = marketRepository;
  }
}

// PASS: [QUARKUS] Constructor injection
@ApplicationScoped
public class MarketService {
  private final MarketRepository marketRepository;

  @Inject
  public MarketService(MarketRepository marketRepository) {
    this.marketRepository = marketRepository;
  }
}

// PASS: [QUARKUS] Package-private field injection (acceptable in Quarkus — avoids proxy issues)
@ApplicationScoped
public class MarketService {
  @Inject
  MarketRepository marketRepository;
}

// FAIL: [SPRING] Field injection with @Autowired
@Autowired
private MarketRepository marketRepository; // use constructor injection

// FAIL: [QUARKUS] @Singleton when interception or lazy init is needed
@Singleton // non-proxyable — use @ApplicationScoped instead
public class MarketService {}

响应式模式 [QUARKUS]

// PASS: Return Uni/Multi from reactive endpoints
@GET
@Path("/{slug}")
public Uni<Market> findBySlug(@PathParam("slug") String slug) {
  return Market.find("slug", slug)
      .<Market>firstResult()
      .onItem().ifNull().failWith(() -> new MarketNotFoundException(slug));
}

// PASS: Non-blocking pipeline composition
public Uni<OrderConfirmation> placeOrder(OrderRequest req) {
  return validateOrder(req)
      .chain(valid -> persistOrder(valid))
      .chain(order -> notifyFulfillment(order));
}

// FAIL: Blocking call inside a Uni/Multi pipeline
public Uni<Market> find(String slug) {
  Market m = Market.find("slug", slug).firstResult(); // BLOCKING — breaks event loop
  return Uni.createFrom().item(m);
}

// FAIL: Subscribing more than once to a shared Uni
Uni<Market> shared = fetchMarket(slug);
shared.subscribe().with(m -> log(m));
shared.subscribe().with(m -> cache(m)); // double subscribe — use Uni.memoize()

异常

  • 域错误使用非受检异常;为技术异常添加上下文后再包装
  • 创建特定于领域的异常(例如,MarketNotFoundException)
  • 避免使用宽泛的 catch (Exception ex),除非要在集中处理时重新抛出或记录日志
throw new MarketNotFoundException(slug);
集中式异常处理
// [SPRING]
@RestControllerAdvice
public class GlobalExceptionHandler {
  @ExceptionHandler(MarketNotFoundException.class)
  public ResponseEntity<ErrorResponse> handle(MarketNotFoundException ex) {
    return ResponseEntity.status(404).body(ErrorResponse.from(ex));
  }
}

// [QUARKUS] Option A: ExceptionMapper
@Provider
public class MarketNotFoundMapper implements ExceptionMapper<MarketNotFoundException> {
  @Override
  public Response toResponse(MarketNotFoundException ex) {
    return Response.status(404).entity(ErrorResponse.from(ex)).build();
  }
}

// [QUARKUS] Option B: @ServerExceptionMapper (RESTEasy Reactive)
@ServerExceptionMapper
public RestResponse<ErrorResponse> handle(MarketNotFoundException ex) {
  return RestResponse.status(Status.NOT_FOUND, ErrorResponse.from(ex));
}

泛型与类型安全

  • 避免使用原始类型;声明泛型参数
  • 可复用的工具优先使用有界泛型
public <T extends Identifiable> Map<Long, T> indexById(Collection<T> items) { ... }

项目结构

[SPRING] Maven/Gradle
src/main/java/com/example/app/
  config/
  controller/
  service/
  repository/
  domain/
  dto/
  util/
src/main/resources/
  application.yml
src/test/java/... (mirrors main)
[QUARKUS] Maven/Gradle
src/main/java/com/example/app/
  config/              # @ConfigMapping, @ConfigProperty beans, Producers
  resource/            # JAX-RS resources (not "controller")
  service/
  repository/          # PanacheRepository implementations (if not using active record)
  domain/              # JPA/Panache entities, MongoDB entities
  dto/
  util/
  mapper/              # MapStruct mappers (if used)
src/main/resources/
  application.properties   # Quarkus convention (YAML supported with quarkus-config-yaml)
  import.sql               # Hibernate auto-import for dev/test
src/test/java/... (mirrors main)

格式与风格

  • 始终一致地使用 2 或 4 个空格(遵循项目标准)
  • 每个文件仅包含一个顶层公共类型
  • 方法应简短且专注;提取辅助方法
  • 成员顺序:常量、字段、构造函数、公共方法、受保护方法、私有方法

应避免的代码异味

  • 参数列表过长 → 使用 DTO/构建器
  • 嵌套过深 → 尽早返回
  • 魔法数字 → 使用命名常量
  • 静态可变状态 → 优先使用依赖注入
  • 静默的 catch 代码块 → 记录日志并采取措施,或重新抛出
  • [QUARKUS]:本应使用 @ApplicationScoped 却使用 @Singleton — 会破坏代理和拦截功能
  • [QUARKUS]:混用 quarkus-resteasy-reactive 和 quarkus-resteasy(经典版)— 选择一种技术栈
  • [QUARKUS]:在同一有界上下文中混用 Panache 活动记录模式和仓库模式 — 选择一种

日志记录

// [SPRING] SLF4J
private static final Logger log = LoggerFactory.getLogger(MarketService.class);
log.info("fetch_market slug={}", slug);
log.error("failed_fetch_market slug={}", slug, ex);

// [QUARKUS] JBoss Logging (default, zero-cost at build time)
private static final Logger log = Logger.getLogger(MarketService.class);
log.infof("fetch_market slug=%s", slug);
log.errorf(ex, "failed_fetch_market slug=%s", slug);

// [QUARKUS] Alternative: simplified logging with @Inject
@Inject
Logger log; // CDI-injected, scoped to declaring class

空值处理

  • 仅在不可避免时接受 @Nullable;否则使用 @NonNull
  • 对输入使用 Bean Validation(@NotNull、@NotBlank)
  • [QUARKUS]:对 @BeanParam、@RestForm 和请求体参数应用 @Valid

配置

// [SPRING] @ConfigurationProperties
@ConfigurationProperties(prefix = "market")
public record MarketProperties(int maxPageSize, Duration cacheTtl) {}

// [QUARKUS] @ConfigMapping (type-safe, build-time validated)
@ConfigMapping(prefix = "market")
public interface MarketConfig {
  int maxPageSize();
  Duration cacheTtl();
}

// [QUARKUS] Simple values with @ConfigProperty
@ConfigProperty(name = "market.max-page-size", defaultValue = "100")
int maxPageSize;

测试要求

通用
  • 使用 JUnit 5 + AssertJ 进行流畅断言
  • 使用 Mockito 创建模拟;尽可能避免部分模拟
  • 优先编写确定性测试;不要使用隐藏的休眠
[SPRING]
  • 控制器切片使用 @WebMvcTest,仓库切片使用 @DataJpaTest
  • @SpringBootTest 仅用于完整集成测试
  • 使用 @MockBean 替换 Spring 上下文中的 Bean
[QUARKUS]
  • 单元测试使用纯 JUnit 5 + Mockito(不使用 @QuarkusTest)
  • @QuarkusTest 仅用于 CDI 集成测试
  • 在集成测试中使用 @InjectMock 替换 CDI Bean
  • 使用 Dev Services 管理数据库/Kafka/Redis——如果 Dev Services 已足够,就避免手动配置 Testcontainers
  • 使用 @QuarkusTestResource 管理自定义外部服务的生命周期
// [SPRING] Controller test
@WebMvcTest(MarketController.class)
class MarketControllerTest {
  @Autowired MockMvc mockMvc;
  @MockBean MarketService marketService;
}

// [QUARKUS] Integration test
@QuarkusTest
class MarketResourceTest {
  @InjectMock
  MarketService marketService;

  @Test
  void should_return_404_when_market_not_found() {
    given().when().get("/markets/unknown").then().statusCode(404);
  }
}

// [QUARKUS] Unit test (no CDI, no @QuarkusTest)
@ExtendWith(MockitoExtension.class)
class MarketServiceTest {
  @Mock MarketRepository marketRepository;
  @InjectMocks MarketService marketService;
}

请记住:编写有明确意图、类型明确且可观测的代码。除非证明确有必要,否则应优先考虑可维护性,而非微优化。

评论

支持 Markdown