커리어 · 커뮤니티
Step 334. 포트폴리오 통합 — 실력의 증명 패키지: 흩어진 증거를 하나의 이야기로
트랙 안내: 「커리어·정점」 트랙(Step 334~340)의 시작입니다 — 포트폴리오 통합과 방향 확정에서 국내 우승 도전과 대회 회고록까지, 실력의 증거를 커리어 자산으로 묶습니다.
Level 4 — 전문가 | 난이도 ★★★☆☆ | 예상 소요 시간 2일 (자산 인벤토리 반나절 + 대표작 선정·통합 페이지 1일 + 이력서 반나절)
전제: Step 294의 블로그 포트폴리오 v1, Step 321~323의 CVE 보고서·오픈소스 공개, Step 332~333의 발표 자료, 그리고 지금까지의 대회 기록 전부. 이 챕터는 새로 만드는 것이 아니라 모으는 프로젝트 챕터입니다.
- 준비물: Write-up이 있는 블로그, GitHub 계정, 대회 기록(CTFtime 포함), 자격증 목록, 파이썬 실행 환경. 인벤토리 스크립트는 여러분의 폴더에서 직접 돌리는 실측 도구이고, 통합 페이지의 완성 장면은 화면 예시입니다.
- 주의: 포트폴리오는 공개 문서입니다 — 실명·소속·개인 연락처는 가공하고, 모든 수치는 제3자가 확인 가능한 것만 씁니다(Step 294의 규칙).
- ⚠️ 이 챕터의 실습은 내 랩·합법 플랫폼 전용입니다. 허가 없는 시스템에 적용하면 범죄입니다. 포트폴리오에 싣는 사례는 퇴출된 머신, 종료된 대회, 공개 허가된 분석만 사용합니다.
지금까지의 348개 스텝은 증거로 가득합니다. 300개가 넘는 완료 과제, 수십 편의 Write-up, CVE 분석 보고서, 오픈소스 저장소, 대회 기록, 자격증, 발표 자료까지. 그런데 이 증거들은 블로그와 GitHub와 CTFtime과 하드디스크에 흩어져 있습니다. 채용 담당자도, 협업을 제안하려는 연구자도, 이것을 대신 모아 주지 않습니다.
오늘의 작업은 그 흩어진 증거를 한 페이지의 이야기로 만드는 것입니다. "나는 이런 문제를 풀어 온 사람이다"라는 문장 하나에 링크와 숫자를 붙이는 일 — 개인 브랜딩이라고 부르지만, 실체는 정리와 검증 가능성입니다. Step 294가 블로그라는 한 축의 정돈이었다면, 오늘은 전 채널의 통합입니다.
1. 학습 목표
이 챕터를 끝내면 다음을 할 수 있습니다:
- 흩어진 자산(글·보고서·저장소·대회·자격·발표)을 스크립트로 인벤토리화한다
- 양이 아니라 깊이 기준으로 대표작 5~7개를 선정하고 4줄 요약을 붙인다
- 통합 포트폴리오 페이지(About 또는 GitHub 프로필 README)를 완성한다
- GitHub 프로필을 정돈한다 — pinned 저장소와 지속적인 커밋 기록
- 같은 내용을 1장 A4 이력서로 변환한다 — "아는 것"이 아니라 "한 것" 중심으로
2. 배경 지식 — 오늘의 도구와 개념
오늘의 도구 한눈에 보기
| 구분 | 내용 |
|---|---|
| 언어·환경 | 파이썬 3(인벤토리 스크립트), 마크다운(통합 페이지·이력서), GitHub 프로필 설정 |
| 오늘의 명령 | python step334_inventory.py <자산 폴더> — 자산 집계 + 통합 페이지 초안 생성 |
| 필요한 개념 | 인벤토리화, 대표작 선정 기준, 4줄 요약(문제/접근/결과/배운 것), 검증 가능성 |
| 오늘의 산출물 | 통합 포트폴리오 페이지(대표작 요약 포함) + 1장 이력서 + 정돈된 GitHub 프로필 |
2-1. 인벤토리 — 모으기 전에 세어본다
포트폴리오 작업의 첫 순서는 글쓰기가 아니라 재고 조사입니다. 사람은 자기가 쌓은 것의 양을 과소평가합니다 — 매일의 과제로 겪은 것들은 "당연한 것"으로 느껴지기 때문입니다. 그래서 기억이 아니라 파일 시스템으로 셉니다.
인벤토리의 대상은 일곱 종류입니다 — Write-up, CVE·취약점 분석 보고서, 오픈소스 저장소, 대회 기록(순위 포함), 자격증, 발표 자료, 멘토링 경력. 이 중 다섯 종류는 파일과 공개 기록으로 기계가 셀 수 있고, 오늘의 스크립트가 그 부분을 담당합니다.
2-2. 대표작 — 양이 아니라 깊이
인벤토리가 끝나면 유혹이 옵니다 — 전부 나열하고 싶어집니다. 그것이 포트폴리오 실패의 첫 번째 패턴입니다. 읽는 사람의 시간은 유한하고, 50개의 링크는 0개의 링크와 같은 효과를 냅니다 — 아무것도 클릭되지 않습니다.
대표작은 5~7개, 기준은 Step 294에서 세운 네 가지를 계승합니다.
| 기준 | 확인 질문 |
|---|---|
| 난이도 | 당시의 나를 밀어붙인 문제인가 |
| 서사 | 시행착오와 버린 가설이 드러나는가 |
| 재현성 | 독자가 명령과 출력만으로 따라 할 수 있는가 |
| 분야 균형 | 한 분야에 몰리지 않는가 (단, 지원 직무가 정해졌다면 그 분야를 앞에) |
Level 4에서 추가되는 기준이 하나 있습니다 — 다양성의 축을 넓히는 것. Write-up만 7개가 아니라, Write-up + CVE 보고서 + 오픈소스 도구 + 대회 기록 + 발표 자료가 섞여야 "풀이자"가 아니라 "만들고 분석하고 가르치는 사람"의 형상이 완성됩니다.
2-3. 4줄 요약 — 대표작의 포장 규격
선정한 대표작마다 붙이는 요약의 포맷은 네 줄로 고정합니다.
문제: 무엇을 풀어야 했는가 (한 문장)
내 접근: 처음 시도와 그것이 바뀐 지점
결과: 숫자 — 점수, 순위, 기간, 재현 여부
배운 것: 다음의 나에게 남은 것 (기법이 아니라 판단 기준)
네 줄인 이유는 읽는 사람 때문입니다. 채용 담당자는 대표작 링크를 눌러 글 전체를 읽지 않고, 링크 옆의 요약을 먼저 봅니다. 네 줄이 그 사람의 30초 안에 들어가고, 그 안에 "문제-사고-결과-성장"의 회로가 완성되어야 합니다. "배운 것" 칸이 비어 있는 요약은 아직 완성이 아닙니다 — 그 칸이 이 사람이 같은 문제를 다시 만났을 때 더 빨라진다는 증거입니다.
2-4. 이력서 변환 — 같은 내용, 다른 문법
통합 페이지와 이력서는 내용이 같고 문법이 다릅니다. 페이지는 탐색하는 독자를 위한 것이고, 이력서는 1장 A4 안에 스캔하는 독자를 위한 것입니다.
문법의 차이는 한 가지로 수렴합니다 — 기술을 "아는 것"이 아니라 "그걸로 한 것"으로 쓰는 것. "Python, 네트워크 프로토콜, 리버싱 가능"은 아는 것의 목록입니다. "퍼징 캠페인 자동화 스크립트로 취약점 2건 발굴(CVE-XXXX-XXXX)", "국내 메이저 CTF 본선 5위(40팀)"는 한 것의 목록입니다. 같은 실력이어도 후자만이 검증 가능하고, 검증 가능한 문장만이 면접으로 연결됩니다.
3. 따라 하기
3-1. 자산 폴더 구성 — 증거를 한곳에 모은다
실제 자산은 블로그·GitHub·CTFtime에 흩어져 있지만, 집계를 위해 로컬에 거울 폴더를 만듭니다. 링크와 파일명만 모으는 것이니 복사 부담은 작습니다.
portfolio/
├── posts/ ← Write-up 마크다운 (블로그 소스 그대로)
├── reports/ ← CVE·취약점 분석 보고서
├── repos/ ← 오픈소스 저장소 (폴더 이름만 — 저장소별 빈 폴더)
├── talks/ ← 발표 자료
└── records.csv ← 대회 기록: 대회명,순위,참가팀수,비고
records.csv는 직접 만듭니다 — CTFtime 팀 페이지와 대회 공식 스코어보드를 보며 옮깁니다. 이 CSV가 대회 기록의 단일 원천이 됩니다. 이력서에도, 통합 페이지에도, 다음 대회 신청서에도 이 파일의 숫자만 쓰는 규칙을 세우면 문서마다 숫자가 다른 사고를 막을 수 있습니다.
3-2. 인벤토리 스크립트 — 기계가 먼저 센다
거울 폴더를 스캔해 자산을 집계하고 통합 페이지의 초안까지 생성하는 스크립트입니다. step334_inventory.py로 저장하세요.
# step334_inventory.py — 포트폴리오 자산 인벤토리 + 통합 페이지 스켈레톤 생성기
# 사용: python step334_inventory.py <포트폴리오 자산 폴더>
import csv
import re
import sys
from pathlib import Path
def count_md(folder: Path):
if not folder.is_dir():
return []
return sorted(p.name for p in folder.glob("*.md"))
def read_category(path: Path):
m = re.search(r"^category:\s*(.+)$", path.read_text(encoding="utf-8"), re.M)
return m.group(1).strip() if m else "(미분류)"
def main():
root = Path(sys.argv[1])
posts = count_md(root / "posts")
reports = count_md(root / "reports")
repos = sorted(p.name for p in (root / "repos").iterdir() if p.is_dir())
if (root / "repos").is_dir() else []
talks = count_md(root / "talks")
cats = {}
for name in posts:
c = read_category(root / "posts" / name)
cats[c] = cats.get(c, 0) + 1
records = []
rec = root / "records.csv"
if rec.exists():
with rec.open(encoding="utf-8") as f:
records = list(csv.DictReader(f))
print("=== 포트폴리오 자산 인벤토리 ===")
print(f"Write-up: {len(posts)}편 | 분석 보고서: {len(reports)}건"
f" | 오픈소스 저장소: {len(repos)}개 | 발표 자료: {len(talks)}건")
print("\n[Write-up 분야 분포]")
for c, n in sorted(cats.items(), key=lambda x: -x[1]):
print(f" {c}: {n}편")
print("\n[대회 기록]")
for r in records:
rank, total = r["순위"], r["참가팀수"]
pct = f"상위 {int(rank)/int(total)*100:.0f}%"
if rank.isdigit() and total.isdigit() else "기록 없음"
print(f" {r['대회명']}: {rank}위 / {total}팀 ({pct}) — {r['비고']}")
skel = root / "portfolio_draft.md"
lines = [
"# 통합 포트폴리오 (초안 — 스크립트 생성)",
"",
"한 줄 소개: (여기에 — 어떤 문제를 풀어 온 사람인지 한 문장)",
"",
"### 대표작 (5~7개를 손으로 선정 — 문제/접근/결과/배운 것 4줄씩)",
"1. (posts/ 에서 선정) — ",
"2. ",
"",
"### 정량 기록 (스크립트가 채운 부분 — 검증 가능한 것만 유지)",
f"- Write-up 누적 {len(posts)}편 (분야: "
+ ", ".join(f"{c} {n}" for c, n in sorted(cats.items(), key=lambda x: -x[1])) + ")",
f"- 취약점 분석 보고서 {len(reports)}건 / 오픈소스 저장소 {len(repos)}개 / 발표 {len(talks)}건",
"- 대회: " + "; ".join(f"{r['대회명']} {r['순위']}/{r['참가팀수']}" for r in records),
"",
"### 연락처: (대회용 계정 — 실명·개인 이메일 금지)",
]
skel.write_text("\n".join(lines), encoding="utf-8")
print(f"\n통합 페이지 초안 생성: {skel.name}"
" — 대표작 선정과 4줄 요약은 사람이 채웁니다.")
if __name__ == "__main__":
main()
샘플 거울 폴더(Write-up 8편, 보고서 2건, 저장소 3개, 발표 1건, 대회 기록 3건)에 대고 실행한 실측 출력입니다:
=== 포트폴리오 자산 인벤토리 ===
Write-up: 8편 | 분석 보고서: 2건 | 오픈소스 저장소: 3개 | 발표 자료: 1건
[Write-up 분야 분포]
pwn: 7편
web: 1편
[대회 기록]
예선통과컵: 12위 / 180팀 (상위 7%) — 예선 통과
본선메이저: 5위 / 40팀 (상위 12%) — 본선 5위
국제온라인: —위 / —팀 (기록 없음) — 완주
통합 페이지 초안 생성: portfolio_draft.md — 대표작 선정과 4줄 요약은 사람이 채웁니다.
읽는 법: 세 가지를 봅니다. ① 총량의 확인 — "낼 만한 게 부족한 것 같다"는 불안이 숫자로 정정됩니다. 샘플은 8편뿐이지만, 여러분의 실제 폴더에서는 수십 편이 나올 것입니다. 부족한 게 아니라 정리가 안 된 것이었다는 사실이 여기서 증명됩니다. ② 분야 분포의 편향 — 샘플은 pwn 7편, web 1편입니다. 이 편향이 대표작 선정(3-3)의 입력값이 됩니다. ③ "기록 없음" 행 — 국제온라인 대회는 순위를 안 적고 완주만 적었습니다. 확인 불가능한 수치를 비워 두는 것은 결함이 아니라 규칙 준수입니다.
생성된 portfolio_draft.md의 내용도 실측 그대로입니다:
# 통합 포트폴리오 (초안 — 스크립트 생성)
한 줄 소개: (여기에 — 어떤 문제를 풀어 온 사람인지 한 문장)
### 대표작 (5~7개를 손으로 선정 — 문제/접근/결과/배운 것 4줄씩)
1. (posts/ 에서 선정) —
2.
### 정량 기록 (스크립트가 채운 부분 — 검증 가능한 것만 유지)
- Write-up 누적 8편 (분야: pwn 7, web 1)
- 취약점 분석 보고서 2건 / 오픈소스 저장소 3개 / 발표 1건
- 대회: 예선통과컵 12/180; 본선메이저 5/40; 국제온라인 —/—
### 연락처: (대회용 계정 — 실명·개인 이메일 금지)
기계가 채운 곳과 빈칸의 경계를 보세요 — 정량 기록은 스크립트가, 대표작과 한 줄 소개는 사람이 채웁니다. 이 분업이 2-1~2-3의 원칙 그대로입니다.
3-3. 대표작 선정과 4줄 요약
2-2의 기준표로 5~7개를 고릅니다. 샘플 폴더 기준의 선정 화면 예시입니다:
■ 대표작 선정 결과 (화면 예시)
1. [Write-up] heap-house — 힙 하우스 계열 기법을 처음으로 혼자 재현한 기록
문제: 최신 glibc의 힙 보호 기법이 적용된 바이너리 공격
내 접근: house of X 계열을 순서대로 시도, 3번째에서 검사 로직 발견
결과: 6시간 만에 셸 획득, 대회 내 2솔브
배운 것: 보호 기법 목록을 먼저 열거하는 것이 시도 순서를 결정한다
2. [보고서] CVE-2026-EX02 독립 분석 — 패치 diff로 취약점을 역추적
문제: 공개된 보안 패치에서 취약점의 원인을 독립적으로 특정
내 접근: diff의 변경 함수 3개를 후보로 두고 PoC로 하나씩 소거
결과: 근본 원인 특정 + 재현 PoC 작성, 보고서 12페이지
배운 것: 패치는 "답이 적힌 문제"다 — diff를 읽는 눈이 연구의 출발점
3. [오픈소스] payload-fuzzer — 대회용 페이로드 생성기를 공개 저장소로
... (4줄 생략 예시)
선정할 때 3-2 출력의 분야 편향을 되돌아보세요. 샘플처럼 pwn에 몰렸다면, web의 1편이 자동으로 후보가 됩니다 — 대표작은 "가장 잘한 것"의 목록인 동시에 "내가 커버하는 범위"의 광고이기 때문입니다.
3-4. 통합 페이지 배치 — About 또는 GitHub 프로필 README
완성한 초안을 배치할 곳은 둘 중 하나입니다. 블로그에 이미 독자가 있다면 About 페이지, 채용 열람이 주목적이라면 GitHub 프로필 README(계정 이름과 같은 이름의 저장소에 넣는 특수 README)입니다. 둘 다 해도 되지만, 내용은 하나의 원천에서 관리하세요 — 지금 만든 portfolio_draft.md가 그 원천입니다.
GitHub 프로필 정돈의 체크리스트는 세 가지입니다.
[ ] pinned 저장소 6개 선정 — 대표 도구 + Write-up 모음 + CVE 분석 저장소
(트로피가 아니라 증거를 고정한다: README가 성의 있는 저장소만)
[ ] 각 pinned 저장소 README 점검 — 첫 화면에 "무엇을/왜/어떻게 쓰는가" 3줄
[ ] 커밋 잔디의 지속성 — 일일 커밋이 목표가 아니라 "끊기지 않는 것"이 목표
잔디에 대해 한 가지만 짚습니다. 채용 담당자가 잔디에서 보는 것은 밀도가 아니라 지속입니다 — 오랜 기간에 걸친 드문드문한 초록이, 두 달 빽빽하고 넉 달 공백인 패턴보다 신뢰를 줍니다. 잔디를 꾸미려는 시도(자동 커밋 봇 등)는 확대경이 들어오는 순간 역효과입니다.
3-5. 1장 이력서 — 같은 증거의 압축
통합 페이지가 완성되면 그것을 1장 A4로 압축합니다. 압축의 규칙은 2-4의 문법 하나입니다 — "아는 것"을 "한 것"으로.
■ 이력서 기술란 변환 예시 (화면 예시)
[변환 전 — 아는 것]
Python, C, 네트워크 프로토콜 이해, 웹 취약점, 리버싱, CTF 경험
[변환 후 — 한 것]
- CTF: 국내 메이저 대회 본선 5위(40팀), Write-up 누적 50편+
- 취약점 분석: 공개 패치 diff 독립 분석 2건, 재현 PoC 포함 보고서 작성
- 오픈소스: 대회용 익스플로잇 라이브러리 등 저장소 3개 운영 (README·테스트 포함)
- 발표·멘토링: 입문자 대상 힙 입문 세미나 1회, 스터디 멘토링 N주
읽는 법: 변환 전의 모든 단어가 변환 후에도 살아 있지만, 전부 동사의 목적어로 이동했습니다. "리버싱"은 사라지고 "패치 diff 독립 분석"이 남았습니다. 채용 화면에서 읽히는 것은 후자뿐이라는 것을 기억하세요. 그리고 분량 규칙 — 1장을 넘기는 순간 가장 강한 문장이 묻힙니다. 빼는 것이 이력서 작업의 본질입니다.
4. 미션과 연습문제
미션 — 통합 포트폴리오 + 1장 이력서
- 3-1의 구조로 자산 거울 폴더를 만들고,
records.csv에 확인 가능한 대회 기록만 옮깁니다. step334_inventory.py를 실행해 자산 총량과 분야 분포를 확인하고, 생성된portfolio_draft.md를 받습니다.- 2-2의 기준표로 대표작 5~7개를 선정하고 각각 4줄 요약을 붙여 초안을 완성합니다 — 분야 편향이 보이면 균형을 조정합니다.
- 통합 페이지를 배치합니다(블로그 About 또는 GitHub 프로필 README) — 개인정보 가공과 수치 검증 가능성을 최종 점검합니다.
- GitHub pinned 저장소 6개를 정돈하고, 같은 내용을 2-4의 문법으로 1장 이력서에 압축합니다.
연습문제
문제 1. 포트폴리오 작업이 "모으기"가 아니라 "재고 조사"로 시작해야 하는 이유를, 사람이 자기 축적물을 과소평가하는 심리와 연결해 설명해 보세요.
문제 2. 대표작 목록에 Write-up만 7개를 나열하는 것이 왜 불완전한가요? Level 4에서 추가되는 선정 기준의 관점에서 답해 보세요.
문제 3. 4줄 요약의 "배운 것" 칸이 비어 있으면 그 요약이 증명하지 못하는 것은 무엇인가요?
문제 4. 이력서에서 "아는 것" 목록이 "한 것" 목록보다 약한 이유를, 검증 가능성과 면접으로의 연결 관점에서 설명해 보세요.
해설과 완료 기준 · 펼치기/접기
5. 모범 답안과 완료 기준
미션 모범 답안
검증 기준으로 확인하세요.
- 거울 폴더의 완결성: 일곱 종류의 자산 중 파일로 표현 가능한 것이 빠짐없이 모였는가 — 멘토링 경력처럼 파일이 없는 것은
records.csv의 비고나 별도 메모로 존재하는가. - 스크립트 실행: 인벤토리 출력의 총량·분포·대회 기록이 실제 자산과 일치하는가.
- 대표작의 품질: 5~7개 각각에 4줄 요약이 있고, "배운 것" 칸이 비어 있는 요약이 없는가 — 축의 다양성(글/보고서/도구/대회/발표)이 확보됐는가.
- 배치의 완성: 통합 페이지가 공개 상태이고, 모든 수치가 클릭 한두 번으로 제3자가 확인 가능한가.
- 이력서의 문법: 기술 목록이 전부 "한 것" 문장으로 변환됐고, 1장을 넘지 않는가.
연습문제 해답
문제 1 해답. 인간의 기억은 반복된 경험을 "당연한 것"으로 압축합니다 — 매일 한 과제는 축적물이 아니라 일상으로 느껴집니다. 그래서 머리로 떠올리면 실제보다 적게 느껴지고, 그 과소평가가 "낼 만한 게 부족하다"는 불안으로 나타납니다. 재고 조사는 이 왜곡을 파일 시스템이라는 객관 장치로 교정하는 절차입니다. 스크립트가 세어 준 숫자는 감정이 아니라 사실이고, 그 사실 위에서만 선정과 배치라는 판단이 정확해집니다. 순서가 뒤집히면 — 글부터 쓰면 — 없는 줄 알고 새로 만들거나, 있는 줄 알고 빠뜨리는 사고가 납니다.
문제 2 해답. Write-up만의 목록은 "문제 풀이자"라는 단일 형상만 증명하기 때문입니다. Level 4의 대표작은 축의 다양성을 요구합니다 — 풀이(Write-up), 분석(CVE 보고서), 제작(오픈소스 도구), 경쟁(대회 기록), 전달(발표·멘토링). 채용 담당자나 협업 제안자는 이 다섯 축의 조합으로 지원자의 역할 가능성을 판단합니다. 한 축만 있는 포트폴리오는 그 축이 아무리 깊어도 "다른 것도 할 수 있는가"라는 질문에 답을 주지 못합니다. 대표작 선정은 실력의 심사인 동시에 역할의 설계입니다.
문제 3 해답. "다음에 더 잘할 사람인가"를 증명하지 못합니다. 문제-접근-결과의 세 줄은 과거의 사실만 담고, 배운 것의 한 줄만이 그 경험이 판단 기준으로 전환됐음을 보여 줍니다. 읽는 사람의 관점에서 세 줄짜리 요약은 "그 문제를 풀었다"이고, 네 줄짜리 요약은 "그 문제가 이 사람을 바꿨다"입니다. 채용이 과거의 실적 심사인 동시에 미래의 성장 가능성 평가라는 점에서, 네 번째 줄이 요약의 절반의 가치를 가집니다.
문제 4 해답. "아는 것"은 주장이고 "한 것"은 증거이기 때문입니다. "리버싱 가능"은 지원자 스스로 검증할 수 없고 면접관도 서류에서 확인할 방법이 없는 문장입니다 — 면접에서 다시 물어봐야 하므로 서류 단계에서 아무 점수도 만들지 못합니다. 반면 "패치 diff 독립 분석 2건, PoC 포함 보고서"는 링크로 즉시 검증되고, 면접 질문을 "할 수 있나요"가 아니라 "어떻게 하셨나요"로 바꿉니다. 후자의 면접은 지원자가 가장 잘 아는 이야기를 하는 자리이고, 전자는 검증 시험대입니다. 같은 실력이라도 문법이 자리의 성격을 결정합니다.
완료 기준 체크리스트
- [ ] 자산 거울 폴더(posts/reports/repos/talks/records.csv)를 구성했다
- [ ]
step334_inventory.py를 실행해 총량·분야 분포·대회 기록을 확인했다 - [ ] 대표작 5~7개를 기준표로 선정하고 4줄 요약(특히 "배운 것")을 완성했다
- [ ] 통합 페이지를 배치했다 — 수치 검증 가능성과 개인정보 가공을 점검했다
- [ ] GitHub pinned 저장소를 증거 중심으로 정돈했다 (README 3줄 포함)
- [ ] 1장 이력서를 "한 것" 문법으로 작성했다
- [ ]
records.csv를 대회 기록의 단일 원천으로 정하고 모든 문서의 숫자를 통일했다
6. 흔한 실수와 해결
벽 1. 스크립트를 실행하니 폴더 인자 에러가 나요
증상:
root = Path(sys.argv[1])
~~~~~~~~^^^
IndexError: list index out of range
원인: 자산 폴더 경로를 인자로 안 줬습니다 (실측 — 인자 없이 실행하면 동일 에러).
해결: python step334_inventory.py portfolio처럼 폴더를 붙여 실행하세요. 경로에 공백이 있으면 큰따옴표로 감쌉니다. 하위 폴더(posts 등)가 없어도 스크립트는 죽지 않고 0건으로 세니, 일부만 있는 상태로 시작해도 됩니다.
벽 2. 대회 기록이 "기록 없음"으로 나와요
증상: records.csv에 적은 순위가 "(기록 없음)"으로 처리됩니다.
원인: 스크립트는 순위·참가팀수 칸이 둘 다 숫자일 때만 백분위를 계산합니다. "12등", "5위/40팀"처럼 문자가 섞이면 숫자로 인식하지 못합니다 (실측 — 샘플의 국제온라인 행이 이 경우).
해결: CSV의 순위·참가팀수 칸에는 숫자만 넣으세요 — 12, 40. "위", "팀" 같은 단위는 출력에서 스크립트가 붙입니다. 순위를 모르는 대회(완주만 기록)는 예시처럼 —를 넣는 것이 정답입니다.
벽 3. 대표작을 고르다 보니 자꾸 10개가 넘어가요
증상: 이것도 저것도 대표라서 7개로 못 줄입니다.
원인: 선정 기준이 아니라 애착으로 고르고 있는 상태입니다 — 오래 고생한 것이 대표작이라는 착각.
해결: 절차를 뒤집으세요 — 먼저 7칸짜리 빈 표를 만들고, 축별(풀이/분석/제작/경쟁/전달)로 한 칸씩만 채웁니다. 같은 축에서 경쟁하는 후보가 둘이면 "독자가 30초 안에 가치를 알 수 있는가"로 결판합니다 — 고생의 양은 독자에게 전달되지 않습니다. 빠진 것은 삭제가 아니라 통합 페이지 본문의 전체 목록 링크에 남습니다. 대표작은 커튼이고, 목록은 무대입니다.
벽 4. 통합 페이지에 뭘 썼는지 이력서와 숫자가 달라요
증상: 블로그에는 "Write-up 50편", 이력서에는 "45편", GitHub에는 "40여 편".
원인: 각 문서를 다른 날에 따로 고쳤기 때문입니다 — 원천이 없으면 문서는 반드시 갈라집니다.
해결: 3-1의 규칙으로 돌아가세요 — 숫자의 단일 원천(records.csv와 인벤토리 스크립트 출력)을 정하고, 모든 문서는 그 출력을 복사해서 씁니다. 숫자가 바뀔 때는 원천을 먼저 고치고 문서를 다시 생성합니다. 면접관이 두 문서를 나란히 놓는 순간, 숫자의 불일치는 "과장"으로 읽힙니다 — 실수여도요.
벽 5. 정리하고 보니 남들보다 빈약해 보여요 — 이대로 내도 되나요?
증상: 다른 사람의 화려한 포트폴리오를 보고 자괴감이 옵니다.
원인: 비교 대상의 선정 오류입니다 — 그 사람의 최종판과 여러분의 첫 통합판을 비교하고 있습니다. 그리고 화려함의 상당 부분은 여러분이 아직 안 써 본 장식입니다.
해결: 사실로 정정하세요 — 부족한 게 아니라 정리가 안 된 것이었고, 오늘 그 정리를 끝냈습니다. 기준은 하나입니다 — 모든 문장이 검증 가능한가. 검증 가능한 8편이 검증 불가능한 80편을 이깁니다. 포트폴리오는 완성품이 아니라 갱신되는 문서입니다 — 다음 대회, 다음 분석이 이 페이지의 다음 버전을 만듭니다. 오늘 필요한 것은 v1의 공개이지 완성이 아닙니다.
7. 정리
오늘의 개념
| 개념 | 한 줄 설명 |
|---|---|
| 인벤토리화 | 기억이 아니라 파일 시스템으로 세는 재고 조사 — 과소평가의 교정 |
| 대표작 선정 | 양이 아니라 깊이 + 축의 다양성(풀이/분석/제작/경쟁/전달) |
| 4줄 요약 | 문제·접근·결과·배운 것 — "배운 것"이 성장의 증거 |
| 검증 가능성 | 모든 수치는 제3자 확인 가능한 것만 — 신뢰의 본체 |
| 단일 원천 | 숫자는 records.csv 하나에서 — 문서 간 불일치 방지 |
| 이력서 문법 | "아는 것"이 아니라 "한 것" — 주장이 아니라 증거 |
오늘의 도구·명령어
| 도구·명령 | 하는 일 |
|---|---|
python step334_inventory.py <폴더> |
자산 총량·분야 분포·대회 기록 집계 + 통합 페이지 초안 생성 |
| 거울 폴더 구조 | posts/reports/repos/talks + records.csv — 증거의 집결지 |
| GitHub 프로필 README | 계정명과 같은 저장소의 특수 README — 채용 열람의 첫 화면 |
| pinned 저장소 6개 | 트로피가 아니라 증거를 고정 — README 3줄 규칙 |
| 1장 이력서 압축 | 통합 페이지의 "한 것" 문법 압축본 |
명령어보다 중요한 감각
포트폴리오는 자기 PR이 아니라 검증 가능성의 축적입니다. 오늘 만든 페이지의 가치는 디자인이 아니라, 임의의 문장 하나를 골라도 그 끝에 링크와 숫자가 붙어 있다는 구조에 있습니다. 그 구조가 만드는 것은 신뢰이고, 신뢰는 채용과 협업의 유일한 통화입니다.
그리고 이 페이지는 완성품이 아닙니다. 다음 대회의 순위, 다음 분석의 보고서, 다음 저장소가 이 페이지를 갱신합니다. 중요한 것은 오늘 갱신 가능한 형태로 만들었다는 것 — 원천이 하나이고, 구조가 잡혔고, 빈칸이 무엇으로 채워져야 하는지 안다는 것입니다. 이제 남은 것은 이 페이지를 무기로 쓸 다음 결정, 커리어 방향의 확정입니다.
전부 체크되면 Step 334 완료입니다.
한 단계 더 가까이
수업을 마쳤나요?
본문의 완료 기준을 확인한 뒤 직접 표시하세요.