Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,34 @@
# java-calculator-precourse
# java-calculator-precourse

## 기능 요구 사항

---

입력한 문자열에서 숫자를 추출하여 더하는 계산기를 구현한다.

- 쉼표(,) 또는 콜론(:)을 구분자로 가지는 문자열을 전달하는 경우 구분자를 기준으로 분리한 각 숫자의 합을 반환한다.
- 예: "" => 0, "1,2" => 3, "1,2,3" => 6, "1,2:3" => 6
- 앞의 기본 구분자(쉼표, 콜론) 외에 커스텀 구분자를 지정할 수 있다. 커스텀 구분자는 문자열 앞부분의 "//"와 "\n" 사이에 위치하는 문자를 커스텀 구분자로 사용한다.
- 예를 들어 "//;\n1;2;3"과 같이 값을 입력할 경우 커스텀 구분자는 세미콜론(;)이며, 결과 값은 6이 반환되어야 한다.
- 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`을 발생시킨 후 애플리케이션은 종료되어야 한다.

## 프로그래밍 요구사항

---

### Java

- JDK 21 버전에서 실행 가능해야 한다.
- 프로그램 실행의 시작점은 `Application`의 `main()` 이다.
- `build.gradle` 파일은 변경할 수 없으며, **제공된 라이브러리 이외의 외부 라이브러리는 사용하지 않는다.**
- 프로그램 종료 시 `System.exit()`를 호출하지 않는다.
- 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 등의 이름을 바꾸거나 이동하지 않는다.
- 자바 코드 컨벤션을 지키면서 프로그래밍한다.
- 기본적으로 Java Style Guide(https://github.com/woowacourse/woowacourse-docs/tree/main/styleguide/java)를 원칙으로 한다.

### 라이브러리

### Java

- `camp.nextstep.edu.missionutils`에서 제공하는 `Console` API를 사용하여 구현해야 한다.
- 사용자가 입력하는 값은 `camp.nextstep.edu.missionutils.Console`의 `readLine()`을 활용한다.
7 changes: 5 additions & 2 deletions src/main/java/calculator/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package calculator;

import calculator.controller.CalculatorController;

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
CalculatorController controller = new CalculatorController();
controller.startCalculator();
}
}
}
18 changes: 18 additions & 0 deletions src/main/java/calculator/controller/CalculatorController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package calculator.controller;

import calculator.model.Calculator;
import calculator.view.InputView;
import calculator.view.OutputView;

public class CalculatorController {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

컨트롤러에서 뷰와 모델을 static call을 하는 이유가 있나요?
특별한 이유가 없다면 의존성을 생각해서 DI를 하는게 더 낫지 않을까요?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아하 그 방향대로 수정해보겠습니다!

public void startCalculator() {
try {
String input = InputView.getInput();
int result = Calculator.processSum(input);
OutputView.printResult(result);
} catch (IllegalArgumentException e) {
OutputView.printError(e.getMessage());
throw e;
}
}
}
48 changes: 48 additions & 0 deletions src/main/java/calculator/model/Calculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package calculator.model;

import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

덕분에 좋은 라이브러리들 알아갑니다

public class Calculator {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

필드가 없고 기능만 있다면 모델 보다는 service나 utils로 취급되는게 더 적절해보여요!
모델로 둔다면 모델인 만큼 상태를 저장하는 목적 등이 강조되면 좀 더 좋을 것 같아요

public static int processSum(String numbers) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 메서드가 갖고있는 책임이 너무 큰 것 같습니다. 구분자를 확인 및 설정하는 것, 예외를 처리하는 것, 계산하는 것 등 세부 기능으로 메서드를 만들어 분리하는 쪽이 더 좋지 않을까요?

if (numbers == null || numbers.trim().isEmpty()) {
return 0;
}

List<String> delimiters = Arrays.asList(",", ":");

Matcher matcher = Pattern.compile("^//(.)\\\\n(.*)").matcher(numbers);

if (matcher.find()) {
String customDelimiter = Pattern.quote(matcher.group(1));
delimiters = Arrays.asList(customDelimiter, ",", ":");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

delimiter 필드를 재정의할 필요가 있을까요? List로 둔다면 ArrayList 내지는 LinkedList 등으로 두고 원소를 추가하는 편이 더 직관적으로 와닿을 것 같아요!

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 원소를 추가하는 편이 좀 더 낫고 안전할 것 같아요!

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

동의합니다.
한 번에 사용 가능한걸 두 번에 나눌 필요는 없다고 생각합니당

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오호 그 방법으로 하는게 더 가독성 좋을 것 같네용

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 추가하는 편이 더 좋은 것 같습니다!

numbers = matcher.group(2);
}

if (numbers.trim().isEmpty()) {
throw new IllegalArgumentException("커스텀 구분자 이후 숫자가 없습니다.");
}

String delimiterRegex = String.join("|", delimiters);

return Arrays.stream(numbers.split(delimiterRegex))
.map(String::trim)
.filter(num -> !num.isEmpty())
.mapToInt(Calculator::parseValidation)
.sum();
}

private static int parseValidation(String num) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

밑에서 변환 예외와 음수 예외를 다루는데 제가 받아들이기에는 역할이 2개여서 가독성이 떨어지는 것 같아요. 개인적인 생각으로는 검증 역시 메서드를 나눠도 좋을 것 같아요.
그리고 검증을 모델 내부에 두신 이유도 궁금합니다!ㅎㅎ

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

검증이랑 예외처리를 따로 분리하고싶었으나...시간 부족이슈로.. 구현과 테스트 정상작동에 집중했습니다. 수정해보겠습니다!

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

어떠한 기능만 수행해서 return해주는 메서드의 파라미터는 첫 글자만 남겨서 네이밍해도 괜찮을 것 같아요.
ex) number -> n, string -> s

Copy link
Copy Markdown
Author

@seulnan seulnan Feb 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제안해주신 방식도 간결한 코드에서 효과적일 수 있다고도 생각합니다. 다만, 네이밍이 짧아지면 가독성이 떨어지고 유지보수할 때 혼란이 될 수도 있다고 생각해서요 그래도 현록님이 이 방식이 더 괜찮다고 생각하시는 이유가 있을까용? 생각하시는 더 좋은 규칙이 있다면 같이 얘기해보고싶어요! @Regyung

try {
int value = Integer.parseInt(num);
if (value < 0) {
throw new IllegalArgumentException("음수는 입력할 수 없습니다: " + num);
}
return value;
} catch (NumberFormatException e) {
throw new IllegalArgumentException("잘못된 숫자 입력: " + num);
}
}
}
12 changes: 12 additions & 0 deletions src/main/java/calculator/view/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package calculator.view;

import camp.nextstep.edu.missionutils.Console;

public class InputView {
public static String getInput() {
System.out.print("문자열을 입력하세요 :");
String userInputValue = Console.readLine();

return userInputValue;
}
}
10 changes: 10 additions & 0 deletions src/main/java/calculator/view/OutputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package calculator.view;

public class OutputView {
public static void printResult(int result) {
System.out.println("결과 : " + result);
}
public static void printError(String message) {
System.out.println("오류 발생 : " + message);
Comment thread
seulnan marked this conversation as resolved.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 같은 생각입니다!

}
}
25 changes: 25 additions & 0 deletions src/test/java/calculator/BasicCalculatorTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package calculator;

import calculator.model.Calculator;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

테스트 케이스를 공부하게 되었습니다 이렇게 쓰는거였군요


class BasicCalculatorTest { // ✅ 더 명확한 이름으로 변경

@Test
void 빈문자열_테스트() {
assertThat(Calculator.processSum("")).isEqualTo(0);
}

@Test
void 기본_구분자_테스트() {
assertThat(Calculator.processSum("1,2:3")).isEqualTo(6);
}

@Test
void 하나의_숫자만_입력() {
assertThat(Calculator.processSum("5")).isEqualTo(5);
}

}
Comment thread
seulnan marked this conversation as resolved.
23 changes: 23 additions & 0 deletions src/test/java/calculator/CustomCalculatorTest.java
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

문자열 중간에 커스텀 구분자를 추가할 순 없을까요?
예) //]\n1]//[\n1,2
문제서 어느정도까지의 융통성을 요구하는지 모르겠는데 어떻게 판단하고 케이스를 짜셨나요

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

커스텀 구분자는 문자열 앞부분의 //와 \n 사이에 위치하는 문자를 사용한다 는 기능 요구사항을 기준으로 잡았습니다. 입력값을 커스텀 구분자로 나눈 후에도 숫자가 아닌 값이 포함될 경우, 이는 잘못된 입력이라고 판단했습니다. 따라서, 경수님이 말씀해주신 중간에 임의의 구분자를 추가하는 케이스는 예외로 처리하는 것이 더 적절하다고 보았습니다

Copy link
Copy Markdown
Author

@seulnan seulnan Feb 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 경수님께서 중간에 커스텀 구분자를 허용해야 한다고 생각하신다면, 어떤 요구사항을 기준으로 고려하셨는지 의견을 나눠보면 좋을 것 같아요! @Siul49

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package calculator;

import calculator.model.Calculator;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class CustomCalculatorTest {
@Test
void 기본_구분자_테스트() {
assertThat(Calculator.processSum("1,2:3")).isEqualTo(6);
}

@Test
void 커스텀_구분자_테스트() {
assertThat(Calculator.processSum("//;\\n1;2;3")).isEqualTo(6);
}

@Test
void 기본_구분자_혼합사용_테스트() {
assertThat(Calculator.processSum("//;\\n1;2,3:4")).isEqualTo(10);
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 나중에 생각난 것이지만 커스텀 구분자가 2개 이상일 수도 있을 상황이 존재할 것도 같습니다!

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오오 그 예외는 생각하지 못했는데 반영해보겠습니당