2026-08-10 (월) 개발일지
오늘은 여러 프로젝트를 동시에 정리했다. demo_apps 폴더 혼동 문제부터 시작해 하네스의 채점 기준 모호성, 게임 문제 변환, 그리고 이력서 파서까지 — 각각 단순해 보였지만 파보니 단서들이 연쇄적으로 얽혀있었다.
demo_apps 폴더명 공백 문제 — 로컬 폴더 두 개가 같은 이름인 줄 알았던 이유
오늘 아침 개발 폴더가 두 개 있다는 걸 알았다. 하나는 ~/demo_apps/dongbaek 이고 다른 하나도 demo_apps 인데, 파일 탐색기에서 둘이 구별이 안 됐다. 처음엔 단순 복제 실수라 생각했다 — 옛날에 클론 받고 잊어버린 걸 또 받은 건가 싶었다.
그런데 두 폴더를 나란히 놓고 자세히 보니 폴더명 끝에 공백이 붙어있는 쪽이 있었다. demo_apps vs demo_apps (공백 한 칸). 파일 탐색기는 이런 차이를 시각적으로 거의 못 보여주니까 한 폴더인 줄 알기 쉬웠다.
폴더명 끝에 공백이 붙어있는 쪽이 실제로는 다른 폴더였다. 시스템 레벨에서는 완전히 별개지만, 휴먼 인터페이스에선 구별이 안 되는 그런 상황이었다.
두 폴더의 크기를 비교했다. 공백 폴더가 65MB 였고 — 대부분 옛날 클론 결과물이었다. 진짜 개발 폴더는 ~/demo_apps/dongbaek 쪽이었고, 거기에 dev 브랜치와 최신 커밋(ec1b35b)이 있었다.
두 폴더 안의 apps/backend/.env 를 직접 비교했다. 둘 다 121바이트, 같은 키들이 들어있었다. 현행 저장소의 .env 가 더 최신이라 건질 필요가 없었다.
처리 방법을 정했다. 공백 폴더를 완전 삭제하지 말고 휴지통으로 보냈다 — 나중에 되돌릴 수 있게. 그리고 진짜 개발 폴더에 기획 문서(demo-app-v2-plan.md, 156줄)를 복사해 넣었다. 문서 무결성을 확인했고 — 바이트 단위로 일치했다.
결과: 이제 ~/demo_apps 하나만 남았고, 그 안의 dongbaek 폴더가 진짜 개발 브랜치다. 옛 클론은 휴지통 demo_apps-old-clone-20260810 으로 이동했다.
하네스 — 기획안 모호성을 “판정 제외 목록”으로 못박기
하네스 프로젝트에서 채점 기준이 공개 과제문과 맞지 않는 부분들이 있었다. 처음엔 “채점기 버그”라고 생각했는데, 파보니 과제문 자체가 모호했던 거였다.
18개의 critical 기준을 공개 과제문과 1:1 대조했다. 15개는 명확하게 유도되고 있었지만, 3개는 해석이 갈렸다:
- A 과제의 “예약 취소·노쇼” 처리 방식 (2개)
- B 과제의 “부정 리뷰” 감지 기준 (1개)
처음엔 이 3개를 “채점 기준이 잘못됐다”고 봤다. 하지만 실제로는 과제문을 읽는 사람마다 다르게 이해할 여지가 있었다. 예를 들어 “취소나 노쇼가 된 예약도 나중에 다시 보려고 남겨둔다”는 문장이 과제문에 없어서, 참가자가 그걸 어떻게 처리해야 할지 알 수 없었던 것.
모호한 기준 3개를 과제문에 명시하는 게 가장 깔끔한 해법이었다. 채점 기준을 바꾸는 게 아니라, 과제문에 4줄을 추가해서 모호성을 없애는 방식으로.
과제문에 추가한 문장들:
tasks/a-salon.md: “취소나 노쇼가 된 예약도 나중에 다시 보려고 남겨둔다.”tasks/a-salon.md: “매일 아침 그날 예약부터 확인한다.”tasks/b-reviews.md: “부정 리뷰가 전체에서 얼마나 되는지 감이 잡히지 않아 답답하다.”
그리고 판정 제외 규칙을 protocol.md 에 추가했다. 제외 기준은 사전에 고정된 목록만 인정하고, 실행 후에 추가하지 않는다는 원칙. 이렇게 하면 “채점 후에 기준을 바꿨다”는 의심이 안 생긴다.
검증: grep -c '판정 제외' protocol.md → 4줄 (≥1 통과). TASK-COVERAGE.md 의 critical 행 18개, 전부 판정 과 처리 필드가 채워져 있었다.
게임 — 52개 문제를 stdin/stdout 계약으로 변환
옵시디언에 작성한 라이브 코테 문제 52개를 문제집에 넣기로 했다. 처음엔 “함수형 문제를 그대로 옮기면 되겠지” 했는데, 파보니 클래스 기반 구조가 많아서 stdin/stdout 으로 변환이 필요했다.
예를 들어 “두 개의 스택으로 큐를 구현하라” 같은 문제는 원래 클래스형이었다:
class MyQueue:
def push(self, x): ...
def pop(self): ...
이걸 stdin/stdout 계약으로 바꿨다:
push 1
push 2
pop
pop
33개 문제를 변환했고, 각각의 레퍼런스 솔루션이 자기 테스트 케이스를 통과하는 걸 확인했다. 변환 과정에서 5개 문제는 컨테이너 전용 topology 였아서 제외했다 — 명령 스트림으로 표현할 방법이 없었다.
마지막에 빌드를 돌렸다. 344 passed / 5 failed — 예상한 그 5개였다. 39개 미션, 39개 표본, 12개 챕터로 정리됐다. 커밋(ae8ea7e)을 완료했고, 작업 트리는 깨끗하다.
자동이력서지원 — 파서 결함을 세 가지 전략으로 비교
이력서 PDF를 파싱하는 세 가지 방식을 만들어 비교했다. 각 방식이 22개 엔트리 중 몇 개를 정확하게 추출하는지 봤다.
세 가지 전략:
- date-anchored: 기간 문자열(
2019.03~2021.06)을 찾아 그 주변을 엔트리로 묶기 - title-anchored: 직책이나 회사명 같은 제목 패턴을 찾아 그 주변을 묶기
- layout-anchored: PDF의 좌표 정보(
tm[5]= 문서 전역 Y축)를 직접 사용해 시각적 순서대로 정렬
점수만 보면 date-anchored와 layout-anchored가 7/7 로 같았다. 하지만 오귀속(misattribution)의 원인이 달랐다.
date-anchored의 문제는 앵커 규칙 자체에 있었다. 문자열 모양으로 “이건 기간이다”라고 판정하니까, 학점 1.87 안의 87 같은 게 87% 처럼 보여서 거짓 매칭을 일으켰다. 이건 재설계 없이는 못 막는다.
layout-anchored의 문제는 예외 분기 한 곳이었다. 재직 중인 항목(재직중)은 기간이 없어서 드롭됐는데, 그 줄 한 개만 고치면 된다.
결함의 위치로 갈랐다. date-anchored는 근본적 설계 문제, layout-anchored는 예외 처리 누락 — 후자가 훨씬 싸다.
최종 선택: layout-anchored를 본체로 삼기로 했다. 파서를 정리하고 coverage audit을 돌렸다. 162줄 중 소실 0 — 모든 원문 라인이 어딘가에 랜딩했다. 정말 놓친 게 없나 확인하기 위해 4가지 변형(mutation) 테스트를 돌렸다. 각 변형마다 예상한 엔트리가 정확히 사라지는 걸 확인했다.
오늘의 한줄
같은 문제도 보는 각도에 따라 “버그”가 되기도 “설계 결함”이 되기도 한다 — 원인의 깊이를 모르면 같은 증상을 계속 반복 수술한다.