2주 차를 마무리하고 3주 차 로또 과제를 진행하며, 지난주 회고를 작성해 본다. 1주 차 과제에서는 구현 자체보다 다양한 예외 케이스를 처리하는 것이 어려웠던 것 같다. 반면, 2주 차 과제인 자동차 경주에서는 예외 처리보다는 책임 분리를 어떻게 더 잘할 수 있을지에 대한 고민을 많이 하게 되었다. 그리고 1주 차에 리팩토링 했던 방법을 기억하며 2주 차에 적용하는 과정이 자연스럽게 이어지면서, 조금 더 수월하게 구현할 수 있었다.
이번 주차에는 소감문에 중간 회고로 3가지 항목을 작성하도록 안내받았다.
잠시 잊고 있었던 지원서를 다시 보며 프리코스 목표에 대해 다시 생각해 보았고, 그때의 나와 프리코스를 진행한 지금의 나의 생각이 다르다는 것을 느꼈다.
지원서에 적은 나의 목표는 전반적으로 "부족함을 찾아 배우겠다" 라는 내용이었다. 하지만 여러 참가자들의 각기 다른 코드들을 보면서, 모든 부분이 부족해 보일 것 같다는 생각을 했다. 과제를 잘 진행하다가도, 다른 코드들을 살펴보면 조바심이 느껴졌다. '이 사람들은 도대체 어떻게 생각한 걸까?'라는 의문도 든다.
짧은 시간 안에 내가 더 많이 배워갈 수 있는 것이 무엇일까 고민하며, 이것저것 보기보다 우선 빠르게 구현한 코드를 스스로 점검해 개선점을 찾고, 그동안 공부한 지식을 녹여 리팩토링을 진행했다. 너무 많은 정보에 휩싸여 방향을 잃기보다는, 오히려 내가 잘한다고 생각하는 부분이나 자신 있는 부분을 찾고 더 잘 활용하는 것이 좋겠다고 판단했다. 프리코스에 참여하게 된 이유 중 하나가 자신감을 얻고 싶었던 것이기 때문에, 이 목표가 더 나에게 맞는 것 같다는 생각이 들었다.
이번 프리코스에서는 Airbnb 컨벤션 가이드가 안내되어 있었고, 이를 착실하게 설정한 후 평소처럼 신나게 구현을 하면서 당연히 적용될 것이라 생각하고 따로 검사를 하지 않았다가 나중에야 적용되지 않았다는 사실을 알게 되었다.
나는 독학을 해오며 프로젝트도 혼자 진행했기 때문에 협업 경험이 부족하다. 현재 백엔드 개발자와 협업하여 새로운 프로젝트를 진행하고 있지만, 프론트엔드는 여전히 혼자 맡고 있기 때문에 여러 명과 협업하며 부딪혀 본 경험이 없다. 컨벤션을 신경 써야 하는 환경을 마주해보지 않았기에, 그 중요성을 간과했던 것 같다. 새로운 프로젝트에서 Eslint와 Prettier는 기본과도 같은 도구인데, 그 기본을 말 그대로 ‘그냥’ 써왔다는 점을 깨닫고 다시 한번 반성했다.
프리코스 과제를 제출하는 과정에서, 제출 전에 안내문을 한 번 더 확인하게 된다. 처음에는 별거 아닌 것처럼 보였지만, 이러한 규칙을 제공받은 경험이 부족했기 때문에 그 작은 규칙 하나하나에서도 배우는 점이 있었다고 느낀다.
1주 차가 끝나고 공통 피드백을 받았다. 함께 첨부된 문제 풀이 영상을 보며, 내가 과제를 너무 어렵게 해석하고 받아들이고 있었다는 것을 느꼈다. 특히 테스트 코드에 겁을 먹고, 예외 처리에 지나치게 신경 쓰느라 처음 기능 목록 정리에 많은 시간이 걸렸다.
구현할 기능들을 정리하면서, 1주 차에서 얻은 개선점과 학습 목표를 바탕으로 새로운 목표를 세웠다.
async run() {
await this.#processCarNames();
await this.#processTotalRounds();
const game = new Game(this.#carNames, this.#totalRounds);
game.start();
game.printGameResult();
}
run 메서드는 자동차 이름을 입력받고, 경주 횟수를 설정한 후 새로운 게임 인스턴스를 생성한다. 이후, 해당 게임을 시작하고 마지막으로 결과를 출력하는 역할을 한다.
1주 차에서는 이 부분을 try-catch문으로 감싸 예외 처리를 했지만, 2주 차에서는 이를 제외했다. 예외 케이스를 최대한 별도의 로직으로 처리하면서 놓친 부분이다. try-catch문을 사용하면 미처 처리하지 못한 에러가 발생했을 때 사용자에게 적절한 안내를 제공할 수 있다. 안전하게 3주 차에는 이 부분을 적용하도록 한다.
class InputProcessor {
static async get(value) {
const input = await Console.readLineAsync(value);
return input;
}
}
// 사용
async #processCarNames() {
const input = await InputProcessor.get('경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분) \\n');
this.#carNames = input.split(',');
Validator.carNames(this.#carNames);
}
사용자의 입력을 받는 클래스를 만들어, get 메서드로 입력값을 가져오는 방식으로 변경했다. 이렇게 함으로써, Console.print와 같은 출력 로직이 어떻게 작동하는지 알 필요 없이, 입력 처리 부분을 명확하게 분리할 수 있었다.
// Car.js
class Car {
constructor(name) {
this.name = name;
this.score = 0;
}
move() {
const result = generateRandomNumber();
const isMovingForward = result >= 4;
if (isMovingForward) {
this.score += 1;
}
}
record() {
return `${this.name} : ${'-'.repeat(this.score)}`;
}
}
자동차 클래스는 내부적으로 이름과 점수를 갖는다. 자동차를 움직일 수 있고, 점수에 따라 요구사항인 `-` 출력을 기록할 수 있다. 이번 과제에서는 외부에서 쓰는 메서드는 최대한 간결하게 표현하고자 했다. 정적 메서드로 쓰면 클래스명 자체로 의미가 부여되고, 인스턴스를 생성하면 인스턴스명으로 의미를 부여할 수 있기 때문에 내부 메서드명을 너무 복잡하지 않게 작성했다.
// Game.js
constructor(cars, totalRounds) {
this.#cars = cars.map((car) => new Car(car));
this.#totalRounds = totalRounds;
this.#roundRecord = '';
}
Game 클래스는 인스턴스를 생성할 때, 자동차 이름을 받아 새로운 Car 인스턴스를 생성하도록 했다. `this.#cars`는 이제 각각의 자동차 이름을 가진 인스턴스 배열이 된다.
this.#cars = cars.map((car) => new Car(car));
완성된 코드만 보면 간단하고 쉬워 보이지만, 처음 Car 클래스를 분리하겠다는 생각을 했을 때는 이 로직을 떠올리는 데 시간이 걸렸다. 처음에는 단순히 이름만 반환하는 방식으로 구현했다가, 이후 인스턴스를 배열에 담아두는 방식으로 변경했다. 이렇게 하면 this.#cars를 사용할 때 Car 클래스의 메서드들을 활용할 수 있어 사용성이 훨씬 좋아진다. 비슷한 고민을 지금도 하고 있는데, 로직을 바로바로 떠올리고 분리하는 과정이 쉽지 않다.
export default function checkArrayLength(value) {
return value.length < 2;
}
describe('유틸 함수', () => {
test('배열의 길이가 2보다 작으면 true를 반환한다.', () => {
expect(checkArrayLength(['pobi'])).toBe(true);
});
});
이번에 분리한 유틸 함수에 대해 테스트 코드를 작성하면서 헷갈렸던 부분은 예외를 처리하는 유틸 함수가 예외 상황에서 true를 반환해야 할지, false를 반환해야 할지에 대한 것이었다. 예를 들어, 이번 과제에서는 경주에 참여하는 자동차가 2대 이상이어야 게임이 실행되도록 했는데, 자동차 이름 배열의 길이가 2보다 작을 경우 예외 처리를 하려고 했다. 이때, `checkArrayLength` 함수의 조건을 배열의 길이를 2 미만일 때로 할지, 아니면 2 이상일 때로 할지 고민되었다.
`isNaN`과 같은 내장 API를 사용하면 함수 이름 자체에서 반환값을 예상할 수 있지만, 커스텀 유틸 함수는 내가 원하는 조건에 맞춰 동작하기 때문에 일정한 규칙이 필요하다고 느꼈다. 코드를 모두 작성하고 보니, 예외로 처리되는 상황에서 전체적으로 true를 반환하도록 구현했다는 것을 알게 되었다.
static arraySize(value) {
if (checkArrayLength(value)) {
handleError('[ERROR] : 자동차가 2대 이상 참여해야 경주를 시작할 수 있어요.');
}
}
이 고민을 하게 된 이유는, 사용하는 곳에서 if문을 통해 에러를 처리하는 방식을 고려했기 때문이다. 주관적이긴 하지만 사용한 부분을 보면 부자연스럽게 느껴진다. 조건이 맞을 때 true를 반환하고, if (!checkArrayLength(value)) {}처럼 작성하면 배열의 길이가 조건에 맞지 않을 때 false로 인식되어, 에러를 더 직관적으로 처리할 수 있지 않을까라는 생각이 들었다. 특히 예시처럼 예외 처리를 위한 유틸 함수명을 모두 `check-`로 통일해서 어떤 결과를 의도한 것일지 더 헷갈린다.
3주 차 과제에서도 이 부분에서 시간이 많이 소요되고 있다. 조금 더 작성하면서 연습이 필요하겠지만, 테스트 코드를 직접 작성해보니 내가 구현한 코드를 어떻게 실행하고 싶은지에 대해 고민해 보게 된다는 점이 테스트 코드의 장점 중 하나라는 생각이 든다.
현재 3주 차 과제의 마지막 기능을 구현하고 있는데 1주 차 회고 때처럼 이번에도 과제를 진행하면서 회고를 쓰고 있다. 이렇게 해야 복습하면서 개선점을 찾고, 놓쳤던 부분을 짚고 넘어갈 수 있기 때문이다. 프리코스는 과제 하나하나가 작은 단위여서 다시 복습하고 되돌아보기에 부담스럽지 않다. 그리고 벌써 1주 차 과제를 보면 '왜 저렇게 해놨지?'라는 생각이 든다. 어제의 코드도 이미 레거시라는 말이 괜히 있는 게 아닌가보다.
이번에는 또 다른 목표를 세워 과제를 진행하고 있는데, 확실히 규칙과 목표를 세우고 의식할수록 진도가 더뎌진다. 2주 차에 비해 고민해야 할 부분이 많아지면서, 커밋 하나하나에 정말 많은 고민을 하고 있다. 새롭게 바꿔본 진행 방법이 잘 맞길 바라며 이번 주차도 무사히 마무리하길 기대해 본다.
[우아한 테크 코스 7기] 프리코스 4주 차 회고 (1) | 2024.11.19 |
---|---|
[우아한 테크 코스 7기] 프리코스 3주 차 회고 (1) | 2024.11.13 |
[우아한 테크 코스 7기] 프리코스 1주 차 회고 (3) | 2024.10.24 |