Ruby 2500만 라인 포맷팅: Stripe의 `rubyfmt` 스토리

Stripe는 최근 rubyfmt를 사용해 2500만 라인의 Ruby 코드베이스 전체를 하룻밤 사이에 포맷팅한 경험을 공유했습니다. 이 야심찬 프로젝트는 방대한 규모에서 코드 품질과 개발자 생산성을 유지하는 데 필요한 복잡성과 고려사항을 강조합니다. 이 작업은 일관된 코드 스타일에 대한 큰 헌신을 보여주며, 자동화 도구를 활용해 개발 워크플로를 간소화하고 엔지니어의 인지 부하를 줄입니다.

이처럼 방대한 코드베이스를 점진적으로가 아니라 단일 원자적 작업으로 재포맷하기로 한 결정은 위험과 보상의 계산된 평가를 반영합니다. 이는 대규모 엔지니어링 조직에 통합된 코드 스타일이 최우선이라는 믿음을 강조하며, GitHub가 렌더링하기 어려울 정도로 거대한 diff가 발생하더라도 마찬가지입니다.

도전의 규모: 2500만 라인

Stripe의 Ruby 코드베이스—2500만 라인—의 어마어마한 규모는 많은 논의를 불러일으켰습니다. 많은 사람들이 이 수치에 놀라며, 그러한 시스템의 특성과 아키텍처에 대해 질문했습니다.

"나는 2500만 라인이라는 부분에 충격받았어요! 하나의 코드베이스에 이렇게 어마어마한 양의 코드는 상상도 할 수 없습니다. 더 자세히 알고 싶어요." — @varun_ch

주요 금융 처리 회사가 Ruby를 사용한다는 점도 눈살을 찌푸리게 했으며, 일부는 우려를 표했습니다.

"주요 금융 처리 회사가 돈 관리 시스템을 Ruby로 작성한다니. 무섭네요." — @andrewstuart

하지만 이러한 규모는 도구에 대한 독특한 기회를 제공하기도 합니다. 한 댓글자는 우리가 처리하는 데이터에 비해 코드 자체는 종종 매우 작아 대규모 작업이 가능하다고 언급했습니다.

"코드에 대한 통찰은 우리가 데이터 규모에 비해 코드는 아주 작다는 것입니다. 토큰이 스트리밍되는 동안에도 즉각적인 git 작업과 “전체 코드를 대상으로 이 도구를 실행”하는 것이 일반적입니다. 이 통찰은 명백해 보일 수 있지만, 작업하면서 이를 인식하고 있다면 자신과 팀을 위해 꽤 놀라운 도구를 만들 수 있습니다." — @cadamsdotcom

rubyfmt 접근법: 한 번에 전체 vs. 점진적

Stripe가 주말에 “한 번에 전체” 재포맷을 수행하기로 선택한 것은 병합 충돌을 피하기 위한 의도적인 전략이었습니다. 팀은 자신감을 위해 테스트 스위트에 크게 의존했지만, 이렇게 거대한 변화가 갖는 어려움을 인정했습니다.

이 접근법은 일부 개발자들이 대규모 코드베이스에 선호하는 점진적 전략과 대조됩니다. 점진적 방법은 새로운 PR에 의해 파일이 수정될 때만 재포맷하거나, 더 작은 배치로 진행할 수 있습니다.

"그들이 한 번에 전체 재포맷을 선택한 것이 놀랍습니다. 주말에 진행하더라도 그 규모에서는 많은 열린 PR에 영향을 미칠 수밖에 없습니다. 저는 과거에 몇몇 큰 코드베이스(수십만에서 수백만 LOC)에 포맷터를 도입했는데, 항상 열린 PR에 의해 수정되지 않은 모든 파일을 재포맷하는 스크립트를 통해 점진적으로 진행했습니다. 초기 실행에서는 전체 파일의 95%를 재포맷했습니다." — @hobofan

빅뱅 접근법의 장점은 전체 코드베이스에 즉각적인 일관성을 제공하여 일부 파일만 포맷되고 다른 파일은 포맷되지 않는 과도기를 없앤다는 점입니다. rubyfmt 도구 자체는 Rust로 작성되었으며, 이는 성능이 중요한 개발자 도구에서 흔히 선택되는 언어입니다.

정확성 보장: 정상성 검사

모든 자동 포맷터에서 중요한 측면은 공백만 변경하고 코드 의미를 절대 바꾸지 않도록 보장하는 것입니다. Stripe 팀은 rubyfmt에 대한 자신감을 견고한 테스트를 통해 강화했습니다. 예를 들어 Dart 포맷터는 엄격한 내부 정상성 검사를 사용합니다:

"dart 포맷터는 내부 정상성 검사를 가지고 있습니다. 포맷되지 않은 문자열과 포맷된 문자열을 병렬로 순회하면서 공백을 건너뜁니다. 비공백 문자가 일치하지 않으면 즉시 중단합니다. 이는 포맷터가 변경하는 것이 공백뿐임을 보장하며, 거대한 코드베이스에서 눈을 감고 실행해도 덜 무섭게 만듭니다." — @munificent

이러한 종류의 보호 장치는 특히 복잡한 언어 기능이나 새로운 구문을 다룰 때 매우 귀중합니다. 포맷팅 오류로 인해 미묘한 버그가 코드베이스에 스며드는 것을 방지합니다.

코드 포맷팅 철학

포맷팅에 투입된 노력은 특히 AI가 코드 생성에 점점 더 관여하는 시대에 그 궁극적인 가치에 대한 논쟁을 촉발했습니다.

"코드를 포맷팅하는 것이 이제 무슨 의미가 있나요." — @throwatdem12311

"분명히 이제는 인간이 읽을 필요도 없고, AI가 우리에게 식사표를 작성해 주는 시대에 쓰기 전용 코드 시대가 도래했습니다. 2500만 라인의 형편없는 코드를 포맷팅할 필요가 무엇이며, AI가 코드를 인간이 읽을 수 있게 만드는 데 토큰을 낭비하는 이유가 뭘까요?" — @CrzyLngPwd

하지만 코드 가독성이라는 인간적 요소는 협업과 유지보수에 여전히 중요합니다. 유머러스한 일화는 개인 취향과 팀 표준 사이의 긴장을 강조했습니다:

"팀 리드 개발자는 코드 포맷팅에 신경 쓰는 것을 싫어했기 때문에, 나는 그의 난잡한 스파게티 같은 코드를 좋은 들여쓰기와 레이아웃으로 바꿔 일반 사람들이 파싱하기 쉽게 만드는 makenice라는 도구를 만들었습니다. 그는 격분했죠... 그래서 나는 그가 좋아할 것 같은 방식으로 코드를 포맷팅하는 makenasty를 만들었습니다. 나는 makenastymakenice를 팀 몇 명에게만 공유했는데, 그들은 읽기 쉬운 형태와 팀 리드가 좋아하는 형태 사이를 쉽게 변환할 수 있어 매우 만족했습니다." — @CrzyLngPwd

이는 포맷팅이 사소한 디테일처럼 보일 수 있지만, 개발자 경험과 팀 결속에 큰 영향을 미친다는 것을 보여줍니다. 미래에는 텍스트 기반 포맷팅을 완전히 벗어나 코드를 파스 트리 형태로 저장하고 조작하는 방향으로 전환될 수도 있습니다:

"정말로 파스 트리를 저장하고 이를 git과 같은 방식으로 노출하는 것을 막는 원칙적인 제약이 없다는 점을 떠올리게 합니다. 이렇게 하면 포맷팅 자체가 필요 없게 되고, 포맷팅에 기반한 병합 충돌 전체를 해결할 필요도 없어집니다. 포맷은 단지 데이터, 즉 코드 위에 입히는 테마에 불과합니다." — @eigenblake

전략적 도구와 생산성

rubyfmt 이야기는 개발자 생산성 도구에 투자하는 가치의 증거입니다. 가장 복잡한 구문부터 해결함으로써 rubyfmt 팀은 견고한 포맷터를 구축하기 위한 건전한 전략을 채택했습니다.

"그 복잡성을 고려했을 때 가설은 간단했습니다: 가장 어려운 구문을 먼저 해결하면 나머지는 따라올 것이라는 것이죠. 항상 보기 좋은 사례입니다. 저는 사람들이 일반적인 경우에만 설계하는 함정에 빠지는 것을 보았는데, 실제로 대부분의 코드는 덜 일반적인 경우를 다루게 됩니다." — @nitwit005

이 접근법은 도구가 마주하게 될 모든 코드 스펙트럼을 처리할 수 있도록 보장하며, 신뢰할 수 있고 일관된 출력을 제공합니다. 수백만 라인의 코드에 일관된 스타일을 자동으로 적용할 수 있게 되면 개발자는 수동 포맷팅 고민에서 해방되어 로직과 기능에 집중할 수 있습니다.

Stripe가 rubyfmt를 사용해 거대한 Ruby 코드베이스를 하룻밤 사이에 성공적으로 재포맷한 것은 개발자 도구와 대규모 코드 관리 분야에서 중요한 성과로 자리 잡았습니다. 이는 일관된 코드 스타일의 중요성, 견고한 자동 도구의 힘, 그리고 광범위한 엔지니어링 환경에서 높은 생산성과 코드 품질을 유지하기 위한 전략적 결정을 강조합니다.

Sources