✏️ 문제를 풀며..
오랜만에 문제를 풀어서 처음에는 다소 긴장됐지만, 일주일이라는 충분한 기간과 “1주차 문제는 어렵지 않다”는 공지를 보고 금세 긴장이 풀렸다. 문제를 처음 접했을 때는 전체 요구사항을 세부 구현 단위로 나누어 정리한 뒤, 난이도가 높지 않다고 판단되어 바로 구현에 들어갔다.
초반에는 클린 코드보다는 로직 구현 자체에 집중했다. 기능 구현을 마친 뒤 테스트가 정상적으로 통과되는 것을 확인하고, 코드의 구조를 개선하기 위해 리팩토링 단계로 넘어갔다. 특히 Main 메서드에 집중되어 있던 일련의 로직을 기능 단위로 분리하고, 객체지향적인 관점에서 구조를 재설계하는 것이 바람직하다고 판단하였다. 이에 따라 클래스를 재구성하고 책임을 분리하며 코드를 다시 구현했다.
하지만 이 과정은 생각보다 쉽지 않았다. 하나의 메서드에 몰려 있던 로직을 적절히 나누는 것이 단순한 분리 작업이 아니라, 각 클래스의 역할과 책임을 명확히 정의하는 문제였기 때문이다. 좋은 구조를 만들기 위해 여러 방향으로 고민하며 시도와 수정을 반복했다. 특히 “각 클래스의 책임을 어디까지 볼 것인가”에 초점을 맞추었고, 그 결과 처음보다 훨씬 구조적이고 읽기 좋은 코드가 완성되었다. 리팩토링 후 테스트를 다시 돌렸을 때 모든 기능이 정상적으로 작동하는 것을 확인하고 큰 성취감과 함께 만족스러움을 느꼈다.
✏️ 문제 풀이 후 스터디
이후 스터디원들과 코드 리뷰 세션을 진행했다. 각자 깃허브 저장소 주소를 공유하고 서로의 코드를 읽은 뒤, 한 명씩 돌아가며 자신의 코드를 설명하고 질문과 피드백을 주고받는 방식으로 진행했다.
생각보다 훌륭한 코드들이 많았고, 각자의 개성이 뚜렷하게 드러났다. 특히 기억에 남는 것은 구분자 처리 로직을 전략 패턴(Strategy Pattern)으로 구현한 스터디원의 코드였다. 단순한 분기문 대신 객체지향 원칙을 활용해 문제를 푼 방식이 인상적이었고, 그 코드를 보며 “이렇게도 접근할 수 있구나” 하고 감탄했다.
반면 나 자신은 나름대로 코드를 잘 작성했다고 생각했지만, 리뷰를 통해 놓치고 있던 세부적인 부분이 많았다는 사실을 깨달았다. 스터디원들의 피드백을 정리하면 다음과 같다.
- split()의 인자로 정규표현식을 사용할 수 있음
- 숫자가 누락된 경우 예외 처리 필요 (1:2: 또는 1::2 등)
- 숫자 오버플로우 예외 처리
이 피드백을 코드에 반영하면서, 코드의 완성도와 안정성이 크게 향상되었다. 특히 단순히 문제를 해결하는 수준을 넘어, 예외 상황까지 고려한 견고한 코드 설계의 중요성을 몸소 느낄 수 있었다.
각 항목을 조금 더 자세히 살펴보겠다.
1. split() 의 인자로 정규표현식을 사용할 수 있음
처음에는 split()에 단순 문자열만 넣을 수 있는 줄 알고, 모든 구분자를 일일이 통일하는 방식으로 구현했었다.
// 각 구분자를 통일
for (String s : DELIMITER_LIST) {
input = input.replace(s, " ");
}
// 숫자만 추출
String[] numbers = input.split(" ");
하지만 split()은 인자로 정규표현식(Regex) 을 받을 수 있기 때문에, 아래처럼 훨씬 깔끔하게 구현할 수 있었다.
// 구분자들을 정규표현식으로 변환
String delimiterRegex = String.join("|", delimiters);
// 숫자만 추출
String[] parsedNumbers = str.split(delimiterRegex, -1);
두 방식 모두 가능하지만, 정규표현식을 사용하는 쪽이 훨씬 직관적이고 코드의 의도도 잘 드러난다고 생각했다.
2. 숫자가 누락된 경우 예외 처리 (1:2: or 1::2)
내 기존 코드에서는 '1:2': or '1::2'와 같은 입력이 들어오면 원래는 숫자가 누락돼 오류가 발생해야 하지만 예외 없이 정상 출력되는 문제가 있었다.
해결 방법은 의외로 간단했다.
split()의 두 번째 인자에 음수값(-1) 을 넣으면 빈 문자열도 분리 대상이 되기 때문에, 누락된 값을 감지할 수 있다.
String[] parsedNumbers = str.split(delimiterRegex, -1);
limit 값의 의미는 다음과 같다:
| > 0 | 최대 limit - 1번만 분할하고, 나머지는 마지막 요소에 포함 |
| = 0 | 가능한 한 모두 분할하지만, 맨 뒤의 빈 문자열은 제거 |
| < 0 | 가능한 한 모두 분할하고, 빈 문자열도 포함 (제거 안 함) ✅ |
이렇게 분리한 후, 숫자로 변환할 때 빈 문자열이 있으면 예외를 던지도록 처리했다.
private int convertStringToNumber(String number) {
if (number.isEmpty()) {
throw new IllegalArgumentException("빈 피연산자가 포함되어 있습니다.");
}
// 나머지 로직 생략...
}
3. 숫자 오버플로우 예외 처리
숫자 오버플로우는 두 가지 경우로 나눌 수 있다.
(1) 피연산자 자체가 오버플로우인 경우
입력된 숫자가 int 범위를 초과할 수 있으므로, BigInteger를 이용해 미리 검증했다.
private static final BigInteger MAX_NUMBER = BigInteger.valueOf(Integer.MAX_VALUE);
private void validateNumberOverflow(String number) {
BigInteger bi = new BigInteger(number);
if (bi.compareTo(MAX_NUMBER) > 0) {
throw new IllegalArgumentException("입력된 숫자가 너무 큽니다.");
}
}
물론 Integer.parseInt() 시에도 NumberFormatException이 발생하지만,
현재 코드에서는 다른 예외와 구분해서 처리하고 싶었기 때문에 별도로 검증 로직을 추가했다.
catch (NumberFormatException e) {
throw new IllegalArgumentException("입력값이 잘못됐거나 지정된 구분자 외에는 사용할 수 없습니다.");
}
(2) 계산 과정에서 오버플로우가 발생하는 경우
단순히 total += number로 더하면 오버플로우가 발생해도 예외가 나지 않고 음수로 순환된다.
이를 방지하기 위해 Math.addExact()를 사용했다.
int total = 0;
for (Integer number : numbers) {
try {
total = Math.addExact(total, number);
} catch (ArithmeticException e) {
throw new IllegalArgumentException("계산결과가 너무 큽니다.");
}
}
Math.addExact()는 내부적으로 오버플로우를 감지해 ArithmeticException을 던지기 때문에,
보다 안전하게 덧셈을 수행할 수 있다.
✏️ 제출 후 코드리뷰
제출 후 조금 더 내 코드에 대해 다양한 사람들의 의견을 듣고 다양한 사람들의 코드를 접하고 싶어서 스터디 외적으로 추가적인 코드리뷰를 진행했다.
기대했던대로 스터디때에는 받지 못했던 추가적인 피드백들을 많이 받았다. 칭찬도 있었고 부족함에 대한 지적도 있었다.
우선 칭찬은 대체적으로 코드에 대한 꼼꼼함에 대한 부분이 많았다. 즉, 예외를 다방면으로 생각하고 적절하게 잘처리했다는 것이었다.
반면 클래스 구조에 대한 부족함을 많이 지적을 받았다. 나름 잘 짰다고 생각했는데 놓치고 있던 것들이 있었다. 대표적인 몇개만 뽑자면
- 입출력 클래스를 분리해라.
- StringSumCalculator의 calculate() 메서드가 너무 하는 역할이 많다.
나는 이를 해결하기 위해 MVC패턴을 도입하기로 했다. 앞단에 Controller를 두고 해당 컨트롤러에서 입력을 View로 받고 문자열 덧셈 계산기 관련된 로직을 Model로 분리해서 처리한뒤 출력을 View로 마무리 했다.
또한 ‘파사드 패턴(Facade Pattern)’을 도입하여 StringSumCalculator의 책임을 분리하고 앞단에 파사드를 두어 매개체를 제공하였다.
[참고자료]
💠 퍼사드(Facade) 패턴 - 완벽 마스터하기
Facade Pattern 퍼사드 패턴(Facade Pattern)은 사용하기 복잡한 클래스 라이브러리에 대해 사용하기 편하게 간편한 인터페이스(API)를 구성하기 위한 구조 패턴 이다. 예를들어 라이브러리의 각 클래스
inpa.tistory.com
구조는 다음과 같다.

| 클래스 | 설명 |
| StringSumCalculatorController | View로 부터 입력을 받아서 비즈니스 로직을 호출하고 결과를 View로 출력한다. |
| InputView | 입력을 받는다. |
| OutputView | 결과를 출력한다. |
| StringSumCalculatorFacade | 문자열 덧셈 계산기에 대한 파사드 클래스. 내부적으로 필요한 서브시스템을 호출해 문자열 덧셈 계산기에 대한 일련의 과정을 처리한다. |
| DelimiterParserService | 구분자를 추출하는 클래스 |
| CalculatorService | 계산을 담당하는 클래스 |
| NumberParserService | 숫자를 추출하는 클래스 |
최종 리포지토리
GitHub - jhk01007/java-calculator-8: 우테코 프리코스 1주차 과제
우테코 프리코스 1주차 과제. Contribute to jhk01007/java-calculator-8 development by creating an account on GitHub.
github.com
한결 더 완성된 코드가 된 것 같아 뿌듯하다.
✏️ 1주차를 마치며
원래는 혼자 공부하는 걸 좋아하는 편이지만, 이번 코드 리뷰 스터디를 통해 다른 사람의 코드를 보며 배우는 것의 가치를 새삼 깨달았다. 같은 문제라도 접근 방식, 코드 구조, 예외 처리 로직이 모두 달랐고, 그 차이를 비교하고 토론하는 과정에서 새로운 시각을 많이 얻었다.
특히 내 코드에 대한 피드백을 듣는 과정이 큰 자극이 되었다. ‘내가 짠 코드가 완벽하지 않을 수 있다’는 것을 받아들이고, 다른 사람의 아이디어를 흡수해 더 나은 구조를 고민하게 되었다. 그 덕분에 단순히 코딩 실력뿐 아니라, 코드에 대한 사고방식과 문제 접근 태도 자체가 성장했다는 것을 느꼈다.
이번 경험을 통해 협업의 중요성과 코드 리뷰의 효과를 체감했고, 앞으로도 이런 형태의 스터디를 꾸준히 이어가며 다양한 시각을 배우고 서로의 성장을 도울 수 있는 개발자가 되고 싶다.
'우테코 8기 > 프리코스 회고' 카테고리의 다른 글
| [8기 프리코스 최종 회고] 코딩테스트 및 합격 후기 (0) | 2026.01.23 |
|---|---|
| [프리코스] 3주차 회고 (0) | 2025.11.04 |
| [프리코스] 2주차 회고 (0) | 2025.10.28 |