리팩터링 2판 2장 정리

Study리팩터링 2판스터디javascript

들어가며

본 게시글은 사내 스터디로 공부한 리팩터링 2판 (마틴 파울러) ‘2장’을 정리 & 요약하는 글입니다.


Chapter 02. 리팩터링 원칙


2.1 리팩터링 정의

리팩터링: [명사] 소프트웨어의 겉보기 동작은 그대로 유지한 채, 코드를 이해하고 수정하기 쉽도록 내부 구조를 변경하는 기법

리팩터링(하다): [동사] 소프트웨어의 겉보기 동작은 그대로 유지한 채, 여러 가지 리팩터링 기법을 적용해서 소프트웨어를 재구성하다.

  • 수많은 사람이 코드를 정리하는 작업을 모조리 ‘리팩터링’이라고 표현하고 있는데, 특정한 방식에 따라 코드를 정리하는 것만이 리팩터링이다.

  • 리팩터링은 결국 동작을 보존하는 작은 단계들을 거쳐 코드를 수정하고, 이러한 단계들을 순차적으로 연결하여 큰 변화를 만들어내는 일이다.


주의) 누군가 “리팩터링하다가 코드가 깨져서 며칠이나 고생했다”라고 한다면, 십중팔구 리팩터링한 것이 아니다. (= 재구성이다)

  • 리팩터링은 ‘성능 최적화’와 비슷하다. 단지 목적이 다를뿐
    • 리팩터링의 목적은 코드를 이해하고 수정하기 쉽게 만드는 것이다. 프로그램 성능은 좋아질 수도, 나빠질 수도 있다.
    • 반면, 성능 최적화는 오로지 속도 개선에만 신경을 쓴다

2.2 두 개의 모자

  • 소프트웨어를 개발할 때 목적이 ‘기능 추가’ 냐, 아니면 ‘리팩터링’이냐를 명확히 구분해서 작업해야한다.
    • 켄트 벡(저자)은 이를 ‘두 개의 모자’에 비유했음

👲🏻 기능 추가 모자

  • 기능을 추가할 때는 기존 코드는 절대 건드리지 않고 새 기능을 추가하기만 한다. 진척도는 테스트를 추가해서 통과하는 방식으로 측정한다.

👷🏻 리팩터링 모자

  • 기능 추가는 절대 하지 않기로 다짐한 뒤, 오로지 코드 재구성에만 전념한다.
  • 테스트도 새로 만들지 않는다. (놓친 테스트 케이스를 발견하지 않는 한)
  • 부득이 인터페이스를 변경해야 할 때만 기존 테스트틀 수정한다.

  • 필자는 개발하는 동안 두 모자를 자주 바꿔 씀
    • 새 기능을 추가하다 보면 코드 구조를 바꿔야 작업하기 훨씬 쉽겠다는 생각이 들기도 하는데, 그러면 잠시 모자를 바꿔 쓰고 리팩터링 함
    • 코드 구조가 어느 정도 개선되면 다시 모자를 바꿔 쓰고 기능 추가를 이어감
    • 추가한 기능이 제대로 작동하는지 까지 확인했다면 작성한 코드를 살펴본다.
    • 코드가 이해하기 어렵게 짜였다면 다시 모자를 바꿔쓰고 리팩터링

2.3 리팩터링하는 이유

리팩터링이 소프트웨어의 모든 문제점을 해결하는 만병통치약은 절대 아니다. 하지만 코드를 건강한 상태로 유지하는 데 도와주는 약임은 분명함.


  1. 리팩터링하면 소프트웨어 설계가 좋아진다.
  • 리팩터링을 하지 않으면 소프트웨어의 내부 설계(아키텍처)가 썩기 쉽다.ㅅ6
  • 아키텍처를 충분히 이해하지 못한 채 단기 목표만을 위해 코드를 수정하다 보면 기반 구조가 무너지기 쉽다.
  • 그렇게 됐을 때 코드만 봐서는 설계를 파악하고 유지하기가 어려워진다.
  • 규칙적인 리팩터링은 코드의 구조를 지탱해준다.

  1. 리팩터링하면 소프트웨어를 이해하기 쉬워진다.
  • 프로그래밍은 내가 원하는 바를 정확히 표현하는 일
  • 하지만, 프로그램을 동작시키는 데만 신경 쓰다 보면 나중에 그 코드를 다룰 개발자를 배려하지 못하게 됨
  • 따라서, 잘 작동하지만 이상적인 구조는 아닌 코드가 있다면, 코드의 목적이 더 잘 드러나고 내 의도를 더 명확하게 전달하도록 개선해야함
  • 단지 누군가를 배려하기 위해서만은 아님. 예전에 짜두었던 코드를 언제까지나 기억하고 있을 수는 없기에, 나를 위해서라도 기억할 필요가 있는 것들은 최대한 코드에 담아야 함.

  1. 리팩터링하면 버그를 쉽게 찾을 수 있다.
  • 코드를 이해하기 쉽다는 말은 버그를 찾기 쉽다는 말이기도 함
  • 프로그램의 구조를 명확하게 다듬으면 그냥 ‘이럴 것이다’라고 가정하던 점들이 분명히 드러나는데, 버그를 지나치려야 지나칠 수 없을 정도까지 명확해짐

  1. 리팩터링하면 프로그래밍 속도를 높일 수 있다.
  • 한 시스템을 오래 개발하면 초기에는 진척이 빨랐지만 현재는 새 기능 하나 추가하는 데 훨씬 오래 걸릴때가 많다. 새로운 기능 추가할수록 기존 코드베이스에 잘 녹여낼 방법 찾는데 시간이 늘어남.

해결방안: 지속적인 리팩터링을 통해 기존에 작성한 코드를 최대한 활용할 수 있게 만들기

  • 내부 설계가 잘 된 소프트웨어는 새로운 기능을 추가할 지점과 어떻게 고칠지를 쉽게 찾을 수 있음
  • 모듈화가 잘 되어 있으면 전체 코드베이스 중 작은 일부만 이해하면 됨
  • 코드가 명확하면 버그를 만들 가능성도 줄고, 버그를 만들더라도 디버깅하기가 훨씬 쉬움
  • 필자는 위와 같은 효과를 설계 지구력 가설이라고 표현함
    • 설계 지구력 가설 (Design Stamina Hypothesis) : 내부 설계에 심혈을 기울이면 소프트웨어의 지구력이 높아져서 빠르게 개발할 수 있는 상태를 더 오래 지속할 수 있다.
  • 처음부터 좋은 설계를 마련하기란 매우 어렵다. 그래서 빠른 개발이라는 숭고한 목표를 달성하려면 리팩터링이 반드시 필요하다.

2.4 언제 리팩터링해야 할까

리팩터링 3의 법칙

  • 처음에는 그냥 한다
  • 비슷한 일을 두 번째로 하게 되면 계속 진행
  • 세 번째 하게 되면 리팩터링

리팩터링 종류

  • 준비를 위한 리팩터링
    • 함수 매개변수화 하기
  • 이해를 위한 리팩터링
    • 변수를 적절한 이름으로 바꾸고 긴 함수를 잘게 쪼개기
  • 쓰레기 줍기 리팩터링
  • 계획된 리팩터링과 수시로 하는 리팩터링
    • 보기 싫은 코드를 발견하면 바로 리팩토링
    • 수정할 때 쉽게 정돈하고 난후에 수정
  • 오래걸리는 리팩터링
  • 코드 리뷰에 리팩터링 활용하기
    • 코드 리뷰에도 실제로 개선사항을 제시
    • 페어프로그래밍 하면서 실제로 개선

리팩터링하지 말아야 할때

  • 지저분한 코드가 있어도 굳이 수정할 필요가 없을 때
  • 외부 api 다루듯 호출해서 쓰는 경우
  • 새로 작성하는게 더 쉬울 때

2.5 리팩토링 시 고려할 문제

리팩토링은 코드 베이스를 예쁘게 하려는 게 아니라 오직 경제적인 이유로 하는 것이다.


  1. 새 기능 개발 속도 저하
  • 리팩토링의 궁극적인 목적은 개발 속도를 높이는 것이다.
  • 그래서 더 적은 노력으로 더 많은 가치를 창출하는 것
  • 하지만 그래도 상황에 맞게 조율해야 한다. → 완벽한 균형점은 알려주기 어렵다. 숙련이 필요
  • 리팩토링이 필요해 보이면 주저하지 않고 한다.
    • 어떻게 리팩토링 할지 생각이 안 나면 어떻게 하나?
      • 일단 냅두고 지켜보다가 좋은 생각이 나면 한다.
  • 리더는 리팩토링을 추구하는 문화를 이끌어야 한다.
  • 리팩토링의 본질은 꾸미는 것이 아니라 오직 경제적인 이유.

  1. 코드 소유권
  • 리팩터링 하다 보면 모듈의 내부 뿐만 아니라 시스템의 다른 부분과 연동하는 경우를 마주치는 일이 있다.

  • 이런 경우 리팩터링은 난감하다.

  • 왜냐하면 해당 코드의 소유권이 나에게 없기 때문이고, 그 함수가 언제 어떻게 얼마나 실행되는지 알기 어렵기 때문이다.

  • 이런 경우는 리팩토링을 어떻게 하는가?

    • 기존 코드는 건드릴 수 없다. 그러므로 새 함수를 만들어서 기존 함수를 감싸는 식으로 구현할 수 있다.
    • 이렇게 되면 인터페이스는 복잡해지지만 아무튼 할 수는 있다.
  • 따라서 저자는 코드 소유권을 조각조각 작은 단위로 나누어서 관리하는 것을 반대하는 입장이다.

    • 그럼 어떻게 관리해야 하나?
      • 코드의 소유권을 팀에 두고 팀원이라면 누구나 팀이 소유한 코드를 수정할 수 있게 한다.
      • 다른 팀의 소스 브랜치를 따서 수정하고 풀리퀘스트를 올리는 유연한 조직.

  1. 브랜치

“재앙을 피하려면 통합 주기를 짧게 가져가는 것이 좋다.”

  • 주로 기능 브랜치를 두고 프로덕션에 빌드할 때만 마스터 브랜치로 머지하는 방식을 많이 쓴다.
    • 장점:
      • 작업이 끝나지 않은 코드가 마스터에 섞이지 않는다.
      • 버전을 명확히 나눌 수 있다.
      • 문제가 생기면 되돌리기 편하다.
    • 단점:
      • 독립 브랜치로 작업하는 기간이 길어질 수록 마스터로 통합하기 어려워진다.
      • 그리고 그런 작업이 여러 기능 브랜치에서 동시에 개발이 진행될 때는 해결하기 더 어려워진다.
        • 누군가 개인 브랜치에서 작업한 내용을 마스터에 통합하기 전까지는 다른 사람이 그 내용을 볼 수 없다.
        • 통합한 이후에도 마스터에서 달라진 내용을 내 브랜치에 머지해야 하는데, 상당한 비용이 든다.
          • 다른 브랜치에서 함수를 호출하는 코드를 추가했는데, 내 브랜치에서는 그 함수의 이름을 변경했다면?
  • 위와 같은 단점 때문에 개인 브랜치로 작업하는 기간을 짧게 가져가야 한다고 저자는 말한다. (CI 지속적 통합)
    • 하지만 짧게 가져가는 것도 대가를 치러야 하는데
    • 마스터를 건강하게 유지해야 하고, 각 기능을 끌 수 있는 기능 토글을 적용하여 완료되지 않은 기능이 시스템 전체를 망치지 않도록 해야 한다.
  • 머지의 복잡도를 줄일 수 있어서 CI를 선호하지만, 가장 큰 이유는 리팩터링과 궁합이 아주 좋기 때문
    • 리팩터링은 함수 이름을 바꾸는 경우처럼 코드베이스 전반에 걸쳐 수정이 일어나는 경우가 많다.
    • 이런 경우 기능 브랜치에서 작업을 수행하면 머지 과정에서 의미 충돌이 발생하기가 쉽다.
  • 기능 브랜치를 사용하지 말라는 건 아님. 통합 주기를 짧게 가져가라는 것.

  1. 테스팅

리팩터링은 반드시 자가 테스트 코드를 작성하라.

  • 리팩터링 중에 실수가 발생하면?
    • 실수를 빨리 발견하면 됨
  • 핵심은 오류를 재빨리 잡는 것
    • 다양한 측면을 검사하도록 테스트 스위트가 필요
  • 자가 테스트 코드는 리팩터링을 할 수 있게 하는 것 뿐만 아니라 새 기능 추가도 훨씬 안전하게 진행하게 해줌
  • 테스트 코드 작성이 귀찮다면 뛰어난 자동 리팩터링 기능을 제공하는 환경이 도움이 됨

  1. 레거시 코드

“프로그램에서 테스트를 추가할 틈새를 찾아서 시스템을 테스트 해야 한다.”

  • 레거시 시스템을 파악할 때, 리팩토링이 많은 도움이 됨
  • 대규모 레거시 시스템을 테스트 코드 없이 명료하게 리팩터링하기는 매우 어려움
  • 정답은 테스트 코드를 보강하는 것
    • 안전한 자동 리팩터링 도구가 있다면 아주 좋음
  • 그래서 자가 테스트 코드를 처음부터 작성해야 한다고 저자는 강조함

참고




Profile picture
@김하연
4년차 프론트엔드 개발자 입니다. 사용자 경험 개선, 코드의 재사용성, 읽기 쉬운 코드에 집중하여 개발합니다.
AboutGithub LinkedinResume
Loading script...