본문 바로가기
우아한테크코스

[우아한테크코스] 우테코 4기 프리코스 회고3 - 2주차 미션

by 윤루트 2022. 1. 12.

우아한테크코스

2주차 미션

 

GitHub - woowacourse/java-racingcar-precourse: 자동차 경주 게임 미션을 위한 저장소

자동차 경주 게임 미션을 위한 저장소. Contribute to woowacourse/java-racingcar-precourse development by creating an account on GitHub.

github.com

2주차 미션의 목적은 보기 좋은 코드의 작성이라고 생각한다.

1주차에서 배운 내용과 피드백을 바탕으로 코드를 작성하면서 클래스 분리에 조금 더 신경써서 작성해야 하며 추가된 요구사항으로는 메소드의 길이가 15라인을 넘어가지 않게 구현해야 하고 else 예약어 사용을 금지했다.

또한, 기본적으로 Car라는 객체가 다음과 같이 미리 정의되어 있다. 

public class Car {
    private final String name;
    private int position = 0;

    public Car(String name) {
        this.name = name;
    }

    // 추가 기능 구현
}

이전 주차 과제에서는 객체에 대해 신경을 전혀 쓰지 못한채 절차 지향적으로 코드를 작성했다. 이번 주차에서 객체를 미리 정의해주면서 객체 지향적으로 코드를 작성하게끔 유도했다.

 

- 객체 지향

학교에서 객체 지향 프로그래밍이라는 수업을 듣긴 했지만, 당시에는 그저 C++ 프로그래밍 정도의 수업로 받아들이지 못했기 때문에 객체 지향이라는 말만 들어보고 이해가 없는 상태였다. 이번 기회에 객체 지향에 대해 공부하고 코드를 작성하고자 했다.

 

여러 좋은 블로그 글을 참고하여 알아보고 주변 훌륭한 개발자분의 도움을 받아 1주차 코드를 리팩토링하며 공부했다.

 

많은 내용을 이해하고 적용하기 어려웠지만, 최대한 적용하려고 노력한 부분은 static 남발하지 않기객체 클래스 내에서 동작시키기이다.

이전 코드를 분석해보면, 모든 메소드를 static을 사용하여 작성한 것을 알 수 있었다. 작성할 당시에는 전혀 개념이 없었기 때문에 어디서든 사용이 가능하여 그렇게 작성했다.

하지만, static을 사용한 메소드는 인스턴스화를 하지 않아도 사용가능 하니 객체 지향적이 아니다.

상태와 기능이 있는 객체 클래스를 만들었고 자연스럽게 클래스 내부에서 동작할 수 있게 코드를 작성할 수 있었다.

 

- 유틸성 클래스의 사용

그렇다면, 기능만 있는 메소드를 작성하고 싶을 때는 어떻게 할까?

기능만 있는 메소드를 모아 utils 패키지에 만들어 사용했고 이것은 static으로 사용했다. 단, private 생성자를 사용하여 인스턴스화를 막는다.

public class HintFactory {

	private HintFactory() {
	}

	private static final int INITIAL_VALUE = 0;

	public static void makeHint(AnswerNumber answerNumber, PlayerNumber playerNumber) {
		Hint hint = new Hint(INITIAL_VALUE, INITIAL_VALUE);
		hint.countStrike(answerNumber, playerNumber);
		hint.countBall(answerNumber, playerNumber);
		OutputView.printHint(hint.getBallCount(), hint.getStrikeCount());
	}
}

하지만, 정말 필요한 경우에만 유틸성 클래스를 사용하고 되도록 static을 안쓰려고 노력했다.

 

- mvc 패턴 사용

이전 주차 과제에서는 임의대로 역할이 맞는 클래스를 묶어 패키지를 만들었다. 하지만, 이것 역시 규칙이 있을 것 같아 알아보게 되었고 디자인 패턴인 mvc 패턴에 대해 알게 되었다.

https://sas-study.tistory.com/406

 

MVC 패턴(Model, View, Controller)

안녕하세요. 오늘은 MVC 패턴에 대한 이야기를 하고자 합니다. 신입 기술면접에서 자주 등장하는 내용인데요. 패턴은 정해져있지만 각자가 이해하는 내용이 달라서 다양한 답이 나오는 주제인것

sas-study.tistory.com

 

따라서 다음과 같이 패키지를 구분하였다.

프로그램 구조

이렇게 구조를 나누니 해당하는 역할에만 집중할 수 있었고 코드를 재사용하며 중복된 코드를 제거할 수 있었다.

 

- else 예약어 사용 X

이전까지 프로그래밍하는 습관 중 하나는 조건을 많이 나누어 예외처리를 잘 할 수 있게끔 노력하는 것이 있었다. 그렇기 때문에 else 사용을 많이 했었기 때문에 해당 요구사항을 처음 접했을 때 반감이 조금 생겼다.

 

하지만 알아보니 else 예약어를 사용하면 코드를 간결하고 명확하게 작성할 수 있고 객체지향적으로 작성할 수 있다.

실제 비즈니스 구조에서 엄청나게 복잡한 코드를 작성하는데, 당연히 조건문 역시 엄청나게 많아질 것이다. 해당 코드에 예를 들어 else 문이 끝나고 추가로 로직을 작성해야 한다면, 그 복잡한 코드를 분석하며 끝까지 확인해야 할 것이다. 비효율적이다.

else 대신 if 문 내부에서 조기 반환 (early return)을 통해 간단히 확인 가능하다.

 

추가적인 설명은 다음 글을 참고하면 좋을 것 같다.

https://tecoble.techcourse.co.kr/post/2020-07-29-dont-use-else/

 

else 예약어를 쓰지 않는다

The ThoughtWorks Anthology의 더 나은 소프트웨어를 향한 9단계: 객체지향 생활 체조 중 규칙…

tecoble.techcourse.co.kr

 

- 예외 처리

이전에 42 서울 라피씬을 진행하며 생긴 습관인데, 프로그래밍을 하며 발생할 수 있는 예외 사항을 최대한 생각하고 처리한다.

이번 과제의 리드미 파일에 처리해야 할 예외 사항이 몇 가지 있었지만, 코드를 작성하며 발생할 수 있는 예외 사항이 계속해서 보였고 이것에 대해 모두 처리하고자 발견할 때 마다 리드미 파일에 예외 사항을 추가 작성하며 예외 처리했다.

이로써 살아있는 리드미 파일을 작성할 수 있었고 좋은 습관을 한 번 더 기를 수 있게 되었다.

작성한 README.md 파일 중 예외 처리 부분

또한, 2주차 과제 피드백에 발생할 수 있는 예외케이스에 대해 고민한다라는 내용의 피드백이 있었기에 미리 해당 내용을 수행했다는 점이 뿌듯했다.

피드백

이전 과제에 비해 조금 일찍 제출했다. 견문이 좁았기 때문에 꽤나 잘 작성했다고 생각했고 더 리팩토링할 부분이 없다고 생각했다. 따라서 제출 이후 다음 과제를 기다리는 동안 알고리즘 문제를 풀며 객체 지향 연습을 했다.

 

공통 피드백의 내용 중 많은 도움이 된 내용은 다음과 같다.

 

- java에서 제공하는 api를 적극 활용한다.

이것도 42 라피씬을 진행하며 생긴 습관인데, 제공하는 api 사용을 자제하려는 습관이 있다. 아무래도 당시 라피씬을 진행할 때는 0부터 구현하며 사용하는 연습을 했기 때문에 필요한 것이었지만, 실제 프로그래밍을 하는데 있어서는 api가 있으면 적극 활용하여 더욱 쉽고 간편하게 사용할 수 있다.

피드백에서는 메소드를 직접 구현하기 전에 java api에서 제공하는 기능인지 검색을 먼저 해보라는 내용이다. 자바에서 제공하는 api가 강력하기 때문에 사용을 잘 하는 것이 중요하다는 생각이 들었다.

 

특히 stream api의 사용은 정말 중요하다고 느꼈다.

여러 줄로 작성했던 코드를 단 한 줄로 끝낼 수 있다. 단지 간단하고 쉽게 작성할 수 있으니 좋다고 생각했지만,

이후에 알게된 사실은 side effect를 방지 할 수 있다는 점이다.

여러 줄로 작성이 되어있으면 중간에 다른 코드가 삽입될 수 있고 변수 등의 값이 변할 수 있는 여지가 충분하다.

하지만, stream을 사용하면 한 줄로 끝낼 수 있으므로 그러한 여지를 최소한으로 줄일 수 있다.

 

- 객체에 메시지를 보내라

상태 데이터를 가지는 객체에서 데이터를 꺼내려(get)하지 말고 객체에 메시지를 보내라는 피드백이다. 피드백에 적힌 예시는 다음과 같다.

 

예를 들어 Car가 우승자인지를 판단하기 위해 최대 이동 거리 값을 가지는 Car인지 판단 가능은?

private boolean isMaxPosition(Car car) {
	return car.getPosition() == maxDistance;
}

위와 같이 구현하지 않고 다음과 같이 Car에게 메시지를 보내 구현한다.

car.isMaxPosition(maxDistance);

 

즉, 객체에서 정의한 메소드를 활용할 때 컨트롤러에서 해당 메소드의 리턴값으로 무언가 조작을 하기 보다는 해당 메소드를 호출만 하면(메소드에 메시지를 보내면) 자동적으로 해당 내용이 구현되게끔 작성한다. 

이렇게 프로그래밍하면 컨트롤러에서는 메소드를 호출만 하면 객체 내부에서 어떤 일이 발생하는지 모르지만, 원하는 일이 수행이 된다.

한마디로 객체 지향적으로 된다.

 

- 필드(인스턴스 변수)의 수를 줄이기 위해 노력한다.

필드의 수가 많으면 객체의 복잡도를 높이고 버그 발생 가능성을 높일 수 있으므로 중복과 불필요한 필드를 확인 후 최소화하라는 피드백이다.

피드백에서 온 예시를 통해 이해해 보자.

 

예를 들어 우승자를 구하는 다음 객체를 보자.

public class Winner() {
	private List<Car> cars;
    private List<String> winners;
    private int maxDistance;
}

위 객체의 `maxDistance`와 `winners`는 자동차 목록(cars)만 있어도 모두 구할 수 있는 값이다.

따라서 위 객체는 다음과 같이 하나의 인스턴스 변수만으로 구현할 수 있다.

public Class Winner {
	private List<Car> cars;
    
    private int getMaxDistance() { ... }
    
    public List<String> getWinners() { ... }
}

 

- 비즈니스 로직과 UI 로직을 분리해라

비즈니스 로직과 UI 로직을 한 클래스가 담당하지 않도록 하라는 피드백이다. 단일 책임의 원칙에도 위배된다.

내가 작성한 코드를 보면 비즈니스 로직에 출력을 담당하는 OutputView의 메소드가 담겨있다. 클래스만 분리했지 역할이 제대로 분리되지 않은 모습이다.

Car 클래스의 한 메소드

해당 피드백을 통해 mvc 패턴에 따라 역할 분리에 더 신경을 더 쓸 것이다.

 

2주차 과제 역시 피드백을 받은 이후 리팩토링했고 시야를 조금 더 넓힐 수 있는 계기가 되었다.

 

 

댓글