2.1 리팩터링 정의

  1. 리팩터링의 사전적 정의

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

  1. 리팩터링 이란

    • 동작을 보존하는 작은 단계들을 거쳐 코드를 수정하고, 이러한 단계들을 순차적으로 연결하여 큰 변화를 만들어내는 일
    • “재구성” 의 특수한 한 형태
    • 단계를 작게 나눔으로써 구성을 체계화할 수 있고, 디버깅 시간을 단축할 수 있음
    • 사용자 관점에서는 달라지는 점이 없음
    • 리팩터링 과정에서 발견된 버그는 리팩터링 후에도 그대로 남아 있어야 함
    • 성능 최적화와 비슷
    • 코드를 이해하고 수정하기 쉽게 만드는 것

2.2 두 개의 모자

  1. 켄트 백은 소프트웨어 개발의 목적을 기능 추가 또는 리팩터링 으로 나누고, 이를 두 개의 모자라고 명명한다.
  2. 기능 추가 시에는 기존 코드는 절대 건드리지 않고 새 기능을 추가하기만 한다.
  3. 반면 리팩터링 시에는 기능 추가는 절대 하지 말아야 한다. (테스트도 새로 만들지 않는다)

2.3 리팩터링하는 이유

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

    • 리팩터링하지 않으면 코드 구조가 무너지고, 설계를 유지하기 어려워지고, 더욱 빨리 부패함
    • 중복 코드를 제거하여 코드량을 줄인다고 시스템이 빨라지는 것은 않지만, 수정하는 데 드는 노력과 이해해야할 코드량이 크게 줄어듬
  2. 리팩터링하면 소프트웨어를 이해하기 쉬워짐

    • 다른 사람이 내 코드를 이해할 수 있도록 작성해야 함
    • 리팩터링은 코드가 더 잘 읽히게 도와줌
    • 코드의 목적이 더 잘 드러나게, 내 의도를 더 명확하게 전달하도록 개선할 수 있음
    • 추후에 나 자신이 코드를 다시 봤을 때 이해하기도 쉬워 짐
  3. 리팩터링하면 프로그래밍 속도를 높일 수 있음

    • 내부 설계가 잘 된 소프트웨어는 새로운 기능을 추가할 지점과 어떻게 고칠지를 쉽게 찾을 수 있음
    • 모듈화가 잘 되어 있으면 전체 코드베이스 중 작은 일부만 이해하면 됨
    • 저자는 이를 ‘지구력 가설’ 이라고 부름
    • 내부 설계에 심혈을 기울이면 소프트웨어의 지구력이 높아져서 빠르게 개발할 수 있는 상태를 더 오래 지속할 수 있음

2.4 리팩터링 해야 하는 시기

  1. 3의 법칙

    • 처음에 그냥 진행
    • 비슷한 일을 두 번째로 하게 되면, 일단 계속 진행
    • 비슷한 일을 세 번째 하게 되면 리팩터링 진행
  2. 준비를 위한 리팩터링(기능을 쉽게 추가하게 만들기)

    • 리팩터링하기 가장 좋은 시점은 코드베이스에 기능을 새로 추가하기 직전
    • 이 시점에 현재 코드를 살펴보면서, 구조를 살짝 바꾸면 다른 작업을 하기가 훨씬 쉬워질 만한 부분을 찾음
    • 버그를 잡을 때도 마찬가지
    • 상황을 개선해놓으면 버그가 수정된 상태가 오래 지속될 가능성을 높이는 동시에, 같은 곳에서 다른 버그가 발생할 가능성을 줄여 줌
  3. 이해를 위한 리팩터링(코드를 이해하기 쉽게 만들기)

    • 코드를 수정하려면 먼저 그 코드가 하는 일을 파악해야 함
    • 그 코드의 의도가 더 명확하게 드러나도록 리팩터링할 여지는 없는지 찾아봐야 함
  4. 쓰레기 줍기 리팩터링

    • 간단히 수정할 수 있는 것은 즉시 고치고, 시간이 좀 걸리는 일은 짧은 메모만 남긴 다음, 하던 일을 끝내고 나서 처리 함
  5. 코드 리뷰에 리팩터링 활용

    • 코드 리뷰는 개발팀 전체에 지식을 전파하는 데 좋으며, 다른 사람의 아이디어를 얻을 수 있음
    • 리팩터링은 다른 이의 코드를 리뷰하는 데도 도움이 됨
    • 리팩터링은 코드 리뷰의 결과를 더 구체적으로 도출하는 데에도 도움
    • 내가 떠올린 아이디어를 실제로 적용했을 때의 모습을 더 명확하게 볼 수 있음 cf) 짝 프로그래밍
  6. 관리자에게는 뭐라고 말해야 할까?

    • “리팩터링한다고 말하지 말라”. 구체적인 방법은 개발자가 판단해야 함
    • 프로 개발자에게 주어진 임무는 새로운 기능을 빠르게 구현하는 것이고, 가장 빠른 방법은 리팩터링 임
  7. 리팩터링하지 말아야 할 때

    • 지저분한 코드를 발견해도 굳이 수정할 필요가 없다면 리팩터링하지 않음
    • 내부 동작을 이해해야 할 시점에 리팩터링해야 효과를 제대로 볼 수 있음
    • 리팩터링하는 것보다 처음부터 새로 작성하는 게 쉬울 때도 리팩터링하지 않음
    • 이 결정에는 뛰어난 판단력과 경험이 뒷받침되어야 함

2.5 리팩터링 시 고려할 문제

  1. 새 기능 개발 속도 저하

    리팩터링의 궁극적인 목적은 개발 속도를 높여서, 더 적은 노력으로 더 많은 가치를 창출하는 것

    • 균형점을 잡아야 함
    • 준비를 위한 리팩터링이라면 주저하지 않고 시작함
    • 반면 내가 직접 건드릴 일이 거의 없거나, 불편한 정도가 그리 심하지 않다고 판단되면 리팩터링하지 않음
    • 코드베이스를 건강하게 만드는 것에 대해 팀원들에게 공감대를 형성해야 함
    • 그러나 리팩터링의 본질은 코드베이스를 예쁘게 꾸미는 것이 아니며, 오로지 경제적인 이유
    • 리팩터링은 개발 기간을 단축하고자 하는 것
  2. 코드 소유권

    • 리팩터링하다 보면 모듈의 내부뿐 아니라 시스템의 다른 부분과 연동하는 방식에도 영향을 주는 경우가 많음
    • 코드 소유권이 나뉘어 있으면 리팩터링에 방해가 됨
    • 하지만 제약이 따르더라도 리팩터링을 해야 함
    • 저자가 선호하는 방식은 코드의 소유권을 팀에 두는 것
    • 그래서 팀원이라면 누구나 팀이 소유한 코드를 수정할 수 있게 해야 함
  3. 브랜치

    • 브랜치-마스터를 사용하는 방식은 독립 브랜치로 작업하는 기간이 길어질수록 작업 결과를 마스터로 통합하기가 어려워진다는 단점이 있음
    • 기능별 브랜치의 통합 주기는 짧게 관리해야 함
    • 이 방식을 지속적 통합(CI; Continuous Integration), 또는 트렁크 기반 개발(TBD; Trunk-Based Development)이라 함
    • CI는 코드 전반에 거쳐 자잘하게 수정해야 하는 경우가 많을 때 도움이 됨
  4. 테스팅

    • 리팩터링을 하면서 프로그램의 겉보기 동작은 똑같이 유지해야 함
    • 실수하여 동작이 깨지더라도 오류를 재빨리 해결할 수 있어야 함
    • 리팩터링하기 위해서는 자가 테스트 코드를 마련해야 함
    • 자가 테스트 코드는 리팩터링을 할 수 있게 해줄 뿐만 아니라, 새 기능 추가도 훨씬 안전하게 진행할 수 있도록 도와줌
    • 또한 리팩터링 과정에서 버그가 생길 위험이 아주 크다는 불안감을 해소할 수 있음
    • 자가 테스트 코드는 통합 과정에서 발생하는 의미 충돌을 잡는 메커니즘으로 활용할 수 있어서 자연스럽게 CI와도 밀접하게 연관됨
  5. 레거시 코드

    • 레거시 시스템을 파악할 때 리팩터링이 굉장히 도움됨
    • 대규모 레거시 시스템에는 테스트를 보강해야 함
    • 프로그램에서 테스트를 추가할 틈새를 찾아서 시스템을 테스트해야 함
    • 이러한 틈새를 만들 때 리팩터링이 활용됨
    • 서로 관련된 부분끼리 나눠서 하나씩 공략하는 것이 좋음
    • 코드의 한 부분을 훑고 넘어갈 때마다 예전보다 조금이라도 개선하려고 노력하며, 레거시 시스템의 규모가 크다면 자주 보는 부분을 더 많이 리팩터링 해 함
  6. 데이터베이스

    • 데이터베이스의 변경도 다른 리팩터링과 마찬가지로 전체 변경 과정을 작고 독립된 단계들로 쪼개는 것이 핵심
    • 그러나 데이터베이스 리팩터링은 프로덕션 환경에 여러 단계로 나눠서 릴리스하는 것이 대체로 좋다는 점에서 다른 리팩터링과 다름
    • 이렇게 하면 프로덕션 환경에서 문제가 생겼을 때 변경을 되돌리기 쉬움

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

  • 리팩터링은 소프트웨어 아키텍처를 바라보는 관점을 완전히 바꿔놓음
  • 리팩터링은 요구사항 변화에 자연스럽게 대응하도록 코드베이스를 잘 설계해 줌
  • 향후 변경에 유연하게 대처할 수 있는 유연성 메커니즘을 소프트웨어에 심어둘 수 있음
  • 함수 정의 시 다양한 예상 시나리오에 대응하기 위한 매개변수들을 추가하는 것이 그 예
  • 유연성 메커니즘을 구현하는 데 추가적으로 치러야 할 비용들이 있지만, 리팩터링을 활용하면 다르게 접근할 수 있음
  • 미래에 필요해질 유연성과 그 변화에 가장 잘 대응할 수 있는 추측 대신, 그저 현재까지 파악한 요구사항만을 해결하는 소프트웨어를 구축하고 진행하면서 아키텍처도 그에 맞게 리팩터링해서 바꿈
  • 이런 식의 설계 방식을 ‘간결한 설계’, ‘점진적 설계’, ‘YAGNI(you aren’t going to need it’) 등으로 부름ㅁ
  • 나중에 문제를 더 깊이 이해하게 됐을 때 처리하는 쪽이 훨씬 나을 수도 있음

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

  • 리팩터링과 함께 퍼지기 시작한 XP(익스트림 프로그래밍)의 특징은 지속적 통합, 자가 테스트 코드 리팩터링 등의 개성이 강하면서 상호 의존하는 기법들을 하나로 묶은 프로세스
  • XP는 수 년에 걸쳐 애자일의 부흥을 이끌음
  • 애자일을 제대로 적용하기 위해서는 리팩터링이 필수
  • 자가 테스트 코드, 지속적 통합, 리팩터링의 세 기법은 서로 강력한 상승효과를 발휘함
  • 지속적 배포는 소프트웨어를 언제든 릴리스할 수 있는 상태로 유지해줌
  • 게다가 위험요소도 줄이고, 비즈니스 요구에 맞춰 릴리스 일정을 계획할 수 있음
  • 이처럼 견고한 기술적 토대를 바탕으로 프로덕션 코드 반영까지의 시간을 단축하고 버그를 줄여줘 소프트웨어의 신뢰성도 높일 수 있음

2.8 리팩터링과 성능

  • 리팩터링하면 소프트웨어가 느려질 수도 있는 건 사실이지만, 그와 동시에 성능을 튜닝하기는 더 쉬워짐
  • 소프트웨어를 빠르게 만드는 비결은, 먼저 튜닝하기 쉽게 만들고 나서 원하는 속도가 나게끔 튜닝하는 것

빠른 소프트웨어를 작성하는 방법 세 가지

  1. 시간 예산 분배 방식

    • 설계를 여러 컴포넌트로 나눠서 컴포넌트마다 자원(시간과 공간) 예산을 할당
    • 컴포넌트는 할당된 자원 예산을 초과할 수 없음
    • 시간 예산 분배 방식은 멍격한 시간 엄수를 강조 함
  2. 끊임없이 관심을 기울이는 것

    • 대부분 프로그램은 전체 코드 중 극히 일부에서 대부분의 시간을 소비 함
  3. 의도적으로 성능 최적화에 돌입하기 전까지는 성능에 신경 쓰지 않고 코드를 다루기 쉽게 만드는 데 집중함

    • 먼저 프로그램을 분석하여 시간과 공간을 많이 잡아먹는 지점을 알아내고, 그 부분들을 개선
    • 최적화를 위한 수정도 작은 단계로 나눠서 진행하며, 사용자가 만족하는 성능에 도달할 때까지 최적화 대상을 찾아서 제거하는 일을 계속 진행

프로그램을 잘 리팩터링해 두면 성능에 투입할 시간을 벌 수 있으며, 리팩터링이 잘 되어 있는 프로그램은 성능을 더 세밀하게 분석할 수 있음 단기적으로 보면 리팩터링 단계에서는 성능이 느려질 수도 있지만, 최적화 단계에서 코드를 튜닝하기 훨씬 쉬워지기 때문에 결국 더 빠른 소프트웨어를 얻게 됨


참고