Prime Number(소수)를 출력하는 함수에 대한 리팩토링을 한다.
이번 포스팅에 리팩토링할 코드이다.
문제점을 살펴 보면
1. 하나의 메소드가 너무 많은 역할을 한다.
2. 하나의 클래스 역시 너무 많은 역할을 한다.
3. 변수명에서 기능을 유추할 수 없다.
4. 인덴트가 어마무시하다.
일단 리팩토링하기 앞서서 리팩토링을 하는 도중 기능이 변경이 생기지 않도록 테스트 코드를 작성한 후 진행하자.
Rename
package function;
public class PrintPrimes {
public static void main(String[] args) {
final int numberOfPrimes = 1000;
final int linesPerPage = 50;
final int columns = 4;
final int ordmax = 30;
int primes[] = new int[numberOfPrimes + 1];
int pagenumber;
... 이하 생략
3번 문제를 해결 하기 위해 서술적인 변수명을 이용했다.
Extreact Method Object
그 다음 Extract Method Object로 메소드를 클래스로 분리 시키자.
이 때, 기준은 어떤 함수가 일련의 변수들을 가지고 있을 때 하나 이상의 클래스가 숨어있다는 것을 전제하에 시작된다.
private static class PrimePrinterHelper {
private final int numberOfPrimes = 1000;
private final int linesPerPage = 50;
...
private int multiples[] = new int[ordmax + 1];
public void invoke() {
candidate = 1;
primeIndex = 1;
primes[1] = 2;
ord = 2;
square = 9;
while (primeIndex < numberOfPrimes) {
do {
....
{
pagenumber = 1;
pageoffset = 1;
while (pageoffset <= numberOfPrimes) {
System.out.println("The First " + numberOfPrimes +
" Prime Numbers --- Page " + pagenumber);
System.out.println("");
for (rowoffset = pageoffset; rowoffset < pageoffset + linesPerPage; rowoffset++) {
for (column = 0; column < columns; column++)
if (rowoffset + column * linesPerPage <= numberOfPrimes)
System.out.format("%10d", primes[rowoffset + column * linesPerPage]);
System.out.println("");
}
System.out.println("\f");
pagenumber = pagenumber + 1;
pageoffset = pageoffset + linesPerPage * columns;
}
}
Extract Method
이 때 { } 부분은 prime된 결과를 출력하는 메소드로 분리 할 수 있을 것 같다.
private void invoke() {
...
printNumber();
}
private void printNumber() {
pagenumber = 1;
pageoffset = 1;
...
pageoffset = pageoffset + linesPerPage * columns;
}
}
main 메소드의 단계를 나누어 보자
public static void main(String[] args) {
PrimePrinterHelper primePrinterHelper = new PrimePrinterHelper();
int [] primes = primePrinterHelper.invoke();
primePrinterHelper.printNumber(primes);
이 때 invoke는 void가 아닌 primes을 리턴하는 메소드로 만들었다.
이유는 printNumber 메소드에서 값은 invoke에서 변경되는데, 값을 공유 한다면 클래스 분리를 한다거나 할 때, 지나치게 연관성이 깊어지기 때문이다.
Method Local Variable
그러고 보니 나서 보니, printNumber에만 사용되는 클래스 변수들이 있다.
이것들은 printNumber에만 적용되게 바꾸어 주는게 좋다.
private static class PrimePrinterHelper {
private final int linesPerPage = 50;
private int pagenumber;
private int pageoffset;
private int column;
public int[] invoke() {
...
}
return primes;
}
public void printNumber(int[] primes) {
pagenumber = 1;
pageoffset = 1;
...
pageoffset = pageoffset + linesPerPage * columns;
}
}
}
}
위 변수들을 독립적으로 바꾸어 준다.
public class PrintPrimes {
static final int linePerPage = 50;
static final int columns = 4;
public static void main(String[] args) {
PrimePrinterHelper primePrinterHelper = new PrimePrinterHelper();
int [] primes = primePrinterHelper.invoke();
primePrinterHelper.printNumber(primes, linePerPage, columns);
}
public void printNumber(int[] primes, int linesPerPage, int column) {
int pagenumber = 1;
int pageoffset = 1;
while (pageoffset <= numberOfPrimes) {
System.out.println("The First " + numberOfPrimes +
" Prime Numbers --- Page " + pagenumber);
System.out.println("");
for (rowoffset = pageoffset; rowoffset < pageoffset + linesPerPage; rowoffset++) {
for (column = 0; column < columns; column++)
if (rowoffset + column * linesPerPage <= numberOfPrimes)
System.out.format("%10d", primes[rowoffset + column * linesPerPage]);
System.out.println("");
}
System.out.println("\f");
pagenumber = pagenumber + 1;
pageoffset = pageoffset + linesPerPage * columns;
}
}
이 때, 변하는 값은 파라미터로, 절때 변하지 않는 값은 지역변수로 정의한다.
이러고 나니 PrintPrimes은 PrimePrinterHelper와 독립적이게 됬다.
이는 클래스로 바꿀수 있다는 의미이고, 변수 몇가지를 더 파라미터로 추가해 클래스를 나누었다.
private class PrimePrinterHelper {
private void printNumbers(int[] numbers, int numberOfPrimes, int linesPerPage, int columns) {
new NumberPrinter(numbers, numberOfPrimes, linesPerPage, columns).invoke();
}
}
package function;
public class NumberPrinter {
private int[] numbers;
private int numberOfPrimes;
private int linesPerPage;
private int columns;
public NumberPrinter(int[] numbers, int numberOfPrimes, int linesPerPage, int columns) {
this.numbers = numbers;
this.numberOfPrimes = numberOfPrimes;
this.linesPerPage = linesPerPage;
this.columns = columns;
}
public void invoke() {
int pagenumber = 1;
int pageoffset = 1;
while (pageoffset <= numberOfPrimes) {
System.out.println("The First " + numberOfPrimes +
" Prime Numbers --- Page " + pagenumber);
System.out.println("");
for (int rowoffset = pageoffset; rowoffset < pageoffset + linesPerPage; rowoffset++) {
for (int column = 0; column < columns; column++)
if (rowoffset + column * linesPerPage <= numberOfPrimes)
System.out.format("%10d", numbers[rowoffset + column * linesPerPage]);
System.out.println("");
}
System.out.println("\f");
pagenumber = pagenumber + 1;
pageoffset = pageoffset + linesPerPage * columns;
}
}
}
이 때 numbers와 numberOfPrimes는 함수를 부를 때 마다 변경되는 값이다.
그렇기 때문에, 생성자 보단 invoke()의 파라미터로 바꿔 주는게 좋겠다.
package function;
public class NumberPrinter {
private int linesPerPage;
private int columns;
public NumberPrinter(int linesPerPage, int columns) {
this.linesPerPage = linesPerPage;
this.columns = columns;
}
public void invoke(int[] numbers, int numberOfPrimes) {
int pagenumber = 1;
int pageoffset = 1;
while (pageoffset <= numberOfPrimes) {
System.out.println("The First " + numberOfPrimes +
" Prime Numbers --- Page " + pagenumber);
System.out.println("");
for (int rowoffset = pageoffset; rowoffset < pageoffset + linesPerPage; rowoffset++) {
for (int column = 0; column < columns; column++)
if (rowoffset + column * linesPerPage <= numberOfPrimes)
System.out.format("%10d", numbers[rowoffset + column * linesPerPage]);
System.out.println("");
}
System.out.println("\f");
pagenumber = pagenumber + 1;
pageoffset = pageoffset + linesPerPage * columns;
}
}
}
이런 식으로 계속해서 반복해서 줄여나가면 된다.
보통은 Extract Till you Drop 방식으로 하라고 하는데
1. 더 이상 extract 할 수 없을 떄까지
2. 4줄 이내의 함수들로만 구성된 클래스
3. if wihle문에서 {}가 보인다면 extract 대상
4. {}는 extract할 기회
라고 한다.
가끔 보자.
'TDD, CleanCode' 카테고리의 다른 글
클린코더스 강의 리뷰 3. Function Structure(2) (0) | 2019.05.10 |
---|---|
클린코더스 강의 리뷰 3. Function Structure (0) | 2019.05.08 |
[TDD]테스트 주도 개발 예제(Money) - KentBeck (0) | 2019.05.06 |
클린코더스 강의 리뷰 2. Function (0) | 2019.04.22 |
클린코더스 강의 리뷰 1. OOP (0) | 2019.04.22 |