toss reality — fiction 05

그래도 출근한다

6개월 전, 47건의 코멘트를 받았다.
이제 다른 사람의 PR에 리뷰를 남긴다.

Part I

"반기 리뷰"

2027년 8월 11일 월요일 오후 2시. 입사 6개월. 수습 종료 면담.

회의실에 도현과 박재원이 앉아 있었다. 재원은 FE 리드로서 도현의 반기 리뷰를 진행한다. 책상 위에 노트북이 열려 있고, 화면에는 도현의 6개월간 GitHub 활동 요약이 표시되어 있다.

"도현 님, 처음 올린 PR 기억나요?"

"800줄짜리요."

재원이 웃었다.

"47건 코멘트."

"네, 47건."

"지난달 도현 님이 신규 입사자 PR에 남긴 리뷰 봤어요. TDS 관련 코멘트 특히 좋았습니다. '여기서 커스텀 CSS 대신 TDS Skeleton을 쓰시면 됩니다'라고 썼더라고요."

도현은 고개를 끄덕였다. 그 코멘트를 쓸 때, 6개월 전 김나현이 자기에게 남긴 코멘트가 떠올랐다. "TDS에 Skeleton 컴포넌트가 이미 있어요!" 도현은 그것을 거의 그대로 인용한 것이다.

6개월 전 내가 당한 것을 이제 내가 하고 있다. 코드 리뷰 47건을 받던 사람이, 이제 코드 리뷰를 쓰는 사람이 되었다. 이것이 성장이라는 것을 수치로 증명할 수 있는지 모르겠다. 하지만 6개월 전의 나라면 "TDS Skeleton을 쓰시면 됩니다"라는 코멘트를 쓸 수 없었다. TDS가 뭔지도 몰랐으니까.

"수습 결과부터 말씀드리면, 정규 전환 확정입니다."

도현은 고개를 끄덕였다. 안도감보다 당연하다는 느낌이 먼저 왔다. 6개월을 버텼다. 블라인드에서 봤던 "경력직 6개월 생존" 통계의 바깥에 서 있다. 동기 이수민은 4개월에 떠났다. 도현은 남았다.

"도현 님의 강점을 하나 꼽자면, 피드백 수용 속도예요. 같은 실수를 반복하지 않더라고요. 처음에 TDS를 안 쓰셨는데, 두 번째 PR부터는 TDS 먼저 확인하시잖아요."

"원래 직장에서 혼자 개발해서, 다른 사람의 기준에 맞추는 것 자체가 처음이었거든요."

"그래서 더 인상적이었어요. 보통 경력 5년 넘은 분들이 기존 습관을 바꾸기 어려워하시거든요. 도현 님은 4년간의 습관을 6개월 만에 바꿨어요."

도현은 대답하지 않았다. "4년간의 습관"이라는 표현이 정확했다. 4년간 만든 습관: "fixed stuff" 커밋, 800줄짜리 PR, useState+useEffect, 커스텀 CSS, 테스트 0개, 접근성 무시. 이것들을 6개월 만에 바꾼 것이다. 바꿀 수 있었던 이유는 간단하다. 그 습관이 "좋은 습관"이라고 생각한 적이 없었기 때문이다. 그저 아무도 고쳐주지 않았을 뿐이다.

* * *

Part II

"숫자의 이름"

면담이 끝나고 도현은 자리에서 6개월 성적표를 정리했다.

PR 제출 47건
PR 머지 43건
배포 23회
기능 빌드 8개
기능 생존 3개
기능 킬 5개
코드 리뷰 작성 89건
장애 대응 2회

PR 47건, 머지 43건. 기능 8개 중 살아남은 것 3개. 코드 리뷰 작성 89건. 장애 대응 2회.

그리고 가장 큰 숫자.

최대 임팩트
+0.7%
결제 전환율
코드 변경량
12줄
애니메이션 최적화
연간 매출 영향
2.4억
추정치

결제 완료 애니메이션 0.3초 단축. 전환율 +0.7%. 연간 추정 매출 영향 2.4억 원. 12줄의 코드가 만든 숫자다.

이전에는 매출 영향을 숫자로 증명한 적이 없다. QuoteMate의 1.2% 자동화율이 있었지만, 그것이 매출에 얼마나 기여하는지 계산한 적은 없다. 계산할 도구도 없었고, 계산하라고 요구하는 사람도 없었다. 여기서는 6개월 만에 숫자가 생겼다. 12줄짜리 숫자이긴 하지만, 0은 아니다.

도현은 PR 47건이라는 숫자를 보며 미소를 지었다. 첫 번째 PR의 리뷰 코멘트도 47건이었다. 우연의 일치다. 하지만 의미가 있다고 느꼈다. 47건의 코멘트를 받은 사람이, 6개월 동안 47건의 PR을 올렸다. 코멘트 하나가 PR 하나로 바뀐 셈이다.

* * *

Part III

"자정의 커밋"

8월 16일 토요일 밤 11시. 안양 원룸.

도현은 침대에 누워 있다가 노트북을 열었다. 토스 코드가 아니다. 개인 노트북이다. GitHub에 접속했다. 1년 넘게 열지 않은 리포지토리가 있다.

# GitHub — Personal Repository
Repository: quoteMate-mvp
Last commit: 2026-07-22 # 1년 전
Commit message: "진짜 최종 (3)"
Branch: main (only)
Tests: 0
CI/CD: none

QuoteMate. 전작에서 만든 AI 기반 견적 자동화 MVP. 넥스트비전의 김 대표가 "미래먹거리"라고 불렀던 것. 도현이 이것으로 투자자 앞에서 데모를 했다. Supabase 무료 티어 500MB, ChatGPT API 월 29,000원, Vercel 무료 호스팅. 그리고 투자 유치 목표 3억 원.

도현은 코드를 열었다. 1년 만이다.

quoteMate-mvp/src/pages/quote.js — 1년 전의 코드
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
  fetch('/api/quote')
    .then(res => res.json())
    .then(d => { setData(d); setLoading(false); })
    .catch(e => { setError(e); setLoading(false); });
}, []);

// 스타일: global.css 1,847줄 중 어딘가에 .quote-page { ... }
// 에러 처리: catch(e => setError(e)) — 이후 아무것도 안 함
// 테스트: 없음
// 접근성: 없음
// 타입: JavaScript (TypeScript 아님)

토스의 눈으로 보면 모든 것이 보였다.

useState 3개 나열 — React Query면 한 줄이다. 변수명 "data" — paymentCardInfo처럼 의미 있는 이름이어야 한다. global.css 1,847줄 — emotion으로 컴포넌트별 스타일을 분리해야 한다. 에러 처리 — catch하고 아무것도 안 하는 것은 처리가 아니라 은닉이다. 테스트 0개 — 최소 4개 케이스는 커버해야 한다. 접근성 — aria 속성 하나 없다.

# QuoteMate — 토스 기준 진단

Conventional Commit: 없음 ("진짜 최종 (3)")
PR 기반 개발: 없음 (main 직접 push)
테스트: 0개
컴포넌트 분리: 미흡 (pages/ 3개가 전부)
에러 처리: catch 후 무처리
접근성: 0
CI/CD: 없음
타입 안전성: JavaScript (no types)
아이디어: 유효 (견적 자동화는 실제 수요 있음)
동작: 한다 (Supabase + Vercel + ChatGPT)

1년 전의 나는 이 코드로 3억을 달라고 했다. 투자자 앞에서 이 코드를 시연했다. "진짜 최종 (3)"이라는 커밋 메시지로. 웃음이 나온다. 아이디어는 나쁘지 않았다. 견적 자동화는 실제로 수요가 있는 문제다. 하지만 코드는 나빴다. 동작하는 것과 좋은 것은 다르다. 이것을 알기까지 5년이 걸렸다. 넥스트비전 4년 + 토스 6개월 + 투자 거절 1번.

도현은 리팩토링을 시작했다.

먼저 브랜치를 만들었다. main에 직접 push하지 않는다.

$ git checkout -b refactor/modernize-stack
$ git log --oneline -5
a3f7c21 진짜 최종 (3)
9e2b1d4 ㅇㅇ
f84c0a2 wip
3d19e87 asdf
c72a5f1 fixed stuff

커밋 로그를 보고 잠시 멈추었다. 이것이 1년 전의 자기 기록이다. "진짜 최종 (3)", "ㅇㅇ", "wip", "asdf", "fixed stuff". 넥스트비전에서의 4년이 이 다섯 줄에 요약되어 있다.

도현은 코드를 고치기 시작했다. 이번에는 토스에서 배운 방식으로.

quoteMate-mvp refactoring — first commit
// Before: useState 3개 + useEffect + fetch
// After: React Query

const {
  data: quoteEstimation,
  isLoading,
  error,
} = useQuery({
  queryKey: ['quote', projectId],
  queryFn: () => fetchQuoteEstimation(projectId),
  staleTime: 5 * 60 * 1000,
});

if (error) return <ErrorBoundary error={error} />;
if (isLoading) return <QuoteSkeleton aria-busy="true" />;

React Query. 의미 있는 변수명. 에러 바운더리. 스켈레톤 UI. aria-busy. 6개월 전에는 하나도 몰랐던 것들이다.

커밋 메시지를 썼다.

$ git commit -m "refactor(quote): useState+useEffect를 React Query로 전환"

Conventional Commit. "fixed stuff"가 아니라. 아무도 이 커밋을 리뷰하지 않는다. 개인 프로젝트니까. 하지만 도현은 토스에서 배운 형식으로 쓴다. 습관이 되었기 때문이다.

새벽 2시. 도현은 리팩토링을 멈추었다. 전체를 다 고치지는 못했다. 하지만 quote.js를 Quote.tsx로 바꾸고, React Query를 적용하고, 에러 바운더리를 추가하고, 테스트를 2개 썼다. 커밋 4개.

# QuoteMate refactoring — commits
1. refactor(quote): useState+useEffect를 React Query로 전환
2. refactor(quote): quote.js → Quote.tsx 타입스크립트 마이그레이션
3. feat(quote): 에러 바운더리 및 스켈레톤 UI 추가
4. test(quote): 견적 조회 로딩/에러 상태 테스트 추가

1년 전에는 "진짜 최종 (3)"으로 커밋했다. 지금은 "refactor(quote): useState+useEffect를 React Query로 전환"으로 커밋한다. 같은 사람이다. 같은 프로젝트다. 같은 기능을 고치는 것이다. 달라진 것은 나다. 코드가 좋아진 것이 아니라, 코드를 보는 눈이 좋아진 것이다. 47건의 코멘트가 바꾼 것은 코드가 아니라 기준이다.

* * *

Part IV

"그래도 출근한다"

8월 18일 월요일 오전 8시 50분. 토스 사옥 앞.

도현이 사원증을 찍었다. 6개월 전, 같은 곳에서 사원증을 받았다. 그때는 3,800만 원의 사이닝 보너스가 통장에 찍히는 것이 비현실적이었다. 지금은 480만 원이 매달 들어오는 것이 일상이다.

편의점은 여전히 공짜다. 여전히 밤에만 간다. 커피는 여전히 테이크아웃이다. 자리에서 모니터를 보며 마신다. 금요일 오후는 여전히 스프린트에 바친다. 헤어살롱은 여전히 0회다.

하지만 달라진 것이 있다.

PR을 올릴 때 더 이상 떨리지 않는다. 300줄. Conventional Commit. TDS 먼저. 테스트 3개 이상. 이것이 몸에 배었다. 코드 리뷰를 받을 때 더 이상 부끄럽지 않는다. 코멘트는 공격이 아니라 기준이라는 것을 안다. 47건의 코멘트가 가르쳐준 것은 코드가 아니라 태도다.

엘리베이터에서 재원을 만났다.

"도현 님, 주말에 뭐 하셨어요?"

"예전에 만들었던 사이드 프로젝트 리팩토링했어요."

"오, 뭔 프로젝트요?"

"AI 견적 자동화요. 전 직장에서 만들었던 건데, 토스 기준으로 보니까 전부 다시 짜야 하더라고요."

"ㅋㅋ 다 그래요. 저도 카카오 때 만들었던 코드 나중에 보면 소름 끼쳐요."

도현은 자리에 앉았다. 맥북을 열었다. Slack을 확인했다. 사일로 채널에 새 메시지가 없다. 스프린트 보드를 열었다. 이번 주 작업: 결제 실패 복구 UI의 A/B 테스트 결과 분석 대응. 도현이 만든 BottomSheet가 지금 50%의 사용자에게 보이고 있다. 72시간 뒤에 결과가 나온다.

도현은 작업을 시작하기 전에 사일로 채널에 메시지를 보냈다.

# settlement-silo
한도현 09:03
출근했습니다. 주말에 예전에 만들었던 사이드 프로젝트 리팩토링하다가 발견한 패턴이 있는데, 에러 바운더리 관련이에요. 우리 결제 실패 복구 UI에도 적용할 수 있을 것 같은데, 오늘 공유해도 될까요?
박재원 09:05
오 뭔데요? 점심 때 얘기해요.
강서윤 (PO) 09:06
좋아요! 결제 실패 복구 A/B 결과 나오면 같이 논의하죠.

도현은 맥북 화면을 보았다. VS Code가 열려 있다. 결제 실패 복구 BottomSheet의 코드가 보인다. 6개월 전, 이 화면에서 800줄짜리 PR을 올렸다. 지금은 87줄짜리 BottomSheet가 프로덕션에서 돌아가고 있다.

도현은 코드를 치기 시작했다.

오늘도 만든다. 내일 죽을 수도 있다. 데이터가 -0.3%라고 말하면 죽는다. +0.7%라고 말하면 산다. 그것이 여기의 규칙이다.

편의점은 여전히 공짜다.

야근은 여전히 한다.

워라밸 점수는 여전히 2.1점쯤일 것이다.

하지만 도현은 출근한다. 어제보다 조금 더 깨끗한 코드를 치기 위해. "fixed stuff"가 아닌 커밋을 남기기 위해. 47건의 코멘트가 남긴 기준을 지키기 위해.

넥스트비전에서 4년을 버텼다. 여기서 6개월을 버텼다. 둘 다 버텼다는 점은 같다. 하지만 버틴 결과가 다르다. 넥스트비전에서 버틴 결과는 "진짜 최종 (3)"이라는 커밋이었다. 여기서 버틴 결과는 "refactor(quote): useState+useEffect를 React Query로 전환"이라는 커밋이다.

같은 사람이다. 같은 손으로 키보드를 친다. 달라진 것은 기준이다.

도현은 코드를 친다. 오늘도.

같은 사람이다
달라진 것은 기준이다

"진짜 최종 (3)"에서 "refactor(quote): useState+useEffect를 React Query로 전환"까지. 5년, 47건, 0.3초.