
들어가며
드디어 프리코스 1주차 문제 회고이다. 문제 난이도는 크게 어렵지 않아서 최대한 좋은 코드, 객체지향적인 코드란 무엇일까를 집중적으로 고민하며 한주를 보냈던 것 같다. 글을 쓰는 지금은 아직 팀원들의 코드리뷰를 받지 않았지만, 지난 1주동안 코드에 나의 의도를 담으려고 노력을 정말 많이 했다.
이 글은 내가 무엇을 고민하고 어떻게 나만의 정답을 내렸는지, 그 고민의 흔적들을 허심탄회하게 돌아볼 생각이다.
내가 어떤 1주를 보내왔는지 되돌아보자!
본론으로
⚠️ 문제 개요
1주차 문자열 덧셈 계산기 문제는 사용자의 입력으로 "1,2:3" 이 들어왔을 때, 숫자를 추출하여 6이라는 숫자들의 합을 반환해주면 되는 문제였다. 이때 추출에 쓰이는 기본 구분자는 :(콜론) 과 ,(콤마)이고 사용자는 커스텀 구분자를 지정할 수 있다.
바로 "//<구분자>\n1,2:3" 식으로 입력하여 커스텀 구분자를 지정한다.(ex. "//?\\n1?2,3:4" 입력시 -> 10 출력)
단, 입력은 구분자와 양수로 구분된 문자열이 들어온다.
🛠️ 기능 구현 목록
# 구현 기능 목록
### 덧셈할 문자열 요청 문구 출력 기능
- [x] '덧셈할 문자열을 입력해 주세요.' 메시지를 출력한다.
### 덧셈할 문자열 입력 기능
- [x] 덧셈할 문자열을 입력한다.
- [x] 뷰 레이어의 유효성 검증
- [x] 빈 입력이 아님을 검증한다.
- [x] 원본 입력 문자열 그대로 컨트롤러에 반환한다.
- [x] 뷰 -> 컨트롤러 레이어로 입력값 전달
- [] PositiveNumbers의 인스턴스에서 total()을 호출해 합계를 얻는다.
- [x] 도메인 레이어의 유효성 검증
- [x] 커스텀 구분자 검증(Tokens)
- [x] 입력이 "//"로 시작하면, 커스텀 구분자를 추출한다.
- [x] 첫 "\n"이 존재하는지 확인한다.
- [x] "//"와 "\n" 사이에 정확히 문자 1개가 존재하는지 확인한다.
- [x] 토큰화 검증(Tokens)
- [x] 커스텀 구분자가 있다면 splitWithCustom()을, 없다면 split()을 이용해 문자열을 분리한다.
- [x] 빈 토큰이 있는지 확인한다.
- [x] 분리된 문자열을 반환한다.
- [x] 숫자 검증(PositiveNumbers)
- [x] 각 토큰이 정수 형태인지 확인한다.
- [x] 양수인지 확인한다.
- [x] 검증을 통과한 PositiveNumbers를 반환한다.
### 덧셈 계산 기능
- [x] PositiveNumbers.sum()을 호출하여 합계를 계산한다.
### 결과를 출력하는 기능
- [x] 도메인에서 전달된 덧셈 결과를 출력한다. (ex. 결과 : x)
🧠 시스템 구현도

🧐 고민한 내용들
사실 문제는 어떻게든 풀겠는데... 객체지향적으로 풀려고 하다보니 "역할과 책임" 측면에서 너무 머리가 아팠다. 문제의 난이도가 그렇게 높지 않다보니 무엇이 어떤 책임을 가지면 되는지는 바로 보였다. 하지만 SOLID원칙 중 SRP(단일 책임 원칙) OCP(개방 폐쇄 원칙)을 지키면서 코드를 작성하려니 막막했다.
그래서 일단 사용자 화면부터 생각해서 코드의 흐름을 아래처럼 써 내려갔다..(글씨가 굉장히 부끄럽지만,, 고민의 과정이니 이쁘게 봐주길..ㅎㅎ)


처음에는 Spring 프로젝트에서 흔히 쓰는 계층형 구조인 Controller-> Service-> Repository -> Domain 구조를 무작정 따라하려 했었다. 하지만 생가하면서 점점 굳이 Service, Repository 계층이 필요할까? 라는 생각이 들었고, 최종으로 나온 것이 아래의 사진이다..!!! 처음에는 아래의 구조 정도만 도입하면 충분히 객체지향적으로 구현을 할 수 있을 것이라고 생각했다. (아까의 시스템 구현도를 보면 알다시피 많이 바뀌었다..ㅎㅎ)

그리고 문제를 보면 안내사항의 다음과 같은 말이 있다.
"기능 요구 사항에 기재되지 않은 내용은 스스로 판단하여 구현한다." 그래서 욕심을 내서 테스트는 통과하되, 검증을 빡빡하게 보는 계산기를 만들기로 결정하였다. 그래서 계속 고민하여 아래와 같은 나만의 검증 순서를 만들었다.

아래사진은 여자친구가 개발자이면 좋은점! 본인의 일도 아닌데 같이 고민해줘서 고마워!!


특히 예외를 생각하던 중 "1,2,3," 처럼 마지막이 구분자로 끝나는 애매한 상황에 대해 고민이 깊어졌다. 사실 split()함수는 마지막이 구분자면 마지막 토큰을 버려버린다. 사실 이 케이스는 사용자가 구분자 이후에 값을 입력하려다가 실수한 느낌이니 나는 이를 에러로 잡아야한다고 생각했다. 이를 split(delimiter, -1) 로 처리하였다!! 어떻게 적용했는지는 아래의 글에 자세하게 나와있다.
https://geniusjun4663.tistory.com/45
[JAVA] split() 메서드 정복하기 (나는 왜 split(delimiter, -1)을 썻을까?)
들어가며문자열을 쪼개야겠다 생각이 들면 split() 메서드가 떠오르기 마련이다. 우아한테크코스 프리코스 1주차 문제도 split() 메서드를 절묘하게 이용해야하는 문제였다. 그래서 이번 기회에 spl
geniusjun4663.tistory.com
또한 도메인을 처음에는 Number라고 지정했었는데 해당 방법에는 두가지 문제가 있었다.
1. Number는 이미 자바에서 제공하는 클래스명이었다. -> 이런 변수명은 피해야 한다!
2. 문자열로 입력이 들어오는데, 문자열에서 바로 숫자로 파싱되는 과정에서 꽤 많은 검증이 필요하다. 이를 하나의 객체에서 하기에는 너무 무겁다!
그래서 나는 분리된 문자열을 저장하는 Tokens, 그 분리된 문자열을 계산하기 위해 숫자로 저장하는 PositiveNumbers로 도메인명을 바꾸고 두 개의 객체를 만들기로 하였다. 그 과정에서 일급 콜렉션이라는 좋은 개념을 배워서 바로 적용하였다. 자세한 내용은 아래 글에 있다.
https://geniusjun4663.tistory.com/43
[JAVA] 일급 콜렉션을 이용하여 상태와 로직을 따로 관리하자!
들어가며우아한테크코스 코드컨벤션 문서를 보면 아래와 같은 내용이 있다.콜렉션에 대해 일급 콜렉션 적용...? 뭐 일급비밀(?) 그런건가? 깔깔 처음 봤을 때 감이 하나도 안와서 수많은 기술블
geniusjun4663.tistory.com
일급 콜렉션은 상태와 로직을 따로 관리할 수 있어 다른 클래스의 책임을 덜어주고 캡슐화까지 챙길 수 있는 매우 좋은 개념인 것 같다. 일급 콜렉션을 공부하다 보니 팩토리 메서드로 값을 반환해주는 것도 매력적이라 바로 코드에 적용하고 그 과정을 아래 글에 작성해보았다.
https://geniusjun4663.tistory.com/40
[JAVA] 정적 팩토리 메서드(Static Factory Method)란? (버거 먹고싶은 작성자와 스프링의 반환 방식을 곁
들어가며현재 우아한테크코스 8기 1주차 문제를 열심히 풀어보는 중이다! 1주차라 그런지 문제 자체의 난이도는 크게 어렵지 않다(?). 그래서 앞으로 어려워질 것을 대비해서 자바의 객체지향을
geniusjun4663.tistory.com
처음에는 검증, 파싱로직을 그냥 global한 유틸성 로직으로 따로 관리하였다. 하지만 역할과 책임 측면에서 생각을 하다보니 다음과 같은 생각을 하였다.
검증, 파싱 로직이 글로벌하게 쓰이는 중인가..? 오히려 도메인 객체쪽의 책임과 밀접한 관련이 있는 것이 아닌가?
라는 생각에 나는 책임별로 검증도 나누자!! 라는 결론을 내렸다.
그래서 private static 메서드를 이용해서 클래스 아래에 선언해놓고 끌어다 쓰는 식으로 설계했었다. 근데 뭔가 찜찜했다. 요구사항이 늘어난다면 계속 줄줄이 추가할 것인가?! -> 뭔가 확장성도 챙기고 싶다!!! 라는 생각이 들었다.
결론적으로는 정적 중첩 클래스라는 개념을 적용하였다!!! 아래의 코드를 보자.
public final class ConsoleReader {
public static String readMessage() {
return Validator.validate(Console.readLine());
}
private static class Validator {
public static String validate(String message) {
return validateBlank(message);
}
private static String validateBlank(final String message) {
if (message.isBlank()) {
throw CustomException.from(ErrorMessage.BLANK_INPUT_ERROR);
}
return message;
}
}
}
책임별로 검증을 담당하는 정적 중첩 클래스를 따로 만들어 나중에 계층별 예외 처리에 발이 묶이지 않게 하였고, 확장성도 챙겼다고 자신있게 말한다!(사용자의 터무니 없는 빈 값 검증은 도메인의 역할은 아니지 않은가! 나는 콘솔 입력쪽에서 처리하였다.) 자세한 내용은 아래 글에 작성하였다.
https://geniusjun4663.tistory.com/41
[JAVA] 정적 중첩 클래스를 활용하여 계층간 독립적인 Validation을 적용해보자
들어가며우아한테크코스 프리코스 1주차 문제를 풀며 객체지향적인 코드에 도달하기 위해 수많은 검색과 코드를 보고있다. 내가 최근 느낀점은 "책임과 역할을 상황에 맞게 잘 나누는 것" 이 가
geniusjun4663.tistory.com
이번 적용한 코드에서는 팩토리 메서드로 객체를 생성하는데, 정확히는 팩토리 메서드의 내부에서도 생성자를 이용하여 객체를 생성한다! 처음에는 그냥 생성자에 this.tokens = tokens 이런식으로 반환했었는데, 검색하다가 좋은 개념을 찾아서 적용해보았다. 아래 코드를 보면 그냥 반환해주는 것이 아닌, List.copyOf()를 이용하여 반환해준다. 왜 그랬을까?
// Tokens 생성자
private Tokens(List<String> tokens) {
this.tokens = List.copyOf(tokens);
}
// PositiveNumbers 생성자
private PositiveNumbers(List<Integer> positiveNumbers) {
this.positiveNumbers = List.copyOf(positiveNumbers);
}
이는 객체를 생성해서 외부로 반환하였을 때 값에 접근은 가능하지만, 변경은 불가능하게끔 "불변"인 리스트를 반환해주기 위함이었다. 즉 도메인의 무결성과 신뢰성을 보장하기 위해서 불변리스트를 반환해주었다! 설계적인 측면뿐만 아니라, 이렇게 자료구조의 형태로 객체지향을 지키는 방법도 있음을 이번에 배웠다. 자세한 내용은 아래와 같다.
https://geniusjun4663.tistory.com/46
[JAVA] Collection의 복사 방법에 대해 알아보자!(방어적 복사, 얕은 복사, 깊은 복사) feat. 내가 List.cop
들어가며요즘은 더 좋은 코드를 위해 검색하는 시간이 대다수이다... 그중에서 절묘한 차이로 자료구조의 의미가 완전 달라지는 내용이 있다! 그것은 바로 Collection의 복사방법!!우테코 프리코스
geniusjun4663.tistory.com
Test Code 작성에 관해서는 깊게 알고 있지 못했는데, 이번 기회에 좀 배운 것 같다. 다음에 여유가 된다면 테스트 코드를 작성하며 로직을 완성해나가는 방법론도 있길래 한번 적용해볼까 한다.(TDD) 다만 좀 특이한 점이 있어서 적어볼까 한다.
- Junit5 → 자바에서 제공해주는 기본 테스팅 프레임워크 → assertThat, assertThorw정도의 기본 테스팅 명령어만 있다.
- AssertJ → 자바 테스트를 돕기 위한 다양한 도구가 있는 라이브러리 → 의존성 추가해줘야함
위와 같은 두가지로 정리되길래, AssertJ는 외부라이브러리니 쓰면 안되겠거니.. 했는데, build.gradle에 추가되어있는 'com.github.woowacourse-projects:mission-utils:1.2.0'을 구글링해보니 아래와 같이 의존성이 다 추가되어있었다... 그래서 테스트 코드 다시 작성했다는 사실이 😂
dependencies {
api 'org.assertj:assertj-core:3.21.0'
api 'org.junit.jupiter:junit-jupiter:5.8.1'
api 'org.mockito:mockito-inline:3.12.4'
}
다만 특이한 점은 이번 기수부터 자바 버전을 21로 올렸는데, 자바 21은 현재 의존성에 추가되어 있는 Mockito 3.12.4는 호환이 안된다고 한다. mockito 라이브러리를 이용한 테스트코드 작성 방법도 기술블로그에 많길래 따라해보려 했는데, 자꾸 호환이 안돼서 포기했다. (잠깐 실수하신 걸까..? 의도하신 건가..?)
마무리하며
이처럼 내 생각대로 먼저 스파게티 코드를 짜보고 나서 이를 개선하기 위해 블로그를 검색했다. 그리고 배운 지식을 정리하는 겸 내 블로그도 작성하니 정말 바쁜 1주일을 보낸 듯 하다. 근데 이 과정에서 정말!! 많은 것을 배웠다!! 모르는 것을 찾기 위해 선배 개발자분들의 좋은 글을 많이 보다보니, 글로 담아내질 못할정도로 많은 정보가 내 머리를 스쳐 지나갔다.(남아있겠지..?ㅎㅎ)
스프링 프레임워크에 의존하지 않고 검증도 하고 파싱도 하려니 생각을 깊게 해야해서 너무 좋다. 결국 프레임워크 친구들도 이런 느낌으로 이루어져 있지 않겠는가! 고민을 코드로 풀어내는 과정에서 코드 기술이 많이 느는 느낌이라 괜히 기분이 좋다.
이번 주차도 많이 배웠다고 생각하는데 프리코스만 잘 끝내도 많이 성장한다는 말이 사실이었다. 앞으로도 파이팅 해보며 많은 것을 흡수해보자!! 아자자!
https://github.com/woowacourse-precourse/java-calculator-8/pull/429
[문자열 덧셈 계산기] 노창준 미션 제출합니다. by geniusjun · Pull Request #429 · woowacourse-precourse/java-c
🔗 미션 구현 과정에서 고민한 내용들을 담은 회고 글 🧠 구현 기능 목록 덧셈할 문자열 요청 문구 출력 기능 '덧셈할 문자열을 입력해 주세요.' 메시지를 출력한다. 덧셈할 문자열 입력 기능
github.com
'Various Dev > 우아한테크코스' 카테고리의 다른 글
| [우아한테크코스] 테스트 코드 작성 연습해보기(우테코 제공 라이브러리 적극 활용! NsTest, assertSimpleTest) (0) | 2025.10.30 |
|---|---|
| [우아한테크코스] 프리코스 2주차 문제 회고 - 자동차 경주 (2) | 2025.10.28 |
| [미리보는 프리코스] 시작 하루 전..! 가장 hot한 논제 "각 계층의 필요시점은 어떻게 될까요?" by me (근데 이제 회고와 다짐을 곁들인..) (2) | 2025.10.13 |
| [미리보는 프리코스] 6기 프리코스 1주차 숫자 야구게임 미리 풀어보기 (0) | 2025.10.03 |
| [미리보는 프리코스] 나의 우테코 백엔드 8기 "도전", 기록하자. (0) | 2025.09.29 |