Software Engineering? - Software Engineering at Google

2022. 6. 4. 20:49ETC/Software Engineering

본 포스팅은 Software Engineering at Google의 내용을 정리한 내용입니다.

 

 

안녕하세요. 오랜만에 시리즈를 연재하려 합니다.

주제는 "구글의 Software Engineering"으로, 구글이 말하는 software engineering의 철학이나 관례 등을 배우고자 시작합니다.

해당 내용은 O'reilly에서 출간하고, 저자에 의해 공개된 Software Engineering at Google을 바탕으로 참고하여 정리한 내용입니다.

원문 내용이 번역하기 힘들기도 하고 번역본도 번역체나 이해하기 어려운 부분들이 있어서 생각보다 한 장을 정리하는데 꽤 시간이 들었습니다. 그래도 재밌기도 하고 기록하고 싶은 내용이 많아 몇 가지 정리해볼 예정입니다.

 

 

 


 

What Is Software Engineering?

 

ProgrammingSoftware Engineering은 어떤 차이가 있을까

이 둘의 가장 큰 차이는 Time, Scale, Trade-offs at Play시간, 규모(확장), 실무에서의 트레이드 오프 이 세 가지이다.

소프트웨어 엔지니어링 프로젝트의 엔지니어는 시간의 흐름과 변경이 될 가능성에 더 신경 써야 한다.

 

구글에서는 소프트웨어 엔지니어링을 아래와 같이 표현한다.

 

"Software engineering is programming integrated over time"

 

의역해보면, "Software Engineering은 연속된 Programming의 집합체" 쯤 되지 않을까 싶다.

Software Engineering은 그 순간의 Programming들을 모두 합산한 것이며,

Programming은 새로운 Software를 제작하는 것이다.

 

 

Sustainability

업무 'Task' 차원에서 Programming 작업과 Software Engineering 작업의 차이를 구별해보자.

 

Programming - development
Software engineering - development, modification, maintenance

 

프로그래밍은 코드 생산에 관한 것이라면, 

소프트웨어 엔지니어링은 프로그래밍을 확장하여 소프트웨어의 수명이 다할 때까지 코드를 유지보수까지 고려한다.

 

시간을 더한 차원의 관점에서 Software EngineeringProgramming과 다른데,

정육면체와 정사각형, 그리고 거리와 속도를 차원의 입장에서 구분하듯이 

소프트웨어 엔지니어링은 프로그래밍이 아니라고 할 수 있다.

 

"이 코드의 예상 수명은?" 을 물었을 때 짧게 혹은 길게 살아남을 코드를 구분해야 한다. 

오래 살아남은 코드는 변경될 수 있으며, 이 차이가 소프트웨어의 지속 가능성sustainability의 핵심이다.

코드의 수명은 천차만별이며, 코드의 변경은 직접적인 변화이든지, 아니면 외부 라이브러리, 프레임워크, 운영체제 등에 의한 간접적인 변화에서든 올 수 있다.

 

 

TimeScaleTrade-offs 

단명하는 코드와 오래 남는 코드의 수명은 적어도 10만 배는 되기 때문에 두 프로젝트를 똑같이 취급하는 건 너무 안일한 생각이다.

 

소프트웨어 엔지니어는 이 지속 가능성을 잃지 않으면서 조직, 제품, 개발 워크플로의 규모를 확장의 비용을 관리해야 하는데,

팀은 이 사실을 주지하고 트레이드오프를 평가하여 합리적인 결정을 내려야 한다.

 

때로는 유지보수를 위한 변경사항 적용을 연기하거나, 심지어 확장성이 떨어지는 선택을 받아들여야 할 때도 있다.

이러한 결정은 훗날 다시 검토해야 할 수도 있음을 잊지 말아야 하며, 이 결정 때문에 생긴 지연 비용을 정확히 계산해두어야 한다.

 

 

Conclusion

소프트웨어 엔지니어링이 프로그래밍보다 우수하다는 것이 아니라, 이 둘을 구분해서 개발해야한다는 것이다.

 

이 둘에 적용되는 제약 사항, 가치, 모범 사례가 서로 다르며,

한 영역에서는 훌륭한 도구라도 다른 영역에서는 그렇지 않을수 있다.

 

예를들어, 단 며칠만 활용할 프로젝트라면 통합 테스트나 지속적 배포 continuous deployment(CD) 에 목맬 필요가 없다.

대신 쉽게 해결할 수 있고 당장 활용할 수 있는 방법을 찾아야 한다.

 

이제 시간, 규모 확장, 트레이드오프에 대해 조금 더 상세히 알아보자.

 

 

 

Time and Change

시간이 지나면 항상 변화하기 마련이다.

필자는 하이럼의 법칙을 소개하며 이 내용을 보충한다.

 

 

Hyrum’s Law

With a sufficient number of users of an API, it does not matter what you promise in the contract: all observable behaviors of your system will be depended on by somebody.

하이럼 본인은 ‘암시적 의존성 법칙(The Law of Implicit Dependencies)’이라 부르지만,

구글 엔지니어 사이에서는 거의 ‘하이럼의 법칙으로 부른.

 

 

해석 해보면 아래와 같다.

"API의 사용자가 충분히 많을 때, API 명세는 그 의미를 잃는다:

사용자들이 어떻게 사용하는지에 따라 그 API 역할이 달라지기 때문이다."

 

하이럼의 법칙은 사용자들이 많아질수록 API를 제작한 사람의 의도와는 상관없이,

사용자들은 그 API에 접근할 수 있는 한 다양한 행위들을 저지를 수 있다는 의미다.

그 행위가 예상할 수 있는 영역일 수도, 예상을 하지 못한 영역일 수 있다.

 

즉, 의도대로 사용되도록 개발한 최선의 방식, 최고의 엔지니어, 꼼꼼한 코드 리뷰를 수행했다고 해도

이를 위해 제공하는 명세서나 Best Practices가 완벽하다고 가정할 수 없다는 것이다.

현실에서는 API 사용자가 명세에는 없는 기능을 찾아 활용할 수도 있는데,

이 기능이 유용해서 많은 사용자가 생긴다면 이 API를 변경하기 어렵게 된다.

 

 

이해하기 어려울 수 있으니 예시를 통해 알아보자.

Hashset을 사용하는 프로그램을 개발하다가, 해시 테이블이 느려지도록 하는 데이터를 넣는 공격자를 발견했다.

그래서 무작위성을 갖는 해시 알고리즘을 제작했는데 다른 개발자가 이 의도와는 상관없이 난수발생기로 사용할 수 있다.

그렇게 이 hashset을 위한 해시 알고리즘은 의도와는 다른 프로그램에서 호출되면서 이 기능에 의존하는 프로그램이 생긴 것이다.

 

 

Clever or Clean ?

이렇게 의도와는 상관없는 코드를 고려하여 코드 스타일을 분류할 수 있다면 아래와 같다.

 

비의도적인 방식이나 변경될 수 있는 의존성을 가진 코드를 'hacky임시방편의' 혹은 'clever기발한' 코드라고 한다면,

Best Practice를 따르고 확장성을 생각한 코드를 'clean', 'maintainable' 코드라고 할 수 있다.

 

 

둘 다 나름의 목적이 있지만, 어느 것을 선택할지는 코드의 기대 수명에 크게 좌우된다.

만약 ‘clever’이 칭찬으로 느껴진다면 프로그래밍이라하고, 질책으로 느껴진다면 소프트웨어 엔지니어링이라 말할 수 있다.

clever은 기발한, 영리한으로 해석할 수 있는데 여기서는 '잔꾀를 조금 섞은 기발한 아이디어'라고 의미되지 않는가 싶다.

개발자라면 알만한 잔꾀가 섞인 코드가 아닐까한다. 

 

 

 

Scale and Efficiency

 

개발 프로세스를 포함한 소프트웨어 개발 업무의 많은 것의 효율은 눈치 채기 어렵게 천천히 나빠지는 경향이 있다.

예를 들어 빌드 시간, 리포지터리에서 새로 내려받는 시간, 버전을 업그레이드하는 비용 등은 적극적으로 관리하지 않으면 천천히 악화된다. 코드가 많아지고 변경 이력이 쌓이는 등의 이유로 빌드 시스템이나 버전 관리 시스템이 점점 느려진다면 어느 순간 더는 정상 운영할 수 없는 시점이 온다.

마치 ‘끓는 물 속의 개구리’처럼 서서히, 위험이 현실이 되는 순간까지 단 한 번도 눈치 채지 못할 수도 있다.

처음에 개구리가 끓는 물 안에 들어가면 깜짝 놀라 뛰쳐 나오겠지만, 만약 점점 따뜻해져 끓게 되는 뜨거운 물에 들어가게 되면 위험한 줄 모르다가 죽게 된다는 옛 말을 비유한 것이다.

 

이런 문제들은 조직 차원에서 챙기면서, 확장 가능성에 신경 써야지만 안정적으로 관리할 수 있다.

 

소프트웨어 조직에서 가장 중요한 자산인 코드베이스 자체도 확장 가능해야 하는데,

코드베이스의 지속가능성을 생각해보면 아래와 같다.

 

"조직의 코드베이스는 변경해야 할 모든 사항을 안전하게 변경할 수 있고,
코드베이스의 수명동안 변경할 수 있을 때 지속 가능하다."

 

위의 내용을 풀어보면, 코드베이스가 사용되는 동안 변경에 용이해야 지속가능하다는 의미이다.

코드베이스(codebase): 소프트웨어 개발에서 특정 소프트웨어 시스템, 응용 소프트웨어, 소프트웨어 구성 요소를 빌드하기 위해 사용되는 소스 코드의 모임.

 

 

이 책에서 구글 엔지니어는 Churn Rule, The Beyonce Rule 이라는 정책을 소개한다.

 

Churn Rule

갈아타기 규칙

 

새로운 위젯을 개발했다고 했을 때, 유저에게 바꾸라고 하는 것보다 시스템 담당 팀 내부에서 처리할 수 있도록 한다.

그럼 사용자가 많아져도 특정 팀이 노하우를 축적하며 모두 처리할 수 있기 때문에 규모의 경제 효과도 누릴 수 있다.

마이그레이션을 담당하는 전문가 그룹을 따로 두기 때문에 사용자 각자에게 유지보수를 부담시키는 방식보다 확장성이 좋았다.

전문가들이 프로젝트 전체를 파악한 후에, 파생되는 문제들을 해결할 수 있기 때문이다.

 

 

The Beyoncé Rule

비욘세 규칙

 

‘인프라를 변경하여 서비스가 중단되는 등의 문제가 발생하더라도,

같은 문제가 지속적 통합 continuous integration(CI) 시스템의 자동 테스트에서 발견되지 않는다면 인프라팀의 책임이 아니다’

 

공통 CI 시스템에 추가해두지 않은 테스트는 인프라팀이 책임지지 않는다는 뜻이다. 

'비욘세 규칙 The Beyoncé Rule'은 친근하게 표현하면 '네가 좋아했다면 CI 테스트를 준비해뒀어야지'라는 뜻이 있다.

비욘세의 히트곡 〈Single Ladies (Put a Ring on It)〉의 후렴구 가사 ‘네가 좋아했다면 반지를 끼워줬어야지(If you like it then you should have put a ring on it)’에서 가져온 말이다. 리뷰어_ ‘코드를 짰으면 자기 코드에 대한 테스트도 자기가 제대로 만들었어야지’라는 뜻으로 해석할 수 있다.

 

 

---

 

조직의 규모가 커질 때 엔지니어 한 명에게 할당되는 작업이 비례해서 증가한다면 확장성에 문제가 있는 것이다.

조직에서 반복적으로 수행하는 모든 작업은 필요 인력 측면에서 확장 가능(선형 증가 혹은 더 완만하게 증가)해야 한다.

정책을 정하고 공유하는 것은 개발 프로세스를 확장 가능하게 해주는 아주 멋진 도구이다. 

 

 

Example: Compiler Upgrade

2006년에 구글 엔지니어들은 컴파일러를 업그레이드해야만 하는 상황에 직면했다.

코드베이스가 계속 커지더라도 일정한 수의 엔지니어만으로 업그레이드를 성공적으로 해내는 방법을 찾았다는 내용을 담는다.

 

컴파일러 버전업으로 인한 문제(c++의 std namespace 최적화에 대한 코드 충돌)로 큰 고생을 겪었고, 그 결과 자동화(한 사람이 더 많은 일을 수행), 통합과 일관성(저수준 변경이 영향을 미치는 범위 제한), 전문성(적은 인원으로 더 많은 일을 수행)으로 나누며 조직 변화를 했다. 

 

그 결과 2006년의 업그레이드를 포함한 많은 경험을 통해

아래와 같은 코드베이스의 유연성에 영향을 주는 여러 요인을 찾았다.

 

 

Expertise

전문성 - 여러 방법에 대한 충분한 지식

: 구글은 이미 수많은 플랫폼에서 수백 번의 컴파일러 업그레이드를 성공적으로 마칠 수 있었다.

 

Stability

안정성- 규칙적인 릴리즈

: 더 규칙적으로 릴리스하여 릴리스 사이의 변경량을 줄였다. 몇 언어에서, 한두 주 간격으로 새로운 컴파일러를 배포한다. 

 

Conformity

순응 - 규칙적인 업그레이드

: 업그레이드를 겪지 않은 코드가 많지 않다. 규칙적인 업그레이드가 도움이 되었다. 

 

Familiarity

익숙함 -  자동화

: 업그레이드를 정기적으로 수행하기 때문에 그 과정에서 중복되는 작업을 찾고 자동화 하려고 노력한다. 사이트 신뢰성 엔지니어(SRE)가 삽질을 줄이는 관점과 일맥상통하다. 

 

Policy

정책

: 비욘세 규칙과 같은 유용한 정책과 절차를 갖춘다. 덕분에 인프라팀은 다른 사용 방법까지 걱정할 필요 없이 CI 시스템에 반영된 사용법만 고민하면 된다.

 

 

평범한 소프트웨어 엔지니어(SWE, man-month 중 소프트웨어 엔지니어 국한한 개념)의 단위 시간당 코드 작성량은 상당히 일정한 편이다. 그래서 투입된 SWE-months에 비례하여 성장한다. 

 

 

 

 

Shifting Left

 

개발 과정 중 문제를 일찍 발견하면 비용이 적게 든다. 

 

 

 

왼쪽으로 옮기는 행위를 원점 회귀 shift left 라 하는데,

개발 프로세스에서 보안 점검을 고려하여 ‘보안을 고려하는 시점을 왼쪽(원점)으로 이동시켜라 shift left on security ’라고한 말에서 유래한 듯하다. 제품을 고객에게 배포한 후에야 취약점이 발견되면 해결하는 데 막대한 비용이 들 것이다.

 

코드 커밋 전에 정적 검사나 코드 리뷰로 찾아낸 버그는 프로덕션 이후에 발견한 버그보다 훨씬 저비용으로 고칠 수 있다.

그래서 구글 인프라 팀의 주요 목표 중 하나는 

개발 프로세스 초기에 품질, 안정성, 보안 문제를 찾아 알려주는 도구와 관례를 제공하는 것이다.

 

 

 

Trade-offs and Costs

Example: Markers

화이트보드에 마른 매직을 경험해본적이 얼마나 되는지를 생각해보자.

잘 나오는 마커를 찾으려고 얼마나 많은 시간을 쓰는지 고려해볼 때,

회의나 브레인스토밍과 같은 생산적인 일을 할 시간을 잡아 먹게 된다.

 

구글은 항상 마른 마커로 해결방법을 쓰는 시간 비용을 쓰지 않도록 여러 색의 마커를 구비해둔다.

 

 

Costs

"비용"을 정의하면 아래와 같이 구분할 수 있다.

 

- 금융 비용 (eg. 돈)

- 리소스 비용 (eg.  CPU time)

- 인적 비용 (eg. 엔지니어링 노력)

- 거래 비용 (eg. 조치를 취하는 비용)

- 기회 비용 (eg. 조치를 취하지 않는 비용)

- 사회적 비용 (eg. 선택이 사회 전체에 미치는 영향)

 

 

"because I said so."

비용과 근거, 선례, 논증 등의 타당한 이유로 의사결정을 해야한다

 

'구글의 문화는 데이터 주도적이다' 라는 말이 있다.

실상은 데이터가 없을 때조차 근거, 선례, 논증을 거쳐 결정을 내리곤 한다. 

 

좋은 엔지니어링적 결정이란 결국 가용한 모든 근거 자료에 적절한 가중치를 부여하고, 

이러한 풍부한 지식을 바탕으로 균형점을 잡는 일이다.

때로는 본능과 잘 알려진 모범 사례에 의지해 결정하기도 하지만, 

보통은 실제 비용을 추정하고 예측해보려 시도해본 뒤에 그렇게 한다. 

 

결국 엔지니어링 조직의 선택을 결정짓는 요인을 요약하면 아래와 같다.

- 반드시 해야 하는 일 (법적 요구사항, 고객 요구사항)

- 근거에 기반하여 당시 내릴 수 있는 최선의 선택 (적절한 결정권자가 확정) 

 

 

절대 의사결정이 ‘내가 시켰으니까’가 되어서는 안된다.

즉, 비용과 근거, 선례, 논증 등의 타당한 이유로 의사결정을 해야한다.

 

의사결정을 데이터 주도로 하겠다는 생각은 좋은 출발이다.

하지만 현실에서의 의사결정 대부분은 데이터, 가정, 선례, 논의를 종합하여 이루어진다.

고려 요소의 대부분에 객관적인 데이터가 주어지면 가장 좋겠지만 순전히 데이터만으로 결정되는 경우는 드물다. 

 

 

Example: Distributed Builds

2000년대 중반, 구글에서는 로컬 빌드에 의존했는데, 코드베이스가 커지면서 컴파일 시간도 늘어났다.

그래서 더 크고 성능좋은 로컬 머신을 구입하는 지출은 커졌고 시간도 늘어나며 생산적인 코드 작업(일할) 시간은 줄어갔다.

 

결국 구글은 자체 분산 빌드 시스템을 개발했다. 시스템을 개발할 엔지니어를 투입해야 했고, 다른 모두의 컴파일 습관과 워크플로를 바꿔 새로운 시스템에 적을 시켜야하는 큰 시간을 투입해야 했다.

 

전체적으로는 절약되는 시간이 명백히 컸다. 빌드가 빨라지며 멍때리는 시간이 줄일 수 있다. 

하지만, '효율이 좋아지면 자원 소비가 늘어난다'는 제번스의 역설 Jevons Paradox을 떠오르게 하는 자원을 분별없이 소비하는 상황이 생겨났다.

 

결국 시스템의 목표와 제약을 다시 정하고,

모범 사례를 만들고(의존성 최소화, 의존성 관리 주체를 사람에서 머신으로 변경 등),

새로운 생태계를 위한 도구와 유지보수에도 투자해야 한다는 사실을 깨우쳤다고 한다.

 

 

데이터 주도 방식은 시간이 흘러 데이터가 변하면(혹은 가정이 무너지면) 프로젝트의 방향도 바뀔 수 있음을 뜻한다.

잘못을 인정하고 계획을 수정하는 것은 당연한 일이다.