위아래 둘 중 하나의 칸만 건널 수 있는 다리를 끝까지 건너가는 게임이다.
throw
문을 사용해 예외를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다.3
U
R
다리 건너기 게임을 시작합니다.
최종 게임 결과
[ O | | ]
[ | O | O ]
게임 성공 여부: 성공
총 시도한 횟수: 2
[
, 다리의 끝은 ]
으로 표시|
(앞뒤 공백 포함) 문자열로 구분[ERROR] 다리 길이는 3부터 20 사이의 숫자여야 합니다.
다리 건너기 게임을 시작합니다.
다리의 길이를 입력해주세요.
3
이동할 칸을 선택해주세요. (위: U, 아래: D)
U
[ O ]
[ ]
이동할 칸을 선택해주세요. (위: U, 아래: D)
U
[ O | X ]
[ | ]
게임을 다시 시도할지 여부를 입력해주세요. (재시도: R, 종료: Q)
R
이동할 칸을 선택해주세요. (위: U, 아래: D)
U
[ O ]
[ ]
이동할 칸을 선택해주세요. (위: U, 아래: D)
D
[ O | ]
[ | O ]
이동할 칸을 선택해주세요. (위: U, 아래: D)
D
[ O | | ]
[ | O | O ]
최종 게임 결과
[ O | | ]
[ | O | O ]
게임 성공 여부: 성공
총 시도한 횟수: 2
다리 건너기 게임을 시작합니다.
다리의 길이를 입력해주세요.
3
이동할 칸을 선택해주세요. (위: U, 아래: D)
U
[ O ]
[ ]
이동할 칸을 선택해주세요. (위: U, 아래: D)
U
[ O | X ]
[ | ]
게임을 다시 시도할지 여부를 입력해주세요. (재시도: R, 종료: Q)
Q
최종 게임 결과
[ O | X ]
[ | ]
게임 성공 여부: 실패
총 시도한 횟수: 1
throw
문을 사용해 예외를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력throw
문을 사용해 예외를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력throw
문을 사용해 예외를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력node.js v14.20.1
, Visual Studio Code
로 세팅하였습니다.자바스크립트 코드 컨벤션
은 Airbnb 자바스크립트 스타일 가이드를 참고하였습니다.커밋 메시지 컨벤션
은 커밋 메시지 컨벤션 가이드를 참고하였습니다.지난 로또 미션에서 Lotto
클래스의 필드인 로또번호에 대한 유효성검사는 해당 필드를 가진 Lotto
객체에서 수행하였다. 이것은 "Lotto에서 데이터를 꺼내지(get) 말고 메시지를 던지도록 구조를 바꿔 데이터를 가지는 객체가 일하도록 한다."에 해당하는(객체를 객체스럽게 사용하는) 것이었다.
여기서 의문이 생겼다.
그렇다면 validation로직은 객체 안에 다 넣어야 하는가❓
[우아한테크세미나] 190620 우아한객체지향 by 우아한형제들 개발실장 조영호님에서 답을 얻을 수 있었다.
객체 안의 상태를 조금 체크하는 거라면 객체 안에 validation 로직을 넣어도 되지만 그 객체 상태를 validation하기 위해 여러 객체를 참조해야한다면 분리해라!
왜❓ 객체 안의 상태를 validation하기 위해 여러 객체를 참조해야 하는데 해당 로직이 객체 안에 있으면 객체의 응집도가 낮아지기 때문이다. 결합도는 낮추고 응집도는 높여야한다.
위 세미나 영상에 많은 내용이 있었는데 모든 것을 이해하진 못했지만.. 영상을 보면서 기록해뒀던 내용들을 간단하게 정리해보았다.
클래스들은 구체적인 것이 아니라 추상화에 의존해야 한다.
추상화라 하면 추상클래스 또는 인터페이스라고 생각하는 선입견이 있는데 추상화라는 것은 잘 안 변하는 것이다. (어떤 것들에 비해서 잘 변하지 않으면 그것은 추상적인 것이라고 할 수 있음)
객체참조는 영구적이라 결합도가 가장 높은 의존성을 가진다.
필요한 경우에는 객체 참조를 끊어야 한다.
연관관계라는 것은 탐색 가능한 것이다.
어떤 객체들을 묶고 어떤 객체들을 분리할 것인가❓
장바구니와 장바구니항목은 묶어야할까?
X, 실제로 장바구니가 생성되는 시점과 장바구니에 항목을 넣는 시점은 라이프사이클이 다르다.
장바구니와 장바구니항목 사이에 공유되는 것은 별로 없다.
"이 항목은 넣을 수 있지만 이 항목은 못 넣어요" 등의 제약사항이 없다면 도메인 제약사항을 공유하지 않는 것이다. 하지만 만약 "동일한 업소 것만 장바구니에 넣을 수 있다"라는 제약사항이 있다면 장바구니와 장바구니항목은 도메인 제약사항을 공유하는 것이기 때문에 묶으면 된다. 즉 이것은 비즈니스 룰에 따라 결정된다.
4주차 미션의 목표인 클래스 분리 및 설계를 위한 클래스 다이어그램(사용툴)을 그려보았다. 구현 전에 공책에 그렸을 때는 세세한 필드, 메서드는 작성하지 않았고 구현을 완료하고 필드, 메서드를 추가해서 그렸다.
"다리 건너기" 소프트웨어를 설계할 때 도메인로직과 UI로직을 분리하기 위해 고민하다가 지난 코드리뷰 때에도 많이 봤던 디자인패턴인 MVC패턴을 적용해보기로했다. 도메인로직과 UI로직 사이를 연결하는 Controller
를 추가해주었고 클래스를 분리할 때 의존관계 사이클이 생기지 않도록 주의했다.
내가 정의해본 MVC의 역할은 다음과 같다.
Model
: 데이터를 가진 객체이며 내부 도메인 로직을 담당한다.
View
: UI로직을 담당한다.
Controller
: View와 Model 사이의 인터페이스 역할을 한다. View나 Model의 변경 통지를 받으면 이를 해석해서 각각의 구성 요소에게 통지해야 한다. 또한, 애플리케이션의 메인 로직은 컨트롤러가 담당한다.
내가 분리해본 도메인로직/UI로직을 담당하는 객체는 다음과 같다.
도메인로직 처리
BridgeGameController
, BridgeGame
, BridgeResult
, BridgeMaker
, InputValidator
UI로직 처리
InputView
, OutputView
InputValidator
는 도메인로직에 해당하는지 헷갈렸지만 '다리 건너기 게임'에서 사용되는 데이터들에 관한 유효성검사를 담당하는 객체이기 때문에 도메인 로직에 해당할 것 같다.
3주차 피드백에 링크되어 있던 getter를 사용하는 대신 객체에 메시지를 보내자를 참고하여 출력을 위한 getter 구현 시 Object.freeze
를 사용하여 외부에서 객체를 변경하는 것을 막아주었다.
BridgeResult
클래스에는 getResult
메서드가 있는데 여기서 참조형인 객체를 반환할 때 외부에서 변경할 수 없도록 Object.freeze
로 감싸서 반환하였다.
위는 BridgeGame
클래스의 quit
메서드에서 bridgeResult
의 getResult
를 반환하는 이미지이다.
위는 BridgeGameController
클래스의 quit
메서드에서 bridgeMap
객체를 반환받아 변경해보고 그 결과를 캡쳐한 이미지이다.
bridgeMap.U = [1, 2, 3];
으로 해당 객체의 프로퍼티 값을 변경하려고하니 터미널에서 Cannot assign to read only property 'U' of object '#<Object>'
라는 메시지가 뜨면서 변경이 되지 않는 것을 확인할 수 있었다.
Object.freeze
를 사용하지 않은 경우엔 bridgeMap
의 U
프로퍼티 값이 [1,2,3]으로 변경됨을 확인했다.
우테코 프리코스 원본 레포지토리의 테스트코드가 변경되었다.
나는 변경되기 전에 Fork를 했기 때문에 Pull Request를 생성하면 Can't automatically merge.문구가 뜰 것이었다. 이것은 나에게 주어진 또다른 미션.. 해결방법을 찾기 시작했다.
일단 내 레포지토리의 main
브랜치로 원본 레포지토리 main
브랜치의 변경사항을 Pull(fetch+merge)해왔다. 그리고 이 상태에서 해결해야하는 문제는 내 레포지토리 main
브랜치의 commit 1개를 HyeryongChoi
브랜치에 반영하는 것이었다.
처음에는 pull을 하면 클론해온 후 작업했던 코드들이 다 날라가는 건줄 알고 main
브랜치의 마지막 commit 1개를 내 브랜치로 반영시키려고 했다.
그래서 다른 브랜치의 커밋 하나만 내 브랜치에 반영하는 cherry-pick이라는 것을 써야하나 했는데 ‘팀 개발을 위한 Git, GitHub시작하기’책을 살펴보다가 내 경우와 비슷한 문제에 대한 해결책을 발견하여 p.260의 3-way 병합하기 방법을 선택했다.
git checkout HyeryongChoi
명령어로 HyeryongChoi
브랜치로 체크아웃한 후git log —oneline —all
명령어로 로그를 확인했고git merge main
명령어로 main
브랜치와 병합을 시도했다.CONFLICT~
메시지가 나오면서 충돌로 인해 병합이 실패했다.git status
명령어를 실행하여 충돌 대상 파일이 ApplicationTest.js
라는 것을 확인했다. (실패 원인 파악)<<<<<<< HEAD (현재 변경 사항)
HyeryongChoi
브랜치의ApplicationTest.js
코드
=======
main
브랜치의ApplicationTest.js
코드
>>>>>>> main (수신 변경 사항)
HyeryongChoi
브랜치의 코드가 적용되었고git add ../__tests__/ApplicationTest.js
명령어로 해당 파일을 stage에 올린 뒤 git status
로 상태를 확인했다.git commit -m “커밋 메시지”
로 commit을 한 뒤git push origin HyeryongChoi
로 원격 레포지토리에 push했다. 해결해보니 해결방법은 간단했지만.. 명령어 한줄한줄 치면서, 작업했던 내용이 다 날아가서 과제제출을 못할까봐 매우 불안했다. 🥹
객체 간의 대화, 협력에 대한 좋은 아티클을 읽고 아래와 같이 간단하게 정리했다.
협력을 설계할 때는 객체보다는 메시지를 먼저 선택하고 그 후에 메시지를 수신하기에 적절한 객체를 선택해야 한다.
설계자는 무생물도 생물처럼 ‘의인화’해야 한다.
Ex. 메뉴판 객체가 메시지를 수신하고 메뉴 항목을 찾아서 제공한다.
- 메시지가 객체를 결정한다의 의미: 어떤 요청(메시지)을 처리하기에 적절한 객체를 선택한다.
- 책임에 따라 설계가 이뤄지는 과정: 메시지를 정한 후에 그 메시지를 수신하고 처리할 책임이 있는 객체를 정한다.
- 인터페이스와 구현의 분리: 인터페이스는 인터페이스 대로 정하고 나서 그 후에 구현한다.
생각하라, 객체지향처럼
3주차 미션이 끝나고 다른 지원자분들과 코드리뷰를 주고 받으면서 지난 미션 특히 클래스 분리 과정에서 제가 얼마나 부족했었는지 반성을 많이 했습니다. (지난 미션에선 클래스 분리라는 것을 거의 하지 않았다고 봐도 될 정도였지요..) 그래서 이번 주 미션에서는 이를 만회하고 잘 해내야겠다고 다짐했습니다. 일단 미션 요구사항에서도 클래스 분리에 대한 힌트를 주고있다는 것이 느껴졌는데요.
BridgeGame
클래스에서Inputview
와OutputView
를 사용하지 않는다.
BridgeGame
클래스에서 view
클래스를 참조해서 쓰고있다가 위의 요구사항을 발견하고 이번 미션에서는 MVC패턴을 사용해서 도메인로직과 UI로직을 분리해야겠다고 생각했습니다.
그리고 모델, 뷰, 컨트롤러의 역할을 나름대로 정의해보았습니다.
모델은 데이터를 가진 객체이고 내부 도메인 로직을 담당하는 역할, 뷰는 UI로직을 담당하는 역할 그리고 컨트롤러는 뷰와 모델 사이를 잇는 중재자이며 뷰나 모델의 변경이 있을 땐 각각의 구성 요소에 통지하는 역할을 한다.
또한 도메인로직과 UI로직을 담당하는 객체도 아래와 같이 분리해보았습니다.
도메인로직을 처리하는 객체는
BridgeGameController
, BridgeGame
, BridgeResult
, BridgeMaker
, InputValidator
UI로직을 처리하는 객체는InputView
, OutputView
InputValidator
는 도메인로직에 해당하는지 헷갈렸지만 '다리 건너기 게임'에서 사용되는 데이터들에 관한 유효성검사를 담당하는 객체이기 때문에 도메인 로직에 해당할 것 같다고 판단했습니다.
MVC패턴을 적용하여 클래스 분리를 하면서 "개념은 어찌보면 간단한데 실제 구현할 때 적용하는 것은 애매하고 헷갈리는 부분이 많아서 어렵다"고 생각했습니다. 하지만 MVC패턴을 적용하여 클래스를 분리하면 왜 좋은지 또한 알 수 있었는데, 이렇게 도메인 로직과 UI로직을 분리하고 로직을 처리할 클래스를 분리해 놓으니 리팩터링할 때 주로 확인해야 하는 부분이 어딘지 명확했고 테스트할 때에도 UI로직은 신경쓸 필요없이 도메인 로직만 테스트하면 됐기 때문에 편리함을 느낄 수 있었습니다.
참고포스팅: 자바스크립트에서 객체지향을 하는 게 맞나요?
4주차 미션을 설계하면서 객체에 적절한 책임을 맡기는 과정에서의 문제를 인지하지 못 하고 있다가 리팩터링 과정을 통해 이를 발견하고 해결하였습니다.
우아한형제들 기술블로그에서 생각하라, 객체지향처럼이라는 글을 보게 되어 이 글에 나와있는 대로 메시지 추출 후 해당 메시지(요청)을 수신하고 처리할 책임이 있는 객체를 선택하는 과정을 미션에 적용해보았습니다.
다리 건너기 게임에서의 메시지를 추출해보자
메시지(요청)를 수신하고 처리할 책임이 있는 객체를 선택해보자
이렇게 메시지를 먼저 추출해서 확인하고 나니 메시지 처리에 대한 책임을 잘못 분배했다는 것을 알았습니다. 처음 구현했을 때는 다리를 만드는 기능을 BridgeGameController
가 담당하고 있었는데 이 로직은 Bridge
를 필드로 가지고 있는 BridgeGame
객체가 처리해야 더 적절할 것 같다고 생각해 리팩터링해주었습니다.
이전에는 어떤 객체를 만들어야할지 먼저 생각한 후에 로직을 생각하는 방식으로 설계를 했는데 객체 간의 협력을 설계할 때는 객체 간에 주고 받을 메시지를 먼저 생각해야한다는 것을 배웠고 4주차 미션에서는 이 과정을 리팩터링을 하면서 수행했지만 다음에는 설계과정에서 적용해봐야겠다고 생각했습니다. 이해하기 쉽고 직관적인 설계방법이라 큰 프로젝트를 설계할 때에도 도움이 될 것 같습니다.
함수(또는 메서드)의 길이가 10라인을 넘어가지 않도록 구현한다.
또한 메서드 길이에 대한 위 요구사항 덕분에 리팩터링하면서 "어떻게 하면 메서드라인을 더 줄일 수 있을까" 고민을 많이 하게 됐던 것 같습니다.
BridgeMaker
객체에서 한 줄이라도 줄이기 위해 U인지 D인지 구분하기 위해 썼던 삼항연산자를 없애고 BRIDGE
상수 객체를 이용하여 key가 1이면 U, 0이면 D로 바로 push할 수 있게 리팩터링했습니다.
const upOrDown = oneOrZero ? BRIDGE.up : BRIDGE.down;
pattern.push(upOrDown);
pattern.push(BRIDGE[randomNumber]);
전체적으로 이번에 추가된 요구사항들이 자기가 구현한 코드에 대해서 더 고민하고 학습할 수 있도록 코치님들이 의도하신 것 같다고 생각했습니다. 그리고 실제로도 이를 통해 저는 3주차 미션 보다 더 고민하고 더 만족스러운 결과물을 만들어낼 수 있었다고 생각합니다!
프리코스를 통해 4주 동안 다른 고민과 걱정은 잊고 개발에 몰입하는 경험을 할 수 있었습니다. 이렇게 한 달 내내 개발 생각만 하고 코드에 대해 고민하고 또 꿈에서도 코딩을 하는 경험은 처음이었어요.
프리코스 시작 전에는 4주가 길다고 생각했는데 막상 지나고 보니, 깊게 몰입한 만큼 시간 개념 조차 잊혀져 눈 깜박할 사이에 4주가 지나고 마지막 과제 제출일이 다가왔습니다. 프리코스는 저에게 개발을 업으로 삼는 것에 대해 더욱 확신을 준 경험이었습니다. "내가 이렇게 몰입해서 할 수 있는 일이 있다니!" 좋아하는 드라마를 정주행할 때도 이렇게 몰입하진 못했었는데 말입니다. 행복하게 살기 위해서는 좋아하는 일을 더 많이 하고 하기 싫은 일은 덜 해야한다고 하는데요. 저는 이렇게 몰입하는 경험이 너무나 즐겁고 좋았습니다. 프리코스 자체가 선발과정이기 때문에 마음이 아주 편하지는 않았지만 스스로 학습하고 다른 지원자 분들이 공유해준 자료를 공부하고 서로 의견을 나누고 코드리뷰를 하고 또 힘들 땐 서로 응원해주면서 지낸 4주가 행복했습니다.
그리고 매주 회고록을 작성하면서도 많은 배움이 있었습니다. 스스로의 부족함이 무엇인지, 한 주 한 주 얼마나 성장했는지 알 수 있었고 어떤 것을 배우고 느꼈는지 다시 돌아볼 수 있었습니다. 1주차 회고록을 다시 읽어보았는데 1주차 회고에는 자바스크립트를 사용하는 데에 익숙하지 않다고 써져있었습니다. 그리고 git
을 사용하는 것도요. 겨우 4주인데 저는 이제 git status, add, commit, push origin
은 머리보다 손이 먼저 움직일 정도입니다. git
의 방대한 기능은 경험해보지 못했지만 이제 조금은 친해진 것 같아요. 자바스크립도 그 두껍던 기본서를 4주 동안 옆에 두면서 꽤 많이 읽고 적용해볼 수 있었습니다.
프리코스가 끝난 지금, 저에겐 프리코스가 남긴 숙제들이 많이 남아있습니다. 클린코드, 객체지향, 자바스크립트에 대한 공부를 더 할 것이고 지금까지 받았던 피드백들이 모두 자연스럽게 코드에 녹아들도록 의식적으로 그리고 반복적인 연습을 해나갈 생각입니다.
도대체 수요일이 이렇게 설레는 날이 될 수 있는지! 4주 동안 즐거운 두근거림을 경험하게 해준 코치님들과 다른 지원자분들께 감사드리며 마지막까지 즐겁게 달려보겠습니다.
Be the best version of you!