Armeria(4)
오늘 배운 것
1. 대규모 프로젝트의 빌드 최적화
대형 프로젝트에서 ./gradlew build
를 그냥 하게 되면 30분 동안 빌드하는 걸 보고 있어야 한다. 그러나 ./gradlew build --parallel
를 하면 병렬 빌드를 진행하면서 시간이 크게 단축된다.
원리는 다음과 같다:
Gradle은 빌드를 시작할 때, 모든 작업(Task)들의 의존 관계를 분석하여 ‘작업 그래프(Task Graph)’를 생성한다. 예를 들어 compileJava
는 generateSources
이후에 실행되어야 한다는 식의 순서를 모두 파악한다.
--parallel
옵션이 없으면, Gradle은 이 그래프를 기반으로 하나의 작업자(Worker) 스레드를 사용해 순차적으로 작업을 처리한다.--parallel
옵션을 사용하면, Gradle은 작업 그래프를 보고 서로 의존성이 없는 독립적인 작업들(예::core
모듈의 컴파일과:eureka
모듈의 컴파일)을 동시에 여러 개의 작업자 스레드에 할당하여 병렬로 실행한다. 이는 마치 한 명의 요리사가 순서대로 요리하는 것과 여러 명의 요리사가 각자 다른 요리를 동시에 만드는 것의 차이와 같다.
그러나 조심해야 하는 점도 있다: 비록 Gradle 빌드 시 순서를 결정하는 명시적인 설정들이 있지만, 동시성 프로그래밍의 어려움 때문에 순서가 꼬여 빌드 결과가 달라지는 일이 생길 수 있다.
- Gradle의 순서 제어 설정: Gradle은 태스크 간의 순서를 명확히 하기 위한 여러 장치를 제공한다.
dependsOn
: 가장 기본적인 의존성 설정.B.dependsOn(A)
는 A 태스크가 성공적으로 끝나야만 B 태스크를 시작할 수 있음을 의미한다. (예:compileJava.dependsOn(generateSources)
)mustRunAfter
/shouldRunAfter
:dependsOn
보다 약한 순서 제약.B.mustRunAfter(A)
는 A와 B가 둘 다 실행될 경우, B를 A보다 나중에 실행하라는 의미다. A에 대한 의존성은 없어서 B만 단독 실행될 수도 있다.finalizedBy
:A.finalizedBy(B)
는 A 태스크가 성공하든 실패하든 상관없이, A가 끝나면 항상 B를 실행하라는 의미다. 주로 정리(cleanup) 작업에 사용된다.
- 병렬 빌드의 위험성:
- 잘못된 의존성 설정: 만약
dependsOn
으로 명시해야 할 관계가 누락된 경우, 순차 빌드에서는 우연히 순서가 맞아 성공했지만 병렬 빌드에서는 순서가 뒤바뀌어 실패할 수 있다. - 공유 자원 경합 (Race Condition): 여러 태스크가 동시에 같은 파일에 쓰거나, 같은 테스트용 포트를 점유하려고 시도하면 충돌이 발생하여 빌드가 실패할 수 있다.
- 잘못된 의존성 설정: 만약
2. Armeria 프로젝트의 코드 스타일과 문서화
Armeria 코딩을 하고 빌드하는데 작업 시간 못지않게 코딩 스타일을 맞추고 적절히 문서화하는 데 시간이 오래 걸리는 것 같다. IntelliJ의 CheckStyle-IDEA
플러그인을 통해 그들이 사전에 만든 스타일(settings.jar
)을 적용했는데, 대표적인 규약은 다음과 같다.
OneTopLevelClass
: 하나의.java
파일에는 반드시 하나의 최상위 클래스만 존재해야 한다.- 이유: 클래스
MyService
는MyService.java
파일에 있다는 예측 가능성을 보장하여 프로젝트의 탐색과 유지보수를 쉽게 만든다.
- 이유: 클래스
EqualsHashCode
:equals()
메소드를 오버라이드하는 클래스는 반드시hashCode()
메소드도 오버라이드해야 한다.- 이유:
HashMap
,HashSet
등 해시 기반 컬렉션이 오동작하는 심각한 버그를 예방하기 위함이다. 이는 Java의 근본적인 객체 계약(Object Contract)이다.
- 이유:
HideUtilityClassConstructor
:static
메소드만으로 구성된 유틸리티 클래스는private
생성자를 가져야 한다.- 이유: 유틸리티 클래스는 인스턴스화할 필요가 없다는 의도를 명확히 하고,
new MyUtils()
와 같은 불필요한 객체 생성을 컴파일 단계에서부터 원천 차단한다.
- 이유: 유틸리티 클래스는 인스턴스화할 필요가 없다는 의도를 명확히 하고,
NewlineAtEndOfFile
: 모든 텍스트 파일은 마지막에 개행 문자(newline)로 끝나야 한다.- 이유: POSIX 표준 준수로
cat
,wc
등 커맨드라인 도구와의 호환성을 보장하고, Git에서diff
와blame
기록을 깔끔하고 정확하게 유지하여 협업을 용이하게 한다.
- 이유: POSIX 표준 준수로
SummaryJavadoc
:@return the ...
와 같이 의미 없는 상투적인 Javadoc 요약을 금지한다.- 이유: API 사용자에게 실질적인 정보를 주는, 품질 높은 문서를 작성하도록 유도하여 프로젝트 전체의 코드 가독성과 유지보수성을 높인다.