3시에 미션이 나오고 기대 반 걱정 반으로 문제를 읽었습니다. 처음에는 '읭..? 생각보다 간단한 것 같은데 ?' 라고 생각했는데 그러다가 눈물 펑펑 흘렸습니다.
이번 1주차 회고에서는 어떤 목표를 가지고 어떻게 접근했는지, 목표를 달성했다고 생각하는지 적어보려 합니다.
입력한 문자열에서 숫자를 추출하여 더하는 계산기를 구현한다.
쉼표(,) 또는 콜론(:)을 구분자로 가지는 문자열을 전달하는 경우 구분자를 기준으로 분리한 각 숫자의 합을 반환한다.
◦ 예: "" => 0, "1,2" => 3, "1,2,3" => 6, "1,2:3" => 6
앞의 기본 구분자(쉼표, 콜론) 외에 커스텀 구분자를 지정할 수 있다. 커스텀 구분자는 문자열 앞부분의 "//"와 "\n" 사이에 위치하는 문자를 커스텀 구분자로 사용한다.
◦ 예를 들어 "//;\n1;2;3"
과 같이 값을 입력할 경우 커스텀 구분자는 세미콜론(;)이며, 결과 값은 6이 반환되어야 한다.
사용자가 잘못된 값을 입력할 경우 IllegalArgumentException
을 발생시킨 후 애플리케이션은 종료되어야 한다.
입력 : 구분자와 양수로 구성된 문자열
1,2,3
출력 : 덧셈 결과
결과 : 6
덧셈할 문자열을 입력해 주세요.
1,2:3
결과 : 6
노션에 문제를 읽으면서 생각했던 것들을 다 적었습니다. 처음엔 구분자의 정확한 정의가 무엇인지 궁금해서 찾아봤습니다.
구분자는 데이터를 구분하거나 분리하는 데 사용하는 문자 또는 기호이다.
기본 구분자는 이미 정해져 있기 때문에 문자열을 나누는데에 어려움이 없었고, 커스텀 구분자는 정해져 있지 않기에 어떤 기준으로 사용할 수 없는 구분자를 정해야 할지 생각했습니다.
그래서 정해진 저의 규칙은 아래와 같습니다. 아래 규칙 이외 문자열은 사용이 가능하게 하였습니다.
커스텀 구분자 규칙
- //와 \n는 커스텀 구분자를 정하기 위한 형식이므로 혼동을 줄 수 있기 때문에 사용할 수 없다.
- 공백 : 공백으로 이루어진 구분자는 사용할 수 없다.
- 숫자 : 숫자를 계산해야 하기 때문에 구분자로 사용할 수 없다.
한 가지 역할의 목표를 위해 어떤 역할이 필요한 지 먼저 나누었습니다.
여러 예제를 고민하며 생각날 때마다 테스트 예제를 추가해서 정리했습니다.
이렇게 고민하니 추가적으로 어디서 예외 처리를 하면 좋을 지 알 수 있었던 것 같습니다.
입력값 | 기댓값 |
---|---|
“” | 0 |
1,2,3 | 6 |
1,2:3 | 6 |
//;\n1;2:3,4 | 10 |
//;\n1 | 1 |
//;\n1;2; | 3 |
:1:2:3 | 6 |
//\n12*3 | 6 |
1::2,3 | 6 |
//;\n1;;2;3 | 6 |
//;\n//&\n1;2&3;4 | 10 |
//\n123 | 사용할 수 없는 커스텀 구분자 사용 예외 발생 |
\n1;2;3 | 커스텀 구분자 형식 예외 발생 |
-1,2,3 | 구분자로 나누었을 때 음수 예외 발생 |
//\n1a*3 | 구분자로 나누었을 때 문자열 예외 발생 |
구현한 내용은 깃허브 PR 링크를 걸어두겠습니다!
만약.. 누가 본다면 리뷰도 환영입니다 :)
숫자와 구분자를 많이 다뤄야하는 문제이다보니 정규식은 빠질 수 없다고 생각했습니다. 저는 숫자나 정해진 문자열만 정규식을 사용하려 하였습니다. 이유는 정규식은 정말 다양하고, 형식이 정해져 있는 것도 있어 편리하지만 저 조차도 이게 어떤 걸 표현하는 정규식인지 한 눈에 알아보기 어려웠기 때문입니다. 아직 정규식에 대해서 잘 모르는 다른 사람이 봤을 때도 어떤 정규식인지 알아볼 수 있는 것만 사용했습니다. 아래는 제가 정규식을 공부할 때 도움을 받았던 블로그입니다!
처음에는 기본 구분자와 커스텀 구분자를 추출하고 그걸로 정규식을 만들어서 구분하면 될 것이라고 생각했습니다. 하지만 '나중에 기본 구분자가 추가된다면? 커스텀 구분자로 사용할 수 없는 게 추가된다면? 커스텀 구분자를 나누는 형식을 바꾸고 싶으면 어떻게 하지?' 라는 생각을 하게 되었습니다.
이렇게 다양한 상황을 생각하는 것이 코드를 유지보수할 때 불편한 상황을 빠르게 파악할 수 있다는 것을 깨닫게 해주었습니다.
열거된 상수를 사용해서 이름을 명확하게 나타낼 수 있다는 장점을 이용하여 기본 구분자와 사용할 수 없는 커스텀 구분자를 Enum 클래스로 만들었습니다. 하지만 선언한 상수를 확인하기 위해서 조건문과 반복문이 필요했고 '뭔가 더 좋게 활용할 수 있을 것 같은데..' 라는 생각이 들었습니다. 그래서 Enum 클래스에 대해서 알아보고 메서드를 사용하니 더 깔끔한 코드를 짤 수 있었습니다. (엄청난 효과를 봤습니다!!!)
// 수정 전 코드
...
private boolean containsValidDelimiter(String input) {
for (Delimiter delimiter : Delimiter.values()) {
if (delimiter == Delimiter.COMMA || delimiter == Delimiter.COLON) {
if (input.contains(delimiter.getDelimiter())) {
return true;
}
}
}
return false;
}
...
// 수정 후 코드
...
private boolean containsValidDelimiter(String input) {
return Delimiter.getBasicDelimiters()
.stream()
.anyMatch(input::contains);
}
...
여러 PR을 구경하니 MVC를 사용하신 분들이 많았습니다. 저는 MVC를 쓰지 않아서 패키지 구조에 대한 고민을 하게 되었습니다.
처음에는 controller
를 제외한 모든 클래스를 service
에 포함해야 한다고 생각했지만, 구분자와 숫자를 추출하는 기능이 상태를 가지거나 의존성을 필요로 하지 않는다는 점에서 빈 등록이 불필요하다고 판단했습니다.
이렇게 분리한 덕분에 필요에 따라 재사용이 가능해졌습니다.
하지만 단순히 공통 기능을 util
로 묶는 것보다, 책임과 역할에 맞게 적절히 분리하는 것이 중요하다는 점을 깨달았고 너무 광범위하게 사용할 경우 단일 책임 원칙에 어긋날 수 있기 때문에, util
의 활용은 신중하게 결정해야 한다는 것을 배울 수 있었습니다.
공부하면서 참고했던 블로그입니다!
Util 클래스
앞서 적었던 목표를 달성했는지 생각해보면 아쉬웠던 부분이 많은 것 같습니다.
역할을 분리해놨음에도 계속 해서 중복되는 역할을 가지거나 여러 역할을 가지고 있는 것들도 있는 것 같습니다.
객체 지향에 대해서 완벽히 이해하지 않고 역할에만 초점을 두니 이런 일이 일어나지 않았나 생각합니다..🥺
메서드명은 하는 역할을 명확하게 명시하기 위해서 노력했습니다.
생각보다 메서드명 짓기가 어렵다는 걸 알게되었습니다.(제가 영어에 약한걸지도..)
만약 코드를 보다가 '얘가 무슨 역할을 하는 거지..?' 하는 생각이 든다면 바로 혼내주시면 감사하겠습니다!
이번 미션을 하면서 다양한 지원자분들의 PR도 구경을 많이 했습니다. 보면서 대단하다고 생각하신 분들이 많았습니다.
그런 분들과 모여서 소통하며 많이 배워갈 수 있을 거라고 생각하니 정말 기대가 됩니다!
객체 지향에 대해서 요즘 많이 알아보고 관련 서적도 읽어보고 있습니다. 추천해주셔도 좋습니다!!
이번 2주차 미션에서는 1주차 때 아쉬웠던 부분을 보완해서 객체 지향적이고 더 나은 코드를 위해 최선을 다하겠습니다! 화이팅👊🏻