소스 코드 구현후 보통 main() 함수는 프로그래밍 실행 목적과, 동작 결과 확인을 위한 테스트 목적으로 나뉜다
[문제점]
<java />
public class Calculator {
int add(int i, int j) {
return i + j;
}
int subtract(int i, int j) {
return i - j;
}
int multiply(int i, int j) {
return i * j;
}
int divide(int i, int j) {
return i / j;
}
public static void main(String[] args) {
Calculator cal = new Calculator();
System.out.println(cal.add(3, 4));
System.out.println(cal.subtract(5, 4));
System.out.println(cal.multiply(2, 6));
System.out.println(cal.divide(8, 4));
}
}
프로덕션 코드와 테스트 코드가 같은 클래스에 위치하고 있다는 것
<java />
public class CalculatorTest {
public static void main(String[] args) {
Calculator cal = new Calculator();
System.out.println(cal.add(3, 4));
System.out.println(cal.subtract(5, 4));
System.out.println(cal.multiply(2, 6));
System.out.println(cal.divide(8, 4));
}
}
분리했지만 main() 메서드 하나에서 프로덕션 코드의 여러 메서드 동시 테스트하고 있음
→ 프로덕션 코드 복잡도 증가할시 main() 메서드도 복잡해짐
<java />
public class CalculatorTest {
public static void main(String[] args) {
Calculator cal = new Calculator();
add(cal);
subtract(cal);
multiply(cal);
divide(cal);
}
private static void divide(Calculator cal) {
System.out.println(cal.divide(9, 3));
}
private static void multiply(Calculator cal) {
System.out.println(cal.multiply(9, 3));
}
private static void subtract(Calculator cal) {
System.out.println(cal.subtract(9, 3));
}
private static void add(Calculator cal) {
System.out.println(cal.add(9, 3));
}
}
각 메소드별 분리하지만, 내가 구현하려는 메서드에만 집중하기에는 부적합하다(모두 Calculator클래스를 사용해야 함)
이러한 문제를 JUnit을 통해서 테스트 할수있게 되었다. JUnit까지 작성하게 된다면 너무 옆길로 새게 되어 따로 포스팅하고, 여기선 간단히 짚고 넘어간다.
JUnit : 단위 테스트 프레임워크 중 하나, main()메소드의 한계를 해결함
0.1. JUnit을 통해 테스트 케이스를 나누고 결과까지 요구사항대로 나온다면 개발을 완료한 것일까?
뭐 물론 근시안적으론 문제가 없겠지만, 좀더 나은, 읽기 좋은 코드를 작성하는 게 앞으로의 개발인생에 조금이나마 도움이 되지 않을까?
리팩토링을 통해 좀 더 좋은 코드를 만드는 데 있어서는
중복을 제거, 가독성을 높이고 유지보수를 편하게 구조를 변경해야 할 것이다
보통 리팩토링을 한다고 하면 지켜지는 조건이 있는데
- 메서드가 한 가지 책임만 가지도록 구현한다
- 인덴트(들여쓰기)가 깊이를 1단계로 유지한다
- else를 사용하지 마라
모든 코드를 위 원칙과 같이 지키며 구현할 수는 없을뿐더러 초보 개발자에게는 지금 당장의 코드 구현도 힘이 붙일 수 있다.
그렇다고 내 못생긴 코드 보고 멍 때리고만 있어서는, 계속 못생겨있을 뿐이다. 글을 쓸 때 처음부터 좋은 글을 쓰는 사람도 없지 않나
요구사항의 복잡도가 높을수록 리팩토링이 어려운 건 사실이다. 뭐 그렇다고 손 못 댈 정도는 아니다
요구사항 작은 단위로 나누기(잘 안된다? 연습하면 늘게 되어있다. 앞으로는 요구사항을 완료 후 리팩토링을 ‘시도’ 해보자)
※ 작업의 끝은 기대하는 결과를 확인 후 리팩토링까지 완료했을 때이다
1. 리팩토링
else 쓰지 않고 구현하기
<java />
public class StringCalculator {
public int add(String text) {
if (text == null || text.isEmpty()) {
return 0;
}
if (text.contains(",")) {
String[] values = text.split(",");
int sum = 0;
for(String value : values) {
sum += Integer.parseInt(value);
}
return sum; // if문으로 들어오면 더 밑으로 가지않고 반환됨
}
return Integer.parseInt(text); // if문을 빠져나가는 순간 여기로 옴
}
극단적인 리팩토링
<java />
public class StringCalculator {
public int add(String text) {
if (text == null || text.isEmpty()) {
return 0;
}
String[] values = text.split(",");
return sum(values);
}
private int sum(String[] values) {
int sum = 0;
for(String value : values) {
sum += Integer.parseInt(value);
}
return sum;
}
add() 메소드를 분리해 sum()메소드를 따로 만들었다.
더 할것은 없을까?
메서드는 한 가지 책임만 가져야 한다
<java />
public class StringCalculator {
public int add(String text) {
if (text == null || text.isEmpty()) {
return 0;
}
String[] values = text.split(",");
return sum(toInts(values);
}
// 더하기
private static int sum(int[] numbers) {
int sum = 0;
for(int number : numbers) {
sum += number;
}
return sum;
}
// 합
private int[] toInts(String[] values) {
int[] numbers = new int[values.length];
for(int i = 0; i < values.length; i++) {
int number[i] = Integer.parseInt(values[i]);
}
return numbers;
}
// 정수 변환
}
극단적으로 리팩터링 하게 되면 더 복잡해지지만 연습하기에는 좋다
리팩토링 후에는 public으로 공개하는 add() 메서드가 얼마나 읽기 쉽고, 좋은지를 봐야 한다
더더욱 극단적으로 리팩터링 하기

Alt + Ctrl + M 을 통해서 선택영역을 새로운 함수로 뺄 수가 있다(리팩터링 하기에도, 나누기에도 유용하다)
<code />
public int add(String text) {
if (isBlank(text)) {
return 0;
}
return sum(toInts(split(text)));
}
private static boolean isBlank(String text) {
return text == null || text.isEmpty();
}
요구사항이 변경되며 메서드 이름, 변수 이름을 변경하는 것도 중요한 리팩토링이다.
리팩토링 후에는 테스트코드를 통해서 바로바로 검증을 해줘야 한다. 테스트 코드 없이 수동으로 검증하게 되면 부담이 되어 테스트코드 작성이 꺼려진다
➕ 여기에 더해 테스트 코드와 정규 표현식은 연습해 두면 유용하게 사용할 수 있다
참조
- https://www.youtube.com/@javajigi 박재성님 유튜브