[우아한테크코스 5기] 프리코스 4주차 미션 회고

[우아한테크코스 5기] 프리코스 4주차 미션 회고

·

6 min read

3주차 그 후

요구사항을 더 꼼꼼히

그동안 미션을 진행하면서 요구 사항을 꼼꼼히 읽었다고 생각했습니다. 그러나 3주차에서는 놓친 부분을 발견하였습니다.

수익률은 소수점 둘째 자리에서 반올림한다. (ex. 100.0%, 51.5%, 1,000,000.0%)

왜 예시를 보지 않았느냐!

저도 처음에는 100.0% 와 같이 출력하였지만 소수점 둘째 자리에서 반올림이라는 문구를 보고 100% 로 수정하였습니다. (그냥 요구사항을 한 번 더 보지 말던가)
1000의 자리 마다 콤마를 찍는 것은 생각치도 못했습니다.

4주차에서는 요구사항을 다 외울 것처럼 꼼꼼히 여러 번 읽어봐야겠습니다.

클래스 사용할 때 주의하자

3주차에서는 클래스와 객체를 시/군/구를 나누는 것처럼 사용했습니다.

마트료시카

결과를 다루는 모든 함수를 클래스에 집어 넣었습니다. 클래스는 print와 count 2가지 일을 하게 되는 것이죠.
단일 책임 원칙에 위배되는 행동입니다.

이럴 때는 클래스가 아닌 result를 다루는 함수를 모으는 객체를 모듈화시키는 편이 더 좋을 것 같습니다.

뿐만 아니라, private 필드를 사용하지 않고 외부에서 값을 가져와 사용한다던지, 필드의 수를 어마어마하게 많이 늘여놨다던지...
3주차 공통 피드백을 읽어보고 코치님께서 제 코드만 보고 피드백하신 줄 알았습니다. 🙂🔫


Ready, Get Set...

객체 및 클래스 용도 정리

이번 미션에서는 여러가지 객체와 클래스를 부여해주었습니다. 자세한 사용법과 제한 사항이 설명되어 있어서 저에게 한 줄기의 빛 같은 존재가 되어주었습니다. ✨✨✨✨✨
주어진 객체와 클래스 안에서 최대한 활용하고, 에러를 체크하는 객체만 따로 생성할 예정입니다.

꼼꼼한 기능 구현 목록

  • [ ] 🖨 게임 시작 문구를 출력한다.
  • [ ] ⌨ 다리 길이를 입력한다.

    • [ ] 💥 다리 길이가 숫자가 아닌 경우 예외 처리한다.

    • [ ] 💥 다리 길이가 3 이상 20 이하가 아닌 경우 예외 처리한다. ...

  • [ ] 🖨 게임이 종료되면 최종 결과를 출력한다.

    • [ ] 🖨 게임 성공 여부를 출력한다.

      • [ ] 다리를 끝까지 건넌 경우 성공을 출력한다.

      • [ ] 다리를 건너지 못한 경우 실패를 출력한다.

    • [ ] 🖨 총 시도한 횟수를 출력한다.

      • [ ] 첫 시도를 포함해 게임을 종료할 때까지 시도한 횟수를 나타낸다.
  • [ ] 💥 사용자가 잘못된 값을 입력한 경우 throw문을 사용해 예외를 발생시킨다.

    • [ ] 🖨 "[ERROR]"로 시작하는 에러 메시지를 출력한다.

    • [ ] ⌨ 그 부분부터 입력을 다시 받는다.

요구사항을 찬찬히 살펴보며 모든 조건과 입출력 사항을 정리하였습니다.
이모지를 사용해 입출력과 예외 처리를 구분해봤는데 저는 작업하면서 굉장히 만족스러웠습니다! 다른 사람들은 어떻게 생각하실지 궁금하네요..


Welcome to New World!

JavaScript의 주석 문서화, 이게 바로 멋드러지는 코드지 😎

코드를 설치하고 주어진 객체와 클래스에 이러한 주석을 볼 수 있습니다.

/**
 * @param {number} size 다리의 길이
 * @param {function(): number} generateRandomNumber 무작위 값을 생성해주는 함수
 * @return {string[]} 입력받은 길이에 해당하는 다리 모양. 위 칸이면 U, 아래 칸이면 D로 표현해야 한다.
*/

/**
 * 현재까지 이동한 다리의 상태를 정해진 형식에 맞춰 출력한다.
 * <p>
 * 출력을 위해 필요한 메서드의 인자(parameter)는 자유롭게 추가하거나 변경할 수 있다.
*/

저는 난생 처음 보는 별들의 항연인데요, 이와 관련된 포스팅은 링크(https://velog.io/@yeonsubaek/JavaScript-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%95%A8%EC%88%98-%EC%A3%BC%EC%84%9D-%EB%AC%B8%EC%84%9C%ED%99%94JSDoc)에에) 정리해놓았습니다.

그동안 // 만 사용하여 효율성 떨어지는 주석을 하였는데, JSDoc을 접하게 되어 주석의 신세계를 맛보게 되었습니다.

예외 처리하고 끝내고 싶지 않다구요. 게임 계속 하고 싶다구요.

이 전에는 예외 처리를 하고 프로그램을 종료시켰기 때문에

throw new Error("[Error]");

와 같이 에러 메세지를 던지고 끝마쳤습니다.

하지만 이번 미션의 요구 사항은

사용자가 잘못된 값을 입력한 경우 throw문을 사용해 예외를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다.

이기 때문에 다른 방식을 사용해야 합니다.

저는 try { } catch { } 구문을 사용하였습니다.

hasNumber(size) {
    const notNumber = isNaN(size);
    try {
      if (notNumber) throw new Error(ERROR.NUMBER); // 1
    } catch (error) {
      Console.print(error.message); // 2
    }
    return notNumber; // 3
  },
},
  1. try 안에서 에러 조건을 판별해 에러를 throw 합니다.
    에러가 없는 경우 catch 를 건너 뜁니다.
    에러가 있는 경우 catch 로 이동합니다.

  2. 에러 메세지를 출력합니다.

  3. 에러가 있는 경우 true, 없는 경우 false 로 반환됩니다.

if (Error.hasMoving(moving)) return this.readMoving(bridgeGame);

반환값에 따라 게임 진행 상황이 바뀌게 됩니다.

boolean을 반환할 때 어떤 코드가 더 좋을까요?

// 1. 
hasNumber(size) {
    try {
      if (isNaN(size)) throw new Error(ERROR.NUMBER);
    } catch (error) {
      Console.print(error.message);
      return true;
    }
    return false;
  },
},
// 2. 
hasNumber(size) {
    const notNumber = isNaN(size);
    try {
      if (notNumber) throw new Error(ERROR.NUMBER);
    } catch (error) {
      Console.print(error.message);
    }
    return notNumber;
  },
},

이 점에 대해 궁금해서 커뮤니티에 질문을 하고 다음과 같은 답변을 받았습니다.

예전 순서도를 작성할 때는 처음 듣는 말이, "프로그램의 시작과 끝은 하나로 한다". 요즘 웹 쪽은 시작은 있고 끝은 없는 편이라, 'return'이 여러 곳에 쓰이지만 그것도 'goto'의 일종이라, 가능한 순차적 즉 시작과 끝을 함수 내에서도 하나로 하는게 프로그램을 이해하는데 도움이 될 거예요..

따라서 2번이 더 좋은 코드라고 할 수 있습니다.

return 을 적게 사용하면 순차적인 프로그램을 작성하는데 효과적이라고 이해하였습니다. (제가 잘 이해한게 맞나요?)
제가 항상 미션을 수행하면서 프로그램의 시작과 끝 그리고 흐름을 어떻게 해야 명확히 할 수 있을까 생각했는데, returngoto 로 생각하는 점에서 저의 궁금증을 해결할 수 있었습니다.

요구 사항을 암기할 때까지 계속 읽어보기

3주차 이후로 각성을 하여 4주차에서는 요구사항을 정말 질리도록 확인하였습니다. 다행히 이번에는 요구 사항에서 벗어난 점을 모두 수정하게 되었습니다.

1. 실행 결과 예시

이동할 칸을 선택해주세요. (위: U, 아래: D) U [ O ]
[ ] 이동할 칸을 선택해주세요. (위: U, 아래: D) U [ O | X ]
[ | ]

게임을 다시 시도할지 여부를 입력해주세요. (재시도: R, 종료: Q) R 이동할 칸을 선택해주세요. (위: U, 아래: D) U [ O ] [ ]

보통 값을 입력 받은 후에는 한 칸을 띄워주지만, 사용자가 게임을 다시 시도할지 종료할지 입력을 받은 후에는 한 줄을 띄우지 않습니다.
이 점을 보완하기 위해 이동할 칸을 선택해주세요. (위: U, 아래: D) 앞에 \n 을 붙여 준 것을 이동한 다리를 출력하는 함수에

  printMap(movingList) {
    const upperList = movingList.upper.join(OUTPUT.DIVISION);
    const lowerList = movingList.lower.join(OUTPUT.DIVISION);
    Console.print(OUTPUT.MAP(upperList));
    Console.print(OUTPUT.MAP(lowerList));
    Console.print(OUTPUT.BLANK); // " " 입니다:)
  },

깜찍하게 BLANK(" ")를 넣어주었습니다.

그렇다면 왜 \n 이 아닌 을 넣어줬을까요?
그것은 Console.print() 가 block처럼 한 줄을 이미 차지하기 때문입니다 :)

예외 처리 문구는 줄바꿈 예시가 따로 없어서... 그냥 위아래 촵 붙여주었습니다...

2. 객체 및 클래스 사용 방법

추가된 요구 사항에서

BridgeGame 클래스에서 InputView, OutputView 를 사용하지 않는다.

라는 문구를 뒤늦게 발견하여 부리나케 리팩터링을 다시 하였습니다.

BridgeGame 클래스에서 사용한 OutputViewInputView 객체 내에서 사용할 수 있도록 하였습니다.

/**
 * 사용자가 이동할 칸을 입력받는다.
 */
readMoving(bridgeGame) {
    Console.readLine(`${INPUT.MOVING}${OUTPUT.LINE}`, (moving) => {
    // 중략
    const result = bridgeGame.move(moving);
    OutputView.printMap(result.movingList);
    if (bridgeGame.hasWrong()) return this.readGameCommand(bridgeGame, result);
    if (bridgeGame.hasAll()) return OutputView.printResult(result.movingList, RESULT.SUCCESS, result.attempts);
    return this.readMoving(bridgeGame);
  });
},

OutputView 를 사용하기 위해선 BridgeGame 클래스의 #bridge, #movingList, #attempts 가 필요합니다. BridgeGame의 필드는 외부 접근을 막기 위하여 private로 구현하였습니다. 따라서 result 에 객체로 반환하는 방법을 통해 필요한 값을 사용할 수 있게 되었습니다.

왜 객체 상태를 외부에서 직접 접근하는 방식을 최소화해야 할까요?

만약 외부에서 객체의 필드를 변경한다면 재할당 객체가 아닌 직접 할당 객체가 변경되므로 원본이 훼손될 수 있습니다. 다른 곳에서 객체를 사용할 때 변경된 객체가 직접 할당되므로 객체를 만든 의도에서 벗어난 결과를 나타낼 수 있습니다.


4주가 언제 지나가나 했는데 벌써 프리코스의 막이 내렸습니다. 매주 미션을 수행하면서 제 실력이 향상되는 것이 몸소 느껴져서 항상 뿌듯해하며 미션을 마무리 했습니다😌 혼자서 이만큼 공부하려고 했으면 정말 오래 걸렸을 것 같은데 우테코 덕분에 빠른 시간에 많은 것을 배워갑니다. 프리코스는 끝이났지만, 그동안 배웠던 것들을 총동원해서 앞으로 저의 공부와 프로젝트에도 도입하며 발전시켜 나가겠습니다. 정말 올해 가장 잘한 일을 하나 꼽자면 망설임 없이 우테코 프리코스에 지원했다는 것입니다!! 프리코스에 참여하신 다른 참가자분들도 정말 고생 많으셨습니다. 앞으로 더욱 멋진 개발자가 되어서 현업에서 만나고 싶어요🥰