리팩토링 2판

review, share · 2024-12-5

← 리스트로

리팩터링 2판

테스트코드 + 리팩토링 + 지속적통합

기능을 추가하기 전에 리팩터링 한다

기능을 추가해야 할 때, 코드가 새로운 기능을 추가하기 어려운 상태라면 추가하게 쉬운 상태로 만들고 기능을 추가하는게 좋다.

기능을 안전하게 축하기 위헤선 구조변경이 필요하며 구조변경을 안전하게 하려면 테스트 코드가 필요하다.

큰 덩어리의 코드를 작게 조금씩 추출하여 점진적으로 리팩터링 한다

큰 함수를 작은 여러개의 함수로 나누어 특정한 이름을 붙려서 쪼개는 방식을 함수추출하기라고 한다.

작은 단위로 나눠서 리팩토링을 조금조금씩 진행한다. 이런 점진적 전략은 중간에 실수하더라도 쉽게 버그를 찾을 수 있도록 도와준다.

추출한 코드는 독립적 함수 내에서의 적절한 이름으로 변경한다.

매개 변수는 적을수록 좋으며, 이를 위해 다양한 방법을 사용할 수 있다

추출한 함수의 매개변수는 적으면 적을수록 좋다. 이를 이루기 위한 방법은 여러가지가 있지만 특히 쉽고 유용한 방법은 썽크를 이용하는 것이다.

지역변수로 정의된 함수도 최대한 지역변수에서 제거하여 새로운 함수로 만드는 것이 좋다.

이런식으로 함수추출하기를 하면 코드의 추상화가 자연스럽게 이루어져 프로그램 전체의 논리구조를 파악하기 쉽게된다.

라인 수가 적은게 중요한게 아니라 명확한 추상화와 가독성이 중요하다

함수를 잘개 추출하면 전체 코드 라인은 길어지지만 흐름이 명료해진다.

처음 간결함도 물론 좋지만 기능이 많아지면 불가피하게 코드가 복잡해지므로 명료함을 택하려면 명확한 기능으로 정의된 함수가 많아지는게 유리하다.

코드베이스를 작업할땐 작업 후에 코드가 더 명료해져야 한다.

다형성을 이용한 전략패턴을 잊지말고 적극 활용하자

객체지향의 다형성을 이용하여 조간부로직을 이해하기 쉽게 정리할 수 있다.

물론 꼭 클래스를 활용하지 않아도 모듈을 잘 정리해 놓으면 전략 패턴을 적용할수 있다. 하지만 클래스를 사용하는 방법은 코드를 잘 정리하는 이미 증명된 방법이므로 적극 활용하자.

매일 조금조금씩 점진적 리팩토링을 실천하자

리팩토링이란 겉은 그대로 유지하되, 코드를 이해하고 유지보수 하기 쉽도록 구조를 변경하는 기법 이다.

리팩토링으로 인해 코드가 깨지는게 염려된다면, 리팩토링을 잘못 이해하고 사용하고 있는것이다. 리팩터링은 코드를 깨뜨리지 않는다.

제대로 리팩토링을 이해하고 실행하고 있다면, 리팩터링을 중간에 멈췄더라도 코드는 그대로 동작해야 한다.

리팩터링은 성능최적화나 기능추가와는 틀리다.

성능만 추구하여 코딩한다면 코드는 더 이해하기 어려워 질 수 있다.

기능추가와 리팩토링 작업은 분리되어야 한다. 두 모자를 자주 바꿔쓰며 작업하자.

리팩토링은 초기 설계의 순수성을 보호하는데 도움을 준다

리팩토링은 코드의 구조가 변질되지 않게 보호해준다.

코드를 잘 정리해두면 기억에 의존하는 개발을 하지 않아도 된다. 코드의 함수명과 변수명을 보고 쉽게 히스토리나 구조가 파악되기 때문이다.

잘 리팩토링되며 유지되는 코드는 버그도 더 쉽게 잡을 수 있고 개발 속도도 더 빨라진다.

리팩토링의 시점

  • 3의 법칙

    • 내가 비슷한 작업을 세번째 하고 있다면 코드의 중복이 3개 이상이라는 것이므로 리팩토링 시점이다
  • 새로운 기능을 추가하기 직전

    • 리팩토링을 하면 기존 코드를 더 명확하게 파악할수 있어서 작업 진행에 도움이 된다
    • 창을 더 잘 내다보기 위한 창문 닦는 행동에 비유할 수 있다
  • 눈에 보일때 마다 수시로 조금조금씩 수시로

    • 몇 달에 걸친 리팩토링이라도 중간 단계에서 배포 가능해야 한다
    • (중간에 멈추더라도 코드가 깨지지 않아야 한다)

점전적 리팩토링을 실현하기 위한 전략

  • 리팩터링을 위한 커밋의 분리가 마냥 좋은것은 아니다

    • 기능 추가를 위한 리팩터링의 경우 리팩토링을 위한 커밋이 따로 분리해 놓으면 맥락을 놓칠수가 있다
  • 추상화로 갈아타기

    • 기존 코드와 리팩토링 코드가 공존할수 있게 인터페이스를 마련해 놓으면 점진적 리팩토링이 수월해진다
  • 리팩토링을 이해하지 못하는 관리자에게 리팩토링이란 용어를 구지 쓰지 마라

    • 프로라면 리팩토링이 요건을 빨리 개발하는데 최선임을 안다
  • 리팩토링이 필요없을때도 있다

    • 새로 구현하는게 더 빠를때나, 리팩토링에 이점이 전혀 없을때는 구지 하지않는다
  • 모든 코드를 다 리팩토리유할 필요는 없다

    • 내가 자주 만지지 않거나 수정사항이 없고 그대로 잘 돌아가는 코드면 리팩토링 구지 하지 않는다
  • 리팩토링은 코드를 얘쁘게 꾸미는 목적이 아니다

    • 개발을 더 쉽게 하기위한 경제적인 이유로 하는 것이다
  • 유능한 개발자는 업무에 항상 리팩토링 시간을 할애해 둔다

    • 처음부터 깔끔하게 짜는건 불가능 하다는걸 알기 때문이다.

지속적 통합

브랜치 통합주기는 빠를수록 좋다

최대한 자주 메인 브랜치에 병합하자. 브랜치 전략은 독립된 브랜치의 작업기간이 길어질수록 나중에 통합하기 힘들어지는 단점이 있다.

이런걸 지속적 통합이라고 한다. 완성되지 않은 코드가 영향을 미치지 않도록 flag로 기능 on off를 만든다거나 하는 방식을 적극 활용하자.

테스트가 잘 구축된 코드는 리팩토링이 수월하다

테스트가 잘 짜여 있으면 리퍅토링에 의한 버그에 대해 걱정이 즐어든다.

테스트가 없는 레거시 코드는 애초부터 테스트를 추가하기 힘든 구조이기 때문에 더욱 리팩토링이 어렵다.

이런경우 부분공략과 캠핑장 이용 전략으로 점진적으로 리팩토링 해나가는게 좋다.

리팩토링을 활용하면 점진적 아키텍처 설계가 가능해진다

점진적 설계란 일단은 기능이 동작하게끔만 추가해 놓고, 그 다음에 상황을 보고 새로운 기능에 맞게 다시 리팩토링 하는 것이다.

나중에 더 깊이 문제를 이해한 후 설계하는 것이다. 이런 설계를 에그니 설계라고도 한다.

리팩토링이란 개념이 생기면서 운영되고있는 서비스라도 아키텍처를 대폭 수정할 수 있게 되었다.

리팩토링과 성능

성능은 보통 특정 병목 구간애서 발생하기 때문에 아키텍트와는 상관 없는 경우가 많다.

리팩토링이란 가독성을 위한 작업이기 때문에 오히려 성능이 미세하게 안 좋아 질수는 있지만, 역설적으로 성능 병목 구간을 쉽게 파악하고 튜닝할 수 있게되어 장기적으로 보면 성능에 더 좋다.

일반적으로 유용한 리팩토링 기법

  • 매개변수를 질의함수로 바뀨기

    • 기존값들의 계산으로 대체할수 있는 매개변수는 제거
  • 전적변수를 캡슐화

    • 전역변수를 사용안하는게 좋지만, 사용해야 한다면 게터 세터를 만들어서 캡슐화 해둔다
  • 함께 몰려다니며 사용되는 데이터 뭉치는 클래스로 만들어 관리

    • 데이터 뭉치와 기능들을 그룹화해서 값 전달이나 파악이 용이해진다
  • 추측성 일반화를 하지 않는다

    • 섣부른 최적화는 상황을 더 복잡하게 할 수 있다
  • 메세지 체이닝은 퍼사드기법으로 내부 구조를 은닉화 할수 있다

    • 메시지 체인을 일반화하면 일일이 모든 객체를 다 기억 안해도 된다
  • 데이터클래스(데이터만으로만 묶여있는) 는 나쁜 코드의 냄새다

    • 데이터가 그룹화 되어 있는 경우, 그 데이터들과 같이 자주 사용되는 함수도 있기 마련이다
  • 과도한 상속 제거

    • 부모로부터 물려받아 사용하는 코드가 거의 없는 클래스는 상속을 포기하자
  • 테스트 커버리지에 너무 집착하지 말자

    • 테스트 커버리지는 중요하지 않다 테스트는 위험요인을 찾는 위주로 작성해야 한다.
    • 완벽한 테스트를 만드느라 수행하지 못하느니, 불안전한 테스트라도 수행하는게 좋다.
    • 실력이 완벽히 뛰어난 사람도 한번에 완벽한 테스트 코드를 짜기는 힘들다.

리팩토링 기법 카탈로그

6.1 함수추출하기

단일책임원칙으로 코드의 목적을 알기쉽게. 짧은 함수로 만들고 이름짓기가 중요하다.

6.2 함수 인라인하기

필요한 경우에는 여러 함수를 합친다

6.3 변수추출하기

표현식을 추출하여 인지하기 쉬운 변수로 이름 붙인다

6.4 변수인라인하기

변수의 표현식이 너무 간단해서 큰 도움이 안될 때는 표현식으로 합친다.

6.5 함수선언바꾸기

함수 이름이 잘못됬을때 적합한 이름으로 수정

6.6 변수캡슐화하기

전역 데이터 관리는 문제가 되며, 데이터를.직접 바꾸는것보다는 캡슐화하여 처리하는게 인지 측면이나 도구 활용 측면에서 더 안전하다

6.7 변수이름 바꾸기

변수 이름이 잘못됬을때는 적합한 이름으로 수정

6.8 매개변수 객체만들기

데이터들간의 성격과 경계가 명확해지며 매개변수 전달이 간편해진다.

6.9 여러 함수를 클래스로 묶기

공통데이터를 사용하는 함수무리를 다룰때 편리하다.

함수들이 동작하는 공통 환경을 명확하게 표현가능하다.

6.10 여러 함수를 변환함수로 묶기

같은 패턴으로 구현된 함수가 여러군대 정의되어 있으면 하나로 합친다.

6.11 단계쪼개기

간단한 함수라도 여러 단계와 세부 목적로 나눠지는 경우 나눈다.

7.1 레코드캡술화하기

가변 레코드와 같은 데이터를 저장할때 객체로 캡슐화 하면 편리하다.

해시맵과 같은 자료구조를 사용할 경우 어떤계산에 의해 특정 속성이 만들어지는지 확인하기가 번거롭기 때문이다.

그리고 값도 안전하게 보호할수 있다. 캡슐화 되어 접근자를통해 값을 안전하게 보호하기 때문이다.

7.2 컬렉션 캡슐화

컬랙션 사용중에 값을 변경이나 확보를 할때, 변수 추적을 위한 위한 게터세터나 얕은복사를 처리 해놓는걸 말한다.

가변 데이터를 캡션화 하면 값의 변경을 추적하기가 쉬어진다.

7.3 기본형을 객체로 만들기

기본형 데이터도 객체로 확장해 놓으면 코드가 복잡해질때 쉽게 관리하는 기능을 추가할 수 있게된다.

7.4 임시변수를 질의함수로 바꾸기

자주쓰는 임시변수를 게터함수로 만들어쓰면 편하다 가독성도 좋아진다. thunk 나 computed 를 말한다.

7.5 클래스 추출하기

비대한 클래스일 경우 책임별로 잘게 나누는게 좋다.

7.6 클래스인라인하기

(클래스추출하기의 반대) 때에 따라서 클래스를 합치는게 좋을때도 있다.

7.7 위임숨기기

캡슐화를 통해 객체 사용자가 객체의 필요 이상의 구조 탐색을 숨긴다.

7.8 중재자 제거하기

특정 클래스의 역할이 단지 중재자일 뿐이라면 없애는게 좋을수도 있다.

7.9 알고리즘 교체하기

교체할 코드를 함수 하나로 모아 테스트 한후. 함수만 쏙 교체한다.

8.1 함수 옮기기

특정 메서드에서 더 어울리는 메서드로 옮기거나, 특정 모듈에서 더 적합한 다른 모듈로 함수를 옮긴다.

8.2 필드 옮기기

특정 클래스에서 더 어울리는 클래스로 필드롤 옮긴다. 보통 메서드도 같이 이동된다.

8.3 문장을 함수로 옮기기

함수추출하기의 일종으로, 코드 뭉치에서 특정 목적의 패턴을 함수로 빼내는 것이다.

8.4 문장을 호출한 곳으로 옮기기(8.3의 .반대)

재활용성도 없고 너무 간단한 코드는 합치는게 더 좋을수도 있다

8.5 인라인코드를 함수호출로 바꾸기

함수 추출하기와 같은 맥락으로, 이미 인라인코드에 해당하는 패턴의 함수가 있을때 함수호출로 바꾸는 것이다.

8.6 문장 슬라이드하기

함수를 추출하거나 함수호출로 바꾸기 전에, 코드의 순서나 패턴을 더 쉽게 일반화 할수 있도록 인접한 라인으로 모아두는걸 말한다.

8.7 반복문 쪼개기

한번의 루프로 모든걸 해결하려는 유혹이 있지만 너무 이것에 집착하면 가독성을 잃게 된다.

8.8 반복문을 파이프라인으로 바꾸기

함수형 재귀처리함수를 적극적으로 활용하자.

8.9 죽은 코드 제거하기

이제 우리 시대에는 소스 버전 관리 툴이 있으니, 안쓰는 코드를 다시 언젠가 재활용하기 위해 주석처리해둘 이유는 없다.

9.1 변수 쪼개기

역할이 둘 이상인 변수가 있으면 혼란스럽다.

9.2 필드 이름 바꾸기

변수의 이름은 적절해야 한다

9.3 파생변수를 질의함수로 바꾸기 (7.4 와 동일함)

자주쓰는 임시변수를 게터함수로 만들어쓰면 편하다 가독성도 좋아진다. thunk 나 computed 를 말한다.

9.4 참조를 값으로 바꾸기

가변 객체의 가변적 특성이 사실상 필요없을경우 불변객체로 바꿀 수 있다. (세터를 없애버리는 거나, 새로운 값은 그냥 새로운 객체를 만드어서 불변성을 확보할 수 있다)

9.5 값을 참조로 바꾸기

불변데이터는 여러모로 부작용이 없어서 좋지만, 어쩔수 없는 경우도 있다.

9.6 매직 리터럴로 바꾸기

고정값 상수처리를 말한다.

10.1 조간문 분해하기

조건식과 조건절을 각각 함수로 추출하면 가독성이 좋아진다. 극단적인 예이지만 아래처럼 가능하다

charge = summer() ? summerCharge() : regularCharge()

10.2 조건문 통합하기

조건절이 동일하지만 조건식이 틀린 조건문은 통합할 수 있다.

10.3 중첩된 조건문을 보호구문으로 바꾸기

중첩된 조건문은 가독성이 떨어지므로 가능하면 논리적으로 평평하게 바꾼다.

10.4 조건부 로직을 다형성으로 바꾸기

가독성이 명확해지고 조건문의 분산을 막을 수 있다.

10.5 특이 케이스 추가하기

일종의 널객체 패턴을 말한다. 널 외에도 다양한 케이스의 예외처리에 특이케이스 객체 패턴으로 사용할 수 있다.

10.6 어서션 아용하기

어서션을 비지이스코드에 추가하면 주서거럼 도움이ㅜ된다

10.7 제어 플레그를 탈출문으로 바꾸기

제어플레그는 상황을 복잡하게 만드므로 가능하면 탈출구문이 좋다.

11.1 질의함수와 변경함수 분리하기

순수함수와 비순수함수를 분리해야 혼란이 줄어든다. 우리는 부수효과를 관리해야 한다.

11.2 함수 매개변수화하기

함수이름에 특정 값을 의미하는 암시가 있다면 증복코드를 의미한다 매개변수화 할 수 있다.

11.3 플래그 인수 제거하기 - (11.2 의 반대)

11.4 객체를.통째로 넘기기

객체를 통째로 넘기면 편리하다

11.5 매개변수를 질의함수로 바꾸기

계산할수 있는 값을 매개변수로 넘길 필요는 없다

11.6 질의함수를 매개변수로 바꾸기 - (11.5의 반대)

함수의 순수성을 위해서 매개변수로 꼭 넘겨야 할 때가 있다.

11.7 세터 제거하기

객체가 불변해야 할경우 세터를 제거하면 좋다.

11.8 생성자를 팩터리 함수로 바꾸기

오버로드와 같은 개념이 없거나 다형성의 개념이 없는 언어일 경우 언어의 한계를 극복할 수 있다.

11.9 함수를 명령으로 바꾸기

예를 들어 함수를 객체로 만들어서 실행하는 command패턴 처럼 사용하는 걸 말한다.

기본값을 클래스화 하는것 처럼 함수도 객체화 하면 유리할때가 있다. 대두분의 경우 일급함수를 지원하는 언어는 그 자체로 충분하다.

명령으로 바꿔 놓우면 되돌릴기 와 같은 보조 연산도 가능하며, 커멘드 패턴을 사용하기 위해서 활용될수도 있다.

11.10 명령을 함수로 바꾸기

복잡한 서브루틴을 갖을 필요가 없는 함수라면 일반함수가 좋다.

11.11 수정된 값 반환하기

데이터가 수정된다면 그 사실을 명확히 알려줘야 함. 그런데 함수 내부에서 바꿔버리면 알기 힘듬. 리턴하게 해주고, 호출자가 그걸 받아서 set하도록

11.12 오류코드를 예외로 바꾸기

오류코드를 flag변수로 관리하는 방법은 옛날 방식이고, 현대의 언어는 에러를 throw하는 방식이 존재한다.

11.13 예외를 사전확인으로 바꾸기

진짜 처리할수 없는 진짜 오류에만 에러 던지기를 사용해야 한다. 일반적인 케이스에 오류를 사용하면 안된다. (Try catchn남발하지말자)

12.1 메서드 올리기

중복제거 효과

12.2 필드 올리기

필드중복제거

12.3 생성자 본문 올리기

부모 생성자에서 처리할수 있는 코드는 올려서 중복을 제거한다.

12.4 메서드내리기

공통 기능이 아니라면 자식 클래스로 내린다. (당연히)

12.5 필드내리기

공통 필드가 아니라면 당연히 내린다.

12.6 타입 코드를 서브클래스로 바꾸기

조건부 로직을 다형성으로 바꾸는 10.4 절과 의미가 같다.

12.7 서브클래스 제거하기

다형성 처리의 이점이 없어졌을때는 다시 돌아가자.

12.8 슈처클래스 추출하기

비슷한 일을 하는 두개의 클래스를 일반화해서 공통 코드를 위로 올린다.

12.9 계층 합치기

더이상 부모 자식 클래스 간에 차이가 없어서 상속의 이점이 없을때.

12.10 서브클래스를 위임으로 바꾸기

상속은 값비싸고 깊은 뎁스의 상속은 오히려 가독성을 떨어뜨린다. 상속보다는 합성을 활용하라는 겪언도 있다.

12.11 과 같은 맥락이며 이 경우엔 슈퍼클래스의 관점이다.

12.11 슈퍼클래스를 위임으로 바꾸기

12.10의 맥락과같으며, 이 경우엔 서브클래스의 관점이다.