본문 바로가기
C.W.K.
Stream
Lesson 01 of 05 · published

머리말을 떼고 본문을 비교해

~13 min · drift, tooling, testing, implementation

Level 0흩어진 부품
0 XP0/48 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete

스크립트 하나, 모드 둘

강제 기계 전부가 플래그 달린 스크립트 하나야. 배달 모드에선 매니페스트를 걸으면서 키트 원본을 소비자마다 선언된 대상 경로에 써. 검사 모드에선 같은 매니페스트를 걸으면서 쓰는 대신 비교해. 본문이 원본이랑 다른 대상 전부, 그리고 있어야 하는데 없는 대상 전부를 보고하고.

둘에 프로그램 하나를 쓰는 건 아끼려는 게 아냐. 정확성 논증이야. 배달이랑 검사가 따로 구현이면 "기대되는 내용"이 뭔지에 대해 서로 안 맞을 수 있고, 그러면 검사는 배달이 절대 안 만드는 뭔가를 검증하게 돼. 코드 경로를 같이 쓴다는 건 검사가 배달이 답하는 바로 그 질문을 묻는다는 뜻이야.

실패 종류 둘, 따로 보고

검사 모드는 어긋남이랑 없음을 구분해. 갈라둘 값이 있어. 원인도 처방도 다르거든. 어긋남은 파일이 있는데 본문이 안 맞는 거야. 누가 복사본을 고쳤거나, 키트 변경 뒤에 배달을 안 돌렸거나. 없음은 매니페스트가 이 소비자한테 이 파일이 있어야 한다는데 없는 거야. 보통 배달을 돌리기 전에 새 소비자를 대상 목록에 넣은 경우고.

둘 다 실패지만 메시지가 다르고, 검사의 값 대부분은 메시지에 살아. "키트 원본이랑 다름"은 diff 를 보라는 말이야. "키트는 이게 복사본으로 있을 걸 기대함"은 배달을 돌리라는 말이고. 둘을 뭉뚱그린 실패로 만들면 모든 사건이 같은 조사로 시작해.

배달이랑 검사는 구현 하나여야 해. 검증 대상이랑 따로 쓰인 검증은 결국 그 대상에서 흘러가고, 대상에서 흘러간 검사는 검사 없느니만 못해. 이제 아무도 안 묻는 질문에 대고 자신 있는 초록불을 내놓거든. 검증기를 지을 땐 생산자 코드 경로를 다시 구현하지 말고 재사용할 방법부터 찾아.

리뷰할 때 사주는 것

남의 변경을 읽을 때 나타나는 조용한 이득이 하나 있어. 소비자 저장소에서 복사본 파일을 건드리는 diff 는 즉시 수상해. 머리말이 여기서 고치지 말라고 하고, 어긋남 테스트가 잡았어야 하니까. 그래서 리뷰어는 어느 파일이 공용인지 알 필요가 없어. 머리말이 알려주고, 테스트가 강제하니까.

그게 관례랑 기계의 차이야. 관례는 리뷰어마다 어느 파일이 금지인지 기억할 것을 요구해. 기계는 리뷰어가 변경의 재밌는 부분을 보면서, 지루한 불변량은 이미 지켜지고 있다고 믿어도 되게 해줘.

Code

배달과 검사가 '기대값' 정의 하나를 같이 쓰기·python
def run(check: bool, only_repo: str | None = None) -> int:
    manifest = load_manifest()
    drifted: list[str] = []
    missing: list[str] = []
    deployed = unchanged = 0

    for entry in manifest["files"]:
        source = KIT_ROOT / entry["source"]
        raw = transform(source.read_text(), entry.get("transform"))

        for repo, target_rel in entry["targets"].items():
            if only_repo and repo != only_repo:
                continue
            repo_root = consumers[repo]
            if not repo_root.is_dir():
                # A sibling that is not checked out on this machine is
                # skipped, never failed. Cold clones must still work.
                print(f"consumer repo not found, skipping: {repo}")
                continue

            target = repo_root / target_rel
            # ONE definition of expected, used by both modes:
            expected_body = substitute(raw, entry.get("vars", {}).get(repo))
            expected_file = header_for(entry["source"], target, kit_sha) \
                + expected_body

            if check:
                if not target.is_file():
                    missing.append(f"{repo}:{target_rel}")     # different
                elif strip_header(target.read_text()) != expected_body:
                    drifted.append(f"{repo}:{target_rel}")     # causes
                continue

            if target.is_file() and \
                    strip_header(target.read_text()) == expected_body:
                unchanged += 1
                continue
            target.parent.mkdir(parents=True, exist_ok=True)
            target.write_text(expected_file)
            deployed += 1

    if check:
        for item in drifted:
            print(f"DRIFT: {item} differs from the kit source")
        for item in missing:
            print(f"MISSING: {item} (the kit expects it vendored)")
        return 1 if (drifted or missing) else 0

    print(f"{deployed} deployed, {unchanged} already current")
    return 0

External links

Exercise

네 세계에서 똑같아야 하는 파일 한 쌍을 골라. 환경마다 복제된 설정, 서비스 둘에 거울처럼 있는 스키마, 언어 둘에 있는 상수 목록. 스무 줄짜리 검사기를 써. 둘 다 읽고, 정당하게 달라도 되는 부분을 정규화하고, 나머지를 비교하고, 처방을 이름 댄 메시지를 찍어. 그다음 그걸 깨뜨릴 확률이 제일 높은 사람이 실제로 돌리는 테스트에 물려.
Hint
어려운 건 비교가 아니라 뭐가 정당하게 달라도 되는지 정하는 거야. 그 목록을 머리 말고 검사기 안에 명시적으로 적어. 그게 두 파일 사이 관계의 실제 명세고, 미래 독자가 필요로 할 물건이야.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인 — 댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.