HANYANG UNIV · 2015 – 2020 · 16 REPOSITORIES

학부 5년 코드 감사

배지운(jiunbae)이 학부 5년간 공개 저장소에 남긴 수업 코드 전량을, 저장소 하나당 리뷰어 한 명을 붙여 정독·빌드·실행하고 등급을 매겼다.

저장소16
커밋1,098
기간2015.10–2020.04
중앙값C+
C+종합

한 문장

코드를 쓸 줄 아는 사람이, 자기 코드가 맞는지 확인하는 법을 5년 내내 배우지 않았다.

16개 저장소에서 반복되는 결함은 실력 부족이 아니다. Herlihy–Shavit lock-free 리스트를 Java로 정확히 이식하고(CSE4006), 직접 짠 역전파를 수치미분과 대조해 상대오차 1.86e-07을 얻고(ITE4053), InnoDB 락 매니저에 lock-free 설계를 이식하는(ITE4065) 사람이다. 문제는 그 다음 칸이다 — 만든 것이 맞는지 확인하는 장치가 번번이 스스로 무력화되어 있고, 본인은 그것을 모른 채 "통과했다"고 기록한다.

가장 선명한 증거는 CSE4006이다. 테스트 셋업이 대상 객체를 생성하지 않아 6개 테스트가 전부 NPE이고, 워커 스레드의 AssertionErrorcatch(RuntimeException)에 걸리지 않아 메인 스레드에 도달조차 못 한다. 구조적으로 실패할 수 없는 테스트다. 그 상태에서 제출 보고서는 "테스트를 모두 통과했으므로 올바르게 작동한다"를 두 번 쓴다.

이 판정을 어떻게 했는가

저장소 16개에 리뷰어를 한 명씩 독립으로 붙였다. 지시는 다섯 줄이었다 — 모든 지적에 경로:행번호를 인용할 것, 싸게 검증할 수 있으면 빌드하고 실행해 실제 출력을 증거로 쓸 것, 확인하지 못한 것은 “미검증”이라고 밝힐 것, README의 주장과 구현을 대조할 것, 저장소가 작으면 짧은 보고서가 정답일 것. 그 결과 인용 264건 중 218건이 실제 파일 경로로 확인돼 원본 코드에 연결된다.

판정을 의심할 근거가 필요하다면 그것도 공개해 두었다. 공통 브리프 전문과 각 보고서 하단의 「이 보고서를 만든 프롬프트」에 리뷰어에게 준 지시가 그대로 있다.

등급 분포

B~B- 4개 — 과제 요구를 실제로 충족 C+~C 10개 — 동작하나 핵심이 부분적으로 죽어 있음 D+ 2개 — 제출물이 자기 입력에서 실패

저장소별 판정

각 줄을 누르면 해당 저장소의 전체 보고서로 간다. 등급은 학부 과제 기준이며, 근거는 모두 파일·행 번호로 인용되어 있다.

2015 — 1학년
ITE1009C 프로그래밍C+과제 전부인 XML 저장에서 닫는 태그를 모두 </name>으로 출력 — 커밋된 XML 3개 전부 파싱 실패. 날짜 폴더 4,000줄은 Learn C the Hard Way 예제이나 표기를 뜯은 정황은 없음.
MAT2003선형대수B-최소비용유량 답은 독립 구현 교차검증으로 정확. 그러나 벤치마크 그래프 10개 중 7개가 답을 못 내고 cost: 0을 뱉는데, 보고서는 그 실행시간을 개선 근거로 제시한다.
2016 — 2학년
CSE2010자료구조D+랩 15개를 빠짐없이 커밋했지만 절반 가까이가 자기가 함께 커밋한 입력 파일에서 오답·크래시·컴파일 실패. Lab02는 정답 파일을 커밋해 두고 4/4 불일치 — 한 번도 돌려보지 않은 직접 증거.
ITE2037객체지향프로그래밍C+1,727파일 중 본인 코드는 76파일 5,500줄. 4인 팀 작업이며 팀원 코드는 평가에서 제외했다. 감염자 이동 로직이 입력과 무관하게 항상 Left를 반환 — 보고서 주장과 정면 배치.
ITE2039알고리즘C+LongestCommonSubsequence.cpp 전문이 printf("abcde") 5줄. ASan이 힙 경계 위반 3건 확정. 반면 Heapsort·MatrixChain·RodCutting은 CLRS와 정확히 일치.
MAT4015응용확률및랜덤프로세스Cmean shift를 직접 구현한 것은 맞으나 히스토그램을 L1이 아닌 L2로 정규화해 확률질량함수가 아니고, 유사도는 제곱근 없는 내적이라 Bhattacharyya가 아니다. 비교 대상 OpenCV 베이스라인은 Rect를 값으로 받아 한 프레임도 추적하지 않는다.
2017 — 3학년
ELE3021운영체제D+초기화 실패 경로가 PTR_ERR(NULL) = 0을 반환해 실패가 성공으로 보고된다. 시스템콜 등록은 ABI 컬럼 comoon 오타로 64비트에서 실패. 16주 학기에 코드 커밋 5개.
ENE4014프로그래밍 언어B-LR 파서 구동 루프와 정규식 엔진은 명세 예제를 전부 통과. 그러나 FSA가 인식하는 언어가 입력 파일의 행 순서에 따라 달라져 부분집합 구성이라 부를 수 없다. 버그를 스스로 찾아 고친 커밋 2건 확인.
ITE4065병렬 프로그래밍B-InnoDB 락 매니저 개조는 학부 과제로 드문 깊이다. 다만 성능의 상당 부분이 충돌 검사를 제거해 얻은 것이고, 데드락 탐지는 return true 한 줄에 막혀 실행되지 않으며, 스냅샷 벤치마크는 태스크 push 횟수를 세고 있었다.
CSE4006소프트웨어공학Clock-free는 진짜다 — CAS와 논리 삭제 마킹까지 정확히 이식했다. 그런데 테스트가 구조적으로 실패할 수 없고, 명세가 요구한 lock-free BST는 전 이력에 만들어진 적이 없는데 문서가 제목을 바꿔 누락을 덮었다.
ITE3068소프트웨어 스튜디오C+20점짜리 캐시 성능 비교가 무효다. arcus만 캐시를 비운 채 비교했고, 기대 히트율 6.5%로 역산한 값이 제출 수치와 오차 1%로 일치한다. 시크릿 노출 다수(아래 조치 항목 참조).
2018 — 4학년
CSE4020컴퓨터그래픽스C+"법선을 고쳤다"는 이름의 커밋이 정점별 법선을 면당 하나로 바꿔 40점 항목을 무너뜨렸다. 구 모델 960면이 전부 같은 법선을 쓴다(참값 대비 평균 7.81° 오차).
ITE4005데이터 사이언스B네 과제 모두 실행 검증을 통과한 유일한 저장소. DBSCAN은 정답과 Jaccard 0.97~1.00, 추천기는 데이터 누수 없음. 다만 calssifier 오타 한 글자로 random forest 전체가 죽어 있다.
ITE2038데이터베이스 시스템C+인가가 전혀 없어 GET /customer 한 번으로 전 고객의 평문 비밀번호와 카드번호가 노출되고, 배달부 화면이 실제로 그 요청을 보낸다. 명세가 요구한 기능 다수가 미구현.
2019 – 2020 — 졸업 전후
ITE4053딥러닝 방법론과 응용C+직접 짠 역전파가 수치미분과 상대오차 1.86e-07로 일치 — 이 감사에서 가장 정직한 검증이다. 그런데 손실 함수 설정이 무시되어 README 비교표에 존재할 수 없는 행이 있고, denoising은 검증셋이 곧 학습셋이다.
EWE8044고급컴퓨터공학특론 2C상관계수를 계산해 놓고 버린 뒤 두 셀 앞의 엔트로피를 출력한다. 물리적으로 불가능한 보고값 3개(음수 KL divergence 포함). 커밋 1개, 259MB 중 대부분이 데이터셋.

16개를 관통하는 네 가지

저장소 하나씩 볼 때는 개별 실수로 보이던 것이, 5년치를 겹쳐 놓으면 습관으로 보인다.

1

검증 장치가 스스로 무력화되어 있다

이 감사에서 가장 자주 나온 패턴이며, 다섯 과목에서 각각 다른 형태로 반복된다.

  • CSE4006 — 테스트 셋업이 대상을 생성하지 않고, 워커 스레드의 AssertionError가 메인에 도달하지 못한다. 실패가 불가능한 테스트.
  • CSE2010 — 기대 출력 파일을 커밋해 두고 diff 한 번을 하지 않아 4/4 불일치가 방치됐다.
  • ITE4053 — 손실 함수 인자가 무시되어 MSEBCE의 그래디언트가 비트 단위로 동일하다. 두 실험이 같은 실험이다.
  • ITE4005calssifier 오타로 random forest가 생성만 되고 버려진다. --forest 5 출력이 단일 트리와 동일하다.
  • ITE1009 — XML 리더가 닫는 태그 이름을 검사하지 않아, 깨진 라이터의 출력을 자기 리더만 읽을 수 있다.

공통점은 버그가 "틀린 답"이 아니라 "확인 자체의 부재"로 나타난다는 것이다. 그래서 본인에게는 끝까지 보이지 않는다.

2

측정이 주장을 뒷받침하지 못한다

성능·정확도를 주장하는 다섯 저장소 전부에서, 제시된 숫자가 주장하는 것을 재지 않았다.

  • ITE4065 — wait-free 스냅샷 그래프가 실제로는 태스크 큐 push 횟수를 센 것. 4스레드 2초 실행에 RSS 369MB로 재현했다.
  • ITE3068 — 캐시 비교에서 한쪽만 캐시를 비웠다. 기대 히트율 6.5%로 역산한 값이 제출 수치와 오차 1%.
  • MAT2003 — 답을 내지 못한 실행의 소요시간을 알고리즘 개선 근거로 인용.
  • MAT4015 — 비교 대상 OpenCV 베이스라인이 Rect를 값으로 받아 추적을 하지 않는다. 본인이 올린 성공 데모 GIF가 그 증거다.
  • ITE4005 — 유일하게 베이스라인이 필요했던 추천 과제에 베이스라인이 없었다(리뷰어가 대신 계산: 100차원 SVD의 실이득 2.7%).
3

문서가 코드보다 앞서간다

README 체크박스와 제출 보고서가 구현을 앞지르는 일이 학년과 무관하게 반복된다. ITE4065의 "Failure Link ✓"(구현 없음), CSE4006의 Part 2 제목 바꿔치기(요구된 lock-free BST가 전 이력에 없음), ITE2038의 완료 표기(명세 항목 다수 미구현), ITE2037의 이동 전략 서술(코드는 상수 반환), EWE8044의 "2,500장·100만 패치"(코드는 32장·1만 패치).

의도적 과장으로 보이지는 않는다. 계획을 적고, 구현하고, 계획을 다시 확인하지 않는 순서가 굳어 있다. 문제는 리뷰어가 README를 한 번 믿지 못하게 되면 진짜 성과까지 할인된다는 것이다 — 이 저장소들에는 할인당하면 아까운 성과가 실제로 있다.

4

같은 오해가 1년 간격으로 재발한다

서로 다른 과목, 서로 다른 해에, 독립적으로 같은 실수가 나온다. 우연이 아니라 고착된 오해다.

MAT2003/Problem2i.cc2015·ITE2039/dijkstra.cpp2016
// 2015 — 선형대수 최소비용유량
priority_queue<pair<int,int>> qu;
qu.push({t, dist[t]});          // first = 정점 번호

// 2016 — 알고리즘 다익스트라
priority_queue<pair<t_index, t_cost>> pq;
pq.push({start, 0});            // first = 정점 번호

둘 다 pairfirst로 정렬된다 — 거리가 아니라 정점 번호다. 그리디 불변식이 없으니 둘 다 Dijkstra가 아닌데, 둘 다 Dijkstra라고 부른다. ITE2039 쪽은 dist[start] = 0 초기화조차 파일에 없다.

할인당하면 아까운 것들

감사가 가혹했으므로, 각 보고서가 구체적 근거와 함께 칭찬한 것만 모았다. 형식적 칭찬은 제외했다.

CSE4006 · 2017

AtomicMarkableReference + CAS + 논리 삭제 마킹으로 Herlihy–Shavit lock-free 리스트를 정확히 이식했다. synchronized를 lock-free라 부르는 흔한 사기가 없다.

ITE4053 · 2019

직접 구현한 numpy 역전파를 수치미분과 대조해 최대 상대오차 1.86e-07. README의 실험 수치 7개가 소수점까지 재현된다 — 실험 기록이 정직하다는 뜻이다.

ITE4005 · 2018

DBSCAN이 정답 대비 Jaccard 0.97~1.00, core/border/noise 판정 정확. SVD 추천기에 데이터 누수가 없고 cold-start가 실제로 동작한다.

ENE4014 · 2017 · ITE4065 · 2017

버그를 스스로 발견해 고친 커밋이 남아 있다 — ε-폐포 방문검사가 상태집합에서 배열 인덱스를 찾던 오류(94ba7d6), 공유 난수 생성기 레이스를 캡처 방식으로 제거한 수정. 검증 습관이 없는 것이 아니라 일관되지 않은 것이다.

ITE4065 · 2017

네 과목 중 유일하게 perf로 병목을 먼저 측정하고, 개선 후 다시 측정해 제시했다. 가설 → 측정 → 개선 → 재측정 사이클을 실제로 돌린 유일한 사례다.