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

지나간 날 기록을 고칠 수 있게 방을 하나 더 열었다. 그 방 하나 때문에 여기저기서 전제가 깨졌는데, 깨진 자리들이 다 비슷한 모양이었다.


지난 날 방이 열리자마자 닫혔다

기록실에서 다른 가게의 날짜를 누르면 그 가게로 옮겨가면서 그날 보드를 연다. 그런데 눌러 보면 탭이 잠깐 떴다 사라졌다.

규칙은 「가게가 바뀌면 열어 둔 날짜를 닫는다」였다. 규칙 자체는 맞다 — 다른 가게로 걸어가면 열어 둔 남의 날짜는 닫혀야 한다. 그런데 이번에는 여는 과정에서 가게가 바뀌었다. 열려고 옮겨간 건데 규칙은 그걸 「바뀜」으로 세고 닫아 버렸다.

조건을 「지금 가게와 안 맞으면 닫는다」로 다시 썼다.

  • 닫을 이유는 「무언가 바뀌었다」가 아니라 「지금 둘이 안 맞는다」였다. 앞엣것은 변화를 세는 것이고 뒤엣것은 상태를 보는 것이다
  • 변화를 감시하는 코드는 그 변화가 어디서 왔는지 모른다. 밖에서 온 것이든 자기 쪽에서 낸 것이든 똑같이 보인다
  • 그래서 「이런 일이 생기면 저걸 되돌린다」는 규칙을 쓸 때는, 그 「이런 일」을 일부러 내는 경우가 있는지 먼저 봐야 한다. 상태를 보는 조건에는 이 문제가 없다. 상태에는 출처가 안 적혀 있고 볼 필요도 없다

탭은 방을 넷으로, 거리 끄트머리는 셋으로 세고 있었다

지난 날 방이 생기면서 탭에는 방이 넷인데 거리 끄트머리는 셋으로 알고 있었다. 그래서 기록실에서 오른쪽 끄트머리를 눌러도 새 방으로 못 갔다. 두 곳이 각자 세고 있었다.

목록을 한 번만 만들어 둘 다 그걸 보게 했다.

  • 같은 것을 두 곳에서 세면 처음에는 맞는다. 어긋나는 건 늘 나중에 하나가 늘 때다
  • 그래서 만들 때는 문제가 안 보인다. 「지금 둘 다 셋이니 맞네」에서 끝난다

탭은 생겼는데 갈 곳이 아직 없는 짧은 틈

목록을 하나로 합치고 나서도 짧은 틈이 있었다. 날짜는 정해졌는데 방은 아직 안 세워진 순간이다. 그때 탭은 넷인데 갈 곳은 셋이라, 네 번째 탭을 눌러도 아무 일이 안 난다.

목록의 기준을 「열기로 했나」에서 「실제로 서 있나」로 바꿨다.

  • 눌리는데 아무 일도 안 나면 고장인지 원래 그런지 알 수가 없다. 아예 안 눌리는 편이 낫다
  • 무엇을 보여줄지 정할 때 「그러기로 했나」가 아니라 「지금 있나」를 기준으로 두면 이런 틈이 안 생긴다

격주 업무가 왜 이 목요일에 뜨는지 설명할 수 없었다

반복에 종료일을 붙일 때 「계속 / 언제까지」로 물었다. 끝을 정하는 것이니 끝만 물으면 된다고 봤다.

그런데 「2주마다」는 어디서부터 세느냐에 따라 뜨는 날이 달라진다. 시작일이 그 기준점이다. 그걸 만든 날로 고정해 두고 안 보여주니, 격주 업무가 왜 하필 이 목요일에 뜨는지 설명할 방법이 없었다.

「계속 / 기간」으로 바꿔 시작일과 종료일을 함께 정하게 했다. 물어보는 것이 하나 는 게 아니라 원래 있어야 할 것이 빠져 있었다.

값이 화면에 있어야 하는지 판단할 기준이 여기서 나왔다.

  • 그 값을 모르면 화면에 보이는 결과를 설명할 수 없나 — 그러면 보여야 한다
  • 값이 바뀌면 결과가 바뀌나 — 그러면 고칠 수 있어야 한다

시작일은 둘 다 해당됐다. 그런데 만들 때는 「끝을 정하는 기능」이라고 이름 붙였기 때문에 시작은 딸린 값처럼 보였다.

  • 기능에 붙인 이름이 무엇을 보여줄지까지 정해 버린다. 이름을 정하고 나면 그 이름 밖에 있는 것은 눈에 안 들어온다
  • 이름을 「기간」으로 바꾸니 두 값이 나란해졌다. 이름이 구조를 바로잡은 셈이다

미리보기 문구를 기간부터 읽히게 바꿨다

미리보기 문구도 같이 고쳤다. 「매주 목 · 2027.08.27까지」를 「2026.08.27~2027.08.27 · 매주 목」으로.

「매주 목」을 먼저 읽으면 어느 목요일인지 모른 채로 읽게 된다. 기간이 먼저 오면 그 안에서 매주라는 게 읽힌다.

화면에 무엇을 넣을지 정하고 나면 순서를 한 번 더 봐야 한다. 필요한 게 다 있는데도 안 읽히면 대개 순서 문제다.


세 줄 고치려고 정리 도구를 돌렸다가 육백 줄이 바뀌었다

들여쓰기를 맞추려고 코드 정리 도구를 한 번 돌렸다. 고칠 건 세 줄인데 바뀐 줄이 육백 줄이 됐다. 따옴표와 세미콜론까지 프로젝트와 다른 방식으로 통째로 바뀌었다.

도구는 프로젝트 규칙을 모른다. 설정이 없으면 자기 기본값을 쓰고, 그 기본값이 프로젝트와 다르면 전부 자기 쪽으로 끌어당긴다.

  • 설정 없이 돌린 도구는 「아무것도 안 정한 상태」가 아니라 「그 도구가 정한 상태」다
  • 세 줄을 고치려고 도구를 들이는 건 애초에 과했다. 도구는 반복이 있을 때 값이 나온다

되돌리고 손으로 들여썼는데, 그러고도 바뀐 줄이 백팔십 줄이었다. 들여쓰기가 한 단계 밀린 것까지 다 세어졌기 때문이다. 공백 차이를 빼고 보니 실제로 바뀐 건 세 줄이었다.

  • 큰 변경 뒤에 「의도한 것만 바뀌었나」를 확인할 방법이 있어야 한다
  • 없으면 확인을 포기하게 되고, 포기한 자리에 사고가 숨는다. 이건 성실함의 문제가 아니라 도구의 문제다

며칠 전 기록까지 고칠 수 있게 할까

지난 날 기록을 고칠 수 있게 할지 고민했다. 열면 「기록」이라는 말이 흔들리고, 안 열면 잘못 들어간 것이 영영 남는다.

결정을 밀어 준 건 하나였다. 자정을 넘겨 찍힌 도장이 어제 기록에 들어간 건 사람이 잘못 누른 게 아니라 날짜가 어긋나게 짜여 있던 탓이다. 코드는 고쳤지만 이미 들어간 도장은 그대로 남았다.

  • 앱이 낸 오류를 사람이 못 고치게 두면, 고쳤다고 말해도 그 사람 화면에서는 안 고쳐진 것이다

그다음은 「어디까지 거슬러 올라가 고칠 수 있게 할까」였다. 어제만? 이번 주? 일주일?

어떤 선을 골라도 근거를 못 댔다. 일주일로 하면 팔 일 전 것에서 막히는데, 팔 일과 칠 일 사이에 아무 차이가 없다.

  • 선을 그으면 그 선을 설명해야 한다. 설명 못 하는 선은 안전장치가 아니라 그냥 불편이다
  • 안 자르기로 하고, 자르지 않아서 생기는 것을 하나 받아들였다 — 달력 칸 색이 사후에 바뀔 수 있다

자기만 보는 일지와 남이 보는 기록은 막을 것이 다르다

받아들일 수 있었던 이유가 이거였다. 이 앱의 기록은 볼 사람이 자기뿐이다. 남에게 보이거나 심사를 받는 기록이 아니다.

그러면 「나중에 고쳐서 예뻐 보이게 만들 수 있다」가 문제가 아니다. 스스로를 속이는 건 앱이 막을 문제가 아니다.

  • 자기 일지와 감사 기록은 다르다. 같은 「기록」이라는 말을 쓰지만 막아야 할 것이 정반대다
  • 무엇을 막을지 정할 때 기능부터 보지 말고 「누가 이걸 보나」를 먼저 묻는다. 보는 사람이 자기뿐이면 막을 것이 거의 없다

방을 하나 더 붙이자 굳은 자리 셋이 드러났다

방을 하나 더 붙였더니 세 군데가 한꺼번에 드러났다. 스타일에 방 번호가 하나씩 적혀 있었고, 탭과 거리 끄트머리가 개수를 따로 세고 있었고, 방으로 가는 코드에 맨 숫자가 박혀 있었다.

셋 다 방이 셋일 때는 아무 문제가 없었다. 그래서 안 보였다.

  • 「지금 몇 개인가」에 맞춰 쓴 코드는 그 개수에서는 완벽하게 동작한다. 굳어 있다는 증거가 화면에 안 나타난다
  • 늘려 보거나 합쳐 보는 것이 설계를 확인하는 방법이다. 머리로만 보면 지금 모양이 늘 맞아 보인다
  • 마감을 반복 안으로 합쳤을 때도 같았다. 합쳐 보고 나서야 따로 두던 게 특수한 경우였다는 게 드러났다

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