대규모 기술 프로젝트를 하는 나만의 접근법
원문은 Mitchell Hashimoto님이 에 게재했습니다. 이 블로그 구독하기
새 프로젝트를 처음부터 만드는 일이든, 큰 기능을 구현하는 일이든, 대규모 리팩터링을 시작하는 일이든, 큰 기술 프로젝트를 끝까지 완수하며 동기를 유지하는 일은 쉽지 않다. 내게 정말 잘 통하는 방법은 계속해서 실제 결과를 확인하고, 그에 맞춰 작업 순서를 정하는 것이다.
누구나 새 프로젝트를 시작할 때의 그 설렘을 경험해 봤을 것이다. 처음 몇 주간은 컴퓨터 앞에 앉아 작업할 생각에 가슴이 뛴다. 그러다 시간이 지나면서 점점 딴짓을 하거나 핑계를 대며 작업에서 멀어진다. 이게 실제 업무라면 억지로 꾸역꾸역 결승선까지 끌고 가지만 매일매일이 고통스럽다. 취미로 하는 일이라면 몇 년 뒤에 돌아보며 ‘그때 완성했더라면’ 하고 아쉬워하게 된다.
큰 작업을 눈에 보이는 진전이 드러나는 단위로 쪼개면, 끝까지 일을 마무리하고 프로젝트 내내 설렘을 유지하게 된다는 걸 배웠다. 사람마다 동기를 얻는 방식은 다르니 이 방법이 모두에게 통하지 않을 수도 있다. 하지만 대체로 말하자면, 멋진 데모에 흥분하지 않는 엔지니어는 본 적이 없다. 그리고 목표는 항상 스스로에게 멋진 데모를 선사하는 것이다.
이 글에서 하는 이야기가 특별히 새로운 것이라고 주장하려는 건 아니다. 잘 알려진 소프트웨어 엔지니어링이나 관리 방법론과 겹치는 부분이 분명히 많다. 그저 내가 규모가 큰 기술적 작업을 어떻게 접근하는지, 그리고 왜 그렇게 하는지를 공유하려는 것뿐이다.
이 글 전반에 걸쳐 나의 터미널 에뮬레이터 프로젝트를 예로 들려고 한다. 그래야 실제로 겪은 구체적인 경험을 나눌 수 있기 때문이다. 예로 들 만한 다른 프로젝트도 많지만, 이 프로젝트는 내 본업과는 관련이 없고 비교적 최근 일이라 기억이 생생해서 선택했다.
분명히 해두고 싶은 건, 프로젝트를 완성하지 못했다고 해서 누구를 비난하려는 게 아니라는 점이다. 즐겁고 성취감을 느끼고 있다면(혹은 그냥 신경 쓰지 않는다면), 그걸로 충분하고 응원한다. 이 글은 프로젝트를 더 많이 완성하고 싶은 사람들, 혹은 내가 어떻게든 프로젝트를 더 완성하려 노력하는지 궁금한 사람들을 위한 것이다.
시작선
처음에는 규모가 큰 프로젝트가 있고, 어떻게 시작할지를 고민해야 한다. 내게는 이 부분이 가장 어렵고, 올바른 시작점을 두고 몇 시간, 때로는 며칠씩 망설이기도 한다.
나의 터미널 에뮬레이터의 경우, 이 프로젝트를 끝내려면 반드시 있어야 한다고 알고 있던 큰 구성 요소들이 여럿 있었다. 터미널 파싱, 셸 프로세스 실행 및 관리, 폰트 렌더링, 그리드 렌더링, 입력 처리(키보드/마우스) 등이다. “완성”까지 가는 길에는 비교적 규모가 큰 하위 프로젝트가 수백 개나 있다.
만약 초기 목표를 Neovim을 실행할 수 있는 터미널을 띄우는 것으로 잡았다면, 큰일 날 뻔했다. 미지의 미지까지 감안하지 않더라도, 이 목표는 그 자체로 너무 크게 들린다. 그 길에 GUI 렌더링, 프로세스 실행, 터미널 파싱과 상태 관리 등 수많은 구성 요소가 놓여 있다는 걸 직감적으로 알 수 있다. 이건 나쁜 목표다. 너무 크고, 한두 달 안에 흥미를 잃을 가능성이 크다.
대신 나는 가능한 한 빨리 결과를 볼 수 있는 현실적인 프로젝트가 무엇인지 생각하려 한다. 그 기준으로 걸러내면, 실행 가능한 하위 프로젝트의 수는 극적으로 줄어든다. 예를 들면 이런 것들이다:
- VT 파싱 - 터미널 이스케이프 시퀀스 파싱
- 빈 창 렌더링 - 창을 열고 빈 캔버스 그리기
- 자식 프로세스 실행 - bash, zsh, fish 같은 자식 셸을 실행하고, TTY를 설정한 뒤 그 출력(예: 초기 셸 프롬프트)을 읽을 수 있게 만들기
이 단계에서 모든 큰 하위 프로젝트를 일일이 나열하려 하지는 않는다. 그저 프로젝트가 대략 어떤 모양이 될지 감을 잡고, 독립적으로 만들 수 있으면서 실제로 눈에 보이는 결과를 확인할 수 있는 것 하나를 찾는다.
경험이 가장 도움이 되는 단계가 바로 여기다. 경험이 많은 엔지니어일수록 프로젝트가 어떤 대략적인 형태를 띨지 더 효과적으로 그려낼 수 있다. 다양한 하위 구성 요소를 더 정확하게 파악하고, 각 조각이 어떻게 맞물리는지 볼 수 있다. 경험이 적거나 익숙하지 않은 분야에서는 그저 최선을 다해 추측하고, 언젠가 작업물을 버리게 될 가능성이 더 높다고 예상한다.
초기 결과
초기의 작업은 대체로 그다지 눈에 보이지 않으며, 그래서 가시적인 성과를 확인하기 어려워 보인다. 예를 들어 터미널 프로젝트에서 VT 파싱을 먼저 하기로 했다면, 어떤 형태로든 UI를 연결하지 않고서는 그 동작을 눈으로 볼 수 없다. 다른 프로젝트에서 데이터베이스 스키마와 최소한의 API를 먼저 작업하기로 했다 해도 마찬가지로, CLI나 GUI와 함께 클라이언트를 작성하지 않고서는 그 성과를 볼 수 없다.
처음 선택한 하위 프로젝트가 UI라면 물론 빠르게 결과를 확인할 수 있다! 여러 가지 이유로 나는 프론트엔드부터 시작하는 일이 거의 없고 보통 백엔드부터 시작한다. 그리고 어떤 경우든 결국 백엔드에 도달하게 되고 비슷한 난관에 부딪히게 된다.
이 시기를 넘기는 데 가장 좋은 도구는 자동화된 테스트다(보통 이 단계에서는 단위 테스트). 자동화된 테스트를 이용하면 실제로 코드를 실행해 제대로 동작하는 것을 확인할 수 있고, 좋은 습관을 들이는 이점도 있다.
이는 처음 몇 가지 작업을 고를 때 또 다른 기준이 된다. 그래픽과 관련된 작업이 아니라면, 큰 수고 없이 테스트할 수 있어서 결과를 확인할 수 있는 것을 고르는 게 좋다.
내 터미널의 경우, 당시 터미널에 대해 잘 알지 못하던 부분이기도 하고 아주 쉽게 테스트할 수 있을 것 같아 VT 파싱부터 시작하기로 했다. 문자열로 예시 입력을 주고, 파싱된 동작이나 이벤트를 출력으로 기대하면 되기 때문이다.
“1개 테스트 통과”, “4개 테스트 통과”, “13개 테스트 통과” 식으로 늘어나는 걸 보는 건 나에게 정말 신나는 일이다. 내가 작성한 코드를 실행하는데 제대로 동작한다. 그리고 더 큰 프로젝트의 핵심 하위 구성 요소에서 진전을 이루고 있다는 걸 알 수 있다.
데모를 향해 질주하기
초기 하위 프로젝트에서의 내 목표는 완성된 하위 구성 요소를 만드는 게 아니라, 데모로 가는 길의 다음 단계로 넘어갈 수 있을 만큼 충분히 쓸 만한 하위 구성 요소를 만드는 것이다. ✨
이런 타협은 기능에만 나타나는 게 아니다. 알고리즘적 혹은 설계적 측면에서도 나타날 수 있다. 예를 들어, 나중에는 실제 데이터베이스나 정교한 자료구조를 써야 하거나 스트리밍 데이터를 지원해야 한다는 걸 알고 있을 수도 있다. 하지만 초기 작업에서는 그냥 인메모리 콘텐츠, 딕셔너리 같은 내장 자료구조를 쓰고, 모든 입출력을 한 번에 요구하도록 만들면 된다.
이 타협이 중요하다고 생각해서 다시 한번 강조한다. 완벽을 추구하느라 진전을 방해하지 마라. 더 나아가, 언젠가 반드시 해야 할 개선 때문에 다음 단계로 넘어가는 것을 멈추지 마라. 목표는 데모에 도달하는 것이다.
무엇을 작업하든, 나는 앞 절에서 설명한 자동화된 테스트 피드백과 함께 일주일에 한두 개의 데모를 만들려고 노력한다.
데모를 만들면 제품에 대한 값진 피드백도 얻을 수 있다. 완전히 동작하지 않더라도 무언가 감촉이 좋은지를 빠르게 직감할 수 있다. 이것들은 “최소 기능 제품”이 아니다. 실제로 쓸 만하지는 않기 때문이다. 하지만 엔지니어에게 가치 있는 자기 성찰을 제공하기에는 충분하다.
경험이 오히려 방해가 된다고 생각하는 부분이 바로 여기다. 완벽한 무언가를 만들려다 발이 묶인 시니어 엔지니어들을 본 적이 있다. 데모가 나올 때쯤이면 형편없다는 걸 깨닫는다. 구현이 형편없는 게 아니라, 제품이나 기능 자체가 형편없는 것이다.
터미널 프로젝트에서 내가 처음 선택한 작업이 VT 파싱이었다는 걸 기억할 것이다. 초기 단계에서는 자동화된 테스트가 동작하는 것만 볼 수 있었다. 첫 번째 데모에 도달하기 위해 나는 어떤 명령어를 실행하고, 그 출력을 캡처해 내 VT 파서에 넣은 뒤, 파싱한 것(혹은 파싱하지 못한 것)을 모두 출력하는 셸 스크립트를 만들었다. 시간이 지나면서 이 CLI를 첫 번째 “UI”로 발전시켰고, ASCII로 터미널 그리드를 렌더링했다.
이를 통해 man이나 ls 같은 간단한 프로그램부터 vim 같은 더 복잡한 프로그램까지 실행하며 내 파서가 동작하는(혹은 고장 나는, 그것도 나름대로 똑같이 신나는) 모습을 볼 수 있었기에 큰 만족감을 얻었다.
이 경우 내가 작성하던 CLI는 장기적으로는 별 쓸모가 없었다(결국 꽤 빨리 버렸다). 하지만 데모로 만드는 데 들인 하루 이틀은 중요한 진전이라는 느낌을 주었고, 무언가 동작하는 걸 눈으로 보는 것이 동기를 유지하는 데 도움이 되었다.
자신을 위해 만들기
이 절은 업무로 할당된 프로젝트보다는 개인 프로젝트에 더 해당한다. 다른 사람들을 위해 소프트웨어를 출시하고 싶더라도, 필요한 것만 필요할 때 만들고 가능한 한 빨리 자신의 소프트웨어를 직접 써라.
나는 내가 직접 겪고 있는 문제를 다룰 때 항상 더 동기부여가 된다1. 그리고 자신을 위해 설계한 제품이 자신에게조차 제대로 동작하지 않는다면, 다른 사람들에게도 잘 동작할 가능성은 매우 낮다. 따라서 데모에서 실제 세상에서 쓸 수 있는 제품으로 가는 나의 경로는, 내가 필요하다고 생각하는 기능만 만드는 가장 짧은 길을 찾는 것이다.
내 터미널의 경우, 그건 먼저 내 셸 설정(fish)을 불러올 수 있고, 이어서 Neovim을 실행해 사용할 수 있어야 한다는 의미였다. 그래서 모든 작업을 그에 필요한 기능에만 집중했다. 그 프로그램들이 사용하는 이스케이프 시퀀스만, 내가 매일 쓰는 폰트만 렌더링하는 식이다. 초기에 생략한 기능의 예로는 스크롤, 마우스 선택, 검색, 탭/스플릿 등이 있다.
그리고 나서 내 터미널을 일상용으로 쓰기 시작했다. 이 단계에서는 보통 몇 번의 시행착오가 있다. 생략하거나 잊어버린 기능이 실제로는 필요하다는 걸 깨닫게 되기 때문이다. 내 터미널을 처음 실행했을 때 방향키가 전혀 동작하지 않는다는 걸 알았고, 미묘하지만(작업 흐름을 망가뜨리는) 렌더링 버그도 있었다. 그래서 사용을 포기하기도 했지만, 다음에 할 구체적인 작업들을 얻을 수 있었다.
게다가 내가 직접 코드를 작성한 소프트웨어를 사용한다는 것 자체에서 큰 자부심을 느끼고, 이는 보통 계속 작업할 동기를 유지하는 데 도움이 된다.
정리하기
큰 문제를 더 작은 문제들로 쪼개라. 중요한 건, 각 작은 문제마다 작업 결과를 확인할 수 있는 명확한 방법이 있어야 한다는 점이다.
더 큰 문제의 데모 측면으로 나아갈 수 있을 만큼만 작은 문제를 해결한 뒤, 다음 작은 문제로 넘어가라.
소프트웨어의 실행 가능한 데모를 만들기 시작할 수 있을 만큼만 작은 문제들을 해결한 뒤, 더 많은 기능을 계속 반복적으로 추가하라. 가능한 한 자주 데모를 만들어라.
가능하다면(개인 프로젝트, 자신이 실제로 겪는 문제를 해결하는 업무 프로젝트 등) 자신의 소프트웨어를 직접 쓸 수 있게 해주는 기능을 우선시하라. 그리고 계속해서 자신의 문제부터 해결하라.
필요에 따라 각 구성 요소를 다시 돌아보며 향후 개선을 위해 반복하고, 이 과정을 필요할 때마다 반복하라.
결론
대략 이게 전부다. 나는 개인 프로젝트, 팀 프로젝트, 업무 프로젝트, 학교 프로젝트 등에서 이 전반적인 패턴을 따라왔고, 이렇게 스스로 동기를 유지해 왔다2.
내가 많은 것들을 언급하지 않았다는 걸 알아챘을 것이다! 출시 이야기는 하지 않았다. 많은 사람이 출시에서 동기를 얻는다는 걸 안다. 나는 프로젝트가 성공하기 위해 반드시 출시해야 한다고 생각하지 않는다. 그리고 내게 출시는 장기적으로 동기를 부여하기에는 너무 큰 이벤트다. 툴링(Git 워크플로, CI 등)에 대해서도 이야기하지 않았다. 나는 여러 직장에서 이 방식을 사용해 왔고, 이미 정해진 어떤 프로세스에도 맞춰 적용했다. 그 외에도 마찬가지다.
이는 이 방법이 얼마나 개인적인 과정인지를 보여준다고 생각한다. 누구나 건강하게 동기를 강화할 수 있는 자신만의 과정을 찾아야 한다고 생각한다. 나는 결과를 눈으로 확인하는 것이 나에게 정말 큰 동기가 된다는 걸 깨달았고, 그에 맞춰 작업 방식을 만들었으며, 지금까지는 잘 통하고 있다.
각주
글을 무작위로 읽기
댓글
로그인하고 댓글 남기기