[우아한 테크코스] 프리코스 4주차 (프론트엔드 5기)

seongminn·2022년 11월 22일
0

TIL

목록 보기
11/11
post-thumbnail

3주차 피드백

  • 본인만의 메서드 라인에 대한 기준을 세울 것
  • 발생할 수 있는 예외 사항에 대해서 고민할 것
  • 비즈니스 로직과 UI 로직을 분리할 것
  • 객체의 상태 접근을 제한할 것
  • 객체는 객체스럽게 사용할 것
  • 필드 수를 줄이기 위해 노력할 것
  • 성공하는 케이스 뿐만 아니라 예외 케이스에 대해서도 테스트할 것
  • 테스트 코드도 코드다. 리팩토링하며 개선해나갈 것
  • 테스트를 위한 코드는 구현 코드에서 분리할 것
  • 단위 테스트하기 어려운 코드를 단위 테스트하기

프리코스 마지막 주차가 끝나기까지 채 하루가 남지 않았다. 과제를 진행하면서 정신 없는 와중에 이런 생각이 자꾸만 들었다.

이거 끝나면 뭐 해야 하지..?

함께 프리코스에 참여하고 있는 많은 분들이 동감할 것이라 확신한다. 그도 그럴 것이, 주말 없이 일주일 내내 좋은 코드에 대해 생각했고, 심지어는 꿈 속에서도 고민할 만큼 몰입을 했기 때문에 그 이후에 대해서 깊게 생각해보지는 못했다. 동시에 과제를 진행하는 4주 동안 내 안에서 일어나는 변화를 실시간으로 확인하고 있다. 그렇기 때문에 프리코스가 끝나는 것을 거부하는 아쉬운 마음이 그 다음을 생각하길 방해하는 것인지도 모른다.

암튼 이번 주차 과제에 대해서 말해보자.

🌉 브릿지 게임

드라마를 좋아하는 편은 아니라서 몰랐는데, 오징어 게임에 나왔던 게임이라고 한다. 드라마에서 많은 등장인물을 괴롭힌 것도 모자라, 이제는 열심히 공부하는 우아한 개발자 지망생들을 또 괴롭힌다. 진짜 못됐다.👿

장난처럼 말했지만, 사실은 경험담이다. 진짜 어려웠다. 3주차에 Lotto 게임을 진행하면서 나름 MVC 패턴을 기반으로 프로그램을 설계하는 것에 익숙해졌다고 생각했는데, 참으로 오만한 생각이었다. 3주차에 참고했던 MVC 패턴 규칙을 다시 한 번 복기하며 구조 설계를 시도했지만 깊게 들어갈 수록 객체들간의 의존성이 깊어져 코드가 복잡해지는 문제가 발생했다. 이 때 큰 도움이 됐던 것이 바로 테스트 주도 프로그래밍 방식이다.

🧪 테스트 주도 프로그래밍

2주차가 끝난 뒤 진행되었던 코수타에서 포비님께서 TDD는 쓰레기라고 말씀하셨다. 물론, 진짜 쓰레기라는 것은 아니었던 것 같고(그렇죠 포비님..?), 본인이 할 수 있는 역량 내에서 할 수 있는 최선의 것을 먼저 수행하라는 말씀이셨다. 많은 것을 처음 접해보았던 2주차의 나는 크게 동감했고, 지금 할 수 있는 것을 잘 하는 것에 집중했다.

그러다 보니 TDD와는 조금 거리가 있는 개발을 했던 것 같다. 하지만 인간이란 모름지기 발전을 추구해야 하는 것 아닐까. 3주차 때는 MVC 패턴을 도입하여 객체들 간의 의존성을 낮췄고, 비즈니스 로직과 UI 로직을 분리하는 것에 도전했다. 뭐, 성공인지 아닌지는 모르겠지만, 적어도 도전하고 발전하기 위한 시도를 했다는 점만으로도 큰 의미를 갖는다고 생각한다.

그래서 이번에는 TDD에 도전했다. 결과는 대만족이었다. 역시나 성공인지 아닌지는 또 모르겠지만😅, 앞으로 MVC 패턴과 함께 테스트 주도 프로그래밍 방식을 계속해서 시도할 것이고 그 과정에서 점점 정답을 찾아갈 것이라 확신한다.

암튼, 그럼 테스트 주도 프로그래밍이 도대체 뭐길래?

이름이 어려워서 그렇지, 매우 간단하다. 테스트 코드를 먼저 작성하고, 이를 통과하는 프로덕션 코드를 작성하며 진행하는 것을 말한다. 처음에는 작은 단위의 테스트 코드를 먼저 작성하고, 점점 큰 단위의 테스트 코드를 작성한다.

다음은 Bridge Game을 진행하면서 필자가 실제로 작성한 테스트 코드다.

// 1. Write a failing test

test(
  '3~20까지의 숫자 입력 테스트',
  () => {
    expect(() => {
      new BridgeLengthValidator(1).validate();
    }).toThrow('[ERROR]');
  }
);

이 경우, 3~20의 수를 입력해야 하지만, 1을 입력했기 때문에 [ERROR]라는 문구를 포함한 에러를 반환한다.

그 다음으로 해당 테스트를 통과하기 위한 프로덕션 코드를 작성한다.

// 2. Make the test pass

class BridgeLengthValidator extends Validator {
  #bridgeLength;

  #regex = /^\d{1}$|^1{1}\d{1}$|^20$/;

  constructor(bridgeLength) {
    super();

    this.#bridgeLength = bridgeLength;
  }

  validate() {
    if (!this.#regex.test(this.#bridgeLength))
      throw new Error(ERROR_MESSAGE.BRIDGE_LENGTH);
  }
}

위 유효성 검사 코드는 Lotto 게임에서 인터페이스로 사용자 입력을 받도록 구현하신 분의 코드를 보고 영감을 받았다.

이제 사용자가 3~20 이외의 값을 입력하면 에러를 반환하기 때문에 테스트에 통과한다.

이후 리팩토링을 한다.

// 3. Refactor

class BridgeLengthValidator extends Validator {
  #bridgeLength;

  #regex = /^[3-9]{1}$|^1{1}[0-9]{1}$|^20$/;

  constructor(bridgeLength) {
    super();

    this.#bridgeLength = bridgeLength;
  }

  validate() {
    if (!this.#regex.test(this.#bridgeLength))
      throw new Error(ERROR_MESSAGE.BRIDGE_LENGTH);
  }
}

나의 경우, 리팩토링 하는 과정에서 치명적인 실수를 발견했다. 처음 작성한 정규식을 잘 보면, \d{1} 이렇게 생긴 식을 볼 수 있다. 이는 한 자리의 정수를 검사하는 정규식으로, 사용자 입력 값의 타입이 문자라서 에러를 반환하긴 했지만, 결과적으로는 잘못된 코드였다. 이를 [3-9]로 바꿔주어 3에서 9 사이의 값만 받을 수 있도록 했다.

테스트 코드를 잘 작성한다면, 이런 문제를 미연에 방지할 수 있다.(TDD 최고)

또한, 3주차 피드백의 내용처럼 테스트 코드도 코드다. 그래서 어떻게 하면 더 효율적으로 작성할 수 있을까 고민했다. 위의 Validation 테스트에서는 3~20 이외의 어떤 값이 들어와도 에러를 반환해야 했기에 여러 파라미터를 넘겨 테스트를 해야 했다. 그래서 처음에는 파라미터만 바뀐 똑같은 코드를 중복해서 사용했다.

jest에는 test.each()라는 함수가 존재한다. each() 함수의 인자로 2차원 배열에 테스트 데이터를 담아 넘겨주면 배열을 루프를 돌며 각 요소마다 테스트 함수를 호출할 수 있다. 그래서 다음과 같이 리팩토링 하였다.

class BridgeLengthValidator extends Validator {
  #bridgeLength;

  #regex = /^[3-9]{1}$|^1{1}[0-9]{1}$|^20$/;

  constructor(bridgeLength) {
    super();

    this.#bridgeLength = bridgeLength;
  }

  validate() {
    if (!this.#regex.test(this.#bridgeLength))
      throw new Error(ERROR_MESSAGE.BRIDGE_LENGTH);
  }
}

또한 새로운 코드를 추가할 때도 큰 길잡이가 되었다. 원하는 출력 결과를 얻은 상황에서 보다 좋은 코드를 작성하기 위해 리팩토링을 진행했다. 이 때, 게임 라운드마다 달라지는 상태를 저장하기 위해 새로운 객체를 추가하였고, 코드를 전체적으로 수정해야 했다. 평소 같았으면 코드가 어디서 잘못 되었는지 확인하느라 밤을 지새울 것이 뻔했다. 하지만 미리 잘 작성해 둔 테스트 코드 덕분에 어떤 부분에서 문제가 발생했는지 쉽게 알 수 있었고, 이를 확인하면서 큰 문제 없이 코드를 변경할 수 있었다.

사실 처음에는 테스트 코드를 작성하느라 늦어지는 개발 속도에 불안한 마음이 컸다. 테스트에 통과하지 못하면 다음 코드 작성을 할 수도 없었기에 답답하기도 했다.

하지만 과제를 진행하면 할 수록 속도가 붙는 것을 실감했다. 세부적인 코드들의 테스트를 진행하며 에러가 없음을 확인했기에 Controller에서 전체 흐름을 연결하기만 하면 됐다.
물론, 위와 같은 경우처럼 테스트 코드 역시 사람이 작성하는 것이기 때문에 놓치는 부분이 분명 존재할 수 있다. 그렇기 때문에 테스트 코드에 의존하지 않고, 요구사항을 명확하게 파악하는 것 역시 중요함을 다시 한 번 깨달았다.

🍀 소감

4주차 과제에서는 다른 과제들과 다르게 파일이 몇 가지 주어졌다. 각 파일의 코드는 서로 다른 모양을 하고 있었는데, 이는 모두 자바스크립트에서 객체를 생성하는 방법이었다. 생성자 함수로 객체를 만들 때, 객체의 기본 틀이 되는 클래스 형식이 있었고 그 자체로 객체를 생성하는 방식인 객체 리터럴 형식이 있었다. 이런 기본 틀의 차이는 곧, 객체의 역할에도 영향을 미칠 것이라 생각했다. 그래서 클래스 형식으로 만들어진 파일은 프로퍼티를 동적으로 생성해야 할 필요가 있는 경우, 혹은 프로그램 실행 동안 여러 개의 객체를 생성해야 할 필요가 있는 경우 사용되는 것이라 판단했다. 그리고 객체 리터럴로 생성된 객체는 정적인 사용, 혹은 객체가 재생성될 필요가 없는 경우 사용된다고 판단했다. 이런 스스로만의 기준과 함께 여러 객체를 만들었다. 예를 들어, 라운드가 바뀌면 그 때마다 게임 상태를 초기화 해야 했다. 그래서 현재 게임의 상태를 관리하는 GameState를 class 형식으로 구성했고, 새 라운드가 시작될 때마다 새로운 객체를 생성했다. 이는 새로운 라운드가 시작되었음을 직관적으로 나타낼 수도 있었다. 반면, BridgeMaker 객체는 다리의 경로 정보를 반환할 뿐이었기 때문에 동적인 생성이 필요하지 않았다. 그래서 객체 리터럴로 주어졌음을 확인할 수 있었다.

또한, 이번 주차 과제에서 테스트 주도 프로그래밍 방식에 보다 집중하고자 했다. 프리코스 기간 동안 좋은 코드를 작성하는 습관을 최소 하나 이상 만드는 것이 목표였기 때문에, 3주차에는 MVC 패턴을 시도했고, 그리고 이번 주차에서 TDD 방식을 시도한 것이었다. 처음 접하는 TDD는 굉장히 어렵고 답답한 방식이었다. 테스트에 통과하지 못하면 다음 코드 작성을 할 수 없었기 때문에 개발에 눈에 띄는 진전이 없었기 때문이다. 하지만 작은 단위의 테스트 코드를 먼저 작성하여 세부적인 부분을 단단히 하였기 때문에 개발의 막바지에 도달했을 때는 확실히 속도가 빨라졌음을 느낄 수 있었다. 실제로 BridgeMaker를 통해 입력 받은 값을 올바른 다리 정보로 바꾸는 기능을 테스트 하였고, 현재까지 진행한 게임의 결과를 출력하는 테스트 코드를 작성했다. 그리고 이를 통과할 수 있는 실제 코드를 작성한 뒤 Controller로 각각을 연결하니 프로그램의 전체적인 흐름을 구현할 수 있었다. 그리고 이런 뼈대가 중심이 되었기 때문에 보다 쉽게 리팩토링을 진행할 수 있었다. 처음에는 게임의 상태를 관리하는 GameState 객체가 존재하지 않았고, 이를 전부 BridgeGame 객체에서 관리하고 있었다. BridgeGame 객체의 부담을 줄이기 위해 GameState 객체를 추가하기로 결정하였고, 대대적인 리팩토링에 들어갔다. 이 때, 잘 작성해둔 테스트 코드 덕분에 어떤 메서드에서 원하지 않는 값을 반환하는지 알 수 있었고, 이를 기반으로 하여 쉽게 새로운 코드를 추가할 수 있었다.

우아한 테크코스의 프리코스는 참 신기하다. 충분할 만큼 길다고 하기엔 어려운 4주라는 시간 동안, 한 사람의 습관을 이렇게나 바꿔놓을 수 있다는 것이 몇 번을 생각해도 놀랍다. 과제를 진행하면서 매번 스스로 공부하려는 노력을 했고, 매번 무언가를 시도하려는 노력을 했다. 물론 여전히 완벽하지는 않다. 다만, 시도하고 도전하고자 하는 마음가짐은 언제나 나를 성장하는 길로 인도할 것이고, 그 과정에서 언젠가 정답을 찾아낼 것이라 확신한다. 그리고 우아한 테크코스의 정규코스에 꼭 참가하여, 이 프리코스를 함께 진행한 동료들과 각자만의 정답을 위한 여정을 떠나고자 간절히 소망한다. 그리고 이를 공유하며 함께 성장하길 진심으로 꿈꾼다.

profile
돌멩이도 개발 할 수 있다

0개의 댓글