[인프런 워밍업 스터디 클럽] 0기 - 백엔드 과제 #5

[인프런 워밍업 스터디 클럽] 0기 - 백엔드 과제 #5

본 내용은 자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]를 듣고 작성한 글입니다.

 

5일차에는 클린코드에 대해서 배웠다. 클린한 코드가 왜 중요할까?

개발자들은 항상 요구사항을 구현할 뿐만 아니라 여러 명이서 협업을 하기 때문에 코드를 많이 읽는다. 이러한 과정에서 깔끔한 코드는 다른 사람을 위해서 혹은 나중에 다시 코드를 보고 판단해야 할 나를 위해서 필수적이다.

 

[과제에서 사용한 tool]

운영체제 : MacBook Air M1, 2020

Java : 17.0.9-amzn

IDE : IntelliJ Ultimate

과제


목표

주어진 코드를 클린하게 개선해보면서 클린코드에 대한 감을 익히기

 

[제시된 코드]

public class Main {

    public static void main(String[] args) {
        System.out.print("숫자를 입력하세요 : ");
        Scanner scanner = new Scanner(System.in);
        int a = scanner.nextInt();

        int r1 = 0, r2 = 0, r3 = 0, r4 = 0, r5 = 0, r6 = 0;

        for (int i = 0; i < a; i++) {
            double b = Math.random() * 6;
            if (b >= 0 && b < 1) {
                r1++;
            } else if (b >= 1 && b < 2) {
                r2++;
            } else if (b >= 2 && b < 3) {
                r3++;
            } else if (b >= 3 && b < 4) {
                r4++;
            } else if (b >= 4 && b < 5) {
                r5++;
            } else if (b >= 5 && b < 6) {
                r6++;
            }
        }

        System.out.printf("1번 눈금이 %d번 나왔습니다.\n", r1);
        System.out.printf("2번 눈금이 %d번 나왔습니다.\n", r2);
        System.out.printf("3번 눈금이 %d번 나왔습니다.\n", r3);
        System.out.printf("4번 눈금이 %d번 나왔습니다.\n", r4);
        System.out.printf("5번 눈금이 %d번 나왔습니다.\n", r5);
        System.out.printf("6번 눈금이 %d번 나왔습니다.\n", r6);
    }
}

 

[반복되는 로직 처리하기]

반복이 세 군데에서 일어나고 있다.

  • 변수 r1~r6

  • for문

  • 출력문

먼저, 변수 r1 ~ r6를 배열에 담아보자. 그러면서 동시에 변수명을 각각의 주사위 면이 나온 횟수 를 뜻하는 diceFaceCounts로 바꿔보자.

int[] diceFaceCounts = new int[6];

for (int diceFaceCount : diceFaceCounts) {
    diceFaceCount = 0;
}

그 다음에 for문 안에 특정하게 반복되는 숫자들의 패턴이 보인다. 인덱싱을 해줘서 for문을 만들어보자.

for (int i = 0; i < a; i++) {
    double b = Math.random() * 6;
    for (int j = 0; j < diceFaceCounts.length; j++) {
        if (b >= j && b < j + 1) {
            diceFaceCounts[j]++;
        }
    }
}

이어서 출력문도 1~6까지의 숫자들이 같은 패턴으로 나열되어 있다. 인덱싱을 해 간단하게 나타내주자.

for (int i = 0; i < diceFaceCounts.length; i++) {
    System.out.printf((i + 1) + "번 눈금이 %d번 나왔습니다.\n", diceFaceCounts[i]);
}

1차적으로 리팩토링한 코드는 다음과 같다.

public class Main1 {

    public static void main(String[] args) {
        System.out.print("숫자를 입력하세요 : ");
        Scanner scanner = new Scanner(System.in);
        int a = scanner.nextInt();

        int[] diceFaceCounts = new int[6];

        for (int diceFaceCount : diceFaceCounts) {
            diceFaceCount = 0;
        }

        for (int i = 0; i < a; i++) {
            double b = Math.random() * 6;
            for (int j = 0; j < diceFaceCounts.length; j++) {
                if (b >= j && b < j + 1) {
                    diceFaceCounts[j]++;
                }
            }
        }

        for (int i = 0; i < diceFaceCounts.length; i++) {
            System.out.printf((i + 1) + "번 눈금이 %d번 나왔습니다.\n", diceFaceCounts[i]);
        }
    }
}

 

[메서드로 기능 분리하기]

다음은 이 코드들이 무엇을 하는 건지 명확하게 표현이 되어 있지 않다. 이러한 점을 보완하기 위해 메서드로 코드들을 나눠보자.

Scanner scanner = new Scanner(System.in);
int a = scanner.nextInt(); 

숫자를 입력받는 코드이다.

  • 메서드명 : inputNumber()

  • 반환타입 : int

  • 파라미터 : X

public static int inputNumber() {
    Scanner scanner = new Scanner(System.in);
    return scanner.nextInt();
}

그 다음으로 주사위에 면을 생성하는 로직이다.

int[] diceFaceCounts = new int[6];

for (int diceFaceCount : diceFaceCounts) {
    diceFaceCount = 0;
}
  • 메서드명 : createDiceFaceCounts()

  • 반환타입 : int[]

  • 파라미터 : int faceNumbers

public static int[] createDiceFaceCounts(int faceNumbers) {
    int[] diceFaceCounts = new int[faceNumbers];

    for (int diceFaceCount : diceFaceCounts) {
        diceFaceCount = 0;
    }
    return diceFaceCounts;
}

그 다음엔 주사위면이 각각 몇 회씩 나왔는지 알려주는 로직이다. 2중 for문이라 한 번에 읽히지가 않는다. 기능 단위로 덩어리 지어보자.

for (int i = 0; i < a; i++) {
    double b = Math.random() * 6;
    for (int j = 0; j < diceFaceCounts.length; j++) {
        if (b >= j && b < j + 1) {
        diceFaceCounts[j]++;
        }
    }
}
b >= j && b < j + 1 // 제일 안쪽의 조건문인데 뭘 의미하는건지 구분이 안간다.
  • 메서드명 : isDiceNumber

  • 반환타입 : boolean

  • 파라미터 : int diceNumber, int i

public static boolean isDiceNumber(int diceNumber, int i) {
    return diceNumber >= i && diceNumber < i + 1;
}

두번째로는 randomDiceNumber와 알맞는 주사위면의 횟수를 올려주는 로직이다.

  • 메서드명 : increaseRollCounts

  • 반환타입 : void

  • 파라미터 : int randomDiceNumber, int[] diceFaceCounts

public static void increaseRollCounts(int randomDiceNumber, int[] diceFaceCounts) {
    for (int j = 0; j < diceFaceCounts.length; j++) {
        if (isDiceNumber(randomDiceNumber, j)) { // isDiceNumber메서드를 넣어줬다
            diceFaceCounts[j]++;
        }
    }
}

마지막으로 printRollCounts 메서드를 만들어보자.

public static void printRollCounts(int[] diceFaceCounts) {
    for (int i = 0; i < diceFaceCounts.length; i++) {
        System.out.printf((i + 1) + "번 눈금이 %d번 나왔습니다.\n", diceFaceCounts[i]);
    }
}

총 정리해보면 다음과 같다.

public class Main {

    public static void main(String[] args) {
        System.out.print("숫자를 입력하세요 : ");
        int number = inputNumber();
        int[] diceFaceCounts = createDiceFaceCounts(6);

        for (int i = 0; i < number; i++) {
            int randomDiceNumber = (int) (Math.random() * 6 + 1);
            increaseRollCounts(randomDiceNumber, diceFaceCounts);
        }

        printRollCounts(diceFaceCounts);
    }

    public static int inputNumber() {
        Scanner scanner = new Scanner(System.in);
        return scanner.nextInt();
    }

    public static int[] createDiceFaceCounts(int faceNumbers) {
        int[] diceFaceCounts = new int[faceNumbers];

        for (int diceFaceCount : diceFaceCounts) {
            diceFaceCount = 0;
        }
        return diceFaceCounts;
    }

    public static boolean isDiceNumber(int diceNumber, int i) {
        return diceNumber >= i && diceNumber < i + 1;
    }

    public static void increaseRollCounts(int randomDiceNumber, int[] diceFaceCounts) {
        for (int j = 0; j < diceFaceCounts.length; j++) {
            if (isDiceNumber(randomDiceNumber, j)) {
                diceFaceCounts[j]++;
            }
        }
    }

    public static void printRollCounts(int[] diceFaceCounts) {
        for (int i = 0; i < diceFaceCounts.length; i++) {
            System.out.printf((i + 1) + "번 눈금이 %d번 나왔습니다.\n", diceFaceCounts[i]);
        }
    }
}

 

하지만, 여전히 문제가 있다. 누가 이 메서들을 동작시키는 건지 명시가 안되어 있다. 이 문제를 class화 하여 해결할 수 있다. 이 때, 클래스는 의인화를 하여 마치 메서드를 하는 행위라고 생각하면 클래스 나누기가 수월하다.

  • OutputView class

    • printNumberInput()

    • printRollCounts()

  • InputView class

    • inputNumber()

  • Dice class

    • isDiceNumber()

  • Player class

    • createDiceFaceCounts()

  • Game class

    • increaceRollCounts()

    • incrementFaceCount()

public class InputView {

    public static int inputNumber() {
        Scanner scanner = new Scanner(System.in);
        return scanner.nextInt();
    }
}
public class OutputView {

    public static void printNumberInput() {
        System.out.print("숫자를 입력하세요 : ");
    }

    public static void printRollCounts(int[] diceFaceCounts) {
        for (int i = 0; i < diceFaceCounts.length; i++) {
            System.out.printf((i + 1) + "번 눈금이 %d번 나왔습니다.\n", diceFaceCounts[i]);
        }
    }
}
public class Dice {

    public static boolean isDiceNumber(int diceNumber, int i) {
        return diceNumber >= i && diceNumber < i + 1;
    }
}
public class Player {
    
    public static int[] createDiceFaceCounts(int faceNumbers) {
        int[] diceFaceCounts = new int[faceNumbers];

        for (int diceFaceCount : diceFaceCounts) {
            diceFaceCount = 0;
        }
        return diceFaceCounts;
    }
}
public class Game {

    public static void increaseRollCounts(int randomDiceNumber, int[] diceFaceCounts) {
        for (int j = 0; j < diceFaceCounts.length; j++) {
            if (Dice.isDiceNumber(randomDiceNumber, j)) {
                diceFaceCounts[j]++;
            }
        }
    }

    public static void incrementFaceCount(int number, int[] diceFaceCounts) {
        for (int i = 0; i < number; i++) {
            int randomDiceNumber = (int) (Math.random() * 6 + 1);
            Game.increaseRollCounts(randomDiceNumber, diceFaceCounts);
        }
    }
}
public class Main {

    public static void main(String[] args) {
        OutputView.printNumberInput();
        int number = InputView.inputNumber();
        int[] diceFaceCounts = Player.createDiceFaceCounts(6);
        Game.incrementFaceCount(number, diceFaceCounts);
        OutputView.printRollCounts(diceFaceCounts);
    }
}

 

하.지.만 객체는 상태(멤버 변수)와 행동(메서드)를 가지고, 객체 지향 프로그래밍은 이러한 객체들이 서로 협력하는 프로그래밍을 말한다. 따라서, 상태들을 추가해주고 인스턴스화한 객체가 할 수 있는 행동에 대해서는 static을 제거해준다.

댓글을 작성해보세요.

채널톡 아이콘