제목, 태그, 카테고리로 검색

모든 글
약 12분 분량 프로젝트/타이미

코드 품질 관리에 대하여

목차

일관성 있는 코드를 유지하고 일정 수준 이상의 품질을 보장하기 위해 도입했어요.

도구 선택

SonarQube(올인원)와 개별 도구 조합을 비교했어요. SonarQube는 대시보드와 이력 관리가 편하지만, SonarCloud는 private 레포에서 유료이고 SonarQube 셀프호스팅은 3GB+ RAM이 필요합니다. 1인 개발에 private 레포로 운영하는 상황에서 무료로 쓸 수 있는 개별 도구 조합(Spotless + Checkstyle + SpotBugs + JaCoCo + Codecov)을 선택했어요.


선택한 도구들

1. Spotless - 코드 포맷팅

역할: 코드 스타일 자동 통일

IDE 포맷터나 EditorConfig만으로는 Java의 import 순서, 중괄호 위치 같은 언어별 규칙까지 통일하기 어려워요. Spotless는 Google Java Format을 Gradle 빌드에 통합해서 CI에서 자동 검증하고, 로컬에서 ./gradlew spotlessApply 한 번으로 자동 수정됩니다.

동작 방식:

Terminal window
# 포맷팅 검사 (CI에서 실행)
./gradlew spotlessCheck
# 자동 포맷팅 (로컬에서 실행)
./gradlew spotlessApply

설정 (build.gradle.kts): spotless-config


2. Checkstyle - 코딩 컨벤션

역할: 코딩 규칙 준수 검사

SpotBugs는 바이트코드 분석으로 버그 패턴을 찾고, Checkstyle은 소스코드에서 코딩 컨벤션을 검사해요. 역할이 다르므로 둘 다 사용합니다. PMD(코드 복잡도, 중복 코드 탐지)는 SpotBugs가 일부 역할을 대체하므로 제외했어요. Google Java Style Guide 기반 규칙을 적용하고, suppressions 파일로 특정 클래스를 예외 처리할 수 있습니다.

주요 검사 항목:

  • 네이밍 규칙 (camelCase, CONSTANT_CASE)
  • import 규칙 (star import 금지, 사용하지 않는 import)
  • 블록 규칙 (중괄호 필수, 빈 블록 금지)
  • 메서드 길이 (50줄 제한)
  • 파라미터 개수 (7개 제한, 생성자 제외)

Star Import(import .*) 금지 이유

star-import-problem

왜 금지하는가?

네임스페이스 충돌

java.util.Datejava.sql.Date를 동시에 star import하면 Date 클래스가 어느 패키지인지 모호해져요. 컴파일러가 에러를 내거나, 의도하지 않은 클래스가 사용될 수 있습니다.

의존성 불명확

코드만 보고 어떤 클래스를 실제로 사용하는지 파악할 수 없어요. 코드 리뷰나 디버깅 시 불편합니다.

라이브러리 업그레이드 위험

라이브러리 새 버전에서 추가된 클래스가 기존 코드의 클래스명과 충돌할 수 있어요. 명시적 import는 이 위험을 방지합니다.

참고: Google Java Style Guide - Import statements

예외: 테스트 코드의 static import는 가독성을 위해 허용하기도 함 static-import-exception

Suppressions: DTO, Entity, Config, Test 클래스는 일부 규칙을 완화했어요. 이들은 구조적으로 많은 필드나 긴 설정을 가질 수밖에 없기 때문이에요.


3. SpotBugs - 버그 탐지

역할: 잠재적 버그 패턴 탐지

FindBugs가 2015년 이후 업데이트가 중단되어 그 후속인 SpotBugs를 선택했어요. Google의 Error Prone(컴파일 타임 버그 탐지)도 검토했지만, Gradle 설정이 복잡하고 SpotBugs가 더 많은 버그 패턴을 탐지합니다. 바이트코드 분석으로 소스코드만으로는 찾기 어려운 버그를 잡아줘요.

탐지하는 버그 유형:

  • Null 포인터 역참조 가능성
  • 리소스 누수 (스트림, 커넥션)
  • 동시성 문제
  • 성능 안티패턴
  • 보안 취약점

설정: spotbugs-config

Exclude 설정: DTO, Entity의 getter가 가변 객체를 반환하는 경고(EI_EXPOSE_REP)는 의도된 동작이므로 제외했어요.


4. JaCoCo - 테스트 커버리지

역할: 코드 커버리지 측정 및 검증

Cobertura는 2015년 이후 업데이트가 느려졌고, IntelliJ 내장 커버리지는 IDE에서만 확인 가능해요. JaCoCo는 Eclipse Foundation에서 관리하면서 최신 Java 버전을 빠르게 지원하고, Gradle 통합과 Codecov 연동이 잘 되어 CI 자동화에 적합합니다.

커버리지 기준: jacoco-coverage-config

왜 60%/70%인가?

40% 미만은 테스트가 거의 없는 상태고, 60%면 핵심 비즈니스 로직은 테스트된 상태로 초기 프로젝트에서 현실적인 목표예요. 80%면 대부분의 코드가 테스트된 안정화 단계이고, 100%는 getter/setter까지 모두 테스트해야 하므로 ROI가 낮아 현실적이지 않습니다.

참고: Martin Fowler - Test Coverage


5. Codecov - 커버리지 대시보드

역할: 커버리지 시각화 및 이력 관리

Coveralls도 무료 커버리지 대시보드를 제공하지만, Codecov가 PR 코멘트가 더 깔끔하고 GitHub Actions 통합이 쉬워요. SonarCloud는 private 레포에서 유료이고, 이미 개별 도구를 쓰고 있어서 커버리지 대시보드만 필요했습니다. Codecov는 개인 프로젝트 무료이고 JaCoCo XML 리포트를 업로드하면 바로 동작해요.

Codecov vs Coveralls 실제 비교

codecov-vs-coveralls

위 이미지에서 중요한 차이가 보여요. Codecov는 비율이 아닌 정확한 줄 수로 표시하고, 패치 밖에서 바뀐 커버리지 여부와 패치 안의 코드 커버리지를 구분해서 보여줍니다.

Coveralls는 커버리지가 올랐는지 내렸는지만 보여줘서 숫자 게임처럼 느껴져요. 실제로 패치가 모두 커버되는지 확인하려면 웹사이트에 직접 방문해야 합니다. 반면 Codecov는 PR 코멘트에서 패치에 대한 상세 정보를 바로 확인할 수 있어요.

출처: Codecov vs Coveralls

Codecov 제공 기능:

  • PR별 커버리지 변화 시각화
  • 커버리지 트렌드 그래프
  • 파일/디렉토리별 커버리지 맵
  • Slack/GitHub 통합

가격 정책:

  • Public 레포: 무료
  • Private 레포 (1~5명): 무료
  • Private 레포 (6명 이상): 유료 플랜

private 레포이지만 혼자 만들고 있으니 무료 범위에 해당해요.

참고: Codecov Pricing

설정 방법:

  1. codecov.io에서 GitHub 연동
  2. Repository Settings > Secrets에 CODECOV_TOKEN 추가 (Private 레포는 토큰 필수)
  3. codecov.yml로 세부 설정

codecov.yml 파일 위치:

모노레포 구조에서 codecov.yml은 반드시 레포지토리 루트에 위치해야 해요. backend/ 폴더에 넣으면 인식되지 않습니다.

  • 허용 위치: /, /dev/, /.github/
  • 서브 디렉토리 (예: /backend/)에는 배치 불가

참고: Codecov YAML Reference


CI 파이프라인 구조

ci-pipeline

단계 분리 이유:

  • 코드 품질 검사가 빠르게 실패하면 빌드 시간 절약
  • 어느 단계에서 문제인지 명확히 파악 가능

로컬 개발 워크플로우

커밋 전 검사

Terminal window
# 포맷팅 자동 적용
./gradlew spotlessApply
# 전체 검사 (CI와 동일)
./gradlew check

IDE 설정 권장

IntelliJ IDEA:

  1. File > Settings > Editor > Code Style > Java
  2. Scheme 옆 톱니바퀴 > Import Scheme > Google Style

주요 Gradle 태스크

태스크설명
spotlessCheck포맷팅 검사
spotlessApply포맷팅 자동 적용
checkstyleMain메인 코드 Checkstyle
checkstyleTest테스트 코드 Checkstyle
spotbugsMain메인 코드 SpotBugs
test테스트 실행
jacocoTestReport커버리지 리포트 생성
jacocoTestCoverageVerification커버리지 기준 검증
check위 모든 검사 실행

버전 호환성 (Java 25 + Spring Boot 4)

도구버전Java 25 지원비고
Spring Boot4.0.1OJava 17~25 공식 지원
Spotless7.0.2OGoogle Java Format
Checkstyle12.3.0O10.x는 Java 22까지만 지원
SpotBugs Plugin6.4.8O6.0.x는 Java 25 미지원
SpotBugs Annotations4.9.8O
JaCoCo0.8.14O0.8.12는 Java 22까지만 지원

Java 25 LTS를 사용하려면 위 버전 이상을 사용해야 해요.


비용 요약

도구비용비고
Spotless무료Gradle 플러그인
Checkstyle무료Gradle 내장
SpotBugs무료Gradle 플러그인
JaCoCo무료Gradle 내장
Codecov무료개인/오픈소스 무료
GitHub Actions무료Private 레포 2000분/월

총 비용: $0


참고 자료

Introduced to maintain consistent code and guarantee a certain level of quality.

Tool Selection

I compared SonarQube (all-in-one) with a combination of individual tools. SonarQube offers a convenient dashboard and history management, but SonarCloud is paid for private repos, and self-hosting SonarQube requires 3GB+ RAM. As a solo developer running a private repo, I chose a free combination of individual tools (Spotless + Checkstyle + SpotBugs + JaCoCo + Codecov).


Selected Tools

1. Spotless - Code Formatting

Role: Automatically unify code style

IDE formatters or EditorConfig alone cannot enforce language-specific rules like Java import ordering or brace placement. Spotless integrates Google Java Format into the Gradle build for automatic CI verification, and locally everything is auto-fixed with a single ./gradlew spotlessApply.

How it works:

Terminal window
# Check formatting (run in CI)
./gradlew spotlessCheck
# Auto-format (run locally)
./gradlew spotlessApply

Configuration (build.gradle.kts): spotless-config


2. Checkstyle - Coding Conventions

Role: Verify coding rule compliance

SpotBugs uses bytecode analysis to find bug patterns, while Checkstyle inspects source code for coding convention violations. Since their roles differ, both are used. PMD (code complexity, duplicate code detection) was excluded because SpotBugs partially covers its role. Rules based on the Google Java Style Guide are applied, and a suppressions file can exempt specific classes.

Key checks:

  • Naming rules (camelCase, CONSTANT_CASE)
  • Import rules (no star imports, no unused imports)
  • Block rules (mandatory braces, no empty blocks)
  • Method length (50-line limit)
  • Parameter count (7-parameter limit, excluding constructors)

Why Star Import (import .*) Is Prohibited

star-import-problem

Why prohibit it?

Namespace Collision

If you star-import both java.util.Date and java.sql.Date, it becomes ambiguous which package Date refers to. The compiler may throw an error, or an unintended class could be used.

Unclear Dependencies

You cannot determine which classes are actually used just by looking at the code. This makes code reviews and debugging inconvenient.

Library Upgrade Risk

A new version of a library may introduce classes that conflict with existing class names in your code. Explicit imports prevent this risk.

Reference: Google Java Style Guide - Import statements

Exception: Static imports in test code are sometimes allowed for readability static-import-exception

Suppressions: Rules are relaxed for DTO, Entity, Config, and Test classes. These structurally tend to have many fields or lengthy configurations.


3. SpotBugs - Bug Detection

Role: Detect potential bug patterns

FindBugs has not been updated since 2015, so its successor SpotBugs was chosen. Google’s Error Prone (compile-time bug detection) was also considered, but its Gradle setup is complex and SpotBugs detects more bug patterns. Through bytecode analysis, it catches bugs that are hard to find from source code alone.

Types of bugs detected:

  • Potential null pointer dereference
  • Resource leaks (streams, connections)
  • Concurrency issues
  • Performance anti-patterns
  • Security vulnerabilities

Configuration: spotbugs-config

Exclude configuration: Warnings about DTO/Entity getters returning mutable objects (EI_EXPOSE_REP) were excluded since this is intentional behavior.


4. JaCoCo - Test Coverage

Role: Measure and verify code coverage

Cobertura has had slow updates since 2015, and IntelliJ’s built-in coverage can only be viewed within the IDE. JaCoCo is maintained by the Eclipse Foundation, quickly supports the latest Java versions, and integrates well with Gradle and Codecov, making it suitable for CI automation.

Coverage thresholds: jacoco-coverage-config

Why 60%/70%?

Below 40% means tests are virtually nonexistent. At 60%, core business logic is tested, which is a realistic goal for an early-stage project. At 80%, most code is tested and the project is in a stabilization phase. 100% requires testing even getters/setters, giving low ROI and being impractical.

Reference: Martin Fowler - Test Coverage


5. Codecov - Coverage Dashboard

Role: Visualize coverage and manage history

Coveralls also provides a free coverage dashboard, but Codecov has cleaner PR comments and easier GitHub Actions integration. SonarCloud is paid for private repos, and since individual tools were already in use, only a coverage dashboard was needed. Codecov is free for personal projects and works immediately upon uploading JaCoCo XML reports.

Codecov vs Coveralls Comparison

codecov-vs-coveralls

An important difference is visible in the image above. Codecov displays exact line counts rather than just percentages, and distinguishes between coverage changes outside the patch and code coverage within the patch.

Coveralls only shows whether coverage went up or down, making it feel like a numbers game. To verify whether the patch is fully covered, you have to visit the website directly. In contrast, Codecov lets you check detailed patch information right in the PR comment.

Source: Codecov vs Coveralls

Features provided by Codecov:

  • Per-PR coverage change visualization
  • Coverage trend graphs
  • File/directory coverage map
  • Slack/GitHub integration

Pricing:

  • Public repos: Free
  • Private repos (1-5 users): Free
  • Private repos (6+ users): Paid plans

It is a private repo, but since I am the sole developer, it falls within the free tier.

Reference: Codecov Pricing

Setup steps:

  1. Connect GitHub at codecov.io
  2. Add CODECOV_TOKEN to Repository Settings > Secrets (token required for private repos)
  3. Fine-tune settings with codecov.yml

codecov.yml file location:

In a monorepo structure, codecov.yml must be placed at the repository root. Placing it in the backend/ folder will not be recognized.

  • Allowed locations: /, /dev/, /.github/
  • Cannot be placed in subdirectories (e.g., /backend/)

Reference: Codecov YAML Reference


CI Pipeline Structure

ci-pipeline

Reasons for stage separation:

  • Saves build time when code quality checks fail quickly
  • Makes it clear which stage has the problem

Local Development Workflow

Pre-commit Checks

Terminal window
# Auto-apply formatting
./gradlew spotlessApply
# Full check (same as CI)
./gradlew check

IntelliJ IDEA:

  1. File > Settings > Editor > Code Style > Java
  2. Click the gear icon next to Scheme > Import Scheme > Google Style

Key Gradle Tasks

TaskDescription
spotlessCheckCheck formatting
spotlessApplyAuto-apply formatting
checkstyleMainCheckstyle for main code
checkstyleTestCheckstyle for test code
spotbugsMainSpotBugs for main code
testRun tests
jacocoTestReportGenerate coverage report
jacocoTestCoverageVerificationVerify coverage thresholds
checkRun all checks above

Version Compatibility (Java 25 + Spring Boot 4)

ToolVersionJava 25 SupportNotes
Spring Boot4.0.1YesOfficially supports Java 17-25
Spotless7.0.2YesGoogle Java Format
Checkstyle12.3.0Yes10.x only supports up to Java 22
SpotBugs Plugin6.4.8Yes6.0.x does not support Java 25
SpotBugs Annotations4.9.8Yes
JaCoCo0.8.14Yes0.8.12 only supports up to Java 22

You must use the versions listed above or higher to use Java 25 LTS.


Cost Summary

ToolCostNotes
SpotlessFreeGradle plugin
CheckstyleFreeBuilt into Gradle
SpotBugsFreeGradle plugin
JaCoCoFreeBuilt into Gradle
CodecovFreeFree for individuals/open-source
GitHub ActionsFree2000 min/month for private repos

Total cost: $0


References

Author
작성자 @범수

오늘의 노력이 내일의 전문성을 만든다고 믿습니다.

댓글