최종 테스트를 대비해 빠르게 구현하는 습관을 먼저 들이기로 했습니다.
그래서 TDD와 같은 개발방법론보다는 우선 구현하는 것에 초점을 뒀습니다.
레이어는 MVC 패턴을 차용해 각 관심사를 분리했습니다.
이를 패키지 구조로 아래와 같이 작성했습니다.
calculator/
├── View.class
├── Controller.class
├── StringCalculator.class
└── Application.class
1. 계산기는 기본 구분자(`:`, `,`)를 가진다.
2. 계산기는 커스텀 구분자를 지정할 수 있다.
* 커스텀 구분자는 문자열 앞부분의 `//`와 `\n` 사이에 위치하는 `문자`를 커스텀 구분자로 사용한다.
* `//.\n` -> 커스텀 구분자 = `.`
3. 분리한 각 숫자의 합을 반환한다.
4. 빈 문자열일 경우 0을 반환한다.
5. 잘못된 입력 시 `IllegalArgumentException`이 발생한다.
* 양수가 아니거나 구분자 외 문자가 있는 경우
* 프로그램은 즉시 종료된다.
요구사항을 토대로 기능 구현 목록을 작성하니 세가지 구현 스텝이 나왔습니다.
1 → 3 → 542해당 순서로 기본 구분자용 계산기와 커스텀 구분자용 계산기를 만들면 되겠다는 생각이 들었습니다.
명확한 구현 순서가 정해졌으므로 해당 순서대로 기능을 구현합니다.


:, ,)를 가진다.

IllegalArgumentException이 발생한다.

구현한 내용을 토대로 발생 할 수 있는 엣지케이스에 대해 정리해보며, 해결하는 과정을 거쳤습니다.
"")의 경우 0을 반환한다."0" 입력은 받을 수 없다." ")인 경우는 잘못된 입력이다."1, 2, 3"과 같은 입력은 잘못된 입력이다."//.-\\n1"과 "//.\\n//-\\n1", "a//.-\\n1 같은 입력은 받을 수 없다.//와 \\n 사이에 문자들혹은 문자열이 있는 꼴이다.//와 \\n 사이에 문자열이 있는 꼴이다.//로 시작해야 된다.
각 엣지 케이스에 대해 E2E 테스트를 진행하고 실패하는 테스트들은 해결하며 동작하는 코드를 구현했습니다.
클린 코드 책으로 스터디를 진행하며 1장의 내용을 작성해봤습니다. - 깨끗한 코드 노션 링크
1장은 구루들의 경험이 담긴 내용이 정말 많이 담겨있었는데요.
구루들의 깨끗한 코드에 대한 설명을 들으니, 지금까지 학습했던 내용들이 퍼즐마냥 끼워맞춰지기 시작했습니다.
특히 마이클 페더스이 설명한 “깨끗한 코드는 언제나 누군가 주의 깊게 짰다 는 느낌을 준다”는 내용이 정말 인상깊었습니다. 그래서 동작하는 코드를 다시 되돌아보면서 “나는 얼마나 주의 깊게 작성했을까?”라는 생각이 떠오르더라구요. 1장에서 깨달은 내용으로 리팩토링을 한 번 진행해봤습니다.
스스로에게 물어봅니다. “내 코드를 얼마나 주의 깊게 짰는가?”
위 질문에 답변을 하기 힘들었습니다. 내가 이 프로그램을 만드는 목적이 무엇인지부터 정의할 필요를 느꼈습니다.
“나는 왜 이 코드를 작성하는가?” 단순히 우아한테크코스 합격을 위햬?
그래서 저는 이 프로그램을 사용하는 페르소나를 설정해 개발을 진행했습니다.
기획팀
플랫폼 개발자
각 개발팀
덧셈할 문자열을 입력해 주세요. → 덧셈할 문자열을 입력해 주세요. 예) 1,2:3 =>커스텀 구분 기호 규칙 : //.\n → 기존 구조는 따르나 숫자는 포함되면 안돼.숫자 처리: BigInteger → Long or Interger각 페르소나를 설정하고 개발을 시작하니 단순히 미션 완수가 목적이 아니게 됐습니다.
각 사용자에게 서비스를 제공해주는 개발자의 입장에서, 각 페르소나의 요구사항을 반영하는 나만의 목적을 찾을 수 있었습니다. 요구사항을 반영하기 위해 최대한 확장 가능한 구조로 개선해보는 과정을 가졌습니다.
개발팀 A의 요구사항: 입력 내용이 친절하지 않네..? 나는 입력 예시도 포함하고 싶은데?



개발팀 B 요구사항: 커스텀 구분 기호가 애매한데..? 숫자는 구분 기호로 포함하면 안될 것 같은데;;

DelimiterTokenizer를 생성자로 전달합니다.명확한 네이밍을 위해 Calculator → TextCalculator 로 개선했습니다.

Template-Method Pattern 을 활용해 추상화했습니다.
DelimiterTokenizer를 구현한 SimpleDelimiterTokenizer를, 기본으로 제공하는 문자열 계산기의 표현식 분리 클래스로 사용합니다.SimpleDelimiterTokenizer와 같이 DelimiterTokenizer를 상속받아, 구현해 개발팀 B만의 표현식 분리 처리를 할 수 있습니다.하지만, 근본적인 문제가 있으므로 해결하고 넘어가야합니다. 앞서 작성한 Controller와 View 구현하기 에서 Controller 내부에서 Calculator를 직접 생성하고 있습니다.


이 과정에서 View와 Controller 또한 명확한 네이밍으로 수정했습니다.


DelimiterTokenizer를 상속받아 자유롭게 커스터마이징 할 수 있습니다.
개발팀 B 요구사항: 문자열 계산기의 BigInteger 타입은 메모리 누수가 발생할 수 있어서 위험한데…
기존 코드는 “문자열 계산기”이기 때문에, 충분히 큰 수를 대체할 수 있는 것을 목적으로 구현했습니다.
이번에는 요구사항에 맞게 BigInteger가 아닌 Int나 Long 또한 허용해줘야 할 수 있다는 것을 생각해야 됩니다.




TextCalculator는 제너릭을 활용하며 타입 캐스팅의 문제가 발생 할 수 있습니다.


이렇게 남은 전체 구조를 타입 확장할 수 있는 형태로 리팩토링했습니다.





문제 난이도가 쉬운 덕분에 빠르게 기능을 구현하고, 더 깊이 생각해 볼 수 있었던 과정인 것 같습니다. 다음 주 부터는 난이도가 조금 올라갈 것 같아서, 이정도로 세분화를 해 볼 수 있을지 또 새로운 도전이 될 것 같네요 😊
그리고 클린코드를 아주 짧게 읽었음에도 불구하고 깨닫게 된 내용이 정말 많았습니다.
(클린코드를 완독할 이유가 생김)
실수한 부분도 있습니다… TextCalculate의 calculate 메서드를 List<Positive<T>>이 아닌 Numbers라는 일급컬렉션을 만들어 반환했는데요 ㅠㅠ.. 글을 쓰면서 정리하다보니, 상태 저장이 전혀 필요없는데 오버엔지니어링을 했더라구요…
개발하는 과정에서 계속 바뀌고 하다보니 놓친 것 같습니다.. 이거 글 쓸 시간에 코드 한번 더 볼걸..
어쨌든 이렇게 1주차 프리코스 문자열 계산기를 잘 마무리했습니다. 다들 고생하셨습니다.
안녕하세요 글 잘 읽었습니다 :) 개발팀 B 요구사항 반영 부분에서 textCalculator 클래스를 DIP 원칙에 맞게 수정하셨다고 하셨는데, 아직 DIP 에 대해 개념만 알고 적용해본 적이 없어서 어떤 식으로 리팩터링하셨는지 여쭤보고 싶습니다 ..! 코드를 봐도 아직 잘 이해가 안가네요 ..ㅎ