글 목록으로생각과 관점

저는 코드를 읽어요

변경된 코드에서 상태, 요청 조건과 테스트를 확인하는 과정을 그린 도해예요.

변경된 코드에서 상태, 요청 조건과 테스트를 확인하는 과정을 그린 도해예요.

에이전트가 만든 코드를 읽지 않고 화면 동작에 집중해 본 스프린트가 있었어요. 직접 눌러서 잘 돌아가면 충분할 거라고 생각했는데, 끝나고 핫픽스가 많이 생겼어요. 제가 눌러 보지 않은 조건은 화면이 잘 돌아간다는 판단 안에 없었어요.

그 뒤로는 diff를 읽으면서 어떤 경우를 테스트할지 찾아요. 요구사항을 따라 화면을 확인하는 블랙박스 테스트에, 코드의 분기와 실행 순서에서 경로를 고르는 화이트박스 테스트를 더한 거예요. 읽는 것으로 끝내지 않고 찾은 경로를 실행해서 확인해요.

화면에서 고르는 경우와 코드에서 찾는 경우

블랙박스 테스트에서는 내부 구현보다 입력과 결과를 봐요. 요구사항대로 화면을 누르거나 E2E로 사용자 흐름을 재현하고, QA 시나리오를 확인하는 일이 여기에 들어가요. 사용자가 겪는 동작을 확인하고 구현이 바뀌어도 같은 기대를 유지하기 좋아요.

화이트박스 테스트는 코드 안에서 확인할 경로를 골라요. 조건을 어떤 순서로 검사하는지, 상태를 누가 바꾸는지 읽고 그 경로를 테스트해요. 내부를 알아야 떠올릴 수 있는 경우를 찾아 실행하는 방식이에요.

구분블랙박스 테스트화이트박스 테스트
경우를 고르는 기준요구사항과 사용자 행동분기, 조건 순서와 상태 흐름
제가 하는 일화면 조작, E2E와 QA 시나리오 확인코드에서 경로를 찾고 테스트 작성과 실행
놓칠 수 있는 부분떠올리지 못한 입력과 내부 조건코드만 읽어서는 드러나지 않는 화면 동작

그래서 제 작업에는 둘 다 들어가 있어요.

text
티켓·plan 검토
→ 테스트 작성
→ 에이전트 구현·에이전트 코드 리뷰
→ 변경분 테스트·lint·typecheck
→ 제 diff 리뷰·dev 화면에서 동작 확인
→ 전체 CI(테스트·knip·lint)
→ QA 더블 체크 요청

diff 리뷰가 곧 화이트박스 테스트인 것은 아니에요. 리뷰로 경로를 찾은 뒤 그 조건으로 테스트를 만들고 실행하는 부분이 화이트박스 접근이에요. dev 화면과 QA에서는 요구사항대로 움직이는지도 다시 봐요.

임시 진료일 조건이 있어도 실행되지 않을 수 있었어요

리팩터링은 화면이 같아 보여도 초기값을 넣는 시점이나 뒤로 갔을 때 남는 상태, 요청 실패 처리가 바뀔 수 있어요. 에이전트가 추가한 분기를 제가 모른 채 정상 흐름만 따라가면 이런 차이를 찾기 어려워요.

임시 진료일을 추가할 때도 입력 화면 외에 예약 가능일 판정을 봐야 했어요. 평소 쉬는 요일이라도 병원이 임시로 열었다면 예약할 수 있어야 해요. 기존 휴무 조건 뒤에 새 조건만 붙이면 아래처럼 돼요. 설명에 필요한 순서만 남기고 함수 이름은 바꿨어요.

typescript
if (isPastDate(day)) return false;
if (isWeeklyClosed(day)) return false;
if (hasTemporaryOpening(day)) return true;
return !isTemporarilyClosed(day);

요일 휴무 조건에서 먼저 false를 반환하면 hasTemporaryOpening은 실행되지 않아요. 평소 진료하는 날에 임시 진료일을 넣어 봐서는 이 문제를 찾지 못해요. 휴무 요일과 겹치는 날을 골라야 해요.

typescript
if (isPastDate(day)) return false;
if (hasTemporaryOpening(day)) return true;
if (isWeeklyClosed(day)) return false;
return !isTemporarilyClosed(day);

실제 코드도 지난 날짜를 먼저 막은 뒤, 임시 진료일을 요일 휴무와 임시 휴무보다 먼저 보도록 바꿨어요. 서버는 임시 진료와 임시 휴무를 같은 배열에 담아 줘서 항목을 모두 휴무로 읽던 부분도 종류를 구분해야 했어요. type이 없는 옛 데이터는 계속 휴무로 해석했어요.

이건 운영에서 막힌 예약을 고친 사례가 아니라 기능을 넣으면서 찾은 경우예요. 휴무 요일을 임시 진료일로 열기, 임시 휴무와 겹치더라도 열기, 지난 날짜는 계속 막기를 테스트에 남겼어요. 조건이 추가됐는지만 봤다면 놓쳤을 순서와 옛 데이터의 해석을 함께 확인할 수 있었어요.

코드를 읽고도 스크롤은 놓쳤어요

그렇다고 코드만 읽으면 충분한 것도 아니었어요. 복지몰 Bottom Sheet를 compound 구조로 바꿀 때 AI의 구현과 기존 검토를 믿고 마쳤는데, 헤더가 본문과 같은 스크롤 영역에 들어가 있었어요. 내용을 내리면 제목도 함께 올라갔어요.

나중에 드래그 크래시 때문에 라이브러리를 올리면서도 이 구조를 그대로 옮겼어요. diff를 읽고 높이와 스냅 위치까지 봤지만 제목이 고정되는지는 확인하지 못했어요. 그 뒤로 스크롤과 드래그는 브라우저에서 직접 조작하고, 다시 생기면 알 수 있도록 브라우저 테스트를 남겨요. 수정 과정은 Bottom Sheet 글에 적었어요.

임시 진료일은 조건 순서를 읽어야 검사할 날을 찾을 수 있었고, 시트는 실제로 스크롤해야 놓친 동작을 볼 수 있었어요. 테스트 성공 결과와 코드 리뷰, 화면 확인을 따로 두는 이유예요.

완성본을 받으면 바뀐 흐름을 따라 읽어요

모든 diff를 똑같이 읽지는 않아요. 포맷이나 동작을 보존하는 lint 수정, 정해 둔 위치로 파일을 옮기고 import만 바꾼 작업은 그 범위에 맞춰 봐요. 하지만 'lint 적용' 중에 분기 순서를 바꿨거나, 파일을 옮기며 서버와 클라이언트 경계나 모듈 초기화가 달라졌다면 동작 변경으로 읽어요.

기능과 리팩터링에서는 입력이 어디로 들어오고 누가 상태를 바꾸며 어떤 조건에서 요청하는지 따라가요. 매번 저장소 전체를 읽는 대신 알고 있던 흐름과 달라진 부분을 보고, 필요하면 호출하는 코드까지 넘어가요.

테스트도 같은 방식으로 읽어요. 새 기능은 BDD 형식의 *.spec에서 필요한 조건을 보고, 운영이나 QA 버그는 *.test의 재현 경로를 봐요. 새 패턴의 테스트는 제품 코드를 일부러 깨뜨리면 테스트도 실패하는지 확인해요. 이름에 적힌 동작을 실제로 검사하지 않는 테스트는 성공해도 확인한 것이 없으니까요.

큰 기능은 완성본을 받아도 그대로 끝나지 않았어요. diff와 테스트를 읽고 빠진 요구사항이나 저장소 기준과 다른 부분을 설명하면, 에이전트가 다시 고치고 제가 수정본을 봐요. 다른 작업을 구현하거나 반려한 부분을 고치는 동안에는 먼저 받은 diff나 다음 작업과 연결된 기존 코드를 읽어요. 읽기와 구현을 겹쳐 진행하는 편이에요.

Compound Engineering을 참고하며 반복되는 수정은 skill과 AGENTS.md, lint에 남겼어요. 작은 기능이나 리팩터링에는 이제 손댈 부분이 없을 때도 있어요. 그때도 읽고 확인한 다음 그대로 써요. 구현이 좋아지면 제가 수정하는 시간이 줄지만, 무엇을 만들었는지 아는 일까지 없어지지는 않았어요.

읽을 수 있게 고치는 것도 이 과정의 일부예요

같은 역할의 코드가 다른 이름이나 위치에 있으면 동작을 보기 전에 찾는 데 시간을 써요. 상태를 바꾸는 곳이 흩어져 있거나 화면 처리와 데이터 변환이 섞여 있어도 분기를 놓치기 쉬워요. 읽기가 너무 어려워지면 결국 눈앞의 화면을 누르는 것으로 검사를 끝내게 돼요.

미사용 코드는 Knip으로 찾고, 폴더 위치나 import 방향은 lint로 검사해요. 상태를 어디에 둘지, 어떤 추상화를 쓸지는 사용처와 맥락을 읽어야 해서 skill과 AGENTS.md에 적어요. 한 함수에서 다루는 일의 수준을 맞추고 서로 다른 맥락을 나누는 것도, 다음에 읽을 때 실행 조건을 따라가기 쉽게 하려는 일이에요.

이름이 익숙하다는 것만으로 읽기 좋은 코드는 아니에요. 비슷한 이름으로 다른 역할을 하거나 짧게 만들려고 분기를 숨기면 오히려 놓치기 쉬워요. 각 부분이 무엇을 하고 언제 실행되는지 설명할 수 있는지를 봐요.

AI를 쓰면 폴더를 다시 나누거나 공통 컴포넌트의 인터페이스를 바꾸는 일도 맡길 수 있어요. 어떤 모양으로 바꿀지 정하고 사용처를 따라 읽으며 의도한 대로 바뀌었는지 확인해요. 저는 이 작업도 코드를 읽는 일을 계속하기 위한 정리로 보고 있어요.

참고