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

소속을 가르는 질문 하나

~14 min · boundaries, design, judgment, decision-record

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

시험

후보 모듈마다 질문은 딱 하나야. 이게 앱이 무엇에 관한 것인지를 알아야 하나?

"두 번 쓰이나"가 아냐. "일반적으로 생겼나"도 아니고. "재사용하면 좋겠나"도 아냐. 쓰는 곳 수는 질문을 던질 계기지 답이 아니야. 앱 다섯이 쓰는데 제품 결정을 품고 있는 모듈은 여전히 그 결정을 소유한 앱 거고, 앱 하나만 쓰는데 아무것도 안 품은 모듈은 두 번째 앱이 필요해진 순간 공용 층 거야.

진짜 후보들한테 돌려보면 유난히 딱딱 갈려.

  • 식별자 발급기 — 타임스탬프 더하기 난수, 정렬 가능, 파싱 없음. 아무것도 모름. 들어옴.
  • 저장 시 줄 단위 암호화 — 키 슬롯 이름을 인자로 받음. 아무것도 모름. 들어옴.
  • 기기 자기 로컬 날짜와 시차를 찍는 기록 시각 도우미. 뭘 기록하는지는 모름. 들어옴.
  • 여정 컨테이너 — 시작과 끝과 레인이 있는 여행. 앱 하나에 대해 전부 알고 있음. 영구히 나감. 두 번째 앱이 기술적으로 쓸 수 있어도 마찬가지.
  • 측정값을 서사로 바꾸는 건강 해석. 도메인이랑 그 윤리를 알고 있음. 나감.
  • 작성기랑 기록 카드 — 앱마다 하나씩 있고, 닮았고, 끝까지 도메인 색이 배어 있음. 나감. 그리고 의도된 갈라짐으로 적어둠. 분기마다 다시 제안 안 나오게.

진짜로 어려웠던 건

키트가 들인 제일 큰 표면은 위임 큐 커널이었어. 일을 줄 세우고, 작업자한테 넘기고, 둘이 같은 일을 못 집게 선점을 쥐고, 결과 둘이 안 부딪히게 착지에 리스를 걸고, 전부를 기록하는 작업장의 기계. 서버 쪽 전이 로직을 가진 첫 편입이었고, 딱 프레임워크처럼 생겼어.

그런데도 시험은 같은 답을 냈어. 커널은 퀘스트도 에피소드도 볼트 항목도 몰라. 일의 주체가 대체 뭐냐는 것부터 앱이 주입해. 작업장마다 열 이름이 다르거든. 추가 열, 착지 의미, 검증 규칙, 애초에 무엇이 유효한 일이냐는 각 앱의 솜씨야. 커널이 쥔 건 독립적으로 쓰인 엔진 셋이 이미 수렴해 있던 모양뿐이야.

그리고 그 수렴이 재밌는 증거야. 엔진 한 쌍은 서로의 배관 모듈을 72–87% 글자 그대로 베낀 사이였어. 그건 누가 베꼈다는 것 말곤 아무것도 증명 안 해. 세 번째는 그 둘이랑 글자 겹침이 4% 정도였어. 다른 세션이 다른 도메인을 위해 따로 쓴 건데도, 같은 라우트 계약, 같은 스키마 모양에 도착해 있었어. 실행 시도를 추적하는 열까지 똑같은 표였고. 복사본 둘은 복사를 증명해. 4% 겹침에서 나온 독립적 수렴은 그 모양이 이름 붙기 전부터 진짜였다는 걸 증명하고.

다툴 만한 편입은 결정만 하면 안 되고 근거까지 적어야 해. 뻔한 건은 기록이 필요 없어. ULID 발급기가 앱을 아는지를 두고 다툴 사람은 앞으로도 없거든. 다툴 만한 건은 반드시 다시 다투게 돼. 맥락이 더 적은 사람이, 더 나쁜 순간에. 근거를 적는 건 격식이 아냐. 다음 다툴 만한 건을 새 논쟁이 아니라 비교로 만드는 장치야.

이 시험이 막아주는 것

막아주는 실패는 미묘해. 어느 한 걸음도 실수처럼 안 보이거든. 90% 일반적이라서 모듈이 들어와. 나머지 10% 는 플래그로 처리하고. 세 번째 앱이 변형이 필요해서 플래그가 열거형이 돼. 네 번째가 그 열거형으로 표현 안 되는 동작이 필요해서 콜백이 붙고. 이제 공용 모듈 안엔 애플리케이션이 뭔지에 대한 작고 안 적힌 모형이 들어앉았어. 모든 앱이 그걸 만족시키게 지어져야 하고, 그걸 바꾸려면 도메인 넷을 동시에 이해해야 해.

걸음마다 국소적으론 다 합리적이었어. 맨 위의 질문이 첫 걸음을 거절했을 거고.

Code

시험 적용, 그리고 떨어졌다는 냄새·python
# PASSES: the module cannot tell which app called it.
def new_id(ts_ms: int | None = None) -> str:
    """Timestamp + randomness, lexicographically sortable."""
    ...

def encrypt_line(text: str, *, key_slot: str) -> str:
    """The app names its own key slot; the algorithm does not care."""
    ...

def claims_ddl(subject_col: str) -> str:
    """The app names what a job is ABOUT. The kernel only knows that
    exactly one claim per subject may be active at a time."""
    ...


# FAILS: a shared module that has learned what applications are.
# Nothing here is wrong on its own line. Read it as a whole and it
# is a framework: every app must now be expressible in this vocabulary.
def render_record_card(record, *, app: str):
    if app == "travel":
        title = record["journey"]["name"]        # domain leaked in
    elif app == "journal":
        title = record["date"]                   # ...and again
    elif app == "health":
        title = f"{record['module']} - {record['reading']}"
    else:
        raise ValueError(f"unknown app: {app}")   # <- the smell:
                                                  # the kit can now be
                                                  # WRONG about an app
    ...


# The rewrite that passes: the app supplies the knowledge, the kit
# supplies the mechanism. Note that the kit can no longer be wrong
# about an app, because it no longer knows any app exists.
def render_record_card_ok(record, *, title_of):
    title = title_of(record)
    ...


# A canary for the shared repo's own tests - and note carefully what
# it does NOT assert. Kit modules legitimately NAME consumers in
# provenance docstrings ("extracted from X, Y and Z"), because a
# comment recording where code came from is not knowledge the code
# acts on. What must never appear is a consumer name that the RUNNING
# CODE branches on.
def test_no_kit_module_branches_on_a_consumer(kit_sources, consumers):
    for path, text in kit_sources.items():
        code = strip_comments_and_docstrings(text)
        for name in consumers:
            assert name.lower() not in code.lower(), (
                f"{path} names {name} in executable code"
            )

# And even that is a floor, not the rule. The rule is the question at
# the top - does this module need to know what the app is about? - and
# a module can fail it without ever spelling an app's name, by taking
# a parameter whose VALUES are the apps.

External links

Exercise

네가 다루는 코드베이스에서 제일 '당연히 공용인' 모듈 셋을 골라서 각각에 질문을 돌려. 이게 앱이 무엇에 관한 것인지 알아야 하나? 그다음 각각에서 네 앱, 환경, 테넌트 이름을 grep 해. 실행되는 코드에서 걸리는 곳은 전부 지식이 공용 층으로 샌 곳이야. 출처 주석이나 docstring 안에서 걸린 건 위 카나리아가 일부러 건너뛰는 정당한 경우고. 그중 하나를 골라서, 대신 뭘 주입할지 적어봐.
Hint
grep 은 시끄러운 것만 잡아. 조용한 건 이름이 아니라 값이야. 호출자 하나한테만 맞는 기본값, 어떤 제품 요구에서 나온 마법의 숫자, 화면 하나에 맞춘 정렬. 그런 건 grep 말고 질문이 필요해.

Progress

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

댓글 0

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

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