지난 4주 차 회고에서 다음 단계로 집중해야 할 것으로 1. 미션 다시 구현해 보기 2. 동료이용하기 가 있었다.
혼자 미션들을 다시 구현해 보면서 코드리뷰를 받을 생각을 가지고 있었는데
감사하게도 나에게 맞는 조건 아래 동기분이 스터디를 열어 주셔서 참가했다.
스터디는 미션 다시 풀기와 코드리뷰를 중점적으로 진행했다.
어려웠던 미션(로또, 크리스마스)과 작년 최종 코딩테스트 문제를 실전 코딩테스트처럼 5시간 내로 각자 구현하고
다음 이틀 동안 코드리뷰를 하는 방식으로 진행했다.
프리코스기간 동안 조금 바빴어서
이번에는 소수의 인원이 약속한 만큼 진정한 몰입을 하고 싶었다.
미션들을 다시 구현해 보며 가장 중점적으로 신경 썼던 점은 피드백 적용하기이다.
본 프리코스를 진행하면서 읽어도 이해되지 않은 점도 있었고 적용하지 않은 부분도 많았어서
이번기회에 집중해서 적용해 보며 구현해 보기로 했다.
그리고 그 과정에서 미리 알았더라면 좋았을 싶은 부분과 새롭게 알게 된 개념들을 포스팅하고자 한다.
1. ESLint, Prettier 설정하기
//.eslintrc.js
rules: {
// 들여쓰기 깊이 제한
'max-depth': ['error', 2],
// 함수의 길이 제한
'max-lines-per-function': ['error', { max: 15 }],
// import 파일 확장자 표시
'import/extensions': ['error', 'always', { ignorePackages: true }],
// class 메소드 안에서 this 사용 off
'class-methods-use-this': ['off'],
}
피드백과 프로그래밍 요구사항에 들여 쓰기 제한(depth:2)과 함수의 길이(15라인) 제한이 추가된다.
코딩을 하면서 일일이 세고 있을 수 없는 노릇이므로 ESLint가 자동으로 경고 표시를 할 수 있게 설정을 해준다.
'import/extensions': ['error', 'always', { ignorePackages: true }]
우테코 프리코스 6기는 node v18.17.1 환경에서 실행되는 프로그램을 작성하는데
확장자를 명시해주지 않으면 실행이 안되고 명시하면 ESLint에서는 경고를 뿜는다.
프로그램 실행을 꼭 해야 하므로 확장자를 무조건 명시해 주는 조건을 걸어주었다.
'class-methods-use-this': ['off']
단순히 받은 값을 계산해서 리턴하는 순수함수와 같은 기능을 하는 클래스 내 메서드들은
this를 사용할 필요가 없는데 계속 경고문이 떠 경고를 off 해주었다.
2. 시각적 구분자
자바스크립트에서 숫자에 언더바(_)는 가독성을 높이기 위해 시각적 구분자로 사용할 수 있다.
미션에서 금액을 작성할 일이 많았는데 언더바(_)를 사용할 경우 0이 많아도 어지럽지 않았다. 🤣
const a = 6_000;
const b = 7_000;
console.log(a+b); //13000
언더바(_)는 실제 숫자의 값에 영향을 미치지 않으며 자바스크립트 인터프리터에서 무시된다.
* 언더바(_) 이외의 다른 문자는 사용할 수 없다.
3. JS Doc
사용자의 입력을 받아 유효성 검사를 진행하는 로직이 매 미션마다 있었다.
처음에는 입력값을 바로 들고 와 유효성 검사를 진행했는데 분명 맞는 로직인데 예상한 대로 안 나와서
console.log로 열심히 디버깅을 한 결과 (터미널에서는 색깔로만 숫자, 문자열이 구분된다.)
타입이 원인인 경우가 종종 있었다.(여기서 타입스크립트의 필요성 여실히 느꼈다.)
이번 미션에 적용하진 않았지만 JSDoc을 사용하면 타입을 명시해 줄 수 있다.
/**... */ 주석 사이에 작성하면 JSDoc Parser에 의해 API 문서를 생성할 수 있다.
@ts-check를 주석으로 추가하면 TypeScript처럼 타입 및 에러 체크가 가능하다.
4. 비즈니스로직과 UI로직을 분리한다.
3주 차 피드백 : 비즈니스 로직과 UI 로직을 한 클래스가 담당하지 않도록 한다. 단일 책임의 원칙에도 위배된다.
3주 차 미션이었던 로또 게임 코드를 가져와봤다.
class LottoResult {
//... 생략
static getPrize(winningResult) {
//UI 로직
Console.print('<당첨 내역>')
//비즈니스 로직
let totalPrize = 0;
winningResult.forEach((result, index) => {
if (result !== 0) {
totalPrize += result * NUMBERS.prize[index];
}
//UI 로직
Console.print(`${MESSAGES.matchResult[index]}${MESSAGES.matchQuantity(result)}`);
});
return totalPrize;
}
}
코드를 보면 상금을 계산하고 OutputView를 통해 화면에 보여주는 것까지 getPrize 메소드가 담당하고 있다.
LottoResult 클래스는 로또 상금을 계산하는 비즈니스로직과 계산이 완료된 금액을 화면에 보여주는 책임을 가지고 있는 것이다.
비즈니스로직과 UI로직을 분리한 코드는 아래와 같다.
class Controller{
//...생략
start() {
OutputView.printLottoResult(this.#lottoResult.getResult());
}
getLottoResult(){
this.#lottoResult = new LottoResult();
}
}
const OutputView = {
printLottoResult(index, result) {
Console.print(`${MESSAGES.matchResult[index]}${MESSAGES.matchQuantity(result)}`);
}
}
LottoResult 클래스의. getResult() 메소드로 비즈니스로직을 처리하고
OutputView 객체에서. printLottoResult()로 값을 보여주는 UI로직을 처리했다.
단일 책임 원칙 (SRP, Single Responsibility Principle)
객체는 단 하나의 책임을 가져야 한다는 원칙을 말한다.
그 이유는 하나의 객체가 많은 책임을 가지고 있으면 변경사항이 생겼을 때 연쇄적으로 처리해야 할 코드들이 많아지기 때문이다.
따라서 단일 책임 원칙을 잘 따른다면 유지보수에 용이해지고 책임을 분배로 코드의 가독성 또한 향상시킬 수 있다.
5. 객체는 객체스럽게 사용한다.
3주 차 피드백에 객체는 객체스럽게 사용한다는 제목 아래 이런 내용이 있었다.
데이터를 가지는 객체가 일하도록 부분이 이해가 가질 않았으나 이번 스터디를 진행하며 이해가 되었다.
늘 클래스 안에서 비즈니스로직을 수행한 후 값을 return 하는 메소드를 작성해서 데이터를 꺼내 외부에서 꺼낸 데이터를 가지고 다른 로직을 수행하였다. 이렇게 작성하게 되면 로직이 외부에 노출되고 의도치 않은 결괏값을 가질 수 있다.
이렇게 데이터를 외부에서 꺼내어 사용하는 대신 데이터를 가진 객체를 전달해 내부에서 관련 로직을 처리하면 객체 스스로 상태를 관리하여 캡슐화시킬 수 있다.
예를 들어 크리스마스 프로모션 미션에서 방문 날짜, 주문한 메뉴에 따라 받을 수 있는 할인과 혜택이 달라졌다.
처음 코드를 작성했을 때는 방문 날짜, 주문한 메뉴를 입력받아 외부에서 로직을 처리했었다.
하지만 위 피드백을 이해하고 난 후에는 데이터를 가지는 객체가 일할 수 있게 코드구조를 설정했다.
class Controller {
constructor() {
this.#event = new Event();
}
async startPlan() {
// 직접적인 데이터 대신 입력값에 대한 객체를 생성한 인스턴스를 넘긴다.
this.#event.applyEvent(this.#date, this.#menu);
}
}
class Event {
constructor() {
this.#gift = new Gift();
this.#discount = new Discount();
}
//gift, discount 클래스에서 date와 menu 데이터를 가지고 있는 객체가 일을 하도록 한다.
applyEvent(date, menu) {
this.#gift.apply(menu);
this.#discount.apply(date, menu);
}
}
class Discount {
apply(date, menu) {
if (date.isBeforeChristmas()) {
this.#discount.push(this.#getChristmasDiscount(date));
}
if (!date.isWeekend() && menu.getQuantityByCategory(CATEGORY.dessert)) {
this.#discount.push(this.#getWeekdayDiscount(menu));
}
if (date.isWeekend() && menu.getQuantityByCategory(CATEGORY.main)) {
this.#discount.push(this.#getWeekendDiscount(menu));
}
if (date.isSpecialDay()) {
this.#discount.push(this.#getSpecialDiscount());
}
}
}
6. 테스트 코드작성(의존성 주입, DI)
테스트 코드에 대한 막연한 해보지 않은 두려움 같은 게 있었는데 기본적으로 작성하는 방법은 어렵지 않았다.
1. 어떤 메소드를 실행했을 때 2. 어떤 값을 기대하는지
의 구조로 작성을 하면 된다.
test.each([
[1, 0],
[25, 24],
])('countdDay 메소드 테스트', (date, expected) => {
const day = new Day(date);
expect(day.countdDay()).toBe(expected);
});
단, 입력을 받거나 데이터 통신을 하는 등 예측할 수 없는 동작을 의존하는 메서드를 테스트해야 할 때에는 가짜 함수 mock함수를 만들거나 함수를 호출했는지 여부만 알 수 있게 spy함수를 쓰는 등 비교적 복잡한 개념이 들어간다.
코드리뷰를 하면서 constructor 메소드의 파라미터에 객체를 생성한 분을 보고 질문을 남겼더니 의존성 주입이라는 개념을 알려주셨다.
의존성 주입이란 해당 클래스가 작동하는데 필요한 객체들을 외부에서 주입받아서 사용하는 일종의 디자인패턴이다.
//원래코드
class MyClass {
constructor() {
this.#event = new Event();
}
}
// 의존성 주입 코드
class MyClass {
constructor(event = new Event()) {
this.#event = event;
}
}
// 테스트 코드 변화
// 원래 코드를 테스트할 때
const myObject = new MyClass();
// 테스트 수행
// 의존성 주입을 적용한 코드를 테스트할 때
const mockEvent = createMockEvent(); // 가상의 Event 객체 생성
const myObject = new MyClass(mockEvent);
// 테스트 수행
의존성을 가지는 객체를 실제로 만들지 않고 특정 동작을 createMockEvent 함수를 이용해 가짜 객체로 만들어서 쉽게 테스트할 수 있다.
7. 자바스크립트에서 객체를 만드는 방법
처음 과제를 받았을 때 class로 시작하는 App.js 파일을 보고 당황스러웠던 기억이 난다.
class는 자바스크립트를 공부할 때 이런 것이 있구나~ 하고 사용할 일이 없어 개념적으로만 알고 있었기 때문이다.
new 키워드로 객체를 생성하고 인스턴스를 사용할 일이 없을 때에는 메소드를 모두 static으로 구현한 적도 있다.
자바스크립트를 자료형을 공부할 때 객체는
const obj = {}
이런 형태라고 배웠다. 여기에 프로퍼티를 추가하면
const obj = {
name: 'summer'
}
이런 형식이 되고 obj.name 혹은obj ["name"]으로 name의 value에 접근할 수 있다.
이렇게 정적 메서드만 사용하는 유효성 검사나 UI로직 같은 객체를 만들려면 중괄호({})를 이용해 객체 리터럴로 생성해 주면 훨씬 깔끔한 코드를 만들 수 있다.
const DateValidator = {
validateDate(date) {
this.isEmpty(date);
this.isNumber(date);
this.isInRage(date);
},
isEmpty(date) {
},
isNumber(date) {
},
isInRage(date) {
},
};
export default DateValidator;
8. 예외 처리
4개의 과제에서는 요구사항이 자세하게 들어있다. 하지만 예외상황은 스스로 생각해서 꼼꼼하게 처리해야 한다. 특히 사용자가 입력하는 값에 대해서 예외처리를 잘해야 테스트 통과는 물론 올바르게 동작하는 코드를 작성했다고 할 수 있다.
미션들이 실제 어플리케이션이라고 생각했을 때 사용자 입력값에 대하여 실수로 남긴 공백을 제거해서 사용자 경험을 향상시킨다던지 정수를 입력해야 할 때 음수나 소수를 입력하는 경우를 고려해 본다던지 사용자의 입장에서 일어날 수 있는 다양한 변수들을 깊게 생각해 보는 시간을 가질 수 있었다.
9. 데이터를 다루기 위한 고차함수 사용
모든 과제에는 최소한의 파일만 처음에 주어진다. 데이터의 생성도 입력도 출력도 모두 스스로 코드를 작성해야 한다.
나는 프런트엔드를 지원했기에 json형식으로 데이터를 받는다 생각하고 constant 폴더에 객체로 대부분의 데이터를 저장했다.
에어비앤비 스타일 가이드에 따라 코드를 작성하게 되어있기 때문에 for문 없이 객체와 배열을 순회할 수 있는 방법을 찾아야 했다.
여기서 드물게 사용해 봤더 Set, Map 같은 객체는 물론 map, filter 같은 고차함수 중에서도 결괏값을 누적시키기 위해 가장 안 친한 reduce 함수를 가장 많이 사용했다. 그리고 entries() 같은 처음 보는 메서드도 사용해 보며 자바스크립트의 다양한 API의 세계를 공부할 수 있었다.
10. 나만의 기준 세우기
프리코스가 종료되고 난 후에는 많이 줄었지만 코드리뷰 요청하는 게시글이 많이 올라왔었다. 당시 내 코드에 자신이 많이 없었기에 배울 점을 찾아 다른 사람들의 코드를 엄청 많이 봤다.
같은 과제이지만 이렇게 까지 개개인이 모두 다른 로직을 작성하는 게 신기했고 회차가 거듭될수록 함께 성장하기라는 우테코 취지 하에 어느 정도 유명한(?) 개념들은 대부분의 분들이 적용하고 있었다. 매주 새로운 과제 시작 전 진행되는 코치들의 수다 타임(a.k.a 코수타)에서 프리코스가 끝나갈 때쯤 모든 분들의 실력이 비슷해질 거라고 그런 식으로 코스 설계를 해놓았다고 말씀하신 적이 있는데 당시엔 전혀 믿지 못했지만 지금 돌아보니 어느 정도 맞는 말씀이었다.
얼마 전 코드리뷰 스터디를 하고 내 코드를 돌아봤을 때 내 주관이 있는 코드인가에 대한 의문이 있었다.
코드를 작성하면서 코드리뷰 동안 참고한 코드들을 되뇌며 적용해보기도 했고 스스로 생각하기에 A도 좋은 방법 같지만 B의 방법이 있다면 B로도 적용해 보았다. 이때 학습적으로 가장 도움을 많이 주셨던 스터디원분이 서로의 코드를 보면서 많이 성장할 수 있는 것이 바로 프리코스의 취지 아니겠냐며함께 성장하기를 리마인드 시켜주셨다. 그리고 동시에 본인 코드 스타일을 잃지 않기도 당부해 주셨다.
디스코드 커뮤니티에서도 어떠한 방법이 좋은 방법인지 아니면 본인 취향에 따라 달라지는 점인지에 대한 글들이 많았다. 코드리뷰를 할 때도 제안보단 의도에 대한 궁금증을 많이 남겼는데 달아주신 답변들을 보면서 결국 정답이란 없고 본인의 생각에 대한 근거 아래 기준을 세우는 것이 스스로 성장하는 길임을 깨달았다. 다양한 방법을 적용해 보고 내가 생각하기에 가장 좋은 방법으로 작성한 코드가 내 코드가 되는 것이다.
오늘(12월 11일) 1차 합격발표가 났고 나는 뜨거운(불) 합격을 했지만 프리코스만 거쳐도 충분한 학습경험을 얻은 것 같아 만족하는 지난 한 달이었다.
그리고 메일에는 로드맵이 첨부되어 있는데 로드맵에서 추천해 주는 책 목록을 공유하며 우테코 프리코스 6기 회고 찐막합니다!
1. 소프트웨어 장인 - 산드로 만쿠소
2. 1만 시간의 재발견 - 안데르스 에릭슨
3. 함께 자라기 - 김창준
4. 좋은 코드, 나쁜 코드 - 톰 롱
5. 클린 코드 - 로버트 C. 마틴
6. 테스트 주도 개발 시작하기 - 최범균
7. 테스트 주도 개발 - 켄트 백
8. 리팩터링 - 마틴 파울러
9. 객체지향의 사실과 오해 - 조영호
10. 코어 자바스크립트 - 정재남
11. 쏙쏙 들어오는 함수형 코딩 - 에릭 노먼드
12. 프레임 워크 없는 프론트엔드 개발 - 프란세스코 스트라츨로
13. 타입스크립트 프로그래밍 - 보리스 체르니
Ref
1. 단일 책임 원칙
3. 의존성 주입
'우테코 프리코스' 카테고리의 다른 글
[우테코 프리코스] 4주 차 회고 (7) | 2023.11.28 |
---|---|
[우테코 프리코스] 3주 차 회고 (1) | 2023.11.23 |
[우테코 프리코스] 2주 차 회고 (3) | 2023.11.03 |
[우테코 프리코스] 1주 차 회고 (7) | 2023.10.26 |