리팩터링이란?
: 겉으로 드러나는 기능은 그대로 두고, 내부 구조를 개선하는 것.
- 공학 설계의 관점에서 설계를 한 후 기능을 만드는 것이 일반적이지만, 리팩터링의 관점에서는 기능을 만든 후, 설계를 좋은 구조로 정리해 이해하기 쉽고 변화에 유연한 좋은 코드를 만드는 작업이다.
코드 리팩터링을 위한 작업
- 테스트 작성: 신뢰성 측면에서, 이전과 같은 기능을 수행하는지 테스트를 통해 변경한 코드에 휴먼에러가 없는지 확인한다.
- 형상관리: 변경(리팩터링)때마다 커밋을 하면 변화를 추적하기 편하고, 혹시 모를 상황에 롤백 할 수 있다.
다양한 리팩터링 방법
: 기본적인 리팩터링 기법부터 캡슐화, 기능 이동, 다형성 등 다양한 방법이 있다. 각각의 사용법을 이해하고 사용했을 때의 장점을 생각해보고 적용하면 좋을 것 같다.
- 함수 추출하기, 함수 인라인하기(변수 선언 제거), 함수 선언 바꾸기(이름, 시그니처), 네이밍 더 명확하게 변경하기
- 함수 추출 인텔리제이 단축키:
option
+command
+M
- 함수 추출 인텔리제이 단축키:
- 문장 슬라이드하기 (관련 코드를 한 곳으로 뭉치기), 함수 옮기기(모듈 관점), 단계 쪼개기(코드 분리) 등
- 관련 코드가 한 군데 모아져있으면 임시 변수를 질의 함수로 바꾸기가 수월해진다.
리팩터링 예시
임시 변수를 질의 함수로 바꾸기 (변수를 함수화하기)
1
2
3
4
// before
public void foo() {
int total = prices[priceId] * amount;
}
1
2
3
4
5
6
7
8
9
// after
public void foo() {
int total = Price.getTotal(priceId);
}
// -- Price.java
public static void getTotal(int priceId) {
return prices[priceId] * amount;
}
- 지역변수를 객체의 메서드로 만들면 좋아질 때가 많다.
조건부 로직을 다형성으로 바꾸기
1
2
3
4
5
6
// Before
public void foo() {
switch(Ticket.type) {
// ..
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// After
public interface LottoNumberGeneratable {
List<LottoTicket> generate(int numberCount);
}
public class AutoLottoNumberGenerator implements LottoNumberGeneratable {
@Override
public List<LottoTicket> generate(int numberCount) {
// ..
}
}
public class ManualLottoNumberGenerator implements LottoNumberGeneratable {
@Override
public List<LottoTicket> generate(int numberCount) {
// ..
}
}
성능 저하와 리팩터링
: 책에서는 일단 성능 저하를 고려하지 않고 리팩터링하는 것을 권장한다. 예시를 보니 그 과정에서 해결되는 경우도 더러 있고 실제로 성능차이는 미미해보인다. 성능이 중요한 곳이라면 리팩터링 후 성능차이를 분석해보고 성능을 높여나가는 방향이 좋을 것 같다.
- 시스템에 대해 잘 알더라도 대부분의 추측은 틀린 경우가 많을 것이기 때문에 프로파일링을 통해 정확한 성능 측정이 필요하다.
책 예시의 성능저하
- 인라인 하기: 재사용이 있더라도 immutable한 변수인 경우에 인라인해 임시 변수를 없애는 것을 예시로 든다.
- 코드 슬라이딩: 관련 코드를 한군데로 뭉치기 위해 중첩문을 나누었을 때 수행되는 for loop 수가 증가함에도 코드를 슬라이딩하고, 결국 메서드 분리를 한다.
Reference)
리팩터링 2판