- 1 1. Introduction
- 2 2. How to Receive Command-Line Arguments in Java
- 3 3. 데이터 변환 및 오류 처리
- 4 4. 옵션 스타일 인수 처리
- 5 5. IDE에서 명령줄 인자 설정 및 테스트
- 6 6. 오류 처리 및 보안 설계
- 7 7. 실용적 예시 — 파일 처리, 모드 전환 및 로깅 제어
- 8 8. 실제 배포에서의 모범 사례
- 9 9. 요약 및 설계 템플릿
- 10 FAQ — Frequently Asked Questions
- 10.1 Q1. 선택적 및 필수 인수를 모두 처리하려면 어떻게 해야 하나요?
- 10.2 Q2. 인수에 공백을 포함하려면 (파일 이름이나 구문처럼) 어떻게 해야 하나요?
- 10.3 Q3. 인수가 전혀 제공되지 않으면 어떻게 되나요?
- 10.4 Q4. IntelliJ나 Eclipse와 같은 IDE에서 인수를 테스트하려면 어떻게 해야 하나요?
- 10.5 Q5. “–debug” 또는 “–verbose”와 같은 불리언 플래그를 어떻게 처리하나요?
- 10.6 Q6. “java -jar” 애플리케이션에 여러 인수를 전달하려면 어떻게 해야 하나요?
- 10.7 Q7. 명령줄 대신 설정 파일에서 인수를 읽으려면 어떻게 해야 하나요?
- 10.8 Q8. 인수에 유니코드 또는 비ASCII 문자를 사용할 수 있나요?
- 10.9 Q9. 사용자 제공 인수로 인한 보안 문제를 어떻게 방지할 수 있나요?
- 10.10 Q10. 여전히 “args”를 수동으로 사용해야 할까요, 아니면 라이브러리를 도입해야 할까요?
- 10.11 Q11. 모든 받은 인수를 쉽게 출력하려면 어떻게 하나요?
- 10.12 Q12. 인수와 환경 변수를 섞어 사용할 수 있나요?
- 10.13 Q13. 잘못된 인수 유형을 우아하게 처리하려면 어떻게 하나요?
- 10.14 Q14. 도움말이나 오류에 색상이 있는 출력을 표시할 수 있나요?
- 10.15 Q15. 인수 파싱을 더 효율적으로 디버깅하려면 어떻게 하나요?
1. Introduction
Purpose of This Chapter
Java에서 명령줄 인수는 프로그램이 실행 시 외부 입력을 받아 동작을 변경할 수 있게 하는 기본적인 기능입니다. 이 글에서는 String[] args의 의미부터 실용적인 설계 패턴까지 단계별로 안내합니다. 이 장에서는 먼저 무엇을 할 수 있는지와 왜 중요한지를 명확히 합니다.
What Are Command-Line Arguments?
Java 애플리케이션은 일반적으로 다음 시그니처를 가진 main 메서드로 시작합니다:
public class App {
public static void main(String[] args) {
// args is an array of strings passed at runtime
}
}
args 매개변수는 String 배열이며, 시작 명령에 붙은 값을 저장합니다. 예를 들어:
javac App.java
java App Tokyo 2025 debug
이 경우 args는 ["Tokyo", "2025", "debug"] 를 포함합니다.
인수가 제공되지 않으면 args.length는 0 입니다.
Use Cases
- 환경 또는 대상 전환 — 예: 프로덕션/테스트 모드, 지역 코드, 언어, 로그 레벨 등.
- 외부에서 처리 대상 지정 — 파일명, 디렉터리, URL, ID 목록 등.
- 자동화 및 배치 처리 — 크론 작업이나 CI/CD 파이프라인에서 날짜 범위와 같은 매개변수를 전달.
이 모든 경우가 재컴파일 없이 동작을 바꿀 수 있게 하여, cron 같은 셸 스크립트 및 작업 스케줄러와의 통합에 명령줄 인수가 이상적입니다.
Key Design Considerations
- 필수 인수와 선택 인수 구분 — 필수 인수가 누락되면 도움말을 표시하거나 적절한 상태 코드로 종료합니다.
- 조기 검증 — 가능한 한 빨리 숫자나 날짜 타입으로 변환하고, 잘못된 입력에 대해 명확한 메시지를 제공합니다.
- 기본값 설계 — 선택 인수가 생략되더라도 프로그램이 실행될 수 있도록 안전한 기본값을 제공합니다.
- 가독성 및 유지보수성 유지 — 원시 배열 접근을 여기저기 흩어놓지 말고, 사용 전에 인수를 구조화된 객체(DTO 또는 설정 클래스)로 파싱합니다.
Choosing Between Config Files and Environment Variables
- 명령줄 인수: 일시적인 오버라이드나 작업별 스위치에 가장 적합(가장 높은 우선순위 로컬 설정으로 간주).
- 환경 변수: 비밀 정보나 엔드포인트와 같은 환경 의존 설정에 적합.
- 구성 파일 (properties/JSON/YAML): 여러 항목을 체계적으로 관리하고 재사용 및 버전 관리를 할 때 이상적.
실제로는 구성 파일 + 환경 변수 + 인수를 모두 결합하고, 인수가 설정을 오버라이드하는 가장 높은 우선순위를 갖도록 하는 것이 일반적입니다.
Minimal Example (Listing All Arguments)
public class ArgsEcho {
public static void main(String[] args) {
if (args.length == 0) {
System.out.println("No arguments were provided.");
System.out.println("Example: java ArgsEcho input.txt debug");
return;
}
System.out.println("Received arguments:");
for (int i = 0; i < args.length; i++) {
System.out.printf("args[%d] = %s%n", i, args[i]);
}
}
}
What’s Next (Roadmap)
- 기본 연산 —
String[] args의 길이 확인, 요소 접근 - 타입 변환 — int/double/boolean 처리 및 예외 안전성
- 옵션 스타일 파싱 — 예:
-v,--help,--mode=prod - IDE 설정 및 테스트 시 인수 전달
- 오류 처리 및 보안 고려사항 — 잘못된 입력, 예외
- 실용 예제 — 파일 처리, 모드 전환, 로그 제어
먼저 기억할 원칙: 모든 인수는 문자열로 받아들여지며, 사용 전에 안전하게 변환하고 검증해야 합니다. 다음 장에서는 구문과 일반 패턴을 상세 코드 예제와 함께 설명합니다.
2. How to Receive Command-Line Arguments in Java
Basic Structure
Java에서 명령줄 인수는 main 메서드에 전달되는 문자열 배열(String[] args)로 처리됩니다. 실행 명령에서 클래스 이름 뒤에 입력된 공백으로 구분된 각 토큰은 배열의 요소가 됩니다.
public class Example {
public static void main(String[] args) {
System.out.println("Number of arguments: " + args.length);
for (String arg : args) {
System.out.println(arg);
}
}
}
프로그램을 다음과 같이 실행하면:
javac Example.java
java Example apple orange banana
출력은 다음과 같습니다:
Number of arguments: 3
apple
orange
banana
특정 인수 접근
각 요소는 0부터 시작하는 인덱스로 접근할 수 있습니다. 그러나 ArrayIndexOutOfBoundsException을 방지하기 위해 항상 args.length를 확인하세요.
public class AccessExample {
public static void main(String[] args) {
if (args.length < 2) {
System.out.println("Usage: java AccessExample <name> <age>");
return;
}
String name = args[0];
String age = args[1];
System.out.println("Name: " + name);
System.out.println("Age: " + age);
}
}
java AccessExample Alice 30 으로 실행했을 때 결과는:
Name: Alice
Age: 30
누락된 인수를 안전하게 처리하기
args의 모든 값은 문자열이므로 누락되었거나, 형식이 잘못되었거나, 기대하는 타입으로 변환할 수 없을 수 있습니다. 사용하기 전에 검증하는 것이 좋은 습관입니다.
if (args.length == 0) {
System.out.println("No arguments provided. Please specify input parameters.");
System.exit(1); // Exit with an error code
}
System.exit(int)를 사용하여 종료 상태를 표시할 수 있습니다. 관례적으로 0은 성공을 의미하고, 0이 아닌 값(예: 1 또는 2)은 다양한 오류 유형을 나타냅니다.
따옴표와 공백
공백으로 구분된 인수는 별개의 값으로 취급됩니다. 하나의 인수에 공백을 포함해야 할 경우, 두 개의 따옴표로 감싸면 됩니다:
java Example "New York" Japan
이렇게 하면 다음과 같이 출력됩니다:
args[0] = New York
args[1] = Japan
인수가 제공되지 않을 때
인수가 전혀 제공되지 않으면 args.length는 0이 됩니다. 이를 활용해 로직을 분기할 수 있습니다. 예를 들어:
if (args.length == 0) {
System.out.println("Running in interactive mode...");
} else {
System.out.println("Running with parameters...");
}
이 패턴은 대화형 모드와 배치 실행 모드를 모두 지원하는 도구에서 특히 유용합니다.
3. 데이터 변환 및 오류 처리
모든 명령줄 인수는 문자열(String)로 전달됩니다. 따라서 숫자, 불리언 또는 다른 타입으로 사용하려면 명시적으로 변환해야 합니다. 이 장에서는 데이터 타입을 안전하게 변환하고 발생 가능한 오류를 처리하는 방법을 설명합니다.
문자열을 정수와 실수로 변환하기
Integer.parseInt()와 Double.parseDouble() 메서드는 문자열 값을 숫자 타입으로 변환하는 데 사용됩니다. 입력을 숫자로 파싱할 수 없으면 NumberFormatException이 발생합니다.
public class ParseExample {
public static void main(String[] args) {
if (args.length < 2) {
System.out.println("Usage: java ParseExample <price> <quantity>");
return;
}
try {
double price = Double.parseDouble(args[0]);
int quantity = Integer.parseInt(args[1]);
System.out.println("Total: " + (price * quantity));
} catch (NumberFormatException e) {
System.out.println("Error: Please enter numeric values only.");
}
}
}
실행 예시:
java ParseExample 120.5 3
Total: 361.5
불리언 처리
debug mode나 verbose와 같은 불리언 플래그를 파싱하려면 Boolean.parseBoolean()을 사용할 수 있습니다. 인수가 “true”(대소문자 구분 없이)와 일치할 때만 true를 반환합니다.
boolean debug = false;
if (args.length > 0) {
debug = Boolean.parseBoolean(args[0]);
}
if (debug) {
System.out.println("Debug mode enabled");
} else {
System.out.println("Debug mode disabled");
}
실행 예시:
java Example true
Debug mode enabled
java Example false
Debug mode disabled
기본값을 사용한 안전한 변환
입력이 없거나 잘못된 경우에 대비해 기본값을 제공하는 것이 좋은 습관입니다. 이는 런타임 오류를 방지하고 사용자 경험을 향상시킵니다.
public static int parseIntOrDefault(String s, int defaultValue) {
try {
return Integer.parseInt(s);
} catch (Exception e) {
return defaultValue;
}
}
이 패턴은 필요에 따라 부동 소수점 숫자나 날짜에도 확장할 수 있습니다.
오류를 우아하게 포착하고 보고하기
사용자 입력을 처리할 때 오류 메시지는 명확하고 도움이 되어야 합니다. 단순히 스택 트레이스를 출력하는 대신 올바른 사용법에 대한 안내를 제공하십시오.
try {
int age = Integer.parseInt(args[0]);
if (age < 0) throw new IllegalArgumentException("Age cannot be negative");
System.out.println("Age: " + age);
} catch (NumberFormatException e) {
System.err.println("Error: Please enter a valid number for age.");
} catch (IllegalArgumentException e) {
System.err.println(e.getMessage());
}
System.err.println()을 사용하면 오류 메시지를 표준 오류 스트림으로 보내어 로그나 파이프라인에서 일반 출력과 구분할 수 있습니다.
선택 사항: Java의 Optional 클래스 사용
null 검사를 피하고 가독성을 높이려면 인수 파싱에 Optional<T> 사용을 고려하십시오. 예를 들어:
import java.util.Optional;
public class OptionalExample {
public static void main(String[] args) {
Optional<String> arg0 = (args.length > 0) ? Optional.of(args[0]) : Optional.empty();
String message = arg0.orElse("default");
System.out.println("Argument: " + message);
}
}
이렇게 하면 인수가 제공되지 않더라도 프로그램이 안전하게 실행됩니다.
요약: 견고한 커맨드라인 프로그램을 만들기 위해서는 사용자 입력이 없거나 형식이 잘못될 수 있다는 가정을 항상 해야 합니다. 파싱, 검증, 의미 있는 오류 메시지를 결합하여 안정성을 확보하십시오.
4. 옵션 스타일 인수 처리
Java 프로그램이 커지면서 -h, --help, --mode=prod와 같은 형태의 인수를 처리하는 것이 필수적입니다. 이러한 옵션 스타일 인수는 특히 커맨드라인 유틸리티나 자동화 스크립트에서 프로그램을 더 읽기 쉽고 사용자 친화적으로 만들어 줍니다.
짧은 옵션과 긴 옵션
옵션은 일반적으로 두 가지 스타일로 제공됩니다:
- 짧은 옵션 — 단일 하이픈으로 시작하며, 예:
-v또는-h. - 긴 옵션 — 이중 하이픈으로 시작하며, 예:
--help또는--mode=prod.
startsWith()와 split() 같은 문자열 연산을 사용해 직접 파싱할 수 있습니다.
public class OptionExample {
public static void main(String[] args) {
boolean help = false;
String mode = "dev";
for (String arg : args) {
if (arg.equals("-h") || arg.equals("--help")) {
help = true;
} else if (arg.startsWith("--mode=")) {
mode = arg.split("=", 2)[1];
}
}
if (help) {
System.out.println("Usage: java OptionExample [--mode=<mode>] [-h|--help]");
return;
}
System.out.println("Mode: " + mode);
}
}
실행 예시:
java OptionExample
Mode: dev
java OptionExample --mode=prod
Mode: prod
java OptionExample -h
Usage: java OptionExample [--mode=<mode>] [-h|--help]
플래그와 값 결합하기
때때로 --input data.txt와 같이 값이 별도로 제공되는 플래그를 처리해야 할 때가 있습니다. 이런 경우 인덱스를 사용해 인수를 순회하면서 다음 값을 안전하게 읽을 수 있습니다.
public class InputExample {
public static void main(String[] args) {
String inputFile = null;
for (int i = 0; i < args.length; i++) {
if (args[i].equals("--input") && i + 1 < args.length) {
inputFile = args[i + 1];
}
}
if (inputFile == null) {
System.out.println("Please specify an input file using --input <filename>");
return;
}
System.out.println("Processing file: " + inputFile);
}
}
실행 예시:
java InputExample --input report.csv
Processing file: report.csv
여러 옵션 결합하기
실제 도구는 종종 여러 옵션을 허용합니다 — 예를 들어, --mode=prod --debug --log-level=2. 이러한 옵션을 깔끔하게 관리하려면 모든 옵션을 구성 객체로 파싱하는 것을 고려하세요.
class Config {
String mode = "dev";
boolean debug = false;
int logLevel = 1;
}
public class ConfigExample {
public static void main(String[] args) {
Config cfg = new Config();
for (String arg : args) {
if (arg.startsWith("--mode=")) {
cfg.mode = arg.split("=", 2)[1];
} else if (arg.equals("--debug")) {
cfg.debug = true;
} else if (arg.startsWith("--log-level=")) {
try {
cfg.logLevel = Integer.parseInt(arg.split("=", 2)[1]);
} catch (NumberFormatException e) {
System.err.println("Invalid log level. Using default value: 1");
}
}
}
System.out.println("Mode: " + cfg.mode);
System.out.println("Debug: " + cfg.debug);
System.out.println("Log Level: " + cfg.logLevel);
}
}
Apache Commons CLI 사용 (추천)
대규모 프로젝트에서는 인자를 수동으로 파싱하는 것이 오류가 발생하기 쉽습니다. Apache Commons CLI 라이브러리는 도움말 메시지와 검증을 포함한 명령줄 옵션을 정의하고 처리하는 표준화된 방법을 제공합니다.
import org.apache.commons.cli.*;
public class CLIExample {
public static void main(String[] args) throws Exception {
Options options = new Options();
options.addOption("h", "help", false, "Show help message");
options.addOption("m", "mode", true, "Execution mode (dev/prod)");
CommandLineParser parser = new DefaultParser();
CommandLine cmd = parser.parse(options, args);
if (cmd.hasOption("h")) {
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("CLIExample", options);
return;
}
String mode = cmd.getOptionValue("m", "dev");
System.out.println("Mode: " + mode);
}
}
이 접근 방식은 자동으로 도움말 생성, 입력 검증, 그리고 정의와 로직 사이의 명확한 코드 분리를 지원합니다.
모범 사례 요약
- 짧은 형태(
-h)와 긴 형태(--help) 플래그 모두를 지원합니다. - 간단한 파싱을 위해
startsWith()와split("=")를 사용합니다. - 유지 보수를 향상시키기 위해 구성 클래스를 사용합니다.
- 확장 가능한 구현을 위해 Apache Commons CLI와 같은 라이브러리를 채택합니다.
- 명확성을 위해 항상
--help또는 사용법 출력을 제공합니다.
이와 같이 인자 파싱을 설계하면 Java 도구를 훨씬 더 직관적이고 예측 가능하며 유지 보수가 용이하게 만들 수 있습니다 — 마치 전문적인 명령줄 애플리케이션처럼.
5. IDE에서 명령줄 인자 설정 및 테스트
Java 애플리케이션을 개발할 때, 터미널에서 직접 실행하지 않고도 다양한 명령줄 인자에 따라 프로그램이 어떻게 동작하는지 테스트하고 싶을 때가 많습니다. 이 섹션에서는 Eclipse와 IntelliJ IDEA와 같은 인기 있는 IDE에서 인자를 설정하는 방법을 설명합니다.
Eclipse에서 인자 설정
Eclipse에서는 “Run Configurations” 대화 상자를 통해 인자를 설정할 수 있습니다. 다음 단계를 따르세요:
- 1️⃣ Java 파일을 열고 편집기를 오른쪽 클릭하세요.
- 2️⃣ Run As → Run Configurations… 를 선택하세요.
- 3️⃣ 대화 상자에서 “Java Application” 아래에 있는 클래스 선택.
- 4️⃣ Arguments 탭을 클릭하세요.
- 5️⃣ Program arguments 상자에 원하는 인수를 공백으로 구분하여 입력하세요.
Example:
Tokyo 2025 debug
프로그램을 실행할 때 Eclipse는 이러한 인수를 자동으로 String[] args에 전달합니다.
Tip: 여러 구성 — 예를 들어 “production mode”용 하나와 “debug mode”용 하나 — 를 만들고 쉽게 전환할 수 있습니다.
IntelliJ IDEA에서 인수 설정
IntelliJ IDEA에서 프로세스는 똑같이 간단합니다:
- 1️⃣ Run 버튼(오른쪽 상단 모서리) 옆의 드롭다운을 클릭하세요.
- 2️⃣ Edit Configurations… 를 선택하세요.
- 3️⃣ “Run/Debug Configurations” 창에서 “Application” 아래에 있는 Java 클래스 찾기.
- 4️⃣ Program arguments 필드에 명령줄에서 하듯이 인수를 입력하세요.
Example:
--mode=prod --debug true
Apply를 클릭한 후 Run을 클릭하세요. IntelliJ는 해당 매개변수를 args 배열에 전달하여 프로그램을 실행합니다.
여러 패턴 빠르게 테스트하기
자동화나 배치 도구를 테스트할 때, 다른 인수 세트로 여러 실행 구성을 등록하여 시간을 절약할 수 있습니다 — 예를 들어:
- config-dev :
--mode=dev --debug - config-prod :
--mode=prod --log-level=2 - config-local :
input.txt output.txt
이렇게 하면 소스 코드나 터미널 명령을 수정하지 않고 테스트 조건 간에 한 번 클릭으로 전환할 수 있습니다.
JUnit 테스트 중 인수 전달
자동화 테스트에서 인수 처리를 확인해야 한다면, JUnit 내에서 main 메서드에 String[]를 명시적으로 전달하여 인수를 시뮬레이션할 수 있습니다.
import org.junit.jupiter.api.Test;
public class ArgsTest {
@Test
void testArguments() {
String[] args = {"--mode=prod", "--debug"};
MyApp.main(args);
}
}
이 패턴은 일반 애플리케이션 코드와 동일한 JVM 환경에서 프로그램 로직을 테스트할 수 있게 하여 전체 CI/CD 자동화를 가능하게 합니다.
IDE 인수 테스트의 일반적인 함정
- 🧩 실행 전에 구성을 저장하는 것을 잊음 (특히 Eclipse에서).
- 🧩 공백 오타 — 따옴표로 둘러싸이지 않으면 각 공백이 인수를 구분합니다.
- 🧩 인수 편집 후 다시 실행하지 않음 — 일부 IDE는 이전 구성을 캐시합니다.
- 🧩 환경 변수가 자동으로 변경되기를 기대함 (IDE 설정에서 별도로 구성하세요).
이러한 IDE 인수 설정을 마스터하면 로컬에서 프로덕션과 유사한 동작을 재현하고 예상치 못한 런타임 문제를 줄일 수 있습니다.
6. 오류 처리 및 보안 설계
명령줄 입력을 받을 때 Java 프로그램은 잘못된, 예상치 못한, 또는 악의적인 인수를 우아하게 처리해야 합니다. 이 섹션은 시스템 실패나 오용을 방지하는 안전한 검증 관행과 보안 원칙을 다룹니다.
사용 전에 검증
사용자 입력이 유효하다고 가정하지 마세요. 계산, 파일 작업, 또는 시스템 호출에서 사용하기 전에 항상 인수를 검증하세요. 일반적인 검증에는 다음이 포함됩니다:
- 인수의 수 확인 (
args.length). - 형식 확인 (e.g., numeric, boolean, URL, or date).
- 파일 경로가 존재하고 접근 가능함 확인.
- 주입이나 경로 탐색으로 이어질 수 있는 잘못된 문자 거부.
Example: 사용 전에 숫자 입력 범위 검증:
try {
int threads = Integer.parseInt(args[0]);
if (threads < 1 || threads > 64) {
throw new IllegalArgumentException("Thread count must be between 1 and 64.");
}
System.out.println("Using " + threads + " threads.");
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
}
파일 경로 오용 방지
파일 경로를 나타내는 인수를 다룰 때, 사용자가 ../ 또는 심볼릭 링크를 사용하여 의도된 디렉토리 외부로 탐색하지 못하도록 하세요. 예를 들어:
import java.nio.file.*;
Path baseDir = Paths.get("/var/app/data");
Path inputPath = baseDir.resolve(args[0]).normalize();
if (!inputPath.startsWith(baseDir)) {
throw new SecurityException("Access outside of permitted directory is not allowed.");
}
이것은 사용자가 지정된 영역 외부의 민감한 파일에 접근하려는 경로 탐색 공격을 방지합니다.
임의 명령 실행 피하기
인수는 세정 없이 시스템 명령이나 외부 프로세스에 직접 전달되어서는 안 됩니다. 그렇지 않으면 프로그램이 명령 주입에 취약해질 수 있습니다.
// ❌ Dangerous example (do not use)
Runtime.getRuntime().exec("cat " + args[0]);
// ✅ Safe alternative using ProcessBuilder
ProcessBuilder pb = new ProcessBuilder("cat", args[0]);
pb.redirectErrorStream(true);
pb.start();
ProcessBuilder API는 각 인수를 별도로 처리하여 악의적인 쉘 해석을 방지합니다.
오류 보고 및 종료 코드
전문 도구의 경우, 사용자가 무엇이 잘못되었는지 이해할 수 있도록 명확한 종료 코드와 오류 메시지를 설계하세요. 예시 분류:
0— 성공적인 실행1— 잘못된 입력 또는 인수 오류2— 누락된 파일 또는 리소스3— 권한 거부99— 알려지지 않음 또는 처리되지 않은 예외
예시 구현:
try {
// business logic
} catch (IllegalArgumentException e) {
System.err.println("Invalid argument: " + e.getMessage());
System.exit(1);
} catch (SecurityException e) {
System.err.println("Permission error: " + e.getMessage());
System.exit(3);
} catch (Exception e) {
e.printStackTrace();
System.exit(99);
}
로그 및 오류 출력 세정
사용자 제공 인수를 로깅할 때, 비밀번호, 토큰 또는 개인 데이터와 같은 민감한 정보를 포함하지 마세요. 예시:
String password = args[0];
// ❌ Don't log this directly
// System.out.println("Password: " + password);
// ✅ Use placeholders or masked output
System.out.println("Password provided: [REDACTED]");
이것은 공유 환경이나 CI/CD 파이프라인에서 특히 로그나 콘솔 출력에서 우발적인 데이터 유출을 방지하는 데 도움이 됩니다.
방어적 코딩 요약
- 사용 전에 항상 인수를 검증하세요.
- 디렉토리 탐색을 방지하기 위해 경로를 정규화하고 확인하세요.
- 사용자 입력을 쉘 명령에 연결하지 마세요.
- 자동화 호환성을 위해 명확한 종료 코드를 설계하세요.
- 로그와 메시지에서 민감한 데이터를 마스킹하세요.
- 빠르게 실패하지만 안전하게 실패하세요 — 스택 추적을 노출하는 프로그램 충돌을 피하세요.
이러한 방어적 기법을 적용함으로써 Java 애플리케이션은 예측 불가능한 런타임 환경에서 실행되더라도 견고하고 안전하며 전문적으로 유지될 것입니다.
7. 실용적 예시 — 파일 처리, 모드 전환 및 로깅 제어
인수 처리에 대한 구문과 모범 사례를 이해한 후, 실용적인 사용 사례를 탐색할 시간입니다. 이 섹션에서는 파일 작업, 환경 모드 전환, 동적 로그 제어의 세 가지 전형적인 패턴을 소개합니다. 이는 실제 애플리케이션과 자동화 워크플로에서 일반적입니다.
예시 1: 파일 처리 프로그램
많은 자동화 스크립트에서 명령줄 인수는 입력 및 출력 파일 경로를 지정하는 데 사용됩니다. 아래는 한 파일의 내용을 다른 파일로 복사하는 간단한 예시입니다:
import java.nio.file.*;
import java.io.IOException;
public class FileCopy {
public static void main(String[] args) {
if (args.length < 2) {
System.out.println("Usage: java FileCopy <source> <destination>");
System.exit(1);
}
Path src = Paths.get(args[0]);
Path dst = Paths.get(args[1]);
try {
Files.copy(src, dst, StandardCopyOption.REPLACE_EXISTING);
System.out.println("File copied successfully: " + dst);
} catch (IOException e) {
System.err.println("File copy failed: " + e.getMessage());
System.exit(2);
}
}
}
실행 예시:
java FileCopy input.txt backup/input_copy.txt
파일 경로를 매개변수화함으로써 이 프로그램을 자동화 파이프라인, 백업 스크립트 또는 cron 작업에서 재사용할 수 있습니다.
예시 2: 모드 전환 (개발 / 프로덕션)
애플리케이션은 종종 환경에 따라 다르게 동작합니다 — 예를 들어, 다른 데이터베이스나 API 엔드포인트를 사용합니다. --mode=prod와 같은 인수를 사용하여 동적으로 동작을 전환할 수 있습니다.
public class ModeSwitch {
public static void main(String[] args) {
String mode = "dev"; // default mode
for (String arg : args) {
if (arg.startsWith("--mode=")) {
mode = arg.split("=", 2)[1];
}
}
switch (mode) {
case "dev":
System.out.println("Running in Development Mode");
break;
case "prod":
System.out.println("Running in Production Mode");
break;
case "test":
System.out.println("Running in Test Mode");
break;
default:
System.err.println("Unknown mode: " + mode);
System.exit(1);
}
}
}
실행 예시:
java ModeSwitch --mode=dev
Running in Development Mode
java ModeSwitch --mode=prod
Running in Production Mode
이 설계는 여러 구성을 깔끔하게 관리하고 환경별 로직을 하드코딩하는 것을 피할 수 있게 합니다.
예시 3: 로그 레벨 및 디버그 제어
로깅 레벨은 종종 명령줄 인수를 통해 제어되며, 코드 변경 없이 유연한 진단을 가능하게 합니다.
public class LogControl {
public static void main(String[] args) {
int logLevel = 1; // 1: normal, 2: verbose, 3: debug
for (String arg : args) {
if (arg.startsWith("--log=")) {
try {
logLevel = Integer.parseInt(arg.split("=", 2)[1]);
} catch (NumberFormatException e) {
System.err.println("Invalid log level. Using default: 1");
}
}
}
if (logLevel >= 3) System.out.println("[DEBUG] Debug information enabled");
if (logLevel >= 2) System.out.println("[INFO] Detailed information shown");
System.out.println("[NORMAL] Application started");
}
}
실행 예시:
java LogControl --log=3
[DEBUG] Debug information enabled
[INFO] Detailed information shown
[NORMAL] Application started
이 패턴은 로깅의 상세도를 재컴파일 없이 동적으로 조정해야 하는 프로덕션 도구에서 일반적입니다.
모든 패턴 결합
이 예시들을 결합하여 여러 책임을 처리하는 단일 구성 가능한 도구로 만들 수 있습니다. 예를 들어, --mode, --log, --input 옵션을 동시에 받아들이는 파일 처리 프로그램입니다.
java App --mode=prod --log=2 --input data.txt
인수 파싱을 신중하게 구조화함으로써 실제 배포 환경에 적합한 유연하고 재사용 가능한 명령줄 유틸리티를 만들 수 있습니다.
실용적인 패턴 요약
- ✅ 파일 입출력 유연성을 위해 인자를 사용합니다.
- ✅ 개발, 테스트, 프로덕션 모드 전환을 허용합니다.
- ✅ 명령줄에서 로깅 및 디버깅 제어를 활성화합니다.
- ✅ 이러한 매개변수를 결합하여 다목적 자동화 도구를 구축합니다.
이러한 예시는 현대 Java 자동화 도구의 기반을 나타냅니다 — 가볍고, 매개변수화되며, 스크립트나 스케줄러와 쉽게 통합할 수 있습니다.
8. 실제 배포에서의 모범 사례
Java 애플리케이션이 프로덕션 환경에서 사용되기 시작하면, 명령줄 인자를 일관되고 안전하게 처리하는 것이 전문 소프트웨어 설계의 일부가 됩니다. 이 섹션에서는 유지 보수 가능하고, 안전하며, 확장 가능한 인자 처리를 위한 실제 모범 사례를 요약합니다.
1. 인터페이스 일관성 유지
출시된 후에는 각 명령줄 인자의 의미가 안정적으로 유지되어야 합니다. 절대적으로 필요하지 않은 한 기존 옵션의 이름을 바꾸거나 제거하지 마세요. 새로운 매개변수를 추가할 때는 기본 동작을 변경하지 않아 이전 버전과의 호환성을 유지하십시오.
// Old version
java ReportTool --mode=prod
// New version (compatible)
java ReportTool --mode=prod --log=2
이 접근 방식은 도구에 의존하는 자동화 스크립트, CI 파이프라인 또는 크론 작업이 중단되는 것을 방지합니다.
2. 도움말 옵션 제공
모든 전문 명령줄 도구는 사용법, 사용 가능한 옵션 및 예시를 설명하는 접근 가능한 --help 또는 -h 플래그를 제공해야 합니다.
if (args.length == 0 || Arrays.asList(args).contains("--help")) {
System.out.println("Usage: java MyTool [options]");
System.out.println(" --input <file> Specify input file");
System.out.println(" --mode <type> Choose mode: dev, test, prod");
System.out.println(" --log <level> Set log verbosity (1-3)");
System.exit(0);
}
이는 사용성을 향상시킬 뿐만 아니라 사용자 오류와 지원 요청을 감소시킵니다.
3. 인자 동작을 명확히 문서화
지원되는 모든 인자, 기본값 및 샘플 실행을 나열한 최신 README 또는 온라인 문서를 유지하십시오. 여러 옵션이 상호 작용할 때(예: --mode=prod가 디버깅을 비활성화) 이러한 관계를 명확히 설명하십시오.
# Example documentation section
### Options
--mode=<value> Select execution mode (dev/test/prod)
--log=<level> Verbosity (1: normal, 2: verbose, 3: debug)
--input=<path> Input file path
### Example
java MyTool --mode=prod --log=2 --input report.csv
4. 구성과 코드를 분리
운영 매개변수를 하드코딩하지 마세요. 민감한 데이터나 기본값은 구성 파일이나 환경 변수로 관리하고, 필요할 때 명령줄 인자가 이를 덮어쓰도록 하세요.
String defaultMode = System.getenv().getOrDefault("APP_MODE", "dev");
String mode = defaultMode;
// CLI arguments override environment variable
for (String arg : args) {
if (arg.startsWith("--mode=")) {
mode = arg.split("=", 2)[1];
}
}
System.out.println("Running in " + mode + " mode");
이 구조를 통해 개발자와 운영자는 코드를 재컴파일하거나 수정하지 않고도 동작을 구성할 수 있습니다.
5. 짧은 형태와 긴 형태 모두 지원
짧은 형태(-v)와 긴 형태(--verbose) 옵션을 모두 제공하면 다양한 사용자 선호도에 편리함을 제공합니다:
if (arg.equals("-v") || arg.equals("--verbose")) {
verbose = true;
}
이중 형태는 도구를 UNIX/Linux 관행에 맞추어 경험 많은 엔지니어들의 사용성을 향상시킵니다.
6. 의미 있는 종료 코드 반환
Jenkins, 셸 스크립트, 오케스트레이션 시스템과 같은 통합은 프로세스 종료 코드에 의존합니다. 성공, 경고 및 오류를 명확히 알리기 위해 구별된 코드를 사용하세요. 예시:
0— 성공10— 필수 인자 누락20— 검증 오류30— 런타임 예외
이는 외부 자동화가 지능적으로 대응하도록 하며, 예를 들어 복구 가능한 오류에만 재시도하도록 할 수 있습니다.
7. 인자와 환경을 안전하게 로깅
프로덕션 이슈를 디버깅할 때, 어떤 인수가 전달되었는지 아는 것은 매우 중요합니다. 하지만 인수를 기록할 때는 신중해야 합니다:
- 비밀번호나 토큰과 같은 민감한 값은 마스킹 처리합니다 (
******). - 안전하고 개인 정보가 아닌 파라미터만 기록합니다.
- 타임스탬프와 프로세스 식별자를 포함합니다.
안전한 로그 출력 예시:
[2025-11-11 09:30:15] App started
Mode: prod
Log level: 2
Input: data.csv
Password: [REDACTED]
8. 확장성을 위한 라이브러리 사용
대규모 도구에서는 수동 문자열 파싱을 피하고 다음과 같은 라이브러리를 사용하세요:
- Apache Commons CLI — 간단하고 성숙함.
- Picocli — 최신이며 어노테이션 기반이고 색상 지원 도움말 출력을 지원합니다.
- JCommander — 직관적이며 인수 바인딩에 가볍습니다.
예시 (Picocli):
import picocli.CommandLine;
import picocli.CommandLine.Option;
public class App implements Runnable {
@Option(names = {"-m", "--mode"}, description = "Execution mode")
String mode = "dev";
@Option(names = {"-l", "--log"}, description = "Log level")
int log = 1;
public void run() {
System.out.println("Mode: " + mode + ", Log: " + log);
}
public static void main(String[] args) {
new CommandLine(new App()).execute(args);
}
}
Picocli와 같은 라이브러리는 보일러플레이트 코드를 크게 줄이고, 자동 검증을 제공하며, 도움말 메시지를 자동으로 생성합니다.
9. 국제화 준비
애플리케이션이 전 세계 사용자를 대상으로 한다면, 도움말 메시지와 로그를 국제화를 고려해 설계하세요. 하드코딩된 영문 텍스트 대신 리소스 번들(.properties 파일)을 사용해 메시지를 관리합니다.
ResourceBundle bundle = ResourceBundle.getBundle("messages", Locale.getDefault());
System.out.println(bundle.getString("usage.help"));
이를 통해 프로그램은 시스템 로케일에 따라 자동으로 언어를 전환할 수 있습니다.
10. 인수 로직 테스트 자동화
옵션이 추가되거나 수정될 때 회귀를 방지하기 위해 인수 파싱을 단위 테스트로 커버하세요.
@Test
void testModeArgument() {
String[] args = {"--mode=prod"};
assertDoesNotThrow(() -> MyApp.main(args));
}
자동화된 테스트는 CLI가 업데이트 및 리팩터링 후에도 안정적으로 유지된다는 확신을 줍니다.
요약
- 이전 버전과 호환되는 인수 구조를 유지합니다.
- 명확한
--help문서를 제공합니다. - 유연성을 위해 설정을 코드와 분리합니다.
- 신뢰성과 유지보수를 위해 라이브러리와 자동화를 사용합니다.
- 초기 단계부터 국제화와 보안을 고려합니다.
이 가이드라인을 적용하면 Java 명령줄 도구가 로컬 및 글로벌 환경 모두에서 전문가 수준의 안정성과 사용성을 확보할 수 있습니다.
9. 요약 및 설계 템플릿
이 글에서는 Java에서 명령줄 인수를 처리하는 전체 라이프사이클을 — 기본 구문부터 실제 보안 및 배포 고려사항까지 — 살펴보았습니다. 핵심 개념을 요약하고, 여러분의 프로젝트에 적용할 수 있는 재사용 가능한 설계 템플릿을 제공하겠습니다.
주요 요점
- ✅ 기본 구조:
main메서드에서String[] args를 사용해 파라미터를 받습니다. - ✅ 검증: 누락되거나 잘못된 입력을 항상 확인하고 유용한 피드백을 제공합니다.
- ✅ 변환: 문자열 인수를 안전하게 숫자, 불리언 또는 사용자 정의 타입으로 변환합니다.
- ✅ 옵션 파싱: 명확성을 위해 짧은 형태(
-h)와 긴 형태(--help) 옵션을 모두 지원합니다. - ✅ 보안: 경로를 정규화하고, 입력을 정제하며, 안전하지 않은 명령 실행을 피합니다.
- ✅ 실용적 사용: 파일 처리, 모드 제어, 로그 설정 등에 인수를 활용합니다.
- ✅ 전문적 실천: 문서, 일관된 인터페이스, 종료 코드를 제공합니다.
- ✅ 확장성: 대규모 프로젝트에서는 Picocli 또는 Commons CLI와 같은 라이브러리를 사용합니다.
- ✅ 자동화: JUnit 또는 CI 파이프라인을 통해 인수 처리를 테스트합니다.
재사용 가능한 설계 템플릿
다음 템플릿은 이 가이드에서 논의된 모범 사례(검증, 도움말 표시, 환경 모드 처리 및 로깅 수준)를 하나의 간결한 프로그램에 통합합니다.
import java.util.*;
public class AppTemplate {
static class Config {
String mode = "dev";
int logLevel = 1;
String input = null;
boolean help = false;
}
public static void main(String[] args) {
Config cfg = parseArgs(args);
if (cfg.help) {
printHelp();
System.exit(0);
}
// Logging example
if (cfg.logLevel >= 3) System.out.println("[DEBUG] Mode = " + cfg.mode);
if (cfg.logLevel >= 2) System.out.println("[INFO] Log level set to " + cfg.logLevel);
if (cfg.input != null) {
System.out.println("[INFO] Processing input file: " + cfg.input);
} else {
System.out.println("[WARN] No input file specified. Running default mode.");
}
// Main logic
System.out.println("Running in " + cfg.mode + " mode.");
}
private static Config parseArgs(String[] args) {
Config cfg = new Config();
for (String arg : args) {
if (arg.equals("-h") || arg.equals("--help")) {
cfg.help = true;
} else if (arg.startsWith("--mode=")) {
cfg.mode = arg.split("=", 2)[1];
} else if (arg.startsWith("--log=")) {
try {
cfg.logLevel = Integer.parseInt(arg.split("=", 2)[1]);
} catch (NumberFormatException e) {
System.err.println("Invalid log level, using default (1).");
}
} else if (arg.startsWith("--input=")) {
cfg.input = arg.split("=", 2)[1];
}
}
return cfg;
}
private static void printHelp() {
System.out.println("Usage: java AppTemplate [options]");
System.out.println("Options:");
System.out.println(" --mode=<dev|test|prod> Execution mode (default: dev)");
System.out.println(" --log=<1|2|3> Log level (1:normal, 2:verbose, 3:debug)");
System.out.println(" --input=<file> Input file path");
System.out.println(" -h, --help Show this help message");
}
}
이 설계는 다음을 제공합니다:
- 비즈니스 로직과 분리된 명확한 인수 파싱.
- 자동 도움말 표시.
- 안전한 숫자 변환 및 기본값.
- 디버깅 및 프로덕션 모드를 위한 간단한 로깅 제어.
Extending the Template
다음과 같은 여러 방향으로 이 기본 템플릿을 확장할 수 있습니다:
- 파일 존재 여부 확인 및 예외 처리를 추가합니다.
Properties또는 JSON 구성 파일과 통합합니다.- 하위 명령을 지원합니다(예:
java Tool analyze,java Tool export). - 컬러 콘솔 출력 또는 구조화된 로깅을 구현합니다.
- 누락된 인수에 대한 기본값으로 환경 변수를 로드합니다.
이러한 향상을 결합하면 이 경량 구조를 프로젝트 요구에 맞춘 견고한 CLI 프레임워크로 발전시킬 수 있습니다.
Final Words
명령줄 인수는 단순해 보일 수 있지만, 구성 가능하고 테스트 가능하며 자동화 가능한 소프트웨어의 기반을 형성합니다. API 인터페이스를 설계하듯이 깨끗하고 예측 가능하며 안전하게 설계하십시오.
요약: 잘 구조화된 인수 설계에 노력을 투자하면 디버깅 및 자동화부터 배포와 장기 유지보수에 이르기까지 개발 전 단계에서 큰 이점을 얻을 수 있습니다.
이러한 원칙과 템플릿을 통해 이제 환경, 팀, 그리고 수년간의 진화에 걸쳐 일관되게 동작하는 전문 수준의 명령줄 도구를 설계할 수 있습니다.
FAQ — Frequently Asked Questions
이 섹션에서는 Java에서 명령줄 인수를 처리하는 방법에 대해 개발자들이 흔히 묻는 질문들을 요약합니다. 각 답변에는 간단한 예시 또는 실용적인 안내가 포함됩니다.
Q1. 선택적 및 필수 인수를 모두 처리하려면 어떻게 해야 하나요?
필수 인수는 명시적으로 검증해야 합니다 — 예를 들어 args.length를 확인하거나 특정 플래그가 존재하는지를 확인합니다. 선택적 인수는 안전한 기본값을 가질 수 있습니다.
if (args.length < 1) {
System.err.println("Error: Missing input file");
System.exit(1);
}
String input = args[0];
String mode = (args.length > 1) ? args[1] : "dev";
대규모 프로젝트에서는 Picocli 또는 Apache Commons CLI와 같은 라이브러리를 사용해 인수 스키마를 정의하면, 필수/선택 플래그를 자동으로 지원합니다.
Q2. 인수에 공백을 포함하려면 (파일 이름이나 구문처럼) 어떻게 해야 하나요?
터미널에서 실행할 때 인수를 큰따옴표(double quotes)로 감싸세요:
java Example "New York City" Japan
출력:
args[0] = New York City
args[1] = Japan
이렇게 하면 전체 구문이 하나의 인수로 처리되어 여러 단어로 분리되지 않습니다.
Q3. 인수가 전혀 제공되지 않으면 어떻게 되나요?
인수가 전달되지 않으면 args.length는 0이 됩니다. 도움말 메시지를 표시하거나 기본값을 사용해 이를 안전하게 감지하고 처리할 수 있습니다.
if (args.length == 0) {
System.out.println("No arguments provided. Running default mode...");
}
Q4. IntelliJ나 Eclipse와 같은 IDE에서 인수를 테스트하려면 어떻게 해야 하나요?
두 IDE 모두 프로그램 인수를 설정할 수 있는 구성 대화상자를 제공합니다:
- Eclipse: Run → Run Configurations → Arguments 탭 → 인수를 입력합니다.
- IntelliJ IDEA: Run → Edit Configurations → Program arguments 필드.
예시: --mode=prod --log=2 --input=data.txt
Q5. “–debug” 또는 “–verbose”와 같은 불리언 플래그를 어떻게 처리하나요?
불리언 플래그는 일반적으로 값 없이 나타납니다. equals() 또는 contains() 메서드를 사용해 이를 감지할 수 있습니다.
boolean debug = false;
for (String arg : args) {
if (arg.equals("--debug") || arg.equals("-d")) {
debug = true;
}
}
if (debug) System.out.println("Debug mode enabled.");
Q6. “java -jar” 애플리케이션에 여러 인수를 전달하려면 어떻게 해야 하나요?
패키징된 JAR 파일을 실행할 때는 JAR 이름 뒤에 인수를 배치합니다. 예시:
java -jar MyApp.jar --mode=prod input.txt --log=3
애플리케이션의 main(String[] args)는 표준 실행과 동일한 인수를 받습니다.
Q7. 명령줄 대신 설정 파일에서 인수를 읽으려면 어떻게 해야 하나요?
Java의 Properties 또는 YAML/JSON 라이브러리를 사용해 기본 설정을 로드하고, 필요하면 CLI 인수로 이를 재정의할 수 있습니다.
Properties props = new Properties();
props.load(new FileInputStream("config.properties"));
String mode = props.getProperty("mode", "dev");
// CLI overrides file
for (String arg : args) {
if (arg.startsWith("--mode=")) {
mode = arg.split("=", 2)[1];
}
}
Q8. 인수에 유니코드 또는 비ASCII 문자를 사용할 수 있나요?
네, Java는 args에서 유니코드 문자열을 완전히 지원합니다. 다만, 사용 중인 터미널이나 운영체제 인코딩도 해당 문자를 지원해야 합니다. Windows에서는 UTF-8 로케일(chcp 65001)로 실행하는 것을 고려하고, Linux/macOS에서는 쉘이 UTF-8 인코딩을 사용하도록 확인하세요.
Q9. 사용자 제공 인수로 인한 보안 문제를 어떻게 방지할 수 있나요?
- ✅ 모든 입력을 검증합니다(숫자 범위, 파일 경로, URL 등).
- ✅ 디렉터리 트래버설(
../)을 방지하도록 경로를 정규화합니다. - ✅ 사용자 입력을 쉘 명령에 절대 연결하지 않습니다.
- ✅ 화이트리스트 또는 정규식 패턴을 사용해 엄격히 검증합니다.
프로덕션 도구에서는 쉘 실행을 유발할 수 있는 ;, |, &&와 같은 문자를 거부하거나 이스케이프하는 것을 고려하세요.
Q10. 여전히 “args”를 수동으로 사용해야 할까요, 아니면 라이브러리를 도입해야 할까요?
작은 유틸리티의 경우 String[] args를 수동으로 파싱해도 괜찮습니다. 장기적이거나 엔터프라이즈 수준의 도구라면 전용 라이브러리를 사용하는 것이 좋습니다:
- Picocli — 어노테이션 기반, 통합이 쉬움.
- Apache Commons CLI — 검증된 클래식 라이브러리.
- JCommander — 간단하고 가벼움.
라이브러리를 사용하면 버그를 줄이고, 가독성을 향상시키며, 내장된 도움말과 유효성 검사 기능을 제공합니다.
Q11. 모든 받은 인수를 쉽게 출력하려면 어떻게 하나요?
System.out.println("Received arguments:");
for (int i = 0; i < args.length; i++) {
System.out.printf("args[%d] = %s%n", i, args[i]);
}
이 스니펫은 인수 파싱 로직을 디버깅하는 데 완벽합니다.
Q12. 인수와 환경 변수를 섞어 사용할 수 있나요?
네. 환경 변수는 시스템 전체 구성(예: API 키)에 훌륭하며, 명령줄 인수는 임시 오버라이드에 가장 적합합니다.
String apiKey = System.getenv().getOrDefault("API_KEY", "none");
for (String arg : args) {
if (arg.startsWith("--api=")) {
apiKey = arg.split("=", 2)[1];
}
}
System.out.println("API Key: " + (apiKey.equals("none") ? "not set" : "[REDACTED]"));
이 계층화된 구성 모델은 소프트웨어를 유연하고 안전하게 유지합니다.
Q13. 잘못된 인수 유형을 우아하게 처리하려면 어떻게 하나요?
try-catch 블록을 사용하고 프로그램을 크래시시키지 않으면서 의미 있는 오류 메시지를 제공하세요:
try {
int threads = Integer.parseInt(args[0]);
} catch (NumberFormatException e) {
System.err.println("Invalid number format: " + args[0]);
System.exit(1);
}
이렇게 하면 깔끔한 종료를 보장하고 사용자가 입력을 빠르게 수정할 수 있도록 도와줍니다.
Q14. 도움말이나 오류에 색상이 있는 출력을 표시할 수 있나요?
네, 대부분의 터미널에서 색상이 있는 출력을 위해 ANSI 이스케이프 코드를 사용할 수 있습니다:
final String RED = "u001B[31m";
final String RESET = "u001B[0m";
System.err.println(RED + "Error: Invalid argument" + RESET);
Picocli와 Jansi 같은 라이브러리는 크로스 플랫폼 호환성을 자동으로 처리할 수 있습니다.
Q15. 인수 파싱을 더 효율적으로 디버깅하려면 어떻게 하나요?
시작 시 모든 내부 상태를 출력하는 --debug 또는 --trace 플래그와 함께 “진단” 모드를 추가하세요. 예시:
if (Arrays.asList(args).contains("--debug")) {
System.out.println("[TRACE] Arguments: " + Arrays.toString(args));
}
이것은 프로덕션 환경에서 자동화나 구성 문제를 해결할 때 매우 유용합니다.

