이 글은 Today I Learn 시리즈의 119번째 기록입니다. (총 125개)

오늘 못 한 일을 다른 날로 넘기는 기능이 있다. 넘긴 날에도 못 하면 또 다음으로 이어지고, 받기로 한 날에 아예 앱을 안 열어도 그다음 여는 날까지 따라온다. 한 번 넘기고 끝이 아니라 처리될 때까지 물려 간다고 해서 이 글에서는 이월 사슬이라고 부른다.

이게 있어야 하는 이유는 하나다. 안 그러면 못 한 일이 조용히 사라진다. 근무 기록에서 그건 제일 나쁜 고장이다 — 틀린 게 화면에 안 보이고, 안 보이니 아무도 신고하지 않는다.

8월 24일에 만들어 배포했고, 그 뒤로 매일 쓰이며 돌고 있었다. 넘긴 일이 다음 날 제대로 올라오는지는 쓰는 자리에서 이미 확인되고 있었다.

그런데 만드는 쪽에는 그 사실이 안 와 있었다. 백로그에는 「이월 사슬을 날짜가 바뀐 뒤에 확인하기」가 그대로 남아 있었고, 그 한 줄만 보면 아직 아무도 확인 안 한 기능이다. 실제 상태와 적힌 상태가 일주일 넘게 어긋나 있었다.


확인된 것과 확인됐다고 전해진 것은 다르다

쓰면서 확인하는 건 확인이 맞다. 실제로 넘긴 일이 다음 날 올라오는 걸 매일 보고 있었으니까.

문제는 그게 나만 아는 상태였다는 것이다. 만드는 쪽은 백로그의 「확인하기」 한 줄만 보고 판단하는데, 그 줄이 안 닫혀 있으면 그쪽에서는 계속 미확인이다.

  • 상태를 적어 두는 목록이 있으면, 실제 상태가 아니라 적힌 상태로 판단이 돌아간다
  • 확인은 내가 하고 목록은 그대로 두면 어긋난 채로 굴러간다. 확인한 뒤에 그 줄을 닫는 데까지가 확인이다

목록을 안 닫으면 무엇이 위험한지도 이번에 봤다. 성격이 다른 것이 한 칸에 섞여 있다.

  • 안 만든 기능은 없어서 아무 일도 안 일어난다
  • 만들어서 돌고 있는데 맞는지 모르는 기능은, 틀렸다면 매일 틀리고 있다

이 앱에서 이월이 틀린다는 건 사람이 넘긴 일이 조용히 사라진다는 뜻이다. 그러니 「모르는 상태」로 놔둔 기간이 실제로는 없었더라도, 목록만 보면 그 기간이 일주일이었던 셈이다.

  • 할 일은 쌓아 둬도 그대로지만, 모르는 것은 쌓아 둘수록 무서워진다. 그래서 이 둘은 같은 목록에 두면 안 된다
  • 확인한 것은 그 자리에서 닫는다. 미룬 것은 왜 미뤘는지를 옆에 적는다. 그러면 목록이 실제 상태를 따라간다

미룬 이유가 전체에 해당하는지 쪼개 본다

「날짜가 바뀌어야 확인된다」는 틀린 말이 아니다. 브라우저에서 보려면 자정을 기다리거나 시계를 돌려야 한다.

그런데 확인하려는 것이 무엇인지 나눠 보니 둘이었다.

  • 날이 바뀌면 훑기가 다시 도는가 — 화면에 붙은 부분. 이건 정말 기다려야 한다
  • 훑었을 때 맞는 답이 나오는가 — 계산. 「오늘」이 언제인지 넘겨 주면 지금 끝난다

앞엣것은 정말 기다려야 하고 뒤엣것은 지금 할 수 있다. 그런데 백로그에는 둘이 「확인하기」 한 줄로 묶여 있어서, 기다려야 하는 쪽 때문에 지금 할 수 있는 쪽까지 같이 멈춰 있었다.

  • 막는 이유가 나오면 그게 전부를 막는지 일부를 막는지 먼저 가른다
  • 일부만 막는데 전부가 멈춰 있으면, 대개 「어차피 다 해야 하니까」로 묶어 둔 것이다

옮겨 적은 사본을 돌리는 건 검증이 아니다

확인 스크립트를 만들면서, 앱이 열릴 때 하는 일(멈춰 있는 이월이 있는지 지난 날들을 훑는 것)을 스크립트 쪽에 옮겨 적었다. 열일곱 가지를 돌려 보니 전부 통과했다.

통과한 것이 무엇인지 되짚어 보니 앱이 아니었다. 사본이다.

앱      ─── 훑기 A ───→ 화면
스크립트 ─── 훑기 B ───→ 확인 (통과)

지금은 A와 B가 같을지 몰라도, 나중에 A만 고치면 B는 그대로다. 그때부터 스크립트는 앱이 틀려도 계속 통과한다. 확인 장치가 되레 마음을 놓게 만든다. 아무것도 확인 안 한 것보다 나쁜 자리다.

「17개 통과」는 두 경우에 똑같이 나온다

진짜 코드를 돌렸을 때와 사본을 돌렸을 때, 결과 문구가 구별이 안 된다.

확인 결과를 남길 때는 몇 개가 통과했는지보다 무엇을 불렀는지를 같이 적어야 한다. 숫자만 남기면 나중에 읽는 사람이 앞의 경우로 읽는다.


확인하기 어렵다는 것이 코드를 고칠 이유가 된다

사본이 생긴 데는 이유가 있었다. 그 훑기가 화면 코드 안에 있어서 스크립트가 부를 수가 없었다.

그러니까 바꿔야 할 것은 스크립트가 아니라 그 코드가 있는 자리였다. 훑기를 화면에서 빼내 규칙만 다루는 자리로 옮기고, 「무엇을 고쳐야 하는지」만 돌려주고 저장은 부르는 쪽이 하게 했다. 앱은 진짜 저장소에 쓰고 스크립트는 흉내낸 저장소에 쓰지만, 답을 정하는 계산은 한 곳뿐이다.

앱      ─┐
         ├── 훑기 ──→
스크립트 ─┘
  • 「확인하기 어렵다」는 취향 문제가 아니라 설계 신호다. 부를 수 없다는 건 붙어 있으면 안 될 것이 붙어 있다는 뜻인 경우가 많다
  • 확인하기 쉬운 모양과 잘 짜인 모양은 대체로 같은 방향이었다. 확인 때문에 억지로 비튼 데가 없다

흉내낼 것과 진짜 쓸 것의 경계가 곧 「무엇을 확인하는가」다

스크립트에서 흉내낸 건 「어디에 담아 두느냐」 하나뿐이다. 답을 정하는 부분은 흉내내지 않는다.

그 경계를 어디에 긋느냐가 그 확인의 값어치를 정한다. 흉내를 늘릴수록 확인은 쉬워지고 동시에 확인하는 것이 줄어든다.


잘 되는 길 하나로는 모자란다

처음 떠오른 경우는 「수요일에 넘기면 목요일에 뜬다」였다. 이건 만들면서 이미 본 길이다. 잘 되는 걸 아는 길을 다시 걷는 건 확인이 아니라 안심이다.

찾아야 하는 건 안 되는 길이고, 그건 축을 세워야 나온다. 이월에서 어긋날 만한 축을 세어 봤다.

  • 건너뛰기 — 받는 날에 안 열면. 며칠을 내리 안 열면
  • 되풀이 — 데려온 뒤 또 열면 두 번 오나
  • 이미 한 것 — 넘어온 날에 도장을 찍었으면 그다음 날에도 따라오나
  • 방향 반대 — 앞으로 당긴 것은 원래 날에서 빠지나. 못 했으면 돌아오나
  • 사라진 것 — 넘긴 뒤 그 업무를 보관함에 넣으면

축이 만나는 자리가 경계다. 다섯 축에서 열일곱 가지가 나왔다. 머리로 떠올린 목록이 아니라 축을 곱해서 나온 목록이라 빠진 데를 알아보기도 쉽다.

안 세어 본 것을 같이 적어 둔다

전부 통과했지만 안 본 것도 있다.

  • 자정을 넘길 때 훑기가 실제로 다시 도는가 — 화면에 붙은 부분
  • 두 기기에서 같은 날을 각각 넘겼을 때 — 합치기 규칙과 겹치는 자리

「전부 통과」만 적어 두면 다음에 볼 때 다 확인된 줄 안다. 남은 것을 같이 적으면 그게 다음에 손댈 자리가 된다.


요약 — 눌러야 아는 것과 계산으로 아는 것을 가른다

이번 일로 작업 규칙 하나가 생겼다. 만들고 나서 확인할 때 두 무더기로 나눈다.

  • 계산으로 아는 것 — 실제 함수를 그대로 불러 돌린다
  • 눌러야 아는 것 — 사람이 화면에서 본다

앞엣것을 먼저 걷어내면 뒤엣것이 좁아진다. 이번에는 「자정에 다시 도는가」 하나만 남았고, 그 정도는 기다려도 된다.

거꾸로, 전부 눌러서 확인해야 한다고 두면 「확인하기」가 한 덩어리가 되어 통째로 미뤄진다. 백로그에 그 한 줄이 오래 남아 있던 이유가 그거였다.


Series: Today I Learn

1 C++ 자료형(Data Type) 2 MD5 vs pHash 3 C++에서 함수의 선언과 정의 4 Tkinter padx, pady 5 메모리와 포인터 변수 6 Call by Value, Call by Reference, Call by Pointer 비교 7 const 8 Gemfile — Jekyll 프로젝트의 의존성 파일 9 kramdown-parser-gfm — Jekyll의 GFM 파서 10 파서(Parser) 11 AHU vs OHU 12 I might try it vs I'll try it 뉘앙스 차이 13 SESSION_EXPIRE_AT_BROWSER_CLOSE=True 14 configuration key 15 Git stash vs discard 16 subprocess.Popen으로 Windows 탐색기에 명령어를 전달 17 Post 잔디 분석하기 18 Google Sheets Sync 최적화 19 DSL (Domain Specific Language)과 GPL (General Purpose Language) 20 마크다운 표 그리는 방법 21 쿼리 파라미터(Query Parameter). 기존 QR코드 재활용 22 Django 보안 취약점 점검 및 수정 23 OOP Object-Oriented Programming 객체 지향 프로그래밍 24 Fernet 대칭 암호화 25 Jekyll 코드블록 안의 Liquid 태그 26 insertOnConflictUpdate vs DoUpdate(target) 27 세션 필터 28 아코디언(Accordiaon) UI를 펼친상태로 만들기 29 input의 step 30 Word Cloud 31 Google Sheets를 데이터 버스로(with AppSheet) 32 Django 모델 텍스트 필드 자동 수집 패턴 33 localStorage로 섹션 토글 상태 유지 34 순차 ID 생성(`select_for_update()` + `max()` 조합) 35 역참조 검색과 distinct() 36 xlsx 다운로드와 로딩 오버레이 충돌 37 Android 파일 공유 MIME 타입 38 AssetManifest — Flutter 빌드 타임 asset 목록 런타임 조회 39 UTF-8 BOM과 PowerShell 파일 쓰기 40 소리꽃 KeyBloom TIL 1 41 소리꽃 KeyBloom TIL 2 42 소리꽃 KeyBloom TIL 3 43 메트로놈 Simple Metronome TIL 1 44 소리꽃 KeyBloom TIL 4 45 메트로놈 Simple Metronome TIL 2 46 소리꽃 KeyBloom TIL 5 47 메트로놈 Simple Metronome TIL 3 48 정적 블로그 SEO 정비와 Pagefind 검색 도입 49 메트로놈 Simple Metronome TIL 4 50 안드로이드 AudioTrack 연속 재생, 실측 피커 정렬, 카메라 토치 플래시 51 메트로놈 Simple Metronome TIL 5 52 모래게임 Sandrop TIL 1 53 메트로놈 Simple Metronome TIL 6 54 모래게임 Sandrop TIL 2 55 모래게임 Sandrop TIL 3 56 메트로놈 Simple Metronome TIL 7 57 모래게임 Sandrop TIL 4 58 모래게임 Sandrop TIL 5 59 모래게임 Sandrop TIL 6 60 소리꽃 KeyBloom TIL 6 61 모래게임 Sandrop TIL 7 62 모래게임 Sandrop TIL 8 63 소리꽃 KeyBloom TIL 7 64 모래게임 Sandrop TIL 9 65 소리꽃 KeyBloom TIL 8 66 모래게임 Sandrop TIL 10 67 소리꽃 KeyBloom TIL 9 68 소리꽃 KeyBloom TIL 10 69 소리꽃 KeyBloom TIL 11 70 소리꽃 KeyBloom TIL 12 71 소리꽃 KeyBloom TIL 13 72 온실 GreenHouse TIL 1 73 소리꽃 KeyBloom TIL 14 74 온실 GreenHouse TIL 2 75 소리꽃 KeyBloom TIL 15 76 소리꽃 KeyBloom TIL 16 77 소리꽃 KeyBloom TIL 17 78 소리꽃 KeyBloom TIL 18 79 소리꽃 KeyBloom TIL 19 80 소리꽃 KeyBloom TIL 20 81 로그스톤 상표 셀프 출원 TIL 1 82 소리꽃 KeyBloom TIL 21 83 소리꽃 KeyBloom TIL 22 84 소리꽃 KeyBloom TIL 23 85 소리꽃 KeyBloom TIL 24 86 MiniMacro TIL 1 87 Astro가 무엇인지, 왜 옮기는지 88 MiniMacro TIL 2 89 메트로놈 Simple Metronome TIL 8 90 메트로놈 Simple Metronome TIL 9 91 블로그 Astro 이관 TIL 92 오픈데이 Openday TIL 1 93 오픈데이 Openday TIL 2 94 Unreal Engine MCP TIL 1 95 콘티온 Conti On TIL 11 96 오픈데이 Openday TIL 3 97 오픈데이 Openday TIL 4 98 오픈데이 Openday TIL 5 99 ScorePlayer TIL 1 100 BuildingHub TIL 1 101 ScorePlayer TIL 2 102 BuildingHub TIL 2 103 ScorePlayer TIL 3 104 Unreal Engine MCP TIL 2 105 궁 미로 PalaceMaze TIL 1 106 메트로놈 Simple Metronome TIL 10 107 메트로놈 Simple Metronome TIL 11 108 오픈데이 Openday TIL 6 109 BuildingHub TIL 3 110 Obsidian vault에서 코드만 빼기 — directory junction 111 ScorePlayer TIL 4 112 오픈데이 Openday TIL 7 113 URL은 바뀔 수 있는 것에 묶지 않는다 114 도구는 기능이 아니라 내 일에 맞는지로 고른다 115 돌이키기 힘든 변경은 작게 먼저 확인한다 116 궁 미로 PalaceMaze TIL 3 117 궁 미로 PalaceMaze TIL 2 118 소리꽃 KeyBloom TIL 29 119 오픈데이 Openday TIL 8 읽는 중 120 BuildingHub TIL 4 121 ScorePlayer TIL 5 122 BuildingHub TIL 5 123 MiniMacro TIL 3 124 소리꽃 KeyBloom TIL 30 125 궁 미로 PalaceMaze TIL 4