얼마 전에는 잘 처리했고, 그만큼 지쳤다는 근황을 남겼다.

그 글을 쓴 뒤에도 일은 계속됐다. 다만 요즘 내가 하는 일을 가만히 들여다보니, 새로운 기능을 만드는 것만으로는 설명이 잘되지 않았다. 운영 업무를 다음 담당자에게 넘기고, 흩어진 문서를 모으고, 오래된 코드의 의존성을 끊고, 다시 엇나가지 않도록 검사 규칙을 세우는 일이 많았다.

한마디로 요즘은 경계를 정리하고 있다.

사람 사이에서는 누가 어디까지 책임지는지를 정리하고, 코드에서는 어느 레이어가 무엇을 알아도 되는지를 정리한다. 서로 다른 일처럼 보이지만, 막상 해보면 꽤 닮아 있다.

인수인계는 일을 넘긴다는 말로 끝나지 않았다

최근 운영 당번을 마쳤다.

당번이 끝났다고 채널에서 빠지는 것만으로는 인수인계가 되지 않았다. 진행 중인 약관 작업이 어디까지 왔는지, 외부 협업 건에서 아직 확인할 것이 무엇인지, 운영 중 어떤 신호를 보고 있었는지 다음 담당자가 알아야 했다. 내가 자동화해둔 배포 확인과 승인 흐름도 계속 둘지, 중단할지 결정해야 했다.

그래서 남아 있는 일을 다시 펼쳤다.

지금 진행 중인 일
다음으로 확인할 사람
참고할 문서와 대화
자동화가 대신하던 일
내가 빠진 뒤의 담당자

이 목록을 만들고 나니 인수인계의 기준도 조금 선명해졌다. 내가 알고 있는 것을 전부 설명하는 것이 아니라, 다음 사람이 스스로 판단을 이어갈 수 있을 만큼 맥락을 남기는 것이었다.

그 과정에서 자동화도 함께 넘겼다. 자동화는 편리하지만 소유자가 불분명하면 조용히 책임을 붙잡아두는 장치가 되기도 한다. 내가 당번에서 빠졌는데도 배포 요청을 계속 감지하고 승인한다면, 역할은 끝났지만 책임은 끝나지 않은 셈이다. 그래서 동작을 중단하고, 필요하면 다음 담당자가 다시 설정할 수 있도록 문서를 남겼다.

업무의 경계는 채널의 사용자 그룹보다 조금 더 구체적이어야 했다.

코드에도 비슷한 인수인계가 쌓여 있었다

같은 시기에 정기 배포를 준비하면서 모노레포의 의존성 문제를 정리했다.

팀에는 이미 레이어 규칙이 있었다. 공통 유틸리티가 상위 기능을 알아서는 안 되고, 상태와 맥락을 제공하는 레이어가 구체적인 기능 레이어를 직접 참조해서도 안 된다. 기능끼리 서로 기대기 시작하면 한쪽을 수정할 때 다른 쪽이 함께 흔들린다.

규칙은 있었지만 오래된 참조가 남아 있었다.

  • 순환 참조 32건
  • 공통 유틸리티에서 상위 레이어를 참조하는 3건
  • 컨텍스트 레이어에서 기능 레이어를 참조하는 4건
  • 기능 패키지끼리 직접 참조하는 24건

한 번에 전부 갈아엎지는 않았다. 먼저 실제 소비처를 따라가며 참조의 성격을 나눴다.

어떤 코드는 단순히 잘못된 위치에서 값을 가져오고 있었다. 이런 경우에는 필요한 값을 호출부에서 주입하도록 바꾸면 됐다. 어떤 UI 조각은 하위 레이어가 상위 기능을 직접 알고 있었는데, 슬롯 형태의 props로 바꾸자 의존성 방향을 바로잡을 수 있었다.

여기서 슬롯을 선택 사항으로 두면 또 다른 문제가 생겼다. 호출부가 조립을 빠뜨려도 타입 검사를 통과하고, 화면 일부가 조용히 사라질 수 있었다. 리뷰를 받아 해당 props를 필수로 바꾸었다. 이제 누락은 운영 화면이 아니라 컴파일 단계에서 드러난다.

반대로 이름은 기능 패키지지만 실제로 여러 영역에서 공통 기반처럼 쓰이는 코드도 있었다. 이런 참조를 숫자만 줄이려고 억지로 옮기면 구조가 더 부자연스러워진다. 그래서 두 개 이상의 기능 영역에서 사용되고, 다른 기능에 다시 의존하지 않는 경우처럼 예외의 기준을 정했다.

결과적으로 순환 참조와 명확한 역방향 참조는 0건으로 줄였고, 기능 간 직접 참조는 24건에서 15건으로 줄였다. 남은 숫자는 실패가 아니라 다음 판단의 목록이 됐다.

규칙은 문장보다 실패 방식으로 남았다

이번 작업에서 중요했던 것은 참조 몇 건을 지운 결과만이 아니었다.

정리한 영역에는 검사 규칙을 함께 적용했다. 기존 위반을 모두 없앤 규칙은 새 위반이 생기면 CI에서 실패하도록 만들었다. 아직 위반이 남은 규칙은 현황을 보여주되, 새 위반이 조용히 늘어나지 않도록 기준을 분리했다.

문서에 “이 방향으로 참조하지 마세요”라고 적는 것보다 잘못된 참조가 들어왔을 때 빌드가 실패하는 편이 훨씬 분명하다. 필수 슬롯을 빠뜨렸을 때 타입 검사가 실패하는 것도 마찬가지다.

좋은 규칙은 사람에게 계속 기억하라고 요구하지 않는다. 규칙을 잊었을 때 시스템이 먼저 멈추고 이유를 알려준다.

운영 인수인계에서도 비슷했다. 다음 담당자가 내가 했던 말을 기억해주길 기대하는 대신, 확인할 문서와 자동화의 상태를 남겼다. 코드에서는 의존성 검사와 타입으로, 사람 사이에서는 문서와 소유권으로 실패 지점을 앞당긴 셈이다.

사람의 경계와 코드의 경계

두 일을 함께 하고 나니 닮은 점이 더 잘 보였다.

사람 사이의 경계가 흐리면 이런 일이 생긴다.

  • 담당자가 바뀌어도 이전 사람이 계속 확인한다.
  • 누가 결정해야 하는지 몰라 일이 여러 사람 사이를 돈다.
  • 기록되지 않은 맥락이 특정 사람의 기억에만 남는다.

코드의 경계가 흐릴 때도 비슷하다.

  • 하위 레이어가 상위 기능의 사정을 알아버린다.
  • 한 기능의 수정이 예상하지 못한 다른 기능으로 번진다.
  • 왜 필요한지 알 수 없는 예외가 특정 파일에만 남는다.

결국 두 경우 모두 같은 질문으로 돌아왔다.

누가 무엇을 알아야 하는가?

다음 담당자가 판단에 필요한 맥락은 알아야 하지만, 이전 담당자의 모든 작업 방식을 그대로 물려받을 필요는 없다. 공통 레이어는 필요한 값을 받아야 하지만, 그 값이 어느 기능에서 만들어졌는지까지 알 필요는 없다.

경계를 정리한다는 것은 관계를 끊는 일이 아니었다. 필요한 정보는 전달하되, 불필요한 책임과 지식이 넘어가지 않도록 방향을 정하는 일이었다.

AI와 함께 빨라졌지만, 기준은 자동으로 생기지 않았다

이 작업에도 AI를 많이 사용했다. 앞서 동시에 처리한다는 것에서 정리했듯, 여러 작업의 탐색과 검증은 병렬로 진행하되 최종 판단은 내가 맡았다.

의존성 검사 결과를 분류하고, 참조 경로를 따라가고, 여러 수정 브랜치를 검토하는 일은 훨씬 빨라졌다. 리뷰 에이전트에게 변경을 다시 보여주며 누락된 소비처나 타입 경계를 확인할 수도 있었다. 운영 인수인계에서는 흩어진 대화와 문서를 모아 남은 일을 목록으로 만드는 데 도움을 받았다.

하지만 AI가 예외의 기준까지 대신 정해주지는 못했다.

어떤 패키지를 공통으로 볼 것인지, 어느 자동화를 다음 담당자에게 넘길 것인지, 지금의 구조 변경이 실제 사용자 흐름을 깨뜨리지 않는지는 사람이 판단해야 했다. 속도가 빨라질수록 오히려 기준이 없는 변경도 빠르게 늘어날 수 있었다.

그래서 이번에는 빠르게 고치는 것보다 다음 질문을 계속 붙잡았다.

이 참조를 없애면 무엇을 잃는가?
이 예외는 어떤 조건에서만 허용되는가?
이 역할을 넘긴 뒤에도 내가 계속 붙잡고 있는 것은 무엇인가?
다음 사람이 같은 문제를 만들면 어디에서 발견되는가?

AI는 탐색과 검증의 폭을 넓혀줬다. 경계를 어디에 그을지는 여전히 내 일이었다.

잘 정리했다는 만족과, 계속 정리하고 있다는 피로

이번 결과는 마음에 든다.

오래 남아 있던 순환 참조를 없앴고, 역방향 의존성을 걷어냈고, 리뷰에서 나온 의견을 타입으로 반영했다. 운영 업무도 진행 상황과 자동화까지 포함해 다음 담당자에게 넘겼다. 무엇보다 같은 문제가 되풀이되기 어렵게 만들었다.

동시에 이런 생각도 남는다.

나는 왜 자꾸 경계가 흐린 곳을 발견하고, 그 빈틈을 정리하는 사람이 되는가.

경계를 정리하는 일은 눈에 잘 띄지 않는다. 새로운 화면처럼 바로 보이지 않고, 매출이나 전환율처럼 숫자로 설명하기도 어렵다. 잘 끝나면 아무 일도 일어나지 않는다. 다음 사람이 헤매지 않고, 다음 변경이 엉뚱한 곳으로 번지지 않는 것이 결과다.

그래서 만족과 피로가 함께 있다.

이 일을 해냈다는 사실은 좋다. 다만 할 수 있다는 이유로 경계가 흐린 모든 곳을 계속 내가 정리해야 하는 것은 아니다. 이번에는 자동화를 끄고 역할을 넘긴 것처럼, 코드뿐 아니라 내 책임의 범위도 의식적으로 정리할 필요가 있다.

요즘의 근황

요즘 나는 새 기능을 만드는 일과 함께, 일이 안전하게 이어질 조건을 만들고 있다.

사람에게는 다음 판단을 위한 맥락을 넘긴다. 코드에는 허용되는 의존성 방향을 남긴다. 반복해서 설명해야 했던 규칙은 검사로 옮기고, 조용히 빠질 수 있는 조립은 타입 오류로 드러나게 만든다. 예외가 필요하다면 이름이 아니라 사용 방식으로 기준을 세운다.

이런 일은 화려하지 않다. 하지만 기능 하나보다 오래 남을 때가 있다.

최근의 나를 한 문장으로 정리하면 이렇다.

사람 사이에서는 책임의 경계를, 코드 안에서는 지식의 경계를 정리하고 있다.

잘 정리된 경계는 사람을 고립시키지 않는다. 오히려 각자가 알아야 할 것을 분명하게 만들고, 필요할 때 더 안전하게 연결될 수 있게 한다.

이번 정기 배포가 끝나면 화면에 보이는 변화는 크지 않을 것이다.

그래도 그 안에는 조금 덜 얽힌 코드와, 조금 더 분명해진 규칙이 남는다. 그리고 나는 다음 일을 시작하기 전에, 무엇까지 내 몫으로 가져갈지도 한 번 더 생각해보려 한다.