Skip to content

카고 컬트 프로그래밍과 적시학습

필요할때 그때그때

Makonea
··14분

카고 컬트 프로그래밍

프로그래밍 커뮤니티는 기이하게 저수준 코드에 대한 신앙이 있다.

응용 계층에서 일하는 사람에게 "그래도 근본은 알아야 한다"는 말이 반사적으로 나온다. OS를 알아야 하고 컴파일러를 알아야 하고 캐시 라인을 알아야 진짜라는 말이다.

사실 컴퓨터 공학과 과학에서 배우는게 대다수 ACM 계층에서 배우는 게 이런 지점이기때문에 더욱 그러한 신앙을 강화시킨다.

그리고 이 문제의 핵심은 '알면 좋다'라는 것이다. 이 알면 좋다라는 문제는 반박 불가능하다. 문제는 시간은 유한하고 아는것이 좋은 항목의 목록은 무한하다.

카고 컬트(화물신앙) 이란 무엇인가?

겉모습만 따라 하면 본질적인 결과도 생길 것이라는 맹신을 뜻한다.1
잘하는 엔지니어가 저수준을 아는 경우가 많다는 관찰에서, 그 지식이 어느 경로로 성과가 되는지를 묻지 않고 곧장 처방으로 건너뛴다. 활주로를 베끼면 비행기가 온다고 믿는 추론과 형태가 같다. 반증 조건이 없다는 점에서도 같다. 저수준을 몰라도 잘하는 사람은 더 잘할 수 있었을 사람이 되고, 저수준을 알아도 못하는 사람은 다른 문제가 된다. 이렇게 어떤 관찰로도 무너지지 않는 조언은 조언이 아니라 신념 표명이고, 신념 표명으로는 우선순위를 정할 수 없다.

그러니 물어봐야할 것은 무엇을 알아야하는게 아니라, 그 지식이 어느 경로로 값을 하느냐다. 경로를 특정하면 처방의 모양이 바뀌어야 하니까.

자료구조에서 가장 먼저 배우는 큐(Queue)로 이해해보자

Code
계약과 비용 모형   FIFO 순서를 보장한다
                   넣기와 꺼내기가 상수 시간이다
                   가득 찼을 때 무엇이 일어나는가, 막히는가 버리는가
                   여러 스레드가 동시에 만져도 되는가

구현               링 버퍼인가 연결 리스트인가
                   크기를 늘릴 때 어떻게 복사하는가
                   락을 쓰는가 CAS를 쓰는가

계약과 비용 모형은 그것을 언제 쓸지에 대한 문제고, 아랫칸은 그것을 만들 때 필요하다.

응용 어플리케이션 계층에서 하루종일 하는 것은 결정이지 제작이 아니다. 그러니 윗칸을 아는 것이 지식이고, 아래의 구현을 모르는것이 결함이 아니다.

여기서 계약과 비용 모형은 구현의 요약본이 아니다. 윗칸은 그 자체로 완결되는 지식이고, 윗칸은 아랫칸 없이 정확할 수 있다.

상수 시간이라는 사실은 링 버퍼를 짜 봐야만 알 수 있는 것이 아니며, 단순 측정만 해도 알 수 있는 사실이다. 근본을 알아야 한다는 조언은 이 둘을 자꾸 합친다. 뭉개면 결정에 필요한 지식을 제작 능력의 부산물처럼 취급하게 되고, 그 순간 학습 순서가 뒤집힌다.

추상화는 자본이다

표준 라이브러리와 프레임워크는 남이 이미 지불한 비용에 가깝다. 정렬, 해시 테이블, TLS 핸드셰이크, 스케줄러는 수십 년치의 검증과 최적화가 쌓인 결과다. 이것을 다시 만드는 것은 학습이 아니라 자본 파괴다.

자본 파괴가 무조건 나쁘다고 생각을 하진 않는다. 왜냐하면 내려온 유산을 파괴하고 새로운 걸 만들어내는 것이 나쁘지 않다. 가장 쉽게 유명세를 얻는 방법이기도 하니까. 문제는 그것이 자신이 저 라이브러리 계층으로 들어가겠다는 선언에서나 필요한 것이다.

ACM 학위와 커리큘럼, 컴퓨터 공학 학계에서는 이 기술들이 필요하다. 그리고 거대 기업의 인프라 계층에 종사한다면 필요한 기술일 것이다. 문제는 이게 모든 프로그래머의 경로가 아닌 것이다.

Brooks가 본질적 복잡성과 우연적 복잡성을 나눈 이유가 이것이며2, 도구가 좋아져서 줄일 수 있는 것은 우연적 복잡성이고, 문제 자체에서 오는 본질적 복잡성은 줄지 않는다.
설계자의 인지 자원이 가야 할 곳은 본질이다.
이 트래픽과 이 도메인에서 메시지 큐를 둘 것인가 트랜잭션 하나로 끝낼 것인가는 아무도 대신 해 주지 않는 판단이고, 큐를 손으로 짜는 일은 이미 누군가 해 두었다.

Norman의 구분으로 말하면 지식은 머릿속에만 있어야 하는 것이 아니라 세상에 놓여 있어도 된다.3문서, 시그니처, 타입, 검색 결과는 전부 세상에 놓인 지식이다. 머릿속에 옮겨 두어야 하는 것은 세상에서 꺼내 오는 비용이 너무 크거나 꺼내 올 시점을 놓치는 것뿐이다.

심지어 미리 배우는 방식은 실제로 잘 동작하지 않는다.

적시 학습은 차선책이 아니다. 성인이 도구를 익히는 실제 방식에 더 가깝다.

Carroll이 1980년대에 관찰한 것이 정확히 이것이다.4

사람들은 체계적인 교재를 순서대로 읽지 않는다. 바로 무언가를 시도하고, 막히면 그 지점에서만 읽고, 자기가 하려던 일과 연결되지 않는 설명은 건너뛴다. 이것을 나쁜 습관으로 보고 교재를 더 두껍게 만드는 대신, 시작 비용을 줄이고 실제 과제에 붙이고 실패에서 회복하는 경로를 넣는 쪽이 더 잘 작동했다고 말한다.

한 걸음 더 나가면, 사전 설명이 해롭기까지 하다. 초보자에게 도움이 되는 상세한 안내가 이미 아는 사람에게는 성능을 떨어뜨린다.5머릿속의 모형과 눈앞의 설명을 대조하는 일 자체가 부하이기 때문이다. 그래서 배울 준비가 되지 않은 시점에 밀어 넣은 저수준 설명은 저장되지 않는다. 저장되지 않는 정도가 아니라 그 시점에 배웠어야 할 것을 밀어낸다.

신입생에게 OS를 먼저 지우면 대개 이 일이 일어난다. 그 지식이 붙을 자리가 아직 없기때문이다.

문제는 이게 과거에 컴퓨터 시대의 교육 경로를 그대로 따라간다. 그저 경로 의존성인 것이다. 커널과 OS 개발자가 될 것이 아닌데도 불구하고 리눅스를 만드는데 OS를 만드는 개발자가 되는 방식을 따라가게 된다.

적시학습의 약점

물론 이 전략에도 약점이 있다.

찾아보려면 검색어, 즉 키워드가 있어야 한다. 검색어는 증상이 원인의 이름을 스스로 말해 줄 때만 나온다. 그리고 증상은 대체로 그러지 않는다.

가령 프로그래밍에서 에러가 나온다고 하자.

Code
이름을 말해 주는 증상    NullReferenceException
                         connection refused
                         컴파일 오류 CS8618
                         지금 바로 검색 가능

이름을 말해 주지 않는 증상 평소엔 40 ms인데 가끔 900 ms
                         부하가 높을 때만 응답이 뭉텅이로 늦음
                         스테이징에선 되는데 운영에선 안 됨
                         무엇을 검색해야 하는지가 답의 일부

이름을 말해 주지 않는 증상의 오른쪽이 적시학습의 약점이다. GC의 정지 시간인지, 커넥션 풀 고갈인지, 캐시 라인 공유인지, 인덱스가 안 타는 것인지를 이미 후보로 떠올릴 수 있어야 검색이 시작된다. 후보 목록 즉 키워드가 없으면 검색창 앞에서 멈춘다.

이것이 왜 어려운지에 대한 오래된 측정이 있다. 사람들에게 같은 대상을 부르는 이름을 스스로 대게 하면 두 사람이 같은 단어를 고를 확률이 20퍼센트를 넘지 않았다.6

이름을 미리 알지 못하면 그 이름으로 색인된 자료에는 도달할 수 없는 것이다. 검색 엔진이 좋아져도 이 격차는 줄어들 뿐 사라지지 않는다.

하지만 이게 효율이 좋으냐는 부차적 문제이다. 1의 문제를 대비하기위해서 10의 문제를 준비한다는 것은 사실 거의 불가능하다.

그러므로 저수준 지식의 실제 쓰임은 문제를 푸는 것이 아니라 후보를 떠올리는 것 정도면 충분하다.

자신이 프로그래밍하며 마주하는 기이한 현상들을 검색창에 입력할 '정확한 명사(Exact Noun)'를 획득하는 것. 그것이 응용 계층 엔지니어가 저수준 지식에 투자해야 할 유일하고도 완벽한 이유다. 구현 능력이 아니라 현상의 이름을 아는 것만으로도 디버깅의 비용은 10에서 1로 수렴하기 때문이다

필요한 것은 GC를 구현할 능력이 아니라, 응답 시간 분포에 이상한 꼬리가 보일 때 GC가 후보 목록에 들어오는 것이다. 자신이 있는 프로그래밍 레이어를 결정하고 그 바로 아래 레이어 정도만 훑어봐도 된다.

그럼에도 불구하고, 왜 그들은 진짜 프로그래밍의 허상을 만드는가?

그들이 존경받기 때문이다.
OS, 컴파일러, 데이터베이스를 밑바닥부터 닦아낸 선구자들의 작업은 압도적으로 훌륭하며 마땅히 존경받아야 한다.

하지만 커뮤니티는 이 '존경'을 뒤틀어, 기계와 가까울수록 우월하다는 거대한 '상징적 자본(Symbolic Capital)'을 만들어냈다.

문제는 이 상징적 자본이 시장에서 창출하는 실질적 비즈니스 가치와 일치하지 않느냐는 다른 문제다. 응용 계층에서 비즈니스 문제를 해결하여 현금을 창출하는 능력보다, 직접 메모리를 할당하고 포인터를 다루는 '어려움' 그 자체를 훈장처럼 여긴다. 목적(가치 창출)과 수단(저수준 제어)이 역전된 것이다.

초기 시대의 엔지니어들은 누수되는 추상화 속에서 생존하기 위해 저수준 지식을 강제로 뇌에 새겨야만 했다. 그들의 생존기는 무용담이 되었고, 곧 커뮤니티의 '통과 의례'로 굳어졌다. 이것은 일종의 종교화이자 지적 허영이다. 남들이 만들어둔 훌륭한 추상화(자본)를 레버리지 삼아 빠르게 비즈니스 로직(Application)을 구축하는 사람들을 깎아내림으로써, 자신들이 과거에 지불했던 막대한 학습 비용을 보상받으려 하는 심리라고도 할 수 있다. 진짜 프로그래밍이라는 허상은 결국 낡은 기득권의 사다리 걷어차기일 뿐이다.

그들의 프로그래밍도 프로그래밍이지만, 응용 계층 위에서 남들을 신뢰해서 만들고 사용자에게 만족감을 주는 프로그래밍도 프로그래밍이다.

다른 게임을 해야만한다. 그들과 같은 게임을 하는 순간 특출난 재능이 없다면 그 게임에서 20년,30년이 넘는 개발자들과 싸움을 할 수 없다.

왜 기울어진 운동장에서 게임을 하려고 하는가?

이것이 응용 계층 엔지니어가 취해야 할 하향식(Top-down) 생존법이다.
바닥의 원리를 모두 깨우친 뒤에야 무언가를 창조할 수 있다는 것은 학계의 낡은 환상이다.

현실의 우리는 프레임워크나 엔진 위에서 거대한 블랙박스를 먼저 휘두르며 결과를 만든다. 그러다 블랙박스의 한계에 부딪혀 추상화가 찢어지는 그 틈새가 보일 때, 비로소 그 틈을 통해 하위 계층(렌더러, 그래픽스 API)으로 깊게 파고들면 된다.

모든 것을 미리 아는 것이 아니라, 필요가 발생한 그 순간에 필요한 만큼만 깊어지는 것(Just-in-Time Deep Dive).

시간은 유한하다는 것을 인지해야만 한다.

카고 컬트 프로그래밍에서 벗어나기

저수준 지식이 쓸모 없다는 것은 카고 컬트 프로그래밍이다. 중요한 것은 다른것이다. 내가 어느 영역에 이름 있고 싶어지는지를 선택하고 그 계층에 맞는 기술을 익혀야한다.

큐를 구현하는 사람에게는 링 버퍼와 CAS가 본업이고, 컴파일러를 만드는 사람에게는 ABI와 메모리 모델이 응용 지식이다. 렌더러를 만드는 사람에게는 GPU 파이프라인이 추상화의 밑바닥이 아니라 매일 다루는 작업 표면이다. 지식의 높고 낮음은 절대적인 것이 아니라 자신이 서 있는 계층에 따라 달라진다.

문제는 특정 계층의 지식을 모든 프로그래머가 반드시 거쳐야 할 보편적 통과 의례로 만드는 데 있다.

배워야 할 것은 무한하지만 시간은 유한하다. 그러므로 학습에는 선택 기준이 필요하다.

그 지식이 현재의 결정에 직접 필요한가.
반복해서 나타나는 현상에 정확한 이름을 붙여 주는가.
사용 중인 추상화가 무너졌을 때 한 계층 아래로 내려갈 수 있게 하는가.
아니면 단지 커뮤니티에서 근본적으로 보이게 해 주는가.

앞의 세 가지라면 배울 가치가 있다. 마지막이라면 그것은 기술 투자가 아니라 상징적 소비에 가깝다.

응용 계층 엔지니어에게 필요한 것은 모든 하위 계층 정복이라면 수 많은 홈페이지들과 프로그램들이 탄생했겠는가? 평소에는 잘 만들어진 추상화를 자본처럼 사용하고, 이상한 현상이 나타났을 때 그 현상의 정확한 명사를 떠올리고, 필요한 만큼만 아래로 내려갈 수 있으면 된다.

이것은 무지를 찬양하는 태도가 아니다. 오히려 자신의 무지와 시간의 한계를 인정하고, 어디에 깊어질지를 의식적으로 선택하는 태도다. 그것이 프로그래머의 태도여야만 한다.

프로그래밍의 근본은 기계와 얼마나 가까운 곳에서 일하느냐가 아니다. 자신이 맡은 계층에서 어떤 결정을 내려야 하는지 알고, 그 결정이 실패할 때 어느 경계를 의심해야 하는지 아는 것이다.

그러므로 먼저 자신이 어떤 영역에서 이름을 얻고 싶은지를 정해야 한다. 운영체제인지, 데이터베이스인지, 게임 엔진인지, 응용 제품인지에 따라 배워야 할 근본도 달라진다.

바닥까지 내려가야 프로그래머가 되는 것은 아니다. 자신이 서 있는 층에서 가치를 만들고, 바닥이 드러나는 순간 정확히 내려갈 수 있으면 된다.

남들이 내리는 판단 유무에 자신의 가치를 맡기지 않으면 그것으로 충분하다.

각주

  1. Richard P. Feynman, "Cargo Cult Science," Engineering and Science, vol. 37, no. 7, pp. 10-13, June 1974. 캘리포니아 공과대학 1974년 졸업식 연설. 전문
  2. Frederick P. Brooks, Jr., "No Silver Bullet: Essence and Accidents of Software Engineering," IEEE Computer, vol. 20, no. 4, pp. 10-19, April 1987. DOI: 10.1109/MC.1987.1663532
  3. Donald A. Norman, The Design of Everyday Things, Basic Books, 1988. 개정판 2013. 머릿속의 지식과 세상에 놓인 지식의 구분은 기억 장에서 다룬다.
  4. John M. Carroll, The Nurnberg Funnel: Designing Minimalist Instruction for Practical Computer Skill, MIT Press, 1990. ISBN 9780262031639.
  5. Slava Kalyuga, Paul Ayres, Paul Chandler, John Sweller, "The Expertise Reversal Effect," Educational Psychologist, vol. 38, no. 1, pp. 23-31, 2003. DOI: 10.1207/S15326985EP3801_4
  6. George W. Furnas, Thomas K. Landauer, Louis M. Gomez, Susan T. Dumais, "The Vocabulary Problem in Human-System Communication," Communications of the ACM, vol. 30, no. 11, pp. 964-971, 1987. DOI: 10.1145/32206.32212