들어가며
2주 차 미션 시작에 앞서!!
1주 차 미션(문자열 덧셈 계산기) 코드리뷰를 하며 얻은 리뷰 및 인사이트를
이번 미션에 반영해보고 싶어 피드백을 정리해보았다.
이번 리뷰에서는 도메인의 기준과 책임 분리에 대해 많은 인사이트를 얻었다.
리뷰어분들이 남겨주신 피드백을 통해 내가 추상적으로 갖고 있던 도메인 기준을 재정의할 수 있었고, 책임 분리를 더 세세하게 할 수 있다는 점을 배웠다.
나만의 언어로 다시 정리하면서, 피드백을 소화해보자!!
[1주 차 미션 링크]
https://github.com/woowacourse-precourse/java-calculator-8/pull/544
[문자열 덧셈 계산기] 임지현 미션 제출합니다. by Jiihyun · Pull Request #544 · woowacourse-precourse/java-cal
▶️️ 흐름 구분자 & 양수로 된 문자열을 입력한다. 구분자를 기준으로 분리한 숫자들을 모두 더한다. 덧셈 결과를 반환한다. 📝️ 기능 목록 ▪️ 입력 기능 구분자와 양수로 구성된 문자열
github.com
1. 불변 컬렉션 복사 메서드를 활용하자

리스트를 그대로 다른 변수에 대입할 경우,
단순히 참조값만 복사되기 때문에 원본 데이터가 함께 변경되는 문제가 발생할 수 있다.
예를 들어, 기존 리스트를 복사한 뒤 새 데이터를 추가했는데
원본 리스트까지 같이 바뀌는 현상이 일어날 수 있다.
List<Integer> original = new ArrayList<>(List.of(1, 2, 3));
List<Integer> copy = original; // 단순 참조 복사
copy.add(4);
System.out.println(original); // [1, 2, 3, 4] -> 원본까지 변경됨!
copyOf()는 불변(immutable) 리스트를 새로 생성하기 때문에
복사본을 수정하더라도 원본 리스트에는 영향을 주지 않는다.
List<Integer> original = new ArrayList<>(List.of(1, 2, 3));
List<Integer> copy = List.copyOf(original);
copy.add(4); // UnsupportedOperationException 발생
▪️ copyOf() 사용 효과
- 원본 리스트의 불필요한 변경을 막을 수 있음
- 안전한 복사를 통해 코드의 안정성과 예측 가능성을 높임
2. 도메인 로직에 대한 기준을 명확히 하자


미션을 진행하면서 정적 멤버 객체(static 클래스) 사용을 고려한 적이 있었다.
⬇️ 나의 정적 멤버 객체 사용 기준
1. 비즈니스 규칙과 직접적인 연관이 없는 공통 기능 클래스
2. 인스턴스 변수가 없어 상태를 지니지 않음
3. 두 개 이상의 도메인에서 공통적으로 사용됨
객체가 상태를 가지지 않고 인스턴스 변수가 없었기에 “굳이 매번 인스턴스를 생성할 필요가 있을까?” 하는 생각이 들었다.
하지만 DelimiterExtracter 클래스는 “구분자는 숫자를 포함할 수 없다” 와 같은 비즈니스 규칙이 포함되어 있었다. (구분자 추출 전에 검증로직을 포함 시켜 버린 것,,,)
이런 규칙은 나중에 변경될 가능성이 있기 때문에,
정적 클래스로 고정시켜 버리면 변경에 유연하게 대응하기 어려워질 위험(OCP 위반)이 있다고 판단했다.
그래서 결국 인스턴스화된 객체로 관리하는 방식을 선택했다.
이후 코드 리뷰를 받으면서,
내가 도메인 로직을 너무 추상적으로 생각하고 있었다는 점을 깨달았다.
도메인과 이를 보조하는 유틸성 로직을 명확히 구분하지 못하다 보니,
사실상 유틸로 분리할 수 있는 코드까지 도메인 안에 섞여 있었다.
이 점을 개선시키기 위해, 도메인 로직을 판단하는 명확한 기준을 세워보았다.
"이 로직이 바뀌기 위해서는 기획자 등 다른 분야와 조율이 필요한가?"
만약 그렇다면, 이는 프로그램의 핵심 비즈니스 규칙과 개념을 표현하는 영역
즉, 도메인 로직에 해당한다.
따라서 validate() 메서드처럼 “커스텀 구분자로 빈문자열 비허용” 이라는 비즈니스 규칙을 별도의 객체로 분리한다면,
DelimiterExtractor는 단순히 “문자열을 파싱하는 기능”만 담당하게 되어
유틸 클래스로 분리할 수 있게 된다!
=> 파싱과 검증의 관심사가 분리되어 코드 명확성 증가 & 비즈니스 규칙 변경 시 파싱 코드 수정 불필요
▪️ 도메인 판단 기준
- 이 로직이 시스템의 핵심 규칙(비즈니스 규칙)을 표현하고 있는가?
- 해당 로직이 바뀌기 위해서는 기획자 등 다른 분야와 조율이 필요한가?
도메인에 대해 고민하고 있을 때,
커뮤니티 함께-나누기 채널에 공유해주신 블로그 글을 보고 많은 도움을 얻었다!
이 글을 읽는 분께도 도움이 될 수 있으니 남겨놓아야지ㅎ
https://velog.io/@ahhpc2012/%EB%8F%84%EB%A9%94%EC%9D%B8-%EB%A1%9C%EC%A7%81%EC%9D%B4-%EB%AD%94%EB%8D%B0-feat.-MVC-%ED%8C%A8%ED%84%B4
도메인 로직이 뭔데? (feat. MVC 패턴)
우테코 프리코스 1주차 미션에서 MVC 패턴을 적용해보며 “’도메인 로직’이 정확히 무엇일까?”라는 의문이 들어 공부하고 이해한 내용을 정리해봤습니다.
velog.io
3. 예외 메세지 하드 코딩 하지 말자

기능에 대한 예외처리를 할 때 예외 메세지를 throw new IllegalArgumentException("[ERROR] 숫자는 양수만 가능합니다."); 이렇게 직접 입력했다.
그 결과, 사진과 같이 테스트에서도 중복이 발생했다..!
만약 예외 메세지가 수정되면 테스트가 실패하게 되니, 테스트 코드의 메세지 또한 수정해줘야 한다.
또한, 메시지 오타나 일관성 문제도 발생하기 쉽다.
이에 대한 해결법으로 ENUM을 활용하려 한다!
리팩토링 해보자면 아래와 같다.
public enum ExceptionMessage {
WRONG_INPUT("잘못된 값을 입력하셨습니다."),
NON_POSITIVE_NUMBER("숫자는 양수만 가능합니다.");
private static final String EXCEPTION_PREFIX = "[ERROR] ";
final String message;
CalculatorExceptionMessage(String message) {
this.message = EXCEPTION_PREFIX + message;
}
public String getMessage() {
return message;
}
}
이렇게 되면 예외를 던질 때 직접 입력하지 않고, 코드로 활용할 수 있다.
assertThatThrownBy(() -> delimiterExtractor.extract(expression))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage(ExceptionMessage.NON_POSITIVE_NUMBER.getMessage());
▪️ ENUM 사용 효과
- 모든 예외 메시지를 중앙에서 일관되게 관리 가능
- 메시지가 변경되더라도 테스트 코드 수정 최소화
- 오타가 방지되므로 컴파일 타임에 안전성(type safety) 보장
4. 입력 리소스를 사용한 후에는 닫도록 하자

Console은 내부적으로 시스템 리소스(InputStream)를 점유한다.
따라서 이를 닫지 않으면 OS 레벨에서 파일 핸들이나 스트림이 해제되지 않아서, 장시간 실행 시 리소스가 점점 쌓이게 된다.
-> 리소스 누수(Resource Leak) 발생!!
물론 현재는 프로그램 종료 시 JVM이 리소스를 정리하기 때문에 Console을 명시적으로 닫지 않아도 동작에는 문제가 없다.
다만, 명시적으로 닫는 습관을 두면 리소스 관리가 명확해지고, 장시간 실행되는 환경에서도 안전하다고 한다.
따라서 앞으로는 아래 코드처럼 적용해 볼 수 있을 것 같다!
public class Application {
public static void main(String[] args) {
try {
CalculatorController calculatorController = new CalculatorController(
new InputView(), new OutputView(),
new CalculatorFacade(new DelimiterExtractor(), new ExpressionParser(), new NumberExtractor()));
calculatorController.run();
} finally {
Console.close();
}
}
}
▪️ close() 사용 효과
- 자원 누수를 막을 수 있음
마무리
다른 분들 코드도 리뷰하고 내 리뷰 피드백 정리하니 어느새 미션 3일차가 되어버린 거 실화인가요? 덜덜덜 실화입니다,,,,,
내 기준 완벽한 코드를 제출했다 생각했지만, 다양한 리뷰를 통해 많은 것을 배울 수 있었다.
리뷰 없이 혼자서는 깨닫지 못했을 것들을 많이 얻은 것 같다.
다음 미션에서는 이번에 배운 점들을 반영해
더 명확한 도메인 기준, 더 분리된 책임, 더 읽기 좋은 코드를 만들어보자 💪
'Reflection' 카테고리의 다른 글
| 우아한테크코스 프리코스 - 3주 차 회고 (0) | 2025.11.04 |
|---|---|
| 우아한테크코스 프리코스 - 2주 차 회고 (0) | 2025.10.28 |
| 우아한테크코스 프리코스 - 1주 차 회고 (0) | 2025.10.21 |
| 우아한테크코스 재도전에 앞서 (2) | 2025.10.13 |
| DB 스터디 회고 (1) | 2025.02.04 |