Logo
(번역) 컴퓨터는 이해가능하다

(번역) 컴퓨터는 이해가능하다

Kihyo Kihyo
October 19, 2025
15 min read
index

얼마 전 웹서핑을 하다가 현재 앤트로픽에서 엔지니어이자 연구원으로 근무하고 있는 Nelson Elhage가 쓴 Computers can be understood라는 글을 봤다. 다 읽고나니 이 분 마인드가 내가 평소에 CS 공부할 때랑 너무 비슷해서 공감이 가고 아직 CS 뉴비인 나한텐 굉장히 도움이 되는 한편, 이 마인드가 어떠한 단점을 또한 가져다주는지 잘 얘기하는 것 같아 (사실 읽으면서 뜨끔하는 게 많았음) 나만 알기엔 아까워서 이렇게 번역해서 올려본다.

혹시나 오역 및 CS 용어에 문제가 있다면 언제든 알려주시면 감사하겠습니다.


컴퓨터는 이해가능하다.

서론

본 포스트에서는 그 동안 제가 해왔던 모든 소프트웨어 엔지니어링 작업들을 하면서 깨닫고 갖게된 기본적 마음가짐에 대해 얘기해보고자 합니다. 이 마음가짐에는 함의점도 있고 장점도 있지만, 동시에 제 스스로를 함정에 빠트린 경우들도 있기에 이러한 것들에 대해 상세히 말해보려고 해요.

소프트웨어는 이해가능하다.

저는 컴퓨터와 소프트웨어는 이해가능하다라는 믿음을 마음 깊숙히 자리시키고 소프트웨어에 접근합니다. 제게 있어 이러한 믿음은 무슨 심오하고 이론적인 표명이라기보단, 제가 (컴퓨터에 대해) 물어볼법한 질문은 마음만 먹고 살펴보고 또 배우다보면 결국 이해가능한 답을 얻을 수 있다는 마음 속 깊은 곳에서 우러나오는 믿음같은 거라 볼 수 있습니다.

어떻게 보면 이런 믿음은 요즘 관점에선 급진적으로 보일 수도 있습니다. 오늘날 소프트웨어와 하드웨어 시스템은 수많은 계층(layer)으로 이루어져있고, 이 계층들은 서로 독립적이면서도 동시에 서로가 서로에게 겹겹이 쌓여있는 형태로 이루어져있기에 거의 상상조차 불가한 복잡도를 담고 있죠. 웹 애플리케이션을 예를 들어 갖고 얘기해보죠. 트랜지스터부터 실리콘까지의 마이크로 아키텍처부터, CPU 명령 집합, OS 커널, 유저 라이브러리, 웹브라우저 자바스크립트 가상머신, 자바스크립트 라이브러리, 애플리케이션 코드에 이르기까지, 더군다나 코드를 실행시키기 위해 사용되는 모든 관련 네트워크 서비스까지 말하자면 입이 아플 정도입니다. 이런 복잡도를 맞닿게 되면, 배워야할 것이 너무나도 많아서 사실상 우리가 사용하는 소프트웨어 및 하드웨어 시스템들은 마치 구체적인 이해를 얻기 어려운 블랙박스처럼 받아들이는 게 쉬울 정도입니다.

하지만 저는 이러한 사고방식에 반대합니다. 비록 모든 계층에 이루어진 구체적인 구현적 디테일들을 모두 이해할 수는 없더라도, 적어도 어느정도 추상적인 수준에서 바라본다면 어느 계층에서든 간에 그 목적에 필요부합할만큼 깊게 모두 이해할 수 있다고 봅니다.

컴퓨터는 마법이 아니다.

컴퓨터는 본질적으로 엄격한 규칙들을 순차적으로 따르는 결정기반체(deterministic foundations)로 (대부분) 이루어진 집합입니다. 우리는 이러한 집합을 기반으로 추상 기반의 계층들을 쌓고 또 쌓으며 이렇게 쌓인 각각의 계층들은 다시 한 번 똑같은 방식으로 추상 기반 계층들을 쌓아올림으로써 (대부분) 재현가능하고 결정기반 처리들을 수행하게끔 하죠.

따라서 컴퓨터는 마법이 아닙니다. 즉, 우리가 알고있는 논리체계를 뛰어넘어 명령들을 수행함으로써 작위적이고 예측할 수 없는 결정을 해대는 미지의 악마들을 조우하게 하는 계층같은 건 없다는 겁니다. 그러므로 단일 계층에서 이루어지는 대부분의 수행들은 그 다음 계층에 있는 개념들을 통해 대부분 이해될 수 있으며, 모든 수행들은 여러 계층들을 충분히 파봄으로써 이해될 수 있습니다.

소스(코드), 문서화, 역설계

현대 컴퓨터 시스템들 중 상당수는 오픈소스이며 그 구현체들을 살펴봄으로써 직접적으로 이해할 수 있습니다. 어떤 개발자가 Ruby on Rails 기반 앱을 MySQL DB에 연결해 리눅스 서버를 통해 배포한다고 해봅시다. 이를 위해 사용된 소프트웨어들은 모두 오픈소스 기반일 것이고 필요하다면 어떤 식으로 구현됐는지 살펴볼 수 있을 겁니다.

시스템이 오픈소스화 되어있지 않아있다면 종종 굉장히 구체적이고 심도있게 작성된 문서가 존재하는 편입니다. 앞서 말한 개발 예시에서 오픈소스화가 되어있지 않은 경우라면 그 첫번째는 바로 인텔 x86 프로세서와 같은 하드웨어겠죠. 그러나 인텔은 자사 프로세서 제품들에 대한 수천 페이지 짜리 문서를 공개해놓았고, 직접 코드를 실행시키면서 인텔 프로세서들이 구체적으로 어떻게 작동되는지 하나하나 살펴보는데 있어 더할나위 없이 좋은 자료로서 활용되고 있습니다.

혹여 소스(코드)와 문서를 볼 수 없거나 불충분하더라도 마음만 먹다면 실험해보고 뜯어봄으로써 시스템을 역설계해볼 수 있습니다. 종종 누군가 그런 걸 이미 해놓았을 거고 또 공개해 놓았을 겁니다. 특히나 보안쪽 엔지니어들이 이러한 작업에 가장 능숙한 편이죠. 개인적으로 되게 좋아하는 예시가 두 개가 있는데, 하나는 구글의 프로젝트 제로 팀의 인텔 하스웰 마이크로아키텍처 브랜치 예측기(Haswell microarchitecture’s branch predictor) 역설계이고 다른 하나는 사파리 웹킷 힙(Webkit heap) 문서1입니다.

이러한 사례들은 수년의 경험으로 다져진 업계 최고의 엔지니어들이 엄청난 노력을 통해 이루어낸 결실입니다. 물론 제가 저런 역설계를 어디에서나 효율적으로 할 수 있다고 얘기하는 건 아닙니다. 그러나 제게 있어 저러한 결과물들이 있다는 것은 첫째로 이러한 작업들을 내가 정말로 원한다면 분명 할 수 있는 것이고, 둘째로는 종종 누군가 이미 해놓았고 그걸 공개해놓았기에 그걸 이용하면 되므로 제가 직접할 필요가 없다는 것 두 가지를 증명한다는 거죠.

컴퓨터는 이해될 수 있다는 마음가짐이 보여주는 것

의존성 이해

어쩌면 이러한 마음가짐 또는 비슷한 사고방식이 가장 직접적으로 보여줄 수 있는 것은 바로 제가 살펴보는 시스템이 어떤 방식으로 의존되고 있고 작동되고 있으며, 또 어떻게 구현되어있는지 살펴보고 배우는데에 시간을 쓰게 된다는 것입니다. 만약 어떤 라이브러리나 프레임워크를 갖고 중요한 애플리케이션을 설계하고 있다면 저같은 경우엔 거의 항상 그 라이브러리 또는 프레임워크의 소스코드를 살펴볼 것이며, 이상수행이 나타나 디버깅이 필요하다든가 관련 문서에 내가 알고자 하는 답이 없다면 매번 소스코드를 파볼 겁니다.

디버깅

위와 같은 습관을 가진 채 제가 설계하는 시스템이 지닌 의존성에 대해 많은 것을 알게 되는 건 분명 까다로운 디버깅을 하는데 있어 엄청난 도움이 됐습니다. 무슨 도구가 되었든 간에 그 도구를 충분히 장시간 동안 사용해왔다면 그 도구를 갖고서 버그들을 거뜬히 해결할 수 있는 법입니다. 그리고 최소한 도구가 제공하는 추상화 수준에서 버그를 정확하게 기술하고 진단할 수 있는 능력은 매우 중요하죠. 그래야 구체적인 버그 리포트나 재현가능한 최소의 사례를 만들 수 있기 때문이니깐요.

가장 까다로운 버그는 여러 계층에 걸쳐 있거나, 계층 간 추상화 경계가 제대로 분리되지 않았을 때 생기는 경우가 잦습니다. 이러한 버그는 단일 추상 스택 계층에서 이해하기가 불가능한 경우가 흔하며, 이를 완전히 이해하기 위해 여러 추상 수준의 관점에서 바라봐야 할 능력을 요구할 때도 가끔 있습니다. 실제로 이를 위해 본인 부서나 팀에서 여러 스택 층들을 바라보는데 익숙한 사람을 찾아갈 필요가 있을 때, 그 사람에게는 가장 좋아하는 무용담같은 이야기를 만드는 데 있어 좋은 도전거리로 종종 받아들여지곤 하죠.

개인적으로 그런 저만의 무용담들 중 하나는 바로 데스크탑에 일어난 단일비트 메모리 플립(flip)을 추적해 확인하는 거였습니다. 이러한 문제들은 유저스페이스 라이브러리, 커널, 파일시스템과 하드웨어를 오가는 이해를 잘 하고 있지 않다면 이해조차 하기 힘든 것들입니다. 제가 얘기했던 tumblr 디버깅 무용담 또한 다른 좋은 예시들을 보여주죠.

문서화

소스코드를 기꺼이 파보려는 의지와 기술은 문서에 덜 의지하게끔 해줍니다. 만약 문서가 어딘가 부족한 지점이 있다면 언제든 소스코드를 들여다봄으로써 제대로 된 답을 얻을 수 있게 되죠. 이러한 방식으로 질문을 답할 수 있는 능력은 팀원들에게 굉장히 좋습니다. 왜냐하면 정말로 잘 정리된 문서조차도 빈틈이 있는 경우가 있기 때문이니깐요.

하지만 반대로 단점도 있습니다. 친숙하지 않은 소스코드를 읽는데 꺼리낌 없는 (제 자신을 포함한) 엔지니어들은 문서화를 습관적으로 평가절하 하거나 (문서가 없이도 답을 얻을 수 있기 때문임) 심하면 평범한 엔지니어보다 자신이 관리하는 시스템에 대한 문서화를 못할 때도 있기 때문입니다.

보안

보안 이슈를 이해하려고 하면 종종 여러 추상적 계층에서 작업해야 합니다. 시스템을 공격하는 해커는 문서화되거나 어떤 계층에서 일어나야할 의도된 동작에 구속되지 않으며, 특히 계층이나 입력 차원에서 사양을 벗어날 때 시스템이 실제로 어떻게 작동하는지 파고들려 합니다. 따라서 이러한 해커의 의도를 알아차려야하죠. 예컨대 C specification은 버퍼 오버플로우를 단지 정의되지 않은 동작(undefined behavior) 이라고만 규정되어 있습니다. 하지만 이를 원격 코드 실행으로 악용하는 방법을 알아내거나 ASLR(Address Space Layout Randomization, 주소 공간 레이아웃 랜덤화), DEP(Data Execution Prevention, 데이터 실행 방지) 같은 대응책의 효과를 논리적으로 따져보려면, 컴파일러와 libc, 기반 하드웨어 등 C specification의 추상적 정의가 실제로 어떻게 구현되어 있는지에 대한 깊은 이해가 필요합니다.

Ksplice에 있었을 때 보안특성을 위주로 리눅스 커널을 위한 무중단 배포(zero-downtime) 소프트웨어 업데이트 제품들을 판매하면서 제 커리어는 보안쪽에 큰 비중을 갖고 시작됐었습니다. 현재 제가 갖고 있는 스킬들과 코드들을 깊게 파는데 꺼리낌을 느끼는 않는 방식 또한 그곳에서 보안 관련 버그 이슈들을 해결하면서 모든 추상 계층들을 이해하면서 배운 겁니다.

성능

소프트웨어의 성능을 파악하고 추론하는 것 또한 다양한 계층을 이해하기를 요구합니다. CPython(또는 PyPy) 구현에 대한 이해 없이는 효율적인 파이썬 코드를 작성하기 어려우며, 생성된 코드와 하드웨어에 대한 이해 없이는 효율적인 캐시 과정을 보여주는 C 코드 작성은 불가능합니다. 그러니 성능을 중시하는 엔지니어들이 모이는 곳을 파고드십시오. 수많은 추상 계층들을 언제나 더 깊게 이해하려는 수많은 엔지니어들을 결국엔 찾게 될 겁니다.

사고모형(mental models) 구축하기

소프트웨어 스택이 담고 있는 기저 계층들을 배우고자 하려는 습관은 소프트웨어가 담고 있는 기저 시스템이 어떻게 이루어져 있는지 사고모형을 구축하려는 습관과 깊게 연관되어 있습니다. 저같은 경우엔 언어, 라이브러리, API 등과 같은 어떤 시스템이 있다면 그것을 단순 규칙 및 수행과 엣지케이스의 집합체로 이해하려하기보단, 그 시스템의 핵심요소들과 더 큰 차원에서의 시스템 차원에서의 수행을 생성해내는 규칙과 원리들을 구성하는 작은 모형으로서 이해하려 합니다.

구체적인 사례를 얘기해보면 그 동안 저는 수백 수천 개의 bash 쉘 스크립트를 작성해왔습니다. 어느 순간부터인가 구체적인 패턴과 안티패턴을 계속 외우기보다는, 커맨드 라인이 처리 될 때 다양한 쉘 확장 단계들이 어떠한 맥락에서 어떤 순서로 적용되는지 이해하기 위해 한 발자국 물러서 bash 문서를 읽기 시작했습니다. 물론 이 문서를 읽는 게 내게 쉘 스크립트의 디테일한 부분들을 신경써가며 쓰는 걸 배워야한다는 부담을 줄여주진 않았죠 (사실 일이 더 생깁니다). 그럼에도 그렇게 관련 지식을 얻음으로써 해당 디테일들을 더 쉽게 이해하고, 새로운 문제나 코드 패턴을 맞닿게 될 때 이를 더 설명력 있게 해주었습니다.

저는 위와 같은 예시가 컴퓨터는 이해가능하다라는 믿음과 관련되어 있고 또 그 믿음을 더 굳게 만들어준다고 생각합니다. 컴퓨터 시스템이라는 것은 음, 말 그대로 시스템스러우며 이해가능한 어떤 핵심 대수(algebra) 또는 논리체계로 이루어져 있습니다. 그리고 이 핵심 요소들은 컴퓨터가 보여주는 가능한 모든 수행 가지수보다 적지만 역설적이게도 이 모든 수행을 생성시키거나 최소한으로 구성할 수 있게 해주죠. 또한 이러한 기저 시스템을 이해하는 시간을 투자하는 것이 시스템을 다루는데 있어 결국 보상을 받을 수 있으리라 믿습니다.

싱글샷 디버깅

소프트웨어가 이루고 있는 시스템에 대해 좋은 사고모형을 갖게 되면 추가적으로 얻는 이점이 하나 더 있습니다. 특히나 그 시스템이 대부분 결정기반이라면 특정 시점에서 일어나는 수행 중 일부만 보고서도 프로그램의 상태에 대해 꽤나 구체적인 추론을 이끌어 내는 것이 가능하다는 겁니다. 예컨대 극단적인 경우 단일 버그 하나가 시스템의 근간을 건드리는 버그로서 일어날 때가 있습니다. 이럴 때 시스템에 대한 풍부한 사고 모형과 코드가 있다면 ‘아, 이 필드가 NULL로 세팅이 되어있었으면 누가 분명 이렇게 세팅했겠지…이런 세팅을 하는 필드는 여기, 저기, 거기가 있을테고 그 중에 여기랑 거기만 NULL argument를 받겠구나’ 하는 방식으로 거꾸로 추론하는 것이 가능하죠.

한 방에 버그를 해결하지 못하더라도, 시스템이 어떻게 작동하는지 관찰함으로써 이론과 가설을 세우고 사고모형을 다질 수 있는 방법이 있습니다. 이러한 방법은 더 구체적인 질문을 하게 해주고, 디버깅 툴이나 출력 함수를 이용하거나 코드를 읽어서 테스트해볼 수 있게 하며, 나아가 결국 사고모형을 좀 더 탄탄히 할 수 있게 해주죠. 제 시간에 맞춰 이러한 사고들을 순차적이나 역순으로 할 수 있게 되는 건 디버깅과 시스템이 어떻게 돌아가는지 배우는데에 매우 큰 도움이 된다는 것입니다.

커널 엔지니어링에서 싱글샷 디버깅

복잡하고 비결정적인 시스템일수록 몇 번의 관찰만으로 시스템이 어떻게 동작할지 예측하는 건 더 힘들어지는 법입니다. 특히나 오늘날 분산시스템에서 관찰가능성에 대해 일부 설명한다고 생각하는데, 이러한 시스템들이 어떻게 동작하는지 제대로 이해하려면 더욱 더 많은 데이터가 필요하기 때문이죠.

하지만 이러한 사실은 시스템 엔지니어, 특히 커널 엔지니어한테는 이미 넓게 알려진 겁니다. LKML 올라온 스레드들이 그런 경우들이죠. 누군가 스택 트레이스와 레지스터 덤프와 함께 단일 크래쉬 로그를 포스트하면 시니어 커널 엔지니어들이 여러 명이 서로 협력하면서 버그가 어디서 일어나는지 추적한 뒤, 레지스터 덤프와 컴파일 코드 간 맵핑이 어떻게 이루어져있고 커널 내부에서 데이터 구조가 어떻게 작동되는지에 대한 이해를 바탕으로 버그의 원인을 구체적으로 추적하는 겁니다.

오라클에서 근무했을 때 솔라리스의 커널 엔지니어들과 담소를 나눌 기회가 있었는데 그들은 위에서 말한 접근방식에서 한 발자국 더 나아간다는 것을 알게 됐습니다. 바로 단일 크래쉬 리포트만으로 커널이 크래쉬되는 근본 원인을 100% 규명한다는 것을 목표로 삼는다는 거였죠. 이를 위해 그들은 구체적이고 섬세한 크래쉬 리포트 방식과 디버그 툴을 개발했습니다 (단순히 버그를 복잡하게 다루는 게 아니었습니다). 이러한 것은 근본적으로 이러한 목표가 시스템이 복잡하더라도 이해가능하며 대부분 결정기반적이다란 믿음과 동시에 실제로 그렇게 할 수 있다라는 믿음에서 우러나온 것이라 생각했고, 이는 꽤나 영감을 주는 것으로 바라봤죠.

제가 보기엔 많은 경우 이러한 마음가짐을 가지는 사람들 중 상당수가 커널 개발자라고 생각합니다. 오늘날 가상화 기술이 있기 십년 전까지만 해도 커널 크래쉬를 디버깅하려면 크래쉬 덤프나 로그 트레이스를 살펴보는 것만이 유일한 방법이었고, 심지어 디버거도 없어서 이를 계속 수행시킬 여력조차 없었기 때문에 예삿일도 아니었을 겁니다. 또한 OS 커널은 결국 하드웨어와 가장 가까운 소프트웨어이기 때문에 코드가 어떻게 동작하고 상호작용하고 있는지 전부 설명하기 위해선 계층의 수가 비교적 작았을 겁니다. 따라서 그들에게 있어 필요한 건 대부분 C 컴파일러 및 컴파일된 코드와 하드웨어를 이해하는 거였을 겁니다. 그러나 커널 윗단의 유저스페이스에서 개발하는 사람들은 필연적으로 훨씬 더 많은 계층을 거쳐야 할테죠.

컴퓨터는 이해가능하다는 마음가짐의 함정

이러한 마음가짐은 지금까지 얘기한 여러 이점을 가져다주지만 동시에 단점 또한 있으며 이 섹션에선 그러한 것들을 이야기하고자 합니다. 크게 봤을 때 소프트웨어를 근본적으로 이해가능하며 구체적인 사고모형을 세우고자 한다는 믿음은 제게는 꽤나 잘 통했지만, 이 방식이 가장 유용하고 가치있는 접근법만은 아니며 그 나름대로 단점이 있다는 점 또한 분명히 하고자 합니다.

이해의 필요성

소프트웨어 시스템은 이해가능하다라는 믿음은 사실 본인이 다루고있는 시스템을 이해해야한다라는 당위성을 쉽게 갖도록 만들어버린다는 점이 있습니다. 저같은 경우엔 기저 계층에 대한 적절한 사고모형을 갖기 못한채 소프트웨어 시스템을 이해하는 것이 굉장히 불편하게 다가오고, 가끔은 이러한 불편함이 제가 이루고자 하는 목표를 달성하는데 있어 해가 될 때가 있습니다.

예컨대 저는 단순히 튜토리얼 한 두 개를 하거나 거기서 나온 예시를 갖고 작고 지엽적인 작업만 한 뒤에 복잡한 시스템을 다루기 시작하는 것을 굉장히 어려워합니. 제가 작업하고 있는 모든 컴포넌트들의 역할과 그 관계들을 적어도 높은 수준에서 이해하기 전까지 저는 계속 불편함을 느끼곤 하죠. 좀 더 구체적인 예시를 들어 얘기해보면, 언제는 한 번 (그 때 당시에는 단 한 번도 다루어보지 않았던) Amazon Lambda를 갖고 단일 HTTP 엔드포인트를 구성하는 중이었습니다. 이에 대한 튜토리얼들은 AWS든 어디서든 쉽게 찾아볼 수 있었죠. 지금 생각해보면 그 중 아무거나 본 다음 그냥 계속 시도해보고 에러들을 고쳐가며 했더라다면 대충 30분 내에 끝냈을 작업이었을텐데, 정작 제가 했던 건 필요한 컴포넌트들을 하나하나 이해해가며 밑바닥부터 시작하기를 고집했던 거였습니다. AWS에 이루어지는 어떤 작업이든 간에 대충 15개의 서로 다른 자사 제품들이 거미줄처럼 이어져있었기에 결국 저는 30개의 문서 탭을 창에 띄워놓았고 제가 만든 엔드포인트는 무슨 방법을 써도 에러만 보여주었고, 무엇보다 도대체 지금 뭐가 어떻게 돌아가고 있는지 조차 이해가 안 갔던 상태가 되어버렸습니다. 저는 끝내 포기하고 말았고 그 문제는 중요한 게 아닐 거라고 단정지어버렸죠.

물론 더 많은 시간과 참을성이 있었다면 Amazon Lambda와 다른 관련 제품들에 대해 꽤나 깊은 이해를 갖게 됐을 거고, 이후 겪을 버그나 관련 문제들을 해결하는데 도움이 됐을테죠. 하지만 그 때 당시 제 목적은 그게 아니었다란 겁니다. 저는 그저 뭔가 돌아간다는 걸 보여준다는 결과물을 제한된 시간 내에 원했을 뿐이었지만, 결국 어느 하나도 돌아가는 게 없었고 그 중 어느 것에 대해서도 제대로 된 이해조차도 하지 못했었죠. 이렇듯 굳이 모든 것을 이해할 필요가 없는 문제를 갖고서 이 문제가 보여주는 복잡한 시스템을 근본적으로 이해하려는 필요성은 매혹적이면서도 동시에 해를 가져다줄 때가 있습니다.

쉬운 것부터 시작하기

그리고 이런 적도 몇 번이나 있었는지 기억조차 안 날 지경입니다. 의존성을 지닌 버그를 맞닿았을 때, 그 의존성을 깊게 이해해서 버그가 정확히 무엇인지 규명하고 고립시키곤 했는데 결국 버그는 이미 상위 버전에선 해결된 거였고 계속 구버전에 갇히곤 했던 적들 말이죠. 아니면 디버그 심볼 없이 빌드된 바이너리에서 발생한 크래쉬를 디버그 하려고 코어덤프와 x86 disassembly를 꼼꼼히 보며 시간을 붓고 있을 때 동료가 디버그 빌드를 찾아 크래쉬를 재현하고 잘 작동하는 gdb 세션이란 푸르디 푸른 초원을 유유히 거니는 걸 봤을 때라든가요.2

물론 저는 이진 기반 역설계를 통해 친숙치 않은 코드를 빠르게 이해하고 작업할 수 있게 해주는 소프트웨어 및 시스템 관련 기술을 갖고 있지만, 이는 제가 작업하고 있는 시스템을 어떻게든 이해하겠다라는 집착과도 같은 욕망이 일부 관여해 얻게 된 겁니다. 하지만 이러한 기술들이 항상 주어진 문제를 풀어줄 수 있는 올바른 기술은 아니라는 걸 저는 말하고 싶네요!

따라서 대부분의 경우 쉽게 해볼 수 있는 것부터 시작하는 게 더 낫고 (예: 의존성 업그레이드, 디버거 이용하기, 예시 몇 개를 갖고 돌려보기) 이러한 접근법이 제대로 통하지 않았을 때 더 깊게 가는 게 좋습니다.

호기심을 갖고 시작하기

컴퓨터는 이해가능하다는 마음가짐을 가지려면 어디서부터 시작해야하고 익숙치 않은 시스템을 이해하는데 필요한 구체적인 기술을 갖기 위해선 어떻게 해야하는지에 대한 생각으로 이제 글을 마무리하고자 합니다. (현 시점에서) 제가 배운 툴킷들은 지난 20여년의 세월 동안 컴퓨터를 다루면서 얻게된 것들이다. 따라서 혹여 이러한 마음가짐은 저같은 경험을 지닌 사람들만이 가질 자격이 있는 것처럼 보일까 내심 걱정이 됩니다. 물론 이 포스트에서 쓴 것들은 제가 직접 경험한 것을 바탕으로 쓴 거지만, 컴퓨터가 이해가능하다란 마음가짐은 이 글을 읽고있는 여러분의 경험이 얼마나 됐든 간에 상관 없이 가질 수 있다고 저는 굳게 믿습니다.

가장 먼저 이 포스트에서 제가 실용적으로 줄 수 있는 조언은 바로 본인이 다루고 있는 시스템에 대해 깊은 호기심을 갖고 파라는 겁니다. 시스템이 어떻게 작동하는지, 왜 그리고 어떻게 그 시스템이 작동하는지 고민해보세요. ‘나라면 이 라이브러리를 어떻게 만들었을가?‘와 같은 질문을 하고, 답이 무엇인지 알 수 없어보이는 틈을 찾아 배울거리로 삼아보세요.

좀 더 실용적인 예시를 들어 조언을 원한다면 저라면 이렇게 할 것 같습니다. 여러분이 지금 삼고 있는 의존성(라이브러리 등)의 소스코드를 읽으세요. 만약 이런 습관이 아직 길들여있지 않다면 말이죠. 리액트에서 웹 앱을 작성하고 있나요? 가끔씩 소스코드를 한 번 붙잡고 쭉 읽어보세요. 장고나 레일즈 기반 웹 앱 작업을 하나요? 해당 프레임워크의 소스코드에 질문을 남겨가며 읽어보세요. 심지어 파이썬이나 루비가 어떻게 구현되어 있는지 잠깐만 살펴봐도 됩니다. 많은 경우 어떤 언어의 표준 라이브러리들은 그 언어로 쓰였기 때문에 무언갈 더 많이 배워야한다든가, C 계열 언어를 배울 필요 없이 라이브러리 소스코드들을 읽을 수 있습니다. 굳이 소스코드를 전부 이해할 필요도 없습니다. 그냥 내일 또 보면 더 이해할 수 있을 거라는 자신감을 갖고 단순히 이해를 쌓는다는 마음으로 하면 됩니다.

소프트웨어 시스템을 더 깊게 배운다는 것은 기존에 알고 있던 것들을 삼아 그 위에 새로운 걸 쌓는 기술과도 같습니다. 많은 시스템들을 갖고 작업할수록 앞으로 작업하게 될 것들에 적용시킬 패턴들을 알게될 거고 또 나중에 겪을 문제들을 해결할 때 사용할 기술, 트릭들을 쌓게 될 겁니다. 어쩌면 처음부터 매우 복잡한 시스템을 이해하는 거 자체가 과한 것처럼 보여도, 항상 이해하려고 시도하면 할수록 더 쉬워지는 법입니다.

이러한 마음가짐을 잘 보여주는 엔지니어 중 한 명이 바로 Stripe에서 운 좋게도 같이 일할 수 있었던 줄리아 에반스(Julia Evans)입니다. 줄리아는 컴퓨터가 어떻게 작동하는지 항상 깊은 호기심을 갖고 있고 이에 대해 글과 말로써 풀어내는 것을 정말로 잘 합니다. 단순히 무엇을 배웠는지 뿐만 아니라, 이를 어떻게 배웠고 그 과정에서 본인이 가졌던 호기심과 신남과 발견들을 가감없이 잘 보여주는데, 아래는 그러한 예시들 중 제가 가장 좋아하는 것들 일부입니다.

  • 커널 개발에 어떻게 뛰어들게 됐는 지에 대한 포스트. 두려워보이는 영역에 어떻게 발을 담구게 그 속에서 길을 찾았는지 보여주는 매우 좋은 구체적인 사례입니다.
  • 위 포스트에 담긴 생각들 중 많은 부분들을 다시 한 번 보여주는 마법사가 되는 법에 대한 토크. 실용적인 조언과 이를 어떻게 적용시킬지에 대해서도 얘기합니다.
  • 좋은 질문을 하는 법. 다른 사람들과 함께 일하는 사람은 물론 컴퓨터에 대해 더 깊이 배우고자 하는 모든 이에게 훌륭한 자료입니다.

마치며

컴퓨터는 분명 복잡합니다. 하지만 그렇다고 미스터리한 건 아닙니다. 그 질문이 무엇이건 간에 원론적이든, 심지어 실제로 대부분의 경우 답이 있는 것들이죠. 미지의 시스템은 호기심과 결심만 있다면 두려움 없이 다가갈 수 있는 법입니다. 이러한 믿음은 저뿐만 아니라 제가 아는 다른 경험 많은 엔지니어들이 소프트웨어 엔지니어 접근법에 있어 큰 부분을 차지합니다. 지금까지 얘기했던 컴퓨터가 이해가능하다란 마음가짐이 이 포스트를 통해 잘 설명되어 전달됐기를 바랍니다. 혹여 어떤 새로운 것을 갖고 배우거나 알고자 하는 마음이 든다면, 저한테 이메일(nelhage@nelhage.com)을 보내 알려주세요!

Footnotes

  1. 역주: 링크가 깨져있음 ㅠㅠ

  2. 구체적인 예를 들자면, 몇 년 전에 제가 EventMachine 메모리 누수를 추적한 글에 대해 여러 사람에게 이런 피드백을 받았습니다. 저는 Valgrind나 tcmalloc의 누수 탐지기 같은 도구를 먼저 시도하지 않고, 곧바로 원시 메모리 이미지를 파헤치는 쪽으로 뛰어든 것에 대해 비판받았죠. 비록 운 좋게 버그를 찾아내긴 했고, 실제로 그런 도구들이 제대로 작동했을지는 확신할 수 없지만, 제 직감이 대체로 더 어렵고 복잡한 접근법을 먼저 시도하려는 쪽으로 지나치게 기울어 있다는 점은 타당한 지적이라고 생각합니다.