중복된 코드를 리팩토링(refactoring)하는 방법을 알아보자.
아래 예제 소스는 자바에서 '정수형 연산'과 '실수형 연산' 처리의 수행 시간 차이를 대략적으로 알아보기 위해 작성되었다.
잘 살펴보면 중복된 코드가 눈에 띈다. 수행 시간(elapsed time)을 계산하는 로직이 2번에 걸쳐 반복되어 코딩되어 있다.
import java.util.Calendar; public class DuplicatedCodes { private static long timeInMillis; /** * 정수형 및 실수형 연산 처리 속도를 비교하는 테스트 프로그램 * * @param args */ public static void main(String[] args) { long elapsedTime; timeInMillis = Calendar.getInstance().getTimeInMillis(); int intSum = 0; // 정수형 변수를 100만번 더한다. for (int i = 0; i < 1000000; i++) { intSum += 100; } elapsedTime = Calendar.getInstance().getTimeInMillis() - timeInMillis; System.out.println("정수형 : " + elapsedTime + "밀리초"); timeInMillis = Calendar.getInstance().getTimeInMillis(); float floatSum = 0; // 실수형 변수를 100만번 더한다. for (int i = 0; i < 1000000; i++) { floatSum += 100.0; } elapsedTime = Calendar.getInstance().getTimeInMillis() - timeInMillis; System.out.println("실수형 : " + elapsedTime + "밀리초"); } }
눈에 잘 띄는 중복 코드는 아래와 같다.
elapsedTime = Calendar.getInstance().getTimeInMillis() - timeInMillis;
리팩토링을 실시하기 전에 '중복된 코드를 리팩토링 해야 하는 이유는 무엇인가?' 라는 질문에 답해보자.
- 재사용성(reuseablity)가 떨어지기 때문이며, 동일한 코드를 반복해서 사용하는 것은 노동력이 많이 소모된다.
- 중복된 코드는 버그가 발견되거나 수정 사항이 발생할 때마다 고쳐야 하는 노력이 많이 든다.
(중복된 횟수 만큼의 수정 작업을 반복해야 한다.) - 여러 위치에 흩어져 있기 때문에 로직(logic)에 대한 문서화 혹은 설명을 작성하기 이렵다.
- 중복된 상태로 그대로 두었다가 누군가 흩어진 중복 코드 중에서 한 곳만 고쳐야 할 경우,
원래의 의도와 달리 처음에는 같은 기능을 하던 코드들이 다른 방식으로 동작하게 되는 문제가 발생할 수 있다.
중복된 코드를 리팩토링하는 것은 다음과 같은 장점을 가지고 있다.
- 다양한 리팩토링 기법 중에서 가장 우선순위를 높여서 해야 한다. (아무리 바쁘고 시간이 없더라도...)
- 리팩토링 기법 중에서 가장 효과가 좋다. (중복 코드 제거를 안한 코드의 유지보수, 장애 처리 비용은 상상을 초월한다.)
- 설계를 개선하고, 코드의 품질을 높이는 가장 빠르고, 확실한 방법이다. (개발자의 레벨이 상승한다.)
- 중복된 코드가 제거된 코드는 문서화 하기 용이하고, 코드 가독성(readability)가 향상될 가능성이 높다.
반면에 다음과 같은 단점도 있다. (과유불급, 지나치면 모자르니만 못하다.)
- 자칫하면 코드의 복잡도가 높아져 코드를 이해 하기 어렵게 된다.
- 중복 코드를 방지하는 과정에서 미약하나마 성능 저하가 발생할 수 있다.
- 지나치게 '중복 방지'에 집착하다보면, 제한된 시간과 노력을 낭비할 가능성이 있다.
(중복 코드가 많지 않거나, 아주 단순한 코드임에도 리팩토링을 하려고 애쓸 필요는 없다.)
중복된 코드를 제거하는 방법은 반복적인 코드 블럭을 함수(function or method)로 변경하는 것이다.
앞서 제시한 예제에서 중복된 코드를 리팩토링한 결과는 다음과 같다.
import java.util.Calendar; public class DuplicatedCodes { private static long timeInMillis; /** * 정수형 및 실수형 연산 처리 속도를 비교하는 테스트 프로그램 * * @param args */ public static void main(String[] args) { timeInMillis = Calendar.getInstance().getTimeInMillis(); int intSum = 0; // 정수형 변수를 100만번 더한다. for (int i = 0; i < 1000000; i++) { intSum += 100; } System.out.println("정수형 : " + calcElapseTime() + "밀리초"); float floatSum = 0; // 실수형 변수를 100만번 더한다. for (int i = 0; i < 1000000; i++) { floatSum += 100.0; } System.out.println("실수형 : " + calcElapseTime() + "밀리초"); } /** * 경과 시간을 계산한다. * * @return 경과된 시간을 밀리초(millseconds) 단위로 반환한다. */ private static long calcElapseTime() { long currentTimeMillis = Calendar.getInstance().getTimeInMillis(); long elapsedTime = currentTimeMillis - timeInMillis; timeInMillis = currentTimeMillis; return elapsedTime; } }
위와 같이 코드를 변경했을 때, 어떤 효과를 기대할 수 있을까? 혹은 무엇이 좋아진 것일까?
- 요건 혹은 기능이 변경되었을 때, 손쉽게 고칠 수 있다. (예를 들어, 수행 시간을 초 단위로 출력하게끔 바꾸어야 하는 경우)
- 중복된 코드 블록을 함수로 모듈화하면서, 코드 길이가 짧아진다.
- 함수 명칭을 통해 기능을 파악할 수 있으므로, 로직을 파악하기 쉬워지고,
함수에 주석을 추가하는 방식으로 문서화를 할 수 있다.