Armeria(4)

오늘 배운 것

1. 대규모 프로젝트의 빌드 최적화

대형 프로젝트에서 ./gradlew build를 그냥 하게 되면 30분 동안 빌드하는 걸 보고 있어야 한다. 그러나 ./gradlew build --parallel를 하면 병렬 빌드를 진행하면서 시간이 크게 단축된다.

원리는 다음과 같다: Gradle은 빌드를 시작할 때, 모든 작업(Task)들의 의존 관계를 분석하여 ‘작업 그래프(Task Graph)’를 생성한다. 예를 들어 compileJavagenerateSources 이후에 실행되어야 한다는 식의 순서를 모두 파악한다.

  • --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 파일에는 반드시 하나의 최상위 클래스만 존재해야 한다.
    • 이유: 클래스 MyServiceMyService.java 파일에 있다는 예측 가능성을 보장하여 프로젝트의 탐색과 유지보수를 쉽게 만든다.
  • EqualsHashCode: equals() 메소드를 오버라이드하는 클래스는 반드시 hashCode() 메소드도 오버라이드해야 한다.
    • 이유: HashMap, HashSet 등 해시 기반 컬렉션이 오동작하는 심각한 버그를 예방하기 위함이다. 이는 Java의 근본적인 객체 계약(Object Contract)이다.
  • HideUtilityClassConstructor: static 메소드만으로 구성된 유틸리티 클래스는 private 생성자를 가져야 한다.
    • 이유: 유틸리티 클래스는 인스턴스화할 필요가 없다는 의도를 명확히 하고, new MyUtils()와 같은 불필요한 객체 생성을 컴파일 단계에서부터 원천 차단한다.
  • NewlineAtEndOfFile: 모든 텍스트 파일은 마지막에 개행 문자(newline)로 끝나야 한다.
    • 이유: POSIX 표준 준수로 cat, wc 등 커맨드라인 도구와의 호환성을 보장하고, Git에서 diffblame 기록을 깔끔하고 정확하게 유지하여 협업을 용이하게 한다.
  • SummaryJavadoc: @return the ... 와 같이 의미 없는 상투적인 Javadoc 요약을 금지한다.
    • 이유: API 사용자에게 실질적인 정보를 주는, 품질 높은 문서를 작성하도록 유도하여 프로젝트 전체의 코드 가독성과 유지보수성을 높인다.

results matching ""

    No results matching ""