Dev

Kill Your Dependencies (번역)

February 27, 2016

author:

Array

Kill Your Dependencies (번역)

이 글은 Mike Perham의 Kill Your Dependencies를 번역한 글입니다. 오타, 오역이 차고 넘칠 수 있습니다…

의존성을 죽입시다.

이 글은 Ruby에 대한 이야기지만, 모든 언어(파이썬, 자바스크립트, 자바, 등) 커뮤니티에서 해당되는 내용이기도 합니다. 의존성의 재앙은 누구도 용서하지 않죠.

Dependency Graph

이 그림은 내가 사용했던 모든 Rails 앱에서 볼 수 있는 의존성 시각화입니다. 다음의 내용들은 무척 친숙할 겁니다.

  • 백수십여 개의 젬을 가지고 있는 Gemfile.
  • Production에서 로딩되는 테스트용 젬.
  • 메모리 상에서 수백 메가바이트를 차지하는 Rails 프로세스.

Rubygems의 시스템은 Ruby를 다른 사람들을 위해서 잘 패키징하여 재활용하기 쉽게 만들었다는 점에서 칭찬할만 합니다. 그러나 이 말은 다시 말하자면, 이러한 젬들의 의존성이 그 젬들을 사용하는 잼들에게 쉽게 이행될 수 있으며, Rails 앱이 수백 개의 의존성을 “인터넷으로부터 다운로드”하게 만드는 결과를 가져옵니다.

루비 젬을 릴리즈하게 되면, 젬의 모든 의존성이 그 젬을 사용하는 모든 앱들의 의존성이 됩니다. 이는 젬들의 버그가 미치는 영향력을 급증하게 만듭니다.

mime-types의 예시

mime-types 젬은 최근 메모리 사용량을 최적화하여 수 메가바이트의 메모리를 절약할 수 있게 되었습니다. 말 그대로 존재하는 모든 Rails 앱은 이 최적화의 혜택을 볼 수 있습니다. 왜냐하면 Rails가 mime-types 젬에 간접적으로 의존하고 있기 때문입니다: rails -> actionmailer -> mail -> mime-types.

다시 말하자면, 이 젬은 앱에서 직접적으로 사용되지 않습니다. Rails에 의해서 직접적으로 사용되는 것도 아닙니다. 액션 메일러에 의해서 직접 사용되지도 않습니다. 이는 액션 메일러의 구현 깊은 곳에서 사용되고 있으며 많은 메모리를 차지하고 있습니다. 존재하는 모든 Rails 앱은 이 때문에 10MB나 많이 메모리를 사용하고 있죠.

앱 개발자들은 들으세요!

애플리케이션에 있는 모든 의존성은 앱을 비대하게, 불안정하게, 몽키패칭이나 버그가 존재하는 코드를 통해서 이상한 동작을 야기할 가능성이 존재합니다. Rails 앱에 의존성을 추가할지를 고려하는 경우에는 지금 정상적인 판단을 할 수 있는지 확인해보는 것이 좋습니다:

  • 내가 정말 이게 필요해?
  • 내가 요구된 최소한의 기능을 직접 구현할 수 있을까?

만약 젬이 필요하다면:

  • 젬이 네이티브 확장을 가지고 있다면, 순수한 루비 구현체를 찾으세요.
  • 젬이 수많은 다른 젬을 이행적으로 의존하고 있다면, 더 간단한 대체재를 찾으세요.

네이티브 확장을 가지고 있는 젬은 시스템을 불안정하게 만들거나 정체불명의 버그나 크래시를 남길 수도 있습니다. 직접 구현한 내용들보다 젬 의존성이 더 많아 보이는 젬들을 피하세요. 예를 들자면, fog 젬은 39개의 젬을 요구합니다. Rails보다도 많은 의존성을 요구하며, 대부분은 별로 필요도 없는 것들입니다.

마지막으로 정말로 필요한 젬만을 불러오세요. 번들러의 그룹 기능을 사용하여 테스트 용 젬들을 테스트를 하지 않을 때에는 사용하지 않도록 하세요.

group :test do
  gem 'rspec'
  gem 'timecop'
  # etc
end

젬 개발자들은 들으세요!

라이브러리 저자로서의 일중 하나는 사용자들과 그들의 애플리케이션을 존중하는 것입니다. 라이브러리의 의존성으로 인한 영향을 최소화하여 불필요한 코드를 불러오거나, 사용자의 애플리케이션에서 문제가 발생하도록 해서는 안됩니다. 라이브러리의 코드는 통제 가능하지만, 라이브러리의 의존성은 통제될 수 없기 때문입니다. 의존성에 존재하는 버그는 라이브러리의 버그가 되어서 사용자들과 그들의 애플리케이션에 스트레스를 줄 수 있습니다.

젬 개발자로서, 각 젬의 의존성들이:

  • 얼마나 메모리를 사용하는지 알고 있나요?
  • 각각을 불러오는데 걸리는 시간이 얼마인지 알고 있나요?
  • 자신의 바깥에 있는 코드를 몽키패칭하고 있는지 아닌지 알고 있나요?

Sidekiq는 자신의 모든 기능들을 위해서 딱 3개의 의존성을 가지고 있습니다: concurrent-ruby, connection_pool, redis.

죽어, json, 진짜로

많은 젬들이 json, oj, multi_json, yajl-ruby에 대한 의존성을 선언합니다. JSON 처리에는 무척 불쾌하고 경화된 레이어들이 다수 존재하며, 이를 정리하는 방법은 단 하나입니다: 다 제거하세요. JOSN은 1.9 이후로 stdlib의 일부가 되었으며, 이를 의존성의 일부로 선언할 필요가 전혀 없습니다. 그냥 require 'json'을 쓰고 루비가 처리하도록 내버려두세요.

Rails도 했으니까, 당신도 할 수 있어요!

왜 HTTP 클라이언트를 선택하나요?

모든 Rails 앱은 서로 다른 HTTP 클라이언트를 반 다스 정도 끌어옵니다: faraday, rest-client, httparty, excon, typhoeus, curb, 등등. 이는 여러 젬들이 서로 다른 것들을 내부적으로 사용하고 있기 때문입니다. 루비 젬은 HTTP 클라이언트를 사용하지 말고, 표준의 Net::HTTP을 사용하세요! Net:HTTP의 API를 배운 뒤에, 이러한 의존성을 제거하여 사용자들이 여분의 HTTP 클라이언트 젬을 사용하지 않게 하세요.

curb를 사용해서 좀 더 최적화된 기능을 제공하고 싶다고 합시다. 그런건 좋아요, 하지만 최종 결정권은 사용자에게 줍시다. 애플리케이션 개발자가 원한다면 curb를 사용할 수 있게 하고 net/http가 항상 기본이 되어야 합니다.

Rails 5.0 최적화하기

최근 몇주 동안에, Rails 5.0가 의존하는 젬 숫자를 줄이는 작업을 하고 있었습니다. Rails 4.2.5는 34개의 젬을 요구하며 Rails 5.0b1는 55개의 젬을 요구했습니다. 그리고 Rails 5.0b2는 39개의 잼을 요구합니다. Rails 5.0은 37개, 또는 그 이하의 갯수의 젬을 사용하길 바랍니다. 우리는 지금까지 Celluloid, EventMachine, thread_safe, 그리고 json를 제거했습니다.

불행하게도 이제 더 이상 쉽게 제거할 수 있는 젬이 없습니다. 저는 무척 커다란 의존성과 대량의 네이티브 확장 모듈을 가지고 있는 Nokogiri를 제거하고 싶습니다만, 거기에는 무척 중요한 의존성이 포함되어 있습니다. Oga는 무척 멋지고, 간결한 대체재입니다. 만약 Nokogiri를 의존성으로 사용하는 젬을 만들고 있다면, 그 모듈을 옵션으로 남겨두고 REXML(저도 압니다, 하지만 그건 적어도 stdlib에 포함되어 있죠)을 쓰거나 Oga를 기본으로 사용하는 것을 고려해보세요.

해결책의 일부가 되기

저는 Rails 5.0을 도울 수 있지만 모든 젬들을 고칠 수는 없습니다. 만약 젬 개발자라면, 젬의 의존성을 확인해보고 최대한 제거해보세요. 만약 앱 개발자라면 Gemfile을 확인하고, 필요없는 젬이 없는지 확인해보세요. 간결하게, 좀 더 간결하게 말이죠.

예를 들자면, Stripe gem에서 런타임 의존성에서 두 젬을 제거할 수도 있을 겁니다(역주: 번역 시점에서 json 젬의 의존성이 제거 되었고 rest-client 의존성만이 남아있습니다).

기억해야할 규칙들

소프트웨어 공학 규칙입니다.

  • 코드가 없는 것보다 빠른 코드는 없다.
  • 코드가 없는 것보다 버그가 적은 코드는 없다.
  • 코드가 없는 것보다 적게 메모리를 소모하는 코드는 없다.
  • 코드가 없는 것보다 이해하기 쉬운 코드는 없다.

이러한 의존성을 제거합시다. 젬과 앱이 더 나아질 겁니다.

Array