작성일 2023년 2월 8일
오늘은 2월8일 드디어 프리코스 마지막 미션까지 왔다. 오늘은 2시간 일찍옴! 7시40분에 지하철 타야지 사람이 없는 것 같다. 와서 밥먹고 멍때리니까 8시45분이네..? 졸리다. 언제 이거 쓰고 언제 수업준비하지?ㅋㅋㅋㅋㅋ 암튼 또 달려보자고
4주차 미션은 다리건너기 게임을 구현하는 문제였다. 파일을 나누는 것 부터 그동안 프리코스를 진행하면서 습득한 모든 지식들을 사용하여 구현해야 했다. input 받는 부분과 output부분을 나눴는데 컨트롤러를 사용하지 않다보니 좀 어려웠다. 이건 내가 천천히 공부해야할 부분! 아 그리고 제출 하루전에 메인 브랜치 코드가 바뀌었다는 공지가 왔는데 뭔가 돌발 미션이었을 거라고 생각했음. 그거 처리하는거 나름 재밌었지 모야
README
출력:
- 게임 시작 문구
- 게임 종료 문구
- 사용자가 이동할 때마다 다리 건너기 결과의 출력 형식은 실행 결과 예시를 참고
이동할 수 있는 칸 O 표시, 이동할 수 없는 칸 X 표시
선택하지 않은 칸은 공백 한 칸으로 표시
다리의 시작은 [, 다리의 끝은 ]으로 표시
다리 칸의 구분은 |(앞뒤 공백 포함) 문자열로 구분
현재까지 건넌 다리를 모두 출력 - 예외 상황 시 에러 문구를 출력. 단, 에러 문구는 "[ERROR]"로 시작
프로그래밍:
- jest를 이용해 기능 정상 동작 확인
- else 지양
- 도메인 로직에 대한 단위 테스트 구현(UI로직에 대한 단위 테스트 제외)
- indent 2까지만 허용
- 함수의 길이 10라인 까지만 구현
- 매서드 파라미터 개수는 최대 3개까지 허용
클래스 공통:
- InputView, OutputView, BridgeGame, BridgeMaker 클래스(또는 객체)의 요구사항을 참고하여 구현
- 클래스(또는 객체)의 제약 사항은 아래 클래스별 세부 설명을 참고
- 이외 필요한 클래스(또는 객체)와 메서드는 자유롭게 구현
- InputView 에서만 MissionUtils의 Console.readLine() 을 이용해 사용자의 입력을 받을 수 있다
- BridgeGame 클래스에서 InputView, OutputView 를 사용하지 않는다.
InputView 객체
- 제공된 InputView 객체를 활용해 구현.
- InputView의 파일 경로는 변경할 수 있다.
- InputView의 메서드의 인자는 변경할 수 있다.
- 사용자 값 입력을 위해 필요한 메서드를 추가할 수 있다.
- InputView 에서만 MissionUtils의 Console.readLine() 을 이용해 사용자의 입력을 받을 수 있다
OutputView 객체
- 제공된 OutputView 객체를 활용해 구현해야 한다.
- OutputView의 파일 경로는 변경할 수 있다.
- OutputView의 메서드의 이름은 변경할 수 없고, 인자는 필요에 따라 추가하거나 변경할 수 있다.
- 값 출력을 위해 필요한 메서드를 추가
BridgeGame 클래스
- 제공된 BridgeGame 클래스를 활용해 구현해야 한다.
- BridgeGame에 필드(인스턴스 변수)를 추가할 수 있다.
- BridgeGame의 파일 경로는 변경할 수 있다.
- BridgeGame의 메서드의 이름은 변경할 수 없고, 인자는 필요에 따라 추가하거나 변경할 수 있다.
- 게임 진행을 위해 필요한 메서드를 추가 하거나 변경할 수 있다.
BridgeMaker 객체
- 제공된 BridgeMaker 객체를 활용해 구현해야 한다.
- BridgeMaker에 프로퍼티를 추가할 수 없다.
- BridgeMaker의 파일 경로는 변경할 수 없다.
- BridgeMaker의 메서드의 시그니처(인자, 이름)와 반환 타입은 변경할 수 없다.
BridgeRandomNumberGenerator 객체
- Random 값 추출은 제공된 BridgeRandomNumberGenerator의 generate()를 활용한다.
- BridgeRandomNumberGenerator의 코드는 변경할 수 없다.
사용 예시- 다리 칸을 생성하기 위한 Random 값은 아래와 같이 추출한다.
- const number = generateRandomNumber();
문제 풀이 후기
지난번 과제에 비해서 제약사항과 요구사항이 많아서 파악하는 것 부터 시간이 많이 걸렸다. 특히 class 와 객체를 이렇게 많이 다뤄본건 처음이라 막막하다는 생각이 들기도 했다. 3주차 피드백 때 객체는 객체 답게 사용하라고 하셔서 최대한 그렇게 하려고 노력했는데 아무래도 독학이다보니 이런 방식이 맞는지 확신 할 수 없는 부분이 조금 아쉬웠다.
메소드 자체의 로직은 구현하는 것이 그렇게 어렵지는 않았는데 요구사항을 지키는 것이 까다로웠다. 또한 여러 파일은 봐야하니 경로와 위치를 제대로 확인하지 않아 오류가 많이 발생했었다. 클래스가 여러개라 jest로 각각 확인하는것이 어려웠다. 기능별로 추가 확인해보라고 하셨는데 사실 테스트 파일을 따로 만들어서 하는게 나한테는 처음이라 테스트 적용이 정말정말 어려웠다. 지난번 로또때는 테스트 하라고 파일을 따로 만들어주신게 있어서 그곳에 넣어서 확인하면 되었는데 이번에는 내가 테스트 파일을 만드려고 하니 제대로 돌아가고 있는 것이 맞는지 확인하는 것이 어려웠다.
또한 try-catch문 사용에서 시간을 많이 소요했다. 맨 처음에는 입력 받는 부분을 try로 오류 처리부분을 catch로 잡아야겠다 라고 생각해서 에러헨들러 파일을 따로 만들어서
try {InputView.readBridgeSize();
}catch(e) {
MU.Console.print(e);
InputView.readBridgeSize();
}
이렇게 진행했는데 계속 catch부분에 안들어가는 것이다. 동기방식이라 안돌아가나? 라고 생각을 해서 await를 사용했는데 똑같이 catch에 들어가지 않아서 아예 방식이 잘못되었다는 생각을 하게 되었다. 그래서 try-catch가 어떻게 작동하는지 보기위해 readBridgeSize자체에서 try-catch를 사용해보았다. 첫 접근 방식은
try{ MU.Console.readLine( ...여기에서 오류 throw...)}
catch(e){
MU.Console.print(e);
readBridgeSize();
}
였다. 이것도 또한 catch문에 잡히지 않고 try문이 전부 들어간다는 것이 확인되었다. 그래서 왜일까 하고 찾아보니 readLine자체는 잘 작동되었다는 것이 이유였다. 나는 callback쪽에서 throw을 하고 있었는데 readLine은 끝까지 돌아간 것이니 catch에 오류가 넘어가지 않았었다. 그래서 두번째 접근 방식은 아예 readLine 안에서 try-catch를 하는 것이었다.
MU.Console.readLine(...,(a) =>{여기에서 try-catch})
이런식으로 하니 오류가 catch문으로 들어가고 그래서 내가 원했던 정확한 입력 값이 들어올 때 까지 반복되는 코드가 완성되었다.
다만 이 방식의 문제는 요구사항이었던 메서드의 10라인을 넘어가는 것 이었다. 그래서 아예 readLine의 callback 함수를 밖으로 빼서 정의하고 넣어야겠다 라는 생각을 했다. 입력받을 때 마다 에러를 처리해야하니 에러헨들러 파일을 사용해야겠다고 느껴서 파일을 만들어 넣었는데 자꾸만 is not funcion 에러가 뜨는 것이었다.
호출방법이 잘못되었나 싶어서 호출방법도 다르게 해보고 처음에는 객체로 만들었던 것을 클래스로도 바꿔보았는데 에러가 떠서 컴퓨터가 잘못된줄 알았다. import부분에서도 잘 되었다고 확인이 되고, 마우스 커서를 올려보았을 때 객체도 잘 들어가 있었다. 파일을 만드는 방법이 잘못되었나 생각이 들어서 잘 작동하는 outputView 파일을 복사해서 내가 만든 핸들러 파일에 넣고 돌려보니 그건 또 잘 돌아가는 것이었다. export도 객체선언도 다 잘되었는데 뭐가 문제지 하고 열심히 공부를 해보았는데 이럴 수가! 순한 참조가 문제 였던 것이다.
inputView에서는 에러 처리를 위해 ErrorHander를 import하고, ErrorHandler 파일에서는 catch문에서 다시 readBridgeSize함수를 호출하기위해 inputView를 import 했는데 이것이 순환 참조라고 한다. A객체에서 B객체를 사용하면 A객체는 B객체에 의존하게 되는데 이 뜻은 B가 변경되었을 때 A도 영향을 받는다는 것이다. 순환참조가 발생되면 파일간의 경계가 사라지고 연쇄적으로 변경에 의한 영향이 발생할 가능성이 높기 때문에 파일끼리 분리하기도 어렵다고 한다. 즉, 좋은 설계가 아니었던 것이다. 이러한 문제점을 처음알게되어서 오류를 해결하는데에는 시간이 많이 소요되었지만, 앞으로의 개발에서 설계 방향을 어떻게 잡아야하는지 실마리를 잡은 느낌이었다. 따라서 메서드 라인 요구사항에 맞추기 위해서 오류 부분을 출력하는 부분만 따로 빼는 방식을 선택했다.
미리 코드를 작성하고 제출기한을 기다리고 있었는데 제출 하루 전에 공지메일이 왔다. 바로 테스트 코드에서 사용하는 타입이 요구 사항과 다르다는 것이었다. bridgeMaker 객체에서 ===가 아닌 ==로 비교를 했기 때문에 코드상에서는 문제가 없었지만 원래 저장소가 달라져서 달라진 부분만 내 저장소로 불러와서 작성하고 있던 브랜치에 합치고 싶었다.
가장 쉬운 방법으로는 새로 포크를 생성하는 것이겠지만 그렇게 하면 이미 그전에 보낸 커밋내용이 다 없어져 좋은 방법은 아닐 것 같다고 생각했다. 다음생각한 방식은 바뀐 파일 내용만 그냥 복사&붙여넣기를 하는 것이었다. 하지만 이건 테스트 코드를 내가 바꿨다고 커밋이 작성되기 때문에 나중에 확인할 때 좋지않을 것이라고 생각했다. 그래서 git의 사용법을 공부할 겸 방식을 찾아보았다. 바뀐 저장소를 내 포크에 불러오는 것은 생각보다 간단했다. 깃배쉬에서 명령어를 치니 적용되었다! 다음은 내 커밋을 그대로 남겨두고 메인만 적용 하는 것이었는데, 이 과정에서 처음보는 기능을 배우게 되었다. 바로 rebase였다.
rebase는 merge와 다르게 그냥 병합하는 것이 아닌, main의 마지막 변경점을 기준으로 내 브랜치가 새로 시작하도록 시작 기준점을 바꿔주는 것이라고 한다. rebase를 사용하면 원본 브랜치에서 각 커밋에 대해 새로운 커밋을 만들어 프로젝트 기록을 다시 작성해주기 때문에 내 커밋이 그대로 살아있게 된다. 나중에 팀원들과 프로젝트를 하게 된다면 분명 내가 코드를 작성한 후에 메인이 바뀌는 일이 생길 수 있을텐데 그럴 경우에 사용하면 용이하겠다 라는
생각을 하게 되었다.
'우아한 테크 코스 회고 > 프리코스 후기' 카테고리의 다른 글
[우아한테크코스 5기 _ 프리코스 FE] 최종코딩테스트 후기 /점심메뉴추천 (0) | 2023.04.21 |
---|---|
[우아한테크코스 5기 _ 프리코스 FE] 3주차미션 후기/ 로또 (0) | 2023.04.21 |
[우아한테크코스 5기 _ 프리코스 FE] 2주차미션 후기/ 숫자야구게임 (0) | 2023.04.21 |
[우아한테크코스 5기 _ 프리코스 FE] 온보딩미션 후기 (0) | 2023.04.21 |
작성일 2023년 2월 8일
오늘은 2월8일 드디어 프리코스 마지막 미션까지 왔다. 오늘은 2시간 일찍옴! 7시40분에 지하철 타야지 사람이 없는 것 같다. 와서 밥먹고 멍때리니까 8시45분이네..? 졸리다. 언제 이거 쓰고 언제 수업준비하지?ㅋㅋㅋㅋㅋ 암튼 또 달려보자고
4주차 미션은 다리건너기 게임을 구현하는 문제였다. 파일을 나누는 것 부터 그동안 프리코스를 진행하면서 습득한 모든 지식들을 사용하여 구현해야 했다. input 받는 부분과 output부분을 나눴는데 컨트롤러를 사용하지 않다보니 좀 어려웠다. 이건 내가 천천히 공부해야할 부분! 아 그리고 제출 하루전에 메인 브랜치 코드가 바뀌었다는 공지가 왔는데 뭔가 돌발 미션이었을 거라고 생각했음. 그거 처리하는거 나름 재밌었지 모야
README
출력:
- 게임 시작 문구
- 게임 종료 문구
- 사용자가 이동할 때마다 다리 건너기 결과의 출력 형식은 실행 결과 예시를 참고
이동할 수 있는 칸 O 표시, 이동할 수 없는 칸 X 표시
선택하지 않은 칸은 공백 한 칸으로 표시
다리의 시작은 [, 다리의 끝은 ]으로 표시
다리 칸의 구분은 |(앞뒤 공백 포함) 문자열로 구분
현재까지 건넌 다리를 모두 출력 - 예외 상황 시 에러 문구를 출력. 단, 에러 문구는 "[ERROR]"로 시작
프로그래밍:
- jest를 이용해 기능 정상 동작 확인
- else 지양
- 도메인 로직에 대한 단위 테스트 구현(UI로직에 대한 단위 테스트 제외)
- indent 2까지만 허용
- 함수의 길이 10라인 까지만 구현
- 매서드 파라미터 개수는 최대 3개까지 허용
클래스 공통:
- InputView, OutputView, BridgeGame, BridgeMaker 클래스(또는 객체)의 요구사항을 참고하여 구현
- 클래스(또는 객체)의 제약 사항은 아래 클래스별 세부 설명을 참고
- 이외 필요한 클래스(또는 객체)와 메서드는 자유롭게 구현
- InputView 에서만 MissionUtils의 Console.readLine() 을 이용해 사용자의 입력을 받을 수 있다
- BridgeGame 클래스에서 InputView, OutputView 를 사용하지 않는다.
InputView 객체
- 제공된 InputView 객체를 활용해 구현.
- InputView의 파일 경로는 변경할 수 있다.
- InputView의 메서드의 인자는 변경할 수 있다.
- 사용자 값 입력을 위해 필요한 메서드를 추가할 수 있다.
- InputView 에서만 MissionUtils의 Console.readLine() 을 이용해 사용자의 입력을 받을 수 있다
OutputView 객체
- 제공된 OutputView 객체를 활용해 구현해야 한다.
- OutputView의 파일 경로는 변경할 수 있다.
- OutputView의 메서드의 이름은 변경할 수 없고, 인자는 필요에 따라 추가하거나 변경할 수 있다.
- 값 출력을 위해 필요한 메서드를 추가
BridgeGame 클래스
- 제공된 BridgeGame 클래스를 활용해 구현해야 한다.
- BridgeGame에 필드(인스턴스 변수)를 추가할 수 있다.
- BridgeGame의 파일 경로는 변경할 수 있다.
- BridgeGame의 메서드의 이름은 변경할 수 없고, 인자는 필요에 따라 추가하거나 변경할 수 있다.
- 게임 진행을 위해 필요한 메서드를 추가 하거나 변경할 수 있다.
BridgeMaker 객체
- 제공된 BridgeMaker 객체를 활용해 구현해야 한다.
- BridgeMaker에 프로퍼티를 추가할 수 없다.
- BridgeMaker의 파일 경로는 변경할 수 없다.
- BridgeMaker의 메서드의 시그니처(인자, 이름)와 반환 타입은 변경할 수 없다.
BridgeRandomNumberGenerator 객체
- Random 값 추출은 제공된 BridgeRandomNumberGenerator의 generate()를 활용한다.
- BridgeRandomNumberGenerator의 코드는 변경할 수 없다.
사용 예시- 다리 칸을 생성하기 위한 Random 값은 아래와 같이 추출한다.
- const number = generateRandomNumber();
문제 풀이 후기
지난번 과제에 비해서 제약사항과 요구사항이 많아서 파악하는 것 부터 시간이 많이 걸렸다. 특히 class 와 객체를 이렇게 많이 다뤄본건 처음이라 막막하다는 생각이 들기도 했다. 3주차 피드백 때 객체는 객체 답게 사용하라고 하셔서 최대한 그렇게 하려고 노력했는데 아무래도 독학이다보니 이런 방식이 맞는지 확신 할 수 없는 부분이 조금 아쉬웠다.
메소드 자체의 로직은 구현하는 것이 그렇게 어렵지는 않았는데 요구사항을 지키는 것이 까다로웠다. 또한 여러 파일은 봐야하니 경로와 위치를 제대로 확인하지 않아 오류가 많이 발생했었다. 클래스가 여러개라 jest로 각각 확인하는것이 어려웠다. 기능별로 추가 확인해보라고 하셨는데 사실 테스트 파일을 따로 만들어서 하는게 나한테는 처음이라 테스트 적용이 정말정말 어려웠다. 지난번 로또때는 테스트 하라고 파일을 따로 만들어주신게 있어서 그곳에 넣어서 확인하면 되었는데 이번에는 내가 테스트 파일을 만드려고 하니 제대로 돌아가고 있는 것이 맞는지 확인하는 것이 어려웠다.
또한 try-catch문 사용에서 시간을 많이 소요했다. 맨 처음에는 입력 받는 부분을 try로 오류 처리부분을 catch로 잡아야겠다 라고 생각해서 에러헨들러 파일을 따로 만들어서
try {InputView.readBridgeSize();
}catch(e) {
MU.Console.print(e);
InputView.readBridgeSize();
}
이렇게 진행했는데 계속 catch부분에 안들어가는 것이다. 동기방식이라 안돌아가나? 라고 생각을 해서 await를 사용했는데 똑같이 catch에 들어가지 않아서 아예 방식이 잘못되었다는 생각을 하게 되었다. 그래서 try-catch가 어떻게 작동하는지 보기위해 readBridgeSize자체에서 try-catch를 사용해보았다. 첫 접근 방식은
try{ MU.Console.readLine( ...여기에서 오류 throw...)}
catch(e){
MU.Console.print(e);
readBridgeSize();
}
였다. 이것도 또한 catch문에 잡히지 않고 try문이 전부 들어간다는 것이 확인되었다. 그래서 왜일까 하고 찾아보니 readLine자체는 잘 작동되었다는 것이 이유였다. 나는 callback쪽에서 throw을 하고 있었는데 readLine은 끝까지 돌아간 것이니 catch에 오류가 넘어가지 않았었다. 그래서 두번째 접근 방식은 아예 readLine 안에서 try-catch를 하는 것이었다.
MU.Console.readLine(...,(a) =>{여기에서 try-catch})
이런식으로 하니 오류가 catch문으로 들어가고 그래서 내가 원했던 정확한 입력 값이 들어올 때 까지 반복되는 코드가 완성되었다.
다만 이 방식의 문제는 요구사항이었던 메서드의 10라인을 넘어가는 것 이었다. 그래서 아예 readLine의 callback 함수를 밖으로 빼서 정의하고 넣어야겠다 라는 생각을 했다. 입력받을 때 마다 에러를 처리해야하니 에러헨들러 파일을 사용해야겠다고 느껴서 파일을 만들어 넣었는데 자꾸만 is not funcion 에러가 뜨는 것이었다.
호출방법이 잘못되었나 싶어서 호출방법도 다르게 해보고 처음에는 객체로 만들었던 것을 클래스로도 바꿔보았는데 에러가 떠서 컴퓨터가 잘못된줄 알았다. import부분에서도 잘 되었다고 확인이 되고, 마우스 커서를 올려보았을 때 객체도 잘 들어가 있었다. 파일을 만드는 방법이 잘못되었나 생각이 들어서 잘 작동하는 outputView 파일을 복사해서 내가 만든 핸들러 파일에 넣고 돌려보니 그건 또 잘 돌아가는 것이었다. export도 객체선언도 다 잘되었는데 뭐가 문제지 하고 열심히 공부를 해보았는데 이럴 수가! 순한 참조가 문제 였던 것이다.
inputView에서는 에러 처리를 위해 ErrorHander를 import하고, ErrorHandler 파일에서는 catch문에서 다시 readBridgeSize함수를 호출하기위해 inputView를 import 했는데 이것이 순환 참조라고 한다. A객체에서 B객체를 사용하면 A객체는 B객체에 의존하게 되는데 이 뜻은 B가 변경되었을 때 A도 영향을 받는다는 것이다. 순환참조가 발생되면 파일간의 경계가 사라지고 연쇄적으로 변경에 의한 영향이 발생할 가능성이 높기 때문에 파일끼리 분리하기도 어렵다고 한다. 즉, 좋은 설계가 아니었던 것이다. 이러한 문제점을 처음알게되어서 오류를 해결하는데에는 시간이 많이 소요되었지만, 앞으로의 개발에서 설계 방향을 어떻게 잡아야하는지 실마리를 잡은 느낌이었다. 따라서 메서드 라인 요구사항에 맞추기 위해서 오류 부분을 출력하는 부분만 따로 빼는 방식을 선택했다.
미리 코드를 작성하고 제출기한을 기다리고 있었는데 제출 하루 전에 공지메일이 왔다. 바로 테스트 코드에서 사용하는 타입이 요구 사항과 다르다는 것이었다. bridgeMaker 객체에서 ===가 아닌 ==로 비교를 했기 때문에 코드상에서는 문제가 없었지만 원래 저장소가 달라져서 달라진 부분만 내 저장소로 불러와서 작성하고 있던 브랜치에 합치고 싶었다.
가장 쉬운 방법으로는 새로 포크를 생성하는 것이겠지만 그렇게 하면 이미 그전에 보낸 커밋내용이 다 없어져 좋은 방법은 아닐 것 같다고 생각했다. 다음생각한 방식은 바뀐 파일 내용만 그냥 복사&붙여넣기를 하는 것이었다. 하지만 이건 테스트 코드를 내가 바꿨다고 커밋이 작성되기 때문에 나중에 확인할 때 좋지않을 것이라고 생각했다. 그래서 git의 사용법을 공부할 겸 방식을 찾아보았다. 바뀐 저장소를 내 포크에 불러오는 것은 생각보다 간단했다. 깃배쉬에서 명령어를 치니 적용되었다! 다음은 내 커밋을 그대로 남겨두고 메인만 적용 하는 것이었는데, 이 과정에서 처음보는 기능을 배우게 되었다. 바로 rebase였다.
rebase는 merge와 다르게 그냥 병합하는 것이 아닌, main의 마지막 변경점을 기준으로 내 브랜치가 새로 시작하도록 시작 기준점을 바꿔주는 것이라고 한다. rebase를 사용하면 원본 브랜치에서 각 커밋에 대해 새로운 커밋을 만들어 프로젝트 기록을 다시 작성해주기 때문에 내 커밋이 그대로 살아있게 된다. 나중에 팀원들과 프로젝트를 하게 된다면 분명 내가 코드를 작성한 후에 메인이 바뀌는 일이 생길 수 있을텐데 그럴 경우에 사용하면 용이하겠다 라는
생각을 하게 되었다.
'우아한 테크 코스 회고 > 프리코스 후기' 카테고리의 다른 글
[우아한테크코스 5기 _ 프리코스 FE] 최종코딩테스트 후기 /점심메뉴추천 (0) | 2023.04.21 |
---|---|
[우아한테크코스 5기 _ 프리코스 FE] 3주차미션 후기/ 로또 (0) | 2023.04.21 |
[우아한테크코스 5기 _ 프리코스 FE] 2주차미션 후기/ 숫자야구게임 (0) | 2023.04.21 |
[우아한테크코스 5기 _ 프리코스 FE] 온보딩미션 후기 (0) | 2023.04.21 |