우아한 테크코스 프리코스가 4주차를 기점으로 끝났다. 정말 배운것도 많았고, 다양한사람들을 만나서 재밌었다.
이번에는 기본 템플릿으로 어느정도 분리된 클래스, 함수를 제공해줬다. 그리고 View와 관련된 함수나 클래스들은 도메인로직에 들어가면 안된다는 제한사항이 있어서 나는 MVC라는 디자인패턴으로 이번 미션을 진행했다.
자세한 설명은 오른쪽 링크를 참조 바란다.https://github.com/woowacourse-precourse/javascript-bridge
우선 이번에는 기본적으로 제공한 템플릿이 좀 있었고, 클래스별 요구사항이 좀 있었다.
그래서 나는 요구사항들을 읽어보고 왜 이런 요구를 했을까를 먼저 고민했다.
//요구사항들
도메인 로직에 단위 테스트를 구현해야 한다. 단, UI(Console.readLine, Console.print) 로직에 대한 단위 테스트는 제외한다.
핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 구분한다.
InputView 에서만 MissionUtils의 Console.readLine() 을 이용해 사용자의 입력을 받을 수 있다.
BridgeGame 클래스에서 InputView, OutputView 를 사용하지 않는다.
이를 보고 내가 내린판단은 MVC와 유사하다고 생각했다.
왜냐하면 내가 그동안 작성했던 코드들에서는 적절히 Class를 분리했다고 했지만 사실 정확한 용도에 맞는 분리를 한건 아니었다.
그래서 이번에는 이런부분을 최대한 신경써서 M에서는 이번 기능에대한 중요 계산하는것들, V에서는 오직 UI(내 코드에서는 console관련), C에서는 M과 V의 값들을 중간에서 처리하는 역할만 하도록 했다.
거기에 더해 저번주에도 적용했던 가독성을 고려한 코드를 작성하려했다
root
├── package-lock.json
├── package.json
├── __test__
| ├── 메인테스트.js
| └── 기능 테스트 모음.js
├── docs
| └── README.md
└── src
├── App.js
├── BridgeController.js //Class
├── BridgeGame.js //Class
├── BridgeMaker.js //Function
├── BridgeRandomNumberGenerator.js //Function
├── InputView.js //Function
├── OutputView.js //Function
├── constant
| └── constant.js
├── utils
| ├── Validation.js
M,V,C와 같은 단어들이 많이 나오는데 자주나와서 여기서 정의를 하고 시작하겠다.
M = Model, V = View, C = Controller
MVC패턴이란
프론트엔드에서 MV* 아키텍쳐란 무엇인가요?
위의 블로그들을 참고했습니다.
원래 디자인패턴에 대한 지식이 많지 않았기에 MVC패턴에 대해서 먼저 공부했다.
내가 배우고 정리한것들은 아래와 같다.
우리의 어플리케이션이 유저들에게 제공될때 제일먼저 비춰지는건 우리의 V, 해당 V에서 발생한 이벤트들을 C를 거쳐서 M으로 가서 해당 데이터를 처리한다. 해당 데이터가 처리됐을떄 C를 거쳐서 다시 V에 처리한 데이터를 표시하거나 M에서 갈 필요없이 C만 거쳐서 V가 변경되기도한다.
그래서 서로간에 유기적으로 소통해야하며 V에 M에관한 로직이 있으면 안돼고 그 반대또한 마찬가지다.
그래서 나는 이번 프로젝트를 이런 관점에서 생각했고 V는 InputView, OutputView 함수에서 정의하고 해당 값들이 필요하면 C(BridgeController)를 통해서 해당 함수를 호출하며 V에서 발생한 Event들과 관련된 데이터를 처리해야할 경우 M(BridgeGame)에서 처리했다.
여기서는 오로직 계산에 관련된 기능들만 정의했다. 그리고 상태들이 저장될 필요가 있어서 Class로 두고 시작했고 상태들은 필드에 정의하고 사용했다.
move(userCommand) {
const moveResult = this.compareCommandAndBridge(userCommand);
return moveResult;
}
compareCommandAndBridge(userCommand) {
const compareResult = userCommand === this.bridge[this.count];
this.count += 1;
if (compareResult) {
this.toPrintBridge.bind(this)(compareResult, userCommand);
if (this.count === this.bridge.length) return "END";
return true; //
} else {
this.toPrintBridge.bind(this)(compareResult, userCommand);
return false;
}
}
toPrintBridge(compareResult, userCommand) {
const compareResultToPrint = compareResult ? "O" : "X";
this.printArr = [...this.printArr, { compareResultToPrint, userCommand }];
}
...
위 코드처럼 count와 같은 상태들을 직접 변경시키는걸 진행하고 있으며 this.printArr같은 값들도 직접변경시키면서 이 클래스에서는 상태들을 직접변경시키는 일을한다.
V에서는 모든 UI관련 함수들을 정의했다. 이번에 값들을 우테코에서 제공해준 라이브러리를 이용해서 Console창으로 값을 받고 띄어주는 UI를 진행했는데 InputView에서는 값을 입력받는 함수들, OutputView에서는 오로직 출력만 담당하는 함수들을 선언했다.
readMoving(callback, nextCallback, endCallback) {
Console.readLine(ANNOUNCEMENT_MESSAGE.MOVE_SPACE, (userInput) => {
const validation = validateIsBridgeWords(
userInput,
callback,
nextCallback
);
if (validation) {
const result = callback(userInput);
if (result === "END") return endCallback(BRIDGE_DETAIL.SUCCESS);
if (result) this.readMoving(callback, nextCallback, endCallback);
else if (!result) nextCallback();
}
});
},
}
코드가 콜백을 넘겨받고 실행하는등 더러운데 이게 readLine이라는 Method가 비동기적으로 실행하기 때문에 콜백을 넘겨받고 값을 받은후 실행할 수 있도록하기위해 이렇게 작성했다.
코드를 읽어보면 View를 제외한 아무런 기능도 하지않는다. Claaback함수를 실행하긴 하지만 이건 위에서 말한 비동기 코드기 떄문에 그런것일 뿐이다.
마지막으로 대망의 Controller다.
위에서 선언한 M과 V를 적절히 소통하면서 M까지 거칠필요가 없는 상태값들또한 C에서 담아두고 사용했다.
constructor() {
this.bridgeGame = new BridgeGame();
}
play() {
this.printBridgeGameStart();
}
printBridgeGameStart() {
OutputView.printStart();
this.getBridge();
}
getBridge() {
InputView.readBridgeSize(
this.makeBridge.bind(this),
this.startBridge.bind(this)
);
}
게임이 시작되면 (V에서 사용자가 이벤트를 발생하면) 내가만든 어플에서는 해당 이벤트가 Play라는 method로 정의했다.
그리고 이 Class에서 Constructor를 이용해서 아까 만든 M(BridgeGame)을 선언해준다.
그래서 controller에 요청을하고 유저에 이벤트에맞게 알맞은 값을 출력해야한다.
하지만 게임이 시작됐다는 V는 상태를 변경시킬필요가 없기때문에 M을 거치지않고 바로 원하는 값만 출력한다.
하지만 아래처럼 M이 필요한 로직은 적절히 상태를 변경시키고 변경시킨 상태를 다시 V에 전달한다.
moving(userInput) {
const result = this.bridgeGame.move(userInput);
this.printMap.bind(this)(this.bridgeGame.printArr);
return result;
}
여기있는 코드들도 자세히보면 M과 V에있는 코드들을 실행만 할뿐 어떤 계산도 하지않는 단순히 전달하는 기능만한다.
저번에는 단위테스트를 통해서 내가 만들고 있는 기능에대한 피드백을 용도로 사용을 했다.
근데 너무 작은 단위테스트만 했기에 내가 실제로 코드를 리팩토링 하는경우가 생겼을떄 내가 만든 테스트들로 모든걸 해결하긴 부족해서 직접 어플리케이션을 돌려보면서 리팩토링을 진행했다.
그래서 이번에는 후에 리팩토링할때도 문제가 없도록 단위테스트를 많이 만들려고 했다라고 말하고싶지만 시간이 부족해서 다 작성을 못했다.
그래도 이런부분을 추가적으로 리팩토링할 생각이다.
위에서 MVC에 의거해 적절한 분리를 잘했기때문에 이건 시간문제일것 같다라는 생각을 했다.
우테코를 하기전에는 모든걸 함수로 실행하고 리액트에서도 함수형컴포넌트로 작성했기떄문에 bind가 있는지도 몰랐다.
하지만 이번에 해당 클래스에서 다른 클래스의 Method를 처리해야하는 경우가 생겼는데 이때 비동기때문에 this scope가 일치하지 않아서 오류가 많이 발생했다.
그래서 이를 해결하기 위해서 bind를 사용했고 위에서 작성한 코드들을 보면 bind가 떡칠되있다.
bind에 대한 설명은 예시로 들면서 하겠다.
//BridgeController.js
getUpDownCommand() {
InputView.readMoving(
this.moving.bind(this),
this.getRetryCommand.bind(this),
this.printResult.bind(this)
);
}
moving(userInput) {
const result = this.bridgeGame.move(userInput);
this.printMap.bind(this)(this.bridgeGame.printArr);
return result;
}
위 코드에서 InputView에 readMoving이라는 Method에 3개의 콜백함수를 전달하고 그중에 하나인 Moving method가 있다.
//InputView.js
readMoving(callback, nextCallback, endCallback) {
Console.readLine(ANNOUNCEMENT_MESSAGE.MOVE_SPACE, (userInput) => {
...
callback(userInput);
},
그리고 위코드는 아까 받은 콜백함수들을 직접실행해주는 Method이다.
왜 위에서 bind(this)를 해줬냐면 readMoving이라는 Method에서 전해받은 콜백함수를 풀어쓰면 아래와 같다.
readMoving(callback, nextCallback, endCallback) {
Console.readLine(ANNOUNCEMENT_MESSAGE.MOVE_SPACE, (userInput) => {
...
const result = this.bridgeGame.move(userInput);
this.printMap.bind(this)(this.bridgeGame.printArr);
return result;
},
그럼 여기서 bind를 해주지않으면 readMoving이라는 Method에서 this.bridgeGame.move는 readMoving이 종속해있는 클래스를 기준으로 실행한다.
그래서 만약에 해당 Method가 있다면 readMoving이 종속해있는 클래스의 move를 실행할거고 없다면 에러를 뱉어낼것이다.
하지만 여기서 우리가 bind를 해줌으로써 this.bridgeGame.move는 우리가 이걸 선언했던 scope의 this가 되기떄문에 올바르게 실행될것이다.
프리코스가 끝났다. 벌써? 라는 생각이들정도로 1달이 이렇게 빨리 지나간줄 몰랐다.
내입으로 말하긴 부끄럽지만 성장을 많이 한것같다. 프론트엔드로써 성숙해졌다는 생각도 들었지만 개발자로써도 많이 성장했다.
함께 성장하기
이번에 프리코스를 진행하기전에 PR리뷰를 남겨본적도 없었으며, 다른사람의 코드를 보는것도 굉장히 드물었다.
하지만 이번에 같이 PR리뷰를 남기고 리뷰받은 내용으로 리팩토링을 진행하면서 해당 과정을 발표하고 피드백하는걸 경험했는데 정말 많이 성장했다.
흔히 선배개발자들이 페어프로그래밍, 코드리뷰를 많이 추천하시는데 왜 그걸 하고 왜 그게 좋은지에 대해서 분명히 알아갔다.
그리고 함께 성장했다는 느낌을 많이 받았는데, 프리코스 2주차부터 사람들과 함께 스터디를 진행했다.
처음에는 나를 포함해 다들 기존에 자기가 편한방식으로 코딩하는 경향이 있었는데, 서로 리뷰하면서 각자 코드의 좋았던점들은 반영하고 나쁜점들은 제외하면서 마지막 4주차에 코드에서는 다들 엄청난 코드들이 완성되어 있었다.
무려 8명의 리뷰어
개발자스럽게 생각하기
항상 내가 강조하는게 소통을 잘해요였다.
하지만 이번에 우아한테크코스를 진행하면서 나는 소통을 잘하는게 맞았나? 라는 생각이 들었다.
왜냐하면 소통이 실제로 커뮤니케이션 하는 부분이 있겠지만 개발자에게 소통은 코드로 소통하는것도 있다.
하지만 나는기능만 돌아가는 코드를 작성했고( 내가 했던 프로젝트를 다시보니 함수 하나가 무려 130줄을 하는것도 있었다:( ) 나만 알법한 커밋내용(찐으로 기능~~ 완성했습니다와 같은)을 작성했다.
하지만 이번에는 이런 부분이 문제임을 인지하고 프리코스에서 건내준 컨벤션을 최대한 지키려고 노력했으며 커밋도 한번에 모든걸 하는게아니라 작업별로 나눠서 커밋하는 방식을 준수했다.
이런부분들을 적용하면서 개발자스럽게 소통하는법을 배웠다.
그리고 Test코드를 적용해보는것도 개발자스러움을 성장하는데 많은 도움이 됐다.
자바스크립트 숙련
많은 사람들이 실제 현업을 가면 버릇처럼하는 말들이 있고 후배들에게 하는 말들이 있다.
자바스크립트를 잘해야된다!
나는 이걸 이해를 잘 못했다.
하지만 이번에 코드를 작성하면서 기본적인 Method들도 내가 아는선에서만 코딩했고(for문만 쓴다던지) bind나 Class에 대해서는 잘 알지 못했다.
하지만 이런걸 잘 쓴다면 훨씬 좋은 코드를 작성할 수 있음을 알게됐고 이런건 코어 자바스크립트를 많이 공부해야 될것같다는 생각을 했다.
우테코를 하지 않았으면 현업갈떄까지 절~대로 인지를 못했을것 같다.
이 한달동안 사람들과 소통하고 성장할 수 있어서 너무 재밌었다.
내가 처음으로 스터디를 주최해서 사람들과 스터디를 진행해봤다.
그리고 프리코스에서 알게된 울산사람들과 모각코를 진행하기로 했고, 수많은 고수들을 알게되면서 정말 많이 배워갔다.
이번에 했던 경험에서 자신감을 얻었고, 내가 배웠던것들을 적용해보기 위해서 오늘부터 내가 기획한 스프린트를 시작한다.
이번을 기점으로 나는 같이 성장함의 재미를 또 느껴서 앞으로 나만의 방식으로 발산해갈것같고 나중엔 내가 도움을 주는 사람이 되고싶다.
우테코에 되면 정말 좋겠지만, 안된다 하더라도 한달간에 배웠던 좋은 경험들은 개발자인생에 정말 도움이 될것같다.
혹여나 고민하고 있는사람들이 있다면 해보는걸 추천한다!!!!
4주간 수고 많으셨습니다 모승 👍
스프린트 리드라니 앞으로의 활동도 응원해요!