java-coding-standards

java-coding-standards

熱門

Java 編碼規範,適用於 Spring Boot 與 Quarkus 服務:命名、不可變性、Optional 使用、串流、例外處理、泛型、CDI、反應式模式及專案結構。自動套用框架特定慣例。

23萬星標
3.5萬分支
更新於 2026/7/14
SKILL.md
readonlyread-only
name
java-coding-standards
description

Java 編碼規範,適用於 Spring Boot 與 Quarkus 服務:命名、不可變性、Optional 使用、串流、例外處理、泛型、CDI、反應式模式及專案結構。自動套用框架特定慣例。

Java 編碼規範

適用於 Spring Boot 與 Quarkus 服務中可讀、可維護的 Java (17+) 程式碼規範。

使用時機

  • 在 Spring Boot 或 Quarkus 專案中撰寫或審查 Java 程式碼
  • 強制執行命名、不可變性或例外處理慣例
  • 使用 record、sealed class 或 pattern matching (Java 17+)
  • 審查 Optional、串流或泛型的使用
  • 組織套件與專案結構
  • [QUARKUS]:使用 CDI scope、Panache 實體或反應式管線

運作方式

框架偵測

套用規範前,從建置檔判斷框架:

  • 建置檔包含 quarkus → 套用 [QUARKUS] 慣例
  • 建置檔包含 spring-boot → 套用 [SPRING] 慣例
  • 兩者皆未偵測到 → 僅套用共用慣例

核心原則

  • 清晰勝於花俏
  • 預設不可變;最小化共享可變狀態
  • 快速失敗,並提供有意義的例外
  • 一致的命名與套件結構
  • [QUARKUS]:偏好建置時期而非執行時期處理;盡可能避免執行時期反射

範例

以下各節展示具體的 Spring Boot、Quarkus 及共用 Java 範例,涵蓋命名、不可變性、依賴注入、反應式程式碼、例外處理、專案佈局、日誌、設定與測試。

命名

// PASS:類別/Record:PascalCase
public class MarketService {}
public record Money(BigDecimal amount, Currency currency) {}

// PASS:方法/欄位:camelCase
private final MarketRepository marketRepository;
public Market findBySlug(String slug) {}

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

// PASS:[QUARKUS] JAX-RS 資源命名為 *Resource,而非 *Controller
public class MarketResource {}

// PASS:[SPRING] REST 控制器命名為 *Controller
public class MarketController {}

不可變性

// PASS:偏好 record 與 final 欄位
public record MarketDto(Long id, String name, MarketStatus status) {}

public class Market {
  private final Long id;
  private final String name;
  // 僅有 getter,無 setter
}

// PASS:[QUARKUS] Panache active-record 實體使用 public 欄位(Quarkus 慣例)
@Entity
public class Market extends PanacheEntity {
  public String name;
  public MarketStatus status;
  // Panache 在建置時期產生存取器;此處使用 public 欄位是慣用寫法
}

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

Optional 使用

// PASS:從 find* 方法回傳 Optional
// [SPRING]
Optional<Market> market = marketRepository.findBySlug(slug);

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

// PASS:使用 map/flatMap 而非 get()
return market
    .map(MarketResponse::from)
    .orElseThrow(() -> new EntityNotFoundException("Market not found"));

串流最佳實踐

// PASS:使用串流進行轉換,保持管線簡短
List<String> names = markets.stream()
    .map(Market::name)
    .filter(Objects::nonNull)
    .toList();

// FAIL:避免複雜的巢狀串流;為求清晰,偏好使用迴圈

依賴注入

// PASS:[SPRING] 建構子注入(優於 @Autowired 欄位注入)
@Service
public class MarketService {
  private final MarketRepository marketRepository;

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

// PASS:[QUARKUS] 建構子注入
@ApplicationScoped
public class MarketService {
  private final MarketRepository marketRepository;

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

// PASS:[QUARKUS] 套件私有欄位注入(在 Quarkus 中可接受——避免代理問題)
@ApplicationScoped
public class MarketService {
  @Inject
  MarketRepository marketRepository;
}

// FAIL:[SPRING] 使用 @Autowired 的欄位注入
@Autowired
private MarketRepository marketRepository; // 請使用建構子注入

// FAIL:[QUARKUS] 在需要攔截或延遲初始化時使用 @Singleton
@Singleton // 不可代理——請改用 @ApplicationScoped
public class MarketService {}

反應式模式 [QUARKUS]

// PASS:從反應式端點回傳 Uni/Multi
@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:非阻塞管線組合
public Uni<OrderConfirmation> placeOrder(OrderRequest req) {
  return validateOrder(req)
      .chain(valid -> persistOrder(valid))
      .chain(order -> notifyFulfillment(order));
}

// FAIL:在 Uni/Multi 管線內進行阻塞呼叫
public Uni<Market> find(String slug) {
  Market m = Market.find("slug", slug).firstResult(); // 阻塞——破壞事件迴圈
  return Uni.createFrom().item(m);
}

// FAIL:對同一個 Uni 訂閱超過一次
Uni<Market> shared = fetchMarket(slug);
shared.subscribe().with(m -> log(m));
shared.subscribe().with(m -> cache(m)); // 重複訂閱——請使用 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] 選項 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] 選項 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/... (與 main 對應)

[QUARKUS] Maven/Gradle

src/main/java/com/example/app/
  config/              # @ConfigMapping、@ConfigProperty bean、Producer
  resource/            # JAX-RS 資源(非 "controller")
  service/
  repository/          # PanacheRepository 實作(若不使用 active record)
  domain/              # JPA/Panache 實體、MongoDB 實體
  dto/
  util/
  mapper/              # MapStruct mapper(若使用)
src/main/resources/
  application.properties   # Quarkus 慣例(若使用 quarkus-config-yaml 則支援 YAML)
  import.sql               # Hibernate 開發/測試自動匯入
src/test/java/... (與 main 對應)

格式化與風格

  • 一致使用 2 或 4 個空格(專案標準)
  • 每個檔案一個公開頂層型別
  • 保持方法簡短且專注;提取輔助方法
  • 成員順序:常數、欄位、建構子、公開方法、protected、private

應避免的程式碼壞味道

  • 長參數列表 → 使用 DTO/Builder
  • 深層巢狀 → 提早回傳
  • 魔術數字 → 命名常數
  • 靜態可變狀態 → 偏好依賴注入
  • 無聲的 catch 區塊 → 記錄並處理或重新拋出
  • [QUARKUS]:在應使用 @ApplicationScoped 時使用 @Singleton——破壞代理與攔截
  • [QUARKUS]:混合使用 quarkus-resteasy-reactivequarkus-resteasy(經典)——請選擇一個堆疊
  • [QUARKUS]:在同一個限界上下文內同時使用 Panache active-record 與 repository 模式——請選擇一種

日誌

// [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(預設,建置時期零成本)
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] 替代方案:使用 @Inject 簡化日誌
@Inject
Logger log; // CDI 注入,作用域為宣告類別

Null 處理

  • 僅在無法避免時接受 @Nullable;否則使用 @NonNull
  • 對輸入使用 Bean Validation(@NotNull@NotBlank
  • [QUARKUS]:對 @BeanParam@RestForm 及請求主體參數套用 @Valid

設定

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

// [QUARKUS] @ConfigMapping(型別安全,建置時期驗證)
@ConfigMapping(prefix = "market")
public interface MarketConfig {
  int maxPageSize();
  Duration cacheTtl();
}

// [QUARKUS] 使用 @ConfigProperty 的簡單值
@ConfigProperty(name = "market.max-page-size", defaultValue = "100")
int maxPageSize;

測試預期

共用

  • JUnit 5 + AssertJ 用於流暢斷言
  • Mockito 用於模擬;盡可能避免部分模擬
  • 偏好確定性測試;無隱藏 sleep

[SPRING]

  • @WebMvcTest 用於控制器切片,@DataJpaTest 用於 Repository 切片
  • @SpringBootTest 保留給完整整合測試
  • @MockBean 用於取代 Spring 上下文中的 Bean

[QUARKUS]

  • 純 JUnit 5 + Mockito 用於單元測試(無 @QuarkusTest
  • @QuarkusTest 保留給 CDI 整合測試
  • @InjectMock 用於取代整合測試中的 CDI Bean
  • Dev Services 用於資料庫/Kafka/Redis——當 Dev Services 足夠時,避免手動設定 Testcontainers
  • @QuarkusTestResource 用於自訂外部服務生命週期
// [SPRING] 控制器測試
@WebMvcTest(MarketController.class)
class MarketControllerTest {
  @Autowired MockMvc mockMvc;
  @MockBean MarketService marketService;
}

// [QUARKUS] 整合測試
@QuarkusTest
class MarketResourceTest {
  @InjectMock
  MarketService marketService;

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

// [QUARKUS] 單元測試(無 CDI,無 @QuarkusTest)
@ExtendWith(MockitoExtension.class)
class MarketServiceTest {
  @Mock MarketRepository marketRepository;
  @InjectMocks MarketService marketService;
}

切記:保持程式碼有目的性、型別明確且可觀察。以可維護性為優先,而非微觀最佳化,除非經證明確有必要。