한 줄 정의

스타일 가이드는 포맷팅 규정집이 아니라 ‘규모와 시간 양쪽에서 탄력적인 엔지니어링 환경’이라는 목표를 코드 차원에서 강제하는 법전 이며, 좋은 규칙은 양을 최소화하고, 읽는 사람에게 최적화하고, 일관성을 지키고, 위험한 구조를 막고, 필요하면 실용적 예외를 허용하며 — 무엇보다 사람이 아니라 도구가 자동으로 집행 해야 합니다.

쉽게 말하면

규칙(rule) 은 법입니다. 제안이나 권장사항이 아니라 코드 전반에서 강제되는 사항이라서, 꼭 필요하여 승인된 경우를 제외하고는 무시할 수 없습니다. 반면 지침(guidance) 은 권장사항과 모범 사례입니다. 따르면 이득이라서 어지간하면 따르라고 권하지만, 규칙과 달리 다소 변형해 적용해도 괜찮습니다. 규칙이 ‘반드시(must)‘라면 지침은 ‘되도록(should)‘입니다.

구글은 코딩할 때 따라야 하는, 혹은 하지 말아야 하는 규칙을 모아 프로그래밍 스타일 가이드 로 정리해 표준으로 삼았습니다. ‘스타일’이라는 단어 때문에 코드 포맷팅만 다룬다고 오해하기 쉽지만, 실제로는 구글의 코드를 지배하는 종합적인 규약 모음집입니다. 그렇다고 모든 것을 시시콜콜 규정하지는 않습니다. ‘합리적인 선에서 가능한 한 서술적인 이름을 사용하라’처럼 엔지니어가 스스로 고민해 판단하도록 맡기는 규칙도 있습니다.

이 장의 핵심은 규칙 목록 자체가 아니라 규칙을 만들고, 고치고, 적용하는 메타 프로세스 입니다. 규칙 모음을 정의할 때 던져야 하는 질문은 ‘무슨 규칙이 필요하지?‘가 아니라 ‘어떤 목표를 이루려 하지?’ 입니다. 목표에 집중하면 규칙은 따라옵니다.

왜 중요한가?

규칙을 관리하는 목표는 ‘좋은’ 행동을 장려하고 ‘나쁜’ 행동을 억제하는 것인데, 좋고 나쁨의 해석은 조직마다 다릅니다. 어떤 조직은 메모리 사용량을 줄이는 패턴을 ‘좋다’고 보고, 어떤 조직은 최신 언어 기능 활용을, 또 어떤 조직은 일관성을 최우선으로 봅니다. 그래서 규칙과 지침은 조직이 추구하는 가치를 먼저 파악한 뒤 그 가치를 기준으로 정해집니다. 규칙은 일상의 개발 패턴을 조직이 원하는 방향으로 슬쩍 밀어주는 장치입니다.

확립된 규칙과 지침은 조직이 커지더라도 일관되게 통용되는 공통의 코딩 어휘 가 되어줍니다. 어휘가 통일되면 엔지니어들은 코드를 표현하는 ‘형식’보다 코드에 담을 ‘내용’에 집중할 수 있고, 무의식적으로도 ‘좋은’ 코드를 작성하는 경향이 생깁니다.

구글의 맥락은 극단적입니다. 엔지니어만 3만 명이 넘고, 기술 수준과 배경도 다양하며, 수십 년을 더 존재할 가능성이 큰 20억 라인 이상의 코드베이스에 매일 약 6만 건의 코드가 서브밋됩니다. 그래서 구글의 규칙 모음은 다른 조직과 다른 가치 — ‘규모와 시간 양쪽 측면에서 탄력적인 엔지니어링 환경이 지속되도록 하는 것’ — 에 최적화되어 있습니다. 규칙 중 상당수가 엔지니어의 자유를 제한하는 트레이드오프를 동반하지만, 권위 있는 표준은 일관성을 높여주고 의견 대립을 줄여주므로 혜택이 더 큽니다.

핵심 내용

규칙을 만들 때의 다섯 가지 원칙

구글이 규칙을 만들 때 염두에 두는 원칙은 다섯 가지입니다.

  • 규칙의 양을 최소화합니다.
  • 코드를 읽는 사람에게 맞춥니다.
  • 일관되어야 합니다.
  • 오류가 나기 쉽거나 예상치 못한 동작을 유발하는 구조를 피합니다.
  • 꼭 필요하다면 실용성을 생각해 예외를 허용합니다.
규칙의 양을 최소화한다

모든 걸 스타일 가이드에 욱여넣어서는 안 됩니다. 규칙이 너무 많으면 다 기억하지 못하고, 새로 합류한 엔지니어가 적응하기도 어려우며, 규칙 모음 자체의 관리 비용도 커집니다. 그래서 구글은 너무 자명한 규칙은 의도적으로 배제 합니다. 예를 들어 C++ 스타일 가이드에는 goto 사용에 관한 규칙이 없습니다. C++ 프로그래머들이 이미 금기시하기 때문에 불필요한 낭비를 없앤 것입니다. 한두 명의 잘못 때문에 새 규칙을 만들면 나머지 모두에게 정신적 부담을 주게 되어 조직 규모를 확장할 수 없습니다.

'너무 많다'의 기준은 규칙의 수가 아니라 기억해야 하는 수

도구 지원이 핵심입니다. clang 형식(clang-format)이 쓰이기 이전 엔지니어들은 수많은 포맷팅 규칙을 외우고 있어야 했습니다. 그 규칙들은 여전히 존재하지만 도구가 대신 집행해주면서 준수 비용이 극적으로 낮아졌습니다. 누군가 포맷팅 규칙을 추가하더라도 아무도 신경 쓰지 않는 경지에 도달한 것입니다.

읽는 사람에게 맞춘다

코드는 작성되는 횟수보다 읽히는 횟수가 더 많으며 시간이 지날수록 차이가 벌어집니다. 따라서 읽기 난해한 것보다 타이핑하기 지루한 편이 낫습니다. 파이썬의 조건부 표현은 if문보다 짧아서 작성자에게 편하지만 읽는 사람이 이해하기 어려워서 구글은 사용하지 못하게 했습니다. ‘쓰기에 간편한’ 것보다 ‘읽기에 간단한’ 쪽에 가치를 둔 트레이드오프입니다. 앞으로 그 코드를 읽을 모든 이에게 더 읽기 좋은 코드를 선물하기 위해 당장의 불편함을 감내하는 것입니다.

독자 중심주의의 일환으로 구글은 엔지니어가 의도한 행위를 분명하게 알려주는 증거를 코드에 남기라고 요구합니다. 자바·자바스크립트·C++ 가이드는 슈퍼클래스 메서드를 오버라이드할 때 어노테이션이나 키워드를 반드시 사용하도록 했습니다. 설계 의도를 소스 코드에 명시하지 않으면 읽는 사람이 직접 파악해야 해서 그 코드를 활용하는 모든 이에게 부담이 전가됩니다.

C++의 포인터 소유권이 대표적인 사례입니다. 익숙하지 않은 함수에 포인터가 전달되면 소유권이 호출한 쪽에 있는지, 함수에 넘겨진 것인지, 반환 후에도 계속 사용할 수 있는지 코드만 봐서는 알 수 없습니다. 그래서 구글 C++ 가이드는 소유권을 넘길 목적이라면 std::unique_ptr을 사용하라고 권합니다.

// Foo*를 받는 함수. 전달받은 포인터의 소유권이
// 누구에게 있는지를 알 수 없다.
void TakeFoo(Foo* arg);
 
// 함수 호출이 끝난 후 소유권을 누가 가지게 될지에 대해
// 코드를 읽는 이에게 아무런 정보도 주지 않는다.
Foo* my_foo(NewFoo());
TakeFoo(my_foo);
// std::unique_ptr<Foo>를 받는 함수
void TakeFoo(std::unique_ptr<Foo> arg);
 
// 함수를 호출하면 소유권이 이전되고
// 함수 반환 후에는 unique_ptr을 더 이상 사용할 수 없음을 명확하게 알려준다.
std::unique_ptr<Foo> my_foo(FooFactory());
TakeFoo(std::move(my_foo));

이 규칙을 준수하면 소유권 이전 여부를 명시한 근거가 모든 호출 지점에 자연스럽게 드러납니다. 목적은 ‘현 위치에서 추론하기’ — 다른 코드를 찾아보거나 함수 구현부를 들여다보지 않고도 호출 지점에서 무슨 일이 벌어지는지 명확히 이해할 수 있게 하는 것입니다.

주석 규칙도 같은 목표를 뒷받침합니다. 문서화 주석(파일·클래스·함수 앞의 블록 주석)은 뒤따르는 코드의 설계와 의도를 설명하고, 구현 주석(코드에 산재하는 주석)은 뻔하지 않은 선택의 이유를 해명하거나 까다로운 로직을 설명합니다.

일관되어야 한다

코드베이스의 일관성을 바라보는 시각은 구글이 지역별 연구소에 적용하는 철학과 같습니다. 어느 연구소에서든 출입카드가 다 읽히고, 와이파이에 곧바로 접속되고, 회의실 화상회의 설정이 똑같아서 연구소를 옮겨 다니며 업무를 보기가 아주 쉽습니다. 제약이 과하다고 느낄 수 있지만, 일관성이 주는 이점은 잃게 되는 자유의 가치보다 훨씬 큽니다.

코드가 일관되면 엔지니어와 읽는 이들은 ‘어떻게’ 표현하느냐가 아닌 ‘무엇을’ 수행하느냐에 집중할 수 있습니다. 코드가 의미 단위로 잘 구분되어 분석하기 쉬워지고, 모듈화하거나 중복을 찾기도 쉽습니다. 들여쓰기 공백 수나 한 줄의 최대 길이에 제한을 걸어둔 것도 같은 맥락입니다. 여기서 값진 부분은 제한한 특정 수치가 아니라 ‘단 하나의 답’만 사용한다는 일관성 입니다.

일관성이 안겨주는 이점은 세 가지 측면에서 나타납니다.

  • 규모 확장: 코드가 일관되면 코드를 이해·수정·생성하는 도구를 만들기 쉽습니다. 누락된 임포트문을 추가하거나 쓰이지 않는 임포트문을 제거하는 도구가 있어도 프로젝트마다 임포트문 정렬 방식이 다르면 제대로 작동하지 않는 프로젝트가 생깁니다.
  • 인력 운용: 일관된 코드베이스는 팀 이동 시 적응 시간을 단축하고, SRE·라이브러리 엔지니어·코드 관리인(code janitor)처럼 여러 프로젝트를 동시에 다루는 역할의 사람들이 익숙하지 않은 프로젝트의 코드를 살피고 수정할 수 있게 해줍니다.
  • 시간 관점의 탄력성: 엔지니어가 떠나고 합류하고, 코드 소유권이 옮겨가고, 프로젝트가 합쳐지거나 나뉘는 전환 비용이 낮아져 장기적으로 유지보수 프로세스가 간소화됩니다.

규모가 커지면서...

수년 전 구글의 C++ 스타일 가이드는 “때로는 합리적인 근거를 들어 특정 스타일 규칙을 변경하자는 제안이 들어오지만, 그럼에도 우리는 기존의 일관성을 지키기 위해 현재의 규칙들을 견지할 것입니다”라고 약속했습니다. 하지만 코드베이스가 커지면서 인식이 달라졌습니다. 대규모 변경(Large Scale Change) 도구와 프로세스로 거의 모든 코드를 새 패턴에 맞게 고칠 수 있지만 이 메커니즘도 완벽하지는 않으며, 코드베이스가 커지면 이전 코드 전부가 새 모범 사례를 준수할 수 있을지 확신할 수 없습니다. 결국 완벽한 일관성은 추구하는 비용이 얻는 것보다 큰 단계에 도달했고, C++ 코드베이스 전체가 완전히 일관되는 일은 다시는 없을 것이며 그것을 목표로 삼지도 않겠다고 공표했습니다.

표준을 정할 때 구글은 대체로 사내 일관성을 외부 규약보다 중요하게 생각하며, 일관성에 위계를 둡니다. 파일 하나에서의 일관성이 팀의 규약보다 우선하고, 팀의 규약이 프로젝트보다, 프로젝트가 전체 코드베이스보다 우선합니다. 스타일 가이드에는 과학적·기술적 선택보다 지역적 규약을 따르라고 명시한 규칙이 제법 많습니다.

공백 개수

구글의 파이썬 스타일 가이드는 원래 들여쓰기에 공백 두 개를 쓰도록 했습니다. 외부 표준은 네 개를 권장하는데도 말이죠. 초창기 구글의 파이썬 코드 대부분이 C++ 프로젝트를 지원하는 목적이어서 C++처럼 두 개를 쓴 것입니다. 시간이 흐르자 이 논리가 더는 합리적이지 않게 변했습니다. 파이썬 엔지니어들이 외부 코드를 참고할 때마다 이질감을 느꼈고, 내부 코드를 오픈 소스로 공개할 때마다 바깥 표준에 맞춰 코드를 다듬느라 시간을 허비했습니다. 결국 스타라크(Starlark) 언어용 스타일 가이드를 만들 때는 공백 네 개를 사용하도록 수정하여 바깥세상과 일치시켰습니다.

일반적으로는 규약을 바깥세상과 일관되게 잡는 편이 유리합니다. 작고 독립적이고 수명이 짧은 코드라면 내부 일관성이 훨씬 중요하지만, 수명이 길고 확장될 가능성이 큰 코드라면 언젠가 외부 코드와 상호작용하고 바깥세상으로 나갈 수도 있으므로 길게 보면 널리 쓰이는 표준을 따르는 게 유리합니다.

오류를 내기 쉽거나 예상과 다르게 동작할 여지가 있는 구조는 피하자

복잡한 기능에는 언뜻 봐서는 지나칠 수 있는 미묘한 함정이 숨어 있는 경우가 많습니다. 정확하게 이해하지 못한 채 사용하면 오용하여 버그를 유발하기 쉽고, 정확히 이해하고 사용했더라도 나중에 합류한 팀원이나 유지보수 담당자가 같은 수준으로 이해할지는 보장할 수 없습니다.

그래서 구글의 파이썬 스타일 가이드는 리플렉션 함수인 hasattr()과 getattr() 사용을 제한합니다. 다음 코드를 보면 문제가 드러납니다.

# some_file.py
A_CONSTANT = [
    'foo',
    'bar',
    'baz',
]
# other_file.py
values = []
for field in some_file.A_CONSTANT:
    values.append(getattr(my_object, field))

other_file.py의 코드만 보고는 foo, bar, baz라는 필드에 접근한다는 사실을 알 수 없습니다. 읽는 이는 명확한 증거를 찾을 수 없고, 어떤 문자열이 쓰였는지 바로 알 수 없으므로 객체에서 어떤 속성을 읽어가는지도 쉽게 검증할 수 없습니다. 만약 문자열을 A_CONSTANT가 아닌 원격 프로시저 호출(RPC)이나 다른 데이터 저장소로부터 가져왔다면, 메시지 확인만 잘못해도 눈치 채기 매우 어려운 중요한 보안 결함 으로 이어질 수 있습니다. 사실 hasattr()과 getattr()이 유용하게 쓰이는 상황은 매우 드물며, 대부분의 경우 코드를 읽기 어렵게 하고 버그를 꼬이게 할 뿐입니다.

이런 고급 기능은 잘 이해하고 활용하는 전문가에게는 이상적인 해법일 수 있지만 일반적으로는 이해하기 더 어렵고 널리 쓰이지 않습니다. 구글은 코드베이스가 특정 전문가의 전유물이 아닌 모든 엔지니어의 작업 공간 이 되길 원합니다. 프로덕션 서비스에 장애가 생기면 SRE는 능숙하게 다루지 못하는 언어로 작성된 코드라도 살펴봐야 하기 때문입니다.

실용적 측면을 인정하자

랠프 월도 에머슨, 『자기신뢰(Self-Reliance)』(1841)

“어리석게 일관성만 고집한다면 편협한 홉고블린과 다를 바 없습니다.”

일관되고 단순한 코드베이스를 추구한다고 해서 그 외 모든 것을 맹목적으로 무시하지는 않습니다. 꼭 필요하다면 최적화나 실용성을 위해 예외를 허용합니다.

  • 성능: C++ 스타일 가이드는 예외 처리를 허용하지 않지만, 예외 처리 관련 컴파일러 최적화를 가능하게 해주는 noexcept 지정자는 적절한 상황에서 사용하도록 허용합니다.
  • 상호운용성: 기본 명명 규칙은 카멜 케이스지만 표준 라이브러리 기능을 모방하는 기능에 한해서는 스네이크 케이스 이름을 허용합니다. 구글의 오픈 소스 라이브러리 Abseil이 C++ 14 표준 타입을 C++ 11과 호환되도록 구현하면서 C++ 표준 형식인 스네이크 케이스를 쓴 것이 실제 예입니다. 일부 윈도우 플랫폼 기능을 활용하기 위한 다중 구현 상속도 어쩔 수 없이 허용합니다.
  • 자동 생성 코드: 자바와 자바스크립트 스타일 가이드는 빌드 과정에서 자동 생성된 코드는 규칙을 적용받지 않는다고 명시했습니다. 프로젝트의 통제권 밖에 있는 외부 컴포넌트와 자주 인터페이스하기 때문입니다. 일관성은 매우 중요하지만 융통성이 없어서는 안 됩니다.

스타일 가이드 규칙의 세 범주

모든 스타일 가이드 규칙은 세 범주로 나눌 수 있습니다.

  • 위험을 피하기 위한 규칙
  • 모범 사례를 적용하기 위한 규칙
  • 일관성을 보장하기 위한 규칙
위험 회피하기

기술적인 이유 때문에 반드시 써야 하거나 쓰면 안 되는 언어 특성들에 관한 규칙입니다. 정적 멤버와 변수, 람다식, 예외 처리, 스레드와 접근 제어, 클래스 상속 등의 사용법을 설명합니다. 특히 사용하기 어렵거나 올바르게 사용하기가 까다로운 기능 — 직관적이지 않거나 아주 세심한 데까지 주의해야 미묘한 버그를 피할 수 있는 기능 — 에 관한 판단을 담습니다. 가이드에 담긴 판단 각각에는 결론에 이르기까지 고려한 장점과 단점, 어디에 무게를 두었는지가 설명되어 있으며, 대부분 세월이 흘러도 관리 가능한 코드 에 우선순위를 두고 내려졌습니다.

모범 사례 강제하기

코드 작성 시 모범 사례를 반드시 따르도록 강제하는 규칙입니다. 작성자의 의도가 코드 자체에 명확하게 드러나지 않는 경우 — switch문의 fall-through, 빈 catch 블록, 템플릿 메타프로그래밍 — 에는 의도를 주석으로 남겨야 합니다. 소스 파일의 구조, 패키지·클래스·함수·변수의 이름을 짓는 규칙도 있습니다. 가독성을 높이기 위한 포맷팅 규칙(수직·수평 공백, 한 줄 최대 길이, 괄호 정렬)도 이 범주이며, 언어에 따라서는 자동 포맷팅 도구(Go의 gofmt, 다트의 dartfmt)를 반드시 사용하게 합니다.

새롭거나 아직 널리 이해되지 못한 언어 기능을 제한하기도 합니다. 엔지니어 전반이 해당 기능을 제대로 습득할 때까지 선제적으로 방어선을 치는 것이고, 동시에 업계에서 모범 사례가 만들어지기를 기다리는 효과도 있습니다. 기능이 퍼지면 엔지니어들이 예시 코드를 들고 와 면제를 요청하는데, 이 요청들을 관찰하다 보면 기능이 어떻게 사용되는지 파악할 수 있고 나쁜 패턴과 좋은 패턴을 구분할 예가 쌓입니다. 이 단계에 이르면 기존 규칙을 돌아보며 신기능을 활용할 수 있는 범위를 넓혀줍니다.

사례 연구: std::unique_ptr 도입

std::unique_ptr은 C++ 11에서 표준으로 소개됐지만 구글은 초기에 이 기능을 사용하지 못하게 했습니다. 동작 방식이 엔지니어 대부분에게 익숙하지 않았고, 함께 도입된 move 시멘틱 역시 매우 새로워서 많은 엔지니어가 혼란스러워했기 때문입니다. 검사 도구를 업데이트하여 허용하지 않는 타입을 참조하는 코드를 잡아냈습니다.

시간이 흘러 엔지니어들이 move 시멘틱에 적응하자, std::unique_ptr이 객체 소유권이 어디에 있는지 명확히 알려주어 코드 이해를 돕는다는 — 구글 스타일 가이드의 목표에 부합한다는 — 확신이 강해졌습니다. 새로운 타입과 move 시멘틱이 높이는 복잡성은 여전히 우려 대상이었지만, 장기적으로 코드베이스 품질이 크게 개선될 것이라서 비용을 치르고서라도 도입하는 게 이득이라 판단했습니다.

일관성 구축하기

사소한 문제를 다루는 규칙도 아주 많습니다. 명명 규칙, 들여쓰기 공백 수, 임포트문 순서 같은 것은 어떤 선택을 하든 명확하고 가시적인 차이가 없는 게 보통입니다. 기술 커뮤니티에서 논쟁이 끊이지 않는 이유가 바로 이런 특성 때문일 것입니다. 이 범주 규칙의 목적은 단순히 결정을 내리고 그 결정을 문서로 남기는 것 입니다. 구글은 하나를 선택함으로써 끝없는 논쟁에서 벗어나 더 중요한 일로 시선을 돌릴 수 있게 했습니다. 구글 엔지니어들은 공백을 두 개 쓰냐 네 개 쓰냐로 시간을 낭비하지 않습니다. 이 규칙들은 ‘무엇’을 선택했느냐가 아니라 ‘선택을 했다’는 사실 에 의의가 있습니다.

파킨슨의 사소함의 법칙(law of triviality)

중요한 사안을 고민하는 데는 아주 적은 시간만 들이면서 사소한 일에는 필요 이상의 시간과 노력을 들이는 현상입니다. 명명·공백·임포트 순서 논쟁이 그 좋은 예입니다.

그 외…

스타일 가이드에 없는 것도 많습니다. 구글은 코드베이스의 건실성에 영향을 가장 크게 주는 규칙들에 집중합니다. ‘너무 똑똑하게 짜지 말자’, ‘코드베이스를 포크하지 말자’, ‘바퀴를 다시 발명하지 말자’ 같은 기본적인 엔지니어링 조언은 확실한 모범 사례인데도 포함하지 않았습니다. 스타일 가이드 같은 문서만으로 초심자를 소프트웨어 엔지니어링 숙련자 수준까지 끌어올릴 수는 없으므로 일부러 모든 것을 집어넣지 않은 것입니다.

규칙 수정하기

스타일 가이드는 고정불변이 아닙니다. 세월이 흐르면 결정 당시와 내부 사정이 달라지고 결정에 영향을 준 요인들도 변합니다. 엔지니어들이 특정 규칙을 우회하는 데 에너지를 쓰고 있다면 그 규칙에 기대했던 트레이드오프를 재검토해야 하고, 규칙 적용을 도와주는 도구가 너무 복잡해지고 관리하기 부담된다면 규칙 자체가 쓸모 없어졌다는 신호일 수 있습니다.

핵심 장치는 결정 근거의 문서화 입니다. 구글 스타일 가이드의 규칙에는 각각의 결정을 뒷받침하는 근거가 명시되어 있습니다. 규칙을 추가할 때 장단점과 잠재적 파장을 분석하고, 결정을 실행하는 데 수반되는 변경량이 구글 규모에서 무리가 없는지 검증하는 데 많은 시간을 씁니다. 각 결정에 이른 근거를 문서로 남겨두면 규칙을 변경해야 할 때가 언제인지 알아내기 쉬워집니다. 결정에 영향을 준 요인들이 명확하게 기록되어 있다면, 그 요인 중 하나 이상이 변했는지 여부가 바로 규칙을 다시 평가해야 한다는 신호가 되어 줍니다.

사례 연구: 카멜 케이스 명명법

구글은 파이썬 스타일 가이드를 처음 정의할 때 메서드 이름에 PEP 8과 달리 카멜 케이스를 쓰기로 했습니다. 당시 파이썬은 주로 C++ 개발자가 C++ 코드베이스 위에서 동작하는 스크립팅 계층용으로 사용했고, 파이썬 타입 상당수가 밑단의 C++ 타입을 감싼 것이어서 언어 간 일관성이 더 중요하다고 판단한 것입니다.

시간이 흐르면서 순수 파이썬 프로젝트가 훨씬 많아졌습니다. 순수 파이썬 엔지니어들은 외부 코드를 참조할 때마다 다른 표준에 맞춰줘야 했고, 새로 고용한 파이썬 개발자의 적응도 방해받았습니다. 서드파티 라이브러리를 쓰는 프로젝트에서는 카멜 케이스와 스네이크 케이스가 혼재하는 코드가 만들어졌고, 오픈 소스로 공개한 프로젝트에는 외부 커뮤니티가 구글의 방식에 낯설어하며 경계했습니다.

결국 비용(다른 구글 코드와의 일관성 상실, 기존 방식에 익숙한 직원 재교육)과 이점(다른 파이썬 코드와의 일관성 확보, 이미 규칙에 어긋나 있던 코드 수용)을 놓고 토론한 끝에 규칙을 수정했습니다. 스네이크 케이스를 허용하되 하나의 파일 안에서는 일관되어야 한다는 제한 을 걸고, 기존 코드에는 면책권을 주었으며, 프로젝트별로 가장 적합한 방식을 선택할 수 있게 했습니다.

프로세스

구글의 스타일 가이드 수정 프로세스는 해법을 중심으로 돌아갑니다. 먼저 현재 문제를 찾아내 설명한 다음 해법을 보여주는 형식인데, 여기서 ‘문제’는 잠재적인 가능성을 이야기하는 가상의 예가 아니라 현존하는 구글 코드에서 발견된 패턴으로 입증 해야 합니다. 기존 결정에는 상세한 근거가 서술되어 있으므로, 문제가 주어지면 현시점에서는 다른 결론에 도달할 수 있는지 다시 평가해볼 수 있습니다.

규칙이 수정되어야 할 시점을 알아차리기에는 스타일 가이드에 입각해 코드를 작성하는 엔지니어들의 커뮤니티가 가장 유리합니다. 실제로 수정 대부분이 스타일 가이드 관련 토론 전용의 언어별 메일링 리스트에서 시작됩니다. 제안은 수정할 문구까지 명시된 완벽한 형태일 수도 있고 모호한 질문에서 시작할 수도 있으며, 커뮤니티에서 논의되고 다른 언어 사용자들로부터도 피드백을 받은 뒤 검토를 거친 제안이 최종 승인 단계로 넘어갑니다.

스타일 중재자

구글의 스타일 가이드들은 언어별로 소유자가 따로 있어서 최종 결정과 승인을 책임집니다. 이 소유자들을 스타일 중재자(style arbiter) 라고 부릅니다. 언어별로 경험 많은 전문가 그룹 — 해당 언어용 라이브러리팀의 선임이거나 언어 경험이 풍부한 오랜 구글 직원 — 이 결정권자 역할을 합니다.

수정 여부는 제안에 따르는 엔지니어링 측면의 트레이드오프를 논의하여 결정하며, 중재자는 스타일 가이드가 지향하는 목표에 입각해 판단합니다. 개인의 취향이 아닌 트레이드오프가 기준 입니다. 현재 C++ 스타일 중재자 그룹은 4명인데, 다수결이라면 홀수가 유리한데도 짝수인 이유는 수정이 투표가 아니라 합의 로 이루어지기 때문입니다.

예외

규칙들은 대체로 더 범용적으로 쓰일 수 있게 설계되므로, 특정 상황에서는 일부 규칙을 적용하지 않는 게 이득일 때도 있습니다. 그런 경우 스타일 중재자들과 협의하여 면제하는 편이 정말 나은지 결정합니다. 규칙을 따르기보다 예외를 인정하는 쪽이 이득이라고 판단될 때만 예외를 허용하며, 가볍게 이루어지지 않습니다.

  • C++ 매크로 API는 전역 이름공간에 놓이므로 프로젝트 고유 접두어를 반드시 붙여야 하지만, 진짜로 전역적으로 쓰이는 유틸리티 매크로 일부에는 예외를 인정해놓았습니다. 매크로 이름이 너무 길다거나 프로젝트 일관성을 맞추기 위해서 같은 단순 선호 이유의 면제 요청은 받아들여지지 않습니다. 코드베이스의 무결성이 프로젝트의 일관성보다 중요 하기 때문입니다.
  • 암묵적 형변환 불허 규칙(단일 인수 생성자 불허 포함)의 경우, 다른 타입을 투명하게 감쌀 목적으로 설계된 래퍼 타입은 내부 데이터가 정확하게 표현되므로 면제될 수 있습니다.

합당한 면제 사례가 많아진다면 규칙을 다시 고민하여 더 명확하게 가다듬거나 수정해야 한다는 신호일 수 있습니다. 다만 암묵적 형변환 규칙은 면제 요청 중 많은 수가 실제로는 투명한 래퍼 타입이 아니거나 래퍼 타입 자체가 필요 없는 경우였기에 규칙을 유지하기로 결정되었습니다.

지침

규칙과 더불어 구글은 다양한 형태의 프로그래밍 지침(guidance) 도 관리합니다. 지침이란 엔지니어링 경험에서 선별한 지혜이자 과거로부터 배운 교훈들로 추린 모범 사례를 문서로 남긴 것으로, 주로 사람들이 자주 실수하는 것 혹은 아직 익숙지 않은 새로운 주제에 집중합니다.

  • 언어 입문서(primer): 스타일 가이드가 어느 기능을 허용·불허하는지 정하는 규범이라면, 입문서는 가이드가 권장하는 기능을 자세하게 설명합니다. 해당 언어를 처음 접하는 엔지니어가 구글에서 개발할 때 참고할 거의 모든 주제를 다루는 참고서입니다.
  • ‘금주의 팁’ 시리즈: 일반적인 조언과 구글 특화 조언이 뒤섞인 C++ 팁 시리즈입니다. 객체 수명, 복사와 move 시멘틱, 인수 종속 조회(ADL) 같은 난해한 주제부터 미리 적용된 C++ 17 타입들(string_view, optional, variant) 같은 새 기능, 올바른 코딩 습관까지 다룹니다. 추상적인 예시가 아니라 실제로 목격된 패턴에서 뽑아낸 조언 이라서 실무에서 광범위하고 즉각적으로 적용할 수 있었고, 코드 리뷰와 기술 논의 때 수없이 참조되었습니다.
  • 〈언어 이름〉@Google 101 수업: 새로 입사한 엔지니어는 언어는 잘 알아도 그 언어가 구글 내에서 어떻게 사용되는지는 모릅니다. 이 차이를 매워주기 위해 전일 교육으로 가장 많이 쓰이는 라이브러리, 이디엄, 사내 기본 설정, 커스텀 도구 사용법을 가르칩니다.
  • 참고 자료: 올바로 구현하기 어려운 주제에 관한 언어별 조언(동시성, 해싱), 언어 최신 버전의 새 기능 적용법, 구글 라이브러리가 제공하는 중요한 추상 개념과 데이터 구조 목록 — ‘필요한 게 있는데 우리 라이브러리에서 이걸 뭐라고 부르는지 모르겠어’와 같은 질문에 답해줍니다.

규칙 적용하기

규칙을 정해도 적용하지 않으면 의미가 없습니다. 강제하는 방법으로는 교육과 훈련을 통한 사회적인 방법 과 도구를 이용한 기술적인 방법 이 있습니다. 구글의 훈련 프로그램 중심에는 코드 리뷰가 자리하며, 가독성 프로세스가 코드 리뷰를 통한 멘토링으로 스타일 가이드가 요구하는 습관과 패턴을 체화시켜줍니다.

하지만 규칙이 실제로 지켜지는지를 확인하는 데는 사람보다 되도록 자동화 도구 를 활용합니다. 이유는 세 가지입니다.

  • 누락 방지: 시간이 흐르거나 조직이 커져도 규칙이 실수로 누락되거나 잊히지 않습니다. 규칙이 변경될 때마다 도구에 반영해두기만 하면 모든 프로젝트의 모든 엔지니어가 규칙을 준수하는지 알 수 있습니다.
  • 해석 편차 제거: 규칙을 미묘하게 다르게 해석하거나 적용하는 일을 최소화합니다. 구글은 검사 도구를 작성할 때 단 하나의 변치 않는 정의를 만들고 가능한 모든 입력값으로 검증합니다. 모든 인간은 세상을 어느 정도 편향되게 바라보기 마련이라, 사람에게 맡겨두면 규칙 적용 방식이 엔지니어마다 달라집니다. 도구에 위임할수록 사람에 의한 편향이 끼어들 여지가 줄어듭니다.
  • 확장성: 전문가로 구성된 팀 하나가 도구를 관리하면 회사의 다른 모두가 사용할 수 있습니다. 회사가 두 배로 커져도 모든 규칙을 전사에 적용하는 비용은 거의 그대로입니다.
도구로 강제할 수 없는 규칙

모든 규칙을 도구로 강제할 수는 없습니다. 사람이 판단해야만 하는 규칙이 있기 때문입니다. C++ 가이드의 ‘복잡한 템플릿 메타프로그래밍을 피하라’, ‘너무 뻔하거나 중요하지 않은 타입은 굳이 이름을 짓지 말고 auto를 허용하라’, ‘상속보다 되도록 컴포지션을 활용하라’, 자바 가이드의 ‘클래스의 멤버와 초기화 블록 나열 순서에는 정답이 없다’, ‘예외를 catch한 후 아무 일도 하지 않는 것은 대부분의 경우에 옳지 않다’, ‘Object.finalize()를 오버라이드해야 할 상황은 극히 드물다’ 같은 규칙은 판단이 필요한데, 도구만으로는 (적어도 아직까지는) 할 수 없습니다.

또한 사회적 문제를 기술적 시각으로 해결하려 드는 것은 현명하지 않습니다. ‘변경되는 코드의 크기를 작게 하라’는 규칙이 예입니다. ‘작다’의 정의는 모호합니다 — 똑같은 한 라인을 파일 수백 개에서 수정하는 일은 실제로는 검토하기 쉬운 변경일 수 있고, 단 20라인짜리 수정도 부수효과를 동반한 복잡한 로직이라면 ‘작다’고 평하기 어렵습니다. 변경의 복잡성은 주관적인 영역이라서, 변경 제안이 설정된 최대 라인 수를 초과한다는 이유로 자동 거부하는 도구가 구글에 없는 것입니다. 이런 유형의 규칙은 코드를 승인하고 리뷰하는 엔지니어들의 재량에 맡깁니다. 하지만 기술적 문제를 다루는 규칙이라면 가능한 한 기술적으로 자동 집행되게 하는 걸 선호합니다.

오류 검사기

언어 사용법과 관련한 규칙들의 상당수는 정적 분석 도구로 강제할 수 있습니다. 2018년 중반에 구글 C++ 라이브러리 개발자 몇 명이 비공식적으로 조사한 바에 따르면 C++ 스타일 가이드 규칙 중 90%는 자동으로 검증할 수 있습니다. 오류 검사 도구를 활용하면 코드 작성자는 적용해야 할 규칙을 모두 숙지해야 하는 부담을 덜 수 있고, 도구가 알려주는 위반 사항만 살펴보고 고치면 됩니다. 더욱이 높은 확률로 어떻게 고쳐야 할지도 함께 제시해줍니다. 폐기 예정(deprecated) 태그가 붙은 함수를 사용하는 코드를 찾아 경고하면서 대체 API를 알려주는 도구를 이용하면, 폐기 예정 API를 사용하는 코드가 새로 추가되는 문제는 하룻밤이면 사라집니다.

구글은 C++용으로는 clang-tidy를, 자바용으로는 Error Prone을 이용해 규칙이 자동으로 적용되게 했습니다. 규칙 준수 검사 도구 대부분은 반드시 사용하도록 했지만, 다소 유연하게 적용할 수 있는 모범 사례인 경우에는 프로젝트의 필요에 맞게 검사 항목에서 제외할 수 있는 선택지도 제공합니다.

코드 포맷터

구글은 코드 형식을 일관되게 관리하기 위해 자동 스타일 검사기와 포맷터를 적극 이용합니다. 한 라인의 길이로 몇 글자가 적당할지는 더 이상 논의거리가 되지 않습니다. 매번 똑같은 스타일로 포맷이 맞춰지니 코드 리뷰 때 사소한 스타일 문제를 찾고 체크해놓고 수정하는 데 허비되던 검토 시간이 말끔히 사라집니다. 거대한 코드베이스에서 사람과 자동화 도구의 포맷팅 결과를 관찰해보니 도구가 하는 쪽이 평균적으로 훨씬 뛰어났습니다. 행렬 포맷팅처럼 전문적인 도메인 지식이 필요한 특이 상황을 제외하고는 도구가 잘못 포맷팅하는 일은 거의 없습니다.

구글은 프리서브밋 검사(presubmit check) 로 포맷터를 반드시 사용하게 합니다. 코드를 리포지터리로 서브밋하기 전에 빌드 인프라의 서비스가 포맷터를 수행하여 서브밋할 코드와 차이가 있는지 비교하고, 차이가 있다면 해당 서브밋은 거부됩니다. C++에는 clang-format을, 파이썬에는 YAPF를 래핑한 도구를, Go에는 gofmt, Dart에는 dartfmt, BUILD 파일에는 buildifier를 사용합니다.

사례 연구: gofmt

구글은 2009년 11월 Go 언어를 오픈 소스로 공개하면서, 오픈 소스로 공개한 뒤로는 표준 스타일을 수정하기가 거의 불가능하다는 사실을 알았기에 초기 릴리스 때부터 표준 포맷팅 도구인 gofmt를 함께 배포했습니다. 동기는 코드 스타일 갑론을박으로 낭비되는 시간을 없애고, 스타일을 표준화하여 Go 코드를 자동으로 업데이트해주는 도구를 만들 수 있는 기틀 — 기계(도구)가 편집한 코드와 사람이 편집한 코드를 구분할 수 없게 하는 것 — 을 마련하는 것이었습니다.

효과는 2012년 Go 1.0 발표 몇 달 전에 입증되었습니다. Go팀이 gofix라는 도구로 1.0 이전 코드들을 1.0 문법과 라이브러리에 맞게 자동 업데이트했는데, gofmt 덕분에 gofix가 생성한 diff에는 중요한 부분 — 새로운 문법과 API를 사용하기 위해 변경해야 할 내용 — 만 담겨 있었습니다. 프로그래머들이 변경 내용을 검토하면서 달라진 부분을 익히기도 더 쉬웠습니다.

지금 Go 프로그래머는 ‘모든’ Go 코드가 gofmt로 포맷팅되어 있다고 기대하고, 모든 주요 편집기와 IDE가 gofmt를 지원합니다. 처음에는 표준 강제에 불만을 표하던 사용자들이 지금은 gofmt를 Go 언어를 좋아하는 이유로 자주 꼽습니다. 2012년부터는 buildifier로 모든 BUILD 파일에도 자동 포맷팅을 적용했는데, 당시 약 20만 개였던 BUILD 파일 전체를 재포맷하는 데 엔지니어 한 명이 6주 정도면 충분했습니다.

비교 / 트레이드오프

규칙 vs 지침
관점규칙(rule)지침(guidance)
강제력’반드시(must)’ — 승인된 예외 외에는 무시 불가’되도록(should)’ — 변형 적용 허용
성격법, 강제사항권장사항, 모범 사례
변경스타일 중재자의 합의로만 수정경험이 쌓이면 자연스럽게 갱신
예시파이썬 조건부 표현 금지, 오버라이드 어노테이션 필수언어 입문서, ‘금주의 팁’ 시리즈
내부 일관성 vs 외부 표준
코드 성격유리한 선택근거
작고 독립적이고 수명이 짧은 코드내부 일관성외부와 상호작용할 일이 없어 차이가 드러나지 않음
수명이 길고 확장 가능성이 큰 코드외부 표준언젠가 외부 코드와 연동되거나 오픈 소스로 공개될 수 있음

파이썬 공백 개수와 카멜 케이스 사례 모두 ‘내부 일관성 우선’으로 출발했다가 외부 연동·오픈 소스 공개가 늘면서 외부 표준 쪽으로 수정된 경우입니다. 코드의 수명과 경계가 어디까지 확장될지가 판단 기준입니다.

도구 강제 vs 사람 판단
관점도구(정적 분석·포맷터)사람(코드 리뷰)
적합한 규칙기술적 문제 — 명확한 단일 정의가 가능한 규칙 (C++ 규칙의 90%)판단이 필요한 규칙, 사회적 문제 (‘변경을 작게 하라’)
강점누락 방지, 편향 제거, 조직이 커져도 비용 거의 불변맥락·복잡도처럼 주관적인 영역 평가 가능
약점모호한 기준을 기술하려면 복잡하고 비용이 큼기억과 문서에 의존, 해석 편차 발생

내 생각

  • “goto 규칙은 없다”가 컨벤션 문서 비대화의 해독제입니다. 팀 위키의 코딩 컨벤션이 수십 항목으로 불어나는 흔한 패턴에 대해, “이 규칙을 어기는 사례가 실제 코드에 있는가? 린터가 못 잡는가?”를 추가 기준으로 삼으면 자명한 규칙과 도구가 이미 잡는 규칙을 걷어낼 수 있습니다.

  • ‘현 위치에서 추론하기’는 API 설계 리뷰의 실질적 기준입니다. 호출부만 보고 소유권·부수효과·실패 모드를 알 수 없다면 시그니처가 잘못된 것입니다. 자바/코틀린에서도 같은 원리가 적용됩니다 — Optional 반환, @Nullable, suspend 함수 표기 모두 호출 지점에 증거를 남기는 장치입니다.

  • 규칙마다 결정 근거를 남기는 것은 ADR(Architecture Decision Record)과 동형입니다. “이 규칙은 왜 있지?”에 답할 수 없으면 폐기 시점도 알 수 없습니다. 카멜 케이스 사례처럼, 근거(‘파이썬은 C++ 스크립팅 계층용’)가 기록되어 있었기에 그 전제가 무너진 시점에 재평가 신호가 켜진 것입니다.

  • 면제 요청은 규칙 건강도의 모니터링 지표입니다. 면제 요청이 쌓이는 규칙은 재검토 신호, 합당한 면제가 거의 없는 규칙(암묵적 형변환)은 유지 근거가 됩니다. lint 설정에서 // eslint-disable이나 @SuppressWarnings 사용 빈도를 추적하는 것과 같은 발상입니다.

  • 프리서브밋 포맷터 강제는 CI에서 가장 투자 대비 효율이 좋은 검사입니다. 포맷 논쟁이 PR 리뷰에서 사라지면 리뷰가 로직에만 집중됩니다. 단, gofmt 사례의 교훈은 ‘공개·확산 전에 표준을 박아야 한다’는 타이밍입니다. 코드베이스가 커진 뒤의 일괄 재포맷은 blame 오염과 충돌 비용을 동반합니다.

관련 개념