복잡한 프로젝트에 기여하기
오픈 소스 프로젝트를 자주 유지 보수하고 기여하는 사람으로서, 이런 질문을 자주 받습니다. 어디서부터 시작하나요? 의미 있는 변경을 목표로 새로운 프로젝트에 어떻게 접근하나요? 복잡한 프로젝트의 내부를 대체 어떻게 이해할 수 있나요?
이 질문은 오픈 소스인지 독점 소프트웨어인지, 취미 프로젝트인지 업무용 프로젝트인지와 관계없이 모든 소프트웨어 프로젝트에 적용됩니다. 제가 취하는 접근 방식도 어느 경우에나 같습니다. 다만 전문적인 업무와 오픈 소스 프로젝트 사이에는 중요한 차이가 있습니다. 전문적인 업무에서는 다른 엔지니어에게 직접 도움을 요청할 수 있고, 그들이 도와줄 의무까지는 없더라도 대개 기꺼이 도와줍니다. 반면 오픈 소스 프로젝트에서는 대부분 스스로 해결해야 합니다.
저는 복잡한 프로젝트에 접근할 때 반복해서 사용하는 패턴을 정립했고, 이 글에서 그 과정을 정리해 보려고 합니다. 이 패턴이 모든 사람에게 통하리라고 기대하지는 않습니다. 그래도 다른 사람들이 복잡한 프로젝트를 배우고 기여해 보려는 자신감을 얻는 데 도움이 되기를 바랍니다.
여기서 말하는 복잡한 프로젝트란 구현을 단번에 이해하기 어려운 소프트웨어 프로젝트를 뜻합니다. 주관적인 정의입니다. 어떤 사람은 프로젝트를 복잡하다고 생각하지만 다른 사람은 그렇지 않을 수 있고, 그 반대도 마찬가지입니다.
1단계: 사용자가 되기
어떤 프로젝트의 내부를 이해하는 첫 단계는 그 프로젝트의 사용자가 되는 것입니다. 숙련된 사용자가 될 필요는 없지만, 제가 이 단계를 마쳤다고 판단하는 기준은 그 프로젝트로 실제 무언가를 만들어 보는 것입니다. 작고 단순한 것이라도 괜찮습니다. 예를 들어 Zig 프로그래밍 언어에 기여하기 전에 저는 실제로 사용할 수 있는 라이브러리 몇 개를 만들었습니다.
사용자가 되면 프로젝트가 제공하는 기능을 폭넓게 이해하게 됩니다. 레퍼런스 문서를 읽는 것과 프로젝트를 실제로 사용하는 것 사이에는 큰 차이가 있습니다. 장난감 프로젝트를 만들어 보는 일은 이론적인 이해와 실용적인 이해 사이의 간극을 메우는 중요한 단계입니다.
또한 프로젝트의 관용적인 사용 방식도 배우기 시작합니다. 이런 관용은 프로젝트의 문화적 배경을 이루며, 프로젝트가 왜 그런 방식으로 작동하고 왜 그런 기능을 갖추고 있는지 이해하는 데 도움이 됩니다. 이는 프로젝트에서 일하는 다른 사람들을 공감하는 데 도움이 되므로 중요합니다. 어떤 변경이 그 프로젝트에 어울리고 어떤 변경이 어울리지 않는지 판단하는 기준이 되기도 합니다.
이 시점에는 커뮤니티에 참여하는 것도 강력히 권합니다. IRC나 Discord에 참여하고, 지역 밋업에 참석하고, 발표 영상을 시청하는 등 여러 활동을 해 보세요. 일정 기간은 말하는 것보다 듣는 데 더 많은 시간을 쓰세요. 여기서의 목표는 공감 능력을 기르고 프로젝트가 어떻게 돌아가는지 배우는 것입니다. 다른 사람이 배우는 과정을 지켜보기만 해도 얼마나 많이 배울 수 있는지, 저는 늘 놀라곤 합니다.
2단계: 프로젝트 빌드하기
프로젝트를 빌드하고 작동하는 바이너리 또는 그에 해당하는 결과물을 얻는 방법을 익히세요. 빌드 시스템이나 의존성 등을 이해하려고 애쓸 필요는 없습니다. 소스 코드에서 시스템에서 실행할 수 있는 바이너리로 안정적으로, 반복해서 변환할 수 있을 때까지 가이드나 웹사이트 등에 나온 절차를 그대로 따라 하세요.
프로젝트를 빌드하는 법을 배우기 전에는 코드를 읽지 마세요. 사람들이 프로젝트를 빌드하는 법을 익히기도 전에 소스 코드를 이해하려고 매달리다가 진전을 못 내는 경우를 너무 자주 봅니다. 저에게는 실험하고 무언가를 망가뜨려 보는 과정이 학습의 일부입니다. 프로젝트를 빌드할 수 없으면 소프트웨어를 실험하고 망가뜨리기도 어렵습니다.
기능이 모두 포함된 빌드에 집착하지 마세요. 복잡한 프로젝트에는 적절한 의존성, 시스템, 설정 등이 갖춰져야만 사용할 수 있는 기능이 있는 경우가 많습니다. 그렇더라도 그런 요소를 당장 모두 갖추려고 걱정하지 마세요. 목표는 자신의 시스템에서 충분히 잘 작동하는 바이너리를 얻는 것입니다. 다음 단계들을 진행하면서 경험과 자신감이 쌓이면 기능이 더 완전한 빌드에도 도전할 수 있습니다.
이 단계에서는 테스트 스위트를 실행하는 방법을 익히고 테스트가 통과하도록 만드는 것도 권합니다. 그러면 다음 단계에서 더 쉽게 실험하고 무언가를 망가뜨려 볼 수 있습니다. 복잡한 프로젝트에는 복잡한 테스트 스위트가 있는 경우도 많습니다. 따라서 때로는 테스트 스위트 전체가 아니라 일부만 실행할 수 있게 만드는 것으로 충분합니다. 실험에 필요한 정도면 됩니다.
3단계: 핵심 경로의 내부 구조 익히기
내부 구조를 배울 때 저는 “아래로 추적하고, 위로 배우기”라고 부르는 접근 방식을 사용합니다.
아래로 추적하기
먼저 기능이나 사용 사례 하나를 정하고, 바깥에서 안쪽으로 들어가며 그 기능이 따라가는 코드 경로를 추적합니다. 이 과정에서 거치는 파일, 줄, 함수는 메모해 두지만, 아직 아무것도 어떻게 작동하는지 이해하려고 하지는 않습니다. 이것이 “아래로 추적하기” 단계입니다.
예를 들어 Zig 컴파일러를 공부할 때는 Zig 소스 코드로 실행 파일을 만드는 zig build-exe 명령부터 추적했습니다. 이 과정에서 zig CLI의 소스와 build-exe 하위 명령을 찾았고, 이어서 “Compilation” 하위 시스템으로 들어가 렉서와 파서 등을 호출한다는 것을 확인했습니다. 경로를 추적하는 데 필요한 범위를 넘어 구현 세부 사항을 읽지는 않았습니다.
추적하면서 작성한 메모만으로도 대개 어떤 기능이 작동하는 방식의 “큰 그림”을 얻을 수 있습니다. 파일 이름과 함수 등을 바탕으로 프로젝트의 주요 하위 시스템을 구분하기 시작할 수도 있습니다. 이는 이후 학습 과정을 좀 더 감당할 만한 크기의 단위로 나누는 데 도움이 됩니다.
모든 것을 배우려고 하지 마세요. 사람들이 흔히 하는 실수는 프로젝트 전체를 한 줄씩 읽으려다가 몇 주 또는 몇 달 동안 길을 잃고 결국 의욕을 잃는 것입니다. 집중력을 유지하고 기능 단위로 배우세요.
팁: 기능을 고를 때는 사용자로서 익숙한 기능을 고르세요. 가능하다면 겉보기에는 단순해 보이는 기능을 선택하는 것도 좋습니다. 예를 들어 제가 컴파일러를 배우기 위해 처음 추적해 본 Zig 프로그램은 두 수를 더하고 아무것도 출력하지 않는 프로그램이었습니다.
위로 배우기
기능을 추적한 다음에는 연결된 여러 하위 시스템이 실제로 어떻게 작동하는지 배울 차례입니다. 추적 단계에서는 CLI나 API 호출처럼 가장 바깥쪽 지점에서 시작하지만, 학습 단계에서는 대체로 가장 안쪽 지점에서 시작합니다.
가장 안쪽 지점에서 시작하는 이유는 그 부분이 대개 가장 근본적이고 추상화가 가장 적기 때문입니다. 계층을 따라 위로 올라갈수록 추상화 수준도 높아지는 경향이 있으므로, 구성 요소를 이해하지 못한 상태에서는 배우기가 더 어려워집니다.
특정 하위 시스템을 배우기 시작할 때도 “아래로 추적하고, 위로 배우기”를 재귀적으로 적용합니다. 먼저 외부에 공개된 API 표면을 살펴본 다음 각 API 호출이 어떻게 작동하는지 배웁니다. 상위 계층은 이 하위 시스템을 이런 방식으로 사용하므로, 무엇을 어떤 순서로 배워야 할지 알려 줄 뿐 아니라 스택을 따라 위로 올라갈 때도 더 명확하게 이해할 수 있게 해 줍니다.
실험하고 망가뜨려 보기
“아래로 추적하고, 위로 배우기” 과정에서는 무언가가 어떻게 작동하는지 알아보기 위해 직접 실험하고 망가뜨려 보는 것이 매우 유용합니다. 내부 구조를 읽기 전에 프로젝트를 빌드하는 법부터 익혀야 하는 이유가 바로 여기에 있습니다.
새 로그를 추가하고, 작은 기능을 구현하고, 기존 기능을 바꿔 보세요. 그런 다음 프로젝트를 다시 빌드하고 무슨 일이 일어나는지 확인하세요. 무언가가 작동하는 방식을 정말로 이해했는지 검증하는 좋은 방법이기도 합니다.
예를 들어 Zig 토크나이저를 배울 때 새 토큰을 추가해 보니 토큰화는 되었지만 파서에서 실패했습니다. 다음 시스템인 파서로 넘어갔을 때는 새 토큰이 실제로 무언가를 하도록 만들었습니다. 이런 식으로 계속 진행했습니다.
미디어로 보완하기
이 단계 전체에 걸쳐 코드 깊숙이 파고드는 작업은 책, 동영상, 블로그 글 등 이용할 수 있는 미디어 자료로 보완하세요. 내부 구조를 다루는 기존 자료가 있다면 읽으세요!
다만 이런 자료만으로 전문가 수준에 도달할 수 있다고 기대해서는 안 됩니다. 1단계에서 “사용자가 되기”를 했던 것과 마찬가지로, “유지 보수자가 되기” 위해 실제 소스 코드를 직접 만지고 다뤄 보는 일을 대신할 수 있는 방법은 없습니다. 이 역시 이론과 실제 적용의 차이를 보여 주는 사례입니다.
팁: 내부 구조를 배울 자료가 없다면 직접 써 보세요! Zig에서도 비슷한 최신 자료를 찾을 수 없어서 Zig 컴파일러 내부 구조에 관해 글을 썼습니다. 무언가에 관해 글을 쓰면 배운 내용을 다지는 데 도움이 되고, 앞으로 기여할 사람들에게도 도움이 됩니다.
4단계: 최근 커밋을 읽고 다시 구현하기
내부 구조를 배우는 마지막 단계로, 공부한 하위 시스템과 관련된 최근 커밋을 읽고 변경이 이루어진 이유를 완전히 이해했는지 확인합니다. 교과서 뒤에 있는 연습 문제를 푸는 것과 같은 학습 과정입니다.
프로젝트 전체의 커밋 기록을 살펴보거나, 공부한 하위 시스템과 관련된 특정 파일이나 폴더의 커밋 기록을 살펴봅니다. 그런 다음 해결책인 커밋의 변경 사항을 먼저 공부하거나, 또는 그 커밋이 수정한 버그를 확인하고 직접 고쳐 보면서 비슷한 해결책에 도달하는지 살펴봅니다.
문제를 직접 풀기 위해서는 공부할 커밋 바로 직전의 커밋으로 저장소를 체크아웃합니다. 버그 수정 커밋이라면 그 커밋이 고친 버그를 재현한 다음, 스스로 해결책을 구현해 봅니다. 마지막으로 제가 만든 결과를 유지 보수자나 기여자가 작성한 커밋과 비교합니다.
스스로에게 주는 유일한 힌트는 필요한 변경 규모입니다. 버전 관리 시스템의 diff에 표시되는 +/- 줄 수를 말합니다. 처음에는 변경되는 줄이 50~100줄을 넘는 작업은 피하는 것이 좋습니다.
5단계: 한입 크기의 변경 만들기
저는 작은 일부터 시작해 점차 더 큰 작업을 맡아 가는 방식을 좋아합니다. 이 단계에서는 프로젝트의 기술적 구성 요소를 이해했으므로 이제 사람과 관련된 부분을 배울 차례입니다. 목표는 작은 변경을 만들어 기여 및 리뷰 과정을 익히는 것입니다.
대개 가장 어려운 부분은 만들 만한 작은 변경을 찾는 일입니다. 여기에는 만능 해결책이 없습니다. 저는 이슈를 훑어보며 기여자에게 친화적으로 보이는 작업을 찾습니다. 몇 번 시도하다가 잘못된 방향임을 깨닫거나, 이슈를 완전히 포기하고 다른 이슈를 찾기도 합니다. 그러다 마침내 하나를 찾습니다. 이슈를 찾거나 실제로 수정하는 데 걸리는 시간이 답답할 정도로 길 수 있지만, 그것이 입장료입니다. 요즘 프로젝트에는 새 기여자를 안내하기 위해 “contributor friendly” 같은 라벨을 붙여 두는 경우가 많습니다.
오늘날 대부분의 프로젝트는 기여 절차를 잘 문서화해 둡니다. 따라서 변경을 구현했다면 그 절차를 정확히 따르세요. 앞 단계에서 커뮤니티에 참여했다면, 누군가에게 연락해 도움을 요청하거나 자신의 절차를 다시 확인해 달라고 부탁하기에도 좋은 기회입니다.
예를 들어 제가 Zig에 처음 기여했을 때는 세 줄을 변경하는 작업이었습니다. 앞 단계에 들인 시간은 제외하고, 이틀 저녁에 걸쳐 네다섯 시간 정도가 걸렸습니다. 지금 이 버그가 다시 나타난다면 몇 분 만에 고칠 수 있겠지만, 그 정도 숙련도에 이르기까지는 시간이 필요합니다.
성공
이제 복잡한 프로젝트를 배우고 성공적으로 기여했습니다!
복잡성을 두려워하지 마세요. 많은 엔지니어가 프로그래밍 언어, 브라우저, 데이터베이스처럼 전형적으로 복잡한 프로젝트를 마법처럼 여기거나 초월적인 존재만 만들 수 있는 것으로 생각한다고 봅니다. 하지만 모든 프로젝트는 다른 사람들이 시작했다는 사실을 기억하려고 합니다. 그들이 할 수 있었다면 저도 할 수 있습니다. 여러분도 할 수 있습니다.
제가 이 과정을 공유함으로써 다른 사람들이 복잡한 프로젝트에 좀 더 쉽게 다가갈 수 있기를 바랍니다.
글을 무작위로 읽기