구글 엔지니어는 이렇게 일한다: 소프트웨어 엔지니어링


소프트웨어 엔지니어링과 프로그래밍의 본질적인 차이는 시간, 규모, 트레이드오프를 중심으로 본 지속 가능한 개발이다.

프로그래밍과 소프트웨어 엔지니어링은 같은 말처럼 보이지만, 실제로는 지향점이 다르다.
프로그래밍이 코드를 작성하는 행위에 집중한다면, 소프트웨어 엔지니어링은 변화와 확장, 유지보수까지 포함한 전 생애 주기를 고려하는 작업이다.

여기서는 프로그래밍(개발)과 소프트웨어 엔지니어링(개발+수정+유지보수)의 차이를 시간, 규모, 트레이드오프라는 3가지 관점에서 살펴본다.


시간: 코드의 수명을 바라보는 시야

프로그래밍은 당장의 동작에 초점을 맞추지만, 소프트웨어 엔지니어링은 시간의 흐름에 따른 변화 가능성을 항상 염두에 둔다.

“이 코드는 얼마나 오래 살아남을까?”

변경은 언제든지 일어날 수 있고, 그때마다 코드는 적응해야 한다.
가치 있는 변경에 유연하게 대응할 수 있다면, 그 프로젝트는 지속 가능(sustainable)하다고 평가할 수 있다.

단, 변경에 대응할 수 있는 역량이 있다는 것과 실제로 변경할지 말지는 판단의 영역이다.
우선순위에 따라 ‘변경하지 않기로 한다’는 것도 유효한 전략이다.


규모: 사람과 조직이 함께 성장할 때

개발은 혼자 할 수 있지만, 엔지니어링은 협업과 조직의 성장을 고려한다.

“이 프로젝트에는 몇 명이 참여하는가?”
“개발자들은 어떤 역할을 맡고 있는가?”

규모가 커질수록 효율도 같이 성장하고 있는지 주시해야 한다.

조직의 성장에 따라 아래와 같은 질문이 중요해진다.

  • 반복적인 작업은 어떻게 자동화하고 있는가?
  • 개발자 경험은 프로젝트의 크기에 맞춰 개선되고 있는가?

효율적인 개발 워크플로는 개발자의 생산성을 높이고, 코드의 품질과 유지보수성을 함께 끌어올려 장기적으로 소프트웨어 지속 가능성을 높여준다.


트레이드오프: 명확하지 않은 답을 고르는 기술

소프트웨어 엔지니어링에서는 명확한 정답보다, 덜 나쁜 선택이 더 많다.
주기적으로 복잡한 트레이드오프를 평가하고, 그에 따른 결정을 내려야 한다.

예를 들어

  • 유지보수에 도움이 되는 변경을 지금 하지 않고 미루는 경우
  • 확장성이 부족한 정책을 일시적으로 받아들여야 하는 경우

이 때 중요한 것은 두 가지이다.

  • 다시 검토할 시점을 명확히 설정할 것
  • 의사 결정이 초래할 지연 비용을 계산하고 기록해둘 것

소프트웨어 엔지니어링은 결국 “변화에 대응하는 설계”의 연속이다.


<프로그래밍과 소프트웨어 엔지니어링의 차이>

 프로그래밍소프트웨어 엔지니어링
초점동작하는 코드지속 가능한 시스템
관심사기능 구현유지보수, 협업, 변화
시간현재 중심미래를 고려한 설계
규모개인 단위팀/조직 단위
의사결정로직 중심트레이드오프 중심


1. 시간과 코드의 수명: 왜 유지보수 가능한 소프트웨어가 중요한가?

현업 개발자들 중에는 짧은 수명의 코드만 다뤄본 경우도 많다.
특히 초기 스타트업에서 반복적으로 일해온 엔지니어는 제품이 장기적으로 유지보수되는 경험을 하지 못했을 수도 있다. 하지만 소프트웨어 엔지니어링의 본질은 시간이 흐르면서 변화에 잘 대응하는 시스템을 만드는 것이다.

여기서는 소프트웨어의 기대 수명, 업그레이드 전략, 하이럼의 법칙, 엔트로피 개념을 바탕으로, 장기적인 관점에서 소프트웨어를 어떻게 바라봐야 하는지 정리한다.


코드의 기대 수명과 업그레이드

단기적인 목표에 집중해야 하는 환경에서는 빠르게 동작하는 코드가 우선이다.
하지만 시간이 지나고 프로젝트가 성공하면서 코드는 수명을 연장하게 되고, 그때부터는 완전히 다른 문제에 직면하게 된다.

  • 대부분의 프로젝트는 5년 이내에 업그레이드가 필요하다.
  • 업그레이드 시점이 오면 외부 환경 변화(언어 버전, 라이브러리, 정책 등)에 대응할 준비가 되어있어야 한다.
  • 초기 설계에서 업그레이드를 고려하지 않았다면, 해본 적 없는 작업을 갑자기 해야 하고, 그 결과 유지보수가 큰 고통이 된다.

단순히 ‘동작한다’는 것과 ‘유지보수 가능하다’는 것은 전혀 다른 문제이다.
장기적인 소프트웨어는 반드시 두 요소를 분리해서 생각해야 한다.


1.1. 하이럼의 법칙(Hyrum’s law)

“어떤 동작이든, 그것이 외부로 드러난 이상 누군가는 그 동작에 의존하게 된다.”

하이럼의 법칙은 API 나 시스템의 모든 외부 동작은 결국 누군가에게 의존 대상이 될 수 있다는 원칙이다.
이는 ‘암시적 의존성 원칙(the Law of Implicit Dependencies)’ 라고도 한다.

예를 들어서

  • 문서에 명시된 API 기능은 A,B 뿐이지만
  • 실제로는 C 라는 비공식 동작을 사용자들이 편법으로 활용하고 있고
  • 해당 동작을 내부적으로 제거했더니 사용자 시스템이 망가짐

노출 시간이 길어지고 사용자가 늘어나서 이렇게 하이럼의 법칙이 적용되면 아무리 작고 무해해 보이는 변경이라도 파급 효과가 생길 수 있다.


무해할 듯한 변경이 일부 사용자의 시스템을 망가뜨리는 예시

API 인터페이스를 명확하게 작성해놓아도 현실에서는 API 사용자가 명세에 없는 기능을 찾아 활용하기도 하며, 그 기능이 널리 쓰이면 추후 API 를 변경하기 어렵게 된다. API 의 노출 시간이 길어지고 사용자가 늘어나면 가장 무해할 듯한 변경도 일부 사용자의 소프트웨어를 망가뜨릴 수 있다.

“스페이스바를 누르면 CPU 가 과열되는 버그를 고쳤습니다.”

그러나 한 사용자는 아래와 같이 항의한다.

  • 컨트롤 키가 고장난 키보드에서, CPU 과열 시 스페이스바를 컨트롤 키로 인식되도록 설정해두었음
  • 버그가 ‘기능’처럼 사용되고 있었던 셈
  • 결국 해당 수정으로 인해 정상 사용 불가하여 사용자가 롤백을 요청

(출처: 워크플로)


하이럼의 법칙과 엔트로피

하이럼의 법칙은 시스템의 엔트로피 개념과도 연결된다.

엔트로피는 시스템의 무질서도, 불확실성, 복잡성을 나타내는 물리/정보 이론의 개념이다.
소프트웨어에서 엔트로피가 높다는 것은 의존성, 조건, 예외 처리가 늘어나서 시스템이 예측 불가능하고 관리하기 어려워진 상태를 말한다.
본질적으로 엔트로피는 무질서 혹은 불확실성의 측정치로, 시스템이 얼마나 예측 불가능하고 무작위적인지 나타낸다.

설계 엔트로피에 대한 설명은 2.2. 소프트웨어 엔트로피 를 참고하세요.

시스템이 오래될수록, 수정과 확장의 부담이 커지고 결국 유지보수가 어려워진다.
하지만 하이럼의 법칙이 작용하고, 엔트로피가 낮아지지 않는다고 해서 계획 세우기나 효율성을 포기해서는 안된다.
오히려 더욱 명확한 인터페이스 설계, 문서화, 테스트 전략이 필요하다.


1.2. ‘변하지 않기’를 목표로 하지 않는 이유

“시간의 흐름 따라 ‘변화’는 선택이 아니라 필연이다.”


변경을 피할 수 없는 이유

소프트웨어 시스템에서 ‘변하지 않는 것’을 목표로 하는 접근은 현실과 맞지 않다.
시간이 지나면 반드시 환경이 바뀌고, 그에 따라 소프트웨어도 바뀌어야 하기 때문이다.

대표적인 예시로 보안 취약점 대응을 들 수 있다.

보안 업데이트 사례

  • 하트블리드(Heartbleed)
    • OpenSSL 의 하트비트 확장 기능 구현 오류로 인해, TLS/SSL 통신에서 민감한 데이터가 노출될 수 있는 심각한 취약점이 발견됨
    • 하트블리드 패치는 이런 취약점을 수정한 OpenSSL 버전 1.0.1g 를 배포하여 위 리스크를 해결함
    • 하트블리드 사건 이후 코드 리뷰와 오픈소스 프로젝트의 보안 감사, 지속적인 최신 보안 업데이트가 더욱 중요해졌음
  • 멜트다운(Meltdown) and 스펙터(Spectre)
    • CPU 의 하드웨어 설계 원리를 악용하여 운영체제 커널 메모리에 접근하거나(멜트다운) 다른 프로세스의 메모리를 읽을 수 있는 취약점(스펙터)이 발견됨
    • 이 때문에 운영체제와 브라우저의 구조적 변경 및 성능 트레이드오프를 감수하고서라도 보안 패치를 진행해야 했음

아무리 잘 만든 시스템이라도 ‘아무것도 변하지 않을 거다’라는 가정은 위험하다.
변화는 외부에서 강제되며, 그에 대한 유연한 대응이 없으면 시스템 전체가 무너질 수 있다.


시간이 바꾸는 효율의 기준

기술 환경은 계속 변한다.
과거에는 효율적이었던 코드나 설계가, 현재 하드웨어에서는 성능 병목이 될 수도 있다.

예) 하드웨어와 알고리즘의 격차

  • 과거에는 CPU 와 메모리 속도 차이가 작아서 연산이 병목이었음
  • 지금은 CPU 클럭은 빨라지면서 메모리 지연이 상대적으로 병목이 되는 경우가 많아짐
  • 따라서 캐시 친화적 설계나 데이터 로컬리티가 더 중요해짐

이처럼 이전에는 ‘최적화’였던 설계가 시간이 지나면 ‘비효율’이 될 수 있다.
따라서 하드웨어 환경 변화에 맞춰 설계나 알고리즘을 ‘갱신’하는 것도 지속 가능성의 일부이다.


논리적인 코드라도 오래되면 바뀌어야 한다.

기존 시스템이 아무 문제 없이 잘 동작하더라도 그 자체가 변화를 하지 말아야 할 이유는 아니다.

  • 당시에는 최고의 선택이었고, 설계도 완벽했을 수 있음
  • 하지만 새로운 기술이나 구조가 등장하면, 기존 방식은 새로운 선택지를 제한하는 요인이 되기도 함
  • 따라서, 완벽한 과거의 결정도 시간이 지나면 ‘변경할 가치’가 생김

시간이 흐르면 ‘변경의 이유’는 자연스럽게 만들어진다.
이는 새로운 기술 도입의 효과를 극대화하기 위한 조건이기도 하다.


2. 규모 확장과 효율성

소프트웨어의 규모가 커지면 단순히 코드를 많이 작성하는 문제를 넘어, 빌드/배포/버전 관리/정책 운영/팀 간 협업 방식까지 모두 함께 확장되어야 한다.

여기서는 코드베이스와 조직이 커질수록 고려해야 할 확장성과 워크플로, 확장 가능한 아키텍처와 조직 운영에 대해 다루며, 특히 비효율적인 정책이 어떻게 문제를 유발하고, 어떤 방식으로 해결해야 하는지를 살펴본다.


빌드/버전/리포지터리 관리도 확장되어야 한다.

  • 코드가 많아질수록 빌드 시간은 자연스럽게 증가함
  • 리포지터리의 크기도 커지고, 새로 클론받는데 걸리는 시간도 길어짐
  • 오래된 프로그래밍 언어나 빌드 도구를 계속 사용할 경우, 나중에 업그레이드 비용이 기하급수적으로 증가

이런 지표들은 방치하면 천천히 나빠지지만, 임계점을 넘는 순간 팀 생산성을 급격히 갉아먹기 시작한다.
따라서 ‘조용히 악화되는 지표’에 대한 모니터링과 주기적인 개선은 필수이다.


2.1. 확장하기 어려운 정책들

시스템 폐기 전략의 실패

폐기에 대한 좀 더 상세한 내용은 추후 다룰 예정입니다. (p. 52)

기존에 쓰던 위젯 시스템을 완전히 폐기하고 새롭게 교체한다고 가정해보자.

  • 시스템의 의존성 그래프가 복잡해질수록 폐기 작업은 실패 확률이 높아짐
  • 단 하나의 빌드 실패가 전체 시스템 운영에 영향을 미칠 수 있음
  • 이런 실패는 단일 개발자나 팀이 대응할 수 없으며, 규모가 커질수록 더욱 치명적임

효과적인 폐기 전략: 마이그레이션 팀의 존재

위의 문제를 확장 가능한 방식으로 푼다고 한다면 폐기를 처리하는 방식을 바꾼다는 의미이다.

  • 중앙화된 내부팀이 마이그레이션을 책임지고, 노하우를 쌓으며 반복적으로 처리
  • 사용자가 아니라 시스템 담당팀이 직접 이전과 폐기를 수행
  • 시스템 담당팀은 하위 호환성을 유지한 채 새 버전으로 옮기도록 지원
  • 그 결과:
    • 노하우를 축적한 하나의 팀이 모두 처리하므로 규모의 경제를 실현할 수 있음
    • 현업 팀은 인프라 변경에 따른 혼란없이 자신의 업무에 집중 가능

반면, 각 팀에 ‘알아서 마이그레이션 하라’라고 하면 인프라 변경에 영향을 받는 모든 팀에서 사태를 파악/문제를 해결/더 이상 쓸모없어진 것을 폐기해야 하는 전사적 차원의 불필요한 비용이 발생한다.


독립 개발 브랜치도 확장성의 적이 될 수 있다.

독립 브랜치에서 기능을 개발하는 것은 초기에는 효율적이다.
하지만 시간이 지나며 브랜치가 늘어나고 병합이 지연되면 Trunk(메인 브랜치)와의 동기화 비용이 급격히 증가한다.

독립된 개발 브랜치가 완료되려면 Trunk(메인 개발 브랜치)로 머지되고 테스트되어야 하므로, 아직 다른 개발 브랜치에서 작업 중인 엔지니어들은 다시 동기화하고 테스트하느라 시간을 허비하게 된다.

이 때는 개발 브랜치 전략을 무한정 브랜치 운영이 아닌 Trunk 기반 개발 방식 등 다른 방식을 고려할 필요가 있다.


2.1.1. Git Flow vs Trunk 기반 개발

 Git FlowTrunk 기반 개발
대표 브랜치 구조main, develop, feature/*, release/*, hotfix/* 등main(또는 trunk) 중심, 필요 시 짧은 주기의 feature/* 브랜치
브랜치 생명 주기상대적으로 긺 (수일~수주)매우 짧음(수시간~1일) 또는 직접 trunk 에 커밋
병합 주기기능 완료 후 병합 (비교적 느림)자주, 하루에도 여러 번 병합
릴리즈 방식주기적/버전 단위 릴리즈지속 배포(CD), trunk 에서 바로 배포
릴리즈 브랜치 존재O (release/* 브랜치 사용)X (피처 플래그나 CI 설정으로 릴리즈 관리)
핫픽스 대응hotfix/* 브랜치로 처리trunk 에 직접 패치 및 빠른 배포
적합한 조직 규모소규모~중간 규모, 명확한 릴리즈 관리 필요 조직중간~대규모, 배포 자동화가 잘된 팀
적합한 도메인금융, 정부, QA 중심 조직SaaS, 플랫폼 개발, 스타트업, 실험적 조직 문화
필요한 도구Git 브랜치 전략 문서, 수동 QA 등강력한 CI/CD, 테스트 자동화, 피처 플래그
장점명확한 릴리즈 관리, 롤백 용이빠른 배포와 실험 가능, 협업 효율 높음
단점브랜치 관리 복잡, 병합 충돌 잦음자동화 부족 시 사고 위험, 진입장벽 존재

2.2. 확장 가능한 정책들

비욘세 규칙: 테스트를 준비하지 않으면 보호받을 수 없다.

구글에서는 아래와 같은 정책이 존재한다.

“인프라 변경으로 문제가 생겼을 때, 그것이 CI 테스트에 감지되지 않았다면 인프라팀의 책임이 아니다”

이 정책은 비욘세 규칙이라 불리는데 그 뜻은 이 가사에서 가져왔다.

🎶 “if you like it, then you shoulda put a ring on it”
“네가 좋아했다면 반지를 끼워줬어야지” = “그걸 정말 중요하게 여겼다면 CI 테스트에 넣어뒀어야지”

즉, 어떤 코드가 중요한지 CI 시스템에 반영된 테스트로 표현되지 않았다면, 그건 인프라팀이 고려할 필요가 없는 사안으로 간주된다.

<장점>

  • 인프라팀은 자신들의 변경에만 집중 가능
  • 불확실한 사용 사례까지 고려하느라 리소스를 낭비하지 않음
  • 자동 테스트가 문화적으로 정착되어 품질이 향상됨

CI 에 포함되지 않은 요구사항은 조직적으로 책임지지 않는다는 철학은 조직 규모가 커질수록 명확한 기준점이 된다.


공유 포럼의 힘: 질문이 공유되면 지식도 성장한다.

조직이 커질수록 지식의 비대칭이 문제로 떠오른다.
이를 해결하는 가장 강력한 도구는 공개 질문 + 답변 구조의 포럼이다.

예를 들어:

  • 어떤 기술에 대해 궁금한 점을 공개 포럼에 남김
  • 한 명의 전문가가 대답
  • 결과적으로 100명의 개발자가 더 나은 코드를 작성하게 됨

공유 포럼은 단순한 Q&A 를 넘어, 조직 전체의 기술 역량을 증폭시키는 구조이다.


2.3. 사례: 컴파일러 업그레이드와 하이럼의 법칙

컴파일러 업그레이드는 생각보다 어렵다.

컴파일러와 언어는 버전 업그레이드 시 극도로 보수적인 하위 호환성을 유지하려 한다.
하지만 현실에서는 아래와 같은 문제가 발생한다.

  • 동작 방식이 미묘하게 달라지는 부분이 생김
  • 기존 코드가 특정 버전에 묘하게 의존하고 있을 수 있음
  • 오래 방치된 프로젝트일수록 하이럼의 법칙 문제가 나와서 특정 컴파일러 버전에 더 깊이 의존함

예시:

  • 5년간 컴파일러를 업그레이드하지 않음
  • 최신 버전으로 한 번에 올리려 하자 컴파일 실패, 의존성 충돌, 코드 재작성 등 복잡 문제 발생
  • 결국 업그레이드는 컴파일러와 언어의 변경 사항 중 적용할 방법을 찾지 못한 것들의 우회/패치/트릭으로 점철된 작업이 됨

CI 테스트 없이는 영향 범위를 가늠할 수 없다.

자동화된 테스트와 CI 가 없다면:

  • 컴파일러 변경이 기존 기능을 망가뜨릴지 사전에 알기 어려움
  • 회귀(Regression) 문제가 릴리즈 이후에야 발견될 수 있음

CI 는 업그레이드의 안정성을 제공한다.

회귀 문제

제대로 작동하던 기능이 오작동하는 문제
일반적으로 기능 추가, 리팩터링, 버그 수정 등 다른 목적으로 코드를 수정하는 과정에서 뜻하지 않게 일어남


어떻게 극복할 수 있을까?

  • 자동화
    • 한 사람이 반복 작업을 더 많이 처리할 수 있게
  • 통합과 일관성
    • 하부 구현보다 추상 개념에 의존(= 저수준 변경이 영향을 미치는 범위 제한)
  • 전문화된 인프라팀
    • 적은 인원으로 더 넓은 영향 범위를 다룸

인프라는 자주 변경할수록 안정성이 올라간다.

  • 자주 변경 → 미묘한 하부 구현의 차이에 의존하는 일이 없어짐
  • 대신 언어나 OS 레벨에서 보장하는 추상 개념 활용
  • 궁극적으로 코드베이스의 유연성과 생존력이 증가

<코드베이스의 유연성에 영향을 주는 요인들>

  • 규칙적인 릴리즈
    • 변경량이 적어져 테스트와 검토가 쉬워짐
  • 정기적인 업그레이드
    • 기술 부채 누적을 막음
    • 정기적으로 수행하는 과정에서 중복되는 작업을 찾아 자동화
  • 비욘세 규칙
    • 책임과 범위의 명확한 기준
  • CI 테스트 시스템
    • 변경의 영향을 빠르게 파악할 수 있는 핵심 장치

2.4. 원점 회귀(shift left)

원점 회귀란 소프트웨어 개발 프로세스에서 문제를 가능한 초기에(왼쪽 단계에서) 발견하고 해결하자는 접근 방식이다.

개발의 순서는 보통 아래와 같다.
개념잡기 → 설계 → 구현 → 리뷰 → 테스트 → 커밋 → 카나리 → 배포

문제는 오른쪽으로 갈수록(배포에 가까워질수록) 더 발견하기 어렵고, 수정 비용이 커진다.
반대로, 왼쪽 단계에서 문제를 찾을수록 수정은 빠르고 비용도 낮다.

카나리(Canary) 배포

변경 사항을 전체 사용자에게 적용하기 전에 일부 사용자만 대상으로 조용히 배포하여 검증하는 방법
베타 테스트와 달리 사용자에게 서비스가 변경되었음을 통지하지 않고 진행함

<원점 회귀가 중요한 이유>

  • 커밋 전에 발견된 버그는 대부분 쉽게 수정할 수 있음
  • 정적 분석 도구, lint, 코드 리뷰 등을 통해 찾아낸 버그는 배포 이후 발견한 버그보다 훨씬 싸게 수정 가능
  • 그래서 개발 초기에 품질, 안정성, 보안 문제를 찾아주는 도구와 관례를 제공하는 것은 매우 중요함


<원점 회귀를 실현하는 방법들>

  • 정적 분석 도구
    • 코드 실행 없이 구조와 패턴을 분석하여 잠재적 결함을 탐지
  • 자동화된 테스트
    • PR 단계에서 유닛/통합 테스트 자동 실행
  • 코드 리뷰 문화
    • 협업과 품질 개선을 동시에 달성
  • CI 시스템 연동
    • 커밋 시 자동 품질 체크 및 빌드 검증
  • 보안 사전 분석(SAST)
    • 코드 내 잠재적 보안 취약점 탐지
  • 디자인 리뷰/문서화
    • 설계 단계에서 잠재적 위협 논의

원점 회귀는 단순히 테스트를 앞당기는 것이 아니라, 개발 문화 전체를 ‘문제 예방’ 중심으로 바꾸는 전략이기도 하다.


3. 트레이드오프와 비용

소프트웨어 엔지니어링에서 ‘좋은 선택’을 한다는 것은 단순히 최고의 기술을 고른다는 뜻이 아니다.
현실에서는 항상 제한된 자원, 시간, 팀 상황, 요구사항 안에서 트레이드오프를 감수하며 결정해야 한다.

‘비용’이라고 하면 흔히 금전적 비용만 떠올리지만, 엔지니어링 조직에서 고려해야 할 비용은 훨씬 더 다양하다.

  • 금융 비용
    • 돈, 예산 등 실제 금액적 비용
  • 인적 비용
    • 엔지니어의 노력, 집중력, 팀 생산성
  • 리소스 비용
    • CPU, 메모리, 디스크 I/O, 트래픽 등의 시스템 자원
  • 거래 비용
    • 무언가를 수정하거나 개선하는데 드는 실행 비용
  • 기회 비용
    • 특정 작업에 집중함으로써, 다른 더 가치 있는 작업을 못하게 되는 비용
  • 사회적 비용
    • 기술적 선택이 다른 팀/조직/사용자에게 미치는 영향(예: 버전 종속성, 통합 장애 등)


인지적 편향도 비용에 포함된다.

  • 현상 유지 편향
    • 지금의 상태를 유지하려는 심리적 경향
    • “굳이 지금 잘 돌아가는 걸 바꿔야 해? → 변화의 필요성을 간과하거나 위험을 과소평가함
  • 손실 회피
    • 잠재적 이득보다, 손실에 대한 두려움이 더 크게 작용
    • “이거 바꾸다가 문제 생기면 책임은 누가 져?” → 의사결정이 소극적이고 보수적으로 흐르게 됨

이 두 가지는 의사결정을 느리게 하거나, 합리적인 선택을 어렵게 만드는 숨은 비용이다.

위에 나열한 비용 외에도 현상 유지 편향(= 현재 상태를 유지하려는 경향)과 손실 회피와 같은 부분도 고려해야 한다.


결국 조직의 선택은 두 가지 기준에 수렴한다.

  • 반드시 해야 하는 일
    • 법적 요구사항
    • 고객 SLA(Service Level Agreement) 또는 치명적 결함 대응 → 이건 해도 되고 안 해도 되는 일이 아님
  • 근거 기반의 합리적 선택
    • 상황에 맞는 최선의 판단
    • 적절한 결정권자가 리스트와 영향을 고려해 선택 → 그 시점에서 가장 합리적인 결정을, 책임있는 사람이 내렸는가?

3.1. 의사결정을 위한 근거 자료

소프트웨어에서 ‘최적의 선택’이란 것은 기술만 보고 판단하는 게 아니라, 조직/사람/시간/자원/유지보수/미래의 확장성까지 고려해 트레이드오프를 이해하고 근거있는 판단을 내리는 것이다.

엔지니어링 조직은 아래 두 가지 시나리오에서 의사결정을 내린다.

  • 정량 데이터를 확보할 수 있는 경우
    • 예: ‘고성능 기능 도입이 자원 효율성을 바꾸는가?’ = ‘2주 동안 특정 기능을 개선하면 메모리를 5GB 더 쓰고, CPU 사용량을 2,000개 줄일 수 있다’
      • 판단 기준:
        • 메모리 5GB 의 비용 vs CPU 2,000개 비용
        • 2주간 엔지니어 인건비
        • 그 엔지니어가 2주간 할 수 있었던 다른 작업의 기회 비용
    • 이럴 땐 계산이 가능하므로 비교와 결정이 상대적으로 명확함
  • 정성적 판단만 가능한 경우
    • 예: ‘이 API 는 왜 쓰기 어려운가?’, ‘우리 조직이 이 제품을 선택했을 때 사회적 영향은 어떤가?’
      • 이런 경우 명확한 수치를 구할 수 없고
      • 선례, 경험, 조직의 문화와 리더십에 의존한 합리적인 추정과 추론이 중요해짐
    • 이 때에도 가능한 지표를 만들고 정량화하려는 노력이 중요함(예: 생산성 스코어, 기술부채 지표 등)

3.2. 사례 1: 분산 빌드 시스템의 교훈

여기서는 개별 생산성 향상과 조직 전체의 자원 통제력 약화에 대한 트레이드오프에 대해 알아본다.

  • 분산 빌드 전에는 각자 로컬에서 빌드 → 느린 빌드 시간 = 개인의 불편, 각자가 최적화 시도
  • 도입 후에는 중앙 시스템이 빌드 → 각 엔지니어는 느린 빌드를 못 느끼고, 최적화에 무관심

즉, 개인의 생산성은 향상되었지만 전체 시스템은 점점 비대해지고, 불필요한 의존성,설정들이 숨어들면서 자원 사용량 증가


로컬 빌드를 하는 조직이 있다.

<문제>

  • 코드 베이스가 커지면서 로컬 빌드 시간이 길어짐 → 고성능 개발 로컬 머신 구매 증가
  • 고성능 로컬 머신은 대부분의 시간 Idle 상태
  • 엔지니어 시간 낭비 = 인적 비용 + 기회비용 증가

<해결>

  • 자체 분산 빌드 시스템 도입
  • 시스템을 개발할 엔지니어 투입
  • 컴파일 습관과 워크플로를 바꾸고 새로운 시스템에 적응시키는데 더 많은 엔지니어 시간 투입
  • 빌드 시스템 자체를 구동할 컴퓨팅 자원 구입
  • 하지만 결과적으로 전체 빌드 시간 단축 및 엔지니어 생산성 향상

<하지만..>

  • 시간이 지나면서 모든 엔지니어가 빌드 시간에 무관심해짐
  • 최적화하지 않은 의존성/설정이 빌드 시스템 내에 쌓이기 시작
  • “모두가 혜택을 보니 아무도 책임지지 않게 되는 현상” 발생

기술 효율이 올라갈수록 자원 소비는 오히려 늘어날 수 있다.
예) 연비 좋은 차 = 장거리 운전 증가


3.3. 사례 2: 시간과 규모 확장 사이에서 결정

여기서는 단기 성능 최적화와 장기 유지보수성의 트레이드오프에 대해 알아본다.

<상황>
우리 팀만 겪는 문제가 있음 → 외부 라이브러리를 포크 or 다시 구현?

<포크의 장점>

  • 당면한 문제를 빠르고 정확하게 해결 가능
  • 외부 솔루션에 의존하지 않고 내가 완전한 통제권 확보

<포크의 위험>

  • 외부 라이브러리 사용한다면 보안 문제 발생 시 그 라이브러리만 수정한 후 사용자들이 수정된 라이브러리를 이용하도록 알려주기만 하면 되는데, 모두 포크를 받아서 사용 중이라면 라이브러리의 포크들 중 결함이 적용되는 것들을 모두 찾아내서 해당 포크를 이용하는 사용자를 모두 추적해야 함
  • 장기 유지보수에 치명적
  • 같은 라이브러리를 다수 팀이 제각각 포크한다면 조직 전체의 기술 부채 증가

수명이 짧은 프로젝트는 포크도 괜찮지만,
인터페이스나 직렬화 포맷, 데이터 구조, 네트워크 프로토콜처럼 프로젝트 수명에 종속되지 않는 컴포넌트는 포크를 피해야 한다.


4. 소프트웨어 엔지니어링 vs 프로그래밍: 지속 가능한 코드를 위한 관점 차이

대부분의 개발자는 ‘코드를 잘 짜는 것’이 곧 좋은 개발이라고 생각한다.
그러나 일정 규모 이상의 조직과 프로젝트에서는 코드 그 자체보다 코드가 유지되고 확장되는 방식이 더 중요해진다.

여기서는 프로그래밍과 소프트웨어 엔지니어링의 차이를 시간, 규모, 트레이드오프(의사결정)의 관점에서 설명하고, 왜 엔지니어링 관점이 필요한지를 정리해본다.


<프로그래밍 vs 소프트웨어 엔지니어링>

 프로그래밍소프트웨어 엔지니어링
목적지금의 문제 해결오랫동안 유지 가능한 시스템 설계, 문제 해결, 변화 대응
초점기능 구현, 코드 그 자체유지보수, 협업, 확장성, 정책, 코드/조직/도구/문화
시점단기적장기적
주요 활동코딩, 디버깅설계, 테스트, 릴리스, 운영, 협업 프로세스
핵심 질문‘이 문제를 어떻게 해결할까?’‘이 방식이 시간이 지나도 유효할까?’
예시스크립트 작성, 툴 제작대규모 시스템 개발, 팀 기반 개발

소프트웨어 엔지니어링이 프로그래밍보다 우월한 방식이라는 것이 아니다.
적용 범위와 목표가 다르기 때문에 각 방식에 어울리는 접근이 필요하다.

단 며칠만 활용할 프로젝트에 통합 테스트나 지속적 배포(CD) 등을 적용할 필요는 없다.
대신 주어진 과업을 쉽게 해결하고 당장 활용할 수 있는 방법을 (그게 무엇이든) 찾아야 한다.


시간: 코드 수명과 지속 가능성

프로그래밍은 현재 문제 해결에 집중한다.
반면 소프트웨어 엔지니어링은 코드의 변화 가능성까지 고려한다.

  • 좋은 소프트웨어는 ‘변경될 가능성’을 고려해야 지속 가능함
  • 유지보수 가능한 코드란, 변화에 대응할 수 있는 코드
  • 보안 패치, 성능 변화, 기술 부채 대응 등은 시간이 지나며 필수적으로 발생

예) 하트블리드/스펙터 같은 취약점 대응은 시간이 흐르며 필연적으로 발생하며, 효율적인 알고리즘도 최신 하드웨어에서는 비효율적일 수 있음


규모: 확장성과 정책

작은 팀에선 명확한 정책이 없어도 소통이 가능하지만, 조직이 커질수록 정책과 워크플로가 생산성과 직결된다.

  • 독립 브랜치 기반 개발을 충돌/동기화 비용 증가
  • 트렁크 기반 개발은 효율적이지만 자동화 인프라 필요
  • 빌드 시간, 의존성 그래프, 테스트 커버리지 등도 규모가 커질수록 중요한 관리 지표임

예) 위젯을 전면 교체하기보다는, 마이그레이션을 점진적으로 처리할 수 있는 인프라가 필요
CI 테스트에 등록되지 않은 문제는 책임지지 않는 비욘세 규칙 등도 대표적인 확장성 대응 정책임


트레이드오프와 의사결정

소프트웨어 엔지니어링은 트레이드오프를 인식하고 판단해야 한다.

  • 단기 성능 vs 장기 유지보수(예: 포크 vs 외부 의존성)
  • 생산성 향상 vs 자원 관리 통제(예: 분산 빌드 도입)
  • 기회 비용, 인건비, 기술 부채 등 다양한 비금전적 비용 고려

예) 분산 빌드는 개별 빌드 시간은 줄이지만, 의존성 관리의식이 느슨해져 자원 낭비가 발생
즉, 기술적 효율이 오히려 장기적인 낭비를 초래할 수 있음


소프트웨어 엔지니어링은 ‘프로그래밍 + 관리 역량’

프로그래밍이 ‘지금 문제 해결’이라면, 소프트웨어 엔지니어링은 ‘문제 해결 + 그 해결 방식 자체의 지속 가능성’을 설계하는 일이다.
단기간 사용할 코드에선 꼭 필요하지 않을 수 있지만, 팀/제품/조직이 커지고 오래 가야 한다면 필수적이다.


정리하며..

  • 프로그래밍코드 생산에 집중하는 활동이다.
    반면 소프트웨어 엔지니어링은 그 코드를 오랜 시간 유지보수하고 변화에 대응할 수 있도록 관리하는 기술적 총체를 뜻한다.
    단기적 구현 뿐 아니라 장기적 유지 가능성과 조직적 협업을 고려한 관점이다.
  • 지속 가능한 소프트웨어를 만들기 위해서는 코드의 기대 수명 동안 변화할 수 있는 모든 요소(의존성, 기술 트렌드, 하드웨어, 사용자 요구 등)에 대응할 수 있는 역량이 필요하다.
    꼭 변경을 수용하지 않더라도, 변경할 수 있는 구조와 역량을 갖춰야 한다.
  • 하이럼의 법칙
    • API 사용자가 충분히 많아지면, 문서화되지 않은 모든 동작조차 누군가에겐 기능처럼 사용되고 있다는 뜻이다.
    • 따라서 내부 구현의 미세한 변화조차 의도하지 않은 사이드 이펙트를 낳을 수 있으므로, 명세 외 행동에 의존하지 않도록 설계하고 유지해야 한다.

참고 사이트 & 함께 보면 좋은 사이트

본 포스트는 구글 엔지니어는 이렇게 일한다 를 읽으며 정리한 내용들입니다.






© 2020.08. by assu10

Powered by assu10