diff --git a/README.md b/README.md index bd90ef0247..a7dd8e2caa 100644 --- a/README.md +++ b/README.md @@ -1 +1,77 @@ -# java-calculator-precourse \ No newline at end of file +# 문자열 덧셈 계산기 + +## 🚀 기능 요구 사항 + +입력한 문자열에서 숫자를 추출하여 더하는 계산기를 구현한다. + +### 기본 기능 +- 입력이 비어 있는 경우 `0`을 반환한다. +- 쉼표(`,`) 또는 콜론(`:`)을 구분자로 가지는 문자열을 전달하는 경우 구분자를 기준으로 분리하여 합산한다. + - 예: `"" → 0`, `"1,2" → 3`, `"1,2:3" → 6` +- 커스텀 구분자를 지정할 수 있다. + - `"//;\n1;2;3"` → 커스텀 구분자 `;`, 결과 `6` +- 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`을 발생시킨다. +- 프로그램은 `System.exit()`를 호출하지 않는다. + +--- + +## 🧩 예외 처리 조건 + +| 구분 | 설명 | 예시 | +|------|------|------| +| 빈 문자열 | 입력이 비어 있으면 `0` 반환 | `""` | +| 음수 입력 | `IllegalArgumentException` 발생 | `"-1,2,3"` | +| 0 입력 | `IllegalArgumentException` 발생 | `"0,1,2"` | +| 숫자 아님 | `IllegalArgumentException` 발생 | `"1,a,3"` | +| 연속된 구분자 | `IllegalArgumentException` 발생 | `"1,,2"` | +| 잘못된 커스텀 구분자 형식 | `IllegalArgumentException` 발생 | `"//;\n"` or `"//\n1,2"` | + +--- + +## 🧪 TDD 진행 순서 + +### 1단계 (Red) +- `StringCalculatorTest` 작성 +- 빈 문자열 입력 시 `0`을 반환하는 테스트 추가 + +### 2단계 (Green) +- 테스트 통과를 위한 최소한의 코드 작성 + +### 3단계 (Refactor) +- 코드 중복 제거 및 메서드 분리 + +--- + +### 전체 TDD 사이클 계획 + +| 단계 | 목표 | 예시 입력 | 기대 결과 | +|------|------|------------|-------------| +| 1 | 빈 문자열 처리 | `""` | `0` | +| 2 | 기본 구분자 처리 | `"1,2:3"` | `6` | +| 3 | 커스텀 구분자 처리 | `"//;\n1;2;3"` | `6` | +| 4 | 잘못된 입력 예외 처리 | `"-1,2,3"` | 예외 발생 | +| 5 | 콘솔 입출력 및 Application 통합 | 사용자 입력 `"1,2:3"` | `"결과 : 6"` 출력 | + +--- + +## 💻 실행 예시 +덧셈할 문자열을 입력해 주세요.
+1,2:3
+결과 : 6 + + +--- + +## 🔧 개발 환경 + +- **JDK:** 21 +- **빌드 도구:** Gradle (build.gradle 수정 불가) +- **입출력:** `camp.nextstep.edu.missionutils.Console` +- **테스트:** JUnit + AssertJ (`NsTest` 기반 테스트 제공) +- **코드 스타일:** Google Java Style Guide + - 블록 들여쓰기: 4 스페이스 + - 최대 줄 길이: 120자 + - 줄바꿈 시 들여쓰기: +8 스페이스 이상 + + + diff --git a/src/main/java/calculator/Application.java b/src/main/java/calculator/Application.java index 573580fb40..6a95f1a265 100644 --- a/src/main/java/calculator/Application.java +++ b/src/main/java/calculator/Application.java @@ -1,7 +1,19 @@ package calculator; +import calculator.domain.StringCalculator; +import camp.nextstep.edu.missionutils.Console; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + try { + System.out.println("덧셈할 문자열을 입력해 주세요."); + + String input = Console.readLine(); + int result = StringCalculator.add(input); + + System.out.println("결과 : " + result); + } finally { + Console.close(); + } } } diff --git a/src/main/java/calculator/domain/StringCalculator.java b/src/main/java/calculator/domain/StringCalculator.java new file mode 100644 index 0000000000..ee6859eb05 --- /dev/null +++ b/src/main/java/calculator/domain/StringCalculator.java @@ -0,0 +1,86 @@ +package calculator.domain; + +import java.util.regex.Pattern; + +public final class StringCalculator { + + private StringCalculator() { + } + + public static int add(String input) { + if (input == null || input.isEmpty()) { + return 0; + } + + if (input.startsWith("//")) { + int realNl = input.indexOf('\n'); + int litNl = input.indexOf("\\n"); + + if (realNl < 0 && litNl < 0) { + throw new IllegalArgumentException("커스텀 구분자 형식이 올바르지 않습니다."); + } + + if (realNl >= 0) { + String delimiter = input.substring(2, realNl); + validateDelimiter(delimiter); + + String body = input.substring(realNl + 1); + ensureHasNumbers(body); + + return sumTokens(body.split(Pattern.quote(delimiter))); + } + + String delimiter = input.substring(2, litNl); + validateDelimiter(delimiter); + + String body = input.substring(litNl + 2); + ensureHasNumbers(body); + + return sumTokens(body.split(Pattern.quote(delimiter))); + } + + return sumTokens(input.split("[,:]")); + } + + private static void validateDelimiter(String delimiter) { + if (delimiter.isEmpty()) { + throw new IllegalArgumentException("커스텀 구분자 형식이 올바르지 않습니다."); + } + } + + private static int sumTokens(String[] tokens) { + int sum = 0; + + for (String token : tokens) { + sum += parsePositive(token); + } + + return sum; + } + + private static int parsePositive(String token) { + if (token == null || token.isBlank()) { + throw new IllegalArgumentException("빈 값은 허용되지 않습니다."); + } + + final int number; + + try { + number = Integer.parseInt(token); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("숫자만 입력 가능합니다.: " + token); + } + + if (number <= 0) { + throw new IllegalArgumentException("양수만 허용됩니다.: " + token); + } + + return number; + } + + private static void ensureHasNumbers(String body) { + if (body == null || body.isEmpty()) { + throw new IllegalArgumentException("덧셈할 숫자가 없습니다."); + } + } +} diff --git a/src/test/java/calculator/domain/StringCalculatorTest.java b/src/test/java/calculator/domain/StringCalculatorTest.java new file mode 100644 index 0000000000..a1dead037e --- /dev/null +++ b/src/test/java/calculator/domain/StringCalculatorTest.java @@ -0,0 +1,52 @@ +package calculator.domain; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; +class StringCalculatorTest { + + @Test + void 빈문자열은_0을_반환한다() { + assertEquals(0, StringCalculator.add("")); + assertEquals(0, StringCalculator.add(null)); + } + + @Test + void 기본구분자_콤마_콜론으로_합산한다() { + assertEquals(3, StringCalculator.add("1,2")); + assertEquals(6, StringCalculator.add("1,2,3")); + assertEquals(6, StringCalculator.add("1,2:3")); + } + + @Test + void 커스텀_구분자_실제개행_지원한다() { + assertEquals(6, StringCalculator.add("//;\n1;2;3")); + } + + @Test + void 커스텀_구분자_리터럴_역슬래시n_지원한다() { + assertEquals(1, StringCalculator.add("//;\\n1")); + } + + @Test + void 커스텀_구분자_비어있으면_예외() { + assertThrows(IllegalArgumentException.class, () -> StringCalculator.add("//\n1,2,3")); + } + + @Test + void 숫자아님은_예외() { + assertThrows(IllegalArgumentException.class, () -> StringCalculator.add("1,a")); + } + + @Test + void 빈토큰_혹은_공백토큰은_예외() { + assertThrows(IllegalArgumentException.class, () -> StringCalculator.add("1,,2")); + assertThrows(IllegalArgumentException.class, () -> StringCalculator.add("1, ,2")); + } + + @Test + void 영_혹은_음수는_예외() { + assertThrows(IllegalArgumentException.class, () -> StringCalculator.add("0,1")); + assertThrows(IllegalArgumentException.class, () -> StringCalculator.add("-1,2,3")); + } +} \ No newline at end of file