Field note
우테코 문자열 계산기: 입력 경계와 검증 책임을 나눈 과정
문자열 덧셈 계산기에서 커스텀 구분자와 개행을 해석하며 겪은 오류, 당시 구현의 한계와 다음 리팩터링 방향을 정리했다.
이 글의 목차
과제의 입력 규칙
문자열에 포함된 숫자를 구분자로 나누고 합계를 반환하는 계산기를 구현했다.
- 기본 구분자: 쉼표(
,)와 콜론(:) - 커스텀 구분자: 문자열 앞부분의
//구분자\n - 빈 문자열:
0 - 음수나 숫자로 해석할 수 없는 토큰:
IllegalArgumentException
예시는 다음과 같다. 아래 코드 블록에서 \n은 두 글자를 그대로 뜻하는 것이 아니라 구분자 선언과 숫자 본문을 나누는 개행 문자를 표시한다.
입력: 1,2:3
결과: 6
입력:
//;
1;2;3
결과: 6
내가 처음 혼동한 부분은 과제 설명에 적힌 \n 표기와 프로그램이 실제로 받는 개행 문자를 같은 것으로 생각한 점이었다. 이 둘을 구분하지 않으면 커스텀 구분자 선언의 끝을 잘못 찾거나 숫자 본문까지 선언부로 처리한다.
커스텀 구분자를 정규식에서 안전하게 사용하기
기본 구분자는 [,:] 같은 정규식 문자 클래스로 처리할 수 있다. 그러나 커스텀 구분자는 사용자가 입력한 문자이므로 |, ., *처럼 정규식에서 특별한 의미를 가진 문자가 들어올 수 있다.
당시 구현에서는 Pattern.quote()로 커스텀 구분자를 리터럴로 바꾼 뒤 기본 구분자와 합쳤다.
private String[] splitTokens(String input) {
String splitDelimiter = "[,:]";
if (input.startsWith("//")) {
String customDelimiter = input.substring(2, 3);
splitDelimiter += "|" + Pattern.quote(customDelimiter);
input = input.substring(input.indexOf('\n') + 1);
}
return input.split(splitDelimiter);
}
substring(2, 3)은 이 미션에서 커스텀 구분자가 한 글자라는 조건에 의존한다. 여러 글자 구분자를 허용한다면 개행 위치를 먼저 찾고 substring(2, newlineIndex)로 선언부 전체를 읽어야 한다.
실패 사례를 먼저 고정하기
디버깅 중에는 정상 입력만 반복해서 실행했다. 그러면 한 오류를 고친 뒤 다른 입력을 깨뜨려도 늦게 발견한다. 다음과 같이 입력 종류를 먼저 나누고 테스트로 고정하는 편이 필요했다.
| 입력 | 기대 결과 | 확인하려는 경계 |
|---|---|---|
| 빈 문자열 | 0 | 입력 없음 |
1,2:3 | 6 | 기본 구분자 혼합 |
//;\n1;2;3 | 6 | 커스텀 선언과 본문 분리 |
//.\n1.2 | 3 | 정규식 특수문자 이스케이프 |
-1,2 | 예외 | 음수 거부 |
1,a | 예외 | 숫자가 아닌 토큰 거부 |

당시 구현 화면. 커스텀 구분자 해석, 토큰 분리와 숫자 검증이 한 클래스에 모여 있었다.
당시 구조의 한계
기존 글에서는 Validator를 만들며 단일 책임 원칙을 적용했다고 썼다. 하지만 실제 구현을 다시 보면 한 클래스가 다음 작업을 함께 맡고 있었다.
- 커스텀 구분자 선언 형식 검사
- 선언부와 숫자 본문 분리
- 정규식 구분자 생성
- 토큰 분리
- 숫자·음수 검증
이 구조를 단일 책임이라고 부르기는 어렵다. 다시 구현한다면 책임을 다음처럼 나눈다.
InputParser: 선언부와 숫자 본문을 분리하고 구분자 목록을 만든다.TokenValidator: 분리된 토큰이 숫자인지, 음수인지 검사한다.Calculator: 검증된 숫자의 합만 계산한다.
분리 기준은 클래스 수를 늘리는 것이 아니다. 입력 문법이 바뀔 때 계산 로직을 수정하지 않고, 검증 규칙이 바뀔 때 구분자 해석을 건드리지 않는 구조인지가 기준이다.
남은 결론
이 미션에서 가장 큰 문제는 개행 문법 자체보다 입력 해석, 검증과 계산을 한 흐름에서 동시에 해결하려 한 것이었다. 다음 구현에서는 정상 출력보다 먼저 입력 경계와 실패 사례를 테스트로 고정하고, 파싱 결과를 검증과 계산에 넘기는 구조부터 만든다.