Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

[Book] 구글 엔지니어는 이렇게 일한다 - 구글러가 전하는 문화, 프로세스, 도구의 모든 것

올해 출간되자마자 주변에서 좋다는 얘기가 많아서 읽어봤는데 정말 오랜만에 너무 좋은 책을 만나서 이번에 읽지 않았으면 후회했을 거다. 책이 당시 고민하는 문제나 생각 등과 맞아떨어질 때 더 좋은 느낌을 받기 때문에 다른 사람은 어떻게 느낄지 모르지만 나는 한 장 한 장 너무 유익하고 좋았기에 강력하게 추천한다. 2020년 4월에 나온 원서의 번역이지만 내용이 시대를 타는 것은 별로 없어서 크게 상관은 없고 700페이지로 양은 꽤 되는 편이다.

요즘 고민하는 부분과 맞는 얘기도 많아서 나중에 찾아볼 수 있게 좀 자세히 정리해 봤다. 인용을 많이 넣었는데 이 책은 이 후기를 읽는다고 책을 읽을 가치가 떨어질 거라고는 전혀 생각하지 않지만 그래도 책을 집중해서 읽어보고 싶다면 아랫글은 안 읽어도 좋다. 기록용으로 적은 거라 좀 많은 내용이 있다.

구글이 20여 년을 고민하면서 발전시킨 구글의 엔지니어 문화가 이 책에 다 나와 있다. 그 경험들도 당연히 대단하지만, 정리가 잘 되어 있어서 구글의 고민과 노력에서 많이 배울 수 있는 책이다. 모든 부분은 아니더라도 몇몇 부분은 비슷한 문제에 대한 구글의 해답을 배울 기회가 될거라고 생각한다. 현재 구글의 결과는 이미 알고 있고 그 안에서 있었던 노력을 알게 되니 더 설득력도 있게 느껴진다.

'프로그래밍'과 '소프트웨어 엔지니어링'은 서로 강조하는 것도 다르고 영향을 미치는 영역도 다릅니다. 하지만 우리 업계는 꽤 오랫동안 둘을 명확히 구분하지 않고 사용해왔습니다.

소프트웨어 엔지니어링은 단순히 코드를 작성하는 행위에 더하여, 시간의 흐름에 발맞춰 한 조직이 그 코드를 구축하고 유지보수하는 데 이용하는 모든 도구와 프로세스를 포괄합니다. 이것이 우리가 제안하는 소프트웨어 엔지니어링의 개념입니다.

이 책을 통해 공유하고자 하는 핵심은 소프트웨어 엔지니어링이란 '흐르는 시간 위에서 순간순간의 프로그래밍을 모두 합산한 것이다'라는 관점입니다.

예전에는 명함이나 프로필에 Developer 혹은 Web Developer를 보고 켄트 벡의 프로필을 보고 멋있다는 생각이 들어서 간단히 Programmer라고 적었던 기억이 있다. 어느 순간부터는 Software Engineer라고 적기 시작했는데 업계에서 대부분이 이렇게 표현하고 있다고 느꼈기에 따라갔는데 이 책에서 "소프트웨어 엔지니어링"의 정의해준 부분이 인상적이었다. 앞으로도 종종 써먹을 것 같다.

Part 1 전제

1 소프트웨어 엔지니어링이란?

프로그래밍과 소프트웨어 엔지니어링의 가장 큰 차이는 시간(time), (규모) 확장(scale), 실전에서의 트레이드오프(trade-offs at play), 이렇게 세 가지라고 생각합니다. 소프트웨어 엔지니어링 프로젝트에서 엔지니어는 시간의 흐름과 언젠가 변경될 가능성에 더 신경써야 합니다. 소프트웨어 엔지니어링 조직은 만들어낼 소프트웨어 자체뿐 아니라 제작하는 조직까지 양 측면 모두에서의 확장과 효율에 더 집중해야 합니다. 마지막으로 소프트웨어 엔지니어링는 대체로 수명과 성장 속도를 정밀하게 예측하기 어려운 상황에서, 결과에 더 큰 영향을 주는 보다 복잡한 결정을 내려야 합니다.
구글에서는 이따금 '소프트웨어 엔지니어링은 흐르는 시간 위에서 순간순간의 프로그래밍을 모두 합산한 것이다'라고 말하곤 합니다.

소프트웨어의 지속 가능성(sustainability)의 핵심입니다. 기술적인 이유든 사업적인 이유든, 소프트웨어의 기대 생애 동안 요구되는 모든 가치 있는 변경에 대응할 수 있다면 '그 프로젝트는 지속 가능하다'라고 말합니다.

수명이 길어질수록 '동작한다'와 '유지보수 가능하다'의 차이를 더 분명하게 인지해야 합니다. 불행히도 둘을 구분하는 완벽한 해법은 없습니다. 소프트웨어를 장기간 유지보수하는 일은 끝나지 않는 전쟁이기 때문이죠.

소프트웨어 엔지니어링이라 부르든 프로그래밍이라고 부르든 코드를 작성하거나 코드 리뷰를 하면서 유지보수를 고민하는 것은 당연하지만 그보다 훨씬 더 지속 가능성에 중점을 두고 있다고 느껴졌다.

다른 엔지니어들이 사용 중인 프로젝트를 유지보수하고 있다면 '동작하다'와 '유지보수 가능하다'를 구분 짓는 가장 중요한 요인은 바로 다음의 '하이럼의 법칙'일 것입니다.
하이럼의 법칙: API 사용자가 충분히 많다면 API 명세에 적힌 내용은 중요하지 않습니다. 시스템에서 눈에 보이는 모든 행위(동작)를 누군가는 이용하게 될 것이기 때문입니다.

책의 전반에 걸쳐서 "하이럼의 법칙"이 자주 등장한다 이 법칙은 처음 알았지만 금방 이해할 수 있었다. 인터페이스는 약속이지만 당연히 사람이 많아지면 이 약속만이 지켜진다고 보장할 수 없고 시스템을 변경하거나 할 때 이러한 부분에 대해 고려를 해야 한다. 오픈 API에서도 마찬가지지만 사내 시스템에서는 인터페이스는 지켰으니 깨진 건 어쩔 수 없다고 할 수도 없는 노릇이다. 이 부분이 프로젝트에 변경을 가할 때 비용을 높게 만들게 하는 요인이고 당연히 예상 못한 부분을 이용하는 것이기 때문에 다 예측하기는 어려워 보인다. 지금 하는 업무에서도 이런 부분을 염두에 두고 있어야겠다는 생각을 하고 실제로는 현재 지원 못 하는 부분을 우회 방법으로 지원하기 위해 우리가 직접 이용하기도 하고 있다.

비용이 너무 많이 드는 변경은 지연되기 쉽습니다. 변경 비용이 시간 흐름보다 가파르게 상승하는 시스템은 분명 확장 가능하지 않습니다.

마이그레이션 작업을 사용자에게 떠넘기는 대신 시스템 담당 팀 내부에서 스스로 처리할 수 있도록 하는 것입니다. 그러면 사용자가 많아져도 노하우를 축적한 하나의 팀이 모두 처리하므로 규모의 경제 효과도 누릴 수 있습니다.

SRE팀에 있다보니 비슷한 문제를 많이 보게 된다. 변경해야 하는 것은 명확한데 너무 많은 부분이 엮여있어서 한번에 바꾸기도, 점진적으로 바꾸기도 어려운 문제들이 많다. 사용자가 해야할 일과 SRE에서 해야할 일을 나누어서 SRE내에서만 할 수 있게 자꾸 만들려고 하는데 여전히 어렵긴 하다.

우리가 좋아하는 구글 내부 정책 중 인프라팀이 인프라 변경을 안전하게 진행하게끔 보호해주는 정책이 하나 있습니다. 바로 '인프라를 변경하여 서비스가 중단되는 등의 문제가 발생하더라도, 같은 문제가 지속적 통합(CI) 시스템의 자동 테스트에서 발견되지 않는다면 인프라팀의 책임이 아니다'라는 정책입니다. 이 정책의 이름은 '비욘세 규칙'이며, 친근하게 표현하며 '네가 좋아했다면 CI 테스트를 준비해뒀어야지'라는 뜻입니다. 공통 CI 시스템에 추가해두지 않은 테스트는 인프라팀이 책임지지 않는다는 뜻입니다.

이건 꽤 흥미로운 규칙이었다. 이 정도까지 규칙이 있다는 것은 프로젝트의 CI뿐 아니라 인프라 변경에 대한 테스트까지 아주 잘 구축되어 있다고 느껴졌다. 이후에 테스트에 대한 설명도 많이 나오지만, 인프라 변경까지 안정성을 확인할 수 있는 테스트는 아주 오랫동안 잘 쌓아온 테스트일 거라는 생각이 들었다. 저렇게 하고 싶기도 한데 처음에는 테스트가 충분치 않고 그렇다고 인프라 변경은 쉽게 할 수도 없는 상황이 오랫동안 지속될 텐데 어떻게 쌓아가는 게 좋을지도 고민이 들었다.

인프라는 더 자주 변경할수록 변경하기가 오히려 쉬워집니다.

이건 인프라뿐만 아니라 코드도 마찬가지.

우리는 2006년의 업그레이드를 포함한 많은 경험을 통해 코드베이스의 유연성에 영향을 주는 여러 요인을 찾아냈습니다.
* 전문성(expertise): 여러 방법에 대한 충분한 지식이 있었습니다. 몇 가지 언어에 대해 구글은 이미 수많은 플랫폼에서 수백 번의 컴파일러 업그레이드를 성공적으로 마쳤습니다.
* 안정성(stability): 더 규칙적으로 릴리스하여 릴리스 사이의 변경량을 줄였습니다. 몇 가지 언어에 대해서는, 구글은 한두 주 간격으로 새로운 컴파일러를 배포합니다.
* 순응(conformity): 업그레이드를 겪지 않은 코드가 많지 않습니다. 규칙적인 업그레이드가 도움이 되었습니다.
* 익숙함(familiarity): 업그레이드를 정기적으로 수행하기 때문에 그 과정에서 중복되는 작업을 찾고 자동화하려고 노력합니다. 많은 면에서 사이트 신뢰성 엔지니어(SRE)가 삽질을 줄이는 관점과 일맥상통합니다.
* 정책(policy): 비욘세 규칙과 같은 유용한 정책과 절차를 갖춥니다. 이러한 정책들 덕분에 인프라팀은 미지의 사용 방법까지 걱정할 필요 없이 업그레이드를 진행할 수 있습니다. CI 시스템에 반영된 사용법만 고민하면 됩니다.

분산 빌드 시스템으로 엔지니어 생산성을 크게 끌어올렸지만, 시간이 흐르면서 분산 빌드 자체가 폭발적으로 커졌습니다. 로컬 빌드 시절에는 엔지니어 각자가 빌드 시간 단축에 신경 썼는데, 분산 빌드로 넘어오면서 관심이 멀어진 탓입니다. 비대해진 빌드에는 불필요한 의존성이 넘쳐나게 됐죠. 최적화되지 못한 빌드가 안겨주는 고통을 모두가 체감할 수 있던 시절에는 최적화의 혜택이 골고루 돌아갔습니다. 그런데 그 혜택과 비대해진 의존성이 병렬 빌드 시스템 속으로 가려지면서 모두가 자원을 분별없이 소비하는 상황을 만들었습니다. 빌드를 최적화해도 혜택을 직접 누리게 되는 사람이 거의 사라졌기 때문입니다. '효율이 좋아지면 자원 소비가 늘어난다'라고 하는 제번스의 역설을 떠올리게 하는 대목이죠.

이 일화는 나한테는 상당히 인상적인 일화였다. 지금 업무에서 배포 시스템을 만들면서 인프라의 관심사와 사내 엔지니어의 관심사를 분리하려고 많이 고민하던 터라 경계를 잘 나누어서 어떻게 분리할지만 생각했는데 여기서 한 대 맞은 기분이 들었다. 관심을 너무 나누었을 때의 문제도 같이 고려해봐야 할 것 같다.

Part 2 문화

2 팀워크 이끌어내기

천재라고 해서 괴짜처럼 행동하는 게 용서받는 시대는 지났습니다. 천재든 아니든 사회성이 부족한 사람은 팀원으로 적합하지 않기 때문이죠.

프로그래머들이 느끼는 매우 일반적인 감정이라서 자연스럽게 동굴에 숨어 일하고 다듬고 또 다듬습니다. 그 과정에서 여러분이 저지른 멍청한 짓은 아무도 보지 못하게 하고 모든 일을 마무리한 후 완성된 걸작을 공개하고 싶어 하죠. 그래서 코드가 완벽해질 때까지 숨기는 것입니다.
진행 중인 프로젝트를 숨기려는 이유는 더 있습니다. 바로 누군가가 아이디어를 훔쳐서 나보다 먼저 세상에 내놓을지 모른다는 두려움입니다. 그래서 비밀에 부치고 통제하려 합니다.

프로그래머는 긴밀하게 피드백받을 때 가장 효율적으로 일합니다. 함수 하나를 짜고 컴파일하고, 테스트 하나 짜고 컴파일하고, 리팩터링 살짝 하고 컴파일합니다. 이 방법이 우리가 코드를 작성하자마자 가장 빠르게 오타와 버그를 잡는 길입니다.

정답은 팀 플레이입니다. '눈이 많아야 버그가 줄어든다'라는 말을 많이 들어봤을 텐데, '눈이 많아야 프로젝트가 탈선하지 않고 옳은 방향으로 나아간다'라는 표현이 더 나을 것 같습니다.

오해하지 마세요. 우리도 방해받지 않고 코딩에 집중할 시간이 필요하다고 생각합니다. 하지만 다른 팀원과의 활발한 소통도 그에 못지않게 중요하다고 믿습니다. 아직 모르는게 많은 팀원이 여러분에게 질문하기조차 어려운 문화라면 분명 문제입니다. 정확한 균형점 찾기란 예술의 영역이니 정답은 없습니다.

더 간단히 말하면 '소프트웨어 엔지니어링은 팀의 단합된 노력입니다.'

협업의 열반에 들어가려면 가장 먼저 사회적 스킬의 '세 기둥'을 배우고 익혀야 합니다. 이 세 원칙은 그저 관계라는 톱니바퀴에 기름을 칠하는 정도가 아니라 모든 건강한 상호작용과 협업의 초석이 되어 줍니다.
기둥 1: 겸손(humility) 당신과 당신의 코드는 우주의 중심이 아닙니다. 당신은 모든 것을 알지도, 완벽하지도 않습니다. 겸손한 사람은 배움에 열려 있습니다.
기둥 2: 존중(respect) 함께 일하는 동료를 진심으로 생각합니다. 친절하게 대하고 그들의 능력과 성취에 감사해합니다.
기둥 3: 신뢰(trust) 동료들이 유능하고 올바른 일을 하리라 믿습니다. 필요하면 그들에게 스스로 방향을 정하게 해도 좋습니다.

팀워크 얘기를 한 장을 할애해서 나눈 것도 좋았다.

구글에서 제가 정말 좋아하는 좌우명은 '실패는 선택이다'입니다. 구글에서는 '가끔씩 실패하지 않는다면 충분히 혁신적이지 않거나 위험을 충분히 감수하지 않은 것이다'라는 믿음이 널리 통용됩니다. 실패를 '배우고 다음 단계로 넘어갈 수 있는 절호의 기회'라고 생각하는 것이죠.

실패를 하는 건 중요하다. 어떻게 실패할지는 여전히 어렵지만...

모호함을 뚫고 번창한다 끊임없이 변화하는 환경 속에서도 상충하는 메시지와 방향에 잘 대처하고, 합의를 이끌어내고, 문제에 대한 진전을 이룰 수 있습니다.
피드백을 소중히 한다 피드백을 주고받을 때 품위와 겸손을 유지하고 개인과 팀의 발전에 피드백이 주는 가치를 이해합니다.
저항(항상성)을 극복한다 다른 이들이 저항하거나 관성 때문에 움직이지 않으려 하더라도 야심 찬 목표를 세우고 밀고 나아갑니다.
사용자를 우선한다 구글 제품의 사용자 입장에서 생각하고 존중하며 그들에게 가장 도움되는 행동을 추구합니다.
팀에 관심을 기울인다 동료들의 입장에서 생각하고 존중하며 팀의 결집을 위해 누가 시키지 않더라도 적극적으로 돕습니다.
옳은 일을 한다 모든 일에 강한 윤리 의식을 갖고 임합니다. 팀과 제품의 진정성을 지키기 위해서라면 어렵거나 불편한 결정을 내릴 수 있어야 합니다.

좋은 말이다.

3 지식 공유

구글은 특히 회사 규모가 커지면서 다음의 문제들을 겪었습니다.
심리적 안전 부족(lack of psychological safety) 불이익이 두려워서 스스로 위험을 감수하거나 실수를 드러내기 꺼리는 환경을 말합니다.
정보 섬(information islands) 조직의 각 부서가 서로 소통하거나 자원을 공유하지 않아서 지식이 파편화됩니다.
단일 장애점(single point of failure, SPOF) 중요한 정보를 한 사람이 독점하면 병목이 생깁니다.
전부 아니면 전무 전문성(all-of-noting expertise) 조직 구성원이 '모든 것을 아는' 사람과 '아무것도 모르는' 초심자로 나뉩니다. 중간층은 거의 사라지죠. 한번 이렇게 되면 전문가들은 항상 모든 일을 자신들이 처리하게 됩니다. 새로운 전문가를 키우기 위한 멘토링이나 무선화에 신경 쓸 여력이 줄어들어 문제가 눈덩이처럼 커집니다. 지식과 책임은 계속 이미 전문가가 된 사람들에게 집중되고, 새로운 팀원이나 초심자들은 그들만의 울타리에 갇혀 느리게 성장하게 됩니다.
앵무새처럼 흉내내기(parroting) 이해하지 못한 상태로 흉내만 내는 것을 말합니다. 이 증상에 빠진 사람은 목적을 이해하지 못하고(주로 '잘은 모르겠지만 이게 맞겠거니' 하고) 무의식적으로 기존 패턴이나 코드를 따라 합니다.
유령의 묘지(haunted graveyard) 무언가 잘못될 게 두려워서 아무도 손대지 않는 영역(주로 코드)를 말합니다.

이 책을 통해서 배울 수 있는 가장 좋은 부분은 구글이 커지면서 고민한 내용과 시도했던 부분에 대해서 배울 수 있다는 점이라고 생각한다. 위 문제들은 조직이 커지면 정도만 다르지 다들 겪는 문제인데 생길 문제를 알 수 있으면 대책도 좀 더 낫게 준비할 수 있지 않나 싶다.

4 공정 사회를 위한 엔지니어링

이번 장에서는 다양한 계층의 사용자를 위한 제품을 설계할 때 엔지니어가 짊어져야 할 책임에 관해 이야기합니다.
전 세계에 영향을 미치는 결정을 내리는 사람들과 그 결정을 받아들일 수밖에 없는 처지의 사람들 사이의 힘의 균형이 점점 무너지고 있기 때문입니다. 심지어 자신들에게 해가 된다고 해도 약자들은 받아들일 수밖에 없습니다.

업계 전반이 다문화, 인종, 성별 등의 관점에서 더 성숙되도록 교육하는 일은 여러분의 책임일 뿐 아니라 여러분 고용주의 책임이기도 합니다.

오늘날의 개발은 많이 쓰이는 기능을 먼저 만들고 특수한 상황에 쓰이는 기능이나 개선은 나중으로 미루는 방식으로 주로 진행됩니다. 하지만 여기에는 결함이 있습니다. 기술을 접하기에 유리한 사람들에게 우선권을 줌으로써 불평등을 가중하는 방식인 것입니다. 모든 사용자층을 고려하는 일을 설계 막바지까지 미룸으로써 훌륭한 엔지니어의 기준을 낮추는 꼴입니다. 대신 시작부터 포용적으로 설계하여 개발이 지향해야 하는 기준점을 높여주세요. 그리하여 기술을 접하기 어려운 사람들도 쉽게 접할 수 있고 행복하게 활용할 수 있는 도구를 만들어주세요. 이런 식으로 우리는 모든 사용자의 경험을 개선할 수 있습니다.

조직 내에 소수 집단 출신 엔지니어를 포함시킨다면 조직 자체를 강화할 뿐 아니라, 더 큰 세상에 진정 유용한 소프트웨어를 설계하고 구현하는 데 꼭 필요한 특별한 눈을 제공해줄 것입니다.

이 내용에 한 장을 할애한 것도 좋았다. 자주 얘기하게 되진 않지만, IT가 세상의 중심이 되면서 그 책임에 대해서도 많이 고민해야 하는데 회사나 사회나 이런 부분은 아직 부족하다고 생각한다. 물론 나를 포함해서 말이다. 다양성 관점에서도 더 많은 경험과 고민도 필요하지만, 엔지니어가 얼마나 큰 영향력을 가지는지는 계속 되새겨야 할 문제라고 본다.

5 팀 이끌기

관리자(manager)는 사람을 이끌고 테크 리드(tech lead)는 기술과 관련한 책임을 집니다. 책임지는 대상면에서는 차이가 크지만 두 역할에 필요한 기술은 꽤나 비슷합니다.

구글은 엔지니어링을 아는 사람만이 소프트웨어 엔지니어링 관리자가 될 수 있도록 했습니다. 그러기 위해 소프트웨어 엔지니어링 경험이 있는 관리자를 고용하거나 기존 소프트웨어 엔지니어링를 관리자로 훈련시키곤 합니다.

구글에서는 규모가 크고 잘 조직된 팀이라면 테크 리드와 엔지니어링 매니저를 각 1명씩, 총 두명이 이끄는 것이 통례입니다. 완전히 번아웃되지 않고서 두 역할을 동시에 수행하기란 너무도 어렵기 때문이죠. 그래서 각각에 집중하는 전문가를 독립적으로 두는 편이 낫습니다.

개인적으로 나는 테크 리드는 해도 관리자는 못한다고 생각하는 편이다. 이쪽은 경험이 많지 않아서 조금씩 배워가고 있지만 개인 기여자일때보다 리드가 되면 실패의 영향이 더 크기 때문에 좋게 말하면 신중해지고 나쁘게 말하면 몸을 사리게 되는것 같다. 실리콘밸리에서 보통 매니저와 테크 리드를 나누어서 배치한다는 것은 알고 있었지만 매니저를 해보고 나서 이 두 역할을 한사람이 맡는 것은 무리한 일이라는 확신이 생겼다. 그래도 경험해 보진 못했기 때문에 이 두 역할이 어떻게 협업해서 일하는지는 요즘 고민하는 문제다.

어쩌다 보니 리더가 되어 있는 상황은 아주 흔합니다. 리더가 될 의사가 전혀 없더라도 말이죠. 그래서 이 고통을 '관리자염(manageritis)'이라고 하는 사람도 있습니다.

경력 대부분을 코딩하며 보낸 사람이라면 하루의 끝에서 작성한 코드, 설계 문서, 해결한 버그 더미 등을 가리키며 '이게 오늘 내가 한 일이야'라고 외칠 수 있습니다. 하지만 '관리' 일로 바삐 보낸 하루의 끝에서는 '오늘 한 일이 하나도 없군'이라고 생각하게 되는 경우가 허다합니다. 이는 마치 매일 사과를 선별하는 일을 수년간 해온 사람이 바나나 재배업으로 전업한 후 '오늘은 사과를 하나도 선별하지 못했군'이라고 푸념하는 꼴입니다.

이런 말 엄청 많이 하는데... 이제 조심해야겠다.

유명한 '피터의 법칙'에 뿌리를 두고 있죠. 피터의 법칙에 따르면 위계 조직에서 직원들은 '자신의 무능력이 드러나는 직급'까지 승진하는 경향이 있습니다. (각주: 교육학자인 로렌스 피터가 내놓은 이론입니다. 이 법칙에 따르면 주어진 위치에서 뛰어난 성과를 내는 직원은 승진을 거듭하다가 더는 성과를 낼 수 없는 위치까지 올라선 후 멈추게 됩니다. 그래서 결국 조직 전체가 무능한 사람들로 채워지는 역설적인 상황에 처하는 것이죠. 조직의 보상과 승진 제도가 적절한지, 나아가 의도대로 시행되고 있는지를 돌아보게 해주는 이론입니다.)

이 법칙도 처음 알았는데 재밌었다. 실제로 많이 보기도 했고...

'관리'병을 치료하려면 '섬기는 리더십'을 자유롭게 응용할 수 있어야 합니다. 리더로서 여러분이 해야 할 가장 중요한 일은 팀을 떠받드는 것입니다.
섬기는 리더가 행하는 '관리'는 오직 팀의 기술적, 사회적 건강 관리뿐입니다. 순수하게 기술적 건강에만 신경 쓰고 싶을 수도 있겠지만 사회적 건강도 못지 않게 중요합니다.

관리자가 직원들을 신뢰한다는 분명한 신호를 주면 직원들은 신뢰에 부응해야 한다는 긍정적인 압박을 느낍니다. 아주 간단하죠. 훌륭한 관리자는 팀이 나아갈 길을 다짐과 동시에 안전과 복지를 챙겨줍니다.

이 사실을 분명하게 공표합시다. 실패해도 괜찮다! 사실 우리는 실패를 '많은 것을 아주 빠르게 배울 수 잇는 기회'로 봅니다(똑같은 일에 계속 실패하는 건 논외입니다).

실패해도 괜찮지만 '팀으로서 함께 실패하고, 실패로부터 배워야 합니다. 한편, 특정 개인이 이룬 성취는 팀이 보는 앞에서 칭찬해 주세요. 하지만 실패한 개인에게는 '개인적으로' 따로 불러서 '건설적인' 비판을 해주세요. 어떤 경우든 배울 기회로 삼고, 겸손, 존중, 신뢰라는 워칙 하에 행동하세요. 그러면 팀이 실패로부터 배우는 데 큰 도움이 될 것입니다.

이런 부분은 내가 생각하는 것과 많이 일치하는 부분이다. 물론 실천은 다른 얘기지만...

저성과자 문제에 가능한 한 빠르게 대처하면 여러분은 팀원들을 돕는다는 본분에 더 충실할 수 있습니다. 저성과자에 적극적으로 대응하다 보면 의외로 작은 격려와 방향 제시가 필요했을 뿐인 경우도 많습니다. 너무 오래 지켜보기만 하면 그들과 팀의 관계가 걷잡을 수 없이 틀어져서 여러분이 도울 수 있는 길이 모두 막혀버릴 수도 있습니다.

대부분의 관리자가 기술 업무를 수행하다가 승진하였기 때문에 사람의 문제는 소홀히 하는 경향이 있습니다. 개인 기여자였을 때와 다를 바 없이 모든 에너지를 팀이 마주한 기술적 문제에 집중하는 것이죠.

목표를 이루기 위해 부품을 조립하는 구체적인 방법은 현장에서 손발을 맞춰 일하는 사람들이 결정하는 게 훨씬 낫다는 뜻이죠. 그래야 팀원들에게 주인의식도 생기고 성공(혹은 실패)에 대한 책임감도 더 크게 느끼게 됩니다. 좋은 팀을 꾸렸고 일의 품질과 속도를 스스로 정하게 하세요. 그러면 여러분이 지키고 서서 당근과 채찍으로 끌고 갈 때보다 훨씬 나은 결실을 얻게 될 것입니다.

리더 역할이 처음인 사람은 대부분 모든 사안을 올바르게 결정하고, 모든 것을 알고, 모든 질문에 답해야 한다는 강박으로 스스로를 옭아맵니다.

사람들은 무언가를 망쳤으면 사과할 줄 아는 리더를 존경합니다. 일반적인 상식과 달리 사과한다고 해서 여러분이 피해를 입는 건 없습니다. 오히려 존경을 얻죠. 왜냐하면 사과한다는 것은 여러분이 성숙하고 상황을 판단할 줄 알며 겸손하다는 증거로 받아들여지기 때문입니다(다시 한번 겸손, 존중, 신뢰를 되뇌이세요).

바로 '질문하기'입니다. 팀원이 여러분에게 조언을 구한다는 것은 마침내 무언가를 해결해줄 기회가 찾아온 것입니다. 신나는 일이죠. 리더가 되기까지 수년 동안 몸에 익은대로 여러분은 곧장 문제 해결 모드로 전환할 것입니다. 하지만 '직접 해결하기'는 가장 마지막에 택해야 하는 전략입니다. 조언을 구하는 사람은 보통 '여러분이 나서서' 해결해주길 원하는 게 아닙니다. 스스로 문제를 해결하는 걸 도와주길 바라는 거죠. 스스로 해결하도록 이끄는 가장 쉬운 방법은 바로 '질문하기'입니다.

팀 리더가 하는 가장 일반적인 일은 합의를 이끌어내는 것입니다. 시작부터 끝까지 과정을 주도할 수 있고 올바른 방향으로 가속이 붙도록 톡 밀어만 줄 수도 있습니다.

테크 리드로서 가장 실천하기 어려운 일을 뽑으라면 '내가 하면 20분이면 끝난 일을 세 시간씩 매달려 있는 주니어 팀원 지켜보기'를 빼놓을 수 없습니다. 스스로 배울 기회를 주는 일은 훌륭한 리더십에서 빠질 수 없는 요소이지만 특히 처음에는 매우 고통스럽습니다.

명확한 목표 세우기는 많은 리더가 곧잘 잊어먹곤 하는 패턴입니다. 팀이 한 방향으로 빠르게 전진하기를 원한다면 리더가 설정한 방향을 모든 팀원이 이해하고 동의해야 합니다.

우리는 칭찬 샌드위치를 사용하지 말 것을 강하게 권합니다. 여러분이 필요 이상으로 가혹하고 잔인해야 한다고 생각하기 때문이 아닙니다. 겉에 둘러진 칭찬 때문에 핵심 메시지, 즉 실제로는 고쳐야 할 점을 지적한 것임을 제대로 인지하지 못하는 사람이 대부분이기 때문입니다.

개인 기여자에서 리더 역할로 이동할 때 가장 어려운 일 하나가 균형잡기입니다. 초기에는 모든 일을 스스로 처리하려 들 것입니다. 반대로 리더 역할을 오래 수행하다보면 아무것도 스스로 하려 들지 않게 되기 쉽습니다.

회사 위쪽에서 무슨 일이 벌어지고 있는지를 팀이 알게 해주는 것도 물론 중요합니다.

자율적인 직원에게는 제품이 나아가야 할 대략적인 방향만 알려주면 어떻게 도달할지는 스스로 결정합니다(구글은 자율적인 엔지니어들을 채용하기 위해 노력합니다). 이렇게 했을 때 동기부여가 더 잘 되는 이유는 다음과 같습니다. 첫째, 제품과의 관계가 더 끈끈해집니다(제품을 만드는 방법을 아마도 여러분보다 더 잘 알 것입니다). 둘째, 제품에 대한 주인 의식이 커집니다. 제품 성공에 팀원들이 기여하는 비중이 커질수록 성공시키려는 의지도 커질 것입니다.

대부분 공감하기에 종종 다시 이 부분을 읽어봐야 할 것 같다. 국내 IT 업계는 엔지니어링 분야가 성장한 거에 비해 매니징 분야는 거의 성장하지 못했다고 생각하는 편이다. 실제로도 매니징 분야를 개선하려는 노력도 많이 보지 못했다. 결국 개인의 능력에 의존해서 매니징을 실패한 사람들이 생기는 가운데 매니징을 잘하게 된 매니저가 가치가 올라가는데 매니저를 키우는 노력도 더 많이 필요하다고 생각하게 된다. 사실 처음 매니저를 하게 되었을 때 어떻게 하는지 알려주는 시스템은 거의 없다시피 해서 제일 좋은 것은 좋은 매니저를 보고 배우는 것인데 아직 성숙하지 못해서 이 또한 쉽지 않다. 이 부분은 회사 차원 이상으로 고민해 봐야 할 문제다.

6 성장하는 조직 이끌기

우리는 이를 '3A 리더십'이라고 부릅니다.
늘 결정하라(Always Be Deciding) 이 정보를 활용하여 가령 이번 달에 수행할 최선의 결정을 내릴 수 있게 되죠. 그리고 다음 달이 되면 트레이드오프들을 다시 평가하고 균형점도 새로 잡아야 할 것입니다. 반복적인 프로세스라는 말이죠. 이것이 바로 '늘 결정하라'라고 부르는 이유입니다.

늘 떠나라(Always Be Leaving) 리더는 모호한 문제를 풀어줄 뿐 아니라 맡은 조직이 여러분 없이도 '스스로' 문제를 풀 수 있게 유도해야 한다는 것입니다. 그럴 수만 있다면 여러분은 새로운 문제 혹은 새로운 조직을 찾아 떠날 수 있게 됩니다. 여러분 덕분에 스스로 자생력을 갖추게 된 팀을 남겨두고 말이죠.

자생력을 갖춘 조직으로 가꾸기 위해서는 세 가지가 필요합니다. 문제 공간을 분할하고, 하위 문제를 위임하고, 필요에 따라 반복하는 것입니다.
문제 공간 분할하기 도전적인 문제는 일반적으로 난해한 하위 문제 여러 개로 구성되기 마련입니다. 그래서 팀들의 팀을 이끄는 리더들은 각각의 팀에 하위 문제 하나씩을 배정하곤 합니다. 이 방식에는 위험이 따릅니다. 하위 문제들은 시간이 지나면 변할 수 있는데 반해 팀은 경직된 경계에 갇혀 있어서 이 변화를 눈치 채기 어렵기 때문입니다. 그래서 가능하다면 조직의 구조를 느슨하게 관리하세요. 하위 팀들의 규모는 유동적이고, 팀원은 다른 하위 팀으로 옮길 수 있고, 상황이 변하면 할당된 문제를 바꿀 수도 있는 식으로요. '너무 경직된'과 '너무 유연한' 사이에서 아슬아슬한 외줄타기를 해야 합니다.
자율주행 조직을 일구는게 여러분의 임무라는 데 동의한다면 가장 효과적인 교육 메커니즘은 바로 위임입니다. 자생력을 갖춘 리더들을 키워야 하며 그들을 훈련시키는 가장 효과적인 방법은 두말할 것 없이 위임입니다. 과제를 던져준 후, 실패하고 다시 시도하고 또 도전하도록 놔두세요.
여러분은 리더들의 리더라는 본연의 목적을 잊지 말아야 한다는 것입니다. 여러분 자신은 어두운 숲 속에서 헤매고 있다면 여러분의 조직에 몹쓸 짓을 하는 것입니다. 매일 아침 출근하면 자신에게 '우리 팀원 중 아무도 할 수 없는 일 중에서 내가 할 수 있는 일은 무엇이 있을까?'라는 질문을 던져보세요.
새로움과의 조우는 개인이 번아웃되지 않도록 예방하는 데 효과적입니다.
관찰과 경청 95%, 절묘하고 시의적절한 개입 5%, 이것이 좋은 관리입니다.

늘 확장하라(Always Be Scaling) 조급함은 리더인 여러분의 효율을 갉아먹는 가장 큰 적입니다. 스스로를 완전한 반응형 모드로 전환해버리면(거의 자동적으로 이렇게 됩니다) 삶 전체의 순간순간을 오로지 '급한' 일만 처리하면서 흘려보내게 됩니다. 멀리 보면 '중요'하지 않은 일들임에도 말이죠. 리더로서 여러분은 '나만이 할 수 있는 일'을 처리해야 함을 잊지 마세요.

상위 20%, 즉 여러분만이 할 수 있는 중요한 일들을 신중하게 골라낸 다음 오직 그 일들에만 집중하는 것입니다. 나머지 80%를 버릴 권한을 자신에게 부여하세요.
처음에는 말도 안 된다고 생각할지 모르지만 수많은 공을 이처럼 의도적으로 떨어뜨리다 보면 놀라운 사실 두 가지를 발견하게 됩니다. 첫째, 중간의 60%는 위임하지 않더라도 때때로 (어떻게 알았는지) 나서서 가져가는 중간 리더들이 나타납니다. 둘째, 중간 칸에 잘못 넣어둔 업무라도 '진짜' 중요한 것이라면 어떤 식으로든 다시 튀어올라 상위 20% 쪽으로 돌아옵니다.

이전 장의 리더십보다는 좀 더 큰 조직의 이야기라고 이해했다. 아직 내가 고민하는 규모는 아니라서 공감하고 넘어갔다.

7 엔지니어링 생산성 측정하기

우리는 중립적인 척하고 싶지만 실제로는 그렇지 않습니다. 우리 모두는 어떤 일이 일어나야 하는지에 대한 선입견을 가지고 있습니다. 처음부터 이 사실을 인정하고 시작하면 무의식 속에서 의도한 결과를 억지로 만들어내는 실수를 막을 수 있습니다.

구글은 지표를 만들 때 GSM이라는 프레임워크를 씁니다. GSM은 목표(goal)/신호(signal)/지표(metric)의 약자입니다.
* 목표는 측정자가 원하는 최종 결과입니다. 측정을 통해 이해하고 싶은 내용을 고차원의 어휘로 표현하되 특정 측정 방식을 명시해서는 안 됩니다.
* 신호는 원하는 최종 결과를 이루었는지 판단하는 방법입니다. 우리가 측정하고 싶어 하는 것이지만 신호 자체는 측정하지 못할 수도 있습니다.
* 지표는 신호를 대변합니다. 우리가 실제로 측정하는 대상이죠. 이상적인 측정법은 아닐 수 있으나 충분히 가깝다고 믿는 것이어야 합니다.

GSM 프레임워크는 지표를 만들 때 유용한 지침이 되어줍니다.
첫째, 가장 먼저 목표를 세우고, 신호를 정한 다음, 마지막으로 지표를 만드는 순서 덕분에 가로등 효과를 없애줍니다. 가로등 효과(streetlight effect)란 '가로등 아래에서 열쇠 찾기'라고 풀어서 표현할 수 있습니다.
둘째, GSM은 실제로 결과를 측정하기 앞서 원칙에 입각하여 적절한 지표들을 선정하게 해줌으로써 지표 크리프(metrics creep)와 지표 편향(metrics bias)을 예방해 줍니다.
마지막으로, GSM은 측정이 되는 영역과 그렇지 않은 영역을 알려줍니다. GSM을 따른다면 먼저 모든 목표를 나열한 다음 각 목표를 포착할 수 있는 신호들을 찾아냅니다.

OKR도 하고 있지만 목표는 필요하긴 한데 목표 프레임워크는 여전히 너무 어렵다. 잘 하고 있는지도 모르겠고...

Part 3 프로세스

8 스타일 가이드와 규칙

규칙을 관리하는 목표는 '좋은' 행동을 장려하고 '나쁜' 행동을 억제하기 위함입니다.

일관성이 주는 이점은 우리가 잃게 되는 자유의 가치보다 훨씬 큽니다. 코드에서도 마찬가지입니다. 때로는 일관성이 구속처럼 느껴지기도 합니다. 하지만 더 많은 엔지니어가 더 많은 일을 적은 노력을 수행할 수 있게 됩니다.

일관성과 잃게 되는 자유의 비교는 지난 1년 동안 매번 고민하는 부분이다. 당연히 상황과 정도에 따라 정답은 없겠지만 일관성에 더 큰 가치를 주고 있어서 생각을 정리하는 데 도움이 되었다.

구글은 코드베이스가 특정 전문가의 전유물이 아닌 모든 엔지니어의 작업 공간이 되길 원합니다. 초보 소프트웨어 엔지니어를 위한 것이기도 하지만 사이트 신뢰성 엔지니어(SRE)에게도 유리합니다. 프로덕션 서비스에 장애가 생기면 SRE는 의심이 가는 코드라면 어디든 살펴봐야 합니다.

기술 커뮤니티에서 논쟁이 끊이지 않는 이유가 바로 이런 특성 때문일 것입니다. 대신 구글은 하나를 선택함으로써 끝없는 논쟁에서 벗어나 더 중요한 일로 시선을 돌릴 수 있게 했습니다. 구글 엔지니어들은 공백을 두 개 쓰냐 네 개 쓰냐로 시간을 낭비하지 않습니다. 이 범주의 규칙들은 우리가 '무엇'을 선택했냐가 아니라 '선택을 했다'는 사실에 의의가 있습니다.

9 코드 리뷰

구글에서는 어떤 변경이든 '승인'을 얻으려면 세 가지 측면에서의 리뷰를 통과해야 합니다.
* 첫째, 다른 엔지니어로부터 정확성(correctness)과 이해 용이성(comprehension)을 평가받습니다. 즉, 작성자가 의도한 작업을 코드가 적절하게 수행하는지를 봅니다.
* 둘째, 변경되는 코드 영역을 관리하는 코드 소유자로부터 변경 코드가 적절하다는 (그리고 특정 디렉터리에 체크인해도 좋다는) 승인을 받습니다. 변경 작성자가 코드 소유자라면 이 승인을 묵시적으로 받은 게 됩니다.
* 셋째, 누군가로부터 '가독성' 승인을 받습니다. 즉, 변경 코드가 해당 언어의 스타일과 모범 사례를 잘 따르고 조직에서 기대하는 방식으로 작성되었는지를 검사받습니다.

구글은 새로운 코드가 '완벽하다'고 합의될 때까지 기다리지 않고 코드베이스를 개선하다고 인증되면 변경을 승인하도록 안내합니다. 코드 리뷰의 속도를 높이는 수단 중 하나입니다.

코드 리뷰를 하면서 고민하는 부분이다. 요즘은 속도를 높이는 쪽으로 좀 더 신경을 쓰고 있지만 여전히 어느 정도가 적당한지를 생각하고 있다. 코드 리뷰의 역할을 꽤 믿기 때문에 그 효과를 최대화하면서 적절한 속도를 찾으려고 하는데 규칙을 적용해도 시간이 지나면 새로운 불편이 생기고 있다.

코드 리뷰는 주어진 변경이 수많은 다른 사람에게도 쉽게 이해되는지를 평가하는 첫 번째 시험대입니다. 코드는 작성되는 횟수보다 읽히는 횟수가 몇 배는 많으므로 이해하기 쉽게 작성하는 게 매우 중요합니다.

코드 리뷰야말로 지식을 퍼뜨리기에 완벽한 기회입니다.

내가 코드 리뷰를 필수로 적용하는 주된 이유이기도 하다.

'작은' 변경이라 하면 일반적으로 변경되는 코드가 약 200줄 이하라는 뜻입니다. 작은 변경은 리뷰어의 부담을 줄여줍니다. 큰 변경과 달리 리뷰받기를 기다리는 변경들이 처리되지 못하고 쌓여가는 일이 줄어든다는 점도 중요합니다.

우리는 첫 번째 LGTM이 가장 중요하며, 두 번째부터는 크게 신경 쓸 만큼의 가치가 없다는 걸 발견했습니다. 리뷰어를 추가해서 얻는 가치보다 비용이 훨씬 빠르게 증가하여 금세 역전됩니다.

난 아직 한 명의 LGTM은 좀 약한 부분도 있고 오히려 승인을 주저하게 만들지 않을까 하는 걱정도 있어서 기억해 두려고 적어두었다.

10 문서자료

현실적으로 대부분의 문서자료는 소프트웨어 엔지니어 스스로가 작성해야 합니다. 따라서 엔지니어가 문서화를 효과적으로 할 수 있도록 도와주는 적절한 도구와 보상이 필요합니다. 엔지니어 들이 양질의 문서자료를 더 쉽게 작성하도록 하는 비결은 무엇일까요? 바로 현재 개발 워크플로에 긴밀하게 통합되고 조직의 선장에 발맞춰 확장되는 프로세스와 도구입니다.

구글에서 문서자료를 개선하고자 해본 시도 중 가장 성공적이었던 방법은 문서자료를 코드처럼 취급하여 엔지니어링 워크플로에 통합하는 것이었습니다. 그 결과 엔지니어들이 간단한 문서자료를 작성하고 유지보수하는 일이 한결 수월해졌습니다.

결국은 엔지니어들 스스로가 문서자료의 거의 대부분을 작성하게 된다는 뜻입니다. 따라서 엔지니어에게 테크니컬 라이터가 되라고 몰아세우지 말고, 대신 문서자료를 보다 쉽게 작성할 수 있도록 도와주는 방법을 생각해야 합니다. 조직은 어느 시점이 되면 문서화에 얼마나 많은 노력을 기울일지를 결정해야 합니다.

중요한 점은 문서화가 엔지니어링 워크플로에 녹아 있다면 문서 자료의 품질이 점차 개선된다는 사실입니다. 현재 구글에서는 문서 대부분이 독자 리뷰를 받도록 합니다.

문서화를 좋아하는 편은 아니지만 문서화를 하더라도 코드 옆에 두는 걸 선호하는 편인데 노력을 많이 들이지 않아서인지 아직 깔끔한 접근을 잘 못 찾아내었다. 그냥 위키 스타일을 사람들이 더 편하게 느끼기도 하고...

중요하다고 생각되는 프로젝트에는 팀에 정말 필요한지와 상관없이 테크니컬 라이터를 배정하는 경향이 있었습니다. 그렇게 한 밑바탕에는 테크니컬 라이터가 팀의 문서 작성과 관리 부담을 덜어줘서 프로젝트의 속도를 높여주리라는 기대가 깔려 있었습니다. 결국은 잘못된 가정으로 판명났습니다.
중요하다고 인정받은 프로젝트의 소프트웨어 엔지니어들은 문서를 작성할 필요가 없어졌습니다. 그리고 엔지니어들을 문서 작성에서 멀어지게 함으로써 기대하던 효과와 반대의 결과가 나왔습니다.

테크니컬 라이터와 일해 본적은 없지만, 앞에서 분산 빌드 사례와 비슷한 맥락으로 이해했다.

11 테스트 개요

GWS의 변화는 구글의 테스트 문화를 격변시키는 분수령이 되었습니다. 모두가 테스트의 이점을 목격하면서 다른 팀들도 비슷한 전략을 도입하기 시작한 것입니다.

우리는 모든 테스트 케이스에는 두 가지 독립된 요소가 있다는 결론에 이르렀습니다. 바로 크기와 범위입니다. 크기(size)는 테스트 케이스 하나를 실행하는 데 필요한 자원을 뜻합니다. 메모리, 프로세스, 시간 등이죠. 한편 범위(scope)는 검증하려는 특정한 코드 경로를 뜻합니다.
구글에서는 모든 테스트를 크기 기준으로 분류하며, 엔지니어들에게 주어진 기능 조각을 검사하는 가능한 한 작은 테스트를 작성하라고 독려합니다. 테스트의 크기를 가늠하는 기준은 코드 줄 수가 아닙니다. 대신 어떻게 동작하고, 무엇을 하고, 얼마나 많은 자원을 소비하는지로 평가합니다.
구글은 전통적인 용어인 '단위 테스트'와 '통합 테스트' 대신 이 정의를 사용합니다. 우리가 테스트 스위트에 바라는 품질은 바로 속도와 결정성이기 때문입니다.

유닛 테스트, 통합 테스트로만 생각했는데 이 분류 방식은 신선한 충격이었고 합리적인 접근이라는 생각이 들었다. 물론 이런 규칙을 적용하려면 꽤 많은 인프라 지원이 필요하겠다는 생각도 들었다. 명확해 보이지만 이 분류를 하기까지 많은 시행착오와 고민이 있었을 거 같다.

세 가지 테스트 중 작은 테스트는 제약이 가장 엄격합니다. 가장 중요한 제약은 바로 테스트가 단 하나의 프로세스에서 실행되어야 한다는 것입니다. 프로그래밍 언어에 따라서는 이 제약을 하나의 '스레드'로까지 좁히는 경우가 많습니다. 즉, 테스트도 테스트 대상 코드와 같은 프로세스에서 실행되어야 한다는 뜻입니다.
중간 크기 테스트는 여러 프로세스와 스레드를 활용할 수 있고, 로컬 호스트로의 네트워크 호출 같은 블로킹 호출도 이용할 수 있습니다. 단, 외부 시스템과의 통신은 여전히 불허합니다. 말하자면 단 한대의 기기에서 수행되어야 합니다.
큰 테스트는 중간 크기 테스트를 구속하던 로컬 호스트 제약에서 해방되어, 테스트와 대상 시스템이 여러 대의 기기를 활용할 수 있게 됩니다. 예를 들어 원격 클러스터에서 구동 중인 시스템을 테스트할 수 있습니다.

구글은 테스트 크기를 많이 강조하지만 테스트 범위도 중요하게 생각합니다. 테스트 범위란 주어진 테스트가 얼마나 많은 코드를 검증하느냐를 말합니다. (보통 단위 테스트라고 하는) 좁은 범위 테스트는 독립된 클래스나 메서드같이 코드베이스 중 작은 일부 로직을 검증하도록 설계됩니다. (보통 통합 테스트라고 하는) 중간 범위 테스트는 적은 수의 컴포넌트들 사의 상호작용을 검증하도록 설계됩니다. 가령 서버와 데이터베이스의 상호작용을 검증합니다. (보통 기능 테스트, 종단간 테스트, 시스템 테스트 등으로 불리는) 넓은 범위 테스트는 시스템의 서로 다른 부분들 사이의 상호작용, 혹은 클래스나 메서드 하나만 실행할 때는 괜찮다가 여럿을 조합해 실행하면 나타나는 예기치 못한 동작을 검증하도록 설계됩니다.

거대한 테스트 스위트를 잘 관리하는 비결은 바로 테스트를 존중하는 문화입니다. 엔지니어들이 테스트에 관심을 갖도록 장려하세요.

12 단위 테스트

단위의 범위를 잘 정해서 어디까지가 공개 API인가를 정하는 일에 과학적인 정답은 없습니다. 그래도 다행히 쓸만한 경험법칙은 있습니다.
* 소수의 다른 클래스를 보조하는 용도가 다인 메서드나 클래스(예: 도우미 클래스)라면 독립된 단위로 생각하지 않는게 좋습니다. 따라서 이런 메서드나 클래스는 직접 테스트하지 말고 이들을 보조하는 클래스를 통해 우회적으로 테스트해야 합니다.
* 소유자의 통제 없이 누구든 접근할 수 있게 설계된 패키지나 클래스라면 거의 예외 없이 직접 테스트해야 하는 단위로 취급해야 합니다. 이때도 테스트는 사용자와 똑같은 방식으로 접근하는 것입니다.
* 소유자만이 접근할 수 있지만 다방면으로 유용한 기능을 제공하도록 설계된 패키지나 클래스(예: 지원 라이브러리) 역시 직접 테스트해야 하는 단위로 봐야 합니다. 이 경우 지원 라이브러리의 코드를 라이브러리 자체용 테스트와 라이브러리 사용자용 테스트 모두에서 건사한다는 점에서 다소 중복이 생길 수 있습니다. 하지만 유익한 중복입니다. 라이브러리 사용자(와 그 테스트) 중 하나가 사라지면 라이브러리의 테스트 커버리지가 낮아질 수 있기 때문입니다.

시스템이 기대한 대로 동작하는지 검증하는 방법은 크게 두 가지입니다. 첫 번째는 상태 테스트(state test)로, 메서드 호출 후 시스템 자체를 관찰합니다. 두 번째는 상호작용 테스트(interaction test)로, 호출을 처리하는 과정에서 시스템이 다른 모듈(시스템)들과 협력하여 기대한 일련의 동작을 수행하는지를 확인합니다.
대체로 상호작용 테스트는 상태 테스트보다 깨지기 쉽습니다.

기억하세요! '테스트 본문에는 테스트를 이해하는 데 필요한 정보를 모두 담아야 하며, 그와 동시에 눈을 어지럽히거나 관련 없는 정보는 담지 않아야 합니다.'

DRY를 고집하는 대신 테스트 코드는 DAMP(가 되도록 노력해야 합니다. DAMP는 '서술적이고 의미 있는 문구(Descriptive And Meaningful Phrase)를 뜻합니다. 단순하고 명료하게만 만들어준다면 테스트에서 다소의 중복은 괜찮습니다.

핵심은 테스트에서의 리팩터링은 반복을 줄이는 게 아니라 더 서술적이고 의미있게 하는 방향으로 이루어져야 한다는 점입니다.

13 테스트 대역

실제 구현을 선호하는 테스트 방식을 고전적 테스트(classical test)라고 하며, 반대로 모의 객체 프레임워크를 선호하는 테스트 방식은 모의 객체 중심주의 테스트(mockist test)라고 합니다.
소프트웨어 업계에는 모의 객체를 예찬하는 사람도 있지만 구글은 모의 객체 중심주의는 확장하기 어렵다고 결론지었습니다.

구글은 오랜 경험을 통해 상태 테스트에 집중해야 훗날 제품과 테스트를 확장할 때 훨씬 유리하다는 사실을 깨달았습니다. 깨지기 쉬운 테스트가 줄어들고 나중에 테스트를 변경하거나 유지보수하기가 쉬워집니다.

14 더 큰 테스트

느릴 수 있습니다. 구글에서 대규모 테스트의 기본 타임아웃 값은 15분이나 1시간입니다. 심지어 몇 시간이나 며칠이 걸리는 테스트도 만들어 활용합니다.
밀폐되지 않을 수 있습니다. 대규모 테스트는 다른 테스트나 최종 사용자와 자원 및 트래픽을 공유하기도 합니다.
비결정적일 수 있습니다. 예컨대 밀폐되지 않은 대규모 테스트라면 다른 테스트나 사용자 상태에 영향을 받을 수 있어서 완벽히 결정적이라고 보장하기가 거의 불가능합니다.

더 큰 테스트들은 시스템 '전체'가 의도대로 동작한다는 확신을 더해주는 역할을 합니다.

더 큰 테스트가 존재하는 첫 번째 이유는 바로 충실성을 높이기 위함입니다. 충실성(fidelity)이란 테스트가 대상의 실제 행위를 얼마나 충실하게 반영했느냐를 나타내는 속성입니다.

더 큰 테스트의 핵심은 이 사이에서 가장 적합한 지점을 찾아내는 것입니다. 충실성이 높아질수록 비용이 커져서 (특히 프로덕션의 경우) 테스트 실패 시 입는 손해도 크기 때문입니다.

더 큰 테스트가 극복해야 할 과제가 두 가지 더 있습니다.
첫 번째는 소유권 문제입니다.
두 번째 과제는 표준화 혹은 표준화 부족입니다.

15 폐기

우리는 이주를 순차적으로 진행하여 궁극적으로는 낡은 시스템을 완전히 걷어내는 과정을 폐기(deprecation)라 합니다.

장기간 운영되는 소프트웨어 생태계에서는 폐기 계획을 세우고 올바로 실행해야 합니다. 그러면 시스템을 운영하고 업그레이드하는 과정에서 수많은 중복 투자와 복잡성을 줄여줍니다. 결국은 자원을 아끼고 개발 속도를 높여주죠. 반면 제대로 폐기시키지 못하면 시스템을 방치할 때보다 더 큰 비용을 치를 수 있습니다.

'코드는 자산이 아니라 부채다'라는 기본 전제에서 시작합니다.

폐기는 시대에 뒤처졌음을 보여줄 수 있고 비슷한 기능의 대체재가 존재하는 시스템에 적합합니다.

폐기는 중요하지만 고통스러운 작업이라 항상 쉽지 않았다. 고통스러워서 이 부분을 미루는 부분이 있는데 장애는 항상 발생하니까 적극적으로 대응하려는 움직임처럼 폐기에 대해서도 구글은 적극적으로 폐기를 하기 위해서 움직인다는 생각이 들었고 미룰수록 더 고통스러워지기 때문에 이 접근 방법이 더 옳다고 느껴졌다.

다른 엔지니어링 활동들처럼 소프트웨어 시스템의 폐기 계획 역시 시스템을 처음 구축할 때 함께 세워둘 수 있습니다.

언젠가는 이루어질 폐기가 매끄럽게 진행되게 하려면 시스템을 설계할 때 무엇을 고려해야 할까요? 다음 두 질문은 구글에서 엔지니어링팀에 권장하는 고려사항입니다.
내 제품의 고객이 잠재적인 대체품으로 이주하기가 얼마나 쉬울까?
내 시스템을 한 부분씩 점진적으로 교체하려면 어떻게 해야 할까?

시스템을 만들 때 폐기 계획은 세워둔다는 것도 같은 맥락으로 흥미로운 부분이다. 만들때 폐기할 고민을 같이하지는 않는 법이니까... 공감은 하지만 아직은 방법은 다 이해하지 못했다.

우리는 폐기 유형을 크게 '권고'와 '강제'로 구분합니다.
권고 폐기(advisory deprecation)는 기한이 없고 조직에서도 우선순위가 높지 않은(혹은 폐기 작업에 자원을 투입할 의지가 없는) 경우입니다. 담당 팀은 고객들이 새로운 시스템으로 이주하기를 바라지만 서둘러 이주를 돕거나 옛 시스템을 바로 걷어낼 계획은 없습니다.
구글의 경험에 비춰보면 권고 폐기는 새로운 시스템이 사용자에게 주는 혜택이 매우 클 때 효과가 좋습니다. 단순히 사용자들에게 새로운 시스템의 출시를 알리고 자가 이주 도구를 제공하는 정도만으로도 이주가 빠르게 이루어집니다. 이때 혜택은 '정말 좋다' 정도가 아니라 '혁신적이다' 수준이어야 합니다. 어중간한 혜택으로는 사용자들이 이주를 주저할 것이며, 심지어 대폭적인 개선이 있더라도 권고 폐기만으로는 모두를 움직이게 하기는 힘들 것입니다.
더 적극적인 장려의 좋은 예가 바로 강제 폐기(compulsory deprecation)입니다. 강제 폐기는 대개 낡은 시스템의 지원 종료일을 못 받는 형태로 이루어집니다. 종료일 이후까지 이주를 끝내지 못한 시스템은 제대로 작동되지 않을 것입니다.

권고 폐기를 할 때 혜택이 "혁신적인 수준"이어야 한다는 부분이 가장 와닿았다.

사용자에게 전달되는 폐기 경고 메시지에 반드시 담겨야 할 특성이 두 가지 있습니다. 바로 실행 가능성(actionability)과 적시성(relevance)입니다.
해당 문제와 관련된 전문 지식을 갖춘 평균적인 엔지니어가 이론적으로 뿐 아니라 실질적인 조치를 취할 수 있다면 실행 가능한 경고입니다.
실행 가능한 경고라도 여전히 성가실 수 있습니다. 폐기 경고가 유용하려면 적시에 떠야 합니다. 사용자가 실제로 관련 동작을 수행할 때 경고가 뜬다면 적절한 때라고 할 수 있습니다.

이 부분도 요즘 시스템을 만들 때 고민하는 부분이라서 힘을 얻었다.

Part 4 도구

16 버전 관리와 브랜치 관리

제품 안정성 유지 차원에서 개발 브랜치를 과하게 사용하는 버전 관리 정책은 근본적으로 잘못되었다고 생각합니다. 결국은 똑같은 커밋들이 트렁크에까지 병합될 것입니다. 큰 단위로 한꺼번에 병합하기보다는 작게 작게 자주 병합하는 게 쉽습니다.

전혀 다른 접근 방법이 필요합니다. 바로 트렁크 기반 개발이죠. 대신 테스트와 CI를 적극 활용하여 모든 빌드와 테스트가 항상 성공하도록 관리하며, 완벽하지 않거나 테스트되지 않은 기능은 비활성화합니다. 엔지니어 개개인이 트렁크와 동기화하고 트렁크에 커밋해야 합니다.

개발 브랜치와 달리 릴리스 브랜치는 대체로 무해합니다. 브랜치라는 기술이 문제가 아니라 어떻게 활용하느냐가 문제인 것이죠.

구글의 DORA(DevOps Research and Assessment) 조직의 연구 결과에 따르면 최고 수준의 기술 조직에는 릴리스 브랜치조차 없습니다. 트렁크로부터 하루에도 몇 번씩 릴리스할 수 있는 지속적 배포(CD)가 잘 자리 잡은 조직에서는 대체로 릴리스 브랜치를 건너뜁니다.

구글의 소스 코드 대부분은 하나의 리포지터리, 즉 모노리포에서 관리되며 약 5만여 엔지니어에게 공유됩니다.

우리는 자체 개발한 중앙집중형 VCS를 이용합니다. 이름은 Piper이고, 80TB가 넘는 콘텐츠와 메타데이터를 담고 있습니다.

대부분은 이해하고 있는 부분이라서 쉽게 읽었다. 그래도 80TB짜리 모노레포의 개발 경험은 어떤지 궁금하긴 하다.

구글은 또한 '빌드 호라이즌(build horizon)'이라는 정책을 써서 잠재적인 버전 왜곡이 지속되는 기간의 상한선을 정해뒀는데, 톡톡한 효과를 보고 있습니다. 빌드 호라이즌이란 프로덕션 환경에서 구동 중인 모든 제품은 최대 6개월 안에 다시 빌드하여 재배포해야 한다는 정책입니다.(보통은 훨씬 자주 이루어집니다.)

배포를 정기적으로 할 수 있게 해야 한다는 것은 최근에 논의한 적이 있는 부분이라 재미있었다. 취지에는 동의하지만 어떻게 하는 게 좋을지는 아직 잘 모르겠다. 가만히 돌아가는 프로젝트를 갑자기 배포시킬 수도 없는 노릇이고...

누군가 충분히 거대하고 상호 호환되면서 독립적인 프로젝트들을 오픈소스로 구축하고, 이 프로젝트들을 가상 모노리포로 묶어 제공한다면 OSS 개발자들의 관행도 변화하기 시작할 거라 생각합니다.

선택에는 대가가 따릅니다. 우리는 원-버전 규칙을 적극 권장합니다. 개발자들이 어디로 커밋해야 할지, 혹은 어느 버전을 이용해야 할지를 선택할 수 없어야 합니다. 조직 전반에 이만큼 큰 영향을 주는 정책은 몇 없습니다. 개별 개발자에게는 성가실 수 있습니다. 하지만 종합적으로 보면 최종 결과가 훨씬 좋습니다.

18 빌드 시스템과 빌드 철학

구글 엔지니어에게 빌드 시스템은 사랑입니다. 구글은 엔지니어가 빠르게 안정적으로 빌드할 수 있도록 설립 초기부터 지금까지 자체 빌드 시스템을 구축하는 데 엄청나게 투자했습니다. 이 노력은 매우 성공적이어서 빌드 시스템의 핵심 구성요소인 Blaze는 퇴사한 구글 직원들이 몇 차례나 다시 구현하기에 이르렀습니다. 그리고 Blaze는 2015년 드디어 Bazel이라는 이름의 오픈 소스로 세상에 공개되었습니다.

태스크 기반 빌드 시스템은 빌드 스크립트가 커져서 복잡해질수록 다루기가 어려워집니다. 결국 '엔지니어에게는 너무 많은 힘을, 시스템에는 충분하지 못한 힘을 준다'라는 문제를 낳습니다.

태스크 기반 프레임워크에서는 성능, 정확성, 유지보수성 문제를 한꺼번에 해결할 수 있는 방법이 없습니다. 빌드 중에 실행되는 임의의 코드(빌드 스크립트)를 엔지니어가 작성할 수 있다는 것은 빌드를 빠르고 정확하게 수행하는 데 필요한 정보 일부가 누락될 수 있다는 뜻입니다. 이 문제를 풀려면 엔지니어 손에 주어진 힘 일부를 빼앗아 시스템에 맡겨야 합니다. 즉, 시스템이 태스크를 실행한다는 개념에서 벗어나 아티팩트를 만들어낸다는 쪽으로 이동해야 합니다.

엔지니어는 여전히 시스템에서 '무엇'을 빌드할지 정해줄 수 있지만 '어떻게'는 시스템이 알아서 하도록 맡기는 것입니다.
이 길이 바로 Blaze와 Blaze에서 파생된 다른 아티팩트 기반 빌드 시스템들(Bazel, Pants, Buck 등)이 선택한 길입니다.

빌드 시스템 교체는 비용이 많이 드는 일입니다. 프로젝트가 커질수록 비용이 커집니다. 그래서 구글은 새로 시작하는 프로젝트라면 거의 예외 없이 처음부터 Bazel 같은 아티팩트 기반 빌드 시스템을 이용하는 게 좋다고 믿습니다.

서드파티 아티팩트는 본질적으로 몇 가지 위험을 안고 있습니다. 첫 번째는 가용성 위험입니다. 서드파티 아티팩트 리포지터리에 접속할 수 없게 되면 외부 의존성을 다운로드할 수 없어서 빌드 전체가 멈춰버릴 것입니다. 두 번째는 보안 위험입니다. 공격자가 서드파티 시스템을 점령하면 우리가 참조하는 아티팩트를 공격자가 설계한 버전으로 대체하여 우리 빌드 결과에 악성 코드를 심을 수 있습니다.

태스크 기반 빌드 시스템도 싫어하진 않아서 그런지 아티팩트 기반에 대해서 완전히 이해하진 못했다. Bazel을 한번 봐야겠다 싶었다.

문제를 완전히 회피하는 방법이 더 있습니다. 바로 프로젝트에 필요한 의존성을 복사하여 프로젝트에 포함시키는 것입니다. 내 소스 코드뿐 아니라 의존성들도 소스 코드나 바이너리 형태로 리포지터리에서 직접 버전 관리한다는 뜻입니다. 이와 같이 외부 의존성을 자신의 관리 하에 두는 것을 벤더링(vendoring)이라고 합니다. 벤더링하면 외부 의존성 전부를 내부 의존성으로 변환하는 효과가 납니다. 구글은 이 전략을 씁니다.

Go언어의 벤더링이 여기서 왔구나 싶었다. 나는 선호하진 않는 편이다. 예전엔 잘 이해도 못했는데 이제는 그 취지는 이해하고 있다.

때로는 엔지니어의 힘과 유연성을 제한해야 생산성을 높일 수 있다라는 사실입니다. 구글의 요구조건을 충족하는 빌드 시스템을 개발하기 위해 우리가 선택한 방법은 엔지니어로부터 빌드 수행 방식을 정의할 수 있는 자유를 빼앗는 것이었습니다.

아까도 나왔던 일관성과 자유의 문제라고 생각한다. 치열하게 고민해야 하지만 구글의 경험처럼 자유를 빼앗는 대신 큰 혜택을 줄 수 있어야 한다는 생각이 더 확고해졌고 이건 자유를 뺏는 게 아니라고 회피성 생각도 하곤 했는데 이게 둘을 비교하고 자유를 뺏는 결정을 한다는 것을 더 인정해야 한다는 생각도 들었다.

19 Critique: 구글의 코드 리뷰 도구

이 도구가 이토록 성공적인 이유는 무엇일까요? '코드 리뷰는 개발자 워크플로의 핵심이다'라는 구글 개발 문화가 Critique(크리틱)에 녹아 있기 때문입니다.
간결성: Critique의 UI는 불필요한 선택을 줄여 코드 리뷰를 쉽게 진행할 수 있는 매끄러운 인터페이스를 제공합니다.
신뢰 제공: 코드 리뷰는 다른 이의 일을 늦추는 게 아니라 힘을 보태주는 활동입니다. 그러려면 동료를 최대한 신뢰해야 합니다.
익숙한 소통 방식: 소통 문제를 도구로 해결하기는 어렵습니다.
워크플로 통합: Critique은 다른 핵심 소프트웨어 개발 도구들에 통합할 수 있는 통합 포인트를 다양하게 제공합니다.

가이드 원칙 중 도구에 영향을 가장 크게 준 원칙은 아마도 간결성일 것입니다. 추가하면 재밌을 만한 기능 후보가 많았지만 소수의 사용자만을 위해 모델을 더 복잡하게 만들지 않도록 자제했습니다.

LGTM이 하나 이상, 충분한 수의 승인, 미해결 댓글이 0개라면 작성자가 변경을 커밋할 수 있습니다. 모든 변경에는 승인 개수와 상관없이 LGTM이 반드시 필요합니다. 적어도 두 명 이상이 검토하도록 하는 장치죠.

20 정적 분석

구글이 정적 분석의 효과를 극대화하기 위해 고군분투하며 얻은 교훈은 다음의 세 가지입니다.
1. 개발자 행복에 집중하자.
2. 정적 분석을 개발자 워크플로에 반드시 끼워 넣자.
3. 사용자가 기여할 수 있도록 하자.

21 의존성 관리

우리가 드릴 수 있는 조언을 하나만 꼽자면 '다른 조건이 모두 같다면 의존성 관리 문제보다는 소스 관리 문제를 택하라'라는 것입니다. '조직'의 의미를 더 넓게 정의해보는 것도 좋습니다. 즉, 팀보다는 회사 전체를 하나의 조직이라고 정의하면 도움이 될 때가 많습니다. 의존성 관리 문제보다는 소스 관리 문제가 생각하기도 훨씬 쉽고 처리 비용도 훨씬 저렴합니다.

프로그래밍 측면에서 보면 직접 처음부터 새로 짜는 것보다 기존 인프라를 재활용하는 게 분명히 더 낫습니다. 두말하면 잔소리고, 기술이 진보할 수 있는 근본에 속합니다.

시간을 고려하기 시작하면 몇 가지 복잡한 트레이드오프가 생겨납니다. '개발' 비용을 줄일 수 있다고 해서 의존성을 임포트하는 게 꼭 옳은 선택은 아닙니다. 특히 소프트웨어 엔지니어링 조직이라면 시간과 변경을 항시 염두에 두어야 합니다. 즉, 지속적인 유지보수 비용까지 감안해야 합니다.

구글의 Abseil 프로젝트는 Go와 많이 비슷하면서 시간에 대한 경고를 추가한 버전이라고 할 수 있습니다. 우리는 영원한 호환성을 약속하고 싶지 않습니다.
성능을 더 끌어낼 수 있다면 (그래서 구글 서비스 대부분이 혜택받을 수 있다면) 구현 세부사항과 ABI를 변경할 권한이 우리에게 주어진다는 뜻입니다.
이를 위해 Abseil은 온전한 ABI 호환성 대신 API 호환성을 다소 제한된 형태로 약속합니다. 정확하게는 호환되지 않는 API 변경도 잇을 수 있으나, 이때는 반드시 새 API로 마이그레이션해주는 자동 리팩터링 도구를 함께 제공할 것을 약속합니다.

구글 프로젝트들이 이용하는 의존성들의 압도적 다수를 구글이 직접 개발했습니다. 구글의 내부 의존성 관리의 대다수는 사실 진정한 의존성 관리가 아니라는 뜻입니다. 설계상 소스 코드 버전 관리에 해당하죠.

다시 말하지만, 버전 관리(프로젝트를 통제할 수 있음)가 의존성 관리(통제할 수 없음)보다 훨씬 쉽습니다.

아마 이부분이 제일 중요한 말인 것 같은데 구글은 모노레포를 쓰기 때문에 이쪽을 더 선택한다는 생각도 들었다.

의존성 관리 해법으로 네 가지를 제안합니다. 바로 '변경 불가', '유의적 버전', '하나로 묶어 배포학', '헤드에서 지내기'입니다.
유의적 버전(Semver)은 오늘날 의존성 네트워크를 관리하는 가장 대표적인 방법입니다. SemVer는 의존성 버전을 표기하는 보편적인 방식입니다.
하나로 묶어 배포하기는 우리 업계에서 수십 년 전부터 이용해온 강력한 의존성 관리 모델입니다. 애플리케이션 구도엥 필요한 의존성들을 모두 찾아서 애플리케이션과 함께 배포하는 방법이죠.
헤드에서 지내기(Live at Head)는 트렁크 기반 개발을 의존성 관리 영역까지 확장한걸로 보면 됩니다. 소스 관리 정책인 트렁크 기반 개발을 업스트림 의존성에까지 적용한 모델입니다.
헤드에서 지내기는 의존성 관리에서 시간과 선택이라는 요소를 제거하려는 시도입니다. 모든 컴포넌트가 항상 최신 버전에 의존하며, 의존하는 쪽에서 수용하기 어려운 형태의 변경은 절대 허용하지 않습니다. 의도치 않게 API나 행위가 달라지게 하는 변경은 일반적으로 다운스트림 의존성의 CI에서 포착되므로 커밋되지 않도록 합니다. 보안 문제 등으로 이런 변경을 피할 수 없는 상황도 있습니다. 그런 경우라면 다운스트림 의존성을 먼저 업데이트하거나 업데이트를 자동으로 수행해주는 도구가 제공될 때만 진행합니다.
요컨대 변경이 다운스트림 고객들에게 미치는 영향을 검증하는 부담이 API 제공자에게 주어집니다. API 제공자의 책임 범위가 대폭 넓어지죠. 책임 주체를 바꾸는 일이라 오픈소스 생태계가 이 길로 첫 발을 내딛게끔 동기를 부여하기가 쉽지는 않아 보입니다.

SemVer의 패치 버전(세 번째 바구니)은 내부 구현만 달라져서 이론적으로는 '안전한' 변경입니다. 구글에서 겪은 하이럼의 법칙과 완전히 반대되는 아이디어죠.
2018년에 Go 언어용 패키지 관리 시스템 구축에 대한 에세이 시리즈의 일환으로 구글의 러스 콕스는 SemVer 의존성 관리의 재미난 변형을 하나 소개했습니다. 바로 MVS, 즉 최소 버전 선택(Minimum Version Selection)입니다.
MVS는 반대로 선택합니다. liba 1.0이 libbase >= 1.7을 요구한다면 1.8이 존재하더라도 1.7부터 시도합니다. 이렇게 하면 '작성자가 개발할 당시에 이용한 의존성들을 가능한 한 충실하게 반영'하여 빌드됩니다.
미래를 100% 확신할 수 없다면 가능한 한 차이가 작게 버전업하는 게 가장 안전합니다.

MVS는 흥미로운데 이게 동작하나? 나중에 업데이트할 때는 어떻게 업데이트하지?라는 생각도 들었다. 한편 SemVer에 대해서도 더 깊게 생각해 보게 되었다.

MVS는 이론과 달리 '새로운 버전에는 항시 호환성을 해치는 변경이 포함될 수 있음'을 인정합니다. MVS를 사용하든 아니든 SemVer가 해결하려는 문제의 핵심을 잘 짚어준 것이죠. 소프트웨어 변경을 버전 번호로 압축해 표현하는 과정에서 정보가 일부 손실됩니다.

선한 의도로 라이브러리를 오픈 소스로 공개했는데 오히려 조직에 해를 끼치는 방식은 크게 두 가지입니다.
첫째, 구현 품질이 떨어지거나 제대로 관리하지 못하면 조직의 평판을 떨어뜨립니다.
둘째, 동기화를 유지할 수 없다면 선의의 릴리스가 엔지니어링 효율을 떨어뜨릴 것입니다.

오픈 소스 형태로든 소스는 공개하지 않는 라이브러리 형태로든, 코드를 외부 세계와 공유하는 일은 단순히 커뮤니티 기여나 비즈니스 기회로만 보고 접근할 문제가 아닙니다. 다른 조직에서 다른 우선순위를 가진, 여러분이 감시할 수 없는 사용자들이 결국 어떤 형태로든 여러분의 코드를 하이럼의 법칙으로 옭아맬 것입니다.

22 대규모 변경

구글에서 LSC(large scale change)의 상당 비중을 인프라팀들을 수행합니다. 하지만 LSC 도구들과 지원 자원들은 누구나 이용할 수 있습니다.

새로운 클래스, 함수, 시스템을 만든 후 사용자 모두에게 새 버전을 쓰도록 강제하지 않는 이유는 무얼까요? 이 방식이 더 쉬워보이지만 아쉽게도 여러 가지 이유로 확장성이 좋지 않다는 게 드러났습니다.
첫째, 하부 시스템을 구축하고 관리하는 인프라팀들은 그 시스템을 활용하는 수만 개의 참조를 수정하는 데 필요한 도메인 지식 역시 갖추고 있습니다. 인프라를 이용하는 팀들은 이런 마이그레이션을 처리하는 방법을 잘 모를 가능성이 큽니다.
둘째, 합당한 보상 없이 할 일만 늘어나는 상황을 좋아할 사람은 없습니다. 새 시스템이 기존 시스템보다 나은 건 확실하더라도 일반적으로 이는 조직 전체 관점에서의 이야기입니다. 팀에 따라서는 자발적으로 업그레이드할 만한 매력을 느끼지 못할 수도 있다는 뜻입니다. 반드시 마이그레이션해야 할 만큼 새로운 시스템이 중요하다면 비용을 조직 차원에서 부담하는 게 맞습니다.
셋째, 대규모로 변경해야 할 시스템을 소유한 팀이 주도해야 변경을 완료하는 데 유리합니다. 우리 경험상 유기적 마이그레이션은 완벽하게 성공하기 어렵습니다.

LSC가 처음인 작성자들은 영향받는 각 프로젝트의 소유자들이 리뷰해줄 거라고 생각할 것입니다. 하지만 대다수의 기계적인 LSC는 해당 변경의 본질과 필요한 빌드 자동화를 깊이 이해하는 전문가 한 명에 맡기는 게 효율적입니다.

23 지속적 통합

전통적인 지속적 빌드에서는 여러분이 만든 바이너리에서 변경된 기능을 테스트하지만 이를 확장하면 업스트림 마이크로서비스의 변경들도 테스트할 수 있습니다. 의존성이 함수 호출 스택에서 HTTP 요청이나 원격 프로시저 호출(RPC)까지 확장되는 것이죠.

프리서브밋 때는 어떤 테스트를 수행해야 할까요? 우리 경험에 따르면 빠르고 안정적인 테스트만 수행해야 합니다. 프리서브밋 때는 커버리지를 다소 포기하고, 대신 놓친 문제들을 포스트서브밋 때 잡아내는 전략입니다. 따라서 이따금 롤백할 수 있음을 감안해야 합니다.

TAP이 실행하는 테스트가 실패하면 다른 엔지니어의 작업에 방해되지 않도록 해당 변경을 빠르게 수정해야 합니다. 우리는 실패하는 테스트를 놔둔 채로 새로운 코드를 커밋하지 말라는 문화적 규범을 확립해 강조하고 있습니다(불규칙한 테스트가 골칫거리지만). 따라서 변경 하나가 TAP 빌드를 통과하지 못하면 그 팀은 후속 개발이나 새로운 릴리스를 하지 못합니다.

테스트 1,000개를 돌려야 하는 변경은 100개를 돌려야 하는 변경보다 바쁜 날에는 수십 분을 더 기다려야 합니다. 엔지니어들이 기다리는 시간을 줄이기 위해 더 작고 목표가 분명한 변경을 만들게 되어 모두에게 이로운 문화가 형성됩니다.

24 지속적 배포

제품 론칭은 절대 끝이 아닙니다. 내가 지금 어디에 와 있고, 다음에 고쳐야 할 가장 중요한 건 무엇이고, 진행 상황을 측정하고, 그다음 문제를 찾아 수정하는 학습 주기가 시작됐을 뿐입니다. 절대 끝은 없습니다. 이 사실을 깨닫는다면 위대한 성취를 이루게 될 것입니다.
- 데이비드 위클리, 전 구글 제품 매니저

지속적 배포(CD) 그리고 애자일 방법론의 핵심 교리는 작은 변경들을 자주 배포할수록 품질이 높아진다는 것입니다. '빠를수록 안전하다(faster is safer)'라는 말이죠.
* 민첩성: 자주, 작게 릴리스합니다.
* 자동화: 잦은 릴리스에 수반되는 반복 작업 부담을 줄이거나 없앱니다.
* 격리: 변경을 격리하여 문제를 쉽게 해결할 수 있도록 모듈화된 아키텍처를 지향합니다.
* 신뢰성: 비정상 종료와 지연시간 같은 주요 상태 지표를 측정하고 꾸준히 개선합니다.
* 데이터 중심 의사 결정: A/B 실험으로 상태 지표를 비교하여 품질을 높입니다.
* 단계적 출시: 변경을 모두에게 동시에 출시하지 않고 소수의 사용자부터 이용해보게 합니다.

소프트웨어는 애초에 복잡할 수밖에 없음을 결국은 인정해야 합니다. 완벽한 바이너리는 없습니다. 프로덕션에 새로운 변경을 릴리스할 때마다 무언가를 결정하고 절충해야 합니다.

두 번째는 '릴리스 열차 시간에 늦으면 기다리지 않고 출발할 것이다'입니다.
릴리스가 다가오면 어느 시점부터 여러분은 완고한 자세로 개발자와 그들이 들고 온 새로운 기능을 외면해야 합니다. 그 시점 이후로는 아무리 애원하고 구걸해도 이번 릴리스에 껴주지 않아야 합니다.

구글은 오랜 기간 수많은 소프트웨어 제품을 경험하며 (직관과 다르게) 빠를수록 안전하다는 사실을 깨우쳤습니다. 안정적인 제품과 빠른 개발은 서로 대척점에 서 있지 않습니다. 오히려 더 작게 변경해 자주 배포하는 제품이 품질도 우수합니다.

빠를수록 안전하다는 말 멋진 말이다. CD에 대한 아이디어를 더 많이 얻고 싶었는데 생각보다 그러지는 못했다.

25 서비스형 컴퓨트

열심히 코드를 작성했다면 이를 실행해줄 하드웨어가 필요합니다. 그래서 하드웨어를 구입하거나 임대해야 하죠. 이것이 서비스형 컴퓨트(CaaS, Compute as a Service)의 본절입니다. 여기서 컴퓨트란 프로그램을 실제로 실행해줄 연산 능력을 뜻합니다.

2002년 구글의 선임 엔지니어였던 제프 딘(Jeff Dean)은 릴리스 프로세스의 일환으로 자동화된 데이터 처리 태스크를 실행하는 일에 대해 다음과 같은 글을 남겼습니다.
[태스크 실행하기]는 자원 흐름을 관리하는 물류 작업과 비슷하며 시간 소모적인 악몽입니다. 현재 우리는 50대 이상의 머신 각각에 프로세스를 띄우고 경과를 모니터링해야 합니다. 머신 중 하나가 고장나면 작업을 다른 머신으로 자동으로 이전하여 수행할 방법이 없고, 작업 진척 상황을 모니터링하는 일 역시 임시방편적인 형태입니다. [...] 이뿐만 아니라 프로세스들이 서로 간섭할 수 있어서 사람이 작성한 복잡한 'sign up'파일을 이용해 컴퓨터 사용량을 제한하는 중인데, 이 때문에 스케쥴링이 효율적으로 못 이루어지고 부족한 컴퓨터 자원을 서로 이용하기 위해 더 치열하게 경합하게 됩니다.

2003년, 이렇게 풀로 나눠 쓰던 컴퓨트 서비스들을 하나의 거대한 풀로 통합하겠다고 하여 Borg 프로젝트가 출범했습니다.

Kubernetes의 시작점이 Borg의 역사도 더 알게 되었고 2002년부터 구글인 이런 노력을 하고 있었구나 싶었다. 이 십여년 간의 경험으로 Kubernetes를 만들었기 때문에 지금의 Kubernetes의 안정성과 설계도 생겼구나 싶었다.

2022/08/31 04:08 2022/08/31 04:08