1, 2주차 미션을 수행하면서 MVC는 프리코스 미션에 대한 설명력이 부족하다는 것을 체감했다. 나는 이번 미션에서 프로그램 내부 구조를 스스로 고안해보기로 했다. 물론 말이 '스스로'지, 외부의 영향을 배제했다고 단언할 수 없다. 결국 어디서 듣고 보고 했던 것들을 짜깁기하는 수준이지만, 나는 모든 요소에 내 나름의 근거를 가지고 처음과 끝 모두 설계했다.
프레임워크
프레임워크라 하면 Spring, Next.js 등 거대한 기술의 집약체를 먼저 떠올리겠지만, 제어 역전(IoC)이라는 넓은 관점에서는 누구나 자신만의 작은 프레임워크를 만들 수 있다. 나에게 프레임워크란 관심사와 최우선 요구사항에 더욱 집중하기 위한 보조 도구일 뿐이다. 프리코스의 세 번째 문제부터는 앞선 두 문제와의 구조적 유사성에 착안하여 util 이라는 작은 패키지 속에 나만의 작업 공간을 만든 셈이다.

AppConfig
내 이야기를 하기에 앞서, 먼저 영감을 받았던 코드를 소개하려 한다. 리뷰를 하다보면 스프링 프레임워크에서 착안한 사고 방식을 흔하게 만나볼 수 있다. 그 중 하나는 보통 AppConfig라는 이름으로 작성되는 IoC 컨테이너이다. 정은님의 코드에서는 모든 핵심 컴포넌트의 인스턴스가 하나의 파일에서 정의된다. 이러한 방식의 강점은 새로운 파일을 작성하는 것으로 손쉽게 의존성 주입(DI)을 수행할 수 있다는 점이다. 생성자를 통해 주입되는 의존성이 변경되면 각 클래스의 동작이 바뀌고, 나아가 애플리케이션의 동작까지 바뀐다. 개방-폐쇄 원칙(OCP)을 준수한 멋진 디자인이다.
이러한 부분은 고민해볼 필요가 있다. 왜냐하면 실제로 변경의 필요가 나타나는 시점에 이러한 요구사항을 준수하기 위해 부랴부랴 구조를 뒤집어 엎는 것은 꽤나 어려운 일이기 때문이다. 따라서, 이러한 특성은 아키텍처의 특성을 갖는다. 즉, 프로젝트를 시작하기 전에 해당 부분의 필요성을 결정하고 들어갈 필요가 있다. 이러한 결정 비용을 on-demand 형태로 지연시킬 수 있는 것은 큰 이점이고, Spring 프레임워크에서 의존성 주입을 지원하는 이러한 필요에 의한 것일수도 있겠다는 생각이 든다.
[로또] 차정은 미션 제출합니다. by jyc0011 · Pull Request #488 · woowacourse-precourse/java-lotto-8
java-lotto-precourse 관련 함수를 묶어 클래스를 만들고, 객체들이 협력하여 하나의 큰 기능을 수행하도록 한다. 클래스와 함수에 대한 단위 테스트를 통해 의도한 대로 정확하게 작동하는 영역을 확
github.com
/util/Global
앞서 정은님이 애플리케이션 전역의 의존성을 모두 작성하셨다면, 나는 필요한 일부분만 작성했다. 아래 작성된 내용들은 프리코스의 앞선 두 문제와의 교집합에 착안하여, domain 진입 이전에 필요한 내용만 모아둔 것이다. 무엇이 바뀌고, 바뀌지 않을지 아는 것은 설계에 있어 굉장히 중요한 요소이다. 프리코스에는 명확한 안내가 없기 때문에 이러한 귀납적 접근은 내 기준에서는 최선이었다고 생각한다.
public interface Global {
Global INSTANCE = new Global() {
};
Console CONSOLE = camp.nextstep.edu.missionutils.Console::readLine;
String ERROR_PREFIX = "[ERROR] ";
Class<? extends RuntimeException> BASE_RUNTIME_EXCEPTION = IllegalArgumentException.class;
ExceptionHandler EXCEPTION_HANDLER = new ExceptionHandler(BASE_RUNTIME_EXCEPTION);
char DEFAULT_DELIMITER = ',';
Tokenizer TOKENIZER = new Tokenizer(DEFAULT_DELIMITER);
default Console console() {
return CONSOLE;
}
default ExceptionHandler exceptionHandler() {
return EXCEPTION_HANDLER;
}
default Tokenizer tokenizer() {
return TOKENIZER;
}
}

프로그램 아키텍처
전체적인 컨셉은 단순하다.
- 문자열 데이터를 입력 받아 적절히 정보를 추출하고,
- 추출된 정보를 도메인에서 굴려 유의미한 자료를 만든 뒤,
- 원하는 방식의 문자열로 가공해서 출력하는 것이다.

예외 처리
모든 예외 처리는 전역 ExceptionHandler를 통해 처리된다. 이는 모든 예외가 동일한 형식으로 관리된다는 가정이 주어진 요구사항을 가장 쉽게 만족시킨다고 판단한 결과이다. 개발자는 각 패키지 내부에 BaseProblem을 구현하는 enum(또는 클래스)을 선언하는 것으로, 예외 상황을 정의하고 관리할 수 있다.
if (number < minInclusive || maxInclusive < number) {
throw LottoProblem.NUMBER_OUT_OF_RANGE.exception();
}
+ 현재의 BaseProblem은 Global을 직접적으로 참고하면서 디미터의 법칙을 위배하고 있다.
따라서, 내부적으로 handler() 메서드를 임시로 추가하면 BaseProblem은 앞으로 이런 느낌이 될 것 같다.
public interface BaseProblem {
String message();
default ExceptionHandler handler() {
return Global.exceptionHandler();
}
default RuntimeException exception() {
return handler().exception(this);
}
default RuntimeException exception(Throwable cause) {
return handler().exception(this, cause);
}
}
출력
가장 이의가 많을 것으로 예상한 부분은 도메인 각 객체에서 출력 문자열을 Presentation에서 정의하는 부분이다. 현재의 구현은 앞으로의 요구사항이 어떻게 달라질지 모른다는 가정 하에서 그냥 코드를 단순 정리한 것에 불과하다. 이는 ViewModel 객체를 사용함으로써 고도화할 수 있다. 가령, 이와 같이 마지막 수익률 통계 계산에 필요한 정보를 제공함으로써 출력에 대한 관심사를 분리할 수 있다. 애플리케이션은 도메인을 최소한으로만 알아야 한다는 원칙이 내게 매우 중요했기에 이러한 결정을 내렸다.
public class ProfitStats {
private final Map<Prize, Integer> prizes;
private final long totalSpent;
private final long totalEarned;
public String prizeStats() {
// TODO implement
}
public double profitRate() {
// TODO implement
}
}
public record ProfitStats(Map<Prize, Integer> prizes, long totalSpent, long totalEarned) {
@Override
public toString() {
// TODO implement
}
}
시나리오와 매니저
도메인의 구현과 절차적인 실행. 나는 이 두 가지야말로 앞서 해결했던 프리코스의 모든 문제들을 포괄하는 핵심이라 보았다. 그래서 다른 부분들은 차후에 변경될 여지가 있겠지만, 적어도 이 구조는 변경될 일이 없을 것이다. 먼저 도메인에서는 패키지 외부에서 접근할 수 있는 인터페이스를 제공해야 하는데, 도메인의 특정한 유즈케이스를 정의하는 것은 Manager의 역할이다. 그 다음, 애플리케이션은 해당 유즈케이스를 소비하기 위한 Scenario를 구현한다. 시나리오에 특별한 것은 없고, 그냥 프로그램의 실행을 절차적으로 표현한 것이다.
+ 매니저는 다소 실험적인 도전을 감행했다. 내부적인 상태 변경을 허용한 것이다. 하지만 그런 것 치고 별다른 성과는 없었다. 왜냐하면 현재의 요구사항이 빈번한 수정을 필요로 하지 않기 때문이다. 그래서 이 부분은 개선이 필요할 것 같다.
마치며...
예비군 동원 훈련을 마치고, 오픈 미션을 하나도 시작하지 못한 상태에서 밀린 리뷰를 완료하고, 3주차 회고를 작성한다.
쓰고 보니 글에 여유가 사라진 것이 느껴진다 ☠️
내 능력을 믿고, 남들 2주 만에 하는 것을 1주 안에 해결할 수 있도록 힘내보자!
'Learn > 우테코' 카테고리의 다른 글
| [프리코스 2주차 회고] 테스트와 리팩터링 (2) | 2025.10.29 |
|---|---|
| [프리코스 1주차 회고] 아 쓰var지 말라고 (2) | 2025.10.23 |
| [프리코스 1주차 회고] 너의 해석은? (3) | 2025.10.21 |