3장. 코드와 함께 춤을 (레거시 코드에 임하는 우리의 자세)

Posted by yunki kim on January 10, 2024

기술 부채를 상환하는 방법

  • 업무를 진행하면서 조금씩 리팩터링하고, 변경 사항은 작고 독립적인 커밋과 PR로 만들자.
  • 단기적으로 리팩터링은 기능 출시를 지연시키지만 장기적으로는 반대의 상황이 된다. 리팩터링과 기능 사이에 균형을 잘 잡아야 한다.
  • 대규모 리팩터링을 위해 팀에 의논할 때, 다음과 같은 방법이 좋다.
    1. 상황을 사실 그대로 설명한다
    2. 부채의 위험과 비용을 기술한다
    3. 해결책을 제안한다
    4. (부채를 그대로 두는 방법을 비롯해) 대안에 대해 논의한다.
    5. 트레이드오프를 따져본다.
      • 위 방법은 문서로 작성하되 개인적인 가치를 기준으로 삼아선 안된다. 필요한 비용과 장점에만 집중하자.

코드 변경으로 인한 고통을 조금이라도 줄이려면

  • 이미 존재하는 대규모 코드베이스를 변경하는 작업은 아주 힘든 일이다. 기존 코드, 기존 스타일, 패턴, 다른 개발자의 사고방식을 이해해야 하기 때문이다. 그럼에도 레거시 코드를 조금 편리하게 변경할 수 있는 방법이 있다.
  • 레거시 코드 변경 알고리즘을 활용하자
    • 마이클 C. 페더스(Michael C. Feathers)는 자신의 저서 ”레거시 코드 활용 전략“에서 기존 코드를 안전하게 수정할 수 있는 과정을 다음과 같이 소개했다

      # 소프트웨어 엔트로피는 늘어나기 마련이다

      • 코드가 지저분해지는 것을 소프트웨어 엔트로피(software entropy)라고 한다. 소프트웨어 엔트로피는 코딩 스타일과 버그 탐지 도구, 코드 리뷰, 지속적이 리펙터링을 통해 관리할 수 있다

      # 결포 피할 수 없는 기술 부채

      • 기술 부채(technical debt)는 소프트웨어 엔트로피를 가중시키는 주요 요인이다. 기술 부채는 기존 코드의 단점을 수정하면서 나중으로 미뤄둔 작업을 의미한다.
      • 모든 부채가 다 나쁜 것은 아니다. 마틴 파울러는 기술 부채를 다음과 같이 나누었다.
        • 의도한 선택 + 신중하지 못한 선택 = 설계할 시간이 없어요
          • 주로 팀이 출시일 압박을 받는 상황에서 만들어진다. ”일단 … 해보자“라는 것에서 시작되는 부채이다.
        • 의도한 선택 + 신중한 선택 = 일단 출시 후 결과를 보고 대처합시다
          • 보편적으로 발생하는 부채 유형이다. 코드의 단점과 출시 속도의 트레이드 오프를 고민하다 결정한 실용적 트레이드오프이다. 나중에 팀이 해결 가능하게 훈련된 부채라면 좋은 부채이다.
        • 의도치 않은 선택 + 신중하지 못한 선택 = 계층화가 도대체 뭔가요
          • 몰라서 모르는 것들 때문에 발생한다. 사전에 구현 계획을 작성해 피드백을 받고 코드 리뷰를 수행하는 방법으로 완화할 수 있다. 꾸준한 학습도 이 부채를 줄이는 데 도움이 된다.
        • 의도치 않은 선택 + 신중한 선택 = 뭘 실수했는지 이제 알겠네요
          • 성장 과정에서 자연스럽게 나타나는 결과이다. 상황을 뒤늦게 깨달을 수 도 있기 때문이다.
          • 문제 도메인에 대해 배우거나 소프트웨어 아키텍트가 성장하는 과정 중 발생하는 자연스러운 결과다. 프로젝트 회고 같은 절차를 활용해 의도치 않았던 부채를 찾아내고 부채의 해결 여부와 적절한 해결 시점을 논의하기도 한다.
      • 변경 지점을 확인한다
        • 코드를 읽고, 실험해 보고, 질문을 하면서 찾는다
      • 테스트할 지점을 확인한다
        • 수정하고자 하는 코드의 짐입점이다.
      • 의존성을 나눈다
        • 테스트하기 위해 필요한 객체와 메소드의 의존성을 의미한다. 즉, 테스트가 용이하지 않은 코드의 구조를 바꾼다는 의미다.
        • 의존성을 나누는 방법은 다음과 같다
          • 크고 복잡한 메소드는 더 작은 크기의 메소드로 나눠서 각기 분리된 기능이 독립적으로 테스트될 수 있게 한다.
          • 인터페이스를 이용해서, 복잡한 객체를 완전하지는 않아도 테스트하기에는 충분한 단순 구현체로 대체할 수 있는 방법을 마련한다.
          • 시간의 흐름같이 제어하기 어려운 실행 환경을 시뮬레이션할 수 있는 명시적 제어 지점을 주입한다.
      • 테스트를 작성한다
      • 변경을 적용하고 리펙터링한다.
  • 코드는 처음보다 더 깔끔하게 유지하자
    • “캠핑장을 떠날 때는 도착했을 때보다 깨끗하게 정리하라.”라는 보이스카우트 원칙을 코드 베이스에도 적용하자. 버그를 수정하거나 새로운 기능을 추가할 때 주변 코드를 정리하자. 코드를 정리하는 커밋은 동작을 변경하는 커밋과 구분하자. 그래야 변경된 코드를 되돌리기 쉽고 리뷰하기 편하다.
  • 점진적으로 변경하자
    • 리팩터링은 두 가지 유형으로 나뉜다
      • 수십 개의 파일을 한 번에 수정하는 변경
      • 리팩터링과 새로운 기능이 뒤섞인 PR
    • 위 유형들에서 리팩터링과 기능 변경을 하나의 커밋에 넣어 놓으면 롤백 하기 어렵다. 따라서 리팩터링 커밋은 작게 유지해야 한다.
  • 리팩터링은 실용적으로 진행하자
    • 리팩터링이 항상 현명한 결정은 아니다. 리팩터링으로 얻을 수 있는 가치보다 리팩터링에 드는 비용이 더 클 수도 있다. 오래되고 deprecated된 코드, 위험도가 낮거나 거의 호출되지 않는 코드는 리팩터링이 필요 없다. 항상 리팩터링 전에 실용성에 무게를 두고 고려하자.
  • IDE를 활용하자
    • IDE에서 제공하는 여러 수정 기능들을 리펙터링에 활용하자. IDE 기능을 활용해서 리팩터링 한다면 간단한 수정으로 많은 코드를 변경할 수 있다. 따라서 수정된 부분을 반드시 사람이 리뷰해야 한다.
    • 리플렉션이나 메타 프로그래밍 형태로 참조하는 메소드 이름은 IDE 자동 수정 기능으로 수정되지 않을 수도 있다

버전 제어 시스템의 권장 기법을 활용하자

  • 개발을 할 때는 변경사항을 일찍, 자주 커밋 해야 한다. 그래야 코드가 변하는 과정 파악, 코드 롤백, 원격 백업으로 활용할 수 있다. 만약 자신이 한 커밋에 의미 없는 메시지를 적었다면, 리뷰를 요청하기 전에 브랜치를 rebase 하고나 커밋을 squash해서 커밋 메시지를 명료하게 정리하자.
  • 커밋을 스쿼시 할 때는 팀의 규칙에 따라 커밋 메시지를 작성해야 한다.
    • 팀에 큐칙이 없다면 크리스 빔(chris beam)가 조언한 ‘깃 커밋 메시지를 작성하는 7가지 요령’ (https://chris.beams.io/posts/git-commit)을 따르는 것도 좋다.
      • 제목과 본문 사이에 빈 줄을 한 행 삽입한다.
      • 제목은 50자 이내로 제한한다.
      • 제목은 대문자로 쓴다
      • 제목 끝에 마침표를 붙이지 않는다
      • 제목은 명령형 문장으로 작성한다
      • 본문의 각 행은 72자를 넘지 않게 한다.
      • 본문에는 코드가 ‘어떻게’ 바뀌었는지 보다는 ‘무슨’ 코드가 ‘왜’ 바뀌었는지를 설명한다.

소프트웨어 개발에서 빠지기 위순 함정을 최대한 피하려면

  • 코드를 처음부터 다시 작성하거나 표준을 무시하는 행동은 코드 베이스를 불안정하게 만들 수 있고, 코드를 다시 작성하는 동안 새로운 기능을 추가하지 못할 수도 있다.
  • 코드를 새로 작성하거나 표준 외의 방법을 도입하는 것은 비용이 너무 높으므로 기존 코드보다 고부가가치를 낼 수 있는 환경에서만 코드를 재작성하자. 코드를 fork 하는 상황도 피하자
  • 되도록 검증된 기술을 사용하자
    • 성공적인 기업이 오래된 라이브러리와 오래된 패턴으로 견고한 코드를 유지할 수 있는 데는 이유가 있다. 성공하는 데는 시간이 걸리고, 기술을 바꾸는 것은 집중을 방해하는 요소이다.
    • 새로운 기술은 커뮤니티 규모가 작고, 안전성이 낮고, 문서화도 미흡하고, 호환성도 낮다. 반면 일반적인 기술이 발생시키는 문제는 예측이 가능하다.
    • 새로운 기술을 도입할 때는 장점이 도입 비용보다 커야 한다.
      • 새로운 기술을 도입할 때는 가능하면 광범위한 사용 사례에 적용 가능하고 여러 팀이 도입할 수 있는 기술이 좋다.
      • 새로운 프로그래밍 언어 도입은 다른 새로운 기술 도입보다 더 큰 비용이 든다. 언어 생태계 성숙도, IDE 지원 수준, 빌드와 패키징 시스템, 인재 풀 등 고려해야 할 것들이 상당히 많다. 언어가 사장되지 않는 한, 단지 오래됐다거나 관심이 줄었다는 사실이 그 언어를 사용하지 않을 이유가 되지 못한다.
  • 제발 악동은 되지 말자
    • 마음에 들지 않는다는 이유로 회사나 업계의 표준을 무시해서는 안 된다. 단기적으로는 다른 사람이 하는 것을 따르되, 그 방법이 표준으로 채택된 이유를 이해하자. 내가 이해하지 못하는 문제를 해결하기 위한 방안일 수도 있다. 이유를 모르겠다면 주변 사람에게 물어보자.
    • 표준을 바꿀 때는 우선순위, 소유권, 비용, 구현 세부내역 등 다각도로 고려해야 한다.
  • 업스트림 커밋 없이 포크만 하는 것은 금물이다.
    • 포크는 다른 소스 코드 레파지토리에 대한 완전하고 독립적인 복사본이다. 자체적인 트렁크(trunk), 브랜치, 태그 등을 갖는다. 깃허브 같은 코드 공유 플랫폼에서는 업스트림 레파지토리에 PR을 보내기 전에 해당 레파지토리를 포 크한다. 포크를 해두면 해당 프로젝트의 주 레파지토리에 쓰기 권한이 없는 사람도 코드에 기여할 수 있다.
    • 하지만 코드에 기여할 생각이 없으면서 해당 레파지토리를 포크 하는 것은 위험한 일이다. 개발자들은 업스트림 레파지토리에 코드를 기여하지 않고, 작은 변경들이 쌓여서 결국 완전히 다른 소프트웨어를 운영하게 된다. 결국 업스트림 레파지토리에 기능을 추가하거나 버그를 수정하는 것이 매우 어려워진다.
  • 코드 재작성에 대한 욕구를 견디자
    • 코드를 새로 작성하는 것은 최후의 수단이다. 새 코드를 작성하는 것은 여간 쉬운 일이 아니기 때문이다. 코드를 재작성 하는 것은 그에 들어가는 비용보다 장점이 클 때만 시도해야 한다. 위험이 크고 소요 비용도 높기 때문이다.

개발자 필수 체크 리스트

  • 이것만은 지키자
    • 점진적 리팩터링하자
    • 리팩터링 커밋과 기능 관련 커밋은 분리하자
    • 변경사항을 작게 유지하자
    • 처음 상태보다 코드를 더 깔끔하게 유지하자
    • 평범한 기술을 사용하자
  • 이것만은 피하자
    • ‘기술 부채’라는 단어를 남용하지 말자
    • 테스트를 목적으로 메서드나 변수를 외부에 공개해서는 안 된다
    • 특정 언어에 연연하지 말자
    • 회사의 표준과 도구를 무시해서는 안 된다
    • 업스트림 커밋 없이 코드베이스를 포크해서는 안 된다.

출처 - 필독! 개발자 온보딩 가이드