Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[step1] 문자열 덧셈 계산기 구현 #769

Open
wants to merge 1 commit into
base: giraffeb
Choose a base branch
from
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
60 changes: 60 additions & 0 deletions src/main/java/calculator/StringCalculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package calculator;

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

public class StringCalculator {
Copy link

Choose a reason for hiding this comment

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

현재 문자열 계산기는 수의 유효성 검증, 값 더하기, 문자열 분리 및 변환 등 많은 책임을 가지고 있어요! 한 객체가 하나의 책임을 가질 수 있도록 책임을 분리해보는 게 어떨까요? 이와 관련해서 객체지향 원칙 중 단일 책임 원칙을 참조해보면 좋을 것 같아요!

https://inpa.tistory.com/entry/OOP-%F0%9F%92%A0-%EC%95%84%EC%A3%BC-%EC%89%BD%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EB%8A%94-SRP-%EB%8B%A8%EC%9D%BC-%EC%B1%85%EC%9E%84-%EC%9B%90%EC%B9%99

예를들어 스스로 양수임을 보장하는 객체가 있다면? 문자열 계산기는 해당 객체에게 그 책임을 위임하고 협력한다면, 문자열 숫자 변환, 음수 검증을 직접 책임지지 않아도 될거예요! 관련해서 참고해보면 더 좋을만한 아티클이예요😊

https://tecoble.techcourse.co.kr/post/2020-05-29-wrap-primitive-type/
https://jojoldu.tistory.com/412

private static final String DEFAULT_SEPERATORS = ",|:";
private static final String DELIMITER_REGEX = "//(.)\\n(.*)";

public int add(String input) {
if (input == null || input.isEmpty()) return 0;
try {
String[] divided = initSeparator(input);

String separator = divided[0];
String inputString = divided[1];

List<String> tokens = tokenize(inputString, separator);
List<Integer> numbers = parse(tokens);

return sum(numbers);

} catch (Exception e) {
throw new RuntimeException();
}
Comment on lines +25 to +27
Copy link

Choose a reason for hiding this comment

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

Exception은 checked exception과 unchecked exception 모두를 포함해요!
모든 예외를 하나로 처리한다면,특정 예외에 대해 적절하게 대응하기 어려울 거예요. 조금 더 구체적으로 예외를 작성해보면 어떨까요?
그리고 Exception e를, new RuntimeException()에 함께 전달하지 않으면 원래 예외의 정보가 사라지게 돼요. 🥲 디버깅 시 어떤 예외가 발생했는지 알 수 없어져서 원인을 찾기가 어려워지는데, 이 부분도 함께 확인해보시면 좋을 것 같아요!

관련해서, Checked Exception, Unchecked Exeption, 예외 전달과 관련하여 더 알아보시면 좋을 것 같아요!

}

public String[] initSeparator(String input) {
Matcher matcher = Pattern.compile(DELIMITER_REGEX).matcher(input);

String customSeperator = DEFAULT_SEPERATORS;
String inputString = input;

if (matcher.find()) {
customSeperator = matcher.group(1);
inputString = matcher.group(2);
}

return new String[]{customSeperator, inputString};
}
Comment on lines +30 to +42
Copy link

Choose a reason for hiding this comment

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

다른 객체가 initSeparator 메소드와 협력한다면,
String[] 반환값에서 특정 인덱스가 무엇을 의미하는지 구체적으로 아는 과정이 동반되고, 이를 기억해야한다고 생각해요!
만약 String[] 반환값의 순서가 바뀐다면 이를 사용하던 클라이언트들은 어떻게 될까요?

캡슐화에 대해 더 알아보시면 좋을 것 같아요!
https://inpa.tistory.com/entry/OOP-%EC%BA%A1%EC%8A%90%ED%99%94Encapsulation-%EC%A0%95%EB%B3%B4-%EC%9D%80%EB%8B%89%EC%9D%98-%EC%99%84%EB%B2%BD-%EC%9D%B4%ED%95%B4


private List<String> tokenize(String input, String seperator) {
return Arrays.stream(input.split(seperator)).toList();
}

private List<Integer> parse(List<String> tokens) {
return tokens.stream().map((String v) -> {
int n = Integer.parseInt(v);
if (n < 1) throw new RuntimeException("음수는 계산할 수 없습니다.");
return n;
}).toList();
}

private int sum(List<Integer> numbers) {
return numbers.stream().reduce(0, Integer::sum);
}

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


import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.NullAndEmptySource;
import org.junit.jupiter.params.provider.ValueSource;

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

/**
* <요구사항>
* 1. 구분자를 가지는 문자열을 전달 받는다.
* 2. 기본 구분자([",", ":"])를 기준으로 분리한 각 숫자의 합을 반환한다.
* 3. 기본 구분자 외에 커스텀 구분자를 지정할 수 있다. 커스텀 구분자는 "//"와 "\n"사이에 위치하는 '문자'를 커스텀 구분자로 사용한다.
* 4. 문자열 계산기에 숫자 이외의 값 또는 음수를 전달하는 경우. RuntimeException 예외를 throw 한다.
* <p>
* <요구사항 - 정제>
* 1.문자열을 받는다.
* 2.문자열은 구분자와 숫자로 구분된다.
* 3.구분자는 "기본 구분자"와 "커스텀 구분자" 2가지로 구성된다.
* 3-1. "기본 구분자" [",", ":"]
* 3-2. "커스텀 구분자" "//"(문자)"/n"
* 4. 숫자는 0을 포함하는 자연수
* 5. 4이외의 수가 전달되는 경우 RuntimeException 예외를 throw한다.
*/
Comment on lines +14 to +29
Copy link

Choose a reason for hiding this comment

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

요구사항은 코드레벨이 아닌 따로 관리해보면 어떨까요? 😃

class StringCalculatorTest {
private StringCalculator calculator;

@BeforeEach
void setUp() {
calculator = new StringCalculator();
}

@DisplayName(value = "빈 문자열 또는 null 값을 입력할 경우 0을 반환해야 한다.")
@ParameterizedTest
@NullAndEmptySource
void emptyOrNull(final String text) {
assertThat(calculator.add(text)).isZero();
}

@DisplayName(value = "숫자 하나를 문자열로 입력할 경우 해당 숫자를 반환한다.")
@ParameterizedTest
@ValueSource(strings = {"1"})
void oneNumber(final String text) {
assertThat(calculator.add(text)).isSameAs(Integer.parseInt(text));
Copy link

Choose a reason for hiding this comment

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

검증부를 하드코딩해보면 어떨까요?😃

https://jojoldu.tistory.com/615

Copy link
Author

Choose a reason for hiding this comment

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

👍 이런 관점이 있군요! 수정해 보겠습니다.

}

@DisplayName(value = "숫자 두개를 쉼표(,) 구분자로 입력할 경우 두 숫자의 합을 반환한다.")
@ParameterizedTest
@ValueSource(strings = {"1,2"})
void twoNumbers(final String text) {
assertThat(calculator.add(text)).isSameAs(3);
}

@DisplayName(value = "구분자를 쉼표(,) 이외에 콜론(:)을 사용할 수 있다.")
@ParameterizedTest
@ValueSource(strings = {"1,2:3"})
void colons(final String text) {
assertThat(calculator.add(text)).isSameAs(6);
}

@DisplayName(value = "//와 \\n 문자 사이에 커스텀 구분자를 지정할 수 있다.")
@ParameterizedTest
@ValueSource(strings = {"//;\n1;2;3"})
void customDelimiter(final String text) {
assertThat(calculator.add(text)).isSameAs(6);
}

@DisplayName(value = "//와 \\n 문자 사이에 커스텀 구분자를 지정할 수 있다.")
@ParameterizedTest
@ValueSource(strings = {"//;\n1;2;3"})
void findCustomDelimiter(final String text) {
String[] divided = calculator.initSeparator(text);
assertThat(divided[0]).isEqualTo(";");
}
Comment on lines +73 to +79
Copy link

Choose a reason for hiding this comment

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

커스텀 구분자를 지정할 수 있는지에 대한 테스트는
66번줄 테스트에서 진행된 것 같은데 구분자에 대해 별도로 테스트하신 의도 설명해주실 수 있을까요?? DM으로도 좋아요!


@DisplayName(value = "문자열 계산기에 음수를 전달하는 경우 RuntimeException 예외 처리를 한다.")
@Test
void negative() {
assertThatExceptionOfType(RuntimeException.class)
.isThrownBy(() -> calculator.add("-1"));
}
}