CS Repository/리팩터링

[리팩터링] 리팩터링 원칙

조금씩 차근차근 2025. 8. 22. 14:42

요약

  • 리팩터링은 소프트웨어의 겉보기 동작은 그대로 유지한 채, 코드를 이해하고 수정하기 쉽도록 내부 구조를 변경하는 기법이다.
  • 리팩터링은 코드의 이해도를 올리고, 생산성을 올린다.
  • 리팩터링 작업은 코드의 이해도를 올리니, 읽기만 하기 보단 리팩터링을 시도해보며 읽는다.
    • 쉽다면 직접 리팩터링을 적용해본다.
  • 기능 추가 작업과 리팩터링 작업을 분리해라.
    • 지금 하고 있는게 리팩터링 작업인지 기능 추가 작업인지 명확히 하라.
  • 리팩터링은 경제적인 이유로 해라.
    • 리팩터링할 수 있어야 코드를 이해했다고 할 수 있고,
    • 코드를 이해해야 새로운 기능을 추가할 수 있다.
  • 일단은 성능을 신경쓰지 말고 코드를 작성해라.
    • 성능은 이후 성능 테스트/프로파일링을 통해 해결해라.

리팩터링의 정의

  • 명사 관점
    • 소프트웨어의 겉보기 동작은 그대로 유지한 채, 코드를 이해하고 수정하기 쉽도록 내부 구조를 변경하는 기법
  • 동사 관점
    • 소프트웨어의 겉보기 동작은 그대로 유지한 채, 여러 가지 리팩터링 기법을 적용해서 소프트웨어를 재구성한다.리팩터링이라는 용어의 남용
  • 코드를 정리하는 작업이 전부 리팩터링이 아니다.
  • 특정한 방식에 따라 코드를 정리하는 것만이 리팩터링이다.
    • 기존의 동작을 그대로 유지해야 한다.
    • 리팩터링 과정에서 발견된 버그는 리팩터링 후에도 그대로 남아 있어야 한다.
  • 누군가 "리팩터링하다가 코드가 깨져서 며칠이나 고생했다"고 한다면, 십중팔구 리팩터링한 것이 아니다.

리스트럭처링(재구성)

리스트럭처링은 리팩터링보다 더 큰 범주로, 리스트럭처링 안에는 이런 것들이 있다.

  • 성능 최적화
    • 코드의 성능을 향상시킨다.
    • 코드가 이해하기 쉬워질수도/어려워질수도 있다.
  • 리팩터링
    • 코드를 이해하고 수정하기 쉽게 만든다.
    • 프로그램 성능이 좋아질수도/나빠질수도 있다.

두 개의 모자

  • 소프트웨어를 개발할 때 목적을 명확히 해라.
  • 지금 현재 하고있는 것이?
    • '기능 추가'인가?
    • '리팩터링'인가?
  • 컴파일-테스트-커밋을 하면 모자를 벗을 수 있다.
  • 기능 추가 시
    • 기존 코드는 절대 건드리지 않고 새 기능 추가만 해라.
  • 리팩터링 시
    • 기능 추가는 절대 하지 말고, 코드 재구성에만 전념해라.
      • 앞 과정에서 놓친 테스트 케이스를 발견하지 않는 한 테스트도 새로 만들지 않는다.
      • 부득이 인터페이스를 변경해야 할 때만 기존 테스트를 수정한다.
  • 현재 내가 무엇을 하고 있는지를 명확히 인지해라.

리팩터링하는 이유

리팩터링을 왜 해야할까?

  • 리팩터링하면 소프트웨어 설계가 좋아진다.
  • 리팩터링하면 소프트웨어를 이해하기 쉬워진다.
  • 리팩터링하면 버그를 쉽게 찾을 수 있다.
  • 리팩터링하면 프로그래밍 속도를 높일 수 있다.

리팩터링하면 소프트웨어 설계가 좋아진다.

  • 리팩터링하지 않으면 소프트웨어의 내부 설계(아키텍처)가 썩기 쉽다.
  • 코드만 봐도 설계를 파악하기 쉽도록, 규칙적으로 리팩터링하라.
  • 코드가 길수록 실수 없이 수정하기 어려워진다.
  • 중복된 코드를 제거하고, 모든 코드가 언제나 고유한 일을 수행함을 보장해라.

리팩터링하면 소프트웨어를 이해하기 쉬워진다.

  • 내 소스 코드는 컴퓨터만 사용하는 게 아니다.
    • 코드를 컴파일하는데 시간이 살짝 더 걸린다고 누가 뭐라 하겠는가?
    • 하지만 다른 프로그래머가 내 코드를 제대로 이해했다면 한 시간에 끝낼 수정을 일주일이나 걸린다면 사정이 달라진다.
  • 프로그램을 동작시키는 데만 신경 쓰다 보면 나중에 그 코드를 다룰 개발자를 배려하지 못한다는 데 있다.
  • 일하는 리듬에 리팩터링을 넣어라.

리팩터링하면 버그를 쉽게 찾을 수 있다.

  • 코드를 이해하기 쉬우면 버그도 찾기 쉽다.
  • "난 뛰어난 프로그래머가 아니에요. 단지 뛰어난 습관을 지닌 괜찮은 프로그래머일 뿐이에요." - 켄트 백 -

리팩터링하면 프로그래밍 속도를 높일 수 있다.

  • 지금까지 제시한 장점을 한 마디로 정리하면 다음과 같다.
    • 리팩터링하면 코드 개발 속도를 높일 수 있다.
  • 좋은 설계는 새로운 기능을 추가할 지점과 수정 지점을 쉽게 찾을 수 있다.
    • 대부분의 코딩 작업은 이 종속성 파악과 원하는 지점 탐색에 소요된다.

언제 리팩터링해야 할까?

  • 보통 한시간 간격으로 수행하게 된다고 한다.
  • 3의 법칙 - 돈 로버츠 -
    • 처음에는 그냥 한다.
    • 비슷한 일을 두 번째로 하게 되면(중복이 생겼다는 사실에 당황스럽겠지만), 일단 계속 진행한다.
    • 비슷한 일을 세 번째 하게 되면 리팩터링한다.

논리적으로 리팩터링을 수행하게 되는 근거

논리적으로 리팩터링을 수행하게 되는 근거는 다음과 같은 것들이 있다.

  • 준비를 위한 리팩터링: 기능을 쉽게 추가하게 만들기
  • 이해를 위한 리팩터링: 코드를 이해하기 쉽게 만들기
  • 쓰레기 줍기 리팩터링
  • 계획된 리팩터링과 수시로 하는 리팩터링
  • 준비를 위한 리팩터링: 기능을 쉽게 추가하게 만들기
  • 리팩터링하기 가장 좋은 시점은 코드베이스에 기능을 새로 추가하기 직전이다.
    • 가령 내 요구사항을 거의 만족하지만 리터럴 값 몇개가 방해되는 함수가 있을 수 있다.
      • 그럼 리팩터링 모자를 쓰고 함수 매개변수화하기(11.2절)를 적용하자.
      • 그러고 나면 그 함수에 필요한 매개변수를 지정해서 호출하기만 하면 된다.
  • 버그를 잡을 때도 마찬가지이다.
    • 오류를 일으키는 코드가 세 곳에 복제되어 퍼져 있다면, 우선 한 곳으로 합치는 편이 작업하기에 훨씬 편하다.
    • 질의 코드에 섞여 있는 갱신 로직을 분리하면 두 작업이 꼬여서 생기는 오류를 크게 줄일 수 있다.

이해를 위한 리팩터링: 코드를 이해하기 쉽게 만들기

  • 코드를 수정하려면 먼저 그 코드가 하는 일을 파악해야 한다.
  • "리팩터링하면 머리로 이해한 것을 코드에 옮겨 담을 수 있다." -워드 커님행-
  • 현재 코드를 이해하기 위해 리팩터링을 수행하면, 코드가 머리로 완전히 이해되고, 이를 테스트로 검증가능하다.

쓰레기 줍기 리팩터링

  • 코드를 파악하던 중에 비효율적인 코드가 있다는 것을 발견했다고 치자.
    • 원래 하려던 작업과 관련 없는 일에 너무 많은 시간을 빼앗기긴 싫을 것이다.
    • 그렇다고 쓰레기가 나뒹굴게 방치해서 나중에 일을 방해하도록 두는 것도 좋지 않다.
    • 그럼 어떻게 해야 하는가?
  • 간단히 수정할 수 있는 것은 즉시 고치고, 시간이 좀 걸리는 일은 짧은 메모만 남긴 뒤, 하던 일을 끝내고 나서 처리한다.

계획된 리팩터링과 수시로 하는 리팩터링

  • 계획된 리팩터링보단, 수시로 하는 리팩터링이 기능 추가의 생산성을 올린다.
    • 계획된 리팩터링이 나쁘다는 것이 아니다.
      • 정기적으로 리팩터링하더라도 어떤 문제는 팀 전체가 달려들어야 할 정도로 곪아갈 수도 있다.
      • 하지만 기회될 때마다 수시로 진행해야 한다.
    • 리팩터링 자체가 기능 추가와 강하게 연관되어 있다는 뜻이다.
  • "보기 싫은 코드를 발견하면 리팩터링하자. 그런데 잘 작성된 코드 역시 수많은 리팩터링을 거쳐야 한다."
  • "무언가 수정하려 할 때는 먼저 수정하기 쉽게 정돈하고(단, 만만치 않을 수 있다) 그런 다음 쉽게 수정하자." -켄트 백-
  • VCS에서 리팩터링 기능과 기능 추가 커밋을 분리해야 한다는 조언을 들은 적이 있다.
    • 이렇게 할 때의 큰 장점은 두 가지 활동을 구분해서 별개로 검토하고 승인할 수 있다는 것이다.
    • 하지만, 리팩터링은 기능 추가와 밀접하게 엮인 경우가 많기 때문에 굳이 나누는 것은 시간 낭비일 수 있다.
    • 또한, 리팩터링을 하게 된 맥락 정보가 사라져서 왜 그렇게 수정했는지 이해하기 어려워진다.

오래 걸리는 리팩터링은?

  • 리팩터링은 대부분 몇 분 안에 끝난다.
    • 길어야 몇 시간 정도다.
  • 팀 전체가 달려들어도 몇 주는 걸리는 대규모 리팩터링은?
    • 라이브러리 교체
    • 일부 코드를 다른 팀과 공유하기 위해 컴포넌트 빼내기
    • 골치 아픈 의존성 정리
  • 이런 때에도 수시로 리팩터링하기.
    • 몇 주에 걸쳐 조금씩 해결해가자.
    • 추상화(인터페이스)로 갈아타며, 조금씩 의존성을 줄여나가자.

코드 리뷰에 리팩터링 활용하기

  • 리팩터링하면 어떻게 더 좋게 바꿀 수 있는지 생각해본다.
    • 쉽다면 직접 리팩터링해본다.
  • 이러면서 작성된 코드에 대한 이해도가 올라간다.
  • 코드 리뷰의 결과를 더 구체적으로 도출하는 데에도 도움된다.

관리자에게 리팩터링을 뭐라고 이야기해야 하는가?

  • 기술을 아는 관리자라면?
    • 설득하기 쉽다.
  • 기술을 모르는 관리자와 고객에게는?
    • 리팩터링한다고 말하지 말라.
    • 리팩터링은 생산성을 올려주는 도구다.
    • 그러니 리팩터링부터 해라.

리팩터링하지 말아야 할 때

  • 지저분한 코드를 발견해도, 굳이 수정할 필요가 없을 때
    • 외부 API를 다루듯 호출해서 쓰는 코드
  • 새로 작성하는 게 쉬울 때
    • 이런 결정은 내리기 쉽지 않다.
    • 뛰어난 판단력과 경험이 뒷받침되어야 한다.

리팩터링 시 고려할 문제

  • 새 기능 개발 속도 저하 문제
  • 코드 소유권 문제
  • 브랜치 전략
  • 자가 테스팅 반드시 진행하기
  • 레거시 코드 문제
  • 데이터베이스 리팩터링

새 기능 개발 속도 저하

  • 예컨대 (대대적인) 리팩터링이 필요해 보이지만, 추가하려는 새 기능이 아주 작아서 기능 추가부터 하고 싶은 상황을 생각해보자.
  • 이럴때는 간단하다면 새 기능부터 해라.
  • 리팩터링은 경제적인 이유로 하는 것이다.
    • 리팩터링할 수 있어야 코드를 이해했다고 할 수 있고,
    • 코드를 이해해야 새로운 기능을 추가할 수 있다.

코드 소유권

  • 함수 선언 바꾸기(6.5절)를 쓰고 싶은데, 그 코드의 소유권이 우리 팀이 아니라면?
  • 인터페이스를 중간 계층에 둬서 쓰는 수밖에 없다.
  • 되도록이면 이런 엄격한 코드 소유권 방식은 지양하는게 좋지만, 상황따라 다르니 이해해야 한다.
  • 아니면 오픈소스처럼 누구나 fork&PR이 가능하도록 개발 모델을 설정하는 것도 좋다.

브랜치

  • 지속적 통합(CI)를 수행해라.
    • 리팩터링을 하다보면 코드베이스 전반에 걸쳐 자잘하게 수정하는 부분이 많아진다.
    • 기능 별 브랜치 방식은 리팩터링을 도저히 진행할 수 없을 정도로 심각한 머지 문제가 발생하기 쉽다.
    • CI는 이 문제를 해결한다.
  • 믿지 못하는 프로그래머로부터 이따금 커밋이 들어오는 오픈 소스 프로젝트라면 기능 별 브랜치도 좋다.
  • 하지만 풀 타임 개발팀이라면, 리팩터링을 위한 CI를 적극적으로 도입해라.

테스팅

  • 리팩터링을 위해서는 자가 테스트 코드를 마련해야 한다.
  • 스스로에 대한 확신이라는 주관적 "감정"이 아닌, 객관적 "증거"가 리팩토링 및 개발 속도를 향상시킨다.

레거시 코드

  • 레거시 시스템을 파악할 때는 리팩터링이 굉장히 도움된다.
  • 하지만 대부분의 레거시 코드는 테스트 코드가 없는 경우가 많다.
    • 이 문제에 대한 정답은 당연히 테스트 코드 보강이지만, 쉽지 않다.
      • 보통은 테스트를 염두에 두고 설계한 시스템만 쉽게 테스트할 수 있다.
    • 프로그램에서 테스트를 추가할 틈새를 찾아서 테스트해야 한다.
      • 이러한 틈새를 만들 때 리팩토링이 활용된다.
      • 테스트 없이 진행하기 때문에 상당히 위험하지만 문제를 해결하기 위해서라면 감내해야 할 위험이다.
      • 이래서 처음부터 자가 테스트 코드를 작성해야 한다는 것이다.

데이터베이스 리팩터링

  • 데이터베이스 리팩터링은 프로덕션 환경에 여러 단계로 나눠서 릴리즈하는 것이 대체로 좋다.
  • 이렇게 해야 프로덕션 환경에서 문제가 생겼을 때 변경을 되돌리기 쉽다.

리팩터링, 아키텍처, 애그니(YAGNI)

  • 리팩터링이 소프트웨어 아키텍처에 끼친 영향
    • 리팩터링은 요구사항 변화에 자연스럽게 대응하도록 코드베이스를 잘 설계해준다.
    • 어느 부분에 유연성이 필요하고 어떻게 해야 그 변화에 가장 잘 대응할 수 있을지 추측하지 않고, 그저 현재까지 파악한 요구사항만을 해결하는 소프트웨어를 구축한다.
      • 단, 이 요구를 멋지게 해결하도록 설계한다.
    • 예상을 통해 요구사항에 대응하는 "유연성 매커니즘"은 복잡도를 올릴 수 있으니 반드시 검증 후에 추가한다.
  • 이런 식의 설계를 YAGNI라 한다.
    • YAGNI는 아키텍처를 고려하지 말라는 뜻이 아니다.
    • 리팩터링을 통해 아키텍처를 구성하라는 뜻이다.

리팩터링과 소프트웨어 개발 프로세스

  • TDD: 자가 테스트 코드 + 리팩터링
  • XP: CI + 자가 테스트 + 리팩터링

리팩터링과 성능

  • 리팩터링하면 성능이 느려질 수도 있다.
    • 하지만 그와 동시에 성능을 튜닝하기는 더 쉬워진다.

"빠른 소프트웨어"를 작성하는 방법

빠른 소프트웨어를 작성한다고 하면, 일반적으로 다음 세가지를 고려할 것이다.

  • 먼저 성능을 튜닝하기 쉽게 만들고 나서, 원하는 속도가 나게끔 튜닝하기
  • 끊임없이 관심을 기울이기
  • 일단 성능에 신경 쓰지 않고 코드를 다루기 쉽게 만드는데 집중하고, 그다음 테스트를 통해 튜닝하기

먼저 성능을 튜닝하기 쉽게 만들고 나서, 원하는 속도가 나게끔 튜닝하기

  • 예시: Hard Real-Time
    • 엄격한 성능이 제한되어있는 프로그램
      • 심장 박동 조율기
      • 라이다 센서를 이용한 자율주행 프로그램
      • 우주선 발사 시스템 등
      • 생명과 직결된 프로그램
    • 해결 방법 - 시간 예산 분배
      • 가장 엄격한 방법
      • 하드 리얼타임 시스템에서 많이 사용한다.
      • 시간을 리소스로 보고 해당 리소스를 주고받는 메커니즘을 제공한다.

끊임없이 관심을 기울이기

  • 직관적으로 흔히 사용하는 방식이지만 실제 효과는 변변찮다.
  • 성능을 개선하기 위해 코드를 수정하다 보면 프로그램은 다루기 어려운 형태로 변하기 쉽고, 결국 개발이 더뎌진다.

일단 성능에 신경 쓰지 않고 코드를 다루기 쉽게 만드는데 집중하고, 그다음 테스트를 통해 튜닝하기

  • 결국 대부분 프로그램은 전체 코드 중 "극히 일부"에서 대부분의 시간을 소비한다.
    • 따라서 "코드 전체를 고르게 최적화"한다면 그 중 90%는 효과가 거의 없기 때문에, 시간 낭비다.
  • 일단 성능을 신경쓰지 않고 코드를 작성해라.
  • 프로파일링 도구를 통해 병목 지점을 파악하고, 성능을 튜닝한다.

리팩터링 자동화

  • IDE에서 지원하는 리팩터링 도구를 적극 활용해라.

본 글은 마틴 파울러의 리팩터링을 참고하여 작성되었습니다.

 

리팩터링 2판 - 예스24

개발자가 선택한 프로그램 가치를 높이는 최고의 코드 관리 기술마틴 파울러의 『리팩터링』이 새롭게 돌아왔다.지난 20년간 전 세계 프로그래머에게 리팩터링의 교본이었던 『리팩토링』은,

www.yes24.com