본문 바로가기

강의/TDD, Clean Code with Java 12기

3단계 자동차경주 - 피드백반영 (6일차, 7일차)

피드백 1

IllegalArgumentException은 RuntimeException 이라 굳이 throws 하지 않으셔도 됩니다 


이렇게 명시적으로 throws를 하시면 이 method를 호출하는 곳에서는
try ~ catch를 통해 반드시 Exception을 처리해야 합니다

물론 일부러 강제 하는 의미도 있겠지만, 어짜피 입력 값이 잘못 되서 실행을 종료해야 하는 케이스라면
throws 안하셔도 될 것 같습니다
Exception을 반드시 명시하면 method를 호출하는 쪽에서 주의를 할 수는 있지만
대신 항상 try catch를 통해 Exception에 대한 처리를 해야 하기 때문에 불편한 부분도
있고 호출한 쪽으로 예외상황에 대한 처리를 위임하는
모습이라 구현 난이도가 복잡해질 수도 있습니다 

이 부분은 참고해주시면 감사하겠습니다 


도움 되실만한 내용 공유 드립니다 

https://cheese10yun.github.io/checked-exception/

 

피드백 반영 및 알게된 내용 정리

 

Throwable을 상속받는 클래스는 Error와 Exception이 있다.

Exception은 다시 Checked Exception과 Unchecked Exception으로 나눠진다.

 

Error

    - 프로그램 코드에 의해서 해결 할 수 없는 심각한 오류

 

Checked Exception (컴파일 에러)

    - 문법, 자료형 등이 일치하지 않을 떄 발생하는 에러

    - 반드시 예외를 처리해야 함.

    - roll-back 하지 않음.

 

Unchecked Exception (런타임 에러)

    - 컴파일은 완료하였지만, 실행도중에 발생하는 오류

    - 명시적인 처리를 강제하지 않음.

    - roll-back 함.

 

Unchecked exception 종류는 5가지로 나뉘어진다.

    - ClassCastException: 객체 형변환을 잘못한 경우

    - ArithmeticException: 0으로 나누는 경우

    - NeagativeArraySizeException: 배열의 크기가 음수인 경우

    - NullPointerException: null객체에 접근하는 경우

    - IndexOutofBoundException: 인덱스의 범위를 벗어난 경우

 

수업을 듣기전에 Exception에 대해서 공부를 해봤었는데 내것이 아니었나보다. 

https://daram.tistory.com/23?category=953984 

제대로된 이해를 하지 못한 것 같다. throw new 예외가 나타날 경우 무조건 throws를 통해 상위단계로 넘겨주거나 try except를 이용해서 해당 메소드에서 처리를 해야 하는 줄 알았는데, Runtime excpetion 과 같은경우에는 명시적인 처리를 강제하지 않는다는 것을 알게 되었다.

 

이에 따라 코드를 아래와 같이 수정해보았다.

수정 전 코드 : 주석처리

 

package racing;

public enum RacingCarEnumInputCheck {
    NUM_OF_CAR(5,"자동차의 수가 너무 적거나 많습니다. (range: 1~5)"),
    CYCLE_OF_RACING(10, "시도할 회수가 너무 적거나 많습니다. (range: 1~10)");

    private final int threthold;
    private final String errorMessage;

    RacingCarEnumInputCheck(int threthold, String errorMessage) {
        this.threthold = threthold;
        this.errorMessage = errorMessage;
    }

    //public void validInputCheck(int cnt) throws IllegalArgumentException {
    public void validInputCheck(int cnt) {
        if (0 >= cnt || threthold < cnt ) {
            throw new IllegalArgumentException(errorMessage);
        }
    }
}
package racing;

import java.util.Scanner;

public class RacingCarInput {
    public int[] requestInput() {
        int numOfCar;
        int numOfCycle;

        Scanner sc = new Scanner(System.in);
        System.out.println("자동차 대수는 몇 대 인가요?");
        numOfCar = sc.nextInt();
        RacingCarEnumInputCheck.valueOf("NUM_OF_CAR").validInputCheck(numOfCar);
        System.out.println("시도할 회수는 몇 회 인가요?");
        numOfCycle = sc.nextInt();
        RacingCarEnumInputCheck.valueOf("CYCLE_OF_RACING").validInputCheck(numOfCycle);
        return new int[] {numOfCar, numOfCycle};
    }
}

피드백 2

 

Enum은 상수 성격에 가깝기 때문에
Enum을 가지고 "어떻게 사용할 지"는
도메인의 역할로 보는게 더 맞지 않나 싶습니다 😅

자동차 대수에 대한 검증이나 시도할 회수에 대한 검증은
자동차 게임에 한정된 조건들이므로 그 쪽으로 위임해보시는게 좋을 것 같습니다 😄

이 부분도 개선 검토 해주시면 감사하겠습니다 🙇

Enum에 대해서 좀 더 기본적인 내용이 잘 정리되어있어서 공유 드립니다 🙇
도움 되셨으면 좋겠습니다 🙏

https://www.nextree.co.kr/p11686

 

 


피드백 3

 

매번 호출 할 때 마다 객체 생성 및 초기화를 하고 있는데요 😅
System Input 자원도 아낄 겸 정적 상수로 선언하시면 좋을 것 같습니다 😄

final, static 한번 정리하고 가시면 좋겠습니다 😄

https://djkeh.github.io/articles/Why-should-final-member-variables-be-conventionally-static-in-Java-kor/

수정 전 코드 : 주석처리

 

 

피드백  3 반영 및 알게된 내용 정리

감사합니다. 좋은 말씀이신 것 같습니다.

numOfCar, numOfCycle은 변하지 않는 값이며, 인스턴스를 생성할 때 한 번만 초기화하고 쭉 변화 없이 사용할 내용이기 때문에 static final 접근제한자를 써 주는 것이 좋을 것 같다는 의견에 완전 공감합니다.

아래와 같이 코드를 수정하도록 하겠습니다.

 

 

 

추가적으로 궁금한 부분이 있습니다.

수정전 코드에서는 RacingCarInput.java, RacingCarOperator.java 는 각각 한번만 호출을 하고있습니다.

그렇기 때문에 지금 코드에 한해서는 static final을 붙여주기 전 후 시스템 효율성 차이는 없는게 맞을까요??


피드백 4

System.out.println("자동차 대수는 몇 대 인가요?"); 
numOfCar = sc.nextInt(); 
RacingCarEnumInputCheck.valueOf("NUM_OF_CAR").validInputCheck(numOfCar);

계속 반복 되고 있는 부분입니다 😄
method로 추출해서 반복을 줄이시죠 😄
이 부분도 개선 검토 해주시면 감사하겠습니다 🙇

 


피드백 5

코드 컨벤션 한번 확인 부탁 드립니다 🙇

private void InitCarLocation() {
private void initCarLocation() {
    
private void MoveCarLocation() {
private void moveCarLocation() {
    
private void MoveCarLocation() {
private void moveCarLocation() {

 

코드 컨벤션을 매일 살펴보며 익히도록 하겠습니다.

[메서드의 이름은 동사이어야 하며, 복합 단어일 경우 첫 단어는 소문자로 시작하고 그 이후에 나오는 단어의 첫 문자는 대문자로 사용해야 한다.]

[참고]

https://google.github.io/styleguide/javaguide.html

https://myeonguni.tistory.com/1596

 

 

 

 


피드백 6

친절한 주석 좋습니다 👍
하지만 이번과정에서는 주석이 없어도 잘 읽히는 코드가 되도록 연습해보시면 어떨까요?? 🤔 😄

도움 되실 만한 내용 공유 드립니다 🙇

https://o-o-wl.tistory.com/34

 


피드백 7

 

RacingCarOperator에 너무 많은 역할이 있습니다 😄
출력도 담당하고, 자동차 경주에 대한 운영도 담당하고, 자동차 자체에 대한 부분도 담당하고 😄
일단 RacingCarOperator의 무거운 어깨를 좀 가볍게 해주시는건 어떨까요?? 😄

자동차게임, 자동차, 경기결과 출력
일단 이렇게만이라도 도메인을 정리하시고 서로 역할을 나눠 가지면
좀 더 도메인들 간에 응집성이나, 재사용성을 높일 수 있을 것 같습니다 😄

MVC 패턴 한번 정리하고 가시면 좋을 것 같습니다 😄

https://m.blog.naver.com/jhc9639/220967034588

 


피드백 8

 

 

RacingCarOperator에 너무 많은 역할이 있습니다 😄
출력도 담당하고, 자동차 경주에 대한 운영도 담당하고, 자동차 자체에 대한 부분도 담당하고 😄
일단 RacingCarOperator의 무거운 어깨를 좀 가볍게 해주시는건 어떨까요?? 😄

자동차게임, 자동차, 경기결과 출력
일단 이렇게만이라도 도메인을 정리하시고 서로 역할을 나눠 가지면
좀 더 도메인들 간에 응집성이나, 재사용성을 높일 수 있을 것 같습니다 😄

MVC 패턴 한번 정리하고 가시면 좋을 것 같습니다 😄

https://m.blog.naver.com/jhc9639/220967034588

 

 


멘토님의 피드백을 받았을 때 어떤 의도로 말씀하시는지 원하는 내용이 무엇인지는 이해가 갔다.

하지만 그것을 내 코드에 반영하여 수정을 하려고 하니 막막하였다.

계속된 수정 끝에, 결국 코드를 처음부터 다시 짜는게 더욱 깔끔할 것 같았다.

 

1. MVC 패턴을 적용하여 코드를 짜볼 것

    - 적어도 자동차게임, 자동차, 경기결과 출력을 나눠서 코드를 작성하자

2. 코드 컨벤션을 준수할 것

3. static final과 같은 접근제한자를 적절한 위치에 사용할 것

4. Runtime error와 같이 unchecked error는 throws를 사용하는 것을 지양할 것

위 4가지를 생각하면 코드를 짜보기로 하였다.

 

main

import racing.RacingCarInput;
import racing.RacingCarOperator;
import step3.controller.RaceManager;
import step3.view.InputView;
import step3.view.ResultView;

import java.util.Scanner;

public class RacingCarMain {
    static int numOfCar;
    static int numOfCycle;

    public static void main(String[] args) {
        RacingCarMain rcm = new RacingCarMain();
        rcm.prepareInput();
    }

    private void prepareInput() {
        InputView iv = new InputView();
        numOfCar = iv.getNumOfCar();
        numOfCycle = iv.getNumOfCycle();

        RaceManager rm = new RaceManager(numOfCar, numOfCycle);
        raceStart(rm);
    }

    private void raceStart(RaceManager rm) {
        rm.inputCheck();
        rm.makeCarAry();
        rm.raceByCycle();
    }


}

controller

package step3.controller;

import step3.model.Car;

public class CarManger {
    private static final int MIN_ROUND = 0;
    private static final int MAX_ROUND = 10;
    private static final String CAR_ERROR_MESSAGE = "자동차 대수는 1대 이상이어야 합니다.";
    Car car;

    public CarManger() {
        this.car = new Car();
    }

    public void moveForward() {
        car.position++;
    }

    public static void inputCarCheck(int round) {
        if (round <= MIN_ROUND) {
            throw new IllegalArgumentException(CAR_ERROR_MESSAGE);
        }
    }
}
package step3.controller;

public class CycleManager {
    private static final int MIN_ROUND = 0;
    private static final int MAX_ROUND = 10;
    private static final String CYCLE_ERROR_MESSAGE = "시도할 횟수는 0보다 커야 합니다.";

    public static void inputRoundCheck(int round) {
        if (round <= MIN_ROUND) {
            throw new IllegalArgumentException(CYCLE_ERROR_MESSAGE);
        }
    }
}
package step3.controller;

import step3.model.Car;
import step3.view.ResultView;

public class RaceManager {
    private static final int THRETHOLD = 4;
    private static final int RAND_THRETHOLD = 10;
    private final int NUM_OF_CAR;
    private final int NUM_OF_CYCLE;

    public Car[] cars;

    public RaceManager(int NUM_OF_CAR, int NUM_OF_CYCLE) {
        this.NUM_OF_CAR = NUM_OF_CAR;
        this.NUM_OF_CYCLE = NUM_OF_CYCLE;
        cars = new Car[NUM_OF_CAR];
    }
    public void inputCheck() {
        CarManger.inputCarCheck(NUM_OF_CAR);
        CycleManager.inputRoundCheck(NUM_OF_CYCLE);
    }

    public Car[] makeCarAry() {
        System.out.println(NUM_OF_CAR);
        for (int i = 0; i < NUM_OF_CAR; i++) {
            cars[i] = new Car();
        }
        return cars;
    }

    public void raceByCycle() {
        for (int i = 0; i <= NUM_OF_CYCLE; i++) {
            raceByCar();
        }
    }

    public void raceByCar() {
        ResultView rv = new ResultView();
        for (int carNumber = 0; carNumber < NUM_OF_CAR; carNumber++) {
            rv.showCarLocation(cars[carNumber]);
            randMove(carNumber);
        }
        System.out.println();
    }

    public void randMove(int carNumber) {
        int rdNum = RandomNumber.getRandomNumber(RAND_THRETHOLD);
        if (rdNum >= THRETHOLD) {
            cars[carNumber].position++;
        }
    }
}
package step3.controller;

import java.util.Random;

public class RandomNumber {
    private static Random rd = new Random();

    public static int getRandomNumber(int bound) {
        return rd.nextInt(bound);
    }
}

model

package step3.model;

public class Car {
    public int position = 1;
}

view

package step3.view;

import java.util.Scanner;

public class InputView {
    private static final String HOW_MANY_CAR = "자동차의 대수는 몇 대 인가요?";
    private static final String HOW_MANY_CYCLE = "시도할 횟수는 몇 회 인가요?";
    private Scanner sc = new Scanner(System.in);

    public int getNumOfCar() {
        System.out.println(HOW_MANY_CAR);
        return getInput();
    }

    public int getNumOfCycle() {
        System.out.println(HOW_MANY_CYCLE);
        return getInput();
    }

    private int getInput() {
        return sc.nextInt();
    }
}
package step3.view;

import step3.model.Car;

public class ResultView {
    public void showCarLocation(Car car) {
        for (int i = 0; i < car.position; i++) {
            System.out.print('-');
        }
        System.out.println();
    }


}

 

정신없이 코드를 작성하였다.

테스트를 통과하겠다는 생각에 너무 다급하게 작성하여, 문제가 많은 것이 한눈에 보인다.

이대로는 답이 없다고 느껴, '오브젝트' 라는 책을 구입하였으며

책을 읽으면서 조금 더 공부한 뒤 다시 도전하도록 하겠다.