diff --git a/README.md b/README.md index bd90ef024..28b3d5439 100644 --- a/README.md +++ b/README.md @@ -1 +1,57 @@ -# java-calculator-precourse \ No newline at end of file +# java-calculator-precourse +## 기능 구현 목록 +입력한 문자열에서 숫자를 추출하여 더하는 계산기를 구현한다. + +### 입력 +- [X] 유저의 입력을 전달한다. + +### 입력값 검사기 +- 커스텀 구분자가 있는 입력일 때 + - [X] 커스텀 구분자가 `//`로 시작해 `\n`가 포함되는지 확인한다. + - [X] 커스텀 구분자가 길이가 1이 아니라면 예외 처리한다. + - [X] 커스텀 구분자가 숫자라면 예외 처리한다. + - [X] 커스텀 구분자가 실수를 표현하는 `.`라면 예외 처리한다. + - [X] 커스텀 구분자가 기본 구분자라면 예외 처리 한다. + + +- 커스텀 구분자가 없을 입력일 때 + - [X] 입력값이 숫자로 시작하는 지 확인한다. + + +- 공통 + - [X] 커스텀 구분자를 입력하려는지 확인한다. + - [X] 음수가 입력되었다면 예외 처리 한다. + - [X] 구분자, `.`, 숫자 외 문자가 검출 된다면 예외 처리한다. + +### 구분자 관리 +- [X] 기본 구분자 외 커스텀 구분자를 전달받아 추가한다. +- [X] 전달받은 구분자가 기본(이미 존재하는지) 구분자인지 확인한다. +- [X] 구분자를 넘겨준다. + +### 문자열 핸들러 +- [X] 커스텀 구분자를 선언한다면 분리한다. +- [X] 분리한 커스텀 구분자를 넘겨준다. +- [X] 전달받은 구분자로 입력받은 문자열을 분리한다. + +### 덧셈기 +- [X] 전달 받은 값을 더해 저장한다. +- [X] 저장한 결과 값을 전달한다. +- [X] 입력값이 출력 범위를 초과한다면 예외 처리한다. +- [X] 더한 값이 출력 범위를 초과한다면 예외 처리한다. + +### 출력 +- [X] 시작 메세지를 출력한다. +- [X] 결과값을 출력한다. + +--- +## 리팩토링 + +- [X] 하드 코딩 제거 +- [X] 코딩 컨벤션 준수 +- [X] 이상한 수식 넣었을 때 에러 + ``` + 덧셈할 문자열을 입력해 주세요. + //;\n112kio12u328948 + Exception in thread "main" java.lang.NumberFormatException: For input string: "112kio12u328948" + ``` +- [ ] 기능 쪼개기 \ No newline at end of file diff --git a/src/main/java/calculator/Application.java b/src/main/java/calculator/Application.java index 573580fb4..12f94718c 100644 --- a/src/main/java/calculator/Application.java +++ b/src/main/java/calculator/Application.java @@ -2,6 +2,7 @@ public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + Controller controller = new Controller(); + controller.start(); } } diff --git a/src/main/java/calculator/Controller.java b/src/main/java/calculator/Controller.java new file mode 100644 index 000000000..1015c1bfa --- /dev/null +++ b/src/main/java/calculator/Controller.java @@ -0,0 +1,46 @@ +package calculator; + +import calculator.util.Adder; +import calculator.util.SeparatorManager; +import calculator.util.StringHandler; +import calculator.util.Validator; +import java.util.List; + +public class Controller { + Input input = new Input(); + Output output = new Output(); + SeparatorManager separatorManager = new SeparatorManager(); + StringHandler stringHandler = new StringHandler(); + Validator validator = new Validator(); + Adder adder = new Adder(); + + public void start() { + output.printStart(); + String userInput = input.readInput(); + validator.validateInput(userInput); + + if (validator.hasCustomSeparator(userInput)) { + String separator = stringHandler.extractSeparator(userInput); + validator.validateSeparator(separator); + separatorManager.add(separator); + String rawNumbers = stringHandler.removeCustom(userInput); + List numbers = stringHandler.getNumbers( + separatorManager.getSeparators(), + rawNumbers); + adder.add(numbers); + output.printResult(adder.getAnswer()); + return; + } + + if (validator.isStartWithDigit(userInput)) { + List numbers = stringHandler.getNumbers( + separatorManager.getSeparators(), + userInput + ); + adder.add(numbers); + output.printResult(adder.getAnswer()); + return; + } + output.printResult(adder.getAnswer()); + } +} diff --git a/src/main/java/calculator/Input.java b/src/main/java/calculator/Input.java new file mode 100644 index 000000000..2476bdead --- /dev/null +++ b/src/main/java/calculator/Input.java @@ -0,0 +1,9 @@ +package calculator; + +import camp.nextstep.edu.missionutils.Console; + +public class Input { + public String readInput() { + return Console.readLine(); + } +} diff --git a/src/main/java/calculator/Output.java b/src/main/java/calculator/Output.java new file mode 100644 index 000000000..ad987a132 --- /dev/null +++ b/src/main/java/calculator/Output.java @@ -0,0 +1,11 @@ +package calculator; + +public class Output { + public void printStart() { + System.out.println("덧셈할 문자열을 입력해 주세요."); + } + + public void printResult(long result) { + System.out.println("결과 : " + result); + } +} diff --git a/src/main/java/calculator/util/Adder.java b/src/main/java/calculator/util/Adder.java new file mode 100644 index 000000000..5ec67194d --- /dev/null +++ b/src/main/java/calculator/util/Adder.java @@ -0,0 +1,39 @@ +package calculator.util; + +import java.util.List; + +public class Adder { + long answer; + + public void add(List numbers) { + answer = 0; + for (String number: numbers) { + if (number.isBlank()) { + continue; + } + validateNumber(number); + long element = Long.parseLong(number); + validateOverflow(element); + answer += element; + } + validateOverflow(answer); + } + + public long getAnswer() { + return answer; + } + + private void validateOverflow(long number) { + if (number < 0) { + throw new IllegalArgumentException("허용 범위를 초과하였습니다."); + } + } + + private void validateNumber(String number) { + for (int i = 0; i < number.length(); i++) { + if (!Character.isDigit(number.charAt(i))) { + throw new IllegalArgumentException("등록된 구분자가 아닙니다."); + } + } + } +} diff --git a/src/main/java/calculator/util/Constants.java b/src/main/java/calculator/util/Constants.java new file mode 100644 index 000000000..b47145240 --- /dev/null +++ b/src/main/java/calculator/util/Constants.java @@ -0,0 +1,11 @@ +package calculator.util; + +import java.util.List; + +public class Constants { + public static final String CUSTOM_PREFIX = "//"; + public static final String CUSTOM_SUFFIX = "\\n"; + public static final int SEPARATOR_LENGTH = 1; + public static final String DECIMAL_POINT = "."; + public static final List DEFAULT_SEPARATORS = List.of(":", ","); +} diff --git a/src/main/java/calculator/util/SeparatorManager.java b/src/main/java/calculator/util/SeparatorManager.java new file mode 100644 index 000000000..cfba21158 --- /dev/null +++ b/src/main/java/calculator/util/SeparatorManager.java @@ -0,0 +1,29 @@ +package calculator.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class SeparatorManager { + List separators; + + public SeparatorManager() { + this.separators = new ArrayList<>(Constants.DEFAULT_SEPARATORS); + } + + private boolean exists(String separator) { + return separators.contains(separator); + } + + public void add(String separator) { + if (this.exists(separator)) { + throw new IllegalArgumentException("이미 존재하는 구분자입니다."); + } + separators.add(separator); + } + + public List getSeparators() { + return Collections.unmodifiableList(separators); + } +} diff --git a/src/main/java/calculator/util/StringHandler.java b/src/main/java/calculator/util/StringHandler.java new file mode 100644 index 000000000..a752cc753 --- /dev/null +++ b/src/main/java/calculator/util/StringHandler.java @@ -0,0 +1,40 @@ +package calculator.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class StringHandler { + public String extractSeparator(String input) { + int start = Constants.CUSTOM_PREFIX.length() ; + int end = input.indexOf(Constants.CUSTOM_SUFFIX); + return input.substring(start, end); + } + + public List getNumbers(List separators, String input) { + List splitInput = new ArrayList<>(); + splitInput.add(input); + + for (String separator : separators) { + splitInput = splitBySeparator(splitInput, separator); + } + + return splitInput; + } + + private List splitBySeparator(List inputList, String separator) { + List result = new ArrayList<>(); + + for (String part : inputList) { + String[] splitParts = part.split(separator); + result.addAll(Arrays.asList(splitParts)); + } + + return result; + } + + public String removeCustom(String input) { + int numberIndex = input.indexOf(Constants.CUSTOM_SUFFIX) + 2; + return input.substring(numberIndex); + } +} diff --git a/src/main/java/calculator/util/Validator.java b/src/main/java/calculator/util/Validator.java new file mode 100644 index 000000000..e25736cfc --- /dev/null +++ b/src/main/java/calculator/util/Validator.java @@ -0,0 +1,48 @@ +package calculator.util; + +public class Validator { + public boolean hasCustomSeparator(String input) { + if (isEmpty(input)) { + return false; + } + return input.startsWith(Constants.CUSTOM_PREFIX) && input.contains(Constants.CUSTOM_SUFFIX); + } + + public boolean isStartWithDigit(String input) { + if (isEmpty(input)) { + return false; + } + return Character.isDigit(input.charAt(0)); + } + + public boolean isEmpty(String input) { + return input == null || input.isBlank(); + } + + public void validateSeparator(String separator) { + if (separator.length() != Constants.SEPARATOR_LENGTH) { + throw new IllegalArgumentException("구분자의 길이는 1이어야 합니다."); + } + if (separator.equals(Constants.DECIMAL_POINT)) { + throw new IllegalArgumentException(".은 구분자가 될 수 없습니다."); + } + if (isStartWithDigit(separator)) { + throw new IllegalArgumentException("숫자는 구분자가 될 수 없습니다."); + } + } + + public void validateInput(String input) { + if (isEmpty(input)) { + return; + } + if (hasCustomSeparator(input)) { + return; + } + if (isStartWithDigit(input)) { + if ((!input.contains(Constants.CUSTOM_PREFIX) && !input.contains(Constants.CUSTOM_SUFFIX))) { + return; + } + } + throw new IllegalArgumentException("포멧에 맞게 입력해 주세요.(ex://@\\n1@2:3,4)"); + } +} \ No newline at end of file diff --git a/src/test/java/calculator/AdderTest.java b/src/test/java/calculator/AdderTest.java new file mode 100644 index 000000000..b62ab4c9e --- /dev/null +++ b/src/test/java/calculator/AdderTest.java @@ -0,0 +1,24 @@ +package calculator; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import calculator.util.Adder; +import java.util.List; +import org.junit.jupiter.api.Test; + +class AdderTest { + Adder adder = new Adder(); + + @Test + void 공백일때_0을_return_하는지() { + adder.add(List.of("")); + assertEquals(0, adder.getAnswer()); + } + + @Test + void 오버플로우_발생시_예외_처리() { + assertThatThrownBy(() ->adder.add(List.of(String.valueOf(Double.MAX_VALUE),"1"))) + .isInstanceOf(IllegalArgumentException.class); + } +} \ No newline at end of file diff --git a/src/test/java/calculator/ApplicationTest.java b/src/test/java/calculator/ApplicationTest.java index 93771fb01..0d174ce87 100644 --- a/src/test/java/calculator/ApplicationTest.java +++ b/src/test/java/calculator/ApplicationTest.java @@ -16,6 +16,14 @@ class ApplicationTest extends NsTest { }); } + @Test + void 공백_입력() { + assertSimpleTest(() -> { + run(" "); + assertThat(output()).contains("결과 : 0"); + }); + } + @Test void 예외_테스트() { assertSimpleTest(() -> diff --git a/src/test/java/calculator/SeparatorManagerTest.java b/src/test/java/calculator/SeparatorManagerTest.java new file mode 100644 index 000000000..5335bd1a0 --- /dev/null +++ b/src/test/java/calculator/SeparatorManagerTest.java @@ -0,0 +1,29 @@ +package calculator; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; + +import calculator.util.SeparatorManager; +import org.junit.jupiter.api.Test; + +class SeparatorManagerTest { + SeparatorManager separatorManager = new SeparatorManager(); + + @Test + void 이미_있는_구분자라면_예외_처리() { + assertThatThrownBy(() -> separatorManager.add(":")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 없는_구분자라면_정상_작동() { + assertThatCode(() -> separatorManager.add(";")) + .doesNotThrowAnyException(); + } + + @Test + void 받은_구분자_리스트의_수정_불가한지_확인() { + assertThatThrownBy(() -> separatorManager.getSeparators().remove(0)) + .isInstanceOf(UnsupportedOperationException.class); + } +} \ No newline at end of file diff --git a/src/test/java/calculator/StringHandlerTest.java b/src/test/java/calculator/StringHandlerTest.java new file mode 100644 index 000000000..6b903435f --- /dev/null +++ b/src/test/java/calculator/StringHandlerTest.java @@ -0,0 +1,38 @@ +package calculator; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import calculator.util.SeparatorManager; +import calculator.util.StringHandler; +import java.util.List; +import org.junit.jupiter.api.Test; + +class StringHandlerTest { + StringHandler handler = new StringHandler(); + SeparatorManager separatorManager = new SeparatorManager(); + + @Test + void 커스텀구분자만_리턴하는지_확인() { + assertEquals("@", + handler.extractSeparator("//@\\n1@2@3")); + } + + + @Test + void 숫자만_반환하는지_확인() { + assertEquals(List.of("1", "2", "3"), + handler.getNumbers(separatorManager.getSeparators(), "1:2,3")); + } + + @Test + void 두자리_이상의_숫자가_잘_반환_되는지_확인() { + assertEquals(List.of("12", "36", "222"), + handler.getNumbers(separatorManager.getSeparators(), "12,36:222")); + } + + @Test + void 커스텀_구분자_선언부분만_잘라내는지_확인() { + assertEquals("1,2;3", + handler.removeCustom("//;\\n1,2;3")); + } +} \ No newline at end of file diff --git a/src/test/java/calculator/ValidatorTest.java b/src/test/java/calculator/ValidatorTest.java new file mode 100644 index 000000000..20c2718db --- /dev/null +++ b/src/test/java/calculator/ValidatorTest.java @@ -0,0 +1,50 @@ +package calculator; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import calculator.util.Validator; +import org.junit.jupiter.api.Test; + +class ValidatorTest { + Validator validator = new Validator(); + + @Test + void 커스텀_구분자의_포맷이_올바르면_True_반환() { + assertTrue(validator.hasCustomSeparator("//;\\n1;2;3")); + } + + @Test + void 커스텀_구분자가_없다면_False_반환() { + assertFalse(validator.hasCustomSeparator("1:2:3")); + } + + @Test + void 숫자로_시작한다면_True_반환() { + assertTrue(validator.isStartWithDigit("1;2;3")); + } + + @Test + void 숫자로_시작하지_않으면_False_반환() { + assertFalse(validator.isStartWithDigit("//;\\n")); + } + + @Test + void 올바른_포맷일때_아무_처리도_하지_않음() { + String[] testCases = {"//@\\n1@2@3", " ", "", "//@\\n", "1:2:3"}; + for (String testCase : testCases) { + assertDoesNotThrow(() -> validator.validateInput(testCase)); + } + } + + @Test + void 잘못된_포맷은_예외_발생() { + String[] testCases = {"1@2@3//@\n", "\n@//1,2,3", "abc"}; + for (String testCase : testCases) { + assertThatThrownBy(() -> validator.validateInput(testCase)) + .isInstanceOf(IllegalArgumentException.class); + } + } +} \ No newline at end of file