[우테코 프리코스] 2주차 과제 회고

SeongWon Oh·2021년 12월 3일
0

우테코 회고록

목록 보기
2/9
post-thumbnail

프리코스 1주차 피드백 리뷰

프리코스 1주차가 끝나고 2주차 과제가 나오기 앞서 1주차 과제에 대한 공통적인 리뷰를 받았습니다. 크게 정리하면 다음과 같은 리뷰들이 있었습니다.

  • 변수, 메서드, 클래스의 이름이 의도를 드러내며 축약하지 않게 작성하여라
  • 컨벤션을 모두 지켰는가? 지키지 않았으면 IDE의 코드 정렬기능을 활용하여라
  • 의미 없는 주석을 달지 말아라
  • 코드의 중복이 없도록 구현하여라
  • 커밋 메시지를 옳바르고 이해가능하게 작성하여라
  • 매직넘버 대신 상수(static final)로 코드의 가독성을 높여라
  • 클래스를 상수, 멤버 변수, 생성자, 메서드 순으로 작성하며 구현 순서를 지켜라
  • README.md파일을 살아있는 문서를 만들기 위해 노력하여라

해당 피드백을 보고 1주차에 작성한 과제 코드를 보았더니 대부분의 내용은 지켰으나 코드의 중복이 있는 것을 확인하였습니다. 코드의 완전한 중복은 아니지만 중복된 값이 존재하는지에 대한 여부를 체크하는 기능상의 중복이 있어 해당 기능은 리펙토링하여 하나의 메서드로 묶어주는게 더 좋았을 것 같다는 생각을 하였습니다.


프리코스 2주차 과제

🚀 미션 간단 설명

해당 미션은 사용자가 입력한 자동차들이 N번의 시도로 Randoms메서드를 통해 나온 결과값에 따라 전진을 하며
N번의 시도가 끝난 후 최종 우승자를 출력하는 프로그래밍 미션입니다.

🔒 제약사항

1. 자동차

  1. 주어진 객체 Code를 이용해야 한다.
  2. 전진하는 자동차 출력할 때 이름을 같이 출력해야 한다.
  3. 이름은 5자 이하만 가능하다
  4. 전진 조건 – 0~9사이에서 무작위 값을 구한 후 값이 4이상일 경우 전진한다.

2. 입력

  1. 자동차 이름은 쉼표(,)를 기준으로 구분
  2. 이름은 5자 이하만 가능.
  3. 사용자는 몇번의 이동을 할 것인지 입력할 수 있어야 한다.
  4. 잘못된 입력 값이 있을 경우 IllegalArgunentException을 발생시키고 "[ERROR]"로 시작하는 에러 메시지 출력 후 재입력을 받는다.

3. 출력

  • 완료 후 우승자를 출력해야 한다.
  • 우승자는 1명 이상일 수 있으며 여러명일 경우 쉼표를 이용하여 구분한다.)
  • 각 차수별로 결과를 출력해야한다. (5회 실행이면 5번 결과 출력)

4. 프로그래밍 요구사항

  • JDK 8 버전 사용
  • Indent를 2까지만 허용
  • 3항 연산자 사용 금지
  • 메서드는 최대한 한가지 기능만!
  • 메서드의 길이가 15라인을 넘지 않도록 구현
  • Else 사용 금지! -> if절에서 return 하는 방식으로 구현하도록!
  • Random, Scanner API대신 camp.nextstep.edu.missionutils에서 제공하는 Randoms, Console API를 활용!
  • 과제에 제안된 Car객체를 사용해야한다.
    • 객체의 기본 생성자 추가 금지
    • name, position의 접근 제어자인 private 변경 금지!
    • 가능하다면 setPosition메서드 추가하지 않고 구현하도록!

🎓 프리코스 2주차에서 배운점

1. 메서드의 분리로 코드의 가독성 향상

지난주에 앞서 이번주도 코드의 가독성을 향상시키는 것에 많은 학습을 하였습니다.
과제의 요구사항에 메서드의 길이가 15라인을 넘지 않도록 구현하라라는 요구사항이 있어 이를 지키다보니 시간적 투자가 필요하였으나 결과론적으로 코드의 가독성이 좋아지는 것을 확인할 수 있었습니다.

아래의 코드는 메서드를 분리하기 전/후의 코드입니다.

메서드 분리 전

    public static void printFinalResult(List<Car> carList) {
        HashMap<Integer, ArrayList<String>> rankMap = new HashMap<>();
        int maxPosition = 0;
        for (Car car : carList) {
            int carPosition = car.getPosition();

            if (maxPosition < carPosition) {
                maxPosition = carPosition;
            }

            if (!rankMap.containsKey(carPosition)) {
                rankMap.put(carPosition, new ArrayList<>());
            }
            rankMap.get(carPosition).add(car.getName());
        }
        
        List<String> winnerList = rankMap.get(maxPosition);

        System.out.print("최종 우승자 : ");
        int winnerListSize = winnerList.size();
        for (int i=0; i< winnerListSize-1; i++) {
            System.out.print(winnerList.get(i) + ", ");
        }
        System.out.println(winnerList.get(winnerListSize-1));

    }

메서드 분리 후

    public static void printFinalResult(List<Car> carList) {
        List<String> winnerList = findWinnerList(carList);

        System.out.print("최종 우승자 : ");
        int winnerListSize = winnerList.size();
        for (int i=0; i< winnerListSize-1; i++) {
            System.out.print(winnerList.get(i) + ", ");
        }
        System.out.println(winnerList.get(winnerListSize-1));
    }

    private static ArrayList<String> findWinnerList(List<Car> carList) {
        HashMap<Integer, ArrayList<String>> rankMap = new HashMap<>();
        int maxPosition = 0;
        for (Car car : carList) {
            int carPosition = car.getPosition();
            maxPosition = Math.max(maxPosition, carPosition);

            if (!rankMap.containsKey(carPosition)) {
                rankMap.put(carPosition, new ArrayList<>());
            }
            rankMap.get(carPosition).add(car.getName());
        }
        return rankMap.get(maxPosition);
    }

해당 코드를 작성할 때 처음에는 최종 우승자를 찾아서 출력한다!라는 하나의 기능이라고 생각하며 작성였습니다. 하지만 메서드를 작성하여보니 15라인이 넘었고 이를 15라인을 넘기지 않도록 기능을 더 세밀하게 분리하며 우승자 List를 뽑는 메서드, 우승자 List를 출력만하는 메서드로 분리하며 구현을 하다 보니 코드의 가독성이 향상된 것을 확인할 수 있었습니다.

2. 메서드 이름 설정

1주차 피드백에서 의미 없는 주석은 달지 않으며 변수와 메서드의 이름을 통해 어떠한 의도인지 드러내라는 피드백을 받고 이름 설정들에 많은 시간이 투자하였습니다.

이름 설정은 변수보다도 메서드 이름 설정에서 많은 어려움을 느꼈습니다.
특히 메서드를 기능별로 쪼개어 만들다보니 외부 클래스에서 호출할 public메서드이름과 내부에서 메인 기능을 실행할 private메서드의 이름을 설정하는데 많은 어려움을 느낀 것 같습니다.

Ex) 유저로부터 자동차 이름을 받는 public메서드 이름과 내부적으로 실제 입력을 받는 기능을 담당하는 private메서드의 이름을 구분하여 설정하기 어려웠습니다.
작성한 public메서드는 외부 클래스로부터 호출되고 내부적으로는 private메서드 들을 호출하여 입력값을 받고 exception이 발생하면 옳바른 값을 받을 때까지 반복문을 돌리는 메서드입니다.
작성한 private메서드는 외부 클래스로부터 호출되며 실질적으로 사용자로부터 입력을 받는 코드가 작성되어 있습니다.

public class InputUtils {
    public static List<Car> returnCarList() {
        List<Car> carList = new ArrayList<>();
        boolean isValid = false;

        while (!isValid) {
            try {
                System.out.println(CAR_NAME_INPUT_MESSAGE);
                carList = getCarList();
                isValid = true;
            } catch (IllegalArgumentException e) {
            }
        }
        return carList;
    }

    private static List<Car> getCarList() {
        List<Car> carList = new ArrayList<>();
        HashSet<String> carSet = new HashSet<>();
        String input = Console.readLine();
        checkCarNameSentence(input);
        String[] carArr = input.split(CAR_NAME_SEPARATOR);

        for (String carName : carArr) {
            checkCarNameWord(carName, carSet);
            carList.add(new Car(carName));
            carSet.add(carName);
        }
        return carList;
    }

메서드의 이름 설정은 과제를 진행하면서도 힘들었고 앞으로도 많은 신경을 써야할 부분인 것 같습니다.

3. Private Method의 테스트 방법

지난주 과제를 진행할 때는 Unit Test에 대해 전반적인 내용에 대해 학습을 하였으나 Private 메서드에 대해 학습하는 방법은 따로 학습을 하지 않았습니다. 하지만 이번 과제를 진행하다 보니 유저로부터 Input 값을 입력받고 input 값에 대한 예외 상황을 처리하는 private 메서드를 많이 만들게 되어 따로 학습을 하게 되었습니다.

private메서드에 대해 학습한 자세한 내용은 Private메서드의 Unit Test를 통해 확인하실 수 있습니다.

테스트 코드 예제

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import static constant.Constant.*;
import static org.assertj.core.api.Assertions.assertThat;

class InputUtilsTest {
    @DisplayName("[자동차 이름] 5자리 이상의 이름인 경우")
    @Test
    public void checkCarNameLengthTest() throws NoSuchMethodException {
        InputUtils inputUtils = new InputUtils();
        Method method = inputUtils.getClass().getDeclaredMethod("checkCarNameLength", String.class);
        method.setAccessible(true);

        try {
            method.invoke(inputUtils, "christopher");
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            assertThat(e.getCause().getMessage()).isEqualTo(ERROR_CAR_NAME_LENGTH);
        }
    }
}

최종 후기

이번 과제를 하며 가장 어렵고 시간 투자를 하였던 부분은 메서드, 변수의 이름 설정이었습니다. 1주 차 피드백에서 의미 없는 주석은 달지 않으며 변수와 메서드의 이름을 통해 어떠한 의도인지 드러내라는 피드백을 받고 이름 설정들에 많은 시간이 투자되었던 것 같습니다.

그래도 1주 차 과제를 하며 Java 프로그래밍 규약 Git 커밋 컨벤션 학습에 많은 시간 투자를 하고 가독성 좋은 프로그래밍을 하기 위해 코드를 수정하며 학습을 하여서 그런지 이번 2주 차 과제는 지난주에 비하여 쉽게 해결할 수 있었습니다. 또한 1주 차 과제를 하며 전반적인 테스트 코드 작성법을 학습하여 2주 차 과제에서의 테스트 코드 작성은 보다 쉽게 할 수 있었으며 테스트 코드 작성을 하다 보니 코드의 안정성과 신뢰성이 높아지는 것 같은 느낌을 받았습니다.

남은 3주 차 과제도 지금까지 배운 것을 적용하고 추가로 많은 것을 배우며 성장하는 개발자가 되도록 노력하겠습니다.🔥

<2021.12.07 추가>
과제 제출을 한 후 다른 지원자들의 코드를 리뷰해보는 시간을 가지며 많은 이들이 과제에 MVC패턴을 적용한 것을 확인하였습니다.
이전까지 Spring프레임워크에서만 사용하였던 MVC패턴을 저도 다음 3주차 과제부터는 적용해보고자 MVC패턴을 학습하였습니다.
📌MVC 패턴이란?
다음 3주차 과제는 MVC패턴을 적용한 보다 나은 코드를 작성하도록 노력해보겠습니다.

profile
블로그 이전했습니다. -> https://seongwon.dev/

0개의 댓글