[인프런 워밍업 스터디 클럽] 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을 제거해준다.
댓글을 작성해보세요.