Skip to content

Latest commit

 

History

History
233 lines (179 loc) · 25.8 KB

21. Dependency Management.md

File metadata and controls

233 lines (179 loc) · 25.8 KB

21. 의존성 관리

  • 의존성 관리(dependency management): 우리가 통제하지 못하는 라이브러리, 패키지, 그 외 의존성들의 네트워크를 관리하는 일
  • 이 장에서 다루는 것
    • 외부 의존성의 버전을 업데이트하는 방법은 무엇인가?
    • 버전을 기술하는 방법은 무엇인가?
    • 의존성에서 어떤 유형의 변경이 허용되거나 예상되는가?
    • 다른 조직에서 만든 코드에 의존하는 게 직접 만드는 것보다 낫다고 판단하는 기준은 무엇인가?
  • 의존성 관리는 시간과 확장 양 축 모두에서 복잡도를 키운다.
    • 소스 관리에서는 코드를 변경할 때 테스트를 수행하고 기존 코드를 손상시키지 않아야 한다. 코드베이스가 공유되어 있어서 무엇이 어떻게 이용되는지 볼 수 있고 우리가 직접 빌드하고 테스트할 수 있다.
    • 반면 의존성 관리는 우리에게 접근 권한이 없거나 속을 들여다볼 수 없는, 조직 외부에서 이루어지는 변경 때문에 일어나는 문제를 다룬다. 업스트림 의존성들(upstream dependencies)은 내가 작성한 코드와 손발을 맞출 수 없으므로 내 빌드나 테스트를 실패하게 만들 가능성이 더 크다.
      • 이런 의존성은 어떻게 관리해야 하는가? 외부 의존성을 만들지 말아야 하는가? 외부 의존성 메인테이너들에게 연락하여 릴리즈들 간에 일관성을 더 잘 지켜달라고 요구할 것인가? 언제 새 버전으로 갈아타야 하는가?
  • 확장이 끼어들면 상황이 한층 더 복잡해진다. 우리는 일반적으로 수많은 외부 의존성들로 이루어진 거대한 네트워크를 다루기 때문이다.
  • '다른 조건이 모두 같다면 의존성 관리 문제보다는 소스 관리 문제를 택하라'. 의존성 관리 문제보다는 소스 관리 문제가 생각하기도 훨씬 쉽고 처리 비용도 훨씬 저렴하다.
  • 의존성을 관리할 때 극복해야 하는 문제, 해법, 한계를 다룬다.

21.1 의존성 관리가 어려운 이유

  • 의존성 하나를 관리하는 방법이 중요한 게 아니다. 수많은 의존성들로 구성된 네트워크와 그 네트워크에 미래에 일어날 변화까지 고려해 관리하는 방법을 강구해야 한다.

21.1.1 요구사항 충돌과 다이아몬드 의존성

  • 의존성 관리에서는 의존성 네트워크를 중심에 두고 생각해야 한다.
  • 버전 비호환 문제의 대표적인 예는 다이아몬드 의존성 문제(diamond dependency problem)이다.
  • 다이아몬드 의존성 문제 혹은 다른 형태의 요구사항 충돌 문제가 성립하려면 의존성 계층이 최소 세 개가 필요하다.
  • 다이아몬드 의존성 문제가 주는 충격은 프로그래밍 언어에 따라 다르다.
    • 어떤 언어에서는 빌드 하나에 의존성 여러 버전을 모두 포함시킬 수 있다. 예를 들어 자바는 의존성이 제공하는 심볼의 이름을 바꾸는 메커니즘이 잘 정립되어 있다.
    • 이와 달리 C++는 다이아몬드 의존성에 대한 내성이 전혀 없다시피 하여 십중팔구 예기치 못한 버그나 정의되지 않은 동작(undefined behavior)으로 이어진다.
  • 심볼을 숨기거나 이름을 바꿔 컴파일한 라이브러리를 중복 적재하는 방식은 다이아몬드 문제의 충격을 다소 완화해주지만 보편적인 해결책이 될 순 없다.
  • 이러한 요구사항 충돌 문제를 쉽게 해결하는 유일한 방법은 모두와 호환되는 더 상위 혹은 하위 버전의 라이브러리를 찾는 것 뿐이다. 이게 불가능하다면 문제가 되는 의존성을 로컬에서 따로 패치해야 한다.
  • '소통하지 않는 단체들이 각자의 계획대로 업그레이드를 진행하면서도 요구사항 충돌을 피할 수 있는 방법은 무엇일까?'

21.2 의존성 임포트하기

  • 프로그래밍 측면에서 보면 직접 처음부터 새로 짜는 것보다 기존 인프라를 재활용하는 게 분명히 더 낫다.
  • 요구사항을 만족하는 외부 소프트웨어(의존성)가 있다면 이용하는 게 좋다.

21.2.1 호환성 약속

  • 개발 비용을 줄일 수 있다고 해서 의존성을 임포트하는 게 꼭 옳은 선택은 아니다.
    • 지속적인 유지보수 비용까지 감안해야 한다.
    • 업그레이드할 계획이 없던 의존성이라 해도 보안 취약점이 발견될 수 있다.
    • 외부 요인 때문에 플랫폼을 갈아타야 할 수 있다.
  • 의존성의 유지보수 비용을 조금 더 정확하게 계산할 수 있게 도와주는 질문들
    • 호환성이 얼마나 잘 지켜지는가?
    • 진화가 얼마나 빠르게(크게) 이루어지는가?
    • 변경 처리 방법은 무엇인가?
    • 각 버전의 지원 기간은 어떻게 되는가?

C++

  • C++ 표준 라이브러리는 거의 무한대의 하위 호환성을 제공하는 예이다.
  • API 호환성 뿐 아니라 바이너리 아티팩트들과의 하위 호환성도 꾸준히 제공해준다. 이를 ABI 호환성(application binary interface compatibility)이라 한다.
  • 자바도 비슷하다. 소스 코드는 언어 버전이 올라가도 호환되며, 옛 버전의 JAR 파일이 새 버전 파일들과 어울려 구동된다.

Go

  • Go 언어는 대부분의 릴리즈에서 소스 코드가 호환되게 해주었지만 바이너리는 그렇지 않았다.
  • Go 언어에서는 빌드한 버전이 다른 바이너리끼리는 링크할 수 없다.

Abseil

  • 성능을 더 끌어낼 수 있다면(그래서 구글 서비스 대부분이 혜택 받을 수 있다면) 구현 세부사항과 ABI를 변경할 권한이 구글에게 주어진다는 뜻이다.

Boost

  • Boost는 버전 간 호환성을 보장하지 않는다. 따라서 Boost 사용자들은 호환성 문제가 생기기 전까지만 업그레이드 하는 게 바람직하다.

  • 우리의 관심은 의존성들이 점차 변해갈 텐데 어떻게 하면 항상 최신 상태로 유지할 것인가이다.
  • 구글에서는 엔지니어들이 '동작하게 만들었다'와 '지원되는 방식으로 동작한다'를 구분하여 생각하는 데 도움되는 지침들을 꾸준히 공유한다.

21.2.2 임포트 시 고려사항

  • 프로그래밍 프로젝트라면 의존성 임포트는 거의 공짜에 가깝다. 필요한 기능을 제공하는지 확인했고 숨겨진 보안 취약점이 없다고 가정한다면 같은 기능을 직접 새로 구현하는 것보다는 이미 있는 것을 사용하는 게 당연히 저렴하다.
  • 구글은 의존성을 임포트하려는 엔지니어들에게 몇 가지 질문들을 던져보라고 권한다.
    • 의존성 자체에 대한 질문
      • 실행해볼 수 있는 테스트가 딸려 있는 프로젝트인가?
      • 테스트는 모두 통과하는가?
      • 의존성 제공자는 누구인가? 똑같이 호환성을 보장하지 않는 오픈 소스 프로젝트더라도 경험과 기술력 차이는 어마어마할 수 있다. 최소한 호환성 측면에서는 C++ 표준 라이브러리나 자바의 Guava 라이브러리를 사용하는 것과 깃허브나 npm에서 임의의 프로젝트를 선택해 사용하는 것은 매우 다른 이야기다. 유명하다고 다 좋은 것은 아니지만 반드시 고려해보는 게 좋다.
      • 지향하는 호환성 정책은 어떠한가?
      • 앞으로 어떤 분야나 용도를 지원해나갈 것인지 자세히 설명하고 있는가?
      • 얼마나 인기 있는 프로젝트인가?
      • 언제까지 이용할 것인가?
      • 파괴적인 변경이 얼마나 빈번하게 행해지고 있는가?
    • 소속된 조직 관점에서의 질문
      • 구글이 같은 기능을 새로 구현하려면 얼마나 복잡한가?
      • 그 의존성을 최신 상태로 유지하면 어떤 이점이 있는가?
      • 업그레이드는 누가 할 것인가?
      • 업그레이드하는 난이도는 어느 정도일 거라 예상하는가?
  • 긴 안목에서 임포트와 재구현 중 어느 쪽이 저렴할지를 예측해줄 완벽한 공식은 아무도 모른다.

21.2.3 의존성 임포트하기 @구글

  • 구글 프로젝트들이 이용하는 의존성들의 압도적 다수는 구글이 개발했다. 구글의 내부 의존성 관리의 대다수는 사실 진정한 의존성 관리가 아니라는 뜻이다.
  • 구글이 의존성을 추가하는 과정
    1. 구글 빌드 시스템에서 잘 빌드되는지 확인한다.
    2. 같은 패키지가 이미 존재하지 않음을 확인한다(버전만 다른 경우 포함).
    3. 유지보수를 맡아줄 엔지니어를 찾아 OWNERS에 추가한다(최소 두 명).
  • 코드베이스에 패키지가 오래 머무를수록 직간접적인 의존성의 수가 계속 늘어날 가능성이 높다. 패키지가 변하지 않는 기간이 늘어날수록 third_party에 추가된 특정 버전을 하이럼의 법칙이 점점 더 강하게 옥죌 것이다. 나중에 다른 버전으로 마이그레이션하기가 그만큼 어려워진다는 뜻이다.

21.3 (이론상의) 의존성 관리

  • 훌륭한 해법이라면 모든 형태의 요구사항 충돌 문제로부터 우리를 보호해줘야 한다(예: 다이아몬드 의존성 버전 충돌).
  • 언제든 새로운 의존 관계나 요구사항이 추가될 수 있는 동적인 생태계에서조차 통하는 해법이어야 한다.
  • 안정적인 의존성 관리 체계란 시간과 규모 모든 면에서 유연해야 한다. 의존성 그래프의 어느 노드라도 영원히 변치 않으리라 가정해서는 안 된다.
  • 의존성 관리 해법 네 가지. 변경 불가, 유의적 버전, 하나로 묶어 배포하기, 헤드에서 지내기.

21.3.1 변경 불가(정적 의존성 모델)

  • 애초부터 변경 자체를 허용하지 않으면 기존 의존성 때문에 불안해할 일이 사라진다.
  • 변경 불가는 대부분의 신생 조직에게는 적합한 모델일 수 있다. 새로 시작하는 프로젝트가 수십 년을 살아남고, 그래서 의존성을 물 흐르듯 업데이드해야 할 필요가 있을지를 알고 시작하는 경우는 흔치 않다. 그래서 프로젝트의 처음 몇 해는 의존성들이 완벽하게 안정적일 거라 가정하고 진행하는 게 훨씬 합리적일 것이다.
  • 프로젝트가 오래 살아남을수록 가정이 틀릴 가능성이 커지며 언제가지 유효할지를 정확하게 알 수 없는 지표가 없다. 의존성을 업그레이드해야만 하는 보안 버그나 기타 중요한 문제가 언제 터질지를 알려줄 조기 경보 시스템이 존재하지 않는다. 의존성들은 서로 얽혀있기 때문에 이론상으로는 단 하나만 업그레이드하려다가 의존성 네트워크 전체를 업데이트해야만 하는 상황에 치달을 수도 있다.

21.3.2 유의적 버전(SemVer)

  • 오늘날 의존성 네트워크를 관리하는 가장 대표적인 방법
  • SemVer: 의존성의 버전을 표기하는 보편적인 방식.
    • 메이저: API가 변경되어 의존성을 이용하던 기존 코드를 깨뜨릴 수 있음
    • 마이너: 순수하게 기능 추가만 있음(기존 코드를 깨뜨리지 않음)
    • 패치: API에 영향을 주지 않는 내부 구현 개선과 버그 수정
  • SemVer 기반 의존성 관리는 대체로 SAT 솔버를 이용한다.
  • 버전 선택(version selection): 의존성 네트워크 전체를 대상으로 어떤 알고리즘을 수행하여 모든 버전 요구사항을 충족하는 의존성 버전을 찾는 행위
  • 의존성 지옥(dependency hell): 만족스러운 버전 조합이 네트워크에 존재하지 않는 상황

21.3.3 하나로 묶어 배포하기

  • 업계에서 수십 년 전부터 이용해온 강력한 의존성 관리 모델, 애플리케이션 구동에 필요한 의존성들을 모두 찾아서 애플리케이션과 함께 배포하는 방법(예: 리눅스 배포판)
  • 이 모델에서는 새로운 역할인 배포자(distributor)가 등장한다. 개별 의존성의 메인테이너들은 다른 의존성들에 대해서는 거의 몰라도 된다. 더 거시적인 관리를 책임지는 배포자가 상호 호환되는 버전들을 찾고 패치하고 검증하는 일을 수행해주기 때문이다. 배포자는 함께 묶어 배포할 버전들을 찾고, 이 버전들로 채운 의존성 트리에서 문제가 없는지 확인하고, 문제가 생기면 해결하는 일을 담당한다.

21.3.4 헤드에서 지내기

  • 몇몇이 구글에서 추진하는 모델.
  • 이론적으로는 멋지지만 의존성 네트워크 참여자들에게 비싼 부담을 새로 지운다는 단점이 있다. 비용은 싸지 않지만 구글 같은 조직의 경계 안에서는 효과적이다.
  • 트렁크 기반 개발을 의존성 관리 영역까지 확장한 걸로 보면 된다. 소스 관리 정책인 트렁크 기반 개발을 업스트림 의존성에까지 적용한 모델이다.
  • SemVer를 버리고 의존성 제공자가 변경사항을 커밋하기 전에 생태계 전체를 대상으로 검증하게 한다.
  • 헤드에서 지내기는 의존성 관리에서 시간과 선택이라는 요소를 제거하려는 시도다. 모든 컴포넌트가 항상 최신 버전에 의존하며, 의존하는 쪽에서 수용하기 어려운 형태의 변경은 절대 허용하지 않는다. 의도치 않게 API나 행위가 달라지게 하는 변경은 일반적으로 다운스트림 의존성의 CI에서 포착되므로 커밋되지 않도록 한다.
  • 변경이 다운스트림 고객들에게 미치는 영향을 검증하는 부담이 API 제공자에게 주어진다.
  • 헤드에서 지내기는 다음과 같은 상황들을 가정한다.
    • 단위 테스트와 CI가 갖춰져 있다.
    • API 제공자 다운스트림 의존성들이 깨지는지를 확인할 수 있다.
    • API 소비자가 테스트들이 계속 통과되고 지원 가능한 방식으로 의존성을 이용 중이다.
  • 얻을 수 있는 보상들
    • API 제공자: 변경이 소비자들에게 신속하고 매끄럽게 받아들여진다.
    • API 소비자: 테스트들이 쳐주는 보호막이 꺼질 일이 없다. 어느 날 갑자기 테스트가 실패하고, 원인이 무엇인지 또 언제 고쳐질지 파악하기 어렵다면 해당 테스트들을 아예 비활성화 해버릴지도 모른다.

21.4 유의적 버전의 한계

  • 현시점까지 업계 표준 의존성 관리는 유의적 버전(SemVer)이다.
  • SemVer에서 점(.)으로 구분한 버전 번호의 정확한 정의는 사람마다 다르다. 릴리즈에 부여된 버전 숫자가 호환성을 확실히 보장해주지 않는다.
  • 의존성 그래프의 버전 값은 메인테이너가 기존 버전과 새버전을 비교해 추정한 결과다. 결국 추정과 자가 증명이라는 불안정한 토대 위에서 버전 충족 로직을 구축할 수밖에 없다.

21.4.1 지나치게 구속할 수 있다

  • 필요 이상으로 버전을 높여서든 SemVer 숫자를 충분히 세밀하게 적용하지 못해서든, SemVer가 과도하게 구속한다면 SemVer 검사만 무시하면 모든 구성요소가 아무런 문제 없이 어우러져 돌아갈 상황이더라도 자동 패키지 관리자와 SAT 솔버가 의존성을 업데이트할 수 없다고 말할 것이다.

21.4.2 확실하지 않은 약속일 수 있다.

  • SemVer를 적용한다 함은 API 제공자가 호환성을 완벽하게 추정할 수 있어서 변경을 다음의 세 가지 바구니에 나눠 담을 수 있다고 가정한다는 뜻이다.
    • 호환 안 됨(수정 혹은 삭제)
    • 조심스럽게 추가
    • API 영향 없음
  • 변경 자체만으로는 파괴적인지 아닌지 판단할 수 없다. 어떻게 쓰이고 있는가라는 맥락이 고려되어야 비로소 판단할 수 있다.

21.4.3 버전업 동기

  • 의존성 메인테이너에게는 파괴적인 변경이나 메이저 버전업을 하지 못하도록 위축시키는 요인이 다양하다. 특히나 호환성에 아주 민감한 프로젝트는 오랜 기간 메이저 버전업을 하지 않는다.
  • 파괴적 변경으로 수많은 사용자가 불편함을 겪더라도 메인테이너는 그 비용 중 작은 일부만 부담한다. 심지어 메인테이너에게는 호환성을 깨뜨려야 이익일 때도 있다(레거시 제약이 없어져야 인터페이스를 깔끔하게 재설계하기 쉽다 등).
  • 모든 프로젝트가 호환성, 쓰임, 파괴적 변경에 대한 스스로의 입장과 확히 밝혀줘야 한다고 생각한다.

21.4.4 최소 버전 선택

  • 2018년에 Go 언어용 패키지 관리 시스템 구축에 대한 에세이 시리즈의 일환으로 구글의 러스콕스는 SemVer 의존성 관리의 변형을 소개했다.
  • 최소 버전 선택(Minimum Version Selection, MVS): 작성자가 개발할 당시에 이용한 의존성들을 가능한 한 충실하게 반영한다.
  • 미래를 100% 확실할 수 없다면 가능한 한 차이가 작게 버전업하는 게 가장 안전하다. 의존성 업데이트도 작게 해주는 게 안전하다.
  • MVS는 영향받는 의존성들을 최소한의 버전업만 수행한 후 확인해보라고 말한다.
  • 안타깝게도 이 아이디어를 경험적으로 검증할 방법을 찾지 못했다.

21.4.5 그래서 유의적 버전을 이용해도 괜찮은가?

  • 어느 정도의 규모까지는 SemVer가 충분히 잘 작동한다. 하지만 SemVer의 의미를 정확히 이해해둬야 한다.
  • SemVer는 다음과 같은 경우에 잘 작동한다.
    • (SemVer 버전업 시 사람에 의한 오류가 나지 않도록) 의존성 제공자들이 이 버전을 정확하고 책임감 있게 관리한다.
    • (지나친 구속과 지나친 약속을 피하기 위해) 의존성들이 충분히 세분화되어 있다.
    • (직간접적으로 사용하는 의존성에서 호환된다고 가정하고 진행한 변경임에도 코드가 예기치 못한 방식으로 오작동하는 일을 피하기 위해) 모든 API를 제공자의 의도대로 사용한다.
  • 이상의 조건을 세심하게 고려하여 철저히 관리되는 소수의 의존성만 신중하게 선택해 네트워크를 구성한 경우라면 SemVer가 완벽한 해법이 되어줄 것이다.

21.5 자원이 무한할 때의 의존성 관리

  • 현재 업계가 SemVer에 의지하는 이유
    • 내부 정보만 있으면 된다(API 제공자가 다운스트림 사용자가 어떻게 이용하는지까지 알 필요 없다).
    • 충분한 테스트를 수행할 컴퓨팅 자원, 테스트 결과를 모니터링 해줄 CI 시스템이 존재하지 않아도 된다(아직 업계 전체에 퍼지지는 않았지만 향후 10년 안에는 반드시 달라질 것이다).
    • 관행이다.
  • 만약 더 많은 컴퓨팅 자원을 쓸 수 있고 다운스트림 의존성 정보를 더 쉽게 얻을 수 있다면 소프트웨어 업계는 분명 이 정보를 이용할 방법을 찾을 것이다.
  • OSS 생태계 전체를 책임지는 CI를 도입하지 않더라도 의존성 그래프와 테스트를 이용하여 의존성 관리라는 목적에 더 잘 부합하는 프리서브밋 분석을 수행할 수 있다.
  • 리팩터링부터 기능 수정까지 모델을 적용하기 위해 변화시켜야 할 OSS 생태계
    • 모든 의존성이 단위 테스트를 제공해야 한다. 단위 테스트가 점점 널리 받아들여지고 품질도 높아지고 있지만 아직 충분하지 못하다.
    • OSS 생태계 대부분이 의존성 네트워크를 이해해야 한다. 이 거대한 네트워크에서 그래프 알고리즘을 수행해줄 메커니즘이 존재하는지 확실치 않다. 네트워크 정보는 공개되어 있지만 적절한 형태로 인덱싱되어 있지 못하다. 많은 패키지 관리 시스템 혹은 의존성 관리 생태계가 각 프로젝트가 무엇을 의존하는지는 알려주지만, 반대로 그 프로젝트에 무엇이 의존하는지까지는 말해주지 못한다.
    • CI를 수행하기 위한 컴퓨팅 자원이 부족하다. 대다수 개발자가 빌드와 테스트에 컴퓨팅 클라우드를 이용하지 않는다.
    • 의존성을 고정해놓는 경우가 많다. 예를 들어 liba와 libb가 특정 버전의 libbase만을 이용하게끔 고정해뒀다면 libbase 메인테이너는 liba와 libb가 신버전 libbase에서도 잘 작동하는지 테스트해볼 수 없다.
    • CI 이력과 평판을 명시하는 것도 좋은 방법일 수 있다. 새로 제안된 변경이 오랜 기간 아무 문제없던 프로젝트를 실패하게 한 것과, 알 수 없는 이유로 자주 실패해온 신생 프로젝트를 실패하게 한 것은 다르게 취급할 수 있다.
  • 네트워크를 이루는 각 의존성의 어느 버전들까지 프리서브밋 검사를 해야하는가? 가장 간단한 전략은 '현시점의 안정 버전을 테스트해라'이다(결국 트렁크 기반 개발로 귀결된다). 따라서 자원이 무한할 때의 의존성 관리는 사실상 '헤드에서 지내기' 모델로 수렴한다.

21.5.1 의존성 내보내기

  • 다른 이들이 이용할 수 있는 소프트웨어를 빌드하는 방법에 대해서도 생각해봐야 한다.
  • 우리 자신과 잠재적인 고객들이 우리가 제공한 소프트웨어를 이용할 때의 이점, 비용, 위험에 대해서까지 생각해봐야 한다.
  • 선한 의도로 라이브러리를 오픈 소스로 공개했는데 오히려 조직에 해를 끼치는 방식
    1. 구현 품질이 떨어지거나 제대로 관리하지 못하면 조직의 평판을 떨어뜨린다.
    2. 동기화를 유지할 수 없다면 선의의 릴리즈가 엔지니어링 효율을 떨어뜨릴 것이다.
  • 외부 사용자는 내부 사용자보다 관리 비용이 훨씬 크다.
  • 오픈 소스 형태로든 소스는 공개하지 않는 라이브러리 형태로든, 코드를 외부 세계와 공유하는 일은 단순히 커뮤니티 기여나 비즈니스 기회로만 보고 접근할 문제가 아니다. 다른 조직에서 다른 우선순위를 가진, 감시할 수 없는 사용자들이 결국 어떤 형태로든 코드를 하이럼의 법칙으로 옭아맬 것이다.
  • 무언가를 릴리즈할지 판단할 때는 장기적인 위험을 반드시 고려야해 한다. 외부에 공개한 의존성은 날이 갈수록 비용이 더 빠르게 증가한다.

21.6 마치며

  • 현재 의존성 네트워크를 관리하는 업계 표준 방법은 유의적 버전, 즉 SemVer이다. 하지만 변경에 수반되는 위험을 요약해 제공하다 보니 때로는 중요한 내용을 빠뜨린다는 한계가 있다.
  • SemVer는 작은 규모에서는 훌륭히 제 역할을 해낸다. 최소 버전 선택(MVS) 방식까지 수용한다면 더욱 효과적이다. 하지만 의존성 네트워크가 커지면 하이럼의 법칙과 정보를 누락한다는 SemVer 자체의 문제 때문에 버전을 선택하기가 점점 어려워진다.
  • 메인테이너가 추정한 호환성(SemVer 버전 숫자)을 버리고 실제로 확인할 수 있는 증거(영향 받는 다운스트림 패키지들의 테스트 수행)를 십분 활용한다면 이상적인 세계에 한 걸음 다가 설 수 있다. 사용자들이 하던 호환성 테스트를 API 제공자가 더 많이 떠안고 앞으로 어떤 유형의 변경이 이루어질 수 있는지 명확하게 알려준다면 더 거대한 의존성 네트워크도 더 충실하게 관리할 수 있을 것이다.

21.7 핵심 정리

  • 의존성 관리보다는 되도록 버전 관리가 되도록 한다. 더 많은 코드를 조직 내로 가져와 투명성과 통제력을 높인다면 문제가 훨씬 단순해진다.
  • 소프트웨어 엔지니어링 프로젝트에서 의존성 추가는 공짜가 아니다. 의존성 네트워크에서의 신뢰 관계를 지속하는 문제는 매우 복잡하다. 조직에 의존성을 임포트 할 때는 장기 지원 비용까지 고려해서 신중하게 결정해야 한다.
  • 의존성 역시 하나의 계약이다. 계약에서는 제공자와 소비자 모두 일정한 권리와 의무를 갖는다. 제공자가 제시하는 내용에는 지금 당장뿐 아니라 미래에 대한 약속도 명확하게 포함되어야 한다.
  • SemVer는 변경이 얼마나 위험할지를 사람이 추정하는, 간단하지만 정보가 일부 손실되는 표현법이다. SemVer와 SAT 솔버 방식의 패키지 관리자는 이 추정이 틀릴 리 없다고 가정하고 동작한다. 그 결과 과잉 구속(의존성 지옥)이나 과소 구속(잘 연동되어야 하지만 그렇지 못함)으로 이어진다.
  • 이에 비해 테스트와 CI는 새로운 버전들이 잘 어울려 돌아가는지를 실제로 보여준다.
  • SemVer 기반 패키지 관리에 최소 버전 선택 전략을 가미하면 충실성이 올라간다. 버전업의 위험성을 추정하는 일은 여전히 사람에 의지하지만, API 제공자가 테스트한 구성과 소비자가 사용할 구성이 비슷해질 가능성이 확실히 높아진다.
  • 단위 테스트, 지속적 통합, (저렴한) 컴퓨팅 자원이 의존성 관리를 이해하고 처리하는 방식에 변화를 가져올 수 있다. 이 혁신적인 변화는 의존성 관리 문제와 제공자/소비자가 맡을 책임을 바라보는 업계의 시각이 근본적으로 달라져야 가능하다.
  • 의존성을 제공하는 일 역시 공짜가 아니다. 담벼락 너머로 던지고 잊어버리는 방식은 평판을 떨어뜨리고 호환성 문제를 일으키기 쉽다. 공개한 의존성을 안정성 있게 지원한다면 마음대로 수정하는 게 어려워진다. 심지어 내부 용도와 맞지 않는 방향을 선택하도록 강요받을 수도 있다. 안정성을 포기한다면 선의가 제대로 평가받지 못할 것이다. 하이럼의 법칙 때문에 주요 고객들이 위험에 노출되어 안정성 포기 계획 자체를 강행하기 어려워질 것이다.