The mythical 10x programmer

Salvatore Sanfilippo

신화 속 10x 프로그래머

원문은 Salvatore Sanfilippo님이 에 게재했습니다. 이 블로그 구독하기

프로그래밍 신화에서 말하는 10x 프로그래머란 평범한 프로그래머보다 열 배의 일을 해내는 프로그래머를 뜻한다. 여기서 평범한 프로그래머란 일을 잘하지만 10x 프로그래머 같은 마법적 능력을 갖추지 못한 사람을 상상하면 된다. 사실 ‘평범한 프로그래머’를 더 정확히 정의하자면, 이 분야의 전문 프로그래머들 사이에서 평균적인 생산성을 보이는 사람을 가리킨다고 하는 편이 낫다.

이런 존재가 실제로 있는지를 두고 프로그래밍 커뮤니티는 극명하게 갈린다. 어떤 이들은 10x 프로그래머 따위는 존재하지 않는다고 말하고, 또 다른 이들은 존재할 뿐만 아니라 어디서 찾아야 할지만 알면 100배 프로그래머도 있다고 주장한다.

프로그래밍을 ‘선형적인’ 분야로 본다면 10x 프로그래머는 분명 비합리적인 가능성처럼 보인다. 어떻게 한 주자가 다른 주자보다 열 배 더 빨리 달릴 수 있겠는가? 혹은 한 건설 노동자가 같은 시간에 다른 노동자보다 열 배나 많은 것을 지을 수 있겠는가? 하지만 프로그래밍은 매우 특별한 의미에서 설계 분야다. 프로그래머가 프로그램의 실제 아키텍처 설계에 참여하지 않더라도, 그것을 구현하는 행위 자체가 구현 전략에 대한 하위 설계를 요구한다.

따라서 프로그램의 설계와 구현이 선형적인 능력이 아니라면, 경험, 코딩 능력, 지식, 불필요한 부분을 알아보는 안목 같은 것들은 내 생각에 단순히 선형적인 이점이 아니다. 프로그램을 만들어내는 과정에서 이들은 곱셈처럼 함께 작용한다. 물론 이런 현상은 프로그래머가 설계와 구현을 모두 맡을 수 있을 때 훨씬 더 두드러진다. 과제가 ‘목표 지향적’일수록 잠재적인 10x 프로그래머는 자신의 능력을 활용해 훨씬 적은 노력으로 목표에 도달할 수 있다. 반대로 당면한 과제가 훨씬 더 경직되어 있고 어떤 도구를 쓰고 어떻게 구현해야 하는지에 대한 구체적인 가이드라인이 정해져 있다면, 적은 시간에 많은 일을 해내는 10x 프로그래머의 능력은 약화된다. 여전히 ‘국소적인’ 설계 가능성을 활용해 훨씬 더 나은 작업을 할 수는 있지만, 목표에 도달하는 경로를 더 근본적인 방식으로 바꿀 수는 없다. 그 경로에는 어쩌면 명세의 일부를 프로젝트에서 완전히 제거하는 것까지 포함될 수 있는데, 그렇게 하면 도달해야 할 목표는 거의 동일해 보이면서도 이를 달성하는 데 필요한 노력은 대폭 줄어든다.

프로그래머로 일한 20년 동안 나는 나와 함께 일하는 다른 프로그래머들을 관찰했다. 동료로서, 주어진 목표를 달성하도록 내가 이끌었던 사람들로서, 그리고 Redis를 비롯한 여러 프로젝트에 패치를 기여한 사람들로서였다. 동시에 많은 사람들이 내가 매우 빠르게 코딩하는 프로그래머라고 말해 주었다. 나는 워커홀릭과는 거리가 멀다는 점을 감안하면, 나 자신도 빠르게 코딩한다는 하나의 사례로 삼고자 한다.

다음은 프로그래머의 생산성에 가장 큰 차이를 만든다고 내가 생각하는 자질들의 목록이다.

  • 순수한 프로그래밍 능력: 하위 과제를 해내는 힘

    프로그래머의 가장 명백한 한계이자 강점 중 하나는 프로그램의 일부, 즉 함수나 알고리즘이나 그 밖의 무엇이든 실제로 구현하는 하위 과제를 처리하는 능력이다. 놀랍게도 무언가를 구현하기 위해 기본적인 명령형 프로그래밍 구문을 매우 효율적으로 사용하는 능력은, 내 경험상 사람들이 생각하는 것만큼 널리 퍼져 있지 않다. 팀에서 때로는 간단한 정렬 알고리즘조차 모르는 매우 무능해 보이는 프로그래머가, 이론적으로는 매우 유능하지만 해결책을 실제로 구현하는 데는 매우 서툰 전공자 출신 프로그래머보다 더 많은 일을 해내는 모습을 보기도 했다.

  • 경험: 패턴 매칭

    여기서 경험이란 반복적으로 등장하는 여러 과제에 대해 이미 탐색해 본 해결책들의 집합을 의미한다. 경험이 많은 프로그래머는 결국 다양한 하위 과제들을 어떻게 다뤄야 하는지 알게 된다. 이는 많은 설계 작업을 덜어줄 뿐만 아니라, 특히 설계 오류에 맞서는 극히 강력한 무기가 된다. 설계 오류는 단순성을 해치는 가장 큰 적 중 하나이기 때문이다.

  • 집중: 실제 시간 대 가상의 시간

    시간의 질을 따지지 않고 코드를 작성하는 데 쓴 시간만 세는 것은 무의미하다. 집중력 부족은 내부적 요인과 외부적 요인에서 비롯될 수 있다. 내부적 요인으로는 미루기, 현재 프로젝트에 대한 흥미 부족(좋아하지 않는 일을 잘할 수는 없다), 운동·건강 부족, 수면 부족이나 질 낮은 수면 등이 있다. 외부적 요인으로는 잦은 회의, 제대로 된 사무실이 없는 근무 환경, 동료들의 잦은 방해 등이 있다. 집중력을 높이려 노력하고 방해를 줄이는 것이 프로그래밍 생산성에 적지 않은 영향을 미치리라는 것은 당연해 보인다. 때로는 집중을 얻기 위해 극단적인 조치가 필요하다. 예를 들어 나는 이메일을 가끔씩만 확인하고 대부분은 답장하지 않는다.

  • 설계상의 희생: 5%를 버려 90%를 얻기

    복잡성은 종종 프로젝트의 비본질적인 목표가 설계 복잡성의 상당 부분을 차지하고 있거나, 본질적인 기능과 비본질적인 기능 사이의 설계적 긴장 때문에 더 중요한 다른 목표를 달성하기 매우 어렵게 만들고 있다는 사실을 인정하려 하지 않을 때 생겨난다. 설계자에게는 손쉽게 얻을 수 없는 부분, 즉 노력과 이점이 비례하지 않는 부분을 모두 알아보는 것이 매우 중요하다. 생산성을 극대화하기 위해 실행되는 프로젝트는 정확히 중요한 측면, 그리고 합리적인 시간 안에 구현할 수 있는 측면에 집중하게 될 것이다. 예를 들어 메시지 브로커인 Disque를 설계할 때, 어느 순간 나는 메시지에 대해 단순히 최선 노력(best-effort) 수준의 순서 보장만 제공함으로써 프로젝트의 다른 모든 측면, 즉 가용성, 질의 언어와 클라이언트 상호작용, 단순성 그리고 성능을 크게 개선할 수 있다는 것을 깨달았다.

  • 단순성

    이건 너무 당연해서 모든 것을 의미하면서도 아무것도 의미하지 않는 지점이다. 단순성이 무엇인지 이해하려면 복잡성이 어떻게 생겨나는지를 살펴볼 가치가 있다. 나는 복잡성을 추동하는 두 가지 주요 원인이 설계상의 희생을 기꺼이 감수하지 않으려는 태도와 설계 과정에서의 오류 누적이라고 믿는다.

    설계 과정을 생각해 보면, 잘못된 경로를 추구할 때마다 우리는 최적의 해법으로부터 점점 더 멀어진다. 잘못된 손에 들어가면 초기 설계 오류는 같은 시스템을 재설계하는 것으로 이어지지 않고, 초기 오류를 메우기 위해 또 다른 복잡한 해결책을 설계하는 것으로 이어진다. 따라서 프로젝트는 잘못된 발걸음을 내디딜 때마다 더 복잡해지고 덜 효율적으로 변한다.

    단순성을 달성하는 방법은 머릿속의 작은 “개념 증명(proof of concepts)”의 관점에서 사고하는 것이다. 그렇게 하면 프로그래머의 머릿속에서 수많은 단순한 설계를 탐색해 볼 수 있고, 가장 실현 가능하고 직접적으로 보이는 해결책에서부터 작업을 시작할 수 있다. 이후에는 경험과 개인적인 설계 능력이 설계를 개선하고 해결해야 할 하위 설계들에 대해 합리적인 해법을 찾도록 도와준다.

    하지만 복잡한 해결책이 필요할 때마다, 그 복잡성을 어떻게 피할 수 있을지에 대해 오랜 시간 숙고하는 것이 중요하다. 완전히 다른 대안까지 고려해도 더 나은 가능성을 찾지 못했을 때에만 그 방향으로 계속 나아가야 한다.

  • 완벽주의, 혹은 생산성을 죽이고 설계를 왜곡하는 법

    완벽주의는 두 가지 형태로 나타난다. 프로그램에서 측정 가능한 최고의 성능을 달성하려는 엔지니어링 문화로서의 완벽주의와, 성격적 특성으로서의 완벽주의다. 두 경우 모두 나는 이를 프로그래머가 일을 빠르게 해내는 데 가장 큰 장애물 중 하나로 본다. 완벽주의와 외부 평가에 대한 두려움은 설계상의 편향을 만들어, 심리적 혹은 사소하게 측정 가능한 파라미터에만 맞춰 설계를 다듬으려다 결국 좋지 않은 선택으로 이어지게 한다. 그 과정에서 견고성, 단순성, 제때 결과물을 내놓는 능력 같은 것들은 종종 전혀 고려되지 않는다.

  • 지식: 약간의 이론은 도움이 된다

    복잡한 과제를 다룰 때, 자료구조, 계산의 근본적 한계, 특정 과제를 모델링하는 데 매우 적합한 자명하지 않은 알고리즘 등에 대한 지식은 적합한 설계를 찾는 능력에 영향을 미치게 된다. 모든 것의 초전문가가 될 필요는 없지만, 적어도 하나의 문제에 대해 다수의 잠재적 해결책을 알고 있는 것은 분명 필요하다. 예를 들어 설계상의 희생(어느 정도의 오류율을 감수하는 것)을 적용하는 것과 확률적 집합 원소 수 추정기(probabilistic set cardinality estimators)에 대한 지식을 결합하면, 스트림에서 고유한 항목 수를 세기 위해 복잡하고 느리며 메모리를 비효율적으로 쓰는 해결책을 피할 수 있다.

  • 로우 레벨: 기계를 이해하기

    고급 언어를 사용할 때조차 프로그램에서 발생하는 많은 문제는 컴퓨터가 주어진 작업을 어떻게 수행할지에 대한 오해에서 비롯된다. 이는 사용된 도구나 알고리즘에 근본적인 문제가 있기 때문에 프로젝트를 처음부터 다시 설계하고 다시 구현해야 하는 상황으로까지 이어질 수 있다. C에 대한 탄탄한 역량, CPU가 어떻게 동작하는지에 대한 이해, 그리고 커널이 어떻게 동작하고 시스템 콜이 어떻게 구현되는지에 대한 명확한 개념은 뒤늦게 닥치는 곤란한 놀라움을 막아줄 수 있다.

  • 디버깅 능력

    버그를 찾는 데 엄청난 시간을 쏟아붓는 것은 매우 쉽다. 버그에 대한 상태를 점진적으로 파악해 합리적인 단계들을 거쳐 수정하는 데 능숙한 능력과, 애초에 버그가 많이 생기지 않을 단순한 코드를 작성하려는 태도가 합쳐지면 프로그래머의 효율성에 큰 영향을 미칠 수 있다.

위와 같은 프로그래머의 자질들이 생산성에 10배의 영향을 미칠 수 있다는 것은 내게는 전혀 놀랍지 않다. 이들이 결합되면 실현 가능한 모델에서 출발해 대안보다 몇 배 더 단순할 수 있는 설계를 훌륭하게 구현할 수 있게 된다. 단순성을 강조하는 한 가지 방식을 나는 “기회주의적 프로그래밍(opportunistic programming)”이라고 부르길 좋아한다. 기본적으로 매 개발 단계마다 최소한의 노력으로 프로그램 사용자층에 최대의 영향을 줄 수 있는 기능 집합을 선택하는 것이다.

이 글은 muse-spark-1.2-contributor 모델을 사용해 번역했습니다.

댓글