본문으로 건너뛰기

Field note

우테코 문자열 계산기: 입력 경계와 검증 책임을 나눈 과정

문자열 덧셈 계산기에서 커스텀 구분자와 개행을 해석하며 겪은 오류, 당시 구현의 한계와 다음 리팩터링 방향을 정리했다.

2분 읽기Seongrok Lee
이 글의 목차

과제의 입력 규칙

문자열에 포함된 숫자를 구분자로 나누고 합계를 반환하는 계산기를 구현했다.

  • 기본 구분자: 쉼표(,)와 콜론(:)
  • 커스텀 구분자: 문자열 앞부분의 //구분자\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:36기본 구분자 혼합
//;\n1;2;36커스텀 선언과 본문 분리
//.\n1.23정규식 특수문자 이스케이프
-1,2예외음수 거부
1,a예외숫자가 아닌 토큰 거부
커스텀 구분자와 숫자 검증을 디버깅하던 당시 코드

당시 구현 화면. 커스텀 구분자 해석, 토큰 분리와 숫자 검증이 한 클래스에 모여 있었다.

당시 구조의 한계

기존 글에서는 Validator를 만들며 단일 책임 원칙을 적용했다고 썼다. 하지만 실제 구현을 다시 보면 한 클래스가 다음 작업을 함께 맡고 있었다.

  1. 커스텀 구분자 선언 형식 검사
  2. 선언부와 숫자 본문 분리
  3. 정규식 구분자 생성
  4. 토큰 분리
  5. 숫자·음수 검증

이 구조를 단일 책임이라고 부르기는 어렵다. 다시 구현한다면 책임을 다음처럼 나눈다.

  • InputParser: 선언부와 숫자 본문을 분리하고 구분자 목록을 만든다.
  • TokenValidator: 분리된 토큰이 숫자인지, 음수인지 검사한다.
  • Calculator: 검증된 숫자의 합만 계산한다.

분리 기준은 클래스 수를 늘리는 것이 아니다. 입력 문법이 바뀔 때 계산 로직을 수정하지 않고, 검증 규칙이 바뀔 때 구분자 해석을 건드리지 않는 구조인지가 기준이다.

남은 결론

이 미션에서 가장 큰 문제는 개행 문법 자체보다 입력 해석, 검증과 계산을 한 흐름에서 동시에 해결하려 한 것이었다. 다음 구현에서는 정상 출력보다 먼저 입력 경계와 실패 사례를 테스트로 고정하고, 파싱 결과를 검증과 계산에 넘기는 구조부터 만든다.