
프리코스에서 설정한 목표는 세 가지였다.
1. 요구사항을 충분히 이해하고 이를 만족시키는 것
2. 확장성과 유지보수성을 고려한 코드 작성
3. 객체 지향 프로그래밍 원칙을 체계적으로 적용해 객체가 자신의 책임을 명확히 하는 것이 목표들을 충분히 고려하며 코드를 작성했는지 돌아보자.
프리코스 미션의 요구 사항으로는 과제 진행 요구 사항, 기능 요구 사항, 프로그래밍 요구 사항 세 가지로 구성되어 있다.
그중에서도 특히 다음 문장이 눈에 띄었다.
기능 요구 사항에서 기재되지 않은 내용은 스스로 판단하여 구현한다.
과제를 단순히 따라가는 것이 아니라, 내가 무엇을 구현해야 할지 스스로 결정할 수 있다는 것을 의미했다.

구현할 기능 목록을 만들고 README.md에 정리한 기능 목록 단위로 커밋을 진행하여 만족시켰다. 
[기능 요구 사항]
- 쉼표(,) 또는 콜론(:)을 구분자로 가지는 문자열을 전달하는 경우 구분자를 기준으로 분리한 각 숫자의 합을 반환한다.
- 예: "" => 0, "1,2" => 3, "1,2,3" => 6, "1,2:3" => 6
- 앞의 기본 구분자(쉼표, 콜론) 외에 커스텀 구분자를 지정할 수 있다. 커스텀 구분자는 문자열 앞부분의 "//"와 "\n" 사이에 위치하는 문자를 커스텀 구분자로 사용한다.
- 예를 들어 "//;\n1;2;3"과 같이 값을 입력할 경우 커스텀 구분자는 세미콜론(;)이며, 결과 값은 6이 반환되어야 한다.
- 사용자가 잘못된 값을 입력할 경우
IllegalArgumentException을 발생시킨 후 애플리케이션은 종료되어야 한다.
위의 요구 사항을 확인하면서, 기능 요구 사항에 기재되지 않은 내용은 스스로 판단하여 구현한다. 라는 문장의 의미를 더욱 명확히 이해할 수 있었다.
예를 들어:
커스텀 구분자를 여러 개 만들 수 있는지에 대한 명확한 설명은 없었다.
//$\n//@\n1$2@3과 같은 문자열에서 두 개의 구분자를 사용할 수 있는지에 대한 판단은 직접 내려야 했다.커스텀 구분자 생성 시 예외 처리도 스스로 결정해야 했다.
//\n234IllegalArgumentException을 발생시켰다. 빈 문자는 구분자로서의 역할을 할 수 없기 때문이다.//2\n22324IllegalArgumentException을 발생시켰다. 숫자를 구분자로 허용하는 것은 합리적이지 않다고 판단했기 때문이다.이와 같은 상황들을 고려하면서, 문제를 해결하기 위한 나만의 기준을 세워가며 구현했다.
JDK 21버전과 camp.nextstep.edu.missionutils.Console의 readLine()을 활용하는 등 요구 사항을 충실히 만족시켰다. 특히, 자바 코드 컨벤션에 대해 xml 설정 방법을 디스코드의 함께-나누기 채널에 공유하여, 많은 사람들이 이 요구 사항을 쉽게 만족할 수 있도록 도움을 주기 위해 블로그를 공유했다. 많은 분들이 댓글로 도움을 받았다고 댓글을 달아 주셔서 큰 보람을 느꼈다. 나 역시 여러 글들을 통해 많은 도움을 받았기에, 이것이 우테코에서 바라는 상호 성장의 모습이 아닐까 하는 생각이 들었다.
이번 과제에서 확장성과 유지보수성을 고려하려고 노력했지만, 솔직히 말해서 잘 적용했다고 자신 있게 말할 수 없다.
특히 이번 미션에서 인터페이스를 사용하여 코드를 작성했지만, 과제를 제출하고 내가 작성한 코드를 다시 보면서 이상한 점을 느꼈다.
아래와 같은 인터페이스를 정의했는데:
public interface Calculator {
long sum(List<Long> numbers);
}
public class AdditionCalculator implements Calculator {
@Override
public long sum(List<Long> numbers) {
return numbers.stream()
.mapToLong(Long::longValue)
.sum();
}
}
나는 AdditionCalculator처럼 더하기를 담당하는 Calculator를 만들었고, 추후에 뺄셈(-), 곱셈(*), 나눗셈(/) 등을 담당하는 다른 Calculator들을 만들고 싶었다. 하지만 인터페이스에서 long sum(List numbers);로 정의했기 때문에, "덧셈"만을 여러 방식으로 구현하게끔 작성된 셈이 되었다.
이 부분에서 잘못 설계한 것을 깨달았다. 다른 계산 방식들을 추가하고 싶었다면, sum이라는 이름 대신 좀 더 일반적인 이름으로 인터페이스를 정의했어야 했고, 덧셈, 뺄셈, 곱셈 같은 다양한 계산 로직을 유연하게 확장할 수 있는 구조를 설계했어야 했다.
예를 들어, calculate와 같이 보다 범용적인 이름을 사용했더라면, 다양한 계산 방식을 추가할 때 인터페이스가 더 적합하게 사용되었을 것 같다:
public interface Calculator {
long calculate(List<Long> numbers);
}
이번 과제를 통해 단순히 "인터페이스를 사용하면 확장성이 좋아진다"는 이유로 남용하기보다는, 왜 사용하는지 정확히 알고 사용해야겠다는 것을 느꼈다.
미션을 수행하면서 객체 지향 프로그래밍 원칙을 명확히 적용하고자 노력했다.
이 원칙은 하나의 클래스는 하나의 책임만 가져야 한다는 원칙으로, 이를 통해 코드의 응집도를 높이고 변화에 따른 영향을 최소화할 수 있다.
Delimiters 클래스: Delimiters 클래스는 오직 구분자 관리를 책임지며, 기본 구분자와 커스텀 구분자를 관리하는 역할을 담당한다. 구분자를 추가하거나 조회하는 로직만을 포함하고, 그 외의 로직은 포함하지 않았다.
CustomDelimiterExtractor와 CustomDelimiterRemover 클래스: 구분자 추출과 제거는 각각 CustomDelimiterExtractor와 CustomDelimiterRemover 클래스에서 담당한다. 두 클래스 모두 구분자 처리에 대한 명확한 책임을 부여 했다. 이를 통해 구분자 관련 로직의 수정이 필요할 경우 다른 클래스에 영향을 주지 않고도 변경할 수 있다.
하지만
2. 확장성과 유지보수성을 고려한 코드를 작성했는가?에서 언급한 문제로 인해, 다른 객체 지향 원칙들은 제대로 지켜지지 못한 것 같다.
Calculator 인터페이스를 사용하면서 OCP(개방-폐쇄 원칙)을 적용하려 했지만, 결과적으로 덧셈만을 처리하는 인터페이스로 제한되어 다른 계산 방식을 추가할 수 없는 구조가 되었다.
이번 과제를 통해, 내가 아직 배울점이 많다는 것을 느꼈다. 다음 과제들에서 이번에 부족했던 점을 보완하고 더 성장하고 싶다!! 화이팅 🔥🔥