JSON DTO Converter (1) - CLI 분석
"ArgumentParser, CommandLineOption, FileValidator, ParsedArguments"를 깊이 있게 파헤치기
이 글은 JSON DTO Converter 프로젝트의 CLI 계층 전체를 상세하게 분석하는 글이다.
프로그램이 실행되기 위해서는
CLI입력 → 인자 파싱 → 값 검증 → 환경 DTO(ParsedArguments) 생성
까지의 과정이 정확히 수행되어야 한다.
실제로 이 단계는 “도구 전체의 입구(Entry Point)”이기 때문에
여기서 제대로 설계하지 않으면 전체 동작이 망가질 수 있다.
1. CLI 계층의 역할
CLI 계층은 다음 4가지 책임만 가진다.
✔ 1) 명령줄 옵션을 정의하고
✔ 2) 사용자가 입력한 인자를 파싱하고
✔ 3) 검증(필수 옵션 체크 / 타입 체크 / 경로 체크)하고
✔ 4) 결과를 불변 DTO로 제공한다(ParsedArguments)
즉,
"사용자가 CLI로 전달한 값을,
프로그램이 이해할 수 있는 형태의 설정(config)으로 변환하는 계층"
이라 할 수 있다.
이 역할 단 하나만 수행하도록 구조화한 것이 핵심이다.
2. CLI 입력 옵션 전체 정리
프로그램이 받아들일 수 있는 옵션은 단 5개이다:
| 옵션 | 필수 | 설명 |
| --input | ✔ | JSON 파일 경로 |
| --root-class | ✔ | 루트 DTO 클래스 이름 |
| --package | ✔ | 생성 DTO의 패키지 이름 |
| --out | ✔ | .java 파일 저장 디렉터리 |
| --inner-classes | ✖ | true/false (기본값 false) |
❗ 중요한 설계 원칙:
옵션과 값 사이에는 반드시 공백이 있어야 한다.
--input sample.json ← ✔
--input=sample.json ← ✖ 금지
이 규칙은 ArgumentParser가 정확하고 단순하게 동작하도록 설계된 중요한 제약이다.
3. CommandLineOption - CLI 옵션을 Enum으로 정의한 이유
많은 CLI 도구가 문자열 비교로 옵션을 처리하지만
이 프로젝트에서는 Enum으로 옵션을 정의했다.
예:
public enum CommandLineOption {
INPUT("--input", true),
ROOT_CLASS("--root-class", true),
PACKAGE("--package", true),
OUT("--out", true),
INNER_CLASSES("--inner-classes", false);
}
왜 Enum인가?
(1) 오타 방지
문자열 기반 파싱은 "--input" 과 "--inptu" 을 구분하지 못해
에러가 발생하면 파악이 어렵다.
Enum을 사용하면 IDE의 자동완성 + 컴파일러 체크까지 받는다.
(2) 필수 옵션(required) 여부 명시
각 옵션이 필수인지 아닌지를 Enum 안에 저장한다.
따라서 ArgumentParser는 다음과 같이 깔끔하게 필수 옵션을 체크할 수 있다.
for (CommandLineOption opt : CommandLineOption.values()) {
if (opt.required() && !parsed.containsKey(opt)) {
throw new UserException("[ERROR] " + opt.name() + "은 필수입니다.");
}
}
(3) 구조적 확장 가능
옵션을 더 늘리고 싶어도 Enum에 한 줄 추가하면 끝!
4. ArgumentParser - CLI 인자 파싱 핵심 로직
ArgumentParser는 다음 5단계를 수행한다:
4-1. 옵션 이름(--something) 인식
CLI 입력의 예:
--input sample.json --root-class ApiRes --package com.test --out src --inner-classes true
ArgumentParser는 편의를 위해 다음 패턴을 강제한다:
--옵션명 값
즉
--옵션=값 과 같이 '=' 이 들어간 형태는 허용하지 않는다.
4-2. 옵션-값 쌍 수집
파서가 수행하는 기본 동작:
- 토큰을 순서대로 읽는다
- "--" 로 시작하면 옵션
- 다음 토큰을 “값”으로 간주
- Map<CommandLineOption, String> 형태로 저장
- 옵션인지 모르면 UserException 발생
이 구조는 단순하지만 명확하여 오류를 줄인다.
4-3. 필수 옵션 누락 오류 검사
ArgumentParser는 Enum에 저장된 required 플래그를 보고
필수 옵션이 제공되지 않았다면 UserException을 즉시 발생시킨다.
예:
- root-class 누락
- package 누락
- input 누락
- out 누락
전부 “사용자가 수정할 수 있는 오류”이므로 UserException이 맞다.
4-4. boolean 옵션( --inner-classes )의 처리
--inner-classes 는 true/false만 허용한다.
--inner-classes true ← OK
--inner-classes false ← OK
--inner-classes maybe ← ERROR
boolean 해석은 ArgumentParser에서 책임지도록 설계했다.
파일 저장과는 관련 없는 로직이므로, generator 계층에 영향을 주지 않게 하기 위함이다.
5. FileValidator — 출력 디렉터리 검증 & 생성
FileValidator는 다음만 책임진다:
- 주어진 경로가 디렉터리인지
- 존재하지 않으면 생성
- 쓰기 권한이 있는지 검증
❗ 왜 이 로직이 CLI 계층에 있어야 하는가?
- 출력 디렉터리가 유효하지 않으면
나머지 파이프라인 전체가 작동할 수 없기 때문
(경로 없이 파일 생성 불가능) - JSON 파싱 계층(json package)이나 코드 생성(generator package)은
"파일 시스템"이라는 개념을 몰라야 한다.
SRP를 지키기 위해 IO 검증은 CLI 계층에서 종결한다.
6. ParsedArguments — 불변(immutable) 환경 설정 객체
ArgumentParser가 최종적으로 반환하는 객체는 ParsedArguments 다.
예:
public final class ParsedArguments {
private final String inputPath;
private final String rootClass;
private final String packageName;
private final String outDir;
private final boolean innerClasses;
}
왜 Immutable인가?
- 설정값은 프로그램 실행 동안 바뀌어서는 안 된다
- thread-safe
- 이후 계층에서 실수로 값을 수정할 여지가 없다
- "환경의 스냅샷" 역할 수행 가능
즉, CLI 분석이 끝난 시점에서
"사용자가 원하는 설정이 무엇이었는가?"를 고정시키는 객체다.
7. BOM(Byte Order Mark) — 개념 설명
CLI 글에서 BOM 설명을 요청한 만큼, 개념을 쉽게 정리해보자.
7-1. BOM이란?
UTF-8 BOM(Byte Order Mark)은
텍스트 파일의 맨 앞에 붙는 3바이트 시퀀스다.
EF BB BF
일부 Windows 기반 에디터(Notepad 등)는
UTF-8 파일을 저장할 때 BOM을 붙이는 경우가 있다.
7-2. BOM이 있으면 어떤 문제가 생기나?
JSON은 다음과 같이 시작해야 한다:
{ "key": "value" }
하지만 BOM이 있으면 실제 파일의 맨 앞은 다음과 같다:
{ "key": "value" }
사람 눈에는 보이지 않지만,
파서 입장에서는 쓰레기 바이트가 들어온 것이다.
Jackson은 BOM 처리를 자동으로 지원하기도 하지만,
이 프로젝트에서는 명시적으로
- BOM 여부 확인
- 필요 시 제거
를 구현했다.
7-3. 왜 CLI 분석 글에서 BOM 설명을 하나?
이유는 간단하다:
- BOM은 사용자 입력의 파일 형식 문제이기 때문
- JsonValidator는 CLI 입력( --input )을 처리하는 과정의 연속선상에서 동작
- 따라서 CLI와 밀접한 상위 개념
그래서 Overview나 JSON 분석 글보다
CLI 글에서 다루는 것이 가장 자연스럽다고 판단하였다.
8. CLI 단계에서의 오류 처리 전략
ArgumentParser / FileValidator는 사용자가 잘못 입력한 경우
전부 UserException 을 발생시킨다.
예:
- 필수 옵션 누락
- 존재하지 않는 파일
- 경로가 파일이 아니라 디렉터리
- boolean 옵션 잘못된 값
- 출력 디렉터리 권한 없음
이 예외들은 모두 "사용자가 수정할 수 있는 문제"이므로
friendly error message가 핵심 철학이다.
예시 출력:
[ERROR] --input은 필수입니다.
[ERROR] --inner-classes 옵션은 true 또는 false만 허용합니다: maybe
9. 최고로 중요한 포인트 - CLI 계층은 전체 파이프라인의 안전장치다
정리하면 CLI 계층의 목적은 다음이다:
✔ 1) 입력이 “제대로 된 JSON 파일인지”
(파일 존재 / 디렉터리 금지 / 크기 제한 / 확장자 / BOM 등)
✔ 2) 옵션이 “정확한 형태인지”
( --option value 형식 고정)
✔ 3) 필수 입력이 모두 들어왔는지
✔ 3) 필수 입력이 모두 들어왔는지
✔ 4) Boolean 옵션을 올바르게 파싱했는지
✔ 5) 출력 디렉터리가 “파일 생성 가능한 상태인지”
(없으면 생성, 권한 체크)
이 단계가 제대로 처리되고 나면,
다음 단계(JSON 분석 계층)부터는 오직 "JSON 구조 분석과 타입 추론"에만 집중하면 된다.
즉, CLI 계층은 전체 프로그램의 안정성과 예측 가능성을 보장하는 초석이다.
이어지는 글에서는 입력 JSON을 검증하고 정제하는
JsonValidator와 JSON 파싱 로직을 다룬다