본문 바로가기

우아한테크코스 프리코스 참여 후기

자바 백엔드 개발자 양성 교육으로 우아한테크코스를 개설한다는 소식을 듣고 지원했는데요.
이번에 진행한 프리 코스의 진행 방식과 후기를 써보겠습니다.

프리코스는 본 과정을 미리 경험해 보는 단계로, 경험 후에 교육의 참여 여부를 결정하는 단계입니다.
프리코스는 3주간 매주 미션을 받아 구현한 뒤 제출하는 방식으로 진행되었고, 제출 후에는 피드백을 받았습니다.

1주차 미션의 목표는 코드 컨벤션 지키기, 함수를 분리하는 연습
2주차 미션은 1주차 목표 + 클래스 분리하는 연습
3주차 미션의 목표는 여러 개의 클래스를 분리한 후 서로 관계를 맺어 하나의 프로그램을 완성하는 것입니다.

이번 코스는 자기 주도 학습과 클린 코드를 작성하는 훈련이었고, 이 프로젝트를 진행하면서 <클린코드> 책의 도움을 많이 받았는데요.
후기를 쓰기에 앞서 클린 코드의 중요성을 한번 설명하고 가겠습니다.

왜 클린코드를 해야 하는가?

Enterprise Software

우리는 다음과 같은 엔터프라이즈 소프트웨어를 개발해야 합니다.

  • 대형 : 10만 ~ 100만 라인
  • 오랫동안
  • 복잡한
  • 많은 개발자
  • 많은 수정
  • 반복 개발

출처 - https://github.com/petozoltan/petozoltan.github.io/wiki/Clean-Code-Outline

Problems

  • 많은 버그 - 잠재적인 버그들이 코드에 숨어 있다.
  • 지나치게 복잡한 코드 - 이해가 어렵다.
  • 깨지기 쉬운 코드 - 변경으로 인해 새로운 버그, 부작용
  • 생산성 저하 - 낮은 개발 속도
  • 기술 부채가 너무 많이 수집 된다.

Technical Debt

  • 보이지 않는 기술적 부채
  • "끝내야 했다"
  • "나중에 고쳐야지"
  • "빠른&더러운" 코딩의 결과

출처 -https://github.com/petozoltan/petozoltan.github.io/wiki/Clean-Code-Outline

위 그래프를 보시면 나쁜 코드가 쌓일수록 생산성은 떨어지고 결국 0에 근접합니다. 그러면 결국에는 기능 추가를 멈추고 리팩토링에만 몇 개월을 소모하거나, 프로젝트가 회복 불능으로 폭파되는 경우도 있다고 합니다.

기술적 부채는 개발의 가장 큰 위험이다.

클린코드의 저자 로버트 C. 마틴이 어느 날 작업을 하면서 입력한 모든 키를 녹화했는데 결과가 놀라웠다고 합니다.

코드를 읽는 시간 대 코드를 짜는 시간 비율이 10대 1을 훌쩍 넘는다. 새 코드를 짜면서 끊임없이 기존 코드를 읽는다.
그러므로 읽기 쉬운 코드를 작성해야 한다.


프리코스 진행과정

1주차 미션

1차 미션은 자바를 배울 때 많이 해보는 숫자야구게임 구현이었습니다.
간단한 문제지만 간단하지 않게 만드는 조건들이 있었습니다.

  • 자바 코드 컨벤션을 지키면서 프로그래밍한다.
  • indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다. (for문 밑에 if문을 사용하면 indent는 2입니다)
  • 함수(또는 메소드)가 한 가지 일만 하도록 최대한 작게 만들어라.

1차 미션의 프로그래밍 요구사항입니다. 이를 지키는 것은 기존에도 알고는 있었지만 평소에 잘 지키지 않다보니 쉽지 않았습니다.

이번 미션을 진행하면서
클린코드와 [우아한 형제들 기술블로그] 오지산님의 - 개발 미션과 함께 읽는 클린 코드의 도움을 많이 받았는데요.

그 중에서 아래 네 가지를 신경 쓰면서 미션을 진행했습니다.

  • 주석 없이 알아볼 수 있는 코드 (이름을 축약하지 않고 신경 써서 짓는다)
  • 코드는 신문처럼 위에서 아래로 이야기처럼 읽혀야 좋다.
    (추상적인 개념을 위로, 저차원 함수를 아래로 배치하여 독자의 빠른 이해를 돕는다.)
  • 함수는 한 가지만, 그 한 가지 일을 잘해야 한다.
  • 코드를 읽는 사람이 세부 구현에 집중하지 않고 논리적인 로직을 쉽게 파악할 수 있도록 작성

컴퓨터의 3자리 숫자와 사용자의 3자리 숫자를 비교해주는 메소드를 예시로 보여드리겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void compareDigits(String comNums, String playerNums) {
    ball = 0;
    strike = 0;
 
    for (int i = 0; i < DIGIT_LENGTH; i++) {
        if (comNums.charAt(i) == playerNums.charAt(i)) {
            strike++;
            break;
        }
 
        for (int j = 0; j < DIGIT_LENGTH; j++) {
            if (i != j && playerNums.charAt(i) == comNums.charAt(j))
                ball++;
        }
    }
}
cs

위 코드는 일단 들여쓰기가 3단이 넘어서 요구사항을 위반합니다.

변경 후

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void compareDigits(String computerNums, String playerNums) {
    initBallAndStrike();
 
    for (int i = 0; i < DIGIT_LENGTH; i++) {
        if (matchSameIndex(computerNums, playerNums, i))
            strike++;
        else if (matchDifferentIndex(computerNums, playerNums, i))
            ball++;
    }
}
 
private void initBallAndStrike() {
    ...
}
 
private boolean matchSameIndex(String computerNums, String playerNums, int index) {
    ...
}
 
private boolean matchDifferentIndex(String computerNums, String playerNums, int index) {
    ...
}
cs

세부 구현은 다른 메소드로 포장하면서 메소드명만 봐도 기능을 알 수 있게 지어줬습니다. 이렇게 해줌으로써 코드를 읽는 사람이 세부 구현에 집중하지 않고 논리적인 로직을 쉽게 파악할 수 있습니다.

2주차 미션

2차 미션은 자동차 경주 게임으로 *_1주차 목표 + 클래스 분리하는 연습이었습니다.
*_이번미션은 1주차 미션에서 조건 두 가지 더 추가되었습니다.

  • 메소드의 길이가 15라인을 넘어가지 않도록 구현한다.
  • else 예약어를 사용하지 않는다.

클래스를 분리하는 일은 많이 어려웠는데요. 저는 클래스가 과연 이 행위(메소드)를 하는게 맞을까? 에 초점을 맞춰서 클래스를 분리했습니다.

이번 과제에서는 박재성님 - 의식적인 연습으로 TDD, 리팩토링 연습하기를 참고했고 처음 들어보는 일급 컬렉션 사용하기를 도전해봤습니다.

일급 컬렉션이란?

Collection을 Wrapping 하면서, 그 외 다른 멤버 변수가 없는 상태를 일급 컬렉션이라 합니다. Wrapping 함으로써 다음과 같은 이점을 가지게 됩니다.

  1. 비즈니스에 종속적인 자료구조
  2. Collection의 불변성을 보장
  3. 상태와 행위를 한 곳에서 관리
  4. 이름이 있는 컬렉션

출처: 기억보단 기록을 블로그 - 일급 컬렉션 (First Class Collection)의 소개와 써야 할 이유

경주용 자동차들을 관리해주는 일급 컬렉션입니다..

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class RacingCars {
    private static final int MIN_CAR_SIZE = 2;
    private static final int MAX_CAR_NAME_LENGTH = 5;
 
    private final List<Car> cars;
 
    public RacingCars(List<Car> cars) {
        validateSize(cars);
        validateName(cars);
        this.cars = cars;
    }
 
    public List<Car> getWinners() {
        List<Car> winners = new ArrayList<>();
        ...
        return winners;
    }
 
    private int calculateMaxPosition() {
        ...
    }
 
        public List<Car> getCars() {
        return cars;
    }
}
cs

저는 여기서 큰 실수를 했는데요. 컬렉션의 값이 변경되면 안 되는데 getter를 추가했습니다.

이렇게 되면 문제는 fianl을 사용해 줘서 재할당은 불가능하지만, 값을 추가, 제거 등을 할 수가 있습니다.

일급 컬렉션이 컬렉션의 불변을 보장해야 한다는 사실을 이미 미션을 제출하고 아는 바람에 잘못 사용해서 아쉬웠지만, 결국 3차 미션에서 일급 컬렉션을 사용하게 되어서 많은 도움이 되었습니다.

3주차 미션

3주차 미션의 목표는 여러 개의 클래스를 분리한 후 서로 관계를 맺어 하나의 프로그램을 완성하는 경험을 하는 것입니다. 또한 2주차 미션보다 더 극단적인 연습을 해보는 것이 목표입니다.

  • 메소드의 길이가 10라인을 넘어가지 않도록 구현한다.
  • indent(들여쓰기) depth를 2가 넘지 않도록 구현한다. 1까지만 허용한다. (while문 밑에 if문이 있으면 안됨)
  • 메소드의 인자 수를 3개까지만 허용한다. 4개 이상은 허용하지 않는다.

벌써 3번째 미션인 만큼 이제는 코드 컨벤션 지키기, 메소드를 나누는 과정은 자연스럽게 하게 되었는데요. 이 부분에서 '내 실력이 많이 늘었구나.' 하면서 조금은 흐뭇했습니다. 하지만 클래스를 나누는 과정은 여전히 어려워서 객체지향 5대 원칙(SOLID), 디자인패턴도 공부했지만, 알면 알수록 더 어려워졌습니다..

이번 미션에서는 아래 세 가지를 신경 썼는데요.

  • 리팩토링을 잘하기 위해서 테스트 코드가 받침이 되어야 한다. (Test 코드 작성)
  • 원시값은 포장한다.
  • 자바 API 최대한 활용하기

확실히 테스트 코드를 작성하니 코드도 좀 더 간결 해지고, 코드의 변경으로 인해 발생하는 문제들을 바로 확인할 수 있어 리팩토링을 적극적으로 할 수 있었습니다.

그리고 원시값은 포장해주니 좀 더 직관적으로 알아보기가 좋았습니다.
이번 미션에서 사용한 원시값 포장의 예를 보여드리겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
public class LottoMoney {
    private final static long PRICE_PER_LOTTO = 1_000;
 
    private final long purchaseAmount;
 
    public LottoMoney(long purchaseAmount) {
        if (purchaseAmount % PRICE_PER_LOTTO != 0)
            throw new IllegalArgumentException("금액이 잘못되었습니다. " + PRICE_PER_LOTTO + " 단위만 가능합니다.");
        this.purchaseAmount = purchaseAmount;
    }
 
        [...]
}
cs

로또를 구매하는 금액을 담는 객체인데요. 객체를 생성할 때 1000원 단위인지 검사를 해줍니다. 이렇게 해줌으로써 LottoMoney를 사용하면 항상 1000원 단위가 보장됩니다. 즉, 어디서나 믿고 사용할 수 있습니다.

그리고 또 다른 이점으로 시간이나 돈을 나타내는 원시 값들을 객체로 만들면 프로그래머에게 그 값이 어떤 값이며 왜 쓰이고 있는지 정보를 전할 수 있습니다.


프리코스 후기

프로그래머는 평생 공부해야 한다. 그러려면 자기 주도 학습을 할 수 있어야 한다

라는 조언을 받은 적이 있습니다. 하지만 혼자 공부할 때 옳은 방식으로 하고 있는지 확신이 없어 불안한 경우가 종종 있었는데, 교육 과정에서는 피드백을 받으면서 코드를 개선하는 부분이 좋았습니다.

하지만 조금 아쉬웠던 부분이 있는데요. 이번 과정에서는 일부분만 피드백을 받았는데요. 저한테 가장 어려웠던 클래스를 분리하는 부분에서는 전체적인 코드는 없다 보니 제가 설계를 잘했는지, 어떻게 하면 더 좋을지 알 수가 없어서 아쉬웠습니다. 다음 과정에서 피드백에 전체적인 코드를 공개하면 좋을 거 같습니다.

3주 동안 미션을 진행하면서 프로그래밍의 기본이지만 어려운 클린코드, 리팩토링, 테스트코드의 중요성과 다양한 자바 API와 자바8 문법을 배우며 많은 성장을 할 수 있는 시간이었습니다.