소프트웨어 개발이 진행되면서 코드의 복잡도는 점점 증가하게 됩니다. 특히 Java와 같은 객체지향 언어에서는 유지보수성이 높은 코드를 작성하는 것이 필수적입니다. 코드가 점점 커지면서 복잡성이 증가하면 유지보수, 기능 추가, 버그 수정 등이 점점 더 어려워집니다. 이런 문제를 해결하기 위한 필수적인 작업이 바로 리팩터링입니다. 이번 블로그에서는 “Java Code 리팩터링의 기본 전략”을 살펴보겠습니다.
리팩터링이란 무엇인가?
리팩터링(Refactoring)은 기존 코드의 기능은 유지하면서 코드의 구조를 개선하는 작업을 의미합니다. 리팩터링의 주된 목적은 코드를 더 간결하고, 읽기 쉬우며, 유지보수가 용이하도록 만드는 것입니다.
Martin Fowler는 “컴퓨터가 이해할 수 있는 코드는 바보라도 짤 수 있다. 유능한 프로그래머는 인간이 이해할 수 있는 코드를 짠다.”라고 말했습니다. 이 말처럼 코드의 가독성은 매우 중요한 요소입니다. 유능한 개발자는 단순히 기능을 구현하는 데 그치지 않고, 다른 사람도 쉽게 이해할 수 있는 코드를 작성하려고 노력해야 합니다.
리팩터링의 핵심 전략
리팩터링은 다양한 기법과 원칙을 통해 이루어질 수 있지만, 그 중에서도 가독성과 유지보수성을 높이는 데 중점을 두고 몇 가지 중요한 전략을 소개하겠습니다.
1. 메서드 추출 (Extract Method)
코드가 길어지면 한 메서드가 너무 많은 일을 하게 되고, 그 결과 책임이 분산되어 버립니다. 메서드가 여러 기능을 담당하게 되면 이를 테스트하기 어렵고, 코드의 흐름을 이해하는 데에도 시간이 걸립니다. 이를 해결하기 위한 첫 번째 리팩터링 기법이 바로 메서드 추출입니다.
메서드 추출의 예:
java
// 고객 정보를 확인
verifyCustomerInfo(order.getCustomer());// 결제 처리
processPayment(order.getPaymentInfo());// 배송 정보 저장
saveShippingDetails(order.getShippingInfo());
이 코드는 3가지 서로 다른 기능을 수행하고 있습니다. 각각의 기능을 별도의 메서드로 추출하면 더 읽기 쉽고, 테스트하기도 쉬운 코드로 변환됩니다.
이처럼 15줄 이내의 메서드를 유지하면서, 각 메서드가 하나의 책임만을 가지도록 하는 것이 중요합니다. 메서드가 너무 많은 역할을 맡으면 가독성은 물론 유지보수성도 떨어지게 됩니다.
2. 메서드를 메서드 객체로 대체 (Replace Method with Method Object)
때로는 메서드가 지나치게 커지고 복잡해져서 하나의 메서드로 해결할 수 없을 때도 있습니다. 이 경우에는 해당 메서드를 메서드 객체로 대체하는 방법이 유효합니다. 즉, 복잡한 로직을 처리하기 위한 별도의 클래스를 만들어 메서드를 옮기고, 더 작은 단위로 분리하는 것입니다.
메서드 객체로 대체 예:
public void process(Order order) {
// 복잡한 로직
}
}
위의 코드를 리팩터링하여 OrderProcessor라는 클래스를 만들고, 이를 통해 복잡한 메서드를 객체로 대체할 수 있습니다. 이처럼 메서드를 객체로 분리함으로써 더 유연하고 확장 가능한 코드를 만들 수 있습니다.
3. 분기점 개수 제한 (Limit Branching Points)
코드에서 **분기점(Branching Point)**이 많아지면, 코드의 복잡도는 기하급수적으로 증가합니다. 예를 들어,
,
,
,
과 같은 분기문이 너무 많아지면 코드 흐름을 파악하기가 어려워지고, 버그 발생 확률도 증가합니다. 이를 방지하기 위해 맥케이브 순환 복잡도를 참고하여 분기점의 개수를 4개로 제한하는 것이 좋습니다.
예:
// 처리
} else if (condition2) {
// 처리
} else if (condition3) {
// 처리
} else {
// 처리
}
이런 식으로 분기점이 4개 이상 넘어가게 되면, 코드가 복잡해지고 관리하기 어려워지므로 분기문을 줄이거나, 전략 패턴(Strategy Pattern) 같은 디자인 패턴을 적용해 복잡도를 줄이는 것이 좋습니다.
4. 중복 코드 제거
코드에서 중복 코드는 악취를 풍기는 주요 원인 중 하나입니다. 같은 로직이 여러 군데에 중복되면, 수정이 필요할 때마다 여러 위치에서 동일한 작업을 해야 하므로 유지보수 비용이 증가합니다. 따라서 중복된 코드를 하나의 메서드로 추출하여 재사용 가능하도록 만드는 것이 좋습니다.
중복 코드 제거 예:
</h4>
public void createUser(String name, String email) {
if (name == null || email == null) {
throw new IllegalArgumentException("Name and email cannot be null");
}
// 유저 생성 로직
}public void updateUser(String name, String email) {
if (name == null || email == null) {
throw new IllegalArgumentException("Name and email cannot be null");
}
// 유저 업데이트 로직
}
public void createUser(String name, String email) {
if (name == null || email == null) {
throw new IllegalArgumentException("Name and email cannot be null");
}
// 유저 생성 로직
}public void updateUser(String name, String email) {
if (name == null || email == null) {
throw new IllegalArgumentException("Name and email cannot be null");
}
// 유저 업데이트 로직
}
위의 코드에서는
과
을 확인하는 코드가 중복되어 있습니다. 이를 메서드로 추출하여 코드 중복을 제거할 수 있습니다.
if (name == null || email == null) {
throw new IllegalArgumentException("Name and email cannot be null");
}
}public void createUser(String name, String email) {
validateUserInput(name, email);
// 유저 생성 로직
}public void updateUser(String name, String email) {
validateUserInput(name, email);
// 유저 업데이트 로직
}
이처럼 중복 코드는 제거하여, 유지보수성을 향상시키는 것이 좋습니다.
결론
Java 리팩터링의 핵심은 책임을 분리하고, 가독성을 높이며, 확장성과 유지보수성을 고려하는 것입니다. 복잡한 코드일수록 잘게 나누고, 중복을 제거하며, 분기점을 줄이는 것이 중요합니다. 이를 통해 더 효율적이고 읽기 쉬운 코드를 만들 수 있으며, 나아가 팀원들과의 협업에서도 큰 이점을 얻게 됩니다.
코드의 확장성과 간결함 사이에서 균형을 찾는 것은 개발자의 몫입니다. 그렇지만 중복을 최소화하고, 가독성을 높이려는 노력은 결코 소홀히 할 수 없습니다.