우아한 테크코스 4주간의 과제를 하며, 배웠던 점 + 느꼈던 점을 써보고자 한다.
4주간 비대면으로 진행한 코스이지만, 생각 이상으로 7*4일 내내 몰입감 있게 진행했던 것 같다.
우선 1주차는 pre-onboarding으로 알고리즘 문제를 풀며 워밍업을 하였고, 나머지 차수는 node.js 기반의 간단한 게임을 만드는 과제를 하였다. 과제에 대한 세세한 내용보다 프로그래머로서 새롭게 배운점들을 써보려 한다.
어떻게 더 쪼개지?
라는 고민을 강제로라도 했었고, 그러다 보니 1함수 1기능에 익숙해졌다.'최대한 함수를 잘게 쪼개라'라는 요구사항에 따라, 내가 실행하고자 하는 기능을 좀 더 세세하게 들여다 보게 되었다.
무심코 쭈욱 나열해서 쓰던 유효성 검사 함수, 단순 테스트 통과만을 위해서 작성했던 여러 method들의 나열.. 이러한 부분을 다 함수로 쪼개어 네이밍을 해주고 나니, 보기에도 좋지만, 다시 코드를 읽어보았을 때에 로직을 이해하기에도 편리했다.
내가 만든
함수가 어떤 동작을 할 것인가?
에 대한 예측을 다른사람도 함수명만 보고 할 수 있어야 한다.
내가 예전에 작성한 코드도 다시 보면, "이게 어떤 함수였더라..?" 하게 되는 경우가 많은데, 남들이 보면 얼마나 더 힘들 것인가..
헷갈리는 단어는 papago에서 검색도 하고, 단어 3-4개 조합으로 썼다가 너무 길면 필요한 부분만 남기고 지우는 과정을 통해서 네이밍을 다듬어 갔다.
값을 하드코딩하게 되면 생기는 문제는 몇가지가 있다. 누군가 내가 쓴 하드코딩한 값을 보았을 때, 이 숫자가 의미하는게 뭐지? 라고 의문을 가지게 되면 코드 가독성도 떨어지게 되고, 그 값을 수정해야할 때, 같은 내용이 어디에 얼마만큼 분포되어있는지 일일히 찾아가며 다 고쳐야 한다. (오류가 무조건 있을 수 밖에 없음)
그렇기 때문에 상수값 뿐만 아니라 유효성을 검사하는 등의 조건도 인라인에 쓰기 보다는 따로 함수를 만들어서 쓰는 것이 좋다고 생각했다.
클린코드 관련해서 배운점을 반영한 코드를 보자면,,
원래 나는 코드를 아래와 같이 썼는데
if (input.length ===3 & input[0] !=="0")
throw new Error("유효하지 않은 숫자 입니다.")
이번 우테코 과제를 진행하면서
1) 상수값은 따로 정의해서 사용하고,
const ValidInputSize = 3;
2) validation 검사 함수를 따로 만들고
isValidNum(input) {
return input.length === ValidInputSize;
}
3) 이 함수를 실행시켜서 유효성을 검사하는 형태로 코드 작성 방식을 바꾸었고, 에러메세지 등도 하드코딩 하지 않고, 따로 constants로 관리하였다.
if (!this.isValidNum(answer)) {
throw new Error(ISNOTVALIDNUMBER);
}
4) 그리고 마지막으로 유효성 검사 함수가 많아지게 되자, App.js 코드를 읽는 흐름에 방해되지 않도록 모든 유효성 검사를 Validation.js 라는 파일에다가 따로 저장해두고 불러와서 썼다.
const ValidInputSize = 3;
const InputValidation = {
isNumber: function (input) {
return !isNaN(Number(input));
},
isValidNum: function (input) {
return input.length === ValidInputSize & input[0] !=="0";
},
isNotDuplicated: function (input) {
return new Set(input).size === ValidInputSize;
},
};
module.exports = InputValidation;
## 로또 게임
### [구현 단계]
#### 1. 구매 금액 입력 받기
- 로또 구입 금액을 입력 받는다. 구입 금액은 1,000원 단위로 입력 받으며 1,000원으로 나누어 떨어지지 않는 경우 예외 처리
#### 2. 구매 현황 보여주기
- 구매 된 로또 개수
- 1~45중 랜덤으로 6개를 뽑은 배열 print
#### 3. 로또 당첨 번호 받기
- 당첨 번호를 입력 받는다. 번호는 쉼표(,)를 기준으로 구분한다. // 1,2,3,4,5,6
- 당첨 번호 : Console.readline(), split(","), 유효성 검사 필요
#### 4. 보너스 번호 입력 받기
- 보너스번호 : 1개의 수를 입력 받는다. Console.readline(), 당첨번호와 중복되는지 체크
#### 5. 당첨 내역 출력하기 (n개 일치, 상금액수, 수량 + 마지막에 수익률)
- 로또 일치 개수, 보너스번호 일치 여부에 따라 상금 현황 프린트
- 수익률 (총 상금액 / 로또구매액 \*100 을 소수점 첫째자리까지 반올림) 표기
#### 6. 게임 종료
- Console.close()
#### 예외 상황 시 에러 문구를 출력
1. 로또 구매 금액 입력 시 발생 할 수 있는 에러
- 숫자가 아닌것들 입력 (isNumber?)
- 1000원으로 나뉘어지지 않는 것 입력
2. 로또 번호 입력 시 발생할 수 있는 에러
- 숫자 아닌것들 입력 (isNumber?)
- 로또 번호 1~45 외의 숫자 입력
- 로또 번호 6개 입력하지 않음
- 중복 수 입력
3. 보너스 번호 입력 시 발생할 수 있는 에러
- 숫자 아닌것들 입력 (isNumber?)
- 로또 번호 1~45 숫자 입력
- 기존 6개 로또 번호와 중복되는 수 입력
생각해보면, case 정리는 현업에서도 매우매우 중요한 부분이일 것이다. 애초에 case에 대한 정리를 해놓지 않고 시작한다면, 예외사항이 발생할 때마다 코드를 덕지덕지 붙이게 되고, 결과적으로 나만 알아볼 수 있는 그런 이상한 로직을 가진 코드들이 생겨나게 되고, 이를 나중에 리팩토링하려고 하면 매우 머리가 아플 것으로 예상 됨.
사실 이전까지는 여러명에서 프로젝트를 할 때 빼고는 readme 파일을 수정해본 적이 잘 없었던 것 같다. README.md는 소스코드에 앞서 해당 프로젝트가 어떠한 프로젝트인지 마크다운으로 작성하여 소개하는 문서이다. 내 레포지토리의 첫인상과 같은 느낌이 아닐까..
readme 파일은 너무 상세하게는 아니더라도, 어떠한 기능을 가지고 있는 것인지라도 쓰는 것이 중요하다.
이번 과제를 하며 테스트 코드를 처음 써봤다. 예시가 있어, 조금씩 응용해나가면서 기능을 고쳐보았다.
위에서 나누었던 case별로 테스트코드를 작성하다보니, 테스트 코드도 잘 써졌고, 결과적으로 모든 요구사항도 충족시킬 수 있었다.
const App = require('../src/App');
const MissionUtils = require('@woowacourse/mission-utils');
const mockQuestions = answers => {
MissionUtils.Console.readLine = jest.fn();
answers.reduce((acc, input) => {
return acc.mockImplementationOnce((question, callback) => {
callback(input);
});
}, MissionUtils.Console.readLine);
};
const mockRandoms = numbers => {
MissionUtils.Random.pickUniqueNumbersInRange = jest.fn();
numbers.reduce((acc, number) => {
return acc.mockReturnValueOnce(number);
}, MissionUtils.Random.pickUniqueNumbersInRange);
};
const getLogSpy = () => {
const logSpy = jest.spyOn(MissionUtils.Console, 'print');
logSpy.mockClear();
return logSpy;
};
describe('로또 테스트', () => {
test('기능 테스트', () => {
mockRandoms([
[8, 21, 23, 41, 42, 43],
[3, 5, 11, 16, 32, 38],
[7, 11, 16, 35, 36, 44],
[1, 8, 11, 31, 41, 42],
[13, 14, 16, 38, 42, 45],
[7, 11, 30, 40, 42, 43],
[2, 13, 22, 32, 38, 45],
[1, 3, 5, 14, 22, 45],
]);
mockQuestions(['8000', '1,2,3,4,5,6', '7']);
const logs = [
'8개를 구매했습니다.',
'[8, 21, 23, 41, 42, 43]',
'[3, 5, 11, 16, 32, 38]',
'[7, 11, 16, 35, 36, 44]',
'[1, 8, 11, 31, 41, 42]',
'[13, 14, 16, 38, 42, 45]',
'[7, 11, 30, 40, 42, 43]',
'[2, 13, 22, 32, 38, 45]',
'[1, 3, 5, 14, 22, 45]',
'3개 일치 (5,000원) - 1개',
'4개 일치 (50,000원) - 0개',
'5개 일치 (1,500,000원) - 0개',
'5개 일치, 보너스 볼 일치 (30,000,000원) - 0개',
'6개 일치 (2,000,000,000원) - 0개',
'총 수익률은 62.5%입니다.',
];
const logSpy = getLogSpy();
const app = new App();
app.play();
logs.forEach(log => {
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(log));
});
});
test('예외 테스트', () => {
mockQuestions(['1000j']);
expect(() => {
const app = new App();
app.play();
}).toThrow('[ERROR]');
});
});
테스트 코드도 결국 하나의 코드이기 때문에 지속적인 리팩터링을 통해 계속 개선해나가야 한다.
프로젝트를 몇개 해보면서도, 컨벤션을 정하고 해야한다! 는 것은 알고 있었지만, 유명한 컨벤션을 참고해볼 생각은 못했다..
협업도 프로그래밍의 일부가 아닌가!
지금까지는 커밋메시지나 네이밍 컨벤션 등을 내가 원하는대로 정해놓고 작성하였는데, 이번 과제에서는 명확한 기준이 주어져있었고 이 기준에 맞추기 위한 노력을 많이 했다.
실제로도 현업에 가게 된다면, 회사의 가이드라인이 주어지고 그에 따른 코드를 작성해야할텐데 이를 미리 경험한 느낌이 들었다.
과제를 하면서 신경써서 커밋 메시지를 남겼었는데, 캡쳐해서 다시 보니 어떠한 흐름으로 프로젝트를 진행해나갔는지 나도 잘 기억할 수 있지만, 이정도면 남들도 잘 알 수 있지 않을까?
비록 최종 불합격을 하였지만, 합불을 떠나서 프리코스를 하게된 게 정말 소중한 기회라고 느껴졌다. 같은 문제를 푸는 수백명의 사람들과 의견을 공유할 수 있고, 문제 외에도 개발자가 되기 위한 과정에 대해서 공유할 수 있다는 점이 참 좋았고 본받을 점도 많았다. 그리고 무엇보다도 꾸역꾸역 과제를 해나가면서 기본 개념이나 클린 코드에 대해서 공부할 수 있었던 점이 만족스럽다.
처음 개발 공부 시작했을 때의 마음처럼 조급해지지 말고 내 페이스를 찾아나가야겠다고 다짐하며 회고록 마무리.