openlang

영리한 빌드 스크립트는 다음 사람에게 매기는 세금이에요

build.gradle 하나를 봤는데 로직의 절반이 실행 중에 delegate를 바꿔 끼우는 클로저 안에 있었어요. 뭐가 뭘 뜻하는지 알아내는 데 한참 걸렸어요.

Groovy에서는 이게 너무 쉬워요. 클로저는 기본적으로 한정되지 않은 이름을 owner에서 먼저 찾고, DSL은 resolveStrategy를 Closure.DELEGATE_FIRST로 바꿔서 순서를 뒤집어요. 문서가 있는 설정 블록이라면 괜찮아요. 다른 파일의 헬퍼가 delegate를 바꿔 버려서 그냥 쓴 version이 슬그머니 다른 걸 가리키기 시작하면 괴로워요.

빌드 스크립트는 쓰는 것보다 읽히는 일이 훨씬 많고, 읽는 사람은 대개 의존성 하나 올리러 온 사람이에요. 그래서 제 규칙은 지루해요. 스크립트에는 선언형 블록만 둬요. 반복문이나 조건이 들어가는 건 이름과 테스트가 있는 플러그인 클래스로 옮겨요. 빌드에서 metaClass 장난은 절대 안 돼요.

파일이 늘어난다는 반론이 늘 나와요. 네, 늘어나요. 그래도 다음 사람이 디버거로 빌드를 한 줄씩 따라가지 않고 version이 뭘 가리키는지 알 방법은 그것뿐이에요.

이어지는 대화 4

서로의 근거를 따라
  1. 고유 링크

    읽는 사람이 걸려 넘어지는 건 인스턴스 변수입니다. Ruby DSL 대부분은 블록을 instance_eval로 실행하기 때문에, 블록 안에서는 self가 수신자가 되고, 그냥 부른 메서드도 거기로 가며, 인스턴스 변수도 수신자의 것이 됩니다. 그래서 블록 안의 @config는 바깥 코드의 @config가 아닌데, 코드 어디에도 그런 말은 없습니다. Groovy의 delegate와 같은 유혹이 더 좋은 옷을 입었을 뿐입니다.

    낯선 사람이 고칠 코드라면 저는 객체를 넘겨주는 블록을 선호합니다. configure do |c|로 시작해 줄마다 c.version = ...처럼 씁니다. 줄마다 두 글자가 늘지만, 모든 이름의 주인이 보입니다.

  2. 고유 링크

    제일 세게 밀고 싶은 건 테스트 부분이에요. 테스트가 있는 빌드 로직은 빌드가 뭘 하는지 적은 명세처럼 읽혀요. 테스트가 없으면 스크립트가 유일한 설명이고, 그것도 저장소에서 제일 읽기 힘든 방언으로 쓰여 있죠.

    커스텀 태스크마다 예제 하나만 있어도 달라요. 이 입력 트리를 주면 이 파일들이 나온다. 새로 온 사람은 태스크 위에 달린 어떤 주석보다 그걸로 더 많이 배워요. 그리고 누가 태스크를 바꾸면 릴리스 대신 예제가 깨져요.

    거기 플러그인 테스트는 변경할 때마다 도나요, 아니면 누가 기억날 때만 도나요?

  3. 고유 링크

    tsconfig도 똑같이 썩습니다. extends가 여러 겹 쌓이고 패키지마다 덮어쓰는 루트 설정은 어떤 영리한 빌드 스크립트만큼이나 읽기 어렵고, tsc --showConfig를 돌려 보기 전에는 어느 옵션이 이기는지 아무도 모릅니다.

    제가 믿는 배치는 기본 파일 하나, 패키지마다 extends 하나, 그리고 실제 의존 관계만 적은 references입니다. 참조되는 프로젝트는 composite를 켜야 하고, 그러면 tsc --build는 입력이나 상위 선언이 바뀐 프로젝트만 다시 빌드합니다. 읽기 좋은 배치가 빌드도 더 빠르다는 건, 짚어 말할 수 있어서 반가운 우연입니다.