[FE] 우테코 6기 프리미션 4주차 회고

DAN·2023년 11월 27일

우테코

목록 보기
4/9
post-thumbnail

크리스마스 프로모션 🎄

3주차 공통 피드백

- 함수(메서드) 라인에 대한 기준 -> 공백 포함 15라인
- 발생할 수 있는 예외 상황에 대해 고민한다.
- 비즈니스 로직과 UI 로직을 분리한다.
- 객체의 상태 접근을 제한한다.
- 객체는 객체스럽게 사용한다.
- 필드의 수를 줄이기 위해 노력한다.
- 성공하는 케이스 뿐만 아니라 예외에 대한 케이스도 테스트한다.
- 테스트 코드도 코드다.
- 테스트를 위한 코드는 구현 코드에서 분리되어야 한다.
- 단위 테스트하기 어려운 코드를 단위 테스트하기(로직을 외부로 분리하기)

구현에 들어가기 전에

이번 미션은 다른 미션과는 차원이 다른 양의 줄글로 요구 사항이 주어졌다. 따라서 구현에 들어가기 전 내가 만들어야 하는 프로그램에 대한 정확한 파악이 필요할 것 같아 문서화를 해 보았다.
(MBTI N답게 개발팀이라는 롤에 이입해서 리드미를 이메일을 형식으로 작성해 보았다. 그래서 제목에 첨부 파일 번호가 붙어 있다 😉)

1. 이벤트 종류 및 세부사항

다양한 종류의 이벤트를 카테고리화하여 핵심적인 내용만 적은 문서다. 프로모션을 할인 이벤트, 증정 이벤트, 배지 부여(멤버십) 총 3가지 종류로 나누어 요약하고 이벤트가 적용되지 않는 상황도 한 눈에 볼 수 있게 정리했다.
복잡한 내용을 단순하게 요약하고 나니 이벤트 객체를 만든다면 어떤 내용이 들어가야 할지, 이벤트 사이의 공통점은 어떻게 뽑을 수 있을지 등 설계에 관한 계획을 세울 수 있었다.

2. 사용자 여정 지도

예약을 하면서 사용자가 경험하는 것들을 한 눈에 보기 위해 사용자 여정 지도의 개념을 차용해 정리했다. 사실 이런 문서를 만들지 않고 출력 예제만을 봐도 괜찮지만, 프로그램이 계산해 줘야 하는 부분과 예외 처리가 되어야 하는 부분이 한 눈에 보였으면 좋겠다고 생각해 이런 표를 만들게 되었다. 덕분에 프로그램의 실행 흐름을 작은 단위로 분리하여 개발 우선순위를 정할 수 있었다.

이벤트 객체 관리하기

1. 클래스 만들기 vs. 객체 리터럴 사용하기

가장 까다로웠던 부분은 역시 이벤트 객체를 만드는 것이었다. 처음엔 Strategy 패턴을 클래스로 구현했지만, 이 방법이 생각보다 가독성이 좋지 않았을 뿐더러 어딘가에서 클래스들의 인스턴스를 생성해줘야 하는 책임을 맡게 되니 과한 설계라는 생각이 들었다. 그러다가 2주 차 공통 피드백의 JavaScript에서 객체를 만드는 다양한 방법을 이해하고 사용한다.는 말이 떠올랐고, 꼭 클래스를 사용해서 객체를 생성할 필요는 없다는 생각에 방향을 틀었다.

2. 이벤트의 공통 책임 뽑아 설계하기

이벤트의 세세한 내용을 구현하기 전에 '이벤트 객체는 어떤 역할을 해야 할까?'를 먼저 생각해 보았다. 그렇게 아래와 같은 3가지 기능이 포함되어야 한다고 생각해 각각을 함수로 분리하여 객체 간 공통 구조로 만들었다.

  1. 이벤트 적용 가능 여부를 검사하는 역할 -> isEventAvailable()
  2. 혜택을 계산하는 역할 -> getBenefit()
  3. 이벤트에 대한 정보를 제공하는 역할 -> getEvent(): {name: string, getEvent: f}

3. 각기 다른 매개변수

그 다음 마주친 난관은 매개변수를 전달하는 부분이었다. 이벤트 객체의 공통 구조를 만든 이유는 해당 객체들을 사용하는 클래스(EventCalendar) 입장에서 동일한 API를 사용해서 값을 구할 수 있도록 만들기 위함이었다.

한편 개별 이벤트 입장에서 결과를 계산하기 위해 참조해야 하는 값은 다를 수 밖에 없다. isEventAvailable()만 보더라도 크리스마스 이벤트는 크리스마스 기간인지, 주말 이벤트는 주말 여부와 디저트 메뉴가 있는지를 검사해야 했다. 이 사실을 깨닫는 순간 '이벤트 이름을 모두 검사해서 그에 맞는 값을 전달해주는 if문을 작성해야 하나..? 내 손으로 💩을 만들어냈구나.'하는 생각에 마음이 힘들어졌다.

일단 심호흡을 하고 문제를 차근차근 해결하기 시작했다. 우선 각 이벤트 객체에서 필요로 하는 isEventAvailable의 파라미터를 한 눈에 보기 위해 객체 형식으로 작성하기 시작했다. 모두 모으니 아래와 같은 객체가 만들어졌다.

{
  isWeekend: bool,
  isSpecialDate: bool,
  isChristmasPeriod: bool,
  totalPrice: number,
  orderCategories: string[],
}

위와 같이 작성하고 나니 해당 정보를 모두 담고 있는 객체를 하나만 만들면 각 객체에서는 구조 분해 할당을 사용해 필요한 값만 사용할 수 있을 거란 아이디어가 떠올랐다.

// EventCalendar.js
event.isEventAvailable(state);

// Events.js
isEventAvailable({ isWeekend, orderCategories }) {
  return isWeekend === false && orderCategories.includes(DESSERTS);
}

👏 잘한 점

미션을 제출할 당시에는 생각하지 못했지만 회고를 하면서 자료를 찾다 보니 이벤트 객체 간 공통 구조를 뽑아내는 발상은 템플릿 메서드 패턴과 유사하고, 이 객체들을 사용하는 아이디어는 책임 연쇄 패턴과 유사하다는 생각이 들었다. 디자인 패턴 적용을 의도하지 않았지만 이와 유사한 설계를 만들어 냈다는 점에서 고민한 보람이 있다고 생각했다.

그리고 모든 필드를 private 하게 유지하고 테스트를 위한 getter를 만들지 않는 데 성공했다! 접근자 파라미터를 적극적으로 사용하고 객체의 책임을 분배할 수 있는 다양한 방법을 고민하다 보니 가능했던 것 같다. 저번 주 미션에서 잘 하지 못했던 부분을 개선해서 뿌듯하다.


🤔 아쉬운 점

위에서 잠깐 언급된 EventCalendar 클래스는 날짜 정보를 설정하고, 해당 날짜에 적용 가능한 이벤트들을 배열로 만들어 이를 순회하며 혜택들을 반환한다. 클래스의 이름은 EventCalendar지만 적용 가능한 이벤트를 검사하는 책임까지 맡고 있는 것이다.

또한 이벤트 적용 가능 여부를 검사하고 이벤트 정보를 담은 객체를 반환하는 것은 모든 이벤트 객체가 거치는 공통적인 절차이기에 확장 가능성과도 맞물려 있는 중요한 로직이다. 이런 설계에 관한 로직과 비즈니스 로직이 섞여 있는 코드는 파악하는 데 불편함이 발생할 가능성이 있다는 생각이 들었다.

따라서 이벤트를 관리하는 이벤트 핸들러를 만들어 책임을 분리하는 리팩토링이 필요하다는 생각이 들었다.


eslint 설정하기

eslint를 사용하려면 패키지를 설치해야 하는데 package.json을 변경하지 못한다는 조건 때문에 사용 하지 못하다가 프리코스 커뮤니티에서 상위 디렉토리에 설치하는 방법이 공유되어서 늦게라도 적용해 보았다.

1. 프리코스 코드들을 관리하는 상위 디렉토리(이하 루트)를 생성하고, 해당 디렉토리에 프로젝트를 클론한다.

2. 루트에서 다음 명령어를 사용해 패키지를 설치한다.

npx install-peerdeps --dev eslint-config-airbnb
npm install -D eslint prettier
npm install -D eslint-config-prettier

3. 프로젝트 폴더 안 package.json과 같은 레벨에 .eslintrc.json 파일을 생성해 아래 코드를 붙여 넣는다.

{
  "env": {
    "es6": true
  },
  "parserOptions": {
    "ecmaVersion": "latest",
    "sourceType": "module"
  },
  "extends": ["airbnb-base", "prettier", "jest"],
  "rules": {
    "class-methods-use-this": "off",
    "import/prefer-default-export": "off",
    "import/extensions": ["off"],
    "max-depth": ["error", 2],
    "max-lines-per-function": ["error", 15],
    "indent": ["error", 2],
    "quotes": ["error", "single"],
    "semi": ["error", "always"],
    "eqeqeq": "error",
    "no-undef": "off",
    "no-console": "warn",
    "no-dupe-args": "error",
    "no-unreachable": "error",
    "no-unused-vars": "warn"
  },
  "plugins": ["import"]
}

사실 3주 차 미션도 eslint config 파일을 만들어 사용했는데 아무런 에러도 뜨지 않아서 '혹시 내가 코드를 잘 작성하고 있는 건까..?'하는 생각도 들었다. 하지만 어림도 없지. 올바르게 설정을 하니 내 코드는 에러 투성이었다.😅

처음엔 익숙한 방식으로 코드를 작성했을 뿐인데 빨간색의 보기 싫은 에러가 뜨니 스트레스를 받았다. 하지만 덕분에 가독성을 저해하는 코드를 작성하고 있었다는 사실을 깨달았고, 조금 더 나은 방식을 고민하게 됐다. 미션을 마치고 나니 참 좋은 도구라는 생각이 들었다.


profile
블로그 옮겼습니다 | dan.io.kr

0개의 댓글