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
108 changes: 107 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,107 @@
# java-calculator-precourse
# java-calculator-precourse

## 과제 진행 요구 사항
- 문자열 덧셈 계산기 저장소를 포크하고 클론 하는 것으로 시작
- 기능을 구현하기 전 README.md에 구현할 기능 목록을 정리해 추가
- Git의 커밋 단위는 앞 단계에서 README.md에 정리한 기능 목록 단위로 추가

## 기능 요구 사항
> 입력한 문자열에서 숫자를 추출하여 더하는 계산기 구현
- 쉼표(,) 또는 콜론(:)을 구분자로 가지는 문자열을 전달하는 경우 구분자를 기준으로 분리한 각 숫자의 합을 반환한다.

`ex) "" => 0, "1,2" => 3, "1,2,3" => 6, "1,2:3" => 6`
- 앞의 기본 구분자(쉼표, 콜론) 외에 커스텀 구분자를 지정할 수 있다.
- 커스텀 구분자는 문자열 앞부분의 "//"와 "\n" 사이에 위치하는 문자를 커스텀 구분자로 사용한다.

`ex) "//;\n1;2;3"과 같이 값을 입력할 경우 커스텀 구분자는 세미콜론이며, 결과 값은 6이 반환되어야 한다.`

- 사용자가 잘못된 값을 입력할 경우 `IllegalArguemntException`을 발생시킨 후 애플리케이션은 종료되어야 한다.

## 입출력 요구 사항
### 입력
- 구분자와 양수로 구성된 문자열
### 출력
- 덧셈 결과

`결과 : 6`

### 실행 결과 예시

`덧셈할 문자열을 입력해 주세요.`

`1,2:3`

`결과 : 6`

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

### 라이브러리
- `camp.nextstep.edu.missionutils`에서 제공하는 Console API를 사용하여 구현해야 한다.
- 사용자가 입력하는 값은 `camp.nextstep.edu.missionutils.Console`의 `readLine()`을 활용한다.

## 기능 목록
- 사용자가 문자열을 입력한다.
- 잘못된 값인지 확인한다.
- 잘못된 값(음수, 숫자가 아닌 문자)이라면 IllegalArgumentException 발생 후 종료
- 커스텀 구분자가 있는지 확인한다.
- 있을 경우 커스텀 구분자를 기준으로 숫자 분리
- 없을 경우 기본 구분자로 분리
- 분리된 문자열을 숫자로 변환한다.
- 변환된 숫자를 합한다.
- 합을 반환한다.

## 베운 것
### regex 패키지 클래스
- Pattern 클래스
- 문자열을 정규표현식 패턴 객체로 변환해주는 역할
- 문자열을 정규식 문법에 알맞게 구성해주지 않으면 예외 발생
- 일반 클래스처럼 공개된 생성자를 제공하지 않아서 정규식 패턴 객체를 생성하려면 compile() 정적 메서드를 호출해야 함
- Matcher 클래스
- 대상 문자열의 패턴을 해석하고 주어진 패턴과 일치하는지 판별하고 반환된 필터링된 결과값들을 지니고 있음
- Patter 클래스와 마찬가지로 공개된 생성자가 없으며, Pattern 객체의 matcher() 메서드를 호출해서 얻음
### StringTokenizer vs split
- StringTokenizer
- java.util에 포함되어 있는 클래스
- 문자 또는 문자열로 문자열 구분
- 빈 문자열을 토큰으로 인식하지 않음
- 결과 값이 문자열
- 전체 토큰을 보고싶다면 반복문으로 하나씩 뽑아야 함
- split
- String 클래스에 속해있는 메서드
- 정규표현식으로 구분
- 빈 문자열을 토큰으로 인식
- 결과값이 문자열 배열
### Character.isDigit()
- 주어진 문자가 숫자인지 판별
- 문자가 숫자면 true
### 문자열을 숫자로 변환
- java.lang.Integer 클래스의 parseInt()와 valueOf() 메서드 사용 가능

**Integer.parseInt()**
- 파라미터로 숫자로 변환할 문자열을 입력받고, 입력받은 문자열을 integer로 변환한 int 값을 리턴
- private type인 int 리턴

**integer.valueOf()**
- 문자열을 변환하여 integer Object를 리턴

### replace vs replaceAll
- replace: 단순 문자열 `replace(".", "#");`
- replaceAll: 정규식 `replaceAll("\\.", "#");`
- 정규식이어서 이스케이프를 사용하지 않으면 전체로 인식해서 모든 문자가 #으로 대체

### IllgalArgumentException
- 메서드에 인수가 잘못된 경우 발생하는 예외
- 메서드에 전달된 인수가 예상된 형식이나 범위를 벗어나는 경우에 발생
- 주로 메서드의 파라미터 유효성을 검사하는데 사용

**발생 이유**
- 메서드에 전달된 인수의 형식이나 타입이 예상과 다를때
- 메서드에 전달된 인수가 null이지만 null을 허용하지 않는 경우
- 메서드에 전달된 인수가 허용되는 범위를 벗어날때
- 기타 인수의 유효성을 검사하는 조건에 위반될 때
10 changes: 10 additions & 0 deletions src/main/java/calculator/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,15 @@
public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
try {
String input = InputView.readInput();
String[] splitInput = StringSplitter.splitInput(input);
int[] numbers = NumberParser.changeNumber(splitInput);
int result = SumCalculator.sum(numbers);
OutputView.printResult(result);
} catch (IllegalArgumentException e) {
System.out.println("Error " + e.getMessage());
throw e;
}
Comment on lines +12 to +15
Copy link

Choose a reason for hiding this comment

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

은샘님이 해주신 제 코드 리뷰와도 관련이 있어서 살펴봤는데요,
마지막 제어 담당 클래스인 Application에서 throw e로 예외를 다시 잡아서 던져주신 것 같아요.
이렇게 하면 프로그램이 비정상 종료되고, 콘솔에 에러 메시지와 스택 트레이스가 두 번 출력될 가능성이 있을 것 같습니다.

따라서 제 생각에는 예외를 다시 잡아 던지는 rethrow 대신 return으로 main()이 정상 종료 시키면 스택트레이스가 안 남아서 출력이 깔끔해질거 같아요

Copy link
Author

Choose a reason for hiding this comment

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

아하 이해했습니다 감사합니다!

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

import camp.nextstep.edu.missionutils.Console;

public class InputView {

public static String readInput() {
System.out.println("덧셈할 문자열을 입력해 주세요.");
String line = Console.readLine();
return line;
}
}
14 changes: 14 additions & 0 deletions src/main/java/calculator/NumberParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package calculator;

public class NumberParser {
public static int[] changeNumber(String[] splitInput) {
int[] numbers = new int[splitInput.length];
for (int i = 0; i < splitInput.length; i++) {
if (!splitInput[i].matches("\\d+")) {
throw new IllegalArgumentException("형식을 벗어남");
}
Comment on lines +7 to +9
Copy link

Choose a reason for hiding this comment

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

이 부분에서 예외 메시지를 더 구체적으로 작성하는건 어떨까요?
예외를 세분화하면 로직의 의도도 명확하게 표현되고, 디버깅 시에도 원인 파악이 쉬워질 것 같다고 생각합니다

Copy link
Author

Choose a reason for hiding this comment

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

저도 그렇게 생각해요! 다음에는 더 세분화해서 작성해보도록 하겠습니다

numbers[i] = Integer.valueOf(splitInput[i]);
Copy link

Choose a reason for hiding this comment

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

엄청 중요한 부분은 아닌데요,
Integer.valueOf()를 사용하면 래퍼 클래스 Integer로 반환이 되는데요,
Integer.parseInt() 대신 Integer.valueOf()를 사용하신 의도가 있을까요?

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

public class OutputView {
public static void printResult(int result) {
System.out.println("결과 : " + result);
}
}
14 changes: 14 additions & 0 deletions src/main/java/calculator/StringSplitter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package calculator;

public class StringSplitter {
public static String[] splitInput(String input) {
Copy link

Choose a reason for hiding this comment

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

입력값이 null이거나 빈 문자열일 경우 에러가 날 것 같아요
if (input == null || input.isEmpty()) return new String[]{"0"};로 예외 방어 해주시면 좋을 것 같습니다

Copy link
Author

Choose a reason for hiding this comment

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

에러가 날 것 같은 부분을 더 세세하게 설정해야겠다고 생각했습니다 반영할게요!

if (!Character.isDigit(input.charAt(0))) {
String delimiter;
delimiter = String.valueOf(input.charAt(2));
input = input.replaceAll("^.*\\\\n", "");
String customRegex = "[" + delimiter + ",:]";
return input.split(customRegex);
}
return input.split("[,:]");
Comment on lines +5 to +12
Copy link

Choose a reason for hiding this comment

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

이 부분에서 의도하신 게 입력값의 첫 글자가 숫자가 아닌 경우 커스텀 구분자가 존재한다고 판단하고,
"//""\n" 사이의 문자를 커스텀 구분자로 인식한 뒤, 이후 부분을 해당 구분자 및 기본 구분자(",", ":")로 나누는 것 같아요

그런데 이 경우 2가지 문제점이 우려되는데요
(1) input = input.replaceAll("^.*\\\\n", "");에서는 "\\\\n"이 이스케이프 문자열 "\\n"만 인식하고, "\n"은 인식하지 못하게 돼요. 즉, 정규식으로는 "\\n", 문자열 리터럴에서는 "\n"이 처리되어야 하는데 이 부분이 누락이 될거 같아요.
(2) input.charAt(2)에서 2번째 문자를 고정 인덱스로 커스텀 구분자를 추출하고 있는데요, 커스텀 구분자가 2글자 이상인 경우를 커버할 수 없을거 같아요 (ex. "//;;\n1;;2") 만약 커스텀 구분자를 한 글자만 가능하도록 기획하셨다면 이 부분은 무시해주셔도 될 거 같습니다

따라서 Pattern, Matcher를 사용해서 "//"\n 사이의 구분자를 추출하는 정규식 방식을 한번 고려해보시면 좋을 것 같습니다

Copy link
Author

Choose a reason for hiding this comment

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

(1) 우테코에서 제공한 테스트 케이스가 "\\n"이어서 그것만 처리하면 된다고 생각했는데 다른 분들 확인하니 "\n"도 함께 생각해서 처리하셨더라구요 조언 해주신 대로 Pattern, Matcher 사용해서 둘 다 처리할 수 있도록 다시 개선해보겠습니다!

(2) 사실 구분자가 2글자 이상인 경우를 어떻게 해야 커버가 가능할까 고민했는데요 "//""\n"전까지를 묶어서 구분자로 설정하고 싶었는데 정규식이 너무 어렵더라구요.. 조금 더 고려해보겠습니다. 또한 이렇게 코드를 짰을 땐 커스텀 구분자가 1글자인 경우에만 실행 가능이라는 기획 의도를 분명하게 나타내고, 2글자 이상일 시 오류등 예외 처리를 했으면 좋았겠다는 생각이 드네요

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

public class SumCalculator {
public static int sum (int[] numbers) {
int total = 0;

for (int number : numbers) {
total += number;
}

return total;
}
}