쉼표(,) 또는 콜론(:)을 구분자로 가지는 문자열을 전달하는 경우 구분자를 기준으로 분리한 각 숫자의 합을 반환한다.
예: "" => 0, "1,2" => 3, "1,2,3" => 6, "1,2:3" => 6
앞의 기본 구분자(쉼표, 콜론) 외에 커스텀 구분자를 지정할 수 있다. 커스텀 구분자는 문자열 앞부분의 "//"와 "\n" 사이에 위치하는 문자를 커스텀 구분자로 사용한다.
예를 들어 "//;\n1;2;3"과 같이 값을 입력할 경우 커스텀 구분자는 세미콜론(;)이며, 결과 값은 6이 반환되어야 한다.
사용자가 잘못된 값을 입력할 경우 "[ERROR]"로 시작하는 메시지와 함께 Error를 발생시킨 후 애플리케이션은 종료되어야 한다.
입력 : 구분자와 양수로 구성된 문자열
출력 : 덧셈 결과
실행결과 예시
덧셈할 문자열을 입력해 주세요.
1,2:3
결과 : 6
먼저 공책을 피고 필요한 기능들을 나열했다.
이 5가지 기능을 중심으로 구현에 들어갔다. 미션을 받은 오후 3시부터 새벽까지 npm run test
통과를 위해 안 쉬고 구현만 했다. 구조는 크게 신경쓰지 않았고 로컬테스트 통과를 목표로 했다. 그렇게 새벽 3시가 되기 직전, 드디어 테스트 통과에 성공했다.
기능 요구 사항에 있던 리드미 작성을 깜빡해서 다음날에 바로 작성을 했다. 로컬 테스트는 통과했지만 전체적인 코드 품질이나 구조가 맘에 들지 않았다. 비효율적으로 돌아가고 있다는게 내 눈에 보일 정도였으니 말이다.
누군가 디스코드에서 에어비앤비 JS 스타일 가이드를 웹스톰에 적용할 수 있는 방법을 올려주어 바로 적용했다. 감사합니다🙇♂️
적용만 하면 스타일 가이드를 세세하게 알 수 없을 것 같아 에어비앤비 JS 스타일가이드를 참고하여 벨로그에 작성했다.-> 에어비앤비 JS 스타일
어떤 것을 상수로 취급할지, 또 어떤 것은 변수로 취급할 지에 대한 고민이 급선무였다. 만약 딱 한번 사용되는 상수/변수 라면 무조건 상수로 취급해야 하는 것인지가 가장 헷갈렸다. 왜냐하면 코드가 변해감에 따라 그 값이 또 다른 곳에서 재사용될 수도 있는 것이었기 때문이다.
내가 정한 변수와 상수 규칙이다. 아마 변수와 상수는 1주차 미션 가이드에 포함되어 있던 것으로 기억한다. let
보단 const
를 쓰려고 했고, let
을 쓰는 경우에도 스코프를 좁게 만드려고 했다.
실제 코드에서 작성한 상수명이다.
const HAS_SLASH_SEPARATORS
//
가 있는지 확인한다.const REMOVE_CUSTOM_SEPARATOR
변수는 반복문 안에서만 사용하는 식으로 스코프를 줄였다.
for (let i = 0; i < SEPARATED_BY_COLONS_AND_COMMA.length; i++) {
let splitTwiceNum = SEPARATED_BY_COLONS_AND_COMMA[i];
sum += VALIDATE_NUMBER(splitTwiceNum);
}
jest란 페이스북에서 만든 테스팅 라이브러리라고 한다. 처음 봤을 때 이게 뭐지 싶었다. describe인지 test인지 처음 보는 코드들이 나열되어 있었다. 이게 테스트를 해 주는 코드라니 일단 건드리지 않는게 좋다고 생각했다.
과제 제출전에 깨달은 건데 아무리 다른 기능을 추가해도 npm run test
하면 거의 실패하지 않았다. 왜냐하면 다른 기능에 대한 테스트코드가 없었기 때문이다..!!
물론 그걸 알고서는 내가 추가한 새로운 기능들에 대한 테스트코드를 작성하여 의도했던 기능들은 모두 구현할 수 있었다.
describe("문자열 계산기", () => {
test("커스텀 구분자 사용", async () => {
const inputs = ["//;\\n1"];
mockQuestions(inputs);
const logSpy = getLogSpy();
const outputs = ["결과 : 1"];
const app = new App();
await app.run();
outputs.forEach((output) => {
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(output));
});
});
test("예외 테스트", async () => {
const inputs = ["-1,2,3"];
mockQuestions(inputs);
const app = new App();
await expect(app.run()).rejects.toThrow("[ERROR]");
});
});
};
처음 파일을 열었을 때 있었던 테스트코드 중 일부이다.
간단하게 얘기해서 describe
는 테스트 그룹을 묶는다. 그 안에 들어갈 콜백함수에서 테스트에 쓰이는 변수나 객체를 선언할 수 있다. 그것들은 테스트 시 일회용으로 사용한다.
test
는 말그대로 test를 시작하는 것이고, expect
는 어떠한 결과를 예상하는 것이다.
정리해보니 describe로 test들을 묶고 input과 output을 입력하면 예상결과에 맞는지 테스틀 해 준다. jest 코드는 다시 한 번 상세하게 파 볼 예정이다!
간단하게 jest 코드를 훑어본 뒤 커스텀 구분자와 예외 처리 테스트를 구분하여 하나하나 기능을 추가하는 방식으로 진행했다.
첫 날 구현했던 코드에 커스텀 구분자 처리가 미흡해 기준을 더 확장했다.
일단 커스텀 구분자가 어디까지 가능한지부터 기준을 세웠다. 기능 요구사항에 따로 정해진 건 없어서 알아서 하면 되는 것 같았다.
커스텀 구분자 기능 구현을 위해 테스트 코드에 테스트를 추가하고 기존의 것과 새로운 테스트를 모두 성공할 시에 또다른 기능을 추가하여 테스트 했다.
/
를 포함할 때\n
일 때등등 매우 많은 케이스가 있었는데 무엇보다 어려웠던 건 커스텀 구분자가 //
를 포함할 때였다.
let pushedCustomStr = []
let customSeparator = [];
let startCustomSeparatorIndex = 0;
for (let i = 0; i < STRING_TO_ADD.length - 1; i++) {
const CURRENT_SLICED_STRING = STRING_TO_ADD.slice(i, i + 1);
const NEXT_SLICED_STRING = STRING_TO_ADD.slice(i + 1, i + 2);
if (CURRENT_SLICED_STRING === '/' && NEXT_SLICED_STRING === '/') {
pushedCustomStr.push(1);
startCustomSeparatorIndex = i + 2;
} else if (CURRENT_SLICED_STRING === '\\' && NEXT_SLICED_STRING === 'n') {
if (pushedCustomStr[pushedCustomStr.length - 1] === 1) {
pushedCustomStr.pop();
customSeparator.push(STRING_TO_ADD.substring(startCustomSeparatorIndex, i));
if (pushedCustomStr.length > 1) {
throw new Error('[ERROR] 잘못된 문자열입니다.');
}
}
}
}
커스텀 구분자를 구하기 위해 구현했던 코드인데, 현재 문자와 다음 문자가 모두 /
인 경우에 모두 스택에 집어넣는 것을 볼 수 있다. 이렇게 하면 커스텀 구분자에 /
가 포함될 때 구분할 수가 없다.
그래서 좀 더 나은 방식이 없을까 하다가, 만약 스택이 비어 있지 않다면 이미 커스텀 구분자의 시작을 의미하는 //
가 이전에 나왔다는 것이기 때문에 이후에 /
가 오는 경우에 그냥 넘어가도록 구현했다.
for (let i = 0; i < STRING_TO_ADD.length - 1; i++) {
const CURRENT_SLICED_STRING = STRING_TO_ADD.slice(i, i + 1);
const NEXT_SLICED_STRING = STRING_TO_ADD.slice(i + 1, i + 2);
if (CURRENT_SLICED_STRING === '/' && NEXT_SLICED_STRING === '/') {
if (pushedCustomStr.length !== 0) {
continue;
}
실수했던 건 이렇게 처리하면 스택에 남는 것이 없어 커스텀 구분자를 따로 처리하지 않아도 되는데, 굳이 //
가 커스텀 구분자에 포함되어 있는지에 대한 검사를 한 번 더 수행한 것이다..
if (pushedCustomStr.length !== 0) {
const HAS_SLASH_SEPARATORS = customSeparator.some(separator => separator.includes("//"));
if (!HAS_SLASH_SEPARATORS) {
throw new Error('[ERROR] 잘못된 문자열입니다.');
}
}
이것이 해당 코드다. 이미 푸쉬한 상태기 때문에 다른 테스트 케이스에 필요한 코드이길 바랄 뿐이다..
제출 당일 바로 푸쉬를 했고 다행히도 테스트 케이스를 성공했다.
통과하긴 했지만 아직 부족한 점들이 많이 보였다. 식겁했던 건 빈문자열일 경우 0을 출력하는 것을 처리하지 않았던 것이다. 빠르게 그 부분을 수정하고 리팩토링을 시작했다.
코드 리뷰를 하다가 알게 된 건데 그렇게 신경썼던 커스텀 구분자 구하기가 사실 match
를 이용해 간단하게 커스텀 구분자만 뽑아낼 수 있었다. 일일이 반복문으로 구분자를 뽑아내었는데 match
를 직접 구현한 게 아닐까 싶을 정도였다..
우테코에서는 js의 메서드를 적극적으로 활용하라는 얘기가 있었어서 이제는 메서드가 있는지부터 확인해보려고 한다.
또한 구현에 너무 치중한 나머지 기능 단위로 함수를 분리하는 작업을 하지 못했다. 그렇기에 코드의 가독성이 떨어진다는 좋은 리뷰를 받았고 2주차 미션부터는 하나의 함수가 하나의 기능만 하도록 구현할 생각이다.
이때까지 JS를 하면서 기능 단위를 분리하는 일이 적었는데 다른 분들의 코드를 보니 많이 부족한 것 같았다. 함수형 프로그래밍을 하면서 하나의 함수가 하나의 기능을 가지도록 하는 것이 2주차 목표다.