주차 회고 3주차 2026-08-05

이번 주엔 새로 만든 게 없다 — 고장 난 것들이 알려준 여섯 가지

3주차엔 기능을 늘리는 대신 이미 만든 것들을 고쳤습니다. 따로 노는 버그처럼 보였는데, 정리해보니 전부 "에러 없이 조용히 틀리는" 같은 종류였습니다.

3주차엔 새 기능을 거의 안 만들었습니다. 대신 이미 돌아가던 것들이 하나씩 어긋나서, 그걸 쫓아다녔어요.

처음엔 다 따로 노는 문제인 줄 알았습니다. 글씨가 굵어지지 않고, 날씨를 못 가져오고, 예산이 이상하게 계산되고, 캘린더 일정이 반만 나오고. 그런데 다 고치고 나서 목록을 보니 공통점이 하나 있었습니다.

전부 에러가 안 났습니다. 프로그램은 멀쩡히 끝났고, 브리핑도 제시간에 왔어요. 다만 내용이 틀렸을 뿐입니다.

이번 주에 고친 것

증상제가 의심한 것실제 원인
브리핑 제목이 안 굵어짐태그를 잘못 썼나다른 파일에 같은 이름 함수가 있어서 서로 덮어씀
예산이 말도 안 되게 계산됨AI 프롬프트가 부실한가AI에게 나눗셈을 시킨 것 자체가 잘못
고쳤는데 그대로임저장이 안 됐나배포를 다시 안 함 (해당 파일과 무관해 보였는데도 필요)
카드 혜택 안내가 자꾸 빠짐조건식이 틀렸나흩어진 if문 — 조건 안 맞으면 조용히 사라짐
날씨 칸이 비어서 옴내 코드 문제인가외부 날씨 서버가 잠깐 죽음. 대비가 없었음
일정이 일부만 나옴캘린더 권한 문제인가”전부 읽는다”고 믿었던 함수가 기본 캘린더 하나만 읽고 있었음

하나씩 풀어볼게요.


1. AI에게 계산을 시키지 마세요

밤 브리핑 맨 아래에 “오늘 하루 총평”을 AI가 써줍니다. 이번 주에 이걸 좀 더 적극적으로 바꿨어요. 예산만 보지 말고 캘린더의 다음 주 일정까지 보고 조언하도록요.

“남은 예산은 넉넉하지만 다음 주에 모임이 두 건 있으니 지금 속도는 조금 빠릅니다” 같은 말을 해주면 좋겠다 싶었거든요.

그런데 결과물에 이런 문장이 나왔습니다.

예산 대비 62% 소진하셨네요!

숫자가 완전히 틀렸습니다. 예산 금액을 단위째 잘못 읽었어요. 쉼표가 들어간 금액을 앞자리만 보고 자릿수를 통째로 올려버린 겁니다. 예를 들면 이런 식입니다.

1,500,000원  →  AI가 읽기를 "1,500만 원"

처음엔 프롬프트에 “금액은 그대로 옮겨 적고 단위를 바꾸지 마세요”라고 못 박았습니다. 좀 나아졌지만 가끔 또 틀렸어요.

그러다 생각을 바꿨습니다. 왜 AI에게 계산을 시키고 있지?

지금은 이렇게 합니다. 나눗셈·퍼센트·남은 일수 같은 건 코드가 전부 미리 계산해서, AI에게는 완성된 문장으로 건네줍니다.

// 코드가 계산해서 문장으로 만들어 건넴
var 하루가능 = 남은일수 > 0 ? Math.max(0, 예산 - 누적) / 남은일수 : 0;
if (남은일수 > 0) lines.push('남은 예산을 남은 일수로 나누면 하루 ' + won(하루가능) + ' 꼴');

// 캘린더 일정도 목록으로 정리해서 건넴
if (events.length) {
  lines.push('앞으로 7일 안에 예정된 일정 ' + events.length + '건:');
  events.forEach(function (e) { lines.push('  · ' + e); });
}

AI는 이 문장들을 받아서 말투를 입히고 맥락을 엮는 일만 합니다. 숫자를 만들지 않아요.

잘하는 걸 시키세요. AI는 “다음 주에 약속이 있으니 지금 속도가 빠르다”는 판단은 잘합니다. 하지만 자릿수 큰 나눗셈은 계산기가 훨씬 잘해요. 계산은 코드, 해석은 AI.

이건 1주차에 쓴 역할 분담 이야기와 같은 결론인데, 그땐 머리로만 알았고 이번엔 데여서 배웠습니다.


2. 파일을 나눠도, 이름은 한 방에 삽니다

브리핑에서 ━ 오늘 지출 ━ 같은 제목 줄을 굵게 만들고 싶었습니다. 텔레그램은 <b> 태그를 지원하니까 간단할 줄 알았어요.

코드를 넣었습니다. 안 굵어집니다. 태그를 확인했습니다. 맞습니다. 테스트했습니다. 통과합니다. 그런데 실제 메시지는 안 굵어져요.

원인은 다른 파일에 있었습니다.

제 앱스크립트 프로젝트에는 파일이 여러 개 있습니다. 아침 브리핑, 밤 브리핑, 카드 추적… 파일을 나눠놨으니 서로 독립적이라고 생각했어요.

아닙니다. 앱스크립트는 프로젝트 안의 모든 파일이 하나의 이름표 공간을 씁니다. 파일이 달라도 같은 이름의 함수가 두 개 있으면, 하나가 조용히 다른 하나를 덮어씁니다. 경고도 에러도 없습니다.

아침브리핑.gs  →  function _볼드적용(text) { ... }   ← 아침용 규칙
밤브리핑.gs    →  function _볼드적용(text) { ... }   ← 밤용 규칙

결과: 둘 중 하나만 살아남음. 밤 브리핑이 아침용 규칙으로 돌아감.

고치는 건 5초였습니다. 밤 브리핑 쪽 이름을 _밤_볼드적용으로 바꿨어요. 문제는 원인을 찾는 데 한참 걸렸다는 겁니다. 저는 계속 밤 브리핑 파일만 들여다보고 있었거든요.

그 뒤로는 파일을 고칠 때마다 프로젝트 전체에서 같은 이름이 또 있는지 훑는 습관을 들였습니다.

폴더로 나눠놨다고 격리된 게 아닙니다. 그 도구가 실제로 이름을 어떻게 취급하는지를 확인해야 합니다.


3. “고쳤는데 왜 그대로지” — 배포는 프로젝트 전체를 찍습니다

텔레그램으로 ”○○ 다 했어”라고 보내면 일정 시트에서 그 항목을 완료 처리하는 기능이 있습니다. 이게 갑자기 못 알아듣기 시작했어요.

관련 파일을 고쳤습니다. 저장했습니다. 그대로입니다.

이때 제가 잘못 판단했습니다. “이 파일에는 외부 요청을 받는 입구(doPost)가 없으니 재배포는 필요 없다”고 생각했어요. 입구가 있는 파일만 다시 배포하면 되는 줄 알았습니다.

틀렸습니다.

웹앱을 배포하면 그 순간의 프로젝트 전체가 통째로 사진처럼 찍힙니다. 외부에서 요청이 들어오면 지금 편집기에 있는 코드가 아니라 찍혀 있는 그 사진 속 코드가 돌아갑니다. 입구는 한 파일에 있지만, 그 입구가 불러 쓰는 다른 파일들도 전부 사진에 같이 들어가 있는 거예요.

확인 방법은 단순했습니다. 실제로 요청을 한 번 보내보니 제가 이미 지운 옛날 문구가 그대로 돌아왔습니다. 편집기의 코드와 실제로 도는 코드가 다르다는 증거였죠.

편집기에서 저장  →  트리거로 도는 기능(정해진 시각에 실행)은 바로 반영 ✅
                 →  웹앱으로 도는 기능(외부 요청 처리)은 재배포해야 반영 ❗

앱스크립트 기준으로는 배포 관리 → 수정(연필) → 버전을 “새 버전”으로 → 배포까지 해야 합니다. 새 배포를 만들면 주소가 바뀌니, 반드시 기존 배포를 수정해야 하고요.

1주차에 로컬·깃허브·배포 3단계를 헷갈렸던 글을 썼는데, 도구만 바뀌었지 같은 함정에 또 빠진 셈입니다. “저장했다”와 “돌아가고 있다”는 다릅니다.


4. 매달 코드를 고쳐야 한다면, 설계가 틀린 겁니다

카드마다 “전달에 얼마 이상 썼으면 이번 달 혜택을 준다”는 조건이 있습니다. 그래서 브리핑이 전월 실적을 알아야 해요.

처음엔 코드에 숫자를 적어뒀습니다.

var 전월실적 = { 'A카드': 800000, 'B카드': 1200000 };   // ← 매달 손으로 고쳐야 함

이걸 보고 바로 말씀하신 게 있었습니다.

“그럼 다음 달엔 다시 앱스크립트를 고쳐야 하는 거잖아. 다른 방법 없어?”

맞는 지적이었습니다. 매달 코드를 여는 건 언젠가 반드시 까먹을 일이고, 까먹으면 브리핑이 조용히 틀린 소리를 하게 됩니다.

그래서 「카드설정」이라는 시트 탭을 하나 만들었습니다. 거기에 월별로 숫자를 적어두면 코드가 읽어가요.

년월A카드B카드메모
2026-07800,0001,200,000카드앱에서 확인
2026-08

숫자를 바꾸는 건 시트에 타이핑이고, 코드는 손대지 않습니다. 값이 비어 있으면 알아서 자동 집계로 넘어가게도 해뒀어요.

자주 바뀌는 값은 코드가 아니라 데이터입니다. 코드에 박아넣으면 값을 바꿀 때마다 프로그램을 고쳐야 하고, 그건 매번 새로 망가질 기회가 생긴다는 뜻입니다.


5. 흩어진 if문은 반드시 빠집니다

카드 혜택 안내가 계속 불완전했습니다. 분명히 조건을 만족했는데 브리핑에 그 얘기가 없어요.

코드를 보니 이렇게 생겼습니다.

if (조건A) 팁.push('A 혜택 이렇게 쓰세요');
if (조건B) 팁.push('B 혜택 남았어요');
if (조건C) 팁.push('C 혜택 곧 끝나요');
// … 이런 게 여기저기 흩어져 있음

문제가 보이시나요? 조건이 안 맞으면 아무 말도 안 합니다. “조건이 안 맞아서 못 알려드립니다”조차 안 나와요. 그냥 사라집니다.

게다가 혜택이 늘어날 때마다 if문을 새로 붙여야 하는데, 붙이는 걸 깜빡하면 그 혜택은 영원히 안내되지 않습니다. 그리고 깜빡했다는 사실조차 모르죠.

그래서 방식을 바꿨습니다. 혜택을 표 하나에 전부 적어놓고, 코드가 그 표를 처음부터 끝까지 훑게 했어요.

var 혜택표 = [
  { 이름: '온라인쇼핑 5%', 종류: '횟수', 쓴것: 2, 한도: 2,
    조건: '건당 2만원 이상',
    대안: '같은 결제는 B카드 야간 할인으로 돌리세요' },
  // … 스무 개 남짓
];

혜택표.forEach(function (b) {
  if (다썼는지(b))      끝난것.push(b);      // 소진 → 대안까지 같이 안내
  else if (임박한지(b)) 임박한것.push(b);    // 한 번 남음 → 미리 알림
});

바뀐 점이 세 가지입니다.

  1. 빠지지 않습니다. 표에 있는 건 전부 검사받습니다.
  2. 다 쓴 혜택도 알려줍니다. “이건 이번 달 끝났어요”라고 말해주고, 대신 쓸 카드까지 같이요.
  3. 혜택 추가가 표에 한 줄 넣는 일이 됐습니다. 코드 로직은 안 건드립니다.

여기서 재밌었던 건 대안끼리 서로 참조하게 만든 부분입니다. “A가 끝났으니 B를 쓰세요”라고 안내하려는데 B도 이미 다 썼으면 소용없잖아요. 그래서 대안으로 지목한 혜택이 살아있는지도 같이 확인하게 했습니다.

조건문이 흩어져 있으면 “빠졌다”는 사실이 안 보입니다. 목록으로 만들어 전부 순회하면, 빠질 수가 없어요.


6. 하나뿐인 연결은 언젠가 끊깁니다

어느 날 아침 브리핑에 날씨가 비어서 왔습니다. “연결하지 못했습니다”라는 문구만요.

제 코드 문제가 아니었습니다. 쓰던 무료 날씨 서버가 그 시각에 잠깐 응답을 안 한 거예요. 제가 어쩔 수 있는 게 아닙니다.

어쩔 수 없다는 게 대비하지 않을 이유는 아니었습니다. 두 가지를 넣었어요.

1) 재시도  — 실패하면 1.5초 쉬고 다시, 최대 3번
             (일시적 끊김은 대부분 여기서 해결됩니다)

2) 대체 경로 — 3번 다 실패하면 다른 기관의 날씨 서버로

대체 서버는 주는 정보가 조금 달랐습니다. 강수 확률이 없고 강수 만 있어요. 그래서 무리해서 맞추지 않고, 가진 것만 정직하게 표시하게 했습니다. 없는 값을 지어내는 것보다 낫다고 봤어요.

외부 서비스는 언젠가 반드시 실패합니다. 그게 이상한 게 아니라 정상입니다. 재시도와 대안, 두 겹만 있어도 대부분의 아침은 무사합니다.


덤: 모르면 버리지 말고, 짐작하되 표시를 남기기

가계부가 상호명을 못 알아보면 전부 ‘기타지출’로 던져놨습니다. 나중에 보면 뭔지 알 수 없는 덩어리만 남죠.

두 가지를 넣었습니다.

첫째, 법인명 사전. 카드 명세서엔 브랜드가 아니라 법인명이 찍힙니다. 우리가 아는 이름과 달라요. 그래서 규칙에 하나도 안 걸렸던 겁니다.

var 상호별칭 = [
  { 법인: '○○○형제들',   브랜드: '배달앱' },
  { 법인: '○○모빌리티',   브랜드: '택시' },
  { 법인: '○○리테일',     브랜드: '편의점' },
];

판정 전에 법인명이 보이면 브랜드명을 슬쩍 덧붙여줍니다. 규칙은 그대로 두고 입력만 보강하는 방식이라, 기존 규칙을 하나도 안 건드렸어요.

둘째, 시간대로 짐작하기. 그래도 모르는 상호는 결제 시각으로 추측합니다. 점심·저녁 시간대면 식사일 가능성이 높으니까요. 평일과 주말은 다르게 잡았고요.

중요한 건 짐작한 티를 남긴 것입니다.

memo: (확실히_모름 ? '[분류확인] ' : (짐작했음 ? '[추정] ' : '')) + 원본알림

[추정]이 붙어 있으면 나중에 시트에서 걸러서 확인할 수 있습니다. 조용히 짐작하는 게 제일 나쁩니다. 맞았는지 틀렸는지 확인할 방법이 없어지니까요.


관통하는 하나

다시 처음 표로 돌아가면, 여섯 개가 전부 이렇습니다.

아무것도 터지지 않았습니다.

  • 볼드가 안 먹어도 → 메시지는 정상 발송
  • 배포가 안 돼도 → 웹앱은 200 응답
  • 혜택 안내가 빠져도 → 브리핑은 멀쩡히 도착
  • 예산 숫자가 틀려도 → 문장은 아주 자연스러움
  • 캘린더가 반만 읽혀도 → 일정 목록은 그럴듯함

에러 메시지가 뜨는 버그는 친절한 버그입니다. 어디가 아픈지 알려주니까요. 진짜 골치 아픈 건 정상처럼 보이는 틀린 결과입니다. 몇 주씩 모르고 지나갈 수 있어요.

그래서 이번 주엔 테스트를 많이 만들었습니다. 지금 22개가 돌아갑니다. 코드를 고칠 때마다 전부 한 번씩 실행해요.

passed=22  failed=0

재밌는 건, 테스트가 실패했을 때 원인이 코드가 아니라 제 테스트였던 적이 여러 번이라는 겁니다. 기댓값을 잘못 적었거나, 예전 이름으로 검사하고 있었거나. 그때마다 “코드를 고칠까, 테스트를 고칠까”를 매번 따져봐야 했습니다.

이것도 배운 것 중 하나였어요. 초록불이 떴다고 맞는 게 아니고, 빨간불이 떴다고 코드가 틀린 것도 아닙니다.

이번 주 남은 숙제

  • 다른 간편결제 수단은 아직 안 고쳤습니다. 카카오페이 때와 같은 이유일 수 있는데, 실제 알림을 아직 못 봤어요. 이번엔 상상해서 만들지 않으려고 기다리는 중입니다.
  • 모르는 상호 짐작 규칙은 이제 막 넣어서, [추정] 표시가 실제로 얼마나 맞는지 한 달쯤 지켜봐야 합니다.

3주차를 한 줄로 줄이면 이렇습니다.

“돌아가는 것처럼 보인다”와 “제대로 돌아간다” 사이에는 생각보다 넓은 강이 있다.