Skip to content

Split View: 낯선 코드베이스에 빠르게 적응하기: 새로 들어온 사람의 기술

|

낯선 코드베이스에 빠르게 적응하기: 새로 들어온 사람의 기술

"코드베이스를 이해하는 가장 빠른 길은 처음부터 읽는 것이 아니다. 그것을 살아 움직이게 만든 다음, 하나의 길을 끝까지 따라가는 것이다."

프롤로그 — 우리는 반복해서 새로 들어온 사람이 된다

"적응(ramp-up)"이라는 단어를 들으면 신입 개발자의 첫 출근날을 떠올리기 쉽습니다. 하지만 현실은 다릅니다. 우리는 커리어 내내 반복해서 새로 들어온 사람이 됩니다.

이직을 하면 새 회사의 코드베이스 앞에 섭니다. 팀을 옮기면 같은 회사인데도 처음 보는 서비스를 맡습니다. 누군가 퇴사하면서 5년 된 결제 시스템을 인수받기도 하고, 오픈소스에 기여하려고 남의 레포를 클론하기도 합니다. 사내에서 다른 팀의 버그를 고쳐주러 잠깐 들어가기도 합니다.

이 모든 상황에 공통점이 하나 있습니다. 이미 존재하는, 내가 만들지 않은, 머릿속에 지도가 없는 코드 앞에 서 있다는 것입니다.

여기서 중요한 사실: 적응은 재능이 아닙니다. 배울 수 있는 기술입니다. 어떤 사람은 낯선 레포에서 사흘 만에 첫 PR을 올리고, 어떤 사람은 3주가 지나도 "아직 파악 중"입니다. 그 차이는 머리가 좋고 나쁨이 아니라 방법을 아느냐 모르느냐입니다.

이 글은 그 방법에 대한 것입니다. 팀이나 매니저 입장에서 "온보딩을 어떻게 설계할까"가 아니라, **개인 입장에서 "나는 어떻게 빨리 생산성을 낼까"**에 대한 실전 가이드입니다. 둘은 다릅니다. 온보딩 설계는 팀의 일이지만, 적응은 결국 내가 하는 일입니다. 좋은 온보딩 문서가 없어도 — 대부분의 경우 없습니다 — 빠르게 적응하는 기술은 내 손에 있습니다.

이 글에서 다룰 내용:

주제
1장일단 돌려보기 — 가장 레버리지가 큰 한 수
2장진입점 찾기 — 요청은 어디서 시작되는가
3장디테일보다 형태 먼저 읽기
4장요청 하나를 끝까지 따라가기 (수직 슬라이스)
5장테스트를 문서로 읽기
6장도구를 쓴다 — grep, 정의로 이동, git을 고고학으로
7장작은 변경을 일찍 한다
8장멘탈 맵을 그리고 적어둔다
9장무엇을, 누구에게 물을 것인가
10장AI 에이전트와 함께 적응하기
에필로그체크리스트 + 안티패턴 + 다음 글 예고

1장 · 일단 돌려보기

낯선 레포를 받았을 때 첫 본능은 보통 "코드를 읽자"입니다. 잘못된 본능입니다. 가장 레버리지가 큰 첫 수는 로컬에서 돌아가게 만드는 것입니다.

1.1 작동하는 환경이 일주일치 독서를 이긴다

코드만 읽으면 머릿속에 정적인 그림만 생깁니다. 하지만 돌아가는 환경이 있으면 살아 있는 시스템을 손에 쥐게 됩니다. 값을 바꿔보고, 로그를 찍어보고, 디버거를 붙이고, "이걸 지우면 어떻게 되지?"를 실제로 해볼 수 있습니다. 읽기만 해서 사흘 걸릴 이해가, 돌려보면 한 시간에 끝나기도 합니다.

그리고 환경을 세팅하는 과정 자체가 학습입니다. 빌드가 어떻게 도는지, 어떤 서비스에 의존하는지, 환경 변수가 무엇을 켜고 끄는지 — README가 알려주지 않는 것들을 손으로 배우게 됩니다.

1.2 첫날의 목표는 "녹색 화면"

첫날 목표를 명확히 하세요. 기능을 이해하는 게 아니라, 앱이 켜지고, 테스트가 통과하는 것입니다.

git clone <repository-url>
cd project
# README의 셋업 단계를 그대로 따라간다
make setup        # 또는 npm install / poetry install / ...
make dev          # 앱을 띄운다
make test         # 테스트가 도는지 본다

이 과정에서 막히는 게 정상입니다. 막히는 지점이야말로 첫 번째 발견입니다. 누락된 환경 변수, 깔리지 않는 의존성, 문서에 없는 사전 조건 — 이것들을 기록해 두세요. 9장에서 다루겠지만, 이건 나중에 온보딩 문서를 고칠 좋은 재료이고, 동시에 "이 팀의 셋업이 얼마나 손에 익었는가"를 보여주는 신호입니다.

1.3 막혔을 때의 순서

순서행동시간 상자
1README, CONTRIBUTING, docs/ 다시 읽기15분
2에러 메시지를 그대로 레포 내부에서 검색10분
3git log로 최근 셋업 관련 커밋 확인10분
4CI 설정 파일을 본다 (정답이 거기 있다)15분
5팀 채팅에서 같은 에러 검색10분
6사람에게 묻는다 (정리된 질문으로)

4번이 핵심입니다. CI 설정(.github/workflows/, .gitlab-ci.yml 등)은 "이 프로젝트를 깨끗한 환경에서 어떻게 세우는가"의 정답지입니다. 사람의 README는 낡지만, CI는 매번 실행되므로 거짓말을 하지 않습니다.

첫날에 "녹색 화면"을 보면, 그날부터 모든 학습이 실험이 됩니다. 못 보면, 모든 학습이 추측으로 남습니다.


2장 · 진입점 찾기

환경이 돌아가면, 다음 질문은 "이 시스템은 어디서 시작되는가"입니다. 코드베이스에는 항상 **진입점(entry point)**이 있습니다. 그곳을 찾으면 나머지가 거기서 뻗어 나갑니다.

2.1 진입점의 종류

시스템 유형진입점
웹 백엔드main, 서버 부트스트랩, 라우트 등록
웹 프런트엔드앱 루트 컴포넌트, 라우터 설정
CLI 도구main 함수, 인자 파서
라이브러리public API, index 파일의 export
배치/워커스케줄러 등록, 큐 컨슈머

대부분의 프레임워크는 진입점이 어디인지 정해져 있습니다. package.jsonscripts, Makefile의 타깃, Procfile, Dockerfile의 CMD — 이것들이 "이 프로젝트가 무엇으로 시작하는가"를 알려줍니다.

2.2 요청의 일생

웹 서비스라면 가장 먼저 그려야 할 그림이 **요청의 일생(request lifecycle)**입니다. HTTP 요청 하나가 들어와서 응답이 나가기까지 어떤 단계를 거치는가.

요청의 일생 (전형적인 웹 백엔드)

  HTTP 요청
  [라우터]  ── URL을 핸들러에 매핑
  [미들웨어]  ── 인증, 로깅, 파싱, rate limit
  [핸들러/컨트롤러]  ── 요청별 진입 함수
  [서비스/도메인 계층]  ── 비즈니스 로직
  [데이터 접근 계층]  ── DB, 캐시, 외부 API
  [응답 직렬화]  ── 결과를 JSON 등으로
  HTTP 응답

이 그림은 거의 모든 웹 백엔드에 적용됩니다. 프레임워크마다 이름과 경계가 다를 뿐입니다. 이 그림을 손에 들고 "각 계층은 이 레포에서 어느 디렉터리에 있지?"를 채워나가면, 그게 곧 코드베이스 지도의 뼈대가 됩니다.

2.3 빌드도 진입점이다

런타임 진입점만큼 중요한 게 빌드 진입점입니다. 소스에서 배포 가능한 산출물까지 어떻게 가는가. 빌드 설정 파일(vite.config, webpack.config, tsconfig, Makefile)을 한 번 훑으면, "이 코드가 실제로 어떤 형태로 실행되는가"가 보입니다. 트랜스파일되는가, 번들되는가, 어떤 환경을 타깃하는가.


3장 · 디테일보다 형태 먼저 읽기

진입점을 찾았다고 바로 함수 내부로 파고들면 안 됩니다. 먼저 **형태(shape)**를 봐야 합니다. 숲을 보고 나무로 가는 것입니다.

3.1 디렉터리 구조 = 팀의 멘탈 모델

디렉터리 구조는 단순한 폴더 정리가 아닙니다. 이 팀이 시스템을 어떻게 쪼개서 생각하는가의 화석입니다.

src/
  routes/        ← HTTP 경계 (진입점)
  services/      ← 비즈니스 로직
  models/        ← 데이터 모양
  lib/           ← 공용 유틸
  jobs/          ← 백그라운드 작업
  config/        ← 설정

이 구조 하나만 봐도 "이 팀은 계층형으로 생각하는구나"를 알 수 있습니다. 반대로 features/checkout/, features/search/처럼 기능별로 쪼개져 있으면 "기능 중심으로 생각하는구나"가 됩니다. 디렉터리 구조를 5분 보는 것이, 무작위 파일을 한 시간 읽는 것보다 낫습니다.

3.2 의존성 그래프 — 무엇이 무엇에 기대는가

다음으로 의존성을 봅니다. 두 가지 층위가 있습니다.

  • 외부 의존성: package.json, requirements.txt, go.mod — 어떤 프레임워크와 라이브러리 위에 서 있는가. 익숙하지 않은 게 있으면 표시해 둡니다.
  • 내부 의존성: 모듈끼리 어떻게 import 하는가. 어느 모듈이 "허브"인지 (많은 곳에서 import됨), 어느 모듈이 "잎"인지 (아무도 import 안 함).

허브 모듈은 시스템의 심장입니다. 거기서부터 읽으면 가장 많은 맥락을 한 번에 얻습니다.

3.3 데이터 모델이 진실을 말한다

마지막으로, 그리고 가장 중요하게 — 데이터 모델을 봅니다. 스키마 정의, ORM 모델, 마이그레이션 파일, 타입 정의.

코드는 거짓말을 할 수 있습니다. 주석은 낡습니다. 하지만 데이터 모델은 시스템이 실제로 무엇을 다루는지를 보여줍니다. User, Order, Subscription 같은 핵심 엔티티와 그들의 관계를 파악하면, 비즈니스의 절반을 이해한 것입니다. 코드가 하는 일은 결국 이 데이터를 만들고, 읽고, 바꾸고, 지우는 것이기 때문입니다.

형태를 먼저 읽으면, 나중에 디테일을 읽을 때 "이게 어디에 속하는지" 바로 압니다. 형태 없이 디테일부터 읽으면, 모든 파일이 둥둥 떠다닙니다.


4장 · 요청 하나를 끝까지 따라가기

형태를 봤다면, 이제 하나의 진짜 요청을 끝까지 따라갑니다. 이걸 수직 슬라이스(vertical slice) 기법이라고 부릅니다.

4.1 수직 슬라이스란

코드베이스 전체를 수평으로 (계층별로) 다 읽으려 하지 마세요. 대신 하나의 기능을 골라, 그것이 시스템의 모든 계층을 관통하는 길을 따라갑니다. 가장 위(요청 진입)에서 가장 아래(DB)까지, 그리고 다시 응답까지.

예: "사용자가 로그인하면 무슨 일이 일어나는가"

수직 슬라이스: 로그인 요청

  POST /login                    ← routes/auth.ts 에서 시작
  authMiddleware 통과            ← middleware/ — 이건 건너뛰는 경로구나
  loginHandler 호출              ← controllers/auth.ts
  AuthService.login()            ← services/auth.ts — 핵심 로직 여기
     ├─▶ UserRepo.findByEmail()  ← db/users.ts — 쿼리는 여기서
     ├─▶ password.verify()       ← lib/crypto.ts
     └─▶ Session.create()        ← services/session.ts
  Set-Cookie + 200 응답          ← 직렬화는 어디서?

이 슬라이스 하나를 따라가면, 라우팅·미들웨어·컨트롤러·서비스·리포지토리·유틸이 어떻게 연결되는지를 한 번에 체험하게 됩니다. 그리고 이 패턴은 다른 기능에도 거의 그대로 반복됩니다. 하나를 깊게 따라가면 열을 얕게 훑은 것보다 낫습니다.

4.2 슬라이스를 따라가는 도구

  • 디버거 중단점을 진입점에 찍고, 진짜 요청을 한 번 보냅니다. 스택을 한 단계씩 따라가면 호출 순서가 그대로 보입니다.
  • 로그 추가: 각 계층에 임시 로그를 박고 요청을 보냅니다. 어떤 순서로 찍히는지가 곧 흐름입니다. (끝나면 지웁니다.)
  • **"정의로 이동"**을 연쇄적으로: 진입 함수에서 호출하는 함수로, 또 그 함수가 호출하는 함수로 계속 점프합니다.

4.3 좋은 첫 슬라이스 고르기

아무 기능이나 고르지 말고, 단순하지만 대표성 있는 것을 고르세요. 로그인, 항목 하나 조회, 간단한 폼 제출 — 시스템의 주요 계층을 다 거치지만 엣지 케이스가 적은 것. "결제 정산"이나 "리포트 생성" 같은 복잡한 것은 두 번째, 세 번째로 미룹니다.


5장 · 테스트를 문서로 읽기

테스트는 "통과/실패"를 확인하는 도구이기만 한 게 아닙니다. 가장 정직한 문서입니다.

5.1 테스트가 문서인 이유

  • 주석은 낡지만, 테스트는 낡으면 빨개집니다. CI가 통과하는 한, 테스트는 현재의 동작을 정확히 반영합니다.
  • 테스트는 "이 함수를 어떻게 호출하는가"의 실사용 예제입니다. 입력 형태, 기대 출력, 에러 케이스가 다 거기 있습니다.
  • 테스트 이름은 종종 의도를 말합니다. it("쿠폰이 만료되면 할인을 적용하지 않는다") — 이 한 줄이 비즈니스 규칙입니다.

5.2 테스트를 읽는 순서

1. 테스트 파일 목록을 본다
   → "이 시스템에서 무엇이 테스트할 가치가 있는가"의 지도

2. 핵심 엔티티의 단위 테스트를 읽는다
   → User, Order 등이 어떻게 동작하도록 의도됐는가

3. 통합/E2E 테스트를 읽는다
   → 계층들이 어떻게 함께 동작하도록 의도됐는가
   → 4장의 "수직 슬라이스"가 코드로 적혀 있는 경우가 많다

4. 테스트 픽스처/팩토리를 본다
   → 유효한 데이터가 실제로 어떻게 생겼는지

3번이 특히 좋습니다. 잘 쓰인 통합 테스트는 "이 기능을 쓰는 올바른 방법"을 코드로 보여줍니다. 4장에서 직접 따라간 슬라이스가 통합 테스트로 이미 적혀 있다면, 그건 검증된 지도입니다.

5.3 테스트가 없다면

테스트가 빈약한 레포도 많습니다. 그럴 땐 두 가지를 합니다. 첫째, 있는 테스트는 더 귀하게 읽습니다 — 적을수록 그게 "팀이 가장 무서워하는 부분"일 확률이 높습니다. 둘째, 7장에서 다루겠지만, 내가 첫 테스트를 추가하는 것이 좋은 첫 기여가 됩니다.


6장 · 도구를 쓴다

낯선 코드베이스에서 맨눈으로 파일을 뒤지는 건 비효율적입니다. 도구가 있습니다. 잘 쓰면 탐색 속도가 한 자릿수 배수로 빨라집니다.

6.1 검색 — grep / ripgrep

문자열 검색은 가장 기본이면서 가장 강력합니다.

# 함수/심볼이 정의되고 사용되는 모든 곳
rg "createOrder"

# 라우트가 어디서 등록되는지
rg "router\.(get|post|put|delete)"

# 환경 변수가 어디서 읽히는지
rg "process\.env\.|os\.environ"

# TODO/FIXME — 팀이 알면서 미뤄둔 것들
rg "TODO|FIXME|HACK|XXX"

# 특정 에러 메시지의 출처
rg "User not found"

ripgrep.gitignore를 존중하고 빠릅니다. "이 문자열이 어디서 오지?"라는 질문의 90%는 rg 한 줄로 답이 나옵니다.

6.2 정의로 이동 / 참조 찾기 / 호출 계층

IDE나 LSP가 주는 기능들입니다.

기능답하는 질문
정의로 이동이건 어디서 정의됐나?
참조 찾기이건 누가 쓰나?
호출 계층이 함수까지 어떤 경로로 도달하나?
타입 정보이 변수의 모양은?

특히 **호출 계층(call hierarchy)**은 4장의 수직 슬라이스를 거꾸로 추적할 때 강력합니다. "이 DB 쿼리 함수는 결국 어떤 HTTP 엔드포인트들에서 호출되지?"를 한 번에 보여줍니다.

6.3 git을 고고학으로

git 히스토리는 "이 코드가 왜 이렇게 됐는가"의 기록입니다. 코드를 읽는 것은 현재를 보는 것이고, git을 읽는 것은 과거를 보는 것입니다.

# 이 줄을 마지막으로 바꾼 커밋과 그 메시지
git blame path/to/file.ts

# 이 파일의 변경 역사
git log --oneline -- path/to/file.ts

# 이 파일이 왜 이렇게 됐는지, 커밋 메시지와 함께
git log -p -- path/to/file.ts

# 특정 문자열이 추가/삭제된 커밋 찾기
git log -S "featureFlag" --oneline

# 이 함수의 변경 역사만
git log -L :functionName:path/to/file.ts

git blame으로 이상한 코드의 커밋을 찾고, 그 커밋 메시지나 연결된 PR/이슈를 읽으면 "왜 이렇게 짰는지"가 나옵니다. 이상하게 보이는 코드의 절반은 사라진 맥락 때문입니다 — 과거의 버그 수정, 외부 API의 제약, 급한 핫픽스. git은 그 맥락을 되살려 줍니다.

코드는 "무엇"을, git 히스토리는 "왜"를, 테스트는 "어떻게 써야 하는지"를 말합니다. 셋을 같이 읽으세요.


7장 · 작은 변경을 일찍 한다

읽기만 하면 적응이 끝나지 않습니다. 어느 시점에 직접 변경을 가해봐야 합니다. 그것도 가능한 한 빨리.

7.1 왜 작은 변경이 중요한가

작은 변경 하나를 끝까지 밀어보면, 개발 루프 전체가 도는지를 증명하게 됩니다. 코드 수정 → 로컬 확인 → 테스트 → 커밋 → PR → 리뷰 → CI → 머지. 이 루프가 막힘없이 도는 걸 한 번 경험하면, 그 다음 진짜 작업은 훨씬 두렵지 않습니다.

그리고 작은 변경은 빨리 피드백을 받습니다. 리뷰어가 "우리 팀은 이렇게 안 해요"라고 말해줄 기회를 일찍 만드는 것입니다. 큰 기능을 2주 만들고 나서 그 말을 듣는 것보다, 오타 수정에서 듣는 게 백 배 낫습니다.

7.2 좋은 첫 변경의 후보

후보왜 좋은가
오타/문서 수정위험 없이 루프 전체를 돈다
누락된 테스트 추가코드를 깊이 이해했다는 증거
에러 메시지 개선작지만 실사용에 도움
작은 리팩터 (이름 바꾸기)도구 사용 연습 + 안전
good first issue 라벨팀이 "초심자용"으로 골라둔 것

good first issue나 그에 준하는 라벨이 있으면 그것부터. 없다면, 1장에서 셋업하며 발견한 문제(낡은 README 한 줄 등)가 완벽한 첫 PR감입니다.

7.3 첫 PR에서 신경 쓸 것

  • 작게: 리뷰어가 5분 안에 다 읽을 수 있게.
  • 팀 컨벤션 따르기: 기존 커밋 메시지, 기존 PR 설명, 기존 코드 스타일을 먼저 보고 흉내 냅니다.
  • "왜"를 적기: PR 설명에 무엇을 왜 바꿨는지 한 문단.
  • CI를 직접 확인: 머지를 기다리지 말고, CI가 초록인지 내가 먼저 봅니다.

첫 주에 머지된 작은 PR 하나는, 읽기만 한 첫 주보다 당신을 훨씬 멀리 데려갑니다.


8장 · 멘탈 맵을 그리고 적어둔다

지금까지 모은 것 — 진입점, 형태, 슬라이스, 테스트, git에서 캔 맥락 — 이걸 머릿속에만 두면 샙니다. 밖으로 꺼내 적어야 합니다.

8.1 자기만의 노트

거창할 필요 없습니다. 마크다운 파일 하나, 또는 위키 페이지 하나면 됩니다. 적을 것:

내 적응 노트 — <서비스 이름>

## 한 줄 요약
이 서비스는 ___를 한다.

## 진입점
- 런타임: src/main.ts
- 빌드: vite.config.ts
- 주요 라우트: src/routes/

## 핵심 데이터 모델
- User ─< Order ─< OrderItem
- Subscription (User에 1:1)

## 따라가 본 슬라이스
- 로그인: routes/auth → services/auth → db/users
- (다음: 주문 생성)

## 아직 모르는 것 / 질문
- [ ] 캐시 무효화는 어디서 일어나나?
- [ ] `LEGACY_MODE` 플래그는 무슨 용도?
- [ ] 왜 결제는 별도 서비스로 분리됐나?

## 헷갈렸던 것 (나중의 나에게)
- `utils/`와 `lib/`의 차이: 합의된 규칙 없음, 그냥 역사적

8.2 다이어그램 하나

글보다 그림이 빠를 때가 있습니다. 거창한 UML이 아니라, 박스와 화살표 수준이면 충분합니다.

멘탈 맵 (낯선 코드베이스, 1주차)

   ┌──────────┐     ┌──────────┐     ┌──────────┐
   │  routes/ │────▶│ services/│────▶│   db/    │
   │ (진입점) │     │ (로직)   │     │ (영속화) │
   └──────────┘     └────┬─────┘     └──────────┘
                   ┌──────────┐     ┌──────────┐
                   │  jobs/   │     │ external │
                   │ (비동기) │────▶│   APIs   │
                   └──────────┘     └──────────┘

   ?  = 아직 안 들어가 본 곳: jobs/ 내부, external 재시도 로직

이 그림을 그리는 행위 자체가 학습입니다. 그리다 보면 "어, 여기 화살표를 못 그리겠네 = 여기를 아직 모르네"가 드러납니다. 모르는 곳에 물음표를 찍어두면, 그게 다음에 읽을 곳의 목록이 됩니다.

8.3 질문 목록을 살아 있게

"아직 모르는 것" 목록은 적응의 핵심 도구입니다. 떠오를 때마다 적고, 답을 찾으면 지웁니다. 이 목록이 줄어드는 속도가 곧 적응의 속도입니다. 그리고 이 목록은 9장 — 사람에게 물을 때 — 의 재료가 됩니다.

머릿속 지도는 안개처럼 흩어집니다. 적어둔 지도는 남고, 다음에 들어올 사람에게 물려줄 수도 있습니다.


9장 · 무엇을, 누구에게 물을 것인가

적응은 혼자 하는 게 아닙니다. 어느 시점엔 사람에게 물어야 합니다. 하지만 무엇을, 누구에게, 어떻게 묻느냐가 결과를 가릅니다.

9.1 묻기 전에: 채팅 히스토리를 먼저 읽는다

질문하기 전에 5분만 투자하세요. 팀 채팅(Slack 등), 이슈 트래커, PR 코멘트를 검색합니다. 당신의 질문은 새로운 게 아닐 확률이 높습니다. 누군가 이미 물었고, 누군가 이미 답했습니다.

  • 에러 메시지 → 채팅에서 그대로 검색
  • "왜 X인가" → 관련 PR/이슈에서 검색
  • 아키텍처 질문 → docs/, 위키, 디자인 문서 검색

이걸 먼저 하면, 사람에게 갈 때 "검색해봤는데 안 나와서"라고 말할 수 있습니다. 그 한마디가 신뢰를 만듭니다.

9.2 좋은 질문 vs 나쁜 질문

나쁜 질문좋은 질문
"이거 어떻게 동작해요?""로그인 슬라이스를 따라가 봤는데, 세션이 services/session.ts에서 생성되는 것까진 봤어요. 근데 만료는 어디서 처리되나요? rg로 못 찾았어요."
"환경 세팅이 안 돼요""make setup이 3단계에서 DB_URL 누락으로 실패해요. README엔 언급이 없는데, 로컬에선 보통 어떤 값을 쓰나요?"
"이 부분이 이해가 안 가요""LEGACY_MODE 플래그가 왜 있는지 궁금해요. git blame으로 2년 전 커밋까진 찾았는데 맥락이 안 보여서요."

좋은 질문의 공통점: 내가 어디까지 해봤는지를 보여준다. 이건 상대의 시간을 아끼는 동시에, 답을 더 정확하게 만듭니다. 상대가 "아, 거기까지 봤으면 이것만 알면 돼"라고 정밀하게 답할 수 있기 때문입니다.

9.3 누구에게 물을 것인가

  • 셋업/환경 문제 → 최근에 합류한 사람. 같은 고생을 방금 했으므로 기억이 생생합니다.
  • "왜 이렇게 됐나"git blame이 가리키는 사람, 또는 그 영역의 오래된 기여자.
  • 아키텍처/큰 그림 → 팀 리드 또는 시니어. 단, 정리된 질문을 모아서 한 번에.
  • 막힌 작업 → 멘토나 버디가 지정돼 있으면 그 사람. 없으면 코드 리뷰어.

같은 사람에게 같은 종류의 질문을 반복하지 마세요. 질문을 모아서, 묶어서, 정리된 형태로 가져가는 것이 상대의 시간을 존중하는 방법입니다.

9.4 받은 답을 흘려보내지 않기

누군가 답을 주면, 그걸 8장의 노트에 적습니다. 같은 걸 두 번 묻지 않기 위해서이고, 다음에 들어올 사람에게 물려주기 위해서입니다. 더 좋게는, 그 답을 README나 위키에 PR로 넣으세요 — 7장의 "좋은 첫 변경"이 또 하나 생긴 셈입니다.

질문은 약점이 아닙니다. 준비 없는 질문이 약점입니다. 5분 검색하고, 어디까지 해봤는지 보여주고, 받은 답을 기록하면 — 질문은 가장 빠른 학습 도구입니다.


10장 · AI 에이전트와 함께 적응하기

2026년의 적응은 한 가지 도구가 더 생겼습니다. AI 코딩 에이전트입니다. 잘 쓰면 강력하고, 잘못 믿으면 위험합니다.

10.1 에이전트가 잘하는 것

낯선 코드베이스에서 AI 에이전트는 빠른 가이드 투어를 줍니다.

요청에이전트가 잘하는 이유
"이 모듈이 무슨 일을 하는지 설명해줘"수백 줄을 빠르게 읽고 요약
"이 함수가 어디서 호출되는지 추적해줘"grep + 정의 추적을 대신
"로그인 요청의 흐름을 끝까지 따라가줘"4장의 수직 슬라이스를 빠르게
"이 디렉터리 구조의 의도가 뭐야?"패턴 인식
"이 낯선 라이브러리 사용법을 이 레포 안 예제로 보여줘"컨텍스트 안에서 예제 추출

특히 "설명해줘"와 "추적해줘"는 에이전트의 강점입니다. 사람이 한 시간 할 탐색을 몇 분으로 줄여줍니다.

10.2 그러나 — 투어를 맹신하지 않는다

문제는, 에이전트의 설명이 그럴듯하지만 틀릴 수 있다는 것입니다. 에이전트는 본 적 없는 부분을 가장 그럴듯한 디폴트로 채웁니다 — 즉 환각합니다. 그리고 환각은 진짜 설명과 똑같이 자신 있게 들립니다.

그래서 원칙은 하나입니다: 에이전트의 투어는 가설로 받고, 코드로 검증한다.

에이전트와 함께 적응하는 루프

  1. 에이전트에게 묻는다
     "결제 흐름을 설명해줘"
  2. 답을 가설로 받는다 (사실로 받지 않는다)
  3. 코드로 검증한다
     - 에이전트가 말한 파일을 직접 연다
     - 에이전트가 말한 함수를 rg로 찾는다
     - 의심되면 디버거/로그로 확인
  4. 맞으면 노트에 적는다 / 틀리면 다시 묻는다

10.3 검증 가능한 질문을 한다

에이전트에게 물을 때, 검증하기 쉬운 형태로 묻는 것이 좋습니다.

  • 나쁨: "이 시스템 안전해?" (검증 불가능, 환각 위험 큼)
  • 좋음: "createOrder를 호출하는 모든 곳을 파일 경로와 함께 나열해줘" (rg로 바로 대조 가능)
  • 좋음: "이 파일에서 외부 네트워크 호출을 하는 줄을 짚어줘" (직접 그 줄을 열어 확인)

구체적이고, 위치를 포함하고, 대조 가능한 질문 — 이것이 에이전트를 안전하게 쓰는 법입니다. 9장에서 사람에게 좋은 질문을 하는 원칙과 똑같습니다.

10.4 에이전트는 사람을 대체하지 않는다

에이전트는 코드에 대한 질문에 강합니다. 하지만 "왜 이런 결정을 했는가" 같은, 코드에 적히지 않은 맥락은 모릅니다. 사라진 회의, 조직의 제약, 미래의 계획 — 그건 여전히 사람에게 물어야 합니다. 에이전트는 9장을 대체하는 게 아니라, 9장에 갈 질문을 더 날카롭게 다듬어 줍니다. 코드로 답할 수 있는 건 에이전트에게, 사람만 답할 수 있는 건 사람에게.

에이전트는 훌륭한 투어 가이드입니다. 하지만 투어 가이드의 말을 받아 적기만 하는 관광객은 길을 외우지 못합니다. 묻고, 검증하고, 직접 걸어보세요.


에필로그 — 적응은 반복되는 기술이다

낯선 코드베이스 앞에 서는 일은 커리어에서 한 번 일어나는 사건이 아닙니다. 이직할 때, 팀을 옮길 때, 서비스를 인수받을 때, 오픈소스에 기여할 때 — 계속 반복됩니다.

그래서 적응은 한 번 익혀두면 평생 쓰는 기술입니다. 그리고 그 기술의 핵심은 단순합니다. 처음부터 다 읽으려 하지 말 것. 일단 돌리고, 진입점을 찾고, 형태를 보고, 하나의 길을 끝까지 따라가고, 작은 변경으로 루프를 증명하고, 지도를 적어두고, 좋은 질문을 하고, AI는 가이드로 쓰되 검증할 것. 빠르게 적응하는 사람은 더 똑똑한 게 아니라, 이 순서를 아는 사람입니다.

빠른 적응 체크리스트

  1. 돌렸다 — 로컬에서 앱이 켜지고 테스트가 통과한다
  2. 진입점을 안다 — 런타임·빌드 진입점이 어디인지 안다
  3. 형태를 봤다 — 디렉터리 구조, 의존성, 데이터 모델을 훑었다
  4. 슬라이스를 따라갔다 — 진짜 요청 하나를 끝에서 끝까지 추적했다
  5. 테스트를 읽었다 — 핵심 동작을 테스트로 확인했다
  6. 도구를 쓴다rg, 정의로 이동, 호출 계층, git blame이 손에 익었다
  7. 작은 변경을 했다 — 첫 PR이 머지됐다(또는 리뷰 중이다)
  8. 지도를 적었다 — 자기만의 노트와 다이어그램, 질문 목록이 있다
  9. 잘 물었다 — 검색 먼저, 어디까지 해봤는지 보여주고, 답을 기록했다
  10. AI를 검증하며 쓴다 — 에이전트의 투어를 가설로 받고 코드로 확인한다

피해야 할 안티패턴

  • 읽기만 하기 — 돌려보지 않고 코드만 읽으면 학습이 추측에 머문다
  • 처음부터 다 읽기 — 형태 없이 파일을 무작위로 읽으면 둥둥 떠다닌다
  • 수평으로 읽기 — 계층을 따로따로 읽지 말고, 하나의 슬라이스를 수직으로
  • 혼자 오래 막히기 — 시간 상자를 정하고, 넘으면 정리된 질문으로 묻는다
  • 준비 없이 묻기 — 검색도 안 하고, 어디까지 해봤는지도 없이 묻지 않는다
  • 변경을 미루기 — "충분히 이해하면 그때" 하지 말고, 작은 변경을 일찍
  • AI 투어 맹신 — 에이전트의 그럴듯한 설명을 사실로 받지 않는다
  • 지도를 머릿속에만 — 적지 않으면 흩어지고, 다음 사람에게 못 물려준다

다음 글 예고

다음 글은 **"레거시 코드와 함께 살기 — 무서운 코드를 안전하게 바꾸는 법"**입니다. 이번 글이 "낯선 코드를 어떻게 이해하는가"였다면, 다음 글은 "이해한 그 코드가 무섭고 테스트도 없을 때, 어떻게 손대는가"입니다. 특성화 테스트, 이음새(seam) 찾기, 작은 단위의 안전한 리팩터, 그리고 레거시를 대하는 마음가짐을 다룹니다.

모든 전문가도 한때는 그 코드베이스의 새로 들어온 사람이었습니다. 차이는 적응에 걸린 시간이고, 그 시간은 방법으로 줄어듭니다. 이 글의 순서를 한 번 몸에 익히면, 다음 낯선 레포는 덜 낯설어집니다.

Ramping Up on an Unfamiliar Codebase Fast: The Newcomer's Craft

"The fastest way to understand a codebase is not to read it from the top. It is to make it run, then follow one path all the way through."

Prologue — We become the newcomer over and over

Say the word "ramp-up" and most people picture a junior developer's first day. But reality is different. Across a whole career, we become the newcomer repeatedly.

You change jobs and stand before a new company's codebase. You transfer teams and inherit a service you have never seen, in the very same company. Someone leaves and you inherit their five-year-old payment system. You clone someone else's repo to contribute to open source. You drop into another team for a week to fix one of their bugs.

All of these situations share one thing. You are standing in front of code that already exists, that you did not write, for which you have no map in your head.

Here is the key fact: ramping up is not a talent. It is a learnable skill. Some people land their first PR on an unfamiliar repo in three days; others are still "getting up to speed" after three weeks. The difference is not raw intelligence — it is whether you know the method.

This post is about that method. Not "how should the team design onboarding" — that is the manager's question — but "how do I, the individual, get productive fast". The two are different. Designing onboarding is the team's job; ramping up is, in the end, something you do. Even with no good onboarding doc — and usually there is none — the skill of ramping up fast is in your own hands.

What this post covers:

ChapterTopic
1Get it running first — the highest-leverage move
2Find the entry points — where does a request begin
3Read the shape before the details
4Follow one real request end-to-end (the vertical slice)
5Read tests as documentation
6Use your tools — grep, go-to-definition, git as archaeology
7Make a small change early
8Build a mental map and write it down
9What to ask, and whom
10Ramping up alongside AI agents
EpilogueChecklist + anti-patterns + next-post teaser

Chapter 1 · Get it running first

When you receive an unfamiliar repo, the first instinct is usually "let's read the code." Wrong instinct. The highest-leverage first move is to make it run locally.

1.1 A working environment beats a week of reading

Reading code alone gives you only a static picture in your head. But a running environment puts a living system in your hands. You can change a value, add a log line, attach a debugger, and actually try "what happens if I delete this?" Understanding that would take three days by reading can take one hour once it runs.

And the act of setting up the environment is itself learning. How the build runs, what services it depends on, what each environment variable turns on and off — you learn by hand the things the README never tells you.

1.2 Day one's goal is a "green screen"

Make day one's goal explicit. It is not to understand a feature; it is for the app to start and the tests to pass.

git clone <repository-url>
cd project
# follow the README setup steps verbatim
make setup        # or npm install / poetry install / ...
make dev          # bring the app up
make test         # see whether the tests run

Getting stuck along the way is normal. The point where you get stuck is your first discovery. A missing environment variable, a dependency that will not install, an undocumented prerequisite — write these down. As Chapter 9 discusses, this is good material for fixing the onboarding doc later, and at the same time a signal of "how worn-in is this team's setup."

1.3 The order of moves when stuck

OrderActionTime box
1Re-read README, CONTRIBUTING, docs/15 min
2Search the exact error message inside the repo10 min
3Check recent setup-related commits with git log10 min
4Read the CI config file (the answer key lives there)15 min
5Search the team chat for the same error10 min
6Ask a person (with a well-formed question)

Step 4 is the key. The CI config (.github/workflows/, .gitlab-ci.yml, and the like) is the answer key to "how do you build this project in a clean environment." A human's README goes stale, but CI runs every time, so it does not lie.

See a "green screen" on day one and from that day every act of learning becomes an experiment. Miss it, and every act of learning stays a guess.


Chapter 2 · Find the entry points

Once the environment runs, the next question is "where does this system begin." A codebase always has an entry point. Find it, and the rest branches out from there.

2.1 Kinds of entry points

System typeEntry point
Web backendmain, server bootstrap, route registration
Web frontendApp root component, router config
CLI toolmain function, argument parser
LibraryThe public API, the exports in the index file
Batch / workerScheduler registration, queue consumer

Most frameworks fix where the entry point lives. The scripts in package.json, the targets in a Makefile, the Procfile, the CMD in a Dockerfile — these tell you "what this project starts with."

2.2 The request lifecycle

For a web service, the first picture to draw is the request lifecycle: from one HTTP request coming in to a response going out, what stages does it pass through.

The request lifecycle (typical web backend)

  HTTP request
  [Router]  ── maps URL to handler
  [Middleware]  ── auth, logging, parsing, rate limit
  [Handler / controller]  ── per-request entry function
  [Service / domain layer]  ── business logic
  [Data access layer]  ── DB, cache, external APIs
  [Response serialization]  ── result into JSON, etc.
  HTTP response

This picture applies to nearly every web backend. Only the names and boundaries differ per framework. Hold this picture and fill in "which directory is each layer in, in this repo?" — that becomes the skeleton of your codebase map.

2.3 The build is an entry point too

As important as the runtime entry point is the build entry point: how source becomes a deployable artifact. Skim the build config files (vite.config, webpack.config, tsconfig, Makefile) once and you will see "what form this code actually runs in." Is it transpiled, is it bundled, what environment does it target.


Chapter 3 · Read the shape before the details

Finding the entry point does not mean you dive straight into a function body. First you look at the shape. See the forest, then walk to the trees.

3.1 Directory structure equals the team's mental model

Directory structure is not mere folder housekeeping. It is a fossil of how this team carves up the system in their thinking.

src/
  routes/        ← HTTP boundary (entry point)
  services/      ← business logic
  models/        ← data shapes
  lib/           ← shared utilities
  jobs/          ← background work
  config/        ← configuration

This structure alone tells you "this team thinks in layers." Conversely, if it is split by feature like features/checkout/, features/search/, then it is "they think feature-first." Five minutes looking at the directory structure beats an hour reading random files.

3.2 The dependency graph — what leans on what

Next you look at dependencies. There are two layers.

  • External dependencies: package.json, requirements.txt, go.mod — what frameworks and libraries does it stand on. Flag anything you are not familiar with.
  • Internal dependencies: how do modules import each other. Which module is the "hub" (imported by many places), which is a "leaf" (imported by no one).

The hub module is the heart of the system. Reading from there gets you the most context at once.

3.3 The data model tells the truth

Finally, and most importantly — look at the data model. Schema definitions, ORM models, migration files, type definitions.

Code can lie. Comments go stale. But the data model shows what the system actually deals with. Grasp the core entities like User, Order, Subscription and their relationships, and you have understood half the business. Because what the code does, in the end, is create, read, change, and delete this data.

Read the shape first and, when you later read a detail, you immediately know "where this belongs." Read details with no shape, and every file floats free.


Chapter 4 · Follow one real request end-to-end

Once you have seen the shape, now you follow one real request all the way through. This is called the vertical slice technique.

4.1 What a vertical slice is

Do not try to read the whole codebase horizontally (layer by layer). Instead, pick one feature and follow the path it cuts through every layer of the system. From the very top (request entry) to the very bottom (the DB), and back out to the response.

Example: "What happens when a user logs in?"

Vertical slice: a login request

  POST /login                    ← starts in routes/auth.ts
  passes authMiddleware          ← middleware/ — ah, this path skips it
  calls loginHandler             ← controllers/auth.ts
  AuthService.login()            ← services/auth.ts — core logic here
     ├─▶ UserRepo.findByEmail()  ← db/users.ts — the query lives here
     ├─▶ password.verify()       ← lib/crypto.ts
     └─▶ Session.create()        ← services/session.ts
  Set-Cookie + 200 response      ← where does serialization happen?

Follow this one slice and you experience, in a single pass, how routing, middleware, controller, service, repository, and utilities connect. And that pattern repeats almost verbatim for other features. Following one deeply beats skimming ten shallowly.

4.2 Tools for following a slice

  • Put a debugger breakpoint at the entry point and send one real request. Step through the stack one frame at a time and the call order shows itself.
  • Add logs: drop a temporary log into each layer and send a request. The order they print in is the flow. (Remove them when done.)
  • Chain "go to definition": from the entry function to the function it calls, to the function that one calls, keep jumping.

4.3 Choosing a good first slice

Do not pick just any feature; pick one that is simple but representative. Login, fetching a single item, a simple form submission — something that passes through the system's main layers but has few edge cases. Save the complex ones like "payment settlement" or "report generation" for second and third.


Chapter 5 · Read tests as documentation

Tests are not only a tool for checking pass or fail. They are the most honest documentation.

5.1 Why tests are documentation

  • Comments go stale, but tests go red when they go stale. As long as CI passes, the tests reflect current behavior exactly.
  • Tests are real-world usage examples of "how do you call this function." The input shape, the expected output, the error cases are all right there.
  • Test names often state intent. it("does not apply the discount when the coupon is expired") — that one line is a business rule.

5.2 The order in which to read tests

1. Look at the list of test files
   → a map of "what is worth testing in this system"

2. Read the unit tests for core entities
   → how User, Order, and the like are intended to behave

3. Read the integration / E2E tests
   → how the layers are intended to work together
   → often the Chapter 4 "vertical slice" written out as code

4. Look at test fixtures / factories
   → what valid data actually looks like

Step 3 is especially good. A well-written integration test shows "the correct way to use this feature" as code. If the slice you traced by hand in Chapter 4 is already written as an integration test, that is a verified map.

5.3 If there are no tests

Plenty of repos are thin on tests. When that is the case, do two things. First, read what tests exist even more carefully — the fewer there are, the more likely each one marks "the part the team fears most." Second, as Chapter 7 discusses, adding the first test yourself makes a good first contribution.


Chapter 6 · Use your tools

Digging through files by naked eye in an unfamiliar codebase is inefficient. There are tools. Used well, they speed up exploration by an order of magnitude.

6.1 Search — grep / ripgrep

String search is the most basic and the most powerful.

# every place a function/symbol is defined and used
rg "createOrder"

# where routes get registered
rg "router\.(get|post|put|delete)"

# where environment variables are read
rg "process\.env\.|os\.environ"

# TODO/FIXME — things the team knowingly deferred
rg "TODO|FIXME|HACK|XXX"

# the source of a specific error message
rg "User not found"

ripgrep respects .gitignore and is fast. Ninety percent of "where does this string come from?" questions get answered by a single line of rg.

6.2 Go to definition / find references / call hierarchy

These are features your IDE or LSP gives you.

FeatureQuestion it answers
Go to definitionWhere is this defined?
Find referencesWho uses this?
Call hierarchyThrough what path does code reach this function?
Type infoWhat is this variable's shape?

The call hierarchy in particular is powerful for tracing the Chapter 4 vertical slice backwards. It shows at once "which HTTP endpoints does this DB query function ultimately get called from."

6.3 git as archaeology

git history is the record of "why this code came to be this way." Reading code is seeing the present; reading git is seeing the past.

# the commit that last changed this line, and its message
git blame path/to/file.ts

# the change history of this file
git log --oneline -- path/to/file.ts

# why this file came to be this way, with commit messages
git log -p -- path/to/file.ts

# find the commit where a specific string was added/removed
git log -S "featureFlag" --oneline

# the change history of one function only
git log -L :functionName:path/to/file.ts

Use git blame to find the commit behind some strange code, then read its commit message or the linked PR/issue, and "why it was written this way" appears. Half of code that looks strange is due to lost context — a past bug fix, an external API's constraint, an urgent hotfix. git brings that context back.

Code tells you "what," git history tells you "why," tests tell you "how to use it." Read all three together.


Chapter 7 · Make a small change early

Reading alone does not finish your ramp-up. At some point you have to make a change yourself. And as early as possible.

7.1 Why a small change matters

Push one small change all the way through and you prove the entire development loop runs. Edit code, check locally, test, commit, PR, review, CI, merge. Experience that loop running without a snag once, and the real work that follows is far less scary.

And a small change gets feedback fast. You create the chance for a reviewer to say "our team doesn't do it that way" early. Hearing that on a typo fix is a hundred times better than hearing it after building a big feature for two weeks.

7.2 Candidates for a good first change

CandidateWhy it is good
Typo / doc fixRuns the whole loop with no risk
Add a missing testEvidence you understood the code deeply
Improve an error messageSmall but helps real usage
A small refactor (renaming)Tool-use practice + safe
A good first issue labelSomething the team picked as "for beginners"

If there is a good first issue label or its equivalent, start there. If not, a problem you found while setting up in Chapter 1 (a stale README line, say) is a perfect first PR.

7.3 What to care about in your first PR

  • Small: a reviewer can read the whole thing in five minutes.
  • Follow team conventions: look at existing commit messages, existing PR descriptions, existing code style first, then imitate them.
  • State the "why": one paragraph in the PR description on what you changed and why.
  • Check CI yourself: do not wait for the merge — be the first to see whether CI is green.

One small PR merged in your first week takes you far further than a first week of only reading.


Chapter 8 · Build a mental map and write it down

Everything you have gathered so far — entry points, the shape, slices, tests, context dug out of git — leaks away if you keep it only in your head. You have to get it out and write it down.

8.1 Your own notes

It need not be grand. One Markdown file, or one wiki page, is enough. What to write:

My ramp-up notes — <service name>

## One-line summary
This service does ___.

## Entry points
- Runtime: src/main.ts
- Build: vite.config.ts
- Main routes: src/routes/

## Core data model
- User ─< Order ─< OrderItem
- Subscription (1:1 with User)

## Slices I have traced
- Login: routes/auth -> services/auth -> db/users
- (next: order creation)

## Still unknown / questions
- [ ] Where does cache invalidation happen?
- [ ] What is the `LEGACY_MODE` flag for?
- [ ] Why is payment split into a separate service?

## Things that confused me (for future me)
- The difference between `utils/` and `lib/`: no agreed rule, just historical

8.2 One diagram

Sometimes a picture is faster than prose. Not grand UML, but boxes and arrows is enough.

Mental map (unfamiliar codebase, week 1)

   ┌──────────┐     ┌──────────┐     ┌──────────┐
   │  routes/ │────▶│ services/│────▶│   db/    │
   │ (entry)  │     │ (logic)  │     │(persist) │
   └──────────┘     └────┬─────┘     └──────────┘
                   ┌──────────┐     ┌──────────┐
                   │  jobs/   │     │ external │
                   │ (async)  │────▶│   APIs   │
                   └──────────┘     └──────────┘

   ?  = places not yet entered: inside jobs/, external retry logic

The act of drawing this picture is itself learning. As you draw, "wait, I cannot draw an arrow here = I do not know this yet" surfaces. Mark the unknown spots with a question mark, and that becomes your list of where to read next.

8.3 Keep the question list alive

The "still unknown" list is a core ramp-up tool. Write things down as they occur to you; erase them when you find the answer. The rate at which this list shrinks is the rate of your ramp-up. And this list is the material for Chapter 9 — when you ask a person.

A map in your head scatters like fog. A written map stays, and you can even hand it to the next person who comes in.


Chapter 9 · What to ask, and whom

Ramping up is not done alone. At some point you have to ask a person. But what, whom, and how you ask decides the outcome.

9.1 Before asking: read the chat history first

Before you ask, invest just five minutes. Search the team chat (Slack and the like), the issue tracker, the PR comments. Your question is probably not new. Someone has already asked, someone has already answered.

  • Error message → search the chat for it verbatim
  • "Why X" → search the related PR/issue
  • An architecture question → search docs/, the wiki, design documents

Do this first, and when you go to a person you can say "I searched but couldn't find it." That one line builds trust.

9.2 A good question vs a bad question

Bad questionGood question
"How does this work?""I traced the login slice; I saw the session gets created in services/session.ts. But where is expiry handled? I couldn't find it with rg."
"My environment setup isn't working""make setup fails at step 3 with DB_URL missing. The README doesn't mention it — what value do people usually use locally?"
"I don't get this part""I'm curious why the LEGACY_MODE flag exists. I traced git blame back to a commit two years ago but couldn't see the context."

What good questions share: they show how far you got yourself. This saves the other person's time and at the same time makes the answer more precise — because they can answer surgically, "ah, if you got that far you only need to know this."

9.3 Whom to ask

  • Setup / environment problems → someone who joined recently. They went through the same pain just now, so it is fresh.
  • "Why did this come to be" → the person git blame points to, or a long-time contributor to that area.
  • Architecture / the big picture → the team lead or a senior. But gather your questions and bring them in one batch.
  • Stuck work → your mentor or buddy, if one is assigned. If not, your code reviewer.

Do not repeatedly ask the same kind of question to the same person. Gathering questions, bundling them, and bringing them in an organized form is how you respect the other person's time.

9.4 Do not let answers you receive drift away

When someone gives you an answer, write it into your Chapter 8 notes. So you do not ask the same thing twice, and so you can hand it to the next person who comes in. Better yet, put that answer into the README or wiki as a PR — that is one more "good first change" from Chapter 7.

A question is not a weakness. An unprepared question is the weakness. Search for five minutes, show how far you got, record the answer you receive — and a question becomes the fastest learning tool.


Chapter 10 · Ramping up alongside AI agents

Ramping up in 2026 has gained one more tool: the AI coding agent. Used well it is powerful; trusted wrongly it is dangerous.

10.1 What agents are good at

On an unfamiliar codebase, an AI agent gives you a fast guided tour.

RequestWhy the agent is good at it
"Explain what this module does"Reads hundreds of lines fast and summarizes
"Trace where this function is called"Does the grep + definition-tracing for you
"Follow the login request flow end-to-end"The Chapter 4 vertical slice, fast
"What is the intent of this directory structure?"Pattern recognition
"Show me how to use this unfamiliar library, using an example inside this repo"Extracts an example from within context

"Explain" and "trace" in particular are the agent's strengths. They cut exploration that would take a human an hour down to a few minutes.

10.2 But — do not blindly trust the tour

The problem is that the agent's explanation can sound plausible and still be wrong. The agent fills in parts it has not seen with the most plausible default — that is, it hallucinates. And a hallucination sounds just as confident as a real explanation.

So the principle is one: take the agent's tour as a hypothesis, and verify with the code.

The loop of ramping up alongside an agent

  1. Ask the agent
     "Explain the payment flow"
  2. Take the answer as a hypothesis (not as fact)
  3. Verify with the code
     - open the files the agent named, yourself
     - find the functions the agent named with rg
     - if in doubt, confirm with the debugger / logs
  4. If right, write it in your notes / if wrong, ask again

10.3 Ask verifiable questions

When you ask the agent, it is better to ask in a form that is easy to verify.

  • Bad: "Is this system safe?" (unverifiable, high hallucination risk)
  • Good: "List every place createOrder is called, with file paths" (you can cross-check with rg directly)
  • Good: "Point out the lines in this file that make external network calls" (open those lines yourself and confirm)

A specific, location-bearing, cross-checkable question — this is how to use the agent safely. It is exactly the same principle as asking a person a good question in Chapter 9.

10.4 The agent does not replace people

The agent is strong at questions about code. But context not written in the code — like "why was this decision made" — it does not know. Vanished meetings, organizational constraints, future plans — that, you still have to ask a person. The agent does not replace Chapter 9; it sharpens the questions you bring to Chapter 9. What can be answered with code, to the agent; what only people can answer, to the people.

The agent is an excellent tour guide. But a tourist who only copies down what the tour guide says never memorizes the route. Ask, verify, and walk it yourself.


Epilogue — Ramping up is a recurring skill

Standing before an unfamiliar codebase is not a one-time event in a career. When you change jobs, when you transfer teams, when you inherit a service, when you contribute to open source — it keeps recurring.

So ramping up is a skill you learn once and use for life. And the core of that skill is simple. Do not try to read it all from the top. Get it running, find the entry points, see the shape, follow one path all the way through, prove the loop with a small change, write down the map, ask good questions, use AI as a guide but verify it. People who ramp up fast are not smarter — they are people who know this order.

The fast ramp-up checklist

  1. It runs — the app starts locally and the tests pass
  2. You know the entry points — you know where the runtime and build entry points are
  3. You have seen the shape — you skimmed the directory structure, dependencies, data model
  4. You followed a slice — you traced one real request end to end
  5. You read the tests — you confirmed core behavior through tests
  6. You use your toolsrg, go-to-definition, call hierarchy, git blame are worn-in
  7. You made a small change — your first PR is merged (or in review)
  8. You wrote down the map — you have your own notes, a diagram, a question list
  9. You asked well — search first, show how far you got, record the answer
  10. You use AI with verification — you take the agent's tour as a hypothesis and confirm with code

Anti-patterns to avoid

  • Only reading — read code without running it and learning stays a guess
  • Reading it all from the top — read files at random with no shape and they float free
  • Reading horizontally — do not read layers separately; read one slice vertically
  • Staying stuck alone for too long — set a time box, and when you cross it, ask with an organized question
  • Asking unprepared — do not ask without having searched and without showing how far you got
  • Deferring the change — do not wait for "once I understand enough"; make a small change early
  • Blindly trusting the AI tour — do not take the agent's plausible explanation as fact
  • A map only in your head — if you do not write it down it scatters, and you cannot hand it to the next person

Next-post teaser

The next post is "Living with legacy code — how to change scary code safely." If this post was "how do you understand unfamiliar code," the next is "when that code you now understand is scary and has no tests, how do you touch it." It covers characterization tests, finding seams, small safe refactors, and the mindset for facing legacy.

Every expert was once the newcomer on that codebase too. The difference is the time it took to ramp up, and that time shrinks with method. Learn the order in this post once and into your body, and the next unfamiliar repo becomes less unfamiliar.