GN⁺: "컬럼이 부족해요" - 최고이면서 최악이었던 코드베이스
(jimmyhmiller.github.io)"merchants2 테이블이요? 네, merchants의 컬럼이 부족해서 merchants2를 만들었습니다."
- 프로그래밍을 처음 시작했을 때, 사람들이 프로그래밍으로 돈을 번다는 사실을 몰랐음
- 첫 소프트웨어 직장에서 많은 것을 배웠는데, 그 곳의 코드베이스는 최악이자 최고였음
데이터베이스는 영원히 살아남음
- 레거시 시스템에서 데이터베이스는 단순한 데이터 저장소 이상의 역할을 함. 시스템 전체의 제약 조건을 설정하고 모든 코드가 만나는 지점임
- SQL Server에는 테이블의 열 개수 제한이 있음. 당시에는 1024개, 현재는 4096개임. Merchants 테이블의 열이 모자라서 Merchants2 테이블을 만들었음 (500개 이상의 열 포함)
- Merchants와 Merchants2는 시스템의 핵심이었음. 모든 것이 Merchants로 연결됨. 다른 정규화된 테이블들도 있었지만 Merchants와 외래키 관계를 가짐
SequenceKey
- SequenceKey는 하나의 열과 하나의 값만 가진 단순한 테이블임
- 이는 ID 생성에 사용되었음. SQL Server가 자동 증가 ID를 지원하지 않았기 때문에 만들어진 것으로 추정됨
- 모든 저장 프로시저에서 SequenceKey에서 키를 가져와 증가시키고, 여러 테이블의 ID로 사용함. 암시적 조인 역할을 했음
Calendar
- Calendar는 수동으로 입력된 달력 테이블임. Calendar가 만료되면 시스템에 로그인할 수 없었음
- 몇 년 전 이런 일이 있었고, 인턴이 5년치를 더 채워 넣었음. 어떤 시스템이 이를 사용하는지는 아무도 모름
Employees
- 매일 아침 7시 15분에 employees 테이블이 삭제되고 ADP에서 받은 CSV로 다시 채워짐
- 이 작업 중에는 시스템에 로그인할 수 없음. 때때로 이 작업이 실패하기도 함
- 데이터는 본사로 복제되어야 했기 때문에 이메일로 한 사람에게 보내져 매일 버튼을 눌러 데이터를 복사했음
교체용 데이터베이스
- 데이터베이스를 정리할 수 있지 않을까 생각할 수 있음. 회사도 그렇게 생각했음
- 데이터베이스의 사본이 있었는데, 데이터는 약 10분 정도 지연되어 있었음. 동기화는 한 방향으로만 이루어짐
- 이 데이터베이스는 정규화되어 있어서 merchants에서 전화번호를 찾으려면 7개의 조인이 필요했음
판매 실적
- 모든 영업사원은 매달 달성해야 할 "win"이라는 할당량이 있었음
- 이를 관리하는 테이블은 매우 복잡했음. 매일 작업이 실행되어 추가/수정된 행을 찾아 본사 시스템과 동기화함
- 한 영업사원이 수동으로 레코드를 변경해달라고 요청하면서 문제가 발생함
- 해당 영업사원은 이미 win을 달성했고 그 달에 큰 판매를 더 했는데, 이를 다음 달로 옮기길 원함
- 인턴이 이 작업을 맡았고, 소문이 퍼지면서 3년 동안 요청이 기하급수적으로 증가함
- 한 때는 인턴 3명이 SQL 문을 작성하는 것이 전부였음. 이를 위한 애플리케이션을 만드는 것은 너무 어렵다고 여겨졌음
코드베이스
- 내가 처음 접한 코드베이스는 Team Foundation Server에 있었음. 중앙 집중식 버전 관리 시스템임
- 주로 작업한 코드베이스는 절반은 VB, 절반은 C#으로 되어 있었음. IIS에서 실행되었고 세션 상태를 모든 곳에 사용함
- 이는 실제로 Path A나 Path B로 페이지에 접근하면 그 페이지에서 매우 다른 것을 보게 된다는 것을 의미함
- 당시 존재했던 모든 자바스크립트 프레임워크가 이 저장소에 체크인되어 있었음. 주로 작성자가 필요하다고 생각한 사용자 정의 변경 사항과 함께였음. 특히 knockout, backbone, marionette가 눈에 띄었고, jquery와 jquery 플러그인도 있었음
- 이 코드베이스 외에도 12개 정도의 SOAP 서비스와 여러 개의 네이티브 Windows 애플리케이션이 있었음
Gilfoyle의 하드 드라이브
- Gilfoyle은 엄청나게 빠른 프로그래머로 알려져 있었음. 그를 만나본 적은 없지만, 그의 코드와 하드 드라이브에 남은 코드를 통해 그를 알고 있었음
- Munch는 Gilfoyle이 회사를 떠난 지 몇 년이 지났음에도 그의 하드 드라이브를 RAID 구성으로 책상 위에 보관하고 있었음
- 왜냐하면 Gilfoyle은 코드를 체크인하지 않고, 단일 사용자를 위한 임의의 일회용 Windows 애플리케이션을 만드는 것으로 유명했기 때문임
- 사용자가 Gilfoyle의 하드 드라이브에만 존재하는 애플리케이션의 버그 보고서를 가지고 오는 것은 드문 일이 아니었음
배송 버그
- 내 업무의 대부분은 팀이 작업에 할당하고 싶어하지 않는 버그를 추적하는 것이었음
- 몇 달에 한 번씩 발생하는 특히 성가신 버그가 있었는데, 배송 후 배송 대기열에 걸린 주문이 있었고, 이미 배송되었으면서도 배송되지 않은 것으로 나타났음
- 해결을 위해 여러 가지 해결책(SQL 스크립트, Windows 애플리케이션 등)을 시도했음. 근본 원인을 추적하지 말라는 조언을 받았지만 나는 참을 수 없었음
- 과정에서 Gilfoyle의 사고방식을 배웠음. 배송 앱은 전체 데이터베이스를 가져온 다음 날짜로 필터링하여 애플리케이션의 시작 날짜 이후의 모든 주문을 보관함
- 앱은 SOAP 서비스에 의존했지만, 서비스스러운 일을 하기 위해서가 아니라 순수 함수였음. 클라이언트가 모든 부작용을 야기했음
- 그 클라이언트에서 거대한 클래스 계층 구조를 발견했는데, 120개의 클래스가 각각 다양한 메서드를 가지고 있었고, 상속은 10단계까지 내려갔음
- 유일한 문제는 모든 메서드가 비어있다는 것이었음
- 이는 반사를 사용할 수 있는 구조를 만들기 위한 것이었음. 그 반사는 파이프로 구분된 문자열(데이터베이스 기반이지만 완전히 정적인 구조)을 만들어 소켓으로 보냈음
- 결국 이는 운송 업체와 통신하는 서비스인 Kewill로 전송되었음. 버그가 발생한 이유는 Kewill이 매월 9자리 숫자를 재사용했고, 누군가 오래된 주문을 삭제하는 cron 작업을 비활성화했기 때문임
아름다운 혼란
- 이 코드베이스에 대해 할 말이 많음. 5년 동안 코드를 출시하지 않고 전체를 다시 작성하는 수석 개발자 팀이나, 모든 것을 통제할 하나의 데이터베이스를 구축하는 Red Hat 컨설턴트 등
- 이 코드베이스에는 많은 미친 구석이 있었고, 단일 기능을 처음부터 시작하기 위해 전담 팀이 있어야 할 만한 이유가 많았음
- 그러나 가장 중요한 이야기는 Justin이 Merchants Search 페이지를 개선한 것임. 이 페이지는 전체 애플리케이션의 진입점이었음
- 모든 고객 서비스 담당자는 상인과 전화 통화를 하면서 ID나 이름을 입력하여 정보를 찾았음. 그러면 모든 정보가 담긴 거대한 페이지로 이동함
- 이 페이지는 필요한 모든 정보와 방문하고 싶은 모든 링크로 가득 차 있어 가장 좋은 방식으로 정보가 밀집되어 있었음. 그러나 엄청나게 느렸음
- Justin은 내 그룹의 유일한 수석 개발자였음. 그는 영리하고 빈정거리는 태도였으며 비즈니스에는 별 관심이 없었음
- 그는 사실대로 말했고, 주먹을 쓰지 않았으며, 항상 주변 팀보다 빠르게 혼자 문제를 해결할 수 있었음
- 어느 날 Justin은 상인 검색 페이지가 얼마나 느린지에 대해 듣는 것에 질려서 가서 고쳤음
- 화면의 모든 상자가 자체 엔드포인트가 되었음. 로드 시 폴드 위의 모든 것이 가져오기 시작했고, 하나가 로드되면 더 많은 요청이 들어왔음
- 페이지 로드 시간이 몇 분에서 1초 미만으로 단축되었음
디커플링의 두 가지 방법
- Justin이 이렇게 할 수 있었던 이유는 이 코드베이스에 마스터 플랜이 없었기 때문임
- 시스템이 맞춰야 할 대략적인 설계도 없었고, API의 예상 형식도, 문서화된 디자인 시스템도, 일관성을 보장하는 아키텍처 검토 위원회도 없었음
- 앱은 완전히 엉망진창이었음. 아무도 고칠 수 없었기 때문에 아무도 시도하지 않았음. 대신 우리는 자신만의 작은 이성의 세계를 만들었음
- 이 모놀리식 앱은 순전히 필요에 의해 가장자리에 좋고 작은 앱들의 소우주로 성장했음
- 앱의 일부를 개선하는 임무를 맡은 각 사람은 필연적으로 그 거미줄을 풀려는 시도를 포기하고, 새로운 것을 만들 좋고 작은 구석을 찾았음. 그리고 천천히 링크를 업데이트하여 좋은 새 것을 가리키고 낡은 것을 고아로 만들었음
- 이것이 엉망으로 들릴 수 있음. 그러나 일하기에 놀랄 만큼 즐거웠음. 코드 중복에 대한 걱정은 사라졌고, 일관성에 대한 걱정도, 확장성에 대한 걱정도 사라졌음
- 코드는 사용을 위해 작성되었고, 주변 영역을 가능한 한 적게 건드리고, 쉽게 교체할 수 있도록 작성되었음. 우리의 코드는 디커플되었는데, 커플링하는 것이 단순히 더 어려웠기 때문임
그 후
- 그 이후 경력에서 그렇게 놀랍게 추한 코드베이스에서 일할 특권을 누린 적이 없음
- 그 이후 만난 모든 추한 코드베이스는 일관성에 대한 필요를 초월하지 못했음
- 아마도 "진지한" 개발자들이 오래전에 코드베이스를 버렸기 때문이었을 것임. 남은 것은 난잡한 인턴과 주니어 개발자뿐이었음
- 아니면 개발자와 사용자 사이에 중간 계층이 없었기 때문일 수도 있음. 번역도, 요구사항 수집도, 카드도 없었음. 그저 당신이 고객 서비스 담당자의 책상 앞에 서서 그들의 삶을 어떻게 개선할 수 있을지 물어보는 것뿐이었음
- 그 직접적인 연결이 그리워짐. 빠른 피드백, 거창한 계획을 세울 필요가 없었던 것, 단순한 문제와 코드의 연결
- 아마 단순히 순진한 노스탤지어일 것임. 그러나 내 어린 시절 최악의 몇 년으로 돌아가고 싶어 하는 것처럼, "엔터프라이즈 설계 패턴"에 직면할 때마다 내 마음은 그 아름답고 끔찍한 코드베이스로 되돌아감
GN⁺의 의견
- 이 글은 레거시 시스템을 다루는 개발자들에게 공감과 위안을 줄 수 있음. 완벽하지 않은 코드베이스도 가치 있고 배울 점이 많다는 것을 보여줌
- 그러나 이 코드베이스의 문제들이 낭만화되어서는 안 됨. 기술 부채가 축적되면 개발 속도를 크게 저하시키고 유지보수를 어렵게 만듦. 장기적으로는 비용이 더 많이 듦
- 코드베이스를 개선하려는 노력을 포기하고 계속 임시방편을 쌓는 것이 정답은 아님. 레거시 시스템을 점진적으로 개선하거나 마이그레이션하는 전략이 필요함
- 개발 문화와 프로세스도 중요함. 코드 리뷰, 아키텍처 설계, 문서화 등 엔지니어링 관행을 잘 따르는 것이 더 나은 코드베이스를 만드는 데 도움이 됨
- 사용자와의 긴밀한 소통과 빠른 피드백은 좋은 점임. 애자일과 같은 방법론을 통해 이를 촉진하면서도 코드 품질을 관리하는 것이 이상적임
- 결국 모든 것은 균형의 문제임. 완벽을 추구하기보다는 지속 가능한 방식으로 사용자의 필요를 충족시키고 기술 부채를 관리하는 것이 중요함
첫 직장에서 펌웨어 개발자를 하며 받은 임무가, 개발자와 소스코드가 없는 제품의 8051 MCU의 hex에서 추출한 어셈 코드를 분석해서 c로 재구현 하는 일이었었네요…
그나마 돌아가는 제품이 있었기에 코드 보고 제품 테스트 해보고 하며 어찌저찌 해내긴 했는데…
지방 출장 갔다가 고쳐내던지 손가락을 자르고 가던지 하는 협박도 들어보고
알수 없는 버그가 사실은 장치가 설치된 벽 뒤에 있던 엘리베이터 때문이었던 적도 있었고
부산 동백섬 apec 회의장 공식 개장하기 전에 들어가 이것저것 설치 했던 기억도 나고 ㅎㅎ
Hacker News 의견
-
첫 회사에서 복잡한 VB 애플리케이션을 관리했음
- 각 고객의 요구에 맞춘 글로벌 변수가 많았음
- 디버그 모드에서만 버그가 나타나지 않아 고객에게 Visual Studio를 설치하고 디버그 모드로 실행하도록 가르쳤음
- 버전 관리가 없었고, 코드가 여러 폴더에 복사되어 혼란스러웠음
- 고객 문제 발생 시 현장에서 코드를 수정했음
- 최종 버전에 대한 합의가 없어 고객마다 다른 버전을 사용했음
-
첫 직장에서 COBOL과 Java로 작성된 레거시 제품을 유지보수했음
- 소스 제어 시스템에서 파일을 개별적으로 체크아웃하여 작업했음
- 고객마다 최종 컴파일된 제품을 나타내는 '마스터' jar 파일이 있었음
- 코드 변경 후 스크립트를 실행하여 마스터 jar 파일을 업데이트했음
- 코드베이스가 전체적으로 컴파일되지 않고 수동으로 패치되었음
- 코드베이스에 많은 불일치가 발생했음
- git으로 마이그레이션하는 데 2년이 걸렸음
-
12,000줄 이상의 Perl 스크립트를 리팩토링했음
- 배열을 몰랐던 작성자가 문자열을 사용하여 배열을 구현했음
- 리팩토링 후 코드가 200줄로 줄어들었음
-
이론과 실제의 차이를 느꼈음
- 많은 회사와 프로젝트가 비슷한 과정을 겪음
- 이상적인 방법을 논의하지만, 실제로는 작동하는 방식으로 진행됨
-
Telegram 안드로이드 클라이언트의 코드베이스가 매우 복잡했음
- 큰 파일로 인해 GitHub가 렌더링을 포기했음
- 모든 메시지 렌더링과 상호작용을 단일 클래스에서 처리했음
- 리팩토링 권한을 받았지만 실행하지 못했음
-
첫 직장에서 보고 작업의 메모리 문제를 해결했음
- 팀 리더와 상사가 놀랐음
- 다른 개발자들이 Linux를 싫어하고 .NET으로 전환하려 했음
-
고객과 직접 소통하며 문제를 해결했던 경험이 좋았음
- 프로토타입을 빠르게 만들어 고객에게 테스트하게 했음
- 코드베이스는 지저분했지만 잘 작동했음
-
여러 시장을 지원하는 시스템을 만들었음
- 원래 시스템을 복사하여 국제 버전을 만들었음
- 5년 후 시스템이 분리되지 않고 서로 얽혀 있음
- 새로운 시스템과 구 시스템이 긴밀하게 결합되어 있음
-
첫 직장에서 경험이 부족한 사람들이 많았음
- 경험을 쌓은 후 더 나은 직장으로 이동했음
-
현대 SQL Server에서 문제를 해결하는 방법을 설명했음
- 고객 맞춤화, 공유 ID, 수동 캘린더 테이블, 테이블 파티셔닝, 지연된 보고 복제본 등
- VB와 C# 혼합 코드베이스가 흔함
- 자동 변환 도구를 사용할 수 있음