테스트 케이스를 모두 작성했다면 코드와 클래스를 정리해도 괜찮음. (점진적인 리팩토링)
새로 추가하는 코드가 설계 품질을 낮추는가?
→ 깔끔히 정리 후 테스트 케이스를 돌려 기존 기능을 깨뜨리지 않았음을 확인
리팩토링 단계에서 적용할 수 있는 기법들
중복은 추가 작업, 추가 위험, 불필요한 복잡도를 뜻한다.
중복 대응 유형
각 메서드를 따로 구현하는 방법도 있음.
int size() {} // 개수를 int로 반환
boolean isEmpty() {} // boolean 반환
통합하여 구현하면 코드를 중복해 구현할 필요가 없어진다.
boolean isEmpty() {
return 0 == size();
}
소규모 재사용 (단 몆 줄이라도 중복을 제거하겠다는 의지가 필요하다.)
public void scaleToOneDimension(float desiredDimension, float imageDimension) {
if (Math.abs(desiredDimension - imageDimension) < errorThreshold)
return;
float scalingFactor = desiredDimension / imageDimension;
scalingFactor = (float) (Math.floor(scalingFactor * 100) * 0.01f);
RenderedOp newImage = ImageUtilities.getScaledImage(
image, scalingFactor, scalingFactor);
image.dispose();
System.gc();
image = newImage;
}
public synchronized void rotate(int degrees) {
RenderedOp newImage = ImageUtilities.getRoatedImage(
image, degrees);
image.dispose();
System.gc();
image = newImage;
}
scaleToOneDimension 메서드와 rotate 메서드의 일부 코드가 동일한 상태
중복된 부분을 새 메서드로 뽑아 코드를 정리한다.
public void scaleToOneDimension(float desiredDimension, float imageDimension) {
if (Math.abs(desiredDimension - imageDimension) < errorThreshold)
return;
float scalingFactor = desiredDimension / imageDimension;
scalingFactor = (float) (Math.floor(scalingFactor * 100) * 0.01f);
replaceImage(ImageUtilities.getScaledImage(
image, scalingFactor, scalingFactor));
}
public synchronized void rotate(int degrees) {
replaceImage(ImageUtilities.getRoatedImage(
image, degrees));
}
private void replaceImage(RenderedOp newImage) {
image.dispose();
System.gc();
image = newImage;
}
공통적인 코드를 새 메서드로 뽑고 보니 클래스가 SRP를 위반한다.
'소규모 재사용'은 시스템 복잡도를 극적으로 줄여준다.
TEMPLATE METHOD 패턴의 활용 (고차원 중복을 제거할 목적으로 자주 사용하는 기법)
public class VacationPolicy {
public void accrueUSDivisionVacation() {
// 지금까지 근무한 시간을 바탕으로 휴가 일수를 계산하는 코드
// ...
// 휴가 일수가 미국 최소 법정 일수롤 만족하는지 확인하는 코드
// ...
// 휴가 일수를 급여 대장에 적용하는 코드
// ...
}
public void accrueEUDivisionVacation() {
// 지금까지 근무한 시간을 바탕으로 휴가 일수를 계산하는 코드
// ...
// 휴가 일수가 유럽연합 최소 법정 일수를 만족하는지 확인하는 코드
// ...
// 후가 일수를 급여 대장에 적용하는 코드
// ...
}
}
최소 법정 일수를 계산하는 코드만 제외하면 두 메서드는 거의 동일하다.
Template Method 패턴을 적용해 눈에 들어오는 중복을 제거
abstract public class VacationPolicy {
public void accrueVacation() {
// 지금까지 근무한 시간을 바탕으로 휴가 일수를 계산하는 코드
// 휴가 일수가 법정 일수롤 만족하는지 확인하는 코드
// 휴가 일수를 급여 대장에 적용하는 코드
calculateBaseVacationHours();
alterForLegalMinimums();
applyToPayroll();
}
private void calculateBaseVacationHours() { /* ... */}
abstract protected void alterForLegalMinimums();
private void applyToPayroll() {/* ... */}
}
public class USVacationPolicy extends VacationPolicy {
@Override
protected void alterForLegalMinimums() {
// 미국 최소 법정 일수를 사용한다.
}
}
public class EUVacationPolicy extends VacationPolicy {
@Override
protected void alterForLegalMinimums() {
// 유럽연합 최소 법정 일수를 사용한다.
}
}
정해진 개발 일정에 쫒기는 상황에서 작성하는 코드에 의도를 분명히 표현하려 추가적인 시간과 노력을 투자하는 것은 정말 어려운 일이라 느낍니다.
하지만 코드 작업을 진행하게 된다면 꼭! 최대한 다음을 염두하면서 작업해보도록 노력할 생각입니다.
- 좋은 이름을 고민하면서, 표준 명칭 사용을 우선적으로 하고
- method와 class가 갖는 책임의 크기를 최소화하려 고려해보고
- 단위 테스트를 통해 단위의 명세와 단위로의 리팩토링을 고려할 수 있다면
수정, 개선에 용이한 코드의 답을 찾을 수 있지 않을까 생각합니다. (경험을 쌓아가면서 이에 대한 저만의 철학을 갖고 싶은 욕심이 생깁니다.)