실제 업무에서 겪은 일을 바탕으로 썼다. 조직, 서비스, 티켓, 채널, 사람과 정확한 수치를 식별할 수 있는 정보는 걷어내고 판단 과정만 남겼다.
최근 작은 운영 작업 하나를 받았다.
외부 제휴 서비스의 처리가 실시간으로 바뀌었으니, 우리 화면도 그 변화에 맞추자는 요청이었다. 티켓에는 두 가지 일이 함께 적혀 있었다.
- 기존 지연 안내 문구를 없앤다.
- 외부 화면에서 돌아오면 원래 화면을 새로고침해 최신 상태를 보여준다.
문구 하나를 지우고 복귀 시점에 새로고침을 호출하면 되는 일처럼 보였다. AI를 활용한 초기 산정도 두 작업을 합쳐 제법 큰 범위로 잡혀 있었다.
그런데 착수 전에 코드를 읽고 대화 기록을 다시 확인하자, 두 작업 모두 그대로 시작하면 안 된다는 사실이 드러났다.
결과적으로 코드는 한 줄도 바꾸지 않았다. 하나는 다시 논의하기로 했고, 다른 하나는 별도 작업으로 분리했다. 원래 티켓에는 취소선이 생겼고 상태는 대기로 바뀌었다.
이번 작업의 결과는 커밋이 아니라 하지 않을 일을 명확히 한 것이었다.
TL;DR
- 티켓은 구현 명세가 아니라, 아직 검증되지 않은 가설일 수 있다.
- 같은 숫자처럼 보여도 “외부 처리 시간”과 “내부 반영 시간”은 서로 다른 시계다.
- 문구 수정과 사용자 흐름 변경은 결정권자도, 검증 방법도 다르다. 한 티켓에 묶지 않는 편이 낫다.
- 코드를 쓰지 않았더라도 잘못된 삭제와 성급한 새로고침을 막았다면 개발 결과가 있다.
- 취소선과 대기 상태는 실패가 아니라, 다음 사람이 같은 오해로 구현을 시작하지 않게 하는 기록이다.
처음 받은 요청은 단순해 보였다
기존 흐름은 이랬다.
내부 화면
-> 외부 제휴 화면에서 신청
-> 내부 시스템으로 결과 전달
-> 결제 수단으로 등록
-> 원래 화면에서 사용
외부 제휴사의 처리가 배치에서 실시간으로 바뀌었다고 했다. 그러면 화면에 남아 있는 “등록까지 몇 분 정도 걸릴 수 있다”는 안내는 낡은 문구처럼 보인다.
사용자가 외부 화면에서 돌아왔을 때 페이지를 새로고침하면, 방금 등록된 결제 수단도 바로 보일 것 같았다.
요청만 읽으면 자연스러운 결론이다.
실시간 처리
-> 지연 문구 삭제
-> 복귀 즉시 새로고침
-> 최신 상태 노출
하지만 이 흐름에는 확인하지 않은 전제가 세 개 있었다.
- 화면의 숫자가 외부 서비스 처리 시간을 뜻하는가?
- 외부 처리가 끝나면 내부 등록도 같은 순간에 끝나는가?
- 사용자가 언제나 신청을 시작한 화면으로 돌아오는가?
셋 중 하나라도 아니면 구현 방법이 달라진다.
첫 번째 전제: 화면에는 요청과 다른 값이 있었다
먼저 실제 문구가 어디에서 만들어지는지 검색했다.
티켓에는 오래된 안내값을 삭제한다고 적혀 있었지만, 코드는 이미 더 짧은 값으로 변경된 상태였다. 이전 작업에서 상수가 바뀌었고 여러 화면이 그 값을 공유하고 있었다.
여기서 곧바로 “그럼 현재 값만 지우면 되겠네”라고 결론 내릴 수도 있었다. 하지만 상수 이름과 소비처를 따라가자 더 중요한 차이가 보였다.
그 숫자는 외부 서비스가 신청을 처리하는 시간이 아니었다. 외부에서 전달된 결과가 내부 서비스에 등록되어 사용 가능한 상태가 되기까지의 시간을 설명하고 있었다.
비슷해 보이지만 서로 다른 시계다.
외부 신청 완료 시각
내부 데이터 수신 시각
결제 수단 등록 완료 시각
현재 화면이 최신 상태를 조회한 시각
첫 번째 시계가 실시간에 가까워졌다고 해서 나머지 시계가 자동으로 같아지지는 않는다. 실시간 전달이 실패하면 기존 배치 경로로 보완한다는 조건도 남아 있었다.
안내 문구를 지우면 화면은 깔끔해진다. 대신 사용자는 실제로 지연이 발생했을 때 무엇을 기다려야 하는지 알 수 없게 된다. 잘못된 숫자는 고쳐야 하지만, 여전히 가능한 지연을 설명하는 문구까지 없애는 것이 맞는지는 제품 판단이 필요했다.
그래서 코드를 지우는 대신 질문을 남겼다.
이 문구는 외부 처리 시간을 안내하는가, 내부 등록 완료까지의 시간을 안내하는가?
실시간 경로가 실패했을 때도 사용자에게 약속할 수 있는 문장은 무엇인가?
답은 아직 정해지지 않았다. 문구는 디자인과 제품 측에서 다시 검토하기로 했고, 구현은 멈췄다.
두 번째 전제: 새로고침은 한 줄짜리 동작이 아니었다
두 번째 요청은 외부 화면에서 돌아오면 원래 화면을 새로고침하자는 것이었다.
하지만 “돌아온다”는 표현부터 실행 가능한 명세가 아니었다.
- 브라우저의 뒤로가기로 돌아오는가?
- 외부 서비스가 콜백 URL로 이동시키는가?
- 신청을 시작한 탭과 돌아온 탭이 같은가?
- 데스크톱에서 시작하고 모바일에서 끝낼 수 있는가?
- 주문 화면, 상품 화면, 내 정보 화면이 같은 복귀 계약을 사용하는가?
단순히 router.refresh()나 location.reload()를 붙이는 것으로 끝내면 우연히 맞는 경로만 고칠 가능성이 높았다.
새로고침 시점을 정하려면 최소한 다음 계약이 필요하다.
복귀 신호: 무엇을 보고 외부 신청 완료를 아는가?
데이터 계약: 어느 상태가 바뀌면 등록 완료인가?
적용 화면: 어떤 화면에서 갱신해야 하는가?
갱신 범위: 전체 페이지인가, 결제 수단 영역만인가?
실패 정책: 최신 상태가 아직 아니면 무엇을 보여주는가?
중복 정책: 여러 번 복귀하거나 새로고침해도 안전한가?
이것은 문구 수정의 부속 작업이 아니었다. 여러 시스템의 데이터 전달 시점과 사용자 이동 경로를 합의해야 하는 별도 기능이었다.
그래서 한 티켓에 끼워 넣지 않고 새 작업으로 분리하기로 했다.
한 티켓에 있었지만 같은 일이 아니었다
처음에는 두 요청이 모두 “실시간 전환 대응”이라는 이름 아래 있었다. 그러나 실제로는 성격이 달랐다.
| 구분 | 안내 문구 | 복귀 후 상태 갱신 |
|---|---|---|
| 핵심 질문 | 사용자에게 어떤 기대 시간을 약속할 것인가 | 완료를 어떤 신호로 판단할 것인가 |
| 결정 주체 | 제품·콘텐츠·디자인 | 여러 시스템 담당자와 클라이언트 |
| 변경 범위 | 공유 문구와 화면 노출 | 이동 경로, 데이터 조회, 상태 갱신 |
| 검증 방법 | 문구 정합성과 예외 상황 확인 | 경로별 통합 테스트와 실패 처리 확인 |
| 현재 결론 | 문구 재검토까지 대기 | 별도 작업으로 분리 |
관련된 일과 같은 일은 다르다.
같은 배경에서 출발했다는 이유로 한 티켓에 묶으면, 작은 문구 변경이 아직 합의되지 않은 시스템 동작까지 끌고 간다. 반대로 흐름 변경을 문구 수정의 부속 작업으로 보면 설계와 검증에 필요한 시간이 과소평가된다.
티켓을 나눈 것은 행정적인 정리가 아니었다. 서로 다른 결정을 서로 기다리지 않도록 의존성을 끊은 것이었다.
AI는 처음 적힌 범위를 충실히 산정했다
처음 티켓에는 작은 UI 변경과 여러 시스템이 얽힌 흐름 변경이 함께 있었다. AI를 활용한 초기 산정은 후자의 불확실성을 읽고 작업을 크게 평가했다.
산정 자체가 틀렸다고 보기는 어렵다. 입력된 범위 안에서는 합리적이었다.
문제는 그 범위가 아직 확정되지 않았다는 점이다.
AI는 티켓에 적힌 두 항목을 실제로 해야 할 일이라고 전제했다. 하지만 코드를 읽어보니 첫 번째 항목의 설명은 현재 구현과 달랐고, 대화 기록을 따라가니 두 번째 항목은 데이터 계약조차 확인 중이었다.
이 차이는 최근 썼던 잘못 이해한 요구도 AI는 빠르게 구현한다와도 이어진다. AI는 주어진 범위를 빠르게 분석하고 구현한다. 그러므로 산정과 구현보다 먼저 해야 할 일은 입력된 범위가 실제로 존재하는지 확인하는 것이다.
티켓을 읽는다
-> 코드에서 현재 상태를 확인한다
-> 대화에서 결정되지 않은 전제를 찾는다
-> 서로 다른 책임과 검증 방법을 분리한다
-> 그다음에 다시 산정한다
처음의 큰 산정값을 유지한 채 일을 시작하는 대신, 산정의 입력부터 고쳤다.
코드를 쓰지 않은 것도 개발 결과다
개발 결과를 커밋과 배포만으로 세면 이번 작업은 아무것도 하지 않은 일이 된다.
하지만 그대로 구현했다면 다음과 같은 변경이 나갔을 수 있다.
- 실제로 남아 있는 지연 경로를 설명하는 문구를 삭제한다.
- 여러 화면이 공유하는 문구를 한 요청만 보고 함께 바꾼다.
- 완료 신호 없이 복귀할 때마다 전체 페이지를 새로고침한다.
- 아직 데이터가 도착하지 않았는데 최신 상태라고 기대하게 만든다.
- 서로 다른 사용자 경로를 하나의 뒤로가기로 가정한다.
코드를 쓰지 않음으로써 이 변경들을 막았다.
애자일 선언의 원칙은 하지 않을 일의 양을 최대화하는 단순성을 중요하게 다룬다. YAGNI도 필요가 확인되기 전에 기능을 만드는 비용을 경고한다.
이번 경험은 미래 기능을 미리 만들지 말자는 이야기와 조금 다르다. 이미 요청된 일이라도 현재 시스템과 제품 계약을 확인하기 전에는 아직 해야 할 일로 확정되지 않았다는 쪽에 가깝다.
취소선은 일을 대충 지운 흔적이 아니었다.
이 항목은 지금 구현하지 않는다.
왜 하지 않는지 근거가 있다.
다시 시작하려면 어떤 결정이 필요한지 알고 있다.
다른 성격의 작업은 별도 범위에서 다룬다.
이 네 문장이 남아 있다면 취소선도 충분한 결과다.
착수 전에 확인할 질문
다음부터 비슷한 요청을 받으면 코드를 열기 전에 티켓만 읽고 판단하지 않으려 한다. 그렇다고 긴 기획 절차를 새로 만들 필요는 없다. 다음 질문이면 시작하기에 충분하다.
현재 상태
- 티켓에 적힌 값과 코드의 값이 같은가?
- 같은 상수나 문구를 사용하는 화면이 더 있는가?
- 이전 변경에서 이미 해결했거나 의미가 달라진 부분은 없는가?
시간과 상태
- 화면의 시간은 어느 시스템의 어떤 완료 시점을 뜻하는가?
- “실시간”은 전달 시작, 데이터 수신, 등록 완료 중 무엇인가?
- 실패하거나 지연될 때의 보완 경로가 있는가?
사용자 흐름
- 사용자는 어디에서 시작하고 어디로 돌아오는가?
- 복귀를 감지할 수 있는 명시적 신호가 있는가?
- 같은 흐름이 다른 탭이나 기기에서도 성립하는가?
범위
- 두 항목의 결정권자와 검증 방법이 같은가?
- 한 항목을 미뤄도 다른 항목을 독립적으로 끝낼 수 있는가?
- 아직 합의되지 않은 동작을 작은 수정에 끼워 넣고 있지는 않은가?
이 질문에 답한 뒤에는 작업이 커질 수도 있고, 작아질 수도 있다. 이번에는 두 항목 모두 당장 구현할 수 있는 상태가 아니었다.
그것도 괜찮다.
취소선 다음에 남겨야 할 것
다만 티켓에 취소선만 긋고 끝내면 다음 사람은 같은 질문을 다시 시작한다. 멈춘 이유와 재개 조건도 함께 남겨야 한다.
현재 확인된 사실
- 실제 코드는 요청서의 설명과 다르다.
- 안내값은 외부 처리 시간이 아니라 내부 반영 시간을 뜻한다.
이번에 하지 않는 일
- 제품 문구가 확정되기 전까지 기존 안내를 삭제하지 않는다.
- 복귀 후 갱신은 현재 티켓에서 구현하지 않는다.
다음 행동
- 제품·디자인에서 지연 상황을 포함한 문구를 확정한다.
- 흐름 변경은 완료 신호와 데이터 계약을 정한 별도 작업으로 만든다.
이렇게 남기면 대기 상태는 방치가 아니라 재개 조건이 있는 상태가 된다.
예전에 요즘은 경계를 정리하고 있다에서 사람과 코드 사이의 책임 경계를 이야기했다. 이번에는 티켓 안의 경계를 정리했다. 문구를 결정하는 일, 데이터를 전달하는 일, 사용자 복귀 흐름을 만드는 일을 한 문장 아래에 두지 않았다.
마치며
나는 개발 일을 받으면 무엇을 만들지부터 생각하는 편이었다. 이번에는 반대로 무엇을 만들지 않아야 하는지를 먼저 확인했다.
티켓의 첫 번째 항목은 현재 코드와 전제가 달랐다. 두 번째 항목은 첫 번째의 부속 작업이 아니었다. 하나는 다시 결정해야 했고, 하나는 별도 설계가 필요했다.
그래서 코드를 쓰지 않았다.
일을 피한 것이 아니라, 잘못된 일을 시작하지 않은 것이다.
좋은 착수는 할 일을 빠르게 시작하는 것만이 아니다. 하지 않을 일을 근거와 함께 지우는 것에서 시작되기도 한다.