Showing Posts From

전쟁

레거시 코드와의 전쟁: 언제까지 봐줘야 하나

레거시 코드와의 전쟁: 언제까지 봐줘야 하나

레거시 코드와의 전쟁: 언제까지 봐줘야 하나 월요일 아침, 또 시작된다 월요일 9시. 커피를 마시며 슬랙을 확인하는 순간, 그 메시지가 떴다. "개발팀, 어제 결제 모듈에서 버그 발생. 긴급 조치 필요!" 한숨을 쉬며 코드베이스로 향한다. 이미 알고 있다. 이 버그는 5년 전 누군가가 만들어놓은 레거시 코드 속에 깊이 박혀있을 것이다. 급히 작성되었을 거다. 커밋 메시지는 아마 "fix: payment bug" 정도. 당시 개발자는 이미 떠났고, 남은 건 주석 없는 코드 덩어리뿐이다.나는 7년을 이 회사에서 보냈다. 그 세월이 얼마나 길었는지는 내가 얼마나 많은 다른 사람의 코드를 봤는가로 측정된다. 입사했을 때 이미 레거시였던 것들, 3년 전에 다시 레거시가 된 것들, 내가 작성했다가 이제는 남이 보기엔 레거시인 것들... 이 모든 게 뒤섞여 있다. "김개발님, 이거 어디 고쳐야 해요?" 후배 정호가 물어본다. 나는 모니터를 보면서 펜을 돌린다. 그 동작은 이미 무의식적이다. 생각할 시간이 필요하다. 코드를 이해할 시간이 아니라, '이 코드를 어떻게 설명할 것인가'에 대한 시간이다. 레거시 코드란 무엇인가 정확히 말하면, 레거시 코드는 "이전 개발자가 작성한 코드"를 뜻하지 않는다. 사실 더 정확한 정의는 "테스트되지 않은 코드"이다. 그리고 대부분의 경우, 그 코드가 작성된 배경을 알 수 없는 코드다. 우리 회사의 결제 모듈은 2017년에 작성되었다. 당시 개발자는 신입이었을 거다. 마감이 촉박했을 거고, 리뷰는 제대로 안 됐을 거다. 그렇게 탄생한 코드가 이제 회사의 심장 부분이 되어버렸다. // payment_module_2017.java - 이런 식의 주석뿐 public class PaymentProcessor { public void processPayment(String userId, String amount, String type) { // TODO: refactor this later if(type.equals("CARD")) { // ... 50줄의 복잡한 로직 } else if(type.equals("ACCOUNT")) { // ... 30줄의 비슷한 로직 } else { // ??? } } }이런 코드를 보면 정말 묘한 감정이 든다. 분노? 아니다. 그것보다는... 안타까움? 아니, 동정심인가? 저 코드를 쓴 사람도, 나처럼 야근하고 있었을 거다. 그 사람도 "나중에 리팩토링하겠지" 하고 있었을 거다. 하지만 "나중"은 절대 오지 않는다. 긴급 이슈의 악순환 "긴급"이라는 단어만큼 개발자를 좌절시키는 단어가 있을까. 지난 3개월간 나의 캘린더를 보면, 내 일정의 60%는 긴급 이슈였다. 긴급하지 않은 계획된 작업은 20%. 나머지 20%는 회의와 리뷰다. 계산이 맞지 않는다고? 응, 내가 초과근무하는 시간이 20%라는 뜻이다. 우리 팀장은 좋은 사람이다. 나쁜 사람이 아니다. 다만 마케팅 부서의 압박을 받고 있고, 나는 그의 압박을 받고 있다. 기획팀은 "이거 간단하죠?"라고 묻는데, 그들이 모르는 건 그 "간단한" 기능이 엄청나게 복잡한 레거시 코드에 짜여있다는 거다. 지난달의 예를 들어보자. 월요일: 새로운 결제 게이트웨이 연동 요청. 마감은 금요일. "간단한 연동이니까 3일이면 되겠지?" 화요일: 기존 결제 모듈의 구조를 파악하려고 시도. 결제 처리 로직이 3개 파일에 흩어져 있음을 발견. 데이터베이스 스키마도 난장판. "누가 이렇게..." 수요일: 기존 로직을 수정하면서 다른 부분에서 버그 발생. 결제 조회 기능이 깨짐. 긴급 회의. 목요일: 야근. 밤 11시, 결국 새로운 연동을 추가했는데 테스트를 다 못 함. 금요일: 아침 10시 배포. 오후 2시 고객 불만 접수. "결제가 안 돼요." 다시 파악 중... 이게 내 리얼 스토리다. 이게 나만의 이야기는 아니겠지만.기술 부채는 이자가 붙는다 경제학에 "복리"라는 개념이 있다. 돈은 시간이 지날수록 기하급수적으로 늘어난다. 기술 부채도 마찬가지다. 초기에 시간을 들여 제대로 작성하지 않은 코드는 나중에 수십 배의 시간을 빼앗는다. 내가 개발한 기능을 테스트하는 데 3시간이 소요된다는 건, 아마 그 아래 있는 레거시 코드가 너무 뒤엉켜서일 거다. 우리 팀에서 일반적인 버그 수정 시간:새로운 기능 버그: 1-2시간 레거시 버그: 4-8시간왜일까? 왜냐하면 레거시 코드의 버그는 단순히 그 부분만 고치는 게 아니기 때문이다. 그 부분이 어디와 연결되어 있는지 파악해야 한다. 그 파악 과정이 90%의 시간을 먹는다. 한 번은 이런 일도 있었다. 아주 간단한 버그였다. 특정 상황에서 결제 시간이 잘못 저장되는 거. 나는 "이거 5분이면 끝나겠네" 했다. 한 시간 반 후, 나는 발견했다. 이 버그는 실제로는 3개의 서로 다른 부분에서 발생하고 있었고, 각각 다른 개발자가 만든 거였으며, 심지어 일부는 데이터베이스 레벨의 문제였다. 최종적으로 3시간이 걸렸다. 그리고 수정한 지 2주 후, 또 다른 버그가 터졌다. 내가 수정한 부분이 다른 모듈과 충돌했기 때문이다. 다시 2시간. 이게 기술 부채의 복리 효과다. 처음엔 5분이 3시간이 되고, 3시간이 나중엔 5시간이 된다. 리팩토링의 꿈 나는 리팩토링을 꿈꾼다. 이 모든 레거시 코드를 들어내고 깨끗하게 다시 짜는 거. 정말 깔끔한 구조. 적절한 디자인 패턴. 완벽한 테스트 커버리지. 그걸 내 팀장에게 제안했던 게 6개월 전이다. "팀장님, 결제 모듈 리팩토링 프로젝트 시작하면 좋을 것 같아요. 지금 이대로면..." 팀장: "알지. 근데 요즘 릴리즈 스케줄이 타이트해서... 나중에 이야기하자." 3개월 후에 다시 제안했다. 팀장: "그러고 싶은데 마케팅에서 새 기능 요청이 들어와서... 지금은 어렵다." 지금은? 지금도 다를 게 없다. 지금도 "긴급"이다. 이게 대부분의 회사에서 일어나는 현실이다. 리팩토링의 중요성을 아무도 부인하지 않는다. 하지만 모두가 현재의 긴급한 일에 밀린다. 그리고 몇 년이 지나면, 레거시가 더 심해진다. 나는 왜 고칠 수 없을까 가장 정직한 대답은: "시간이 없어서" 하지만 더 정직한 대답은: "우선순위가 없어서" 내가 할 수 있는 리팩토링:결제 모듈 전면 개편: 3-4주 필요 사용자 관리 모듈 정리: 2주 필요 로깅 시스템 개선: 1주 필요 에러 핸들링 통일: 3-4일 필요할 수 있는 일들은 많다. 하지만 이 중 어느 것도 직접적인 매출과 연결되지 않는다. 그래서 우선순위가 낮다. 기획팀이 "새로운 결제 수단 추가해요"라고 하면, 그건 매출과 직접 연결된다. 고객이 더 많은 결제 수단을 원하면, 그걸 만들어야 한다. 기획팀의 입장도 이해한다. 하지만 개발자의 입장에서 보면, 그 "새로운 기능"은 이미 망가진 기초 위에 또 다른 벽돌을 얹는 거다.그래도 계속하는 이유 그럼 왜 나는 여전히 여기에 있을까? 더 나은 회사로 이직하면 되지 않을까? 이 질문은 매달 한 번씩 떠오른다. 특히 야근할 때. 특히 주말에 슬랙 알림이 울릴 때. 진짜 이유는 몇 가지다. 첫째, 익숙함 이 회사의 모든 레거시 코드를 내가 안다. 5년 전 누군가가 작성한 코드의 의도를 안다. 왜 이런 식으로 짰는지 알 수 있다 (물론 여전히 이해 불가능한 부분도 많지만). 새로운 회사가면 또 다른 레거시를 만나야 한다. 차라리 이미 알고 있는 악을 택하는 게 낫다. 둘째, 내가 할 수 있는 영향력 내가 여기를 떠나면, 이 레거시 코드는 누가 고칠까? 아마 후배들이 더 힘들어할 거다. 내가 지금 할 수 있는 작은 개선들, 새로운 코드에서는 레거시를 안 만드는 것, 후배들에게 좋은 예시를 보여주는 것... 이런 게 의미 있다고 생각한다. 셋째, 충성심? 아니, 관성 더 솔직하게 말하면, 이직은 너무 힘들다. 이력서 쓰고, 포트폴리오 정리하고, 면접 봐야 한다. 지금의 피로감 속에서는 그 모든 게 산처럼 보인다. 작은 개선들의 누적 하지만 포기하지는 않았다. 대신 접근 방식을 바꿨다. 큰 리팩토링 대신 작은 개선 일주일에 한두 시간, 뭔가 하나씩 정리한다. 기능을 추가할 때마다 그 주변의 코드를 조금씩 개선한다. 이런 식의 "보이 스카우트 룰" - 도착했을 때보다 떠날 때 더 깨끗하게 - 을 적용한다. 문서화 레거시 코드의 가장 큰 문제는 "왜 이렇게 만들었는가"를 모른다는 거다. 그래서 나는 중요한 로직에 대해 몇 줄의 설명을 남기기 시작했다. 코드 리뷰할 때도 후배들에게 "왜"를 설명하려고 한다. 테스트 추가 모든 레거시 코드를 리팩토링할 수는 없지만, 테스트는 추가할 수 있다. 테스트가 있으면, 나중에 누군가 이 코드를 수정할 때 훨씬 안전하다. 기술 부채 추적 우리 팀은 이제 매달 "기술 부채" 미팅을 한다. 여기서 가장 시급한 레거시 코드들을 정리하고, 가능하면 작은 것부터 개선한다. 한 번에 다 고칠 수는 없지만, 계속 움직이면 언젠가는... 깨달음과 수용 7년을 거쳐 내가 배운 것 중 가장 중요한 건, 완벽한 코드는 존재하지 않다는 거다. 아니, 더 정확하게는: "완벽한 코드는 존재하지 않지만, 나아지는 코드는 존재한다"는 거다. 레거시 코드는 악이 아니다. 그건 과거의 결정이 현재에 남긴 흔적이다. 그 개발자가 나쁜 사람이라서 그렇게 작성한 게 아니다. 그는 당시 조건 하에서 할 수 있는 최선을 했을 거다. 마감이 촉박했고, 이해도 낮았고, 테스트할 시간이 없었을 거다. 지금의 나도 마찬가지다. 내가 지금 작성하는 코드가, 10년 후에 누군가에겐 "왜 이렇게 만들었어?"라는 질문을 받을 거다. 그건 피할 수 없는 운명이다. 그래서 내