2장. 리팩터링 원칙

리팩터링 정의

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

 

왜(why) : 이해하고 수정하기 쉬운 코드

무엇을 (what) : 내부 구조를 변경

어떻게 (how) : 겉보기 동작은 그대로 유지한 채

 

 

겉보기 동작의 유지란?

예시 : 함수 매개변수화 하기

function tenPercentRaise(aPerson){
	aPerson.salary = aPerson.salary.multiply(1.1);
}

function fivePercentRaise(aPerson){
	aPerson.salary = aPerson.salary.multiply(1.05);
}

function raise(aPerson, factor){
	aPerson.salary = aPerson.salary.multiply(1 + factor);
}

겉보기 동작이 달라졌는가?

 

 

"리팩터링하다 코드가 깨졌다"고 하면 그것은 리팩터링이 아니다.

restructuring (리스트럭쳐링/재구성)

: 코드베이스를 정리하거나 구조를 바꾸는 모든 작업

 

refactoring

: 모든 리스트럭쳐링 중에서 도중에 중단되더라도 동작이 유지되는 것

 

리팩터링은 그 과정에서의 겉보기 동작 유지가 중요한 것!

리팩터링의 특징은 무엇을 하느냐가 아니라 어떻게 하느냐

 

 

두 개의 모자

기능 추가와 리팩터링을 명확하게 구분하라

 

실제 경험

CR(Code Review)를 요청했다. 해당 commit의 목적은 기능 추가였다. 그러나 코드 수정을 위해 (js circular dependency) 리팩터링을 해야만 했다. 때문에 이 커밋에는 리팩터링과 기능추가가 함께 들어갔다. 

 

이 CR의 문제점

1) 여기에 추가된 코드 라인이 리팩터링인지 기능추가인지, 어떻게 구분할 것인가?

2) 어떤 코드가 파일 A에서 파일 B로 옮겨졌다. 그리고 기능 추가가 이루어졌다. 기능 추가로 수정된 사항을 diff로 알 수가 없다.

 

리팩터링 모자를 썼을 때는, 심지어 '테스트'마저 수정하지 않는다.

 

"리팩터링 커밋과 기능 추가 커밋을 분리해야 한다는 조언 ... 완전히 동의하지는 않는다. 

리팩터링은 기능 추가와 밀접하게 엮인 경우가 너무 많기 때문에 굳이 나누는 것은 시간 낭비일 수 있다."

(계획된 리팩터링과 수시로 하는 리팩터링, p89)

 

대안책

최종 commit은 하나로 묶되, cr은 여러 revision으로 나누어 검토

 

git의 stash / commit / rebase 등의 기능을 잘 활용하면 예술적으로 적용할 수 있다. 

(p.94 '브랜치' 섹션 참고)

 

 

리팩터링, 왜 하는가?

 

설계 지구력 가설

내부 설계에 심혈을 기울이면 소프트웨어의 지구력이 높아져서 빠르게 개발할 수 있는 상태를 더 오래 지속할 수 있다.

 

 

리팩터링, 언제 하는가?

3의 법칙 - 3진(아웃) 리팩터링

1. 일단 개발한다

2. 같은 일을 두 번 하게 되면... 일단 한다

3. 또 같은 일을 하게 되면, 이제야 말로 리팩터링 할 때다

 

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

기능을 추가하기 직전에, '리팩터링을 하면 더 쉽게 추가할 수 있지 않을까?' 고민

 

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

코드를 파악해야 할 일이 있을 때, 이해한 내용을 더 잘 반영할 수 있도록 리팩터링

 

쓰레기 줍기 리팩터링

코드를 파악하다가 '비효율적으로 처리하는 모습을 발견'하면 리팩터링

 

수시로 하는 리팩터링

따로 하지 않고 일반 개발 과정에서

( 가장 이 방법을 많이 사용한다. )

 

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

코드 리뷰를 할 때, 실제로 리팩터링을 해 보기

 

+ 리팩터링 하지 말아야 할 때

지저분해도 굳이 수정할 필요가 없을 때, 차라리 새로 작성하는 게 쉬울 때

 

 

리팩터링 (실질적으로) 언제 하는가?

3의 법칙 (X)

 

준비를 위한 리팩터링 (O)

기능을 추가하기 직전에, '리팩터링을 하면 더 쉽게 추가할 수 있지 않을까' 고민

 

이해를 위한 리팩터링 (X)

코드를 파악해야 할 일이 있을 때, 이해한 내용을 더 잘 반영할 수 있도록 리팩터링

 

쓰레기 줍기 리팩터링 (X)

코드를 파악하다가 '비효율적으로 처리하는 모습을 발견'하면 리팩터링

 

아마존(LRAV팀)의 실질적 리팩터링 분포

 

 

YAGNI (You Aren't Going to Need it)

 

 

YAGNI (You Aren't Going to Need It)

1. 추측하지 말고 현재 요구사항만 충족해라

2. 대신 그것을 최대한 멋지게 해결하도록 설계해라

3. 나중에 더 잘 이해하게 되면 리팩터링으로 바꾼다

 

선제적 아키텍쳐에 소홀하라는 뜻이 아니다.

이미 알고 있는 부분은 최대한 미리 준비하는 게 좋지만 

모르는 걸 미리 대비하기 보다는, 이해하고 나서 반영하는 게 효율적이다.

 

 

리팩터링과 성능

'이렇게 하면 더 느려질 것 같은데...'

let average = 0;
let totalSalary = 0;
for(const p of people){
	averageAge += p.age;
    totalSalary += p.salary;
}
averageAge = averageAge / people.length;

let totalSalary = 0;
for(const p of people){
	totalSalary += p.salary;
}

let averageAge = 0;
for(const p of people){
	averageAge += p.age;    
}
averageAge = averageAge / people.length;

 

직관적 설계 vs. 성능

리팩터링은 이해하기 쉬운 코드를 위해 '속도가 느려지는' 방향인 경우가 많다.

 

성능이 느려져도 상관없으니 보기 좋은 코드가 좋다 (X) 

하드웨어가 발전해서 그 정도 성능 저하는 아무 문제 없을 것 (X)

성능을 튜닝하기는 쉬워지기 때문에 오히려 성능이 좋아진다 (O)

 

90퍼센트의 시간은 낭비

1) 성능에 신경쓰지 않고, 보기 좋게 코딩한다.

2) 성능 최적화 단계가 되면, 프로파일러로 분석하여 오랜 시간을 잡아먹는 코드를 특정한다.

3) 그 부분을 개선한다.

 

리팩터링이 잘 된 코드는 성능 분석에 유리하다.

 

 

기타 주의해서 읽어볼 것

언제 리팩터링 해야 할까

- 관리자에게는 뭐라고 말해야 할까

 

리팩터링 시 고려할 문제

- 브랜치

- 테스팅

 

리팩터링 자동화