diff --git a/README.md b/README.md index 13420b29f..27cd557e5 100644 --- a/README.md +++ b/README.md @@ -1 +1,82 @@ -# javascript-calculator-precourse \ No newline at end of file +# javascript-calculator-precourse + +**과제 진행 요구 사항** + +- 미션은 [문자열 덧셈 계산기](https://github.com/woowacourse-precourse/javascript-calculator-7) 저장소를 포크하고 클론하는 것으로 시작한다. +- **기능을 구현하기 전 `README.md`에 구현할 기능 목록을 정리**해 추가한다. +- Git의 커밋 단위는 앞 단계에서 `README.md`에 정리한 기능 목록 단위로 추가한다. + - [AngularJS Git Commit Message Conventions](https://gist.github.com/stephenparish/9941e89d80e2bc58a153)을 참고해 커밋 메시지를 작성한다. +- 자세한 과제 진행 방법은 프리코스 진행 가이드 문서를 참고한다. + +## **기능 요구 사항 체크리스트** + +입력한 문자열에서 숫자를 추출하여 더하는 계산기를 구현한다. + +입력 + +- [X] 사용자에게 쉼표(,) 또는 콜론(:)을 구분자로 가지는 문자열을 입력 받는다. + - 입력 메세지: ‘덧셈할 문자열을 입력해 주세요.’ + +내부 기능 +1) 일반적인 경우 +- [X] 쉼표(,) 또는 콜론(:)으로 숫자를 구분한다. +- [X] 구분자를 기준으로 각 숫자들을 분리 한다. +- [X] 각 숫자의 합을 반환한다. + - 예: "" => 0, "1,2" => 3, "1,2,3" => 6, "1,2:3" => 6 + + +//;\n1;2;3 + +2) 커스텀 경우 +- 앞의 기본 구분자(쉼표, 콜론) 외에 커스텀 구분자를 지정할 수 있다. +- [X] 문자열 앞부분의 "//"와 "\n" 사이에 위치하는 문자를 커스텀 구분자로 인지한다. + - 예를 들어 "//;\n1;2;3"과 같이 값을 입력할 경우 커스텀 구분자는 세미콜론(;)이며, 결과 값은 6이 반환되어야 한다. +- [X] 구분자를 기준으로 각 숫자들을 분리 한다. +- [X] 각 숫자의 합을 반환한다. + +출력 +- [X] 결과를 반환한다. + - 출력 메시지: '결과 : ${6}' + +예외 처리 +- [X] +- 사용자가 잘못된 값을 입력할 경우 "[ERROR]"로 시작하는 메시지와 함께 `Error`를 발생시킨 후 애플리케이션은 종료되어야 한다. + +### **입출력 요구 사항** + +### **입력** + +- 구분자와 양수로 구성된 문자열 + +### **출력** + +- 덧셈 결과 + +``` +결과 : 6 + +``` + +### **실행 결과 예시** + +``` +덧셈할 문자열을 입력해 주세요. +1,2:3 +결과 : 6 + +``` + +## **프로그래밍 요구 사항** + +- [X] Node.js 20.17.0 버전에서 실행 가능해야 한다. +- [X] 프로그램 실행의 시작점은 `App.js`의 `run()`이다. +- [X] `package.json` 파일은 변경할 수 없으며, **제공된 라이브러리와 스타일 라이브러리 이외의 외부 라이브러리는 사용하지 않는다.** +- 프로그램 종료 시 `process.exit()`를 호출하지 않는다. +- 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 등의 이름을 바꾸거나 이동하지 않는다. +- 자바스크립트 코드 컨벤션을 지키면서 프로그래밍한다. + - 기본적으로 [JavaScript Style Guide](https://github.com/woowacourse/woowacourse-docs/tree/main/styleguide/javascript)를 원칙으로 한다. + +### **라이브러리** + +- `@woowacourse/mission-utils`에서 제공하는 `Console` API를 사용하여 구현해야 한다. + - 사용자의 값을 입력 및 출력하려면 `Console.readLineAsync()`와 `Console.print()`를 활용한다. diff --git a/src/App.js b/src/App.js index 091aa0a5d..883a1aa05 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,10 @@ +import { MainController } from './Controller/mainController.js'; + class App { - async run() {} + async run() { + const controller = new MainController(); + await controller.startProgram(); + } } export default App; diff --git a/src/Constant/constant.js b/src/Constant/constant.js new file mode 100644 index 000000000..b1cef61ce --- /dev/null +++ b/src/Constant/constant.js @@ -0,0 +1,11 @@ +export const DELIMITER = { + COMMA: ',', + COLON: ':', +}; + +export const DELIMITER_MAKER = { + STRING_FIRST: '/', + STRING_SECOND: '/', + STRING_THIRD: '\\', + STRING_FOURTH: 'n', +}; diff --git a/src/Constant/errorMessages.js b/src/Constant/errorMessages.js new file mode 100644 index 000000000..bd8cf38e2 --- /dev/null +++ b/src/Constant/errorMessages.js @@ -0,0 +1,7 @@ +const ERROR_PREFIX = '[ERROR]'; + +export const ERROR_MESSAGES = { + WRONG_INPUT: `${ERROR_PREFIX} 잘못된 입력입니다.`, + WRONG_NORMAL_INPUT: `${ERROR_PREFIX} 컴마나 클론을 기준으로 숫자를 입력하지 않으셨습니다.`, + WRONG_CUSTOM_INPUT: `${ERROR_PREFIX}커스텀 숫자를 기준으로 숫자를 입력하지 않으셨습니다.`, +}; diff --git a/src/Constant/messages.js b/src/Constant/messages.js new file mode 100644 index 000000000..fd6e75f53 --- /dev/null +++ b/src/Constant/messages.js @@ -0,0 +1,7 @@ +export const INPUT_MESSAGES = { + STRING_TO_ADD: `덧셈할 문자열을 입력해 주세요.\n`, +}; + +export const OUTPUT_MESSAGES = { + RESULT: `결과`, +}; diff --git a/src/Controller/mainController.js b/src/Controller/mainController.js new file mode 100644 index 000000000..f8c1c9de6 --- /dev/null +++ b/src/Controller/mainController.js @@ -0,0 +1,23 @@ +import { InputHandler } from '../View/inputView.js'; +import { OutputHandler } from '../View/outputView.js'; + +import { NumbersParserHandler } from '../Model/numbersParser.js'; +import { addNumbers } from '../Utils/calculatorToAdd.js'; +import { ValidationHandler } from '../Validation/validateStringToAdd.js'; + +export class MainController { + constructor() { + this.input = new InputHandler(); + this.output = new OutputHandler(); + } + + async startProgram() { + const stringToAdd = await this.input.getStringToAddInput(); + new ValidationHandler().checkValidation(stringToAdd); + + const parsedNumbers = new NumbersParserHandler().parseNumbers(stringToAdd); + const resultAddedNumbers = addNumbers(parsedNumbers); + + this.output.printResultOfSum(resultAddedNumbers); + } +} diff --git a/src/Model/numbersParser.js b/src/Model/numbersParser.js new file mode 100644 index 000000000..b84b78012 --- /dev/null +++ b/src/Model/numbersParser.js @@ -0,0 +1,51 @@ +import { DELIMITER, DELIMITER_MAKER } from '../Constant/constant.js'; + +export class NumbersParserHandler { + parseNumbers(inputString) { + this.inputString = inputString; + this.specifyCaseValue(); + + if (this.NORMAL_CASE_CHECK) { + return this.parseNumbersForNormal(); + } + + if (this.CUSTOM_CASE_CHECK) { + return this.parseNumbersForCustom(); + } + } + + specifyCaseValue() { + this.NORMAL_CASE_CHECK = + this.inputString[1] === DELIMITER.COMMA || + this.inputString[1] === DELIMITER.COLON; + + this.CUSTOM_CASE_CHECK = + this.inputString[0] === DELIMITER_MAKER.STRING_FIRST && + this.inputString[1] === DELIMITER_MAKER.STRING_SECOND && + this.inputString[3] === DELIMITER_MAKER.STRING_THIRD && + this.inputString[4] === DELIMITER_MAKER.STRING_FOURTH; + } + + // TODO: 리펙토링: 정규식 표현에도 상수화 처리하여 콤마, 클론 넣기 + parseNumbersForNormal() { + if ( + this.inputString[1] === DELIMITER.COMMA || + this.inputString[1] === DELIMITER.COLON + ) { + return this.inputString + .split(/,|:/) + .map((number) => number.trim()) + .map(Number); + } + } + + parseNumbersForCustom() { + const onlyNumbersAndDelimiter = this.inputString.substr(5); + const customDelimiter = this.inputString[2]; + + return onlyNumbersAndDelimiter + .split(customDelimiter) + .map((number) => number.trim()) + .map(Number); + } +} diff --git a/src/Utils/calculatorToAdd.js b/src/Utils/calculatorToAdd.js new file mode 100644 index 000000000..c6ff7a6c5 --- /dev/null +++ b/src/Utils/calculatorToAdd.js @@ -0,0 +1,8 @@ +export const addNumbers = (parsedNumbers) => { + let sum = 0; + parsedNumbers.forEach((number) => { + sum += number; + }); + + return sum; +}; diff --git a/src/Validation/validateStringToAdd.js b/src/Validation/validateStringToAdd.js new file mode 100644 index 000000000..63083024a --- /dev/null +++ b/src/Validation/validateStringToAdd.js @@ -0,0 +1,67 @@ +import { DELIMITER, DELIMITER_MAKER } from '../Constant/constant.js'; +import { ERROR_MESSAGES } from '../Constant/errorMessages.js'; + +export class ValidationHandler { + checkValidation(inputString) { + this.inputString = [...inputString]; + + this.specifyCaseValue(); + this.checkBasicError(); + this.checkNormalOrCustom(); + } + + specifyCaseValue() { + this.NORMAL_CASE_CHECK = + this.inputString[1] === DELIMITER.COMMA || + this.inputString[1] === DELIMITER.COLON; + + this.CUSTOM_CASE_CHECK = + this.inputString[0] === DELIMITER_MAKER.STRING_FIRST && + this.inputString[1] === DELIMITER_MAKER.STRING_SECOND && + this.inputString[3] === DELIMITER_MAKER.STRING_THIRD && + this.inputString[4] === DELIMITER_MAKER.STRING_FOURTH; + } + + checkBasicError() { + if (!(this.NORMAL_CASE_CHECK || this.CUSTOM_CASE_CHECK)) { + throw new Error(ERROR_MESSAGES.WRONG_INPUT); + } + } + + checkNormalOrCustom = () => { + if (this.NORMAL_CASE_CHECK) { + this.validateNormalCase(); + } + + if (this.CUSTOM_CASE_CHECK) { + this.validateCustomCase(); + } + }; + + // TODO: 리펙토링 - forEach를 사용해서 짝수 인덱스만 돌게하는 것 찾기 + validateNormalCase() { + const onlyNumberInArray = []; + for (let i = 0; i < this.inputString.length; i += 2) { + onlyNumberInArray.push(this.inputString[i]); + } + + onlyNumberInArray.forEach((number) => { + if (Number.isNaN(Number(number))) { + throw new Error(ERROR_MESSAGES.WRONG_NORMAL_INPUT); + } + }); + } + + validateCustomCase() { + const onlyNumberInArray = []; + for (let i = 5; i < this.inputString.length; i += 2) { + onlyNumberInArray.push(this.inputString[i]); + } + + onlyNumberInArray.forEach((number) => { + if (Number.isNaN(Number(number))) { + throw new Error(ERROR_MESSAGES.WRONG_CUSTOM_INPUT); + } + }); + } +} diff --git a/src/View/inputView.js b/src/View/inputView.js new file mode 100644 index 000000000..13debcc0d --- /dev/null +++ b/src/View/inputView.js @@ -0,0 +1,9 @@ +import { Console } from '@woowacourse/mission-utils'; +import { INPUT_MESSAGES } from '../Constant/messages.js'; + +// TODO: 공부 - return 뒤에 await 없어도 실행 가능한 이유 +export class InputHandler { + async getStringToAddInput() { + return Console.readLineAsync(INPUT_MESSAGES.STRING_TO_ADD); + } +} diff --git a/src/View/outputView.js b/src/View/outputView.js new file mode 100644 index 000000000..2c33c7f05 --- /dev/null +++ b/src/View/outputView.js @@ -0,0 +1,10 @@ +import { Console } from '@woowacourse/mission-utils'; +import { OUTPUT_MESSAGES } from '../Constant/messages.js'; + +export class OutputHandler { + async printResultOfSum(resultAddedNumbers) { + const { RESULT } = OUTPUT_MESSAGES; + + return Console.print(`${RESULT} : ${resultAddedNumbers}`); + } +} diff --git a/src/index.js b/src/index.js index 02a1d389e..9daefc93f 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,4 @@ -import App from "./App.js"; +import App from './App.js'; const app = new App(); await app.run();