본문으로 바로가기

[Refactoring] 2. 리팩터링 원칙

category Program/Refactoring 2022. 8. 18. 13:37

2.1. 리팩터링의 정의

리팩터링 이라는 정의를 구체적으로 정의하고 사용해야 유용함!

명사 : 리팩터링

  • 소프트웨어의 겉보기 동작은 그대로 유지한 채, 코드를 이해하고 수정하기 쉽도록 내부 구조를 변경하는 기법
  • 함수 추출하기,함수 인라인하기, 변수 추출하기, 조건부로직을 다형성으로 바꾸기 와 같이 이름 붙은 구체적인 리팩터링 기법들이 여기에 포함

동사 : 리팩터링(하다)

  • 소프트웨어의 겉보기 동작은 그대로 유지한 채, 여러 가지 리팩터링 기법을 적용해서 소프트웨어를 재구성 하다.
  • 코드베이스를 정리하거나 구조를 바꾸는 모든 작업을 재구성이라고 하고 리팩터링은 재구성 중 특수한 한 가지 작업 형태
  • 리팩터링은 동작을 보존하면서 작은 단계들을 거쳐 코드를 수정하고 이를 연결하여 큰 변화를 만들어냄
  • 리팩토링하는 동안은 코드가 항상 정상 동작해야하므로 언제든 멈출 수 있어야함!! > 테스트코드 중요..

지금껏 수많은 사람이 코드를 정리하는 작업을 모조리 ‘리팩터링’이라고 표현하고 있는데, 앞에서 제시한 정의를 따르면 특정한 방식에 따라 코드를 정리하는 것만이 리팩터링이다.

겉보기 동작 (observable behavior)

  • 사용자 관점에서 리팩토링 하기 전 후의 기가능느똑같이 동작해야 한다는 의미
  • 기존에 알려진 버그도 리팩터링 중에는 수정하지 않는다 (기능상동일)
    • 새로발견한 숨은 버그는 OK - 중요하건 기능이 동일하게 지되어야함)

리팩터링과 성능 최적화

  • 리팩터링은 성능 최적화와 비슷하지만 목적이 다름 (둘다 코드변경 + 기능 유지)
  • 리팩터링 : 코드를 이해하고 수정하기 쉽게 만드는 것. 성능은 좋아질 수도 나빠질 수도 있다.
  • 성능 최적화 : 속도 개선, 코드가 어려워질수도 있음.

2.2. 두개의 모자

  • 켄트 벡은 이를 `두개의 모자(two hats)`에 비유하며 상황에 맞게 바꿔 쓴 뒤 작업하라고 명시함
  • 둘을 동시에 병행하지 말라는 의미인듯..
  • 기능추가 모자
    • 기존 코드는 건드리지 않고 새 기능을 추가하기만 함
  • 리팩터링 모자
    • 기능 추가는 절대 하지 않고 코드 재구성에만 전념
    • 신규 테스트 케이스도 추가로 작성하지 않음
    • 인터페이스 수정시에만 기존 테스트 수정

2.3. 리팩터링 하는 이유

1. 소프트웨어 설계가 좋아진다.

  • 초기 내부 아키텍쳐 구조 유지
    • 단기 목표만을 위한 코드 수정을 하다 보면 완전한 전체 아키텍쳐 이해없이 수정할 때가 있어 내부구조(아키텍쳐,설계)가 부패되기 쉬움
    • 규칙적인 리팩토링을 통해 내부 구조 유지
  • 중복 코드량을 줄여 모든 코드가 고유한 일을 수행할 수 있도록 보장
    • 설계가 나쁘면 중복코드가 많고 코드가 길어져 수정이 어려워짐
    • 리팩토링을 통해 중복 코드 제거 해줌

2. 소프트웨어를 이해하기 쉬워진다.

  • 리팩터링은 코드를 더 잘 읽히 게 도와줌
  • 내가 컴퓨터에 시키려는 일과 이를 표현한 코드의 차이를 줄여 코드의 목적이 잘 드러나도록 함
  • 이 코드를 다시 다룰 개발자(혹은 나)를 배려하고 생산성 향상으로 선순환시키자!

3. 버그를 쉽게 찾을 수 있다.

  • 코드를 쉽게 이해할 수 있다는건 버그를 찾기 쉬워 진다는 것을 의미함

켄트 백 “난 뛰어난 프로그래머가 아니에요. 단지 뛰어난 습관을 지닌 괜찮은 프로그래머일 뿐이에요”

4. 프로그래밍 속도를 높일 수 있다.

  • 리팩터링을 하면 결국 코드 개발 속도(생산성)을 높힐 수 있다.
  • 설계 지구력 가설 (Design Stamina Hypothesis) - 마틴 파울러
    • 리팩터링을 할 때 초기 시간이 소요된다.
    • 하지만 리팩토링을 통해 좋은 내부 품질을 유지하면 추후 운영이슈 발생시 디버깅 시간, 기능 추가시 드는 시간 등이 현저하게 짧아져 결국 장기적으로는 지구력이 높아져 빠르게 개발할 수 있는 상태를 오래 유지 할 수 있다.

2.4. 언제 리팩터링 해야할까?

저자는 한 시간 간격으로 리팩터링을 진행함.

1. 준비를 위한 리팩터링 :: 기능을 쉽게 추가하게 만들기

  • 리팩토링 하기 가장 좋은 시점은 코드베이스에 기능을 새로 추가하기 직전!
  • 예시)
    • 기존 1+2의 기능이 있는데 3+4, 4+5의 합을 구하는 기능이 필요하다고 했을때 복제해서 사용할 경우 중복 코드 발생. 이후 수정 사항, 버그발생시 시간 소요.
    • 이때 준비를 위한 리팩터링으로 미리 개선해 놓은 뒤 작업을 시작하여 추후 확장성 제고
// 기존 메서드 
function getSum() {
    const a = 1;
    const b = 2;
    return a+b;
}
// refactoring "함수 매개변수화하기" 적용
function getSum(a,b) {
    return a+b;
}

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

  • 코드의 의도가 더 명확하게 드러나도록 리팩터링 할 여지가 없는지 검토
  • 세부 코드 이해를 위한 리팩터링(Comprehension Refactoring)
    • 조건부 로직 구조, 긴 함수 나누기, 함수/변수명 변경 등
    • 내가 이해한 것을 코드에 반영해 두면 더 오래 보존할수 있고 동료들도 알수 있음
    • 밖을 잘 내다보기 위한 창문닦기 - 랄프 존슨
    • 정리되기 전에 잘 보이지 않던 설계를 파악 할 수 있음

3. 쓰레기 줍기 리팩토링 - Litter-Pickup Refactoring

  • 작업 중 원래 하려던 작업과 관련 없는 비효율적으로 구성된 코드들(쓰레기)을 발견 시에 진행 (매개변수로 처리 가능한 중복함수 등)
    • 간단히 수정할 수 있는 것은 즉시 고침
    • 시간이 좀 걸리는 일은 메모 후 하던 일 마치고 처리
  • 이것이 이해를 위한 리팩터링 의 변형인 쓰레기줍기 리팩터링
  • 캠핑 규칙이 제안하 듯 처음 봣을때보다 깔끔하게 하고 떠나자!
  • 코드를 볼때마다 잘게 나눠서 조금씩 개선해도 이 단계가 코드를 깨뜨리지 않음!! >> 결국 문제 해결

4. 계획된 리팩토링과 수시로 하는 리팩토링

  • 프로그래밍 과정에 자연스럽게 녹인 것이지 별개의 활동이 아님
  • 리팩터링 일정을 따로 잡아두지 않고 기능을 추가하거나 버그를 잡는 동안 리팩터링도 함께 진행한다.

보기 싫은 코드를 발견하면 리팩터링하자. 그런데 잘 작성된 코드 역시 수많은 리팩터링을 거쳐야 한다.
무언가 수정하려 할 때는 먼저 수정하기 쉽게 정돈하고 그런다음 쉽게 수정하자. - 켄트백

  • 개발을 새로운 무언가를 추가하는 과정으로 여겨 기능을 추가하면 새로운 코드를 넣고 있었음!
  • 새 기능을 추가하기 쉽도록 코드를 “수정"하는 것이 그 기능을 가장 빠르게 추가하는 길임
  • 계획된 리팩토링이 필요할 때도 있지만 이는 최소한으로 줄이고 리팩토링은 기회가 될때마다 해야한다.
  • 리팩토링 커밋을 나누는 것에 대해는 회의적. 너무 연관되어 있어 나누는데 소모되는 비용이 큼

5. 오래 걸리는 리팩토링

  • 리팩터링은 보통 몇분 길어야 몇시간 안에 끝나지만 가끔 대규모 리팩터링도 존재
    • 라이브러리 교체, 로직 컴포넌트화, 의존성 제거 등
  • 이때 팀 전체가 리팩터링에 매달리는건 회의적
  • 리팩터링 대상 관련 코드를 수정할 때마다 원하는 방향으로 조금씩 개선하는 것이 효과적
    • 리팩터링은 일부를 변경해도 항상 기존 기능은 올바르게 동작하므로 이를 활용하여 작업을 쪼개서 함!
    • 예) 라이브러리 교체시 기존,새 모듈을 통합하는 스펙의 추상 인터페이스 구성하여 호출하도록 먼저 변경해 놓으면 나중에 쉽게 교체 가능!
      • 추상화로 갈아타기 (Branch By Abstraction)

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

  • (코드리뷰는 개발팀 전체에 지식을 전파하는데 큰 도움이 되니 왠만하면 하는게 좋음)
  • 코드리뷰시 리팩터링을 바로 적용하여 개선안을 눈으로 직접 확인해 보면서 진행!
  • 코드 리뷰 결과가 구체적으로 도출되며 이 과정에서 한차원 높은 아이디어가 나오기도 함!
  • 이 방식은 코드 작성자가 없는 풀요청모델(Pull Request Model)에는 적합하지 않음
  • 작성자와 함께 앉아 코드 리뷰+리팩토링 하는걸 추천
    • 자연스럽게 짝프로그래밍(Pair programming)을 할 수있음
    • 실시간 코드리뷰, 결함수 감소, 지식공유, 브레인스토밍을 통한 아이디어 도출 등 장점

7. 관리자에게 뭐라고 말해야 할까

  • 리팩터링은 누적된 오류를 잡거나 가치있는 기능을 못 만들어내는 안 좋은 일이 아니다.
  • 관리자가 기술에 정통하고 설계 지구력 가설도 잘 이해하면 충분히 설득 가능. 오히려 권장함
  • 하지만 대부분 관리자는 코드베이스의 상태가 생산성에 미치는 영향을 모름
  • 그러니까 그냥 리팩터링 한다고 말하지 말아라(?)
  • 결국 관리자는 최대한 빠르게 끝내길 원함
    • 새로운 기능을 가장 빠르게 구현하는 구체적인 방법은 개발자가 판단함.
    • 그리고 가장 빠른 방법은 바로 리팩토링!
    • 그러니까 그냥 하자.

8. 리팩터링을 하지 말아야 할때

  • 리팩터링하면 안되는 상황도 있다.
  • 굳이 수정할 필요가 없거나 외부 API 다루듯 호출해서 쓰는 코드라면 지저분해도 그냥 둔다.
    • 내부 동작을 이해해야 할 시점에 리팩터링하면 됨
  • 리팩터링하는 것보다 처음부터 새로 작성하는 게 쉬울 때에도 리팩터링하지 않는다.

2.5. 리팩터링 시 고려할 문제

새 기능 개발 속도 저하

  • 많은 사람들이 리팩터링때문에 새 기능을 개발하는 속도가 느려진다고 생각한다.
  • 준비를 위한 리팩터링을 하면 변경을 훨씬 쉽게 할 수 있다고 확신함
  • 작은 기능만 추가할 때 대대적인 리팩터링이 필요한 상황은 개발자의 경험을 발휘해서 결정
  • 자주 리팩터링을 하지않으면 코드베이스가 건강할 때와 그렇지 않을때 생산성 차이를 체감하기 어려움

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

  • 리팩터링은 클린코드나 바람직한 엔지니어링 습관처럼 도덕적인 이유로 하는 것이 아니다.
  • 리팩터링은 오로지 경제적인 이유로 하는 것이다. (개발기간 단축, 기능추가, 버그 수정시간 단축 등)
  • 이 본질을 개발자,관리자,고객이 명확하게 이해할 수록 좋은 설계 곡선을 많이 볼 수 있음

코드 소유권

  • 리팩터링하다보면 다른 부분과 연동하는 방식에도 영향을 주는 경우가 많음
  • 함수 선언 바꾸기 를 적용할때 해당 코드의 소유자가 다른팀, 혹은 고객에게 제공된 API라면 누가 얼마나 쓰는지 파악하기가 쉽지 않음 > 공개된 인터페이스
    • 리팩터링 후 함수 랩핑 등 추가 조치 필요
  • 코드 소유권이 나뉘어 있으면 리팩터링에 방해가 됨! > 코드 소유권을 작은 단위로 나누는데는 반대.
  • 코드의 소유권을 팀에 두고 팀원이라면 누구나 팀이 소유한 코드 수정할 수 있게 해야한다.

브랜치

  • 보통 작업할 때 브랜치를 따서 작업하다가 master(develop)브랜치에 통합하는데 독립브랜치에서 작업하는 기간이 길어 질 수록 마스터 브랜치에 통합 하기가 어려워짐( 그래서 자주 내 브랜치에 rebase / merge 하쥐.. )
  • 기능별 브랜치 통합주기를 2~3일주기, 혹은 더 짧게 가져가야함!
  • 지속적 통합(Continuous Integration) / 트렁크 기반 개발(Trunk-Based Development)
    • 최소 하루에 한번 마스터와 통합
    • 기능을 잘 쪼개 놓고 기능 토글 등을 적용하여 완료되지 않은 기능이 적용되지 않도록 제어
    • CI를 하면 머지의 복잡도가 내려가고 리팩토링과도 아주 궁합이 좋음! (충돌 빈도가 줄어든다)
    • CI + Refactoring >익스트림프로그래밍(eXtreme Programming) - 켄트백
  • CI를 완벽히 적용하지 못하더라도 통합 주기는 짧게 가져가자

테스팅

  • 리팩터링하면 동작이 깨지지 않아야함
  • 따라서 리팩터링 후 기능 검증을 위해 자가 테스트 코드(self-testing code)를 갖춰야함
    • 리팩터링 뿐만 아니라 새 기능 추가도 훨씬 안전하게 진행 할 수 있다.
    • 통합 과정에서 발생하는 충돌 해결에도 활용되므로 CI와도 연관
    • CI의 통합된 테스트는 XP의 권장사항이자 지속적배포(Continuous Delivery)의 핵심

레거시코드

  • 레거시 코드는 대부분 복잡하고 테스트 코드도 없으며 다른 사람이 작성한 코드임
  • 리팩터링은 레거시 시스템을 파악하는데 큰 도움이 되나 테스트 코드 없이 리팩터링하기에 쉽지 않음.
  • 따라서 테스트 보강이 선행 되어야 함
  • 이후 서로 연관된 부분끼리 나눠서 하나씩 공략하며 리팩터링 진행

데이터베이스

  • 데이터베이스가 리팩터링하기 어려운 영역이었지만, 요새는 그렇지도 않음
  • 진화형 데이터베이스 설계 / 데이터베이스 리팩터링 기법 - 사달게
    • 마이그레이션 스크립트작성, DB schme 변경을 이 스크립트로 처리하는 기법
    • 컬럼명 변경시 DB마이그레이션 스크립트를 작성하고 해당 컬럼 참조하는 코드로 변경하는 버전을 스크립트와 함께 VCS에 저장후 DB이관시 실행?🥲
  • DB리팩터링은 여러단계로 나눠서 릴리즈 하는 것이 좋다.
  • 컬럼 이름바꿀때 > 변경될 신규컬럼추가 > 클라이언트 변경 > 기존컬럼삭제 :: 병렬 수정

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

  • 리팩터링이 아키텍쳐에 미치는 실질적인 효과는 요구사항 변화에 자연스럽게 대응하도록 코드베이스를 잘 설계해 준다는 것이다.
  • 소프트웨어 요구사항은 실제로 사용해본 뒤 추가로 알게 되는 경우가 많음
    • 이를 대응하기 위해 유연성 매커니즘 을 통해 다양한 예상 시나리오를 고려하여 함수에 매개변수들을 미리 추가해 둠.
    • 오히려 함수가 복잡해져 변화에 대응하는 능력을 떨어 뜨리는 경우가 대부분
  • YAGNI (You Aren’t Going to Need It) / 간결한 설계 / 점진적 설계
    • 앞으로 어느 부분에 유연성이 필요하고 어떻게 해야 그 변화에 가장 잘 대응할 수 있을지 추측하지 않고, 그저 현재까지 파악한 요구사항만을 해결하는 소프트웨어를 구축한다.
    • 그리고 사용자의 요구사항을 더 잘 이해하게 되면 그에 맞게 리팩터링해서 바꾼다.
    • 유연성 매커니즘은 반드시 검증 후에 추가한다. (왠만하면 필요시 추후 리팩터링으로!)
    • 나중에 문제를 더 깊이 이해하게 됐을 때 처리하는 것이 낫다. > 진화형 아키텍쳐

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

  • 리팩터링이 퍼지기 시작한건 익스트림프로그래밍에 도입 됐기 때문.
  • 지속적 통합 +자가테스트코드 + 리팩터링 + @ = 익스트림프로그래밍
  • 자가테스트코드 + 리팩터링 = 테스트 주도 개발(TDD)
  • 현재 애자일을 많이 사용하지만 제대로 적용하려면 리팩터링에 대한 팀의 역량과 열정이 뒷받침 되어 프로세스 전반에 리팩터링이 자연스럽게 스며들도록 해야한다.
  • 세가지 핵심 실천법
    1. 자가 테스트 코드 : 리팩터링 시 기능 검증
    2. 지속적 통합 : 지속적 통합상태 유지하며 항상 리팩터링 할 수 있어야함. 자가테스트코드도 CI의 핵심요소
    3. 리팩터링

위 세 기법을 통해 개발시 서로 강력한 상승효과 및 YAGNI 방식으로 개발을 진행할 수 있다.

  • YAGNI <> 리팩터링 : 서로 긍정적인 영향을 줌. yagni로 인해 리팩터링이 쉬워진다.
  • 요구사항 1변화에 빠르게 대응하곺안정적인 선순환ㅍ구조릂 코드베이스에 심을 수 있음 (애자일)
    • 지속적배포를 통해 언제든 프로덕션 릴리즈 가능!

2.8. 리팩터링과 성능

  • 저자는 소프트웨어를 이해하기 쉽게 만들기 위해 속도가 느려지는 방향으로 수정하는 경우가 많음.
  • 성능을 튜닝하기는 더욱 쉬워질 수 있기때문
  • 대부분의 프로그램은 극히 일부 로직에서 대부분의 시간을 사용함
    • 코드를 고르게 최적화 한다면 그중 90%는 효과가 없으므로 시간낭비
    • 시스템에 대해 잘 알더라도 섣불리 추측하지 말고 성능을 측정해 봐야한다.
  • 그래서 일단 성능에 신경 쓰지 않고 코드를 다루기 쉽게 만드는데 집중
  • 이후 최적화 단계에서 구체적인 절차에 따라 튜닝
    • 프로파일러로 분석하여 시간과 공간을 많이 잡아먹는 지점을 찾아 개선
    • 이때 리팩터링이 잘 되어 있다면
      • 기능 추가 시간이 줄어 성능 튜닝에 투입할 시간이 늘어남
      • 프로그램의 성능을 더 세밀하게 분석 할 수 있음
  • 최적화 단계에서 코드를 튜닝하기 훨씬 쉬워지기 때문에 장기적으로는 결국 더 빠른 소프트웨어를 얻게 됨

2.9. 리팩터링의 유래

  • 정확한 유래는 찾을 수 없었음
  • 리팩터링은 소프트웨어 개발 프로세스 전반의 핵심 요소
  • 워드 커닝햄, 켄트백은 1980년대부터 리팩터링의 중요성을 깨닫고 스몰토크를 활용해 개발하며 리팩터링을 실전 프로젝트에 활용하면서 개선해 나감
  • 스몰토크 커뮤니티에 랄프존슨이 있었고 신규 프레임워크 개발에 리팩터링의 기여에 관해도 연구했음
  • 랄프존슨의 박사과정 학생 빌옵다크님이 있었는데 리팩터링의 가치를 파악하고 스몰토크를 넘어 다른 언어에 적용할 수 있다고 생각해서 의미보존 리팩터링 기법을 연구, 도구로 구현하는 방법 발표. 이게 최초 리팩터링에 관한 연구 논문
  • 이후 존브랜트, 돈로버츠가 이 아이디어를 발전시켜 스몰토크 환경 리팩터링 도구인 리팩터링 브라우저 개발
  • 저자는 마틴파울러는 켄트와 프로젝트 하면서 리팩토링을 하면서 얻는 생산성과 품질의 차이를 느끼고 리팩터링이 중요하다는걸 깨달음
  • 근데 그때는 관련하여 읽을 책도없고 쓰겠다는 사람도 없어서 초판을 썼음!
  • 이후 업계에서 리팩터링이라는 개념이 잘 받아들여졌고 책도 많이 팔림.

2.10. 리팩터링 자동화

  • IntelliJ IDEAEclipse 등 자동 리팩터링을 지원하는 도구가 등장함
  • 최초의 자동리팩터링 기능은 스몰토크용 리팩터링 브라우저
  • 자동 리팩터링을 위해서는 텍스트가아닌 구문트리를 해석해서 다뤄야 함
  • IntelliJ IDEA ⌘+T refactor

2.11 더 알고 싶다면

리팩터링 워크북

  • 리팩터링 연습에 주력한 책

패턴을 활용한 리팩터링

  • 소프트웨어 패턴과 리팩터링 접목
  • 디자인 패턴 책의 핵심적인 패턴을 골라 재구성하기 위한 리팩터링 방법을 다룸

리팩토링 데이터베이스 ,리팩토링 HTML

  • 데이터베이스, HTML에 특화된 리팩터링 책

레거시 코드 활용 전략

  • 테스트 커버리지가 낮은 오래된 코드베이스를 리팩터링하는 방법

Refactoring: Ruby Edition

  • Ruby에 특화된 리팩터링 책