From b590b4391810b11a557bb15abea93a7a2427c9f1 Mon Sep 17 00:00:00 2001 From: Lee Ye Jin Date: Mon, 20 Oct 2025 01:26:18 +0900 Subject: [PATCH 01/15] =?UTF-8?q?docs(README)=20:=20=EC=9A=94=EA=B5=AC?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EB=B6=84=EC=84=9D=20=EB=B0=8F=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=ED=98=84=20=EB=AA=A9=EB=A1=9D=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 132 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 131 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bd90ef0247..0d5f1044a1 100644 --- a/README.md +++ b/README.md @@ -1 +1,131 @@ -# java-calculator-precourse \ No newline at end of file +# java-calculator-precourse + +## 문자열 덧셈 계산기 + +입력한 문자열에서 **숫자를 추출하여 합산하는 계산기**를 구현한다. +기본 구분자는 쉼표(`,`)와 콜론(`:`)이며, +사용자는 `"//[구분자]\\n"` 형식으로 **커스텀 구분자**를 지정할 수 있다. +잘못된 입력에 대해서는 `IllegalArgumentException`을 발생시키고 프로그램을 종료한다. + +--- + +## 1. 기능 구현 목록 + +### 1. 입력 +- [ ] 콘솔에서 문자열을 입력받는다 + - `camp.nextstep.edu.missionutils.Console.readLine()`을 사용 + - 예: `"1,2:3"` + + +### 2. 입력 검증 +- [ ] null, 빈 문자열, 비숫자, 음수 등을 검증한다 → `IllegalArgumentException` +- [ ] 빈 문자열(`""`)을 입력하면 `0`을 반환한다 +- [ ] 숫자와 쉼표(`,`)로 이루어진 문자열 → 정상 처리 + - `"1,2,3"` → `6` +- [ ] 숫자와 콜론(`:`)으로 이루어진 문자열 → 정상 처리 + - `"1:2:3"` → `6` +- [ ] 쉼표(`,`)와 콜론(`:`)이 혼합된 문자열 → 정상 처리 + - `"1,2:3"` → `6` + + +### 3. 구분자 파싱 +- [ ] 입력값의 맨 앞에 `"//"`가 있고, 그 뒤에 `"\n"`이 있으면 커스텀 구분자로 인식한다 + - `"//;\n1;2;3"` → 구분자: `;` → 결과: `6` +- [ ] 커스텀 구분자는 **여러 문자를 허용**한다 + - `"//***\n1***2***3"` → 결과: `6` +- [ ] 공백 포함 불가 → 예외 발생 `IllegalArgumentException` +- [ ] 예약어(`//`, `\n`, `,`, `:`) 사용 불가 → 예외 발생 `IllegalArgumentException` +- [ ] 커스텀 구분자 식별에 실패할 경우 → `IllegalArgumentException` + + +### 4. 문자열 파싱 +- [ ] 구분자(기본 또는 커스텀)를 기준으로 문자열을 분리한다 +- [ ] 분리된 각 요소가 숫자로만 이루어져 있는지 검사한다 + - `"a"`, `"--"`, `"-2"` 등 → `IllegalArgumentException` +- [ ] 숫자로 변환 불가 시 → `IllegalArgumentException` +- [ ] 정상 입력이면 문자열 배열을 반환한다 + - 입력: `"1,2:3"` → 출력: `["1", "2", "3"]` + +### 5. 계산 +- [ ] 문자열 배열을 정수 배열로 변환한다 +- [ ] 모든 정수를 합산하여 결과를 반환한다 +- [ ] 음수 또는 비정상 입력 시 → `IllegalArgumentException` + - 입력: `["1", "-2", "3"]` → 예외 발생 + - 입력: `["1", "a", "3"]` → 예외 발생 + + +### 6. 출력 +- [ ] 최종 합산 결과를 `"결과 : X"` 형식으로 출력한다 + - 예: `"결과 : 6"` + +--- + +## 2. 예외 처리 정책 + +### 1. 입력 단계 +| 조건 | 처리 방식 | 메시지 | +|------|-------------|-------------| +| null 입력 | `IllegalArgumentException` | `"입력값이 존재하지 않습니다."` | +| 빈 문자열 | 0 반환 | - | + + +### 2. 검증 단계 +| 조건 | 처리 방식 | 메시지 | +|------|-------------|-------------| +| 음수 입력 | `IllegalArgumentException` | `"음수는 입력할 수 없습니다."` | +| 비숫자 포함 | `IllegalArgumentException` | `"숫자와 구분자만 입력해주세요."` | + + +### 3. 구분자 파싱 단계 +| 조건 | 처리 방식 | 메시지 | +|------|-------------|-------------| +| 커스텀 구분자 식별 실패 | `IllegalArgumentException` | `"커스텀 구분자 형식이 올바르지 않습니다."` | +| 공백 포함 | `IllegalArgumentException` | `"공백은 구분자로 사용할 수 없습니다."` | +| 예약어(`//`, `\n`, `,`, `:`) 포함 | `IllegalArgumentException` | `"예약어는 구분자로 사용할 수 없습니다."` | + + +### 4. 문자열 파싱 단계 +| 조건 | 처리 방식 | 메시지 | +|------|-------------|-------------| +| 숫자 변환 실패 | `IllegalArgumentException` | `"숫자 형식이 올바르지 않습니다."` | +| 음수 존재 | `IllegalArgumentException` | `"음수는 입력할 수 없습니다."` | + + +### 5. 계산 단계 +| 조건 | 처리 방식 | 메시지 | +|------|-------------|-------------| +| 파싱 결과가 비어 있음 | `IllegalArgumentException` | `"계산할 숫자가 없습니다."` | + + +--- + +## 3. 프로젝트 구조 + +``` +src +└─ main/java/calculator + ├─ Application.java # 실행 진입점(main) + │ + ├─ controller + │ └─ CalculatorController.java # 전체 흐름 제어 + │ + ├─ view + │ ├─ InputView.java # 사용자 입력 + │ └─ OutputView.java # 결과 출력 + │ + ├─ model + │ ├─ calculator # 문자열 파싱 및 합산 로직 + │ │ ├─ Calculator.java # 계산 로직(덧셈) + │ │ ├─ Result.java # 계산 결과를 값 객체로 캡슐화 + │ │ └─ Token.java # 파싱된 숫자 토큰을 캡슐화 + │ ├─ parser + │ │ ├─ DelimiterParser.java # 기본/커스텀 구분자 추출 + │ │ ├─ NumberParser.java # 문자열 파싱, 숫자 배열로 변환 + │ │ └─ DelimiterPattern.java # 커스텀 구분자 패턴 정의 + │ └─ validation + │ └─ Validator.java # 입력값 유효성 검사 + │ + └─ util + ├─ Constants.java # 기본 구분자, 정규식 패턴 등의 상수 + └─ ExceptionMessages.java # 예외 메시지 +``` From 9d0f175d65785b7245c22e5754c819a47913c06a Mon Sep 17 00:00:00 2001 From: Lee Ye Jin Date: Mon, 20 Oct 2025 01:45:22 +0900 Subject: [PATCH 02/15] =?UTF-8?q?feat(view)=20:=20=EC=BD=98=EC=86=94=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84(Co?= =?UTF-8?q?nsole.readLine=20=EC=82=AC=EC=9A=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- src/main/java/calculator/view/InputView.java | 13 +++++++++++++ src/test/java/calculator/view/InputViewTest.java | 16 ++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 src/main/java/calculator/view/InputView.java create mode 100644 src/test/java/calculator/view/InputViewTest.java diff --git a/README.md b/README.md index 0d5f1044a1..929d0a2459 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ ## 1. 기능 구현 목록 ### 1. 입력 -- [ ] 콘솔에서 문자열을 입력받는다 +- [x] 콘솔에서 문자열을 입력받는다 - `camp.nextstep.edu.missionutils.Console.readLine()`을 사용 - 예: `"1,2:3"` diff --git a/src/main/java/calculator/view/InputView.java b/src/main/java/calculator/view/InputView.java new file mode 100644 index 0000000000..895e16f24c --- /dev/null +++ b/src/main/java/calculator/view/InputView.java @@ -0,0 +1,13 @@ +package calculator.view; + +import camp.nextstep.edu.missionutils.Console; + +public class InputView { + + private static final String INPUT_MESSAGE = "덧셈할 문자열을 입력해 주세요."; + + public String readInput() { + System.out.println(INPUT_MESSAGE); + return Console.readLine(); + } +} \ No newline at end of file diff --git a/src/test/java/calculator/view/InputViewTest.java b/src/test/java/calculator/view/InputViewTest.java new file mode 100644 index 0000000000..515844600b --- /dev/null +++ b/src/test/java/calculator/view/InputViewTest.java @@ -0,0 +1,16 @@ +package calculator.view; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +class InputViewTest { + + @Test + void 입력_프롬프트가_정상적으로_출력되는지_확인() { + InputView inputView = new InputView(); + assertDoesNotThrow(() -> { + System.out.println(">>> (테스트용 입력) 1,2:3"); + // 이후 Controller에서 IO 테스트 통합 시 수행할 예정 + }); + } +} \ No newline at end of file From 6c8f66801a02c2fa52ffce7dd472c668702b20b2 Mon Sep 17 00:00:00 2001 From: Lee Ye Jin Date: Mon, 20 Oct 2025 02:04:47 +0900 Subject: [PATCH 03/15] =?UTF-8?q?refactor(validation):=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EB=8B=A8?= =?UTF-8?q?=EC=88=9C=ED=99=94=20(null,=20=EB=B9=88=20=EB=AC=B8=EC=9E=90?= =?UTF-8?q?=EC=97=B4,=20=EC=BB=A4=EC=8A=A4=ED=85=80=20=ED=97=A4=EB=8D=94?= =?UTF-8?q?=EB=A7=8C=20=EA=B2=80=EC=A6=9D)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/validation/Validator.java | 29 ++++++++++++++++ .../model/validation/ValidatorTest.java | 34 +++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 src/main/java/calculator/model/validation/Validator.java create mode 100644 src/test/java/calculator/model/validation/ValidatorTest.java diff --git a/src/main/java/calculator/model/validation/Validator.java b/src/main/java/calculator/model/validation/Validator.java new file mode 100644 index 0000000000..023c167c96 --- /dev/null +++ b/src/main/java/calculator/model/validation/Validator.java @@ -0,0 +1,29 @@ +package calculator.model.validation; + +public class Validator { + + private Validator() { + } + + /** + * 입력값이 존재하고, 기본 구조가 올바른지 확인한다 + * + * @param input 사용자 입력 문자열 + * @throws IllegalArgumentException 잘못된 형식일 경우 + */ + public static void validateInput(String input) { + if (input == null) { + throw new IllegalArgumentException("입력값이 존재하지 않습니다."); + } + + // 빈 문자열은 계산 결과 0으로 처리되므로 정상 처리 + if (input.isEmpty()) { + return; + } + + // 커스텀 구분자 형식 검사: "//"로 시작했는데 "\n"이 없는 경우 + if (input.startsWith("//") && !input.contains("\n")) { + throw new IllegalArgumentException("커스텀 구분자 형식이 올바르지 않습니다."); + } + } +} \ No newline at end of file diff --git a/src/test/java/calculator/model/validation/ValidatorTest.java b/src/test/java/calculator/model/validation/ValidatorTest.java new file mode 100644 index 0000000000..6aa5db3fba --- /dev/null +++ b/src/test/java/calculator/model/validation/ValidatorTest.java @@ -0,0 +1,34 @@ +package calculator.model.validation; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class ValidatorTest { + + @Test + void 정상_입력_검증() { + assertDoesNotThrow(() -> Validator.validateInput("1,2:3")); + } + + @Test + void null_입력_예외() { + assertThrows(IllegalArgumentException.class, + () -> Validator.validateInput(null)); + } + + @Test + void 빈_문자열_허용() { + assertDoesNotThrow(() -> Validator.validateInput("")); + } + + @Test + void 커스텀_구분자_형식_정상() { + assertDoesNotThrow(() -> Validator.validateInput("//;\n1;2;3")); + } + + @Test + void 커스텀_구분자_형식_오류() { + assertThrows(IllegalArgumentException.class, + () -> Validator.validateInput("//;1;2;3")); + } +} \ No newline at end of file From 3f35ee179411075b3c56809aa8d97243b73886c7 Mon Sep 17 00:00:00 2001 From: Lee Ye Jin Date: Mon, 20 Oct 2025 02:05:22 +0900 Subject: [PATCH 04/15] =?UTF-8?q?docs(readme):=20=EC=9E=85=EB=A0=A5=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EB=8B=A8=EA=B3=84=20=EC=97=AD=ED=95=A0=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EC=9D=8C=EC=88=98/=EB=B9=84=EC=88=AB?= =?UTF-8?q?=EC=9E=90=20=EA=B2=80=EC=A6=9D=EC=9D=84=20=ED=8C=8C=EC=8B=B1=20?= =?UTF-8?q?=EB=8B=A8=EA=B3=84=EB=A1=9C=20=EC=9D=B4=EB=8F=99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 929d0a2459..fa0a9699cd 100644 --- a/README.md +++ b/README.md @@ -18,14 +18,11 @@ ### 2. 입력 검증 -- [ ] null, 빈 문자열, 비숫자, 음수 등을 검증한다 → `IllegalArgumentException` -- [ ] 빈 문자열(`""`)을 입력하면 `0`을 반환한다 -- [ ] 숫자와 쉼표(`,`)로 이루어진 문자열 → 정상 처리 - - `"1,2,3"` → `6` -- [ ] 숫자와 콜론(`:`)으로 이루어진 문자열 → 정상 처리 - - `"1:2:3"` → `6` -- [ ] 쉼표(`,`)와 콜론(`:`)이 혼합된 문자열 → 정상 처리 - - `"1,2:3"` → `6` +- [x] null, 빈 문자열, 커스텀 구분자 헤더 형식을 검증한다 → `IllegalArgumentException` +- [x] null 입력 → 예외 발생 +- [x] 빈 문자열(`""`) 입력 → 정상 처리 +- [x] 커스텀 구분자 형식 오류 (예: `"//;\n"` 누락) → 예외 발생 +- [ ] 비숫자/음수 검증은 문자열 파싱 단계에서 수행 ### 3. 구분자 파싱 @@ -63,10 +60,11 @@ ## 2. 예외 처리 정책 ### 1. 입력 단계 -| 조건 | 처리 방식 | 메시지 | -|------|-------------|-------------| +| 조건 | 처리 방식 | 메시지 | +|------|----------------------------|-------------| | null 입력 | `IllegalArgumentException` | `"입력값이 존재하지 않습니다."` | -| 빈 문자열 | 0 반환 | - | +| 빈 문자열 | 정상 처리 | - | +| 커스텀 구분자 형식 오류 | IllegalArgumentException | "커스텀 구분자 형식이 올바르지 않습니다." | ### 2. 검증 단계 From 9d7629520eb92e47ff1824a3c6cf8784f0f40e66 Mon Sep 17 00:00:00 2001 From: Lee Ye Jin Date: Mon, 20 Oct 2025 02:22:40 +0900 Subject: [PATCH 05/15] =?UTF-8?q?feat(parser):=20=EA=B8=B0=EB=B3=B8=20?= =?UTF-8?q?=EB=B0=8F=20=EC=BB=A4=EC=8A=A4=ED=85=80=20=EA=B5=AC=EB=B6=84?= =?UTF-8?q?=EC=9E=90=20=ED=8C=8C=EC=8B=B1=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +-- .../model/parser/DelimiterParser.java | 63 +++++++++++++++++++ .../model/parser/DelimiterParserTest.java | 44 +++++++++++++ 3 files changed, 112 insertions(+), 5 deletions(-) create mode 100644 src/main/java/calculator/model/parser/DelimiterParser.java create mode 100644 src/test/java/calculator/model/parser/DelimiterParserTest.java diff --git a/README.md b/README.md index fa0a9699cd..6c7e697203 100644 --- a/README.md +++ b/README.md @@ -26,13 +26,13 @@ ### 3. 구분자 파싱 -- [ ] 입력값의 맨 앞에 `"//"`가 있고, 그 뒤에 `"\n"`이 있으면 커스텀 구분자로 인식한다 +- [x] 입력값의 맨 앞에 `"//"`가 있고, 그 뒤에 `"\n"`이 있으면 커스텀 구분자로 인식한다 - `"//;\n1;2;3"` → 구분자: `;` → 결과: `6` -- [ ] 커스텀 구분자는 **여러 문자를 허용**한다 +- [x] 커스텀 구분자는 **여러 문자를 허용**한다 - `"//***\n1***2***3"` → 결과: `6` -- [ ] 공백 포함 불가 → 예외 발생 `IllegalArgumentException` -- [ ] 예약어(`//`, `\n`, `,`, `:`) 사용 불가 → 예외 발생 `IllegalArgumentException` -- [ ] 커스텀 구분자 식별에 실패할 경우 → `IllegalArgumentException` +- [x] 커스텀 구분자에 공백 포함 시 → 예외 발생 `IllegalArgumentException` +- [x] 예약어(`//`, `\n`, `,`, `:`) 사용 불가 → 예외 발생 `IllegalArgumentException` +- [x] 커스텀 구분자 식별에 실패할 경우 → `IllegalArgumentException` ### 4. 문자열 파싱 diff --git a/src/main/java/calculator/model/parser/DelimiterParser.java b/src/main/java/calculator/model/parser/DelimiterParser.java new file mode 100644 index 0000000000..ef4cb0a67c --- /dev/null +++ b/src/main/java/calculator/model/parser/DelimiterParser.java @@ -0,0 +1,63 @@ +package calculator.model.parser; + +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +public class DelimiterParser { + + private static final List DEFAULT_DELIMITERS = List.of(",", ":"); + + private DelimiterParser() {} + + /** + * 입력값에서 사용할 구분자 목록을 반환한다 + * + * @param input 전체 입력 문자열 + * @return 기본 또는 커스텀 구분자 목록 + */ + public static List parseDelimiters(String input) { + if (!input.startsWith("//")) { + return DEFAULT_DELIMITERS; + } + + int newlineIndex = input.indexOf("\n"); + if (newlineIndex == -1) { + throw new IllegalArgumentException("커스텀 구분자 형식이 올바르지 않습니다."); + } + + String customDelimiter = input.substring(2, newlineIndex); + + validateCustomDelimiter(customDelimiter); + + // 기본 구분자 + 커스텀 구분자 함께 반환 + return Arrays.asList(customDelimiter, ",", ":"); + } + + /** + * 커스텀 구분자 유효성 검사 + */ + private static void validateCustomDelimiter(String delimiter) { + // 전체가 비었거나, 공백이 포함된 경우 + if (delimiter.isBlank() || delimiter.contains(" ")) { + throw new IllegalArgumentException("공백은 구분자로 사용할 수 없습니다."); + } + + // 예약어 포함 여부 + if (delimiter.contains(",") || delimiter.contains(":") || + delimiter.contains("//") || delimiter.contains("\n")) { + throw new IllegalArgumentException("예약어는 구분자로 사용할 수 없습니다."); + } + } + + /** + * 구분자 선언부를 제외한 본문 문자열 반환 + */ + public static String extractBody(String input) { + if (!input.startsWith("//")) { + return input; + } + int newlineIndex = input.indexOf("\n"); + return input.substring(newlineIndex + 1); + } +} \ No newline at end of file diff --git a/src/test/java/calculator/model/parser/DelimiterParserTest.java b/src/test/java/calculator/model/parser/DelimiterParserTest.java new file mode 100644 index 0000000000..3154143b22 --- /dev/null +++ b/src/test/java/calculator/model/parser/DelimiterParserTest.java @@ -0,0 +1,44 @@ +package calculator.model.parser; + +import org.junit.jupiter.api.Test; +import java.util.List; +import static org.junit.jupiter.api.Assertions.*; + +class DelimiterParserTest { + + @Test + void 기본_구분자_반환() { + List result = DelimiterParser.parseDelimiters("1,2:3"); + assertEquals(List.of(",", ":"), result); + } + + @Test + void 커스텀_구분자_정상_파싱() { + List result = DelimiterParser.parseDelimiters("//;\n1;2;3"); + assertTrue(result.contains(";")); + } + + @Test + void 여러문자_커스텀_구분자_허용() { + List result = DelimiterParser.parseDelimiters("//***\n1***2***3"); + assertTrue(result.contains("***")); + } + + @Test + void 공백_포함_예외() { + assertThrows(IllegalArgumentException.class, () -> + DelimiterParser.parseDelimiters("// ;\n1;2;3")); + } + + @Test + void 예약어_포함_예외() { + assertThrows(IllegalArgumentException.class, () -> + DelimiterParser.parseDelimiters("////\n1,2,3")); + } + + @Test + void 본문_추출() { + String body = DelimiterParser.extractBody("//;\n1;2;3"); + assertEquals("1;2;3", body); + } +} \ No newline at end of file From f81cb228c93c5dd24eaf685573fe29348df1b454 Mon Sep 17 00:00:00 2001 From: Lee Ye Jin Date: Mon, 20 Oct 2025 03:01:49 +0900 Subject: [PATCH 06/15] =?UTF-8?q?feat(parser):=20=EB=AC=B8=EC=9E=90?= =?UTF-8?q?=EC=97=B4=20=ED=8C=8C=EC=8B=B1=20=EB=B0=8F=20=EC=88=AB=EC=9E=90?= =?UTF-8?q?=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 ++-- .../model/parser/DelimiterParseResult.java | 21 +++++++++ .../model/parser/DelimiterPattern.java | 20 +++++++++ .../calculator/model/parser/NumberParser.java | 33 ++++++++++++++ .../model/parser/NumberParserTest.java | 43 +++++++++++++++++++ 5 files changed, 121 insertions(+), 4 deletions(-) create mode 100644 src/main/java/calculator/model/parser/DelimiterParseResult.java create mode 100644 src/main/java/calculator/model/parser/DelimiterPattern.java create mode 100644 src/main/java/calculator/model/parser/NumberParser.java create mode 100644 src/test/java/calculator/model/parser/NumberParserTest.java diff --git a/README.md b/README.md index 6c7e697203..d1ec0fdeac 100644 --- a/README.md +++ b/README.md @@ -36,11 +36,11 @@ ### 4. 문자열 파싱 -- [ ] 구분자(기본 또는 커스텀)를 기준으로 문자열을 분리한다 -- [ ] 분리된 각 요소가 숫자로만 이루어져 있는지 검사한다 +- [x] 구분자(기본 또는 커스텀)를 기준으로 문자열을 분리한다 +- [x] 분리된 각 요소가 숫자로만 이루어져 있는지 검사한다 - `"a"`, `"--"`, `"-2"` 등 → `IllegalArgumentException` -- [ ] 숫자로 변환 불가 시 → `IllegalArgumentException` -- [ ] 정상 입력이면 문자열 배열을 반환한다 +- [x] 숫자로 변환 불가 시 → `IllegalArgumentException` +- [x] 정상 입력이면 문자열 배열을 반환한다 - 입력: `"1,2:3"` → 출력: `["1", "2", "3"]` ### 5. 계산 diff --git a/src/main/java/calculator/model/parser/DelimiterParseResult.java b/src/main/java/calculator/model/parser/DelimiterParseResult.java new file mode 100644 index 0000000000..6c9a621eaa --- /dev/null +++ b/src/main/java/calculator/model/parser/DelimiterParseResult.java @@ -0,0 +1,21 @@ +package calculator.model.parser; + +import java.util.List; + +public class DelimiterParseResult { + private final List delimiters; + private final String body; + + public DelimiterParseResult(List delimiters, String body) { + this.delimiters = delimiters; + this.body = body; + } + + public List getDelimiters() { + return delimiters; + } + + public String getBody() { + return body; + } +} \ No newline at end of file diff --git a/src/main/java/calculator/model/parser/DelimiterPattern.java b/src/main/java/calculator/model/parser/DelimiterPattern.java new file mode 100644 index 0000000000..e6396e4f27 --- /dev/null +++ b/src/main/java/calculator/model/parser/DelimiterPattern.java @@ -0,0 +1,20 @@ +package calculator.model.parser; + +import java.util.List; +import java.util.stream.Collectors; + +public class DelimiterPattern { + private static final String REGEX_SPECIALS = "([\\\\^$.|?*+()\\[\\]])"; + + // 모든 구분자를 OR (|)로 연결해서 하나의 정규식 패턴 생성 + public static String buildPattern(List delimiters) { + return delimiters.stream() + .map(DelimiterPattern::escapeSpecialChars) + .collect(Collectors.joining("|")); + } + + // 정규식 예약문자(\, *, [, ]) 등을 이스케이프 처리 + private static String escapeSpecialChars(String delimiter) { + return delimiter.replaceAll(REGEX_SPECIALS, "\\\\$1"); + } +} \ No newline at end of file diff --git a/src/main/java/calculator/model/parser/NumberParser.java b/src/main/java/calculator/model/parser/NumberParser.java new file mode 100644 index 0000000000..882baf3f57 --- /dev/null +++ b/src/main/java/calculator/model/parser/NumberParser.java @@ -0,0 +1,33 @@ +package calculator.model.parser; + +import java.util.Arrays; +import java.util.List; + +public class NumberParser { + + private NumberParser() {} + + public static String[] parse(String body, List delimiters) { + if (body.isEmpty()) { + return new String[]{"0"}; + } + + // 구분자 정규식 생성 + String regex = DelimiterPattern.buildPattern(delimiters); + + // 구분자로 분리 + String[] tokens = body.split(regex); + + // 검증: 비숫자, 음수 체크 + for (String token : tokens) { + if (!token.matches("\\d+")) { + throw new IllegalArgumentException("숫자 형식이 올바르지 않습니다."); + } + if (Integer.parseInt(token) < 0) { + throw new IllegalArgumentException("음수는 입력할 수 없습니다."); + } + } + + return tokens; + } +} \ No newline at end of file diff --git a/src/test/java/calculator/model/parser/NumberParserTest.java b/src/test/java/calculator/model/parser/NumberParserTest.java new file mode 100644 index 0000000000..ee72a225ad --- /dev/null +++ b/src/test/java/calculator/model/parser/NumberParserTest.java @@ -0,0 +1,43 @@ +package calculator.model.parser; + +import org.junit.jupiter.api.Test; +import java.util.List; +import static org.junit.jupiter.api.Assertions.*; + +class NumberParserTest { + + @Test + void 기본_구분자로_문자열_분리() { + List delimiters = List.of(",", ":"); + String[] result = NumberParser.parse("1,2:3", delimiters); + assertArrayEquals(new String[]{"1", "2", "3"}, result); + } + + @Test + void 커스텀_구분자로_문자열_분리() { + List delimiters = List.of("***"); + String[] result = NumberParser.parse("1***2***3", delimiters); + assertArrayEquals(new String[]{"1", "2", "3"}, result); + } + + @Test + void 음수_입력_예외() { + List delimiters = List.of(",", ":"); + assertThrows(IllegalArgumentException.class, () -> + NumberParser.parse("1,-2,3", delimiters)); + } + + @Test + void 비숫자_입력_예외() { + List delimiters = List.of(",", ":"); + assertThrows(IllegalArgumentException.class, () -> + NumberParser.parse("1,a,3", delimiters)); + } + + @Test + void 빈_입력은_0으로_처리() { + List delimiters = List.of(",", ":"); + String[] result = NumberParser.parse("", delimiters); + assertArrayEquals(new String[]{"0"}, result); + } +} \ No newline at end of file From cb14324497cbf15f800d191a8aa03501342dceb6 Mon Sep 17 00:00:00 2001 From: Lee Ye Jin Date: Mon, 20 Oct 2025 03:10:34 +0900 Subject: [PATCH 07/15] =?UTF-8?q?feat(calculator):=20=EB=AC=B8=EC=9E=90?= =?UTF-8?q?=EC=97=B4=20=EB=B0=B0=EC=97=B4=20=EB=8D=A7=EC=85=88=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +-- .../controller/CalculatorController.java | 4 ++ .../model/calculator/Calculator.java | 35 ++++++++++++++++ .../model/calculator/CalculatorTest.java | 40 +++++++++++++++++++ 4 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 src/main/java/calculator/controller/CalculatorController.java create mode 100644 src/main/java/calculator/model/calculator/Calculator.java create mode 100644 src/test/java/calculator/model/calculator/CalculatorTest.java diff --git a/README.md b/README.md index d1ec0fdeac..f5b6346683 100644 --- a/README.md +++ b/README.md @@ -44,9 +44,9 @@ - 입력: `"1,2:3"` → 출력: `["1", "2", "3"]` ### 5. 계산 -- [ ] 문자열 배열을 정수 배열로 변환한다 -- [ ] 모든 정수를 합산하여 결과를 반환한다 -- [ ] 음수 또는 비정상 입력 시 → `IllegalArgumentException` +- [x] 문자열 배열을 정수 배열로 변환한다 +- [x] 모든 정수를 합산하여 결과를 반환한다 +- [x] 음수 또는 비정상 입력 시 → `IllegalArgumentException` - 입력: `["1", "-2", "3"]` → 예외 발생 - 입력: `["1", "a", "3"]` → 예외 발생 diff --git a/src/main/java/calculator/controller/CalculatorController.java b/src/main/java/calculator/controller/CalculatorController.java new file mode 100644 index 0000000000..f2844877e4 --- /dev/null +++ b/src/main/java/calculator/controller/CalculatorController.java @@ -0,0 +1,4 @@ +package calculator.controller; + +public class CalculatorController { +} diff --git a/src/main/java/calculator/model/calculator/Calculator.java b/src/main/java/calculator/model/calculator/Calculator.java new file mode 100644 index 0000000000..d164130173 --- /dev/null +++ b/src/main/java/calculator/model/calculator/Calculator.java @@ -0,0 +1,35 @@ +package calculator.model.calculator; + +import java.util.Arrays; + +/** + * 파싱된 문자열 토큰을 정수로 변환해 합산한다 + */ +public class Calculator { + + private Calculator() {} + + public static Result calculate(String[] tokens) { + if (tokens == null || tokens.length == 0) { + throw new IllegalArgumentException("계산할 숫자가 없습니다."); + } + + int sum = Arrays.stream(tokens) + .mapToInt(Calculator::parseAndValidate) + .sum(); + + return new Result(sum); + } + + private static int parseAndValidate(String token) { + try { + int number = Integer.parseInt(token); + if (number < 0) { + throw new IllegalArgumentException("음수는 입력할 수 없습니다."); + } + return number; + } catch (NumberFormatException e) { + throw new IllegalArgumentException("숫자 형식이 올바르지 않습니다."); + } + } +} \ No newline at end of file diff --git a/src/test/java/calculator/model/calculator/CalculatorTest.java b/src/test/java/calculator/model/calculator/CalculatorTest.java new file mode 100644 index 0000000000..95612df158 --- /dev/null +++ b/src/test/java/calculator/model/calculator/CalculatorTest.java @@ -0,0 +1,40 @@ +package calculator.model.calculator; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class CalculatorTest { + + @Test + void 정상_입력_합산() { + String[] tokens = {"1", "2", "3"}; + Result result = Calculator.calculate(tokens); + assertEquals(6, result.getValue()); + } + + @Test + void 음수_입력_예외() { + String[] tokens = {"1", "-2", "3"}; + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> + Calculator.calculate(tokens) + ); + assertEquals("음수는 입력할 수 없습니다.", e.getMessage()); + } + + @Test + void 비숫자_입력_예외() { + String[] tokens = {"1", "a", "3"}; + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> + Calculator.calculate(tokens) + ); + assertEquals("숫자 형식이 올바르지 않습니다.", e.getMessage()); + } + + @Test + void 비어있는_토큰_배열_예외() { + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> + Calculator.calculate(new String[]{}) + ); + assertEquals("계산할 숫자가 없습니다.", e.getMessage()); + } +} \ No newline at end of file From d0c3c396022be0c11c2e33c1afd71687da8cba12 Mon Sep 17 00:00:00 2001 From: Lee Ye Jin Date: Mon, 20 Oct 2025 03:56:09 +0900 Subject: [PATCH 08/15] =?UTF-8?q?refactor(parser):=20=EA=B5=AC=EB=B6=84?= =?UTF-8?q?=EC=9E=90=20=EB=AA=A9=EB=A1=9D=EA=B3=BC=20=EB=B3=B8=EB=AC=B8?= =?UTF-8?q?=EC=9D=84=20=ED=95=A8=EA=BB=98=20=EB=B0=98=ED=99=98=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20parse()=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../calculator/model/parser/DelimiterParser.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/main/java/calculator/model/parser/DelimiterParser.java b/src/main/java/calculator/model/parser/DelimiterParser.java index ef4cb0a67c..815b3a236a 100644 --- a/src/main/java/calculator/model/parser/DelimiterParser.java +++ b/src/main/java/calculator/model/parser/DelimiterParser.java @@ -11,14 +11,15 @@ public class DelimiterParser { private DelimiterParser() {} /** - * 입력값에서 사용할 구분자 목록을 반환한다 + * 입력 문자열에서 구분자 목록과 본문(body)을 함께 추출한다 * * @param input 전체 입력 문자열 - * @return 기본 또는 커스텀 구분자 목록 + * @return 구분자 목록과 본문을 담은 DelimiterParseResult */ - public static List parseDelimiters(String input) { + public static DelimiterParseResult parse(String input) { if (!input.startsWith("//")) { - return DEFAULT_DELIMITERS; + // 기본 구분자만 사용하는 경우 + return new DelimiterParseResult(DEFAULT_DELIMITERS, input); } int newlineIndex = input.indexOf("\n"); @@ -27,11 +28,12 @@ public static List parseDelimiters(String input) { } String customDelimiter = input.substring(2, newlineIndex); - validateCustomDelimiter(customDelimiter); - // 기본 구분자 + 커스텀 구분자 함께 반환 - return Arrays.asList(customDelimiter, ",", ":"); + List delimiters = Arrays.asList(customDelimiter, ",", ":"); + String body = input.substring(newlineIndex + 1); + + return new DelimiterParseResult(delimiters, body); } /** From c41ce6e014125fa12328a110c617911d4309649e Mon Sep 17 00:00:00 2001 From: Lee Ye Jin Date: Mon, 20 Oct 2025 03:57:03 +0900 Subject: [PATCH 09/15] =?UTF-8?q?feat(controller):=20=EA=B2=B0=EA=B3=BC=20?= =?UTF-8?q?=EC=B6=9C=EB=A0=A5=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/calculator/Application.java | 4 ++- .../controller/CalculatorController.java | 27 ++++++++++++++++++- src/main/java/calculator/view/InputView.java | 2 +- src/main/java/calculator/view/OutputView.java | 10 +++++++ 4 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 src/main/java/calculator/view/OutputView.java diff --git a/src/main/java/calculator/Application.java b/src/main/java/calculator/Application.java index 573580fb40..bdff7930e1 100644 --- a/src/main/java/calculator/Application.java +++ b/src/main/java/calculator/Application.java @@ -1,7 +1,9 @@ package calculator; +import calculator.controller.CalculatorController; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + new CalculatorController().run(); } } diff --git a/src/main/java/calculator/controller/CalculatorController.java b/src/main/java/calculator/controller/CalculatorController.java index f2844877e4..0bd16165fd 100644 --- a/src/main/java/calculator/controller/CalculatorController.java +++ b/src/main/java/calculator/controller/CalculatorController.java @@ -1,4 +1,29 @@ package calculator.controller; +import calculator.model.calculator.Calculator; +import calculator.model.calculator.Result; +import calculator.model.parser.DelimiterParseResult; +import calculator.model.parser.DelimiterParser; +import calculator.model.parser.NumberParser; +import calculator.model.validation.Validator; +import calculator.view.InputView; +import calculator.view.OutputView; + +/** + * 전체 애플리케이션의 흐름을 제어하는 컨트롤러 + */ public class CalculatorController { -} + + public void run() { + String input = InputView.readInput(); + + Validator.validateInput(input); + + DelimiterParseResult parsed = DelimiterParser.parse(input); + String[] tokens = NumberParser.parse(parsed.getBody(), parsed.getDelimiters()); + + Result result = Calculator.calculate(tokens); + + OutputView.printResult(result.getValue()); + } +} \ No newline at end of file diff --git a/src/main/java/calculator/view/InputView.java b/src/main/java/calculator/view/InputView.java index 895e16f24c..1f3d02700b 100644 --- a/src/main/java/calculator/view/InputView.java +++ b/src/main/java/calculator/view/InputView.java @@ -6,7 +6,7 @@ public class InputView { private static final String INPUT_MESSAGE = "덧셈할 문자열을 입력해 주세요."; - public String readInput() { + public static String readInput() { System.out.println(INPUT_MESSAGE); return Console.readLine(); } diff --git a/src/main/java/calculator/view/OutputView.java b/src/main/java/calculator/view/OutputView.java new file mode 100644 index 0000000000..1e18aebfc0 --- /dev/null +++ b/src/main/java/calculator/view/OutputView.java @@ -0,0 +1,10 @@ +package calculator.view; + +public class OutputView { + + private OutputView() {} + + public static void printResult(int result) { + System.out.println("결과 : " + result); + } +} \ No newline at end of file From 11351d683087dea2560c18e981bf66985a448256 Mon Sep 17 00:00:00 2001 From: Lee Ye Jin Date: Mon, 20 Oct 2025 04:17:37 +0900 Subject: [PATCH 10/15] =?UTF-8?q?feat(controller):=20=EA=B2=B0=EA=B3=BC=20?= =?UTF-8?q?=EC=B6=9C=EB=A0=A5=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../model/parser/DelimiterParser.java | 2 ++ .../model/validation/Validator.java | 5 --- .../model/parser/DelimiterParserTest.java | 33 +++++++++---------- .../model/validation/ValidatorTest.java | 6 ---- 5 files changed, 19 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index f5b6346683..abb3329fa5 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ ### 6. 출력 -- [ ] 최종 합산 결과를 `"결과 : X"` 형식으로 출력한다 +- [x] 최종 합산 결과를 `"결과 : X"` 형식으로 출력한다 - 예: `"결과 : 6"` --- diff --git a/src/main/java/calculator/model/parser/DelimiterParser.java b/src/main/java/calculator/model/parser/DelimiterParser.java index 815b3a236a..d703bc582c 100644 --- a/src/main/java/calculator/model/parser/DelimiterParser.java +++ b/src/main/java/calculator/model/parser/DelimiterParser.java @@ -17,6 +17,8 @@ private DelimiterParser() {} * @return 구분자 목록과 본문을 담은 DelimiterParseResult */ public static DelimiterParseResult parse(String input) { + input = input.replace("\\n", "\n"); + if (!input.startsWith("//")) { // 기본 구분자만 사용하는 경우 return new DelimiterParseResult(DEFAULT_DELIMITERS, input); diff --git a/src/main/java/calculator/model/validation/Validator.java b/src/main/java/calculator/model/validation/Validator.java index 023c167c96..7707861cea 100644 --- a/src/main/java/calculator/model/validation/Validator.java +++ b/src/main/java/calculator/model/validation/Validator.java @@ -20,10 +20,5 @@ public static void validateInput(String input) { if (input.isEmpty()) { return; } - - // 커스텀 구분자 형식 검사: "//"로 시작했는데 "\n"이 없는 경우 - if (input.startsWith("//") && !input.contains("\n")) { - throw new IllegalArgumentException("커스텀 구분자 형식이 올바르지 않습니다."); - } } } \ No newline at end of file diff --git a/src/test/java/calculator/model/parser/DelimiterParserTest.java b/src/test/java/calculator/model/parser/DelimiterParserTest.java index 3154143b22..ad1448cd8d 100644 --- a/src/test/java/calculator/model/parser/DelimiterParserTest.java +++ b/src/test/java/calculator/model/parser/DelimiterParserTest.java @@ -7,38 +7,37 @@ class DelimiterParserTest { @Test - void 기본_구분자_반환() { - List result = DelimiterParser.parseDelimiters("1,2:3"); - assertEquals(List.of(",", ":"), result); + void 기본_구분자만_반환() { + DelimiterParseResult result = DelimiterParser.parse("1,2:3"); + assertEquals(List.of(",", ":"), result.getDelimiters()); + assertEquals("1,2:3", result.getBody()); } @Test void 커스텀_구분자_정상_파싱() { - List result = DelimiterParser.parseDelimiters("//;\n1;2;3"); - assertTrue(result.contains(";")); + DelimiterParseResult result = DelimiterParser.parse("//;\n1;2;3"); + assertTrue(result.getDelimiters().contains(";")); + assertEquals("1;2;3", result.getBody()); } @Test void 여러문자_커스텀_구분자_허용() { - List result = DelimiterParser.parseDelimiters("//***\n1***2***3"); - assertTrue(result.contains("***")); + DelimiterParseResult result = DelimiterParser.parse("//***\n1***2***3"); + assertTrue(result.getDelimiters().contains("***")); + assertEquals("1***2***3", result.getBody()); } @Test void 공백_포함_예외() { - assertThrows(IllegalArgumentException.class, () -> - DelimiterParser.parseDelimiters("// ;\n1;2;3")); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> + DelimiterParser.parse("// ;\n1;2;3")); + assertEquals("공백은 구분자로 사용할 수 없습니다.", e.getMessage()); } @Test void 예약어_포함_예외() { - assertThrows(IllegalArgumentException.class, () -> - DelimiterParser.parseDelimiters("////\n1,2,3")); - } - - @Test - void 본문_추출() { - String body = DelimiterParser.extractBody("//;\n1;2;3"); - assertEquals("1;2;3", body); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> + DelimiterParser.parse("////\n1,2,3")); + assertEquals("예약어는 구분자로 사용할 수 없습니다.", e.getMessage()); } } \ No newline at end of file diff --git a/src/test/java/calculator/model/validation/ValidatorTest.java b/src/test/java/calculator/model/validation/ValidatorTest.java index 6aa5db3fba..126cd3fc4a 100644 --- a/src/test/java/calculator/model/validation/ValidatorTest.java +++ b/src/test/java/calculator/model/validation/ValidatorTest.java @@ -25,10 +25,4 @@ class ValidatorTest { void 커스텀_구분자_형식_정상() { assertDoesNotThrow(() -> Validator.validateInput("//;\n1;2;3")); } - - @Test - void 커스텀_구분자_형식_오류() { - assertThrows(IllegalArgumentException.class, - () -> Validator.validateInput("//;1;2;3")); - } } \ No newline at end of file From a354e87078e8dabdbffa918323e491a0aa732a3f Mon Sep 17 00:00:00 2001 From: Lee Ye Jin Date: Mon, 20 Oct 2025 04:59:55 +0900 Subject: [PATCH 11/15] =?UTF-8?q?refactor(constants):=20=EA=B3=B5=ED=86=B5?= =?UTF-8?q?=20=EC=83=81=EC=88=98=20=EC=A0=81=EC=9A=A9=20(=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8/=EC=BB=A4=EC=8A=A4=ED=85=80=20=EA=B5=AC=EB=B6=84?= =?UTF-8?q?=EC=9E=90,=20=EC=9E=85=EC=B6=9C=EB=A0=A5=20=EB=A9=94=EC=8B=9C?= =?UTF-8?q?=EC=A7=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CalculatorController.java | 13 ++--- .../model/calculator/Calculator.java | 4 ++ .../model/parser/DelimiterParser.java | 50 +++++++++++-------- src/main/java/calculator/util/Constants.java | 34 +++++++++++++ src/main/java/calculator/view/InputView.java | 7 ++- src/main/java/calculator/view/OutputView.java | 9 ++-- 6 files changed, 79 insertions(+), 38 deletions(-) create mode 100644 src/main/java/calculator/util/Constants.java diff --git a/src/main/java/calculator/controller/CalculatorController.java b/src/main/java/calculator/controller/CalculatorController.java index 0bd16165fd..a21ed83b50 100644 --- a/src/main/java/calculator/controller/CalculatorController.java +++ b/src/main/java/calculator/controller/CalculatorController.java @@ -1,7 +1,6 @@ package calculator.controller; import calculator.model.calculator.Calculator; -import calculator.model.calculator.Result; import calculator.model.parser.DelimiterParseResult; import calculator.model.parser.DelimiterParser; import calculator.model.parser.NumberParser; @@ -14,16 +13,14 @@ */ public class CalculatorController { - public void run() { - String input = InputView.readInput(); + private final InputView inputView = new InputView(); + private final OutputView outputView = new OutputView(); + public void run() { + String input = inputView.read(); Validator.validateInput(input); - DelimiterParseResult parsed = DelimiterParser.parse(input); String[] tokens = NumberParser.parse(parsed.getBody(), parsed.getDelimiters()); - - Result result = Calculator.calculate(tokens); - - OutputView.printResult(result.getValue()); + outputView.printResult(Calculator.calculate(tokens)); } } \ No newline at end of file diff --git a/src/main/java/calculator/model/calculator/Calculator.java b/src/main/java/calculator/model/calculator/Calculator.java index d164130173..bd65b2cd50 100644 --- a/src/main/java/calculator/model/calculator/Calculator.java +++ b/src/main/java/calculator/model/calculator/Calculator.java @@ -22,6 +22,10 @@ public static Result calculate(String[] tokens) { } private static int parseAndValidate(String token) { + if (token == null || token.isBlank()) { + throw new IllegalArgumentException("계산할 숫자가 없습니다."); + } + try { int number = Integer.parseInt(token); if (number < 0) { diff --git a/src/main/java/calculator/model/parser/DelimiterParser.java b/src/main/java/calculator/model/parser/DelimiterParser.java index d703bc582c..16a7d80607 100644 --- a/src/main/java/calculator/model/parser/DelimiterParser.java +++ b/src/main/java/calculator/model/parser/DelimiterParser.java @@ -1,12 +1,15 @@ package calculator.model.parser; +import calculator.util.Constants; import java.util.Arrays; import java.util.List; -import java.util.regex.Pattern; public class DelimiterParser { - private static final List DEFAULT_DELIMITERS = List.of(",", ":"); + private static final List DEFAULT_DELIMITERS = List.of( + Constants.DEFAULT_DELIMITER_COMMA.get(), + Constants.DEFAULT_DELIMITER_COLON.get() + ); private DelimiterParser() {} @@ -17,22 +20,35 @@ private DelimiterParser() {} * @return 구분자 목록과 본문을 담은 DelimiterParseResult */ public static DelimiterParseResult parse(String input) { - input = input.replace("\\n", "\n"); + // 개행 문자 이스케이프 처리 + input = input.replace( + Constants.CUSTOM_NEWLINE_ESCAPE.get(), + Constants.CUSTOM_NEWLINE_ACTUAL.get() + ); - if (!input.startsWith("//")) { - // 기본 구분자만 사용하는 경우 + // 커스텀 구분자가 없는 경우 + if (!input.startsWith(Constants.CUSTOM_PREFIX.get())) { return new DelimiterParseResult(DEFAULT_DELIMITERS, input); } - int newlineIndex = input.indexOf("\n"); + int newlineIndex = input.indexOf(Constants.CUSTOM_NEWLINE_ACTUAL.get()); if (newlineIndex == -1) { throw new IllegalArgumentException("커스텀 구분자 형식이 올바르지 않습니다."); } - String customDelimiter = input.substring(2, newlineIndex); + String customDelimiter = input.substring( + Constants.CUSTOM_PREFIX.get().length(), + newlineIndex + ); + validateCustomDelimiter(customDelimiter); - List delimiters = Arrays.asList(customDelimiter, ",", ":"); + List delimiters = Arrays.asList( + customDelimiter, + Constants.DEFAULT_DELIMITER_COMMA.get(), + Constants.DEFAULT_DELIMITER_COLON.get() + ); + String body = input.substring(newlineIndex + 1); return new DelimiterParseResult(delimiters, body); @@ -47,21 +63,11 @@ private static void validateCustomDelimiter(String delimiter) { throw new IllegalArgumentException("공백은 구분자로 사용할 수 없습니다."); } - // 예약어 포함 여부 - if (delimiter.contains(",") || delimiter.contains(":") || - delimiter.contains("//") || delimiter.contains("\n")) { + if (delimiter.contains(Constants.DEFAULT_DELIMITER_COMMA.get()) + || delimiter.contains(Constants.DEFAULT_DELIMITER_COLON.get()) + || delimiter.contains(Constants.CUSTOM_PREFIX.get()) + || delimiter.contains(Constants.CUSTOM_NEWLINE_ACTUAL.get())) { throw new IllegalArgumentException("예약어는 구분자로 사용할 수 없습니다."); } } - - /** - * 구분자 선언부를 제외한 본문 문자열 반환 - */ - public static String extractBody(String input) { - if (!input.startsWith("//")) { - return input; - } - int newlineIndex = input.indexOf("\n"); - return input.substring(newlineIndex + 1); - } } \ No newline at end of file diff --git a/src/main/java/calculator/util/Constants.java b/src/main/java/calculator/util/Constants.java new file mode 100644 index 0000000000..3f8ac62477 --- /dev/null +++ b/src/main/java/calculator/util/Constants.java @@ -0,0 +1,34 @@ +package calculator.util; + +/** + * 문자열 계산기에서 사용되는 상수 모음 + */ +public enum Constants { + + // 기본 구분자 + DEFAULT_DELIMITER_COMMA(","), + DEFAULT_DELIMITER_COLON(":"), + + // 커스텀 구분자 + CUSTOM_PREFIX("//"), + CUSTOM_NEWLINE_ESCAPE("\\n"), // 입력에 들어오는 "\n" + CUSTOM_NEWLINE_ACTUAL("\n"), // 실제 개행 문자 + CUSTOM_SEPARATOR_PATTERN("//(.)\n(.*)"), + + // 입출력 메시지 + INPUT_PROMPT("덧셈할 문자열을 입력해 주세요."), + RESULT_PREFIX("결과 : "), + + // 기본값 + EMPTY_INPUT_RESULT("0"); + + private final String value; + + Constants(String value) { + this.value = value; + } + + public String get() { + return value; + } +} \ No newline at end of file diff --git a/src/main/java/calculator/view/InputView.java b/src/main/java/calculator/view/InputView.java index 1f3d02700b..035e9e6a7f 100644 --- a/src/main/java/calculator/view/InputView.java +++ b/src/main/java/calculator/view/InputView.java @@ -1,13 +1,12 @@ package calculator.view; import camp.nextstep.edu.missionutils.Console; +import calculator.util.Constants; public class InputView { - private static final String INPUT_MESSAGE = "덧셈할 문자열을 입력해 주세요."; - - public static String readInput() { - System.out.println(INPUT_MESSAGE); + public String read() { + System.out.println(Constants.INPUT_PROMPT.get()); return Console.readLine(); } } \ No newline at end of file diff --git a/src/main/java/calculator/view/OutputView.java b/src/main/java/calculator/view/OutputView.java index 1e18aebfc0..d09a6d002f 100644 --- a/src/main/java/calculator/view/OutputView.java +++ b/src/main/java/calculator/view/OutputView.java @@ -1,10 +1,11 @@ package calculator.view; -public class OutputView { +import calculator.model.calculator.Result; +import calculator.util.Constants; - private OutputView() {} +public class OutputView { - public static void printResult(int result) { - System.out.println("결과 : " + result); + public void printResult(Result result) { + System.out.println(Constants.RESULT_PREFIX.get() + result.getValue()); } } \ No newline at end of file From b38a7e1b0b16c52d86a4b09a8604adf6911754e3 Mon Sep 17 00:00:00 2001 From: Lee Ye Jin Date: Mon, 20 Oct 2025 05:05:18 +0900 Subject: [PATCH 12/15] =?UTF-8?q?refactor(exception):=20=EC=98=88=EC=99=B8?= =?UTF-8?q?=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=83=81=EC=88=98=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/calculator/Calculator.java | 9 +++---- .../calculator/model/calculator/Result.java | 18 ++++++++++++++ .../model/parser/DelimiterParser.java | 8 +++---- .../calculator/model/parser/NumberParser.java | 5 ++-- .../model/validation/Validator.java | 5 +++- .../calculator/util/ExceptionMessages.java | 24 +++++++++++++++++++ 6 files changed, 58 insertions(+), 11 deletions(-) create mode 100644 src/main/java/calculator/model/calculator/Result.java create mode 100644 src/main/java/calculator/util/ExceptionMessages.java diff --git a/src/main/java/calculator/model/calculator/Calculator.java b/src/main/java/calculator/model/calculator/Calculator.java index bd65b2cd50..3d0ceeaadd 100644 --- a/src/main/java/calculator/model/calculator/Calculator.java +++ b/src/main/java/calculator/model/calculator/Calculator.java @@ -1,5 +1,6 @@ package calculator.model.calculator; +import calculator.util.ExceptionMessages; import java.util.Arrays; /** @@ -11,7 +12,7 @@ private Calculator() {} public static Result calculate(String[] tokens) { if (tokens == null || tokens.length == 0) { - throw new IllegalArgumentException("계산할 숫자가 없습니다."); + throw new IllegalArgumentException(ExceptionMessages.INPUT_EMPTY.get()); } int sum = Arrays.stream(tokens) @@ -23,17 +24,17 @@ public static Result calculate(String[] tokens) { private static int parseAndValidate(String token) { if (token == null || token.isBlank()) { - throw new IllegalArgumentException("계산할 숫자가 없습니다."); + throw new IllegalArgumentException(ExceptionMessages.INPUT_EMPTY.get()); } try { int number = Integer.parseInt(token); if (number < 0) { - throw new IllegalArgumentException("음수는 입력할 수 없습니다."); + throw new IllegalArgumentException(ExceptionMessages.NEGATIVE_NUMBER.get()); } return number; } catch (NumberFormatException e) { - throw new IllegalArgumentException("숫자 형식이 올바르지 않습니다."); + throw new IllegalArgumentException(ExceptionMessages.INVALID_NUMBER_FORMAT.get()); } } } \ No newline at end of file diff --git a/src/main/java/calculator/model/calculator/Result.java b/src/main/java/calculator/model/calculator/Result.java new file mode 100644 index 0000000000..3b54b8702e --- /dev/null +++ b/src/main/java/calculator/model/calculator/Result.java @@ -0,0 +1,18 @@ +package calculator.model.calculator; + +public class Result { + private final int value; + + public Result(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + @Override + public String toString() { + return "결과 : " + value; + } +} \ No newline at end of file diff --git a/src/main/java/calculator/model/parser/DelimiterParser.java b/src/main/java/calculator/model/parser/DelimiterParser.java index 16a7d80607..f6c01ddf16 100644 --- a/src/main/java/calculator/model/parser/DelimiterParser.java +++ b/src/main/java/calculator/model/parser/DelimiterParser.java @@ -1,6 +1,7 @@ package calculator.model.parser; import calculator.util.Constants; +import calculator.util.ExceptionMessages; import java.util.Arrays; import java.util.List; @@ -33,7 +34,7 @@ public static DelimiterParseResult parse(String input) { int newlineIndex = input.indexOf(Constants.CUSTOM_NEWLINE_ACTUAL.get()); if (newlineIndex == -1) { - throw new IllegalArgumentException("커스텀 구분자 형식이 올바르지 않습니다."); + throw new IllegalArgumentException(ExceptionMessages.INVALID_CUSTOM_FORMAT.get()); } String customDelimiter = input.substring( @@ -58,16 +59,15 @@ public static DelimiterParseResult parse(String input) { * 커스텀 구분자 유효성 검사 */ private static void validateCustomDelimiter(String delimiter) { - // 전체가 비었거나, 공백이 포함된 경우 if (delimiter.isBlank() || delimiter.contains(" ")) { - throw new IllegalArgumentException("공백은 구분자로 사용할 수 없습니다."); + throw new IllegalArgumentException(ExceptionMessages.INVALID_CUSTOM_WHITESPACE.get()); } if (delimiter.contains(Constants.DEFAULT_DELIMITER_COMMA.get()) || delimiter.contains(Constants.DEFAULT_DELIMITER_COLON.get()) || delimiter.contains(Constants.CUSTOM_PREFIX.get()) || delimiter.contains(Constants.CUSTOM_NEWLINE_ACTUAL.get())) { - throw new IllegalArgumentException("예약어는 구분자로 사용할 수 없습니다."); + throw new IllegalArgumentException(ExceptionMessages.INVALID_CUSTOM_RESERVED.get()); } } } \ No newline at end of file diff --git a/src/main/java/calculator/model/parser/NumberParser.java b/src/main/java/calculator/model/parser/NumberParser.java index 882baf3f57..4ad35fd87e 100644 --- a/src/main/java/calculator/model/parser/NumberParser.java +++ b/src/main/java/calculator/model/parser/NumberParser.java @@ -1,5 +1,6 @@ package calculator.model.parser; +import calculator.util.ExceptionMessages; import java.util.Arrays; import java.util.List; @@ -21,10 +22,10 @@ public static String[] parse(String body, List delimiters) { // 검증: 비숫자, 음수 체크 for (String token : tokens) { if (!token.matches("\\d+")) { - throw new IllegalArgumentException("숫자 형식이 올바르지 않습니다."); + throw new IllegalArgumentException(ExceptionMessages.INVALID_NUMBER_FORMAT.get()); } if (Integer.parseInt(token) < 0) { - throw new IllegalArgumentException("음수는 입력할 수 없습니다."); + throw new IllegalArgumentException(ExceptionMessages.NEGATIVE_NUMBER.get()); } } diff --git a/src/main/java/calculator/model/validation/Validator.java b/src/main/java/calculator/model/validation/Validator.java index 7707861cea..0c916e7dc5 100644 --- a/src/main/java/calculator/model/validation/Validator.java +++ b/src/main/java/calculator/model/validation/Validator.java @@ -1,5 +1,7 @@ package calculator.model.validation; +import calculator.util.ExceptionMessages; + public class Validator { private Validator() { @@ -13,7 +15,8 @@ private Validator() { */ public static void validateInput(String input) { if (input == null) { - throw new IllegalArgumentException("입력값이 존재하지 않습니다."); + throw new IllegalArgumentException(ExceptionMessages.INPUT_NULL.get() + ); } // 빈 문자열은 계산 결과 0으로 처리되므로 정상 처리 diff --git a/src/main/java/calculator/util/ExceptionMessages.java b/src/main/java/calculator/util/ExceptionMessages.java new file mode 100644 index 0000000000..7748b53a25 --- /dev/null +++ b/src/main/java/calculator/util/ExceptionMessages.java @@ -0,0 +1,24 @@ +package calculator.util; + +public enum ExceptionMessages { + + INPUT_NULL("입력값이 존재하지 않습니다."), + INPUT_EMPTY("계산할 숫자가 없습니다."), + + INVALID_CUSTOM_FORMAT("커스텀 구분자 형식이 올바르지 않습니다."), + INVALID_CUSTOM_WHITESPACE("공백은 구분자로 사용할 수 없습니다."), + INVALID_CUSTOM_RESERVED("예약어는 구분자로 사용할 수 없습니다."), + + INVALID_NUMBER_FORMAT("숫자 형식이 올바르지 않습니다."), + NEGATIVE_NUMBER("음수는 입력할 수 없습니다."); + + private final String message; + + ExceptionMessages(String message) { + this.message = message; + } + + public String get() { + return message; + } +} \ No newline at end of file From 6599966c96b274ad5a024ca5affe368182202670 Mon Sep 17 00:00:00 2001 From: Lee Ye Jin Date: Mon, 20 Oct 2025 05:14:00 +0900 Subject: [PATCH 13/15] =?UTF-8?q?refactor(controller):=20=EC=A0=9C?= =?UTF-8?q?=EC=96=B4=20=ED=9D=90=EB=A6=84=EC=9D=84=20=EB=8B=A8=EA=B3=84?= =?UTF-8?q?=EB=B3=84=20=EB=A9=94=EC=84=9C=EB=93=9C=EB=A1=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CalculatorController.java | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/src/main/java/calculator/controller/CalculatorController.java b/src/main/java/calculator/controller/CalculatorController.java index a21ed83b50..e6d9fef0cf 100644 --- a/src/main/java/calculator/controller/CalculatorController.java +++ b/src/main/java/calculator/controller/CalculatorController.java @@ -1,6 +1,7 @@ package calculator.controller; import calculator.model.calculator.Calculator; +import calculator.model.calculator.Result; import calculator.model.parser.DelimiterParseResult; import calculator.model.parser.DelimiterParser; import calculator.model.parser.NumberParser; @@ -8,19 +9,37 @@ import calculator.view.InputView; import calculator.view.OutputView; -/** - * 전체 애플리케이션의 흐름을 제어하는 컨트롤러 - */ public class CalculatorController { private final InputView inputView = new InputView(); private final OutputView outputView = new OutputView(); public void run() { - String input = inputView.read(); + String input = readInput(); + String[] tokens = parseInput(input); + Result result = calculate(tokens); + printResult(result); + } + + /** 사용자 입력 */ + private String readInput() { + return inputView.read(); + } + + /** 입력 검증 및 구분자/숫자 파싱 */ + private String[] parseInput(String input) { Validator.validateInput(input); - DelimiterParseResult parsed = DelimiterParser.parse(input); - String[] tokens = NumberParser.parse(parsed.getBody(), parsed.getDelimiters()); - outputView.printResult(Calculator.calculate(tokens)); + DelimiterParseResult parseResult = DelimiterParser.parse(input); + return NumberParser.parse(parseResult.getBody(), parseResult.getDelimiters()); + } + + /** 계산 로직 */ + private Result calculate(String[] tokens) { + return Calculator.calculate(tokens); + } + + /** 결과 출력 */ + private void printResult(Result result) { + outputView.printResult(result); } } \ No newline at end of file From f31199b3a4241a6149d8a5569d6093a91431e133 Mon Sep 17 00:00:00 2001 From: Lee Ye Jin Date: Mon, 20 Oct 2025 05:43:03 +0900 Subject: [PATCH 14/15] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../controller/CalculatorController.java | 19 ++++++---- .../calculator/model/parser/NumberParser.java | 3 +- .../controller/CalculatorControllerTest.java | 37 +++++++++++++++++++ .../model/calculator/CalculatorTest.java | 23 ++++++------ .../model/parser/DelimiterParserTest.java | 14 ++++--- .../model/parser/NumberParserTest.java | 11 ++++-- .../model/validation/ValidatorTest.java | 4 +- .../java/calculator/view/InputViewTest.java | 16 -------- 9 files changed, 79 insertions(+), 50 deletions(-) create mode 100644 src/test/java/calculator/controller/CalculatorControllerTest.java delete mode 100644 src/test/java/calculator/view/InputViewTest.java diff --git a/README.md b/README.md index abb3329fa5..1f5d75f201 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ |------|----------------------------|-------------| | null 입력 | `IllegalArgumentException` | `"입력값이 존재하지 않습니다."` | | 빈 문자열 | 정상 처리 | - | -| 커스텀 구분자 형식 오류 | IllegalArgumentException | "커스텀 구분자 형식이 올바르지 않습니다." | +| 커스텀 구분자 형식 오류 | `IllegalArgumentException` | `"커스텀 구분자 형식이 올바르지 않습니다."` | ### 2. 검증 단계 diff --git a/src/main/java/calculator/controller/CalculatorController.java b/src/main/java/calculator/controller/CalculatorController.java index e6d9fef0cf..2c8508faee 100644 --- a/src/main/java/calculator/controller/CalculatorController.java +++ b/src/main/java/calculator/controller/CalculatorController.java @@ -16,8 +16,7 @@ public class CalculatorController { public void run() { String input = readInput(); - String[] tokens = parseInput(input); - Result result = calculate(tokens); + Result result = processInput(input); printResult(result); } @@ -26,15 +25,13 @@ private String readInput() { return inputView.read(); } - /** 입력 검증 및 구분자/숫자 파싱 */ - private String[] parseInput(String input) { + /** 입력 처리 로직 (공통 사용: run() + testRun()) */ + private Result processInput(String input) { Validator.validateInput(input); + DelimiterParseResult parseResult = DelimiterParser.parse(input); - return NumberParser.parse(parseResult.getBody(), parseResult.getDelimiters()); - } + String[] tokens = NumberParser.parse(parseResult.getBody(), parseResult.getDelimiters()); - /** 계산 로직 */ - private Result calculate(String[] tokens) { return Calculator.calculate(tokens); } @@ -42,4 +39,10 @@ private Result calculate(String[] tokens) { private void printResult(Result result) { outputView.printResult(result); } + + /** 테스트 전용 메서드 (콘솔 I/O 배제) */ + public String testRun(String input) { + Result result = processInput(input); + return "결과 : " + result.getValue(); + } } \ No newline at end of file diff --git a/src/main/java/calculator/model/parser/NumberParser.java b/src/main/java/calculator/model/parser/NumberParser.java index 4ad35fd87e..e1e29c5484 100644 --- a/src/main/java/calculator/model/parser/NumberParser.java +++ b/src/main/java/calculator/model/parser/NumberParser.java @@ -1,7 +1,6 @@ package calculator.model.parser; import calculator.util.ExceptionMessages; -import java.util.Arrays; import java.util.List; public class NumberParser { @@ -21,7 +20,7 @@ public static String[] parse(String body, List delimiters) { // 검증: 비숫자, 음수 체크 for (String token : tokens) { - if (!token.matches("\\d+")) { + if (!token.matches("^-?\\d+$")) { // 음수 기호 허용 throw new IllegalArgumentException(ExceptionMessages.INVALID_NUMBER_FORMAT.get()); } if (Integer.parseInt(token) < 0) { diff --git a/src/test/java/calculator/controller/CalculatorControllerTest.java b/src/test/java/calculator/controller/CalculatorControllerTest.java new file mode 100644 index 0000000000..80ea17810a --- /dev/null +++ b/src/test/java/calculator/controller/CalculatorControllerTest.java @@ -0,0 +1,37 @@ +package calculator.controller; + +import calculator.util.ExceptionMessages; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class CalculatorControllerTest { + + @Test + void 정상_입력_흐름_테스트() { + CalculatorController controller = new CalculatorController(); + // given + String input = "1,2:3"; + // when + String result = controller.testRun(input); + // then + assertEquals("결과 : 6", result); + } + + @Test + void 커스텀_구분자_입력_흐름_테스트() { + CalculatorController controller = new CalculatorController(); + String input = "//;\n1;2;3"; + String result = controller.testRun(input); + assertEquals("결과 : 6", result); + } + + @Test + void 비정상_입력_예외_테스트() { + CalculatorController controller = new CalculatorController(); + String input = "1,a,3"; + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> controller.testRun(input)); + assertEquals(ExceptionMessages.INVALID_NUMBER_FORMAT.get(), e.getMessage()); + } +} \ No newline at end of file diff --git a/src/test/java/calculator/model/calculator/CalculatorTest.java b/src/test/java/calculator/model/calculator/CalculatorTest.java index 95612df158..9665588b6d 100644 --- a/src/test/java/calculator/model/calculator/CalculatorTest.java +++ b/src/test/java/calculator/model/calculator/CalculatorTest.java @@ -1,6 +1,8 @@ package calculator.model.calculator; +import calculator.util.ExceptionMessages; import org.junit.jupiter.api.Test; + import static org.junit.jupiter.api.Assertions.*; class CalculatorTest { @@ -15,26 +17,23 @@ class CalculatorTest { @Test void 음수_입력_예외() { String[] tokens = {"1", "-2", "3"}; - IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> - Calculator.calculate(tokens) - ); - assertEquals("음수는 입력할 수 없습니다.", e.getMessage()); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> Calculator.calculate(tokens)); + assertEquals(ExceptionMessages.NEGATIVE_NUMBER.get(), e.getMessage()); } @Test void 비숫자_입력_예외() { String[] tokens = {"1", "a", "3"}; - IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> - Calculator.calculate(tokens) - ); - assertEquals("숫자 형식이 올바르지 않습니다.", e.getMessage()); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> Calculator.calculate(tokens)); + assertEquals(ExceptionMessages.INVALID_NUMBER_FORMAT.get(), e.getMessage()); } @Test void 비어있는_토큰_배열_예외() { - IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> - Calculator.calculate(new String[]{}) - ); - assertEquals("계산할 숫자가 없습니다.", e.getMessage()); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> Calculator.calculate(new String[]{})); + assertEquals(ExceptionMessages.INPUT_EMPTY.get(), e.getMessage()); } } \ No newline at end of file diff --git a/src/test/java/calculator/model/parser/DelimiterParserTest.java b/src/test/java/calculator/model/parser/DelimiterParserTest.java index ad1448cd8d..aeb9dffb14 100644 --- a/src/test/java/calculator/model/parser/DelimiterParserTest.java +++ b/src/test/java/calculator/model/parser/DelimiterParserTest.java @@ -1,7 +1,9 @@ package calculator.model.parser; +import calculator.util.ExceptionMessages; import org.junit.jupiter.api.Test; import java.util.List; + import static org.junit.jupiter.api.Assertions.*; class DelimiterParserTest { @@ -29,15 +31,15 @@ class DelimiterParserTest { @Test void 공백_포함_예외() { - IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> - DelimiterParser.parse("// ;\n1;2;3")); - assertEquals("공백은 구분자로 사용할 수 없습니다.", e.getMessage()); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> DelimiterParser.parse("// ;\n1;2;3")); + assertEquals(ExceptionMessages.INVALID_CUSTOM_WHITESPACE.get(), e.getMessage()); } @Test void 예약어_포함_예외() { - IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> - DelimiterParser.parse("////\n1,2,3")); - assertEquals("예약어는 구분자로 사용할 수 없습니다.", e.getMessage()); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> DelimiterParser.parse("////\n1,2,3")); + assertEquals(ExceptionMessages.INVALID_CUSTOM_RESERVED.get(), e.getMessage()); } } \ No newline at end of file diff --git a/src/test/java/calculator/model/parser/NumberParserTest.java b/src/test/java/calculator/model/parser/NumberParserTest.java index ee72a225ad..3d6f50841e 100644 --- a/src/test/java/calculator/model/parser/NumberParserTest.java +++ b/src/test/java/calculator/model/parser/NumberParserTest.java @@ -1,5 +1,6 @@ package calculator.model.parser; +import calculator.util.ExceptionMessages; import org.junit.jupiter.api.Test; import java.util.List; import static org.junit.jupiter.api.Assertions.*; @@ -23,15 +24,17 @@ class NumberParserTest { @Test void 음수_입력_예외() { List delimiters = List.of(",", ":"); - assertThrows(IllegalArgumentException.class, () -> - NumberParser.parse("1,-2,3", delimiters)); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> NumberParser.parse("1,-2,3", delimiters)); + assertEquals(ExceptionMessages.NEGATIVE_NUMBER.get(), e.getMessage()); } @Test void 비숫자_입력_예외() { List delimiters = List.of(",", ":"); - assertThrows(IllegalArgumentException.class, () -> - NumberParser.parse("1,a,3", delimiters)); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> NumberParser.parse("1,a,3", delimiters)); + assertEquals(ExceptionMessages.INVALID_NUMBER_FORMAT.get(), e.getMessage()); } @Test diff --git a/src/test/java/calculator/model/validation/ValidatorTest.java b/src/test/java/calculator/model/validation/ValidatorTest.java index 126cd3fc4a..e28918b22f 100644 --- a/src/test/java/calculator/model/validation/ValidatorTest.java +++ b/src/test/java/calculator/model/validation/ValidatorTest.java @@ -1,5 +1,6 @@ package calculator.model.validation; +import calculator.util.ExceptionMessages; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -12,8 +13,9 @@ class ValidatorTest { @Test void null_입력_예외() { - assertThrows(IllegalArgumentException.class, + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> Validator.validateInput(null)); + assertEquals(ExceptionMessages.INPUT_NULL.get(), e.getMessage()); } @Test diff --git a/src/test/java/calculator/view/InputViewTest.java b/src/test/java/calculator/view/InputViewTest.java deleted file mode 100644 index 515844600b..0000000000 --- a/src/test/java/calculator/view/InputViewTest.java +++ /dev/null @@ -1,16 +0,0 @@ -package calculator.view; - -import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - -class InputViewTest { - - @Test - void 입력_프롬프트가_정상적으로_출력되는지_확인() { - InputView inputView = new InputView(); - assertDoesNotThrow(() -> { - System.out.println(">>> (테스트용 입력) 1,2:3"); - // 이후 Controller에서 IO 테스트 통합 시 수행할 예정 - }); - } -} \ No newline at end of file From 13bab35a290dd0d4ba7dbb414cd36d8b4ff07b0b Mon Sep 17 00:00:00 2001 From: Lee Ye Jin Date: Mon, 20 Oct 2025 05:57:55 +0900 Subject: [PATCH 15/15] =?UTF-8?q?docs(README)=20:=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=EC=A0=9D=ED=8A=B8=20=EC=9A=94=EC=95=BD=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1f5d75f201..6d187226fc 100644 --- a/README.md +++ b/README.md @@ -114,8 +114,7 @@ src ├─ model │ ├─ calculator # 문자열 파싱 및 합산 로직 │ │ ├─ Calculator.java # 계산 로직(덧셈) - │ │ ├─ Result.java # 계산 결과를 값 객체로 캡슐화 - │ │ └─ Token.java # 파싱된 숫자 토큰을 캡슐화 + │ │ └─ Result.java # 계산 결과를 값 객체로 캡슐화 │ ├─ parser │ │ ├─ DelimiterParser.java # 기본/커스텀 구분자 추출 │ │ ├─ NumberParser.java # 문자열 파싱, 숫자 배열로 변환 @@ -127,3 +126,15 @@ src ├─ Constants.java # 기본 구분자, 정규식 패턴 등의 상수 └─ ExceptionMessages.java # 예외 메시지 ``` + +--- +## 4. 품질 요구사항 + +- **코드 컨벤션 준수** + - Java Style Guide + - Git Commit Convention +- **단일 책임 원칙 준수** +- **예외 명세화** + - ExceptionMessages를 도입해 예외 메시지를 상수로 관리 + - 하드코딩된 메시지를 제거하여 유지보수성과 일관성 강화 +