0. 과제 설명
8기의 1주차 문제는 문자열 덧셈 계산기였다.
구분자를 이용해서 숫자 문자열을 분리하고, 숫자들을 모두 더해야 한다.
1. 나에게 적용해보기
깃 컨벤션을 보면 기능 단위로 하라고 공지되어있다.
하지만 "기능을 어떻게 정의할까?"라는 생각이 들어 아래와 같이 키워드를 선정하였다.
오늘의 키워드는 "기능"이다 ⭐️
2. 개발 시작
2-0. 리드미 작성
우선 문제에서 주어진대로 리드미를 작성하였다.

- 동작은 시스템 관점에서 시간 순서대로 수행하는 "프로세스"를 작성하였다.
- 기능은 일단 "역할"이라고 생각하여 위와 같이 기능 목록을 작성하였다.
기본적인 틀을 이정도로 잡고, 개발 과정에서 추가로 리드미를 업데이트 하도록 하겠다.
파일 구조
개발하는 과정에서 파일이나 클래스가 더 많아질 수 있지만, 가장 기본이 되는 구조는 아래와 같이 선정하였다.
Controller (Controller, Message)
Service
Domain
Exception (ErrorMessage, Validator)

- 스프링 프로젝트에서 사용하던 Controller, Service, Domain을 가져와 사용하였고, DB 접근이 없기 때문에 Resposioty는 제외하였다.
- 메세지 클래스, 검증기 클래스를 분리하니 코드가 깔끔해져서 Message, ErrorMessage, Validator도 기본 파일 구조로 선정하였다.
2-1. Message 작성
1) 출력 메세지 폼 작성
2) 에러 메세지 폼 작성
출력해야하는 건 사용자에게 하는 출력 메세제와, 에러가 났을 때 개발자에게 하는 에러 메세지가 있다.
출력 메세지 폼 작성
이번 과제에서 출력해야하는 문장은 아래의 두 문장이다.

이걸 Message에서 static final로 구현하였다.
출력해야하는 메세지들을 한 곳에서 관리하는게 편해서 Message를 따로 만들었다.
// Message.java
package calculator.controller;
public class Message {
public static final String REQUEST_STRING_INPUT="덧셈할 문자열을 입력해 주세요.";
public static final String PRINT_FINAL_RESULT="결과 : %d";
}
결과는 계산을 마친 후에 결정되기 때문에, 추후에 값을 넣어줄 형식만 %d로 지정해두었다.
에러 메세지 폼 작성
리드미에서 적어둔 예외 처리를 참고하여 에러메세지를 작성한다.
사용자 입력값 검증에서 숫자가 아닌 값 확인과, 도메인 검증에서 숫자가 아닌 값 확인은 같은 검사여서 둘 다 NOT_NUMBER_ERROR를 사용하기로 하였다.

에러메세지는 입력메세지와 다르게 ENUM을 사용하였다.
// ErrorMessage.java
package calculator.exception;
public enum ErrorMessage {
INVALID_INPUT_FORMAT("입력이 올바르지 않습니다."),
NOT_NUMBER_ERROR("숫자가 아닌 값이 있습니다.");
private final String message;
ErrorMessage(String message){
this.message=message;
}
public String getMessage(){
return message;
}
}
에러 메세지는 추후에 http 상태 코드 등 더 많은 값을 포함할 수 있기 때문에 enum이 낫다고 판단했다.
2-2. Controller 작성
1) 메세지 입출력 기능 추가
2) 구분자 문자열과 숫자 문자열을 분리하는 기능 추가
3) 검증기 호출
controller에서는 위의 3가지 기능 들을 포함한다.
메세지 입출력 기능 추가
이제 Controller에서 메세지 입출력과 관련된 메서드를 만든다.
메서드가 한 가지 일만 담당하도록, 최대한 작은 단위로 구현하려고 한다.
그래서 오직 입력과 출력에만 집중한 메서드를 만들었다.
이때 내가 세운 규칙은 해당 메서드를 거치는 파라미터와 리턴 타입은 무조건 String이다.
숫자로 이루어진 문자열이라고 해서, 해당 메서드에서 숫자로 가공하지 않는다 ❌
// CalculatorController.java
// 사용자에게 출력하는 함수
public void printMessage(String message){
System.out.println(message);
}
// 사용자의 입력을 받는 함수
public String getInput(){
String inputString=Console.readLine();
return inputString;
}
controller 메인 함수 doCalculator()를 만들어서, 그 안에서 메서드를 호출하여 사용하도록 했다.

구분자 문자열과 숫자 문자열을 분리하는 기능 추가
사용자 지정 구분자가 여러 개일 수도 있기 때문에, 첫번째 숫자를 발견을 기준으로 쪼개주도록 하겠다.
숫자를 발견하기 위해 정규표현식을 사용하였다.
// 처음 등장하는 숫자 기준 split
public List<String> splitMessage(String inputMessage) {
Integer idx = -1;
List<String> convertedMessage = new ArrayList<>();
Pattern pattern = Pattern.compile("[0-9]");
Matcher matcher = pattern.matcher(inputMessage);
if (matcher.find()) {
idx=matcher.start();
}
if (idx==-1){
convertedMessage.add(inputMessage);
return convertedMessage;
}
convertedMessage.add(inputMessage.substring(0, idx));
convertedMessage.add(inputMessage.substring(idx));
return convertedMessage;
}
이 함수를 controller 메인 함수에서 호출하여 사용하였다.

2-3. Validator 작성
1) 사용자 입력값 검사 기능 추가
2) 도메인 검사 기능 추가
검사는 사용자가 입력했을 때와 도메인 내에서 진행할 예정이다.
대신, 최대한 검사가 중복되지 않도록 구현할 예정이다.
사용자 입력값 검사 기능 추가
입력은 3가지 종류로 나눌 수 있다.
1. 빈 문자열 "" → 0 반환
2. 숫자로 시작 → 기본 구분자로 분리 후 계산
3. // \n로 시작 → 기본 구분자+사용자 지정 구분자로 분리 후 계산
2 번은 입력에서도 검증이 가능하지만, 3번의 경우 사용자 지정 구분자 때문에 검증이 어렵다. (구분자 등록하고, 숫자 쪼개야 검증할 수 있으니까)
그래서 입력값에서는 가공하지 않고, 비즈니스 로직을 거치치않은 상태에서 할 수 있는 검사를 하도록 하겠다. 가장 쉽게 접근할 수 있는 것이, 시작과 끝 문자이다. 입력값은 시작과 끝의 형식이 정해져있다.
입력이 비어있지 않을 때,
1. 숫자 또는 //로 시작하는 지 검증
2. 숫자로 끝나는지 검증
위의 두 가지 형식을 만족하지 않으면 예외 처리한다.
1) 검증기 코드 작성
// exception/Validator.java
// 사용자 입력값이 숫자 또는 //로 시작하지 않으면 에러처리
public static void checkFirst(String inputMessage){
Pattern pattern=Pattern.compile("^[0-9]|//");
Matcher matcher=pattern.matcher(inputMessage);
if (!matcher.find()){
throw new IllegalArgumentException(ErrorMessage.INVALID_INPUT_FORMAT.getMessage());
}
}
2) controller에서 검증기 호출
// CalculatorController.java
// 시작 문자 확인
Validator.checkFirst(inputMessage);
(참고로 이 과정에서는 TDD를 일부 적용해 보았다.
➡️ [우테코 1주차] 입력값 검사에 TDD 적용해보기)
도메인 검사 기능 추가
원래 도메인 단에서 숫자인지 검증하려고 Validator에 코드를 구현하려고 했지만,
메서드 파라미터를 숫자 하나, 문자 하나로 받게끔 타입을 지정하여 자동 검증되도록 하였다.
간단한 값 검사의 경우, 이와 같이 메서드 파라미터에서 타입을 지정해두면 알아서 예외 처리가 가능하다. (오옹 😲💡)
💡리팩토링을 거쳐 추가한 내용
위의 내용으로 충분할 줄 알았는데, 양수만 들어온다는 요구사항이 있었다.
이거는 도메인에서 값을 더할 때, 추가 검증 코드를 작성하였다.
// Calculator.java
// 누적 총합 갱신
public void updateTotal(Integer num){
Validator.checkPositiveNumber(num);
total=total+num;
}
2-4. Domain 작성
1) 도메인 객체 클래스 추가
2) 덧셈 계산을 포함한 도메인 메서드 추가
도메인 객체를 정의하고, 그 안에 필요한 상태와 메서드를 선별하도록하겠다.
도메인 객체는 무슨 기준으로 정하나요?🙋🏻♀️
"객체지향의 사실과 오해" 책을 보면 객체는 다른 객체와 구분되는 식별자가 있고, 상태와 행동이 있다고 나와있다.
처음에는 숫자와 구분자로 객체를 만들려고 했었다.
- 숫자 - 상태 : 숫자 리스트 , 행동 : 숫자 더하기
- 구분자 - 상태 : String , 행동 : 구분자 등록, 구분자로 리스트 쪼개기
구분자로 숫자 리스트를 쪼개서 숫자 객체에게 넘겨줘야하는데, 객체의 상태나 행동이 너무 적은데 굳이 나누는게 너무 비효율적이라는 생각이 들었다.

결국 내가 구해야하는 건 숫자들의 총합이고, 이를 수행하는 건 계산기다.
따라서, 계산기를 객체로 결정하였다.
도메인 객체 클래스 추가
계산기는 누적 총합과 구분자를 필드로 갖는다.

- 계산기 객체는 static factory method로 구현하여 외부에서 접근할 수 있게 하였다.
- 특수문자는 따로 처리해줘야하기 때문에 Pattern.quote(",")를 사용하였다.
- or 을 의미하는 "|"로 구분자를 연결하여 저장하였다.
덧셈 계산을 포함한 도메인 메서드 추가
숫자를 더하는 걸 Service에서 할지, domain에서 할지 고민했지만,
위에 말한대로 덧셈 계산은 계산기의 행동이기 때문에 도메인 내에서 로직을 구현하였다.

그외에 누적 총합 갱신과 사용자 지정 구분자를 등록하는 메서드도 추가했다.
한번에 하나의 값을 파라미터로 받아서, 자동으로 값 검증을 하려고 한다.
2-5. Service 작성
1. 사용자 지정 구분자 없는 계산기 구현 추가
2. 사용자 지정 구분자 계산기 구현 추가
서비스에서는 controller에서 넘겨준 두 가지 입력값에 따른 로직을 처리해주려고 한다.
사용자 지정 구분자 없는 계산기 구현 추가
사용자 구분 문자가 없는 경우, 간단하게 숫자만 구분해서 계산하면 된다.
// CalculatorService.java
public class CalculatorService {
Calculator calculator=Calculator.buildCalculator();
public Integer useCalculate(String inputMessage){
List<String> numberList=Arrays.asList(calculator.splitNumber(inputMessage));
try{
for (String num:numberList){
calculator.updateTotal(Integer.parseInt(num));
}
}catch(NumberFormatException e){
throw new IllegalArgumentException(ErrorMessage.NOT_NUMBER_ERROR.getMessage());
}
return calculator.getTotal();
}
}
사용자 지정 구분자 계산기 구현 추가
사용자 지정 구분자가 있는 경우 사용자 지정 구분자 등록하는 과정이 추가로 필요하다.
// CalculatorService.java
public void splitCustomDelimeter(String inputMessage){
//사용자 지정 구분자 등록
for (int i=0;i<inputMessage.length();i+=5){
findCustomDelimeter(inputMessage.substring(i,i+5));
}
}
// 사용자 지정 구분자 호출 (구분자는 한글자 문자로 제한)
public void findCustomDelimeter(String delimeterMessage){
// //\n안에 있는 문자로 updateDelimeter 호출
Pattern pattern=Pattern.compile("//(.)\\\\n");
Matcher matcher=pattern.matcher(delimeterMessage);
if (!matcher.find()){
throw new IllegalArgumentException(ErrorMessage.INVALID_INPUT_FORMAT.getMessage());
}
String customDelimeter=matcher.group(1);
calculator.updateDelimeter(customDelimeter);
}
숫자 계산은 아까 위에서 만들어둔 useCalculate()를 그대로 사용하면 되기 때문에 추가할 코드가 없다.

2-6. 전체 기능
전체적으로 본다면 아래와 같이 기능이 분류되었다.
아래에 나와 있는 기능으로 대부분 깃 커밋이 이루어졌다.

3. 테스트 결과
테스트 결과 모두 통과하였다.

4. 우테코 과제 제출
PR 링크 : https://github.com/woowacourse-precourse/java-calculator-8/pull/321
[문자열 덧셈 계산기] 박지영 미션 제출합니다. by ParkJiYeoung8297 · Pull Request #321 · woowacourse-precourse
java-calculator-precourse 요구사항 문자열에서 숫자를 추출해 합을 구하는 계산기 만들기 동작 사용자에게 입력 요청 메세지를 출력한다. 사용자의 입력을 받는다. ← 입력값 검사 실행 사용자 지정
github.com
소감문 :
이번 주에는 "기능"이라는 키워드를 가지고 코드를 구현했다.
깃 커밋을 기능 단위로 하라고 했는데, 기능이 무엇을 의미하는 걸까?에 대한 궁금증이 생겼다.
3주 전에 우아한 테크 코스 지원을 위해 이 문제를 풀어봤었는데, README를 작성해도 정작 README를 보지 않고 개발했었던 기억이 있다. 그래서 이번에는 내가 생각하는 기능을 먼저 README에 적어두고, 개발 중간에 README를 수정하고 참고하여 개발을 하였다. 결과적으로 기능들이 클래스가 가지고 있는 책임과 매핑되는 거 같은 느낌을 받았다.
왜 그럴까를 알아보기 위해 "객체지향의 사실과 오해" 책을 읽어보았고, 객체가 가지고 있는 책임, 협력, 역할에 대해 공부하였다. 책에서는 클래스는 객체가 자체가 아닌 객체를 구현하기 위한 방법이라고 설명한다. 응집도가 높도록 클래스를 구성하니, 기능이 클래스의 책임과 매핑된 것처럼 보였던 것이다.
추가적으로 3주 전 풀었을 때와 다른 도메인 객체를 만들었다는 점도 큰 깨달음을 주었다. 처음에는 숫자와 구분자를 보고, 숫자 도메인 객체와 구분자 도메인 객체를 만들었는데, 위에 "객체지향의 사실과 오해" 책에서 "객체를 결정하는 것은 행동이다. 데이터는 단지 행동을 따를 뿐이다."라는 말이 깨달음을 주었다. 결국 내가 원하는 건 총합을 구하는 행동이기 때문에 굳이 숫자를 저장할 필요가 없고, 누적 합만 구하면 되는 것이었다. 최종적으로 나는 "계산기"라는 객체에서 누적합과 구분자를 상태로 정의하여 도메인 객체를 생성하였다. 도메인 객체를 잘 생성하니까, 코드 구현이 오히려 더 쉬워졌고, 도메인 객체를 잘 설정하는 것이 중요하다는 걸 체감했다.
README는 협업을 위한 문서화 뿐 아니라, 내가 개발할 때도 도움이 되는 문서였다는 것을 이번 기회로 느꼈다. 기능 단위로 README에 작성함으로써, 미리 클래스 별로 책임을 나누고 어떤 협력을 해야 하는지 대략적으로 그려볼 수 있었다. 물론 모든 것을 미리 설계하고 코드 구현을 시작할 수는 없지만, 간단하게 경계를 구분 지어놓는 것이 개발을 더 빠르고 편하게 해주었다. 클래스 별로 응집도가 높으니, Controller, Service, Domain 등의 계층 간의 책임 경계도 더 명확하게 구별이 되었던 것 같다. 이번 미션을 통해, 기능을 나누는 기준이 객체의 책임을 이해하는 첫걸음이라는 걸 느꼈다.
⬇️ 리팩토링, 트러블 슈팅, 했던 고민까지 포함된 버전을 보고 싶다면 아래의 링크를 추천합니다. (참고로 길이 글이 매우 깁니다 🫠)
[우테코] 프리코스 1주차 계산기 회고 velog
'우테코' 카테고리의 다른 글
| [우테코] 프리코스 2주차 자동차 경주 회고 (0) | 2025.10.27 |
|---|---|
| [우테코 2주차] 개발 과정에서 디버깅 적용해보기 (0) | 2025.10.27 |
| [우테코 2주차] 디버깅으로 입출력 예외 분석하기 (0) | 2025.10.25 |
| [우테코 1주차] 입력값 검사에 TDD 적용해보기 (0) | 2025.10.19 |
| 우테코 지원을 시작으로 (1) | 2025.10.14 |