ํ•ด๋‹น ๋ถ€๋ถ„์€ ๋„ˆ๋ฌด ์ฝ”๋“œ๊ฐ€ ๋งŽ์•„, ์ด์ „ ์ƒํƒœ์™€ ์ดํ›„ ์ƒํƒœ๋ฅผ ๋†“๊ณ  ๊ทธ ์‚ฌ์ด์—์„œ ์ €์ž๊ฐ€ ์–ด๋– ํ•œ ํ๋ฆ„์œผ๋กœ ์ด๋ฅผ ์ฒ˜๋ฆฌํ–ˆ๋Š”์ง€ ๊ฐ„๋žตํ•˜๊ฒŒ ์ •๋ฆฌํ•˜๋Š” ๊ฒƒ์„ ๋ชฉ์ ์œผ๋กœ ํ•œ๋‹ค.

Intro

ํ•ด๋‹น ๋ถ€๋ถ„์—์„œ๋Š” ์ถœ๋ฐœ์€ ์ข‹์•˜์œผ๋‚˜, ํ™•์žฅ์„œ์ž‰ ๋ถ€์กฑํ–ˆ๋˜ ๋ชจ๋“ˆ์„ ์†Œ๊ฐœํ•œ๋‹ค. ๊ทธ ๋‹ค์Œ, ์ด๋ฅผ ๊ฐœ์„ ํ•˜๊ณ  ์ •๋ฆฌํ•˜๋Š” ๋‹จ๊ณ„๋ฅผ ์ˆœ์ฐจ์ ์œผ๋กœ ์•Œ์•„๋ณผ ์˜ˆ์ •์ด๋‹ค.

๋‹ค์Œ์˜ ์ฝ”๋“œ๋Š” ๋ช…๋ นํ–‰ ์ธ์ˆ˜์˜ ๊ตฌ๋ฌธ์„ ๋ถ„์„ํ•˜๋Š” ํด๋ž˜์Šค Args๋‹ค. ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

public static void main(String[] args) {
    try {
        Args arg = new Args("l,p#,d*", args); // ํ˜•์‹ ์„ค์ •: Bool, Int, String, args ์ธ์ˆ˜๋กœ ๋„˜๊น€
        boolean logging = arg.getBoolean('l'); // arg๋กœ ํŒŒ์‹ฑ๋œ ๊ฐ’์„ ๋ฐ›์•„ ๋ณ€์ˆ˜ ์ด๋ฆ„์œผ๋กœ ์˜๋ฏธ ๋ถ€์—ฌ
        int port = arg.getInt('p');
        String directory = arg.getString('d');
        executeApplication(logging, port, directory);
    } catch (ArgsException e) {
        System.out.print("Argument error: %s\n", e.errorMessage());
    }
}

ArgsException์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ๋ช…๋ นํ–‰ ์ธ์ˆ˜์˜ ๊ตฌ๋ฌธ์„ ์„ฑ๊ณต์ ์œผ๋กœ ๋ถ„์„ํ–ˆ์œผ๋ฉฐ Args ์ธ์Šคํ„ด์Šค์— ์งˆ์˜๋ฅผ ๋˜์ ธ๋„ ์ข‹๋‹ค๋Š” ๋ง์ด๋‹ค. ์ธ์ˆ˜ ๊ฐ’์„ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด get~() ๋“ฑ์˜ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

Args ๊ตฌํ˜„

์ด์ œ ํ•ด๋‹น ํด๋ž˜์Šค๋ฅผ ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„ํ–ˆ๋Š”์ง€ ์ ์–ด๋ณด๊ฒ ๋‹ค. ์ด๋ฆ„, ํ•จ์ˆ˜ ํฌ๊ธฐ, ์ฝ”๋“œ ํ˜•์‹์— ์ฃผ๋ชฉํ•˜๋ฉฐ ์ฝ๋Š”๋‹ค.

Args.java

package com.objectmentor.utilities.args;
 
import static com.objectmentor.utilities.args.ArgsException.ErrorCode.*; 
import java.util.*;
 
public class Args {
    private Map<Character, ArgumentMarshaler> marshalers;
    private Set<Character> argsFound;
    private ListIterator<String> currentArgument;
    
    public Args(String schema, String[] args) throws ArgsException { 
        marshalers = new HashMap<Character, ArgumentMarshaler>(); 
        argsFound = new HashSet<Character>();
        
        parseSchema(schema); // 1๏ธโƒฃ
        parseArgumentStrings(Arrays.asList(args)); // 2๏ธโƒฃ
    }
    
    // 1๏ธโƒฃ
    private void parseSchema(String schema) throws ArgsException { 
        for (String element : schema.split(","))
        if (element.length() > 0) 
            parseSchemaElement(element.trim());
    }
    
    private void parseSchemaElement(String element) throws ArgsException { 
        char elementId = element.charAt(0);
        String elementTail = element.substring(1); validateSchemaElementId(elementId);
        if (elementTail.length() == 0)
            marshalers.put(elementId, new BooleanArgumentMarshaler());
        else if (elementTail.equals("*")) 
            marshalers.put(elementId, new StringArgumentMarshaler());
        else if (elementTail.equals("#"))
            marshalers.put(elementId, new IntegerArgumentMarshaler());
        else if (elementTail.equals("##")) 
            marshalers.put(elementId, new DoubleArgumentMarshaler());
        else if (elementTail.equals("[*]"))
            marshalers.put(elementId, new StringArrayArgumentMarshaler());
        else
            throw new ArgsException(INVALID_ARGUMENT_FORMAT, elementId, elementTail);
    }
    
    private void validateSchemaElementId(char elementId) throws ArgsException { 
        if (!Character.isLetter(elementId))
        throw new ArgsException(INVALID_ARGUMENT_NAME, elementId, null); 
    }
    
    // 2๏ธโƒฃ
    private void parseArgumentStrings(List<String> argsList) throws ArgsException {
        for (currentArgument = argsList.listIterator(); currentArgument.hasNext();) {
            String argString = currentArgument.next(); 
            if (argString.startsWith("-")) {
                parseArgumentCharacters(argString.substring(1)); 
            } else {
                currentArgument.previous();
                break; 
            }
        } 
    }
    
    private void parseArgumentCharacters(String argChars) throws ArgsException { 
        for (int i = 0; i < argChars.length(); i++)
        parseArgumentCharacter(argChars.charAt(i)); 
    }
    
    private void parseArgumentCharacter(char argChar) throws ArgsException { 
        ArgumentMarshaler m = marshalers.get(argChar);
            if (m == null) {
                throw new ArgsException(UNEXPECTED_ARGUMENT, argChar, null); 
            } else {
                argsFound.add(argChar); 
            try {
                m.set(currentArgument); 
            } catch (ArgsException e) {
                e.setErrorArgumentId(argChar);
                throw e; 
            }
        } 
    }
    
    public boolean has(char arg) { 
        return argsFound.contains(arg);
    }
    
    public int nextArgument() {
        return currentArgument.nextIndex();
    }
    
    public boolean getBoolean(char arg) {
        return BooleanArgumentMarshaler.getValue(marshalers.get(arg));
    }
    
    public String getString(char arg) {
        return StringArgumentMarshaler.getValue(marshalers.get(arg));
    }
    
    public int getInt(char arg) {
        return IntegerArgumentMarshaler.getValue(marshalers.get(arg));
    }
    
    public double getDouble(char arg) {
        return DoubleArgumentMarshaler.getValue(marshalers.get(arg));
    }
    
    public String[] getStringArray(char arg) {
        return StringArrayArgumentMarshaler.getValue(marshalers.get(arg));
    } 
}
 

์œ„์—์„œ ์•„๋ž˜๋กœ ์ฝ”๋“œ๊ฐ€ ๋ฌผ ํ๋ฅด๋“ฏ ์ฝํžŒ๋‹ค. ๋ฐ”๊นฅ์ชฝ์—์„œ ์‚ฌ์šฉํ•˜๋Š” method๋Š” ์ตœ์ƒ๋‹จ์— ์œ„์น˜ํ•ด์žˆ๊ณ , ์•„๋ž˜๋กœ ๋‚ด๋ ค๊ฐˆ ์ˆ˜๋ก ๊ตฌ์ฒด์ ์ธ ๊ตฌํ˜„์ด ๋‚˜์—ด๋˜์–ด ์žˆ๋‹ค. ํ•œ ๊ฐ€์ง€ ๋จผ์ € ์ฝ์–ด๋ณผ ์ฝ”๋“œ๊ฐ€ ์žˆ๋‹ค๋ฉด ArgumentMarshaler ์ •์˜์ด๋‹ค. ์•„๋ž˜์— ์ •์˜๋˜์–ด ์žˆ๋Š” ArgumentMarshaler๊ฐ€ ๋”ฐ๋ฅด๋Š” interface์ด๋‹ค.

ArgumentMarshaler.java

public interface ArgumentMarshaler {
    void set(Iterator<String> currentArgument) throws ArgsException;
}

BooleanArgumentMarshaler.java

public class BooleanArgumentMarshaler implements ArgumentMarshaler { 
    private boolean booleanValue = false;
    
    public void set(Iterator<String> currentArgument) throws ArgsException { 
        booleanValue = true;
    }
    
    public static boolean getValue(ArgumentMarshaler am) {
        if (am != null && am instanceof BooleanArgumentMarshaler)
            return ((BooleanArgumentMarshaler) am).booleanValue; 
        else
            return false; 
    }
}

StringArgumentMarshaler.java

import static com.objectmentor.utilities.args.ArgsException.ErrorCode.*;
 
public class StringArgumentMarshaler implements ArgumentMarshaler { 
    private String stringValue = "";
    
    public void set(Iterator<String> currentArgument) throws ArgsException { 
        try {
            stringValue = currentArgument.next(); 
        } catch (NoSuchElementException e) {
            throw new ArgsException(MISSING_STRING); 
        }
    }
    
    public static String getValue(ArgumentMarshaler am) {
        if (am != null && am instanceof StringArgumentMarshaler)
            return ((StringArgumentMarshaler) am).stringValue; 
        else
            return ""; 
    }
}

IntegerArgumentMarshaler.java

import static com.objectmentor.utilities.args.ArgsException.ErrorCode.*;
 
public class IntegerArgumentMarshaler implements ArgumentMarshaler { 
    private int intValue = 0;
    
    public void set(Iterator<String> currentArgument) throws ArgsException { 
        String parameter = null;
        try {
            parameter = currentArgument.next();
            intValue = Integer.parseInt(parameter);
        } catch (NoSuchElementException e) {
            throw new ArgsException(MISSING_INTEGER);
        } catch (NumberFormatException e) {
            throw new ArgsException(INVALID_INTEGER, parameter); 
        }
    }
    
    public static int getValue(ArgumentMarshaler am) {
        if (am != null && am instanceof IntegerArgumentMarshaler)
            return ((IntegerArgumentMarshaler) am).intValue; 
        else
            return 0; 
    }
}

DoubleArgumentMarshaler, StringArrayArgumentMarshaler๋Š” ํŒจํ„ด์ด ๊ฐ™์•„ ์ƒ๋žตํ•œ๋‹ค. ํ•ด๋‹น ์ฝ”๋“œ๋ฅผ ๋ณด๋‹ค๊ฐ€ ์˜๋ฌธ์ด ๋“œ๋Š” ๊ฒƒ์ด ํ•˜๋‚˜ ์žˆ๋‹ค๋ฉด, ๋ฐ”๋กœ Error ์ฒ˜๋ฆฌ์ด๋‹ค.

ArgsException.java

import static com.objectmentor.utilities.args.ArgsException.ErrorCode.*;
 
public class ArgsException extends Exception { 
    private char errorArgumentId = '\0'; 
    private String errorParameter = null; 
    private ErrorCode errorCode = OK;
    
    public ArgsException() {}
    
    public ArgsException(String message) {super(message);}
    
    public ArgsException(ErrorCode errorCode) { 
        this.errorCode = errorCode;
    }
    
    public ArgsException(ErrorCode errorCode, String errorParameter) { 
        this.errorCode = errorCode;
        this.errorParameter = errorParameter;
    }
    
    public ArgsException(ErrorCode errorCode, char errorArgumentId, String errorParameter) {
        this.errorCode = errorCode; 
        this.errorParameter = errorParameter; 
        this.errorArgumentId = errorArgumentId;
    }
    
    public char getErrorArgumentId() { 
        return errorArgumentId;
    }
    
    public void setErrorArgumentId(char errorArgumentId) { 
        this.errorArgumentId = errorArgumentId;
    }
    
    public String getErrorParameter() { 
        return errorParameter;
    }
    
    public void setErrorParameter(String errorParameter) { 
        this.errorParameter = errorParameter;
    }
    
    public ErrorCode getErrorCode() { 
        return errorCode;
    }
    
    public void setErrorCode(ErrorCode errorCode) { 
        this.errorCode = errorCode;
    }
    
    public String errorMessage() { 
        switch (errorCode) {
        case OK:
            return "TILT: Should not get here.";
        case UNEXPECTED_ARGUMENT:
            return String.format("Argument -%c unexpected.", errorArgumentId);
        case MISSING_STRING:
            return String.format("Could not find string parameter for -%c.", errorArgumentId);
        case INVALID_INTEGER:
            return String.format("Argument -%c expects an integer but was '%s'.", errorArgumentId, errorParameter);
        case MISSING_INTEGER:
            return String.format("Could not find integer parameter for -%c.", errorArgumentId);
        case INVALID_DOUBLE:
            return String.format("Argument -%c expects a double but was '%s'.", errorArgumentId, errorParameter);
        case MISSING_DOUBLE:
            return String.format("Could not find double parameter for -%c.", errorArgumentId); 
        case INVALID_ARGUMENT_NAME:
            return String.format("'%c' is not a valid argument name.", errorArgumentId);
        case INVALID_ARGUMENT_FORMAT:
            return String.format("'%s' is not a valid argument format.", errorParameter);
        }
        return ""; 
    }
    
    public enum ErrorCode {
        OK, INVALID_ARGUMENT_FORMAT, UNEXPECTED_ARGUMENT, INVALID_ARGUMENT_NAME, 
        MISSING_STRING, MISSING_INTEGER, INVALID_INTEGER, MISSING_DOUBLE, INVALID_DOUBLE
    }
}

์ •๋ฆฌ

๊ต‰์žฅํžˆ ๋‹จ์ˆœํ•œ ๊ฐœ๋…์ž„์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ์ฝ”๋“œ๊ฐ€ ๋งŽ์ด ํ•„์š”ํ•˜๋‹ค. ์ด๋ฅผ ์ •์  ํƒ€์ž… ์–ธ์–ด์ธ ์ž๋ฐ”๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ด๋‹ค. (Swift๋Š” ๋”ํ• ์ง€๋„) ํ•˜์ง€๋งŒ ์ฝ์–ด๋ณด๋ฉด ์•Œ๊ฒ ์ง€๋งŒ ๊ต‰์žฅํžˆ ๊ตฌ์กฐ๋„ ๊น”๋”ํ•˜๊ณ  ์ž˜์งœ์ธ ํ”„๋กœ๊ทธ๋žจ์ด๋‹ค.

์ธ์ˆ˜ ์œ ํ˜•์„ ์ถ”๊ฐ€ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ArgumentMarshaler ๋ฅผ ์ฑ„ํƒํ•˜์—ฌ get ํ•จ์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ , parseSchemaElement ํ•จ์ˆ˜์— case ํ•˜๋‚˜๋งŒ ์ถ”๊ฐ€ํ•˜๋ฉด ๋œ๋‹ค.

์–ด๋–ป๊ฒŒ ์งฐ๋Š๋ƒ๊ณ ?

์ฒ˜์Œ๋ถ€ํ„ฐ ์ด๋ ‡๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์—ˆ์„๊นŒ? ์•„๋‹ˆ๋‹ค. ์ ์ง„์ ์œผ๋กœ ๊ฐœ์„ ํ–ˆ๋‹ค. ํ”„๋กœ๊ทธ๋ž˜๋ฐ์€ ๊ณผํ•™๋ณด๋‹ค ๊ณต์˜ˆ(craft)์— ๊ฐ€๊น๋‹ค. ๊นจ๋—ํ•œ ์ฝ”๋“œ๋ฅผ ์งœ๋ ค๋ฉด ๋จผ์ € ์ง€์ €๋ถ„ํ•œ ์ฝ”๋“œ๋ฅผ ์ง  ๋’ค์— ์ •๋ฆฌํ•ด์•ผ ํ•œ๋‹ค๋Š” ์˜๋ฏธ์ด๋‹ค.

๋Œ€๋‹ค์ˆ˜์˜ ์‹ ์ฐธ ํ”„๋กœ๊ทธ๋ž˜๋จธ๋Š” (๋Œ€๋‹ค์ˆ˜ ์ดˆ๋”ฉ๊ณผ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ) ์ด ์ถฉ๊ณ ๋ฅผ ์ถฉ์‹คํžˆ ๋”ฐ๋ฅด์ง€ ์•Š๋Š”๋‹ค. ๊ทธ๋“ค์€ ๋ฌด์กฐ๊ฑด ๋Œ์•„๊ฐ€๋Š” ํ”„๋กœ๊ทธ๋žจ์„ ๋ชฉํ‘œ๋กœ ์žก๋Š”๋‹ค. ์ผ๋‹จ ํ”„๋กœ๊ทธ๋žจ์ด โ€˜๋Œ์•„๊ฐ€๋ฉดโ€™ ๋‹ค์Œ ์—…๋ฌด๋กœ ๋„˜์–ด๊ฐ„๋‹ค. โ€˜๋Œ์•„๊ฐ€๋Š”โ€™ ํ”„๋กœ๊ทธ๋žจ์€ ๊ทธ ์ƒํƒœ๊ฐ€ ์–ด๋–ป๋“  ๊ทธ๋Œ€๋กœ ๋ฒ„๋ ค๋‘”๋‹ค. ๊ฒฝํ—˜์ด ํ’๋ถ€ํ•œ ์ „๋ฌธ ํ”„๋กœ๊ทธ๋ž˜๋จธ๋ผ๋ฉด ์ด๋Ÿฐ ํ–‰๋™์ด ์ „๋ฌธ๊ฐ€๋กœ์„œ ์ž์‚ด ํ–‰์œ„๋ผ๋Š” ์‚ฌ์‹ค์„ ์ž˜ ์•ˆ๋‹ค.

Args: 1์ฐจ ์ดˆ์•ˆ

๊ทธ๋Ÿผ ์ฒ˜์Œ์˜ ์ด ์ฝ”๋“œ๊ฐ€ ์–ด๋• ๋Š”์ง€ ํ™•์ธํ•ด๋ณด์ž.

import java.text.ParseException; 
import java.util.*;
 
public class Args {
    private String schema;
    private String[] args;
    private boolean valid = true;
    private Set<Character> unexpectedArguments = new TreeSet<Character>(); 
    private Map<Character, Boolean> booleanArgs = new HashMap<Character, Boolean>();
    private Map<Character, String> stringArgs = new HashMap<Character, String>(); 
    private Map<Character, Integer> intArgs = new HashMap<Character, Integer>(); 
    private Set<Character> argsFound = new HashSet<Character>();
    private int currentArgument;
    private char errorArgumentId = '\0';
    private String errorParameter = "TILT";
    private ErrorCode errorCode = ErrorCode.OK;
    
    private enum ErrorCode {
        OK, MISSING_STRING, MISSING_INTEGER, INVALID_INTEGER, UNEXPECTED_ARGUMENT}
        
    public Args(String schema, String[] args) throws ParseException { 
        this.schema = schema;
        this.args = args;
        valid = parse();
    }
    
    private boolean parse() throws ParseException { 
        if (schema.length() == 0 && args.length == 0)
            return true; 
            parseSchema(); 
        try {
            parseArguments();
        } catch (ArgsException e) {
 
        }
        return valid;
    }
    
    private boolean parseSchema() throws ParseException { 
        for (String element : schema.split(",")) {
            if (element.length() > 0) {
                String trimmedElement = element.trim(); 
                parseSchemaElement(trimmedElement);
            } 
        }
        return true; 
    }
    
    private void parseSchemaElement(String element) throws ParseException { 
        char elementId = element.charAt(0);
        String elementTail = element.substring(1); 
        validateSchemaElementId(elementId);
        if (isBooleanSchemaElement(elementTail)) 
            parseBooleanSchemaElement(elementId);
        else if (isStringSchemaElement(elementTail)) 
            parseStringSchemaElement(elementId);
        else if (isIntegerSchemaElement(elementTail)) 
            parseIntegerSchemaElement(elementId);
        else
            throw new ParseException(String.format("Argument: %c has invalid format: %s.", elementId, elementTail), 0);
        } 
    }
        
    private void validateSchemaElementId(char elementId) throws ParseException { 
        if (!Character.isLetter(elementId)) {
            throw new ParseException("Bad character:" + elementId + "in Args format: " + schema, 0);
        }
    }
    
    private void parseBooleanSchemaElement(char elementId) { 
        booleanArgs.put(elementId, false);
    }
    
    private void parseIntegerSchemaElement(char elementId) { 
        intArgs.put(elementId, 0);
    }
    
    private void parseStringSchemaElement(char elementId) { 
        stringArgs.put(elementId, "");
    }
    
    private boolean isStringSchemaElement(String elementTail) { 
        return elementTail.equals("*");
    }
    
    private boolean isBooleanSchemaElement(String elementTail) { 
        return elementTail.length() == 0;
    }
    
    private boolean isIntegerSchemaElement(String elementTail) { 
        return elementTail.equals("#");
    }
    
    private boolean parseArguments() throws ArgsException {
        for (currentArgument = 0; currentArgument < args.length; currentArgument++) {
            String arg = args[currentArgument];
            parseArgument(arg); 
        }
        return true; 
    }
    
    private void parseArgument(String arg) throws ArgsException { 
        if (arg.startsWith("-"))
            parseElements(arg); 
    }
    
    private void parseElements(String arg) throws ArgsException { 
        for (int i = 1; i < arg.length(); i++)
            parseElement(arg.charAt(i)); 
    }
    
    private void parseElement(char argChar) throws ArgsException { 
        if (setArgument(argChar))
            argsFound.add(argChar); 
        else 
            unexpectedArguments.add(argChar); 
            errorCode = ErrorCode.UNEXPECTED_ARGUMENT; 
            valid = false;
    }
    
    private boolean setArgument(char argChar) throws ArgsException { 
        if (isBooleanArg(argChar))
            setBooleanArg(argChar, true); 
        else if (isStringArg(argChar))
            setStringArg(argChar); 
        else if (isIntArg(argChar))
            setIntArg(argChar); 
        else
            return false;
        
        return true; 
    }
    
    private boolean isIntArg(char argChar) {
        return intArgs.containsKey(argChar);
    }
    
    private void setIntArg(char argChar) throws ArgsException { 
        currentArgument++;
        String parameter = null;
        try {
            parameter = args[currentArgument];
            intArgs.put(argChar, new Integer(parameter)); 
        } catch (ArrayIndexOutOfBoundsException e) {
            valid = false;
            errorArgumentId = argChar;
            errorCode = ErrorCode.MISSING_INTEGER;
            throw new ArgsException();
        } catch (NumberFormatException e) {
            valid = false;
            errorArgumentId = argChar; 
            errorParameter = parameter;
            errorCode = ErrorCode.INVALID_INTEGER; 
            throw new ArgsException();
        } 
    }
    
    private void setStringArg(char argChar) throws ArgsException { 
        currentArgument++;
        try {
            stringArgs.put(argChar, args[currentArgument]); 
        } catch (ArrayIndexOutOfBoundsException e) {
            valid = false;
            errorArgumentId = argChar;
            errorCode = ErrorCode.MISSING_STRING; 
            throw new ArgsException();
        } 
    }
    
    private boolean isStringArg(char argChar) { 
        return stringArgs.containsKey(argChar);
    }
    
    private void setBooleanArg(char argChar, boolean value) { 
        booleanArgs.put(argChar, value);
    }
    
    private boolean isBooleanArg(char argChar) { 
        return booleanArgs.containsKey(argChar);
    }
    
    public int cardinality() { 
        return argsFound.size();
    }
    
    public String usage() { 
        if (schema.length() > 0)
            return "-[" + schema + "]"; 
        else
            return ""; 
    }
    
    public String errorMessage() throws Exception { 
        switch (errorCode) {
        case OK:
            throw new Exception("TILT: Should not get here.");
        case UNEXPECTED_ARGUMENT:
            return unexpectedArgumentMessage();
        case MISSING_STRING:
            return String.format("Could not find string parameter for -%c.", errorArgumentId);
        case INVALID_INTEGER:
            return String.format("Argument -%c expects an integer but was '%s'.", errorArgumentId, errorParameter);
        case MISSING_INTEGER:
            return String.format("Could not find integer parameter for -%c.", errorArgumentId);
        }
        return ""; 
    }
    
    private String unexpectedArgumentMessage() {
        StringBuffer message = new StringBuffer("Argument(s) -"); 
        for (char c : unexpectedArguments) {
            message.append(c); 
        }
        message.append(" unexpected.");
        
        return message.toString(); 
    }
    
    private boolean falseIfNull(Boolean b) { 
        return b != null && b;
    }
    
    private int zeroIfNull(Integer i) { 
        return i == null ? 0 : i;
    }
    
    private String blankIfNull(String s) { 
        return s == null ? "" : s;
    }
    
    public String getString(char arg) { 
        return blankIfNull(stringArgs.get(arg));
    }
    
    public int getInt(char arg) {
        return zeroIfNull(intArgs.get(arg));
    }
    
    public boolean getBoolean(char arg) { 
        return falseIfNull(booleanArgs.get(arg));
    }
    
    public boolean has(char arg) { 
        return argsFound.contains(arg);
    }
    
    public boolean isValid() { 
        return valid;
    }
    
    private class ArgsException extends Exception {
    } 
}

์ด๋Ÿฐ ์ฝ”๋“œ๋ฅผ ์ฒ˜์Œ๋ถ€ํ„ฐ ์˜๋„ํ•œ ๊ฒƒ์€ ์•„๋‹ˆ์—ˆ๋‹ค. ํ•จ์ˆ˜ ์ด๋ฆ„์ด๋‚˜ ๋ณ€์ˆ˜ ์ด๋ฆ„์„ ์„ ํƒํ•œ ๋ฐฉ์‹, ์–ด์„คํ”„์ง€๋งŒ ๋‚˜๋ฆ„๋Œ€๋กœ ๊ตฌ์กฐ๊ฐ€ ์žˆ๋‹ค๋Š” ์‚ฌ์‹ค ๋“ฑ์ด ๋…ธ๋ ฅ์˜ ์ฆ๊ฑฐ๋‹ค. ํ•˜์ง€๋งŒ ์–ด๋Š ์ˆœ๊ฐ„ ํ”„๋กœ๊ทธ๋žจ์€ ๋‚ด ์†์„ ๋ฒ—์–ด๋‚ฌ๋‹ค. ์ฒซ ๋ฒ„์ „์ด๋˜ Boolean ์ธ์ˆ˜๋งŒ ์ง€์›ํ•˜๋˜ ์ดˆ๊ธฐ ๋ฒ„์ „์—์„œ String๊ณผ Integer ์ธ์ˆ˜ ์œ ํ˜•์„ ์ถ”๊ฐ€ํ•˜๋ฉด์„œ ๋ถ€ํ„ฐ ์žฌ์•™์ด ์‹œ์ž‘๋๋‹ค.

๊ทธ๋ž˜์„œ ๋ฉˆ์ท„๋‹ค

์ถ”๊ฐ€ํ•  ์ธ์ˆ˜ ์œ ํ˜•์ด ์ ์–ด๋„ ๋‘ ๊ฐœ๋Š” ๋” ์žˆ์—ˆ์ง€๋งŒ, ์ด ์ƒํƒœ์—์„œ ์ถ”๊ฐ€ํ•˜๋ฉด ์ฝ”๋“œ๊ฐ€ ๋” ๋‚˜๋น ์งˆ ๊ฒƒ์ด๋ผ๋Š” ์‚ฌ์‹ค์ด ์ž๋ช…ํ–ˆ๋‹ค. ๊ณ„์† ๋ฐ€์–ด๋ถ™์ด๋ฉด ํ”„๋กœ๊ทธ๋žจ์€ ์–ด๋–ป๊ฒŒ๋“  ์™„์„ฑํ•˜๊ฒ ์ง€๋งŒ ๊ทธ๋žฌ๋‹ค๊ฐ€๋Š” ๋„ˆ๋ฌด ์ปค์„œ ์†๋Œ€๊ธฐ ์–ด๋ ค์šด ๊ณจ์นซ๊ฑฐ๋ฆฌ๊ฐ€ ์ƒ๊ฒจ๋‚  ์ฐธ์ด์—ˆ๋‹ค. ์ฝ”๋“œ ๊ตฌ์กฐ๋ฅผ ์œ ์ง€๋ณด์ˆ˜ํ•˜๊ธฐ ์ข‹์€ ์ƒํƒœ๋กœ ๋งŒ๋“ค๋ ค๋ฉด ์ง€๊ธˆ์ด ์ ๊ธฐ๋ผ ํŒ๋‹จํ–ˆ๋‹ค.

์ด๋Ÿฐ ํƒ€์ด๋ฐ์€ ์‚ฌ์‹ค ์šฐ๋ฆฌ๊ฐ€ ์ฝ”๋“œ๋ฅผ ์งœ๋ฉด์„œ๋„ ์€์—ฐ์ค‘์— ๋Š๋ผ๋Š” ํฌ์ธํŠธ์ด๋‹ค. ์ฆ‰, ์ง€๊ธˆํ–ˆ๋˜ ๋ฐฉ์‹์„ ๊ณ ์ง‘ํ–ˆ์„ ๋•Œ ์–ป๋Š” ํšจ์šฉ๋ณด๋‹ค, ์ข‹์€ ๊ตฌ์กฐ๋ฅผ ์„ ํƒํ•˜์—ฌ ์ง„ํ–‰ํ–ˆ์„ ๋•Œ ์–ป๋Š” ํšจ์šฉ์ด ํฌ๋‹ค๊ณ  ์ง๊ด€์ ์œผ๋กœ ๋Š๋ผ๋Š” ์ง€์ ์ด๋‹ค. ๋ฆฌํŒฉํ† ๋ง์„ ํ•˜๋ฉด์„œ๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด์„œ ๋Š๊ผˆ๋˜ ์ ์„ ์ •๋ฆฌํ–ˆ๋‹ค.

  1. ์ธ์ˆ˜ ์œ ํ˜•์„ ์ถ”๊ฐ€ํ•˜๋ฉด์„œ, ์ƒˆ ์ธ์ˆ˜ ์œ ํ˜•์„ ์ถ”๊ฐ€ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์ฃผ์š” ์ง€์  ์„ธ๊ณณ์— ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค๋Š” ์‚ฌ์‹ค์„ ์•Œ์•˜๋‹ค.
  2. ๋ช…๋ นํ–‰ ์ธ์ˆ˜์—์„œ ์ธ์ˆ˜ ์œ ํ˜•์„ ๋ถ„์„ํ•ด์„œ ์ง„์งœ ์œ ํ˜•์œผ๋กœ ๋ณ€๊ฒฝํ•ด์•ผ ํ•œ๋‹ค.
  3. get~ ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ์ง„์งœ ์œ ํ˜•์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

์ด๋Ÿฌํ•œ ์กฐ๊ฑด์„ ๊ธฐ๋ฐ˜์œผ๋กœ ArgumentMarshaler๋ผ๋Š” ๊ฐœ๋…์„ ๋งŒ๋“ค์—ˆ๋‹ค.

์ ์ง„์ ์œผ๋กœ ๊ฐœ์„ ํ•˜๋‹ค

ํ”„๋กœ๊ทธ๋žจ์„ ๋ง์น˜๋Š” ๊ฐ€์žฅ ์ข‹์€ ๋ฐฉ๋ฒ• ์ค‘ ํ•˜๋‚˜๋Š” ๊ฐœ์„ ์ด๋ผ๋Š” ์ด๋ฆ„ ์•„๋ž˜ ๊ตฌ์กฐ๋ฅผ ํฌ๊ฒŒ ๋’ค์ง‘๋Š” ํ–‰์œ„๋‹ค. ์ด๋Ÿฌํ•œ ์ ์—์„œ TDD๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค. ์ฆ‰, ์–ธ์ œ๋ผ๋„ ์‹œ์Šคํ…œ์ด ๋Œ์•„๊ฐ€์•ผ ํ•œ๋‹ค๋Š” ์›์น™์„ ์ ์šฉํ•˜๊ธฐ ์œ„ํ•œ ์˜์ง€๋‹ค.

๋ณ€๊ฒฝ ์ „ํ›„์˜ ์‹œ์Šคํ…œ์˜ ์˜ค์ž‘๋™ ์—ฌ๋ถ€๋ฅผ ๊ฒ€์ฆํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์–ธ์ œ๋“  ์‹คํ–‰์ด ๊ฐ€๋Šฅํ•œ Test Suit๊ฐ€ ํ•„์š”ํ•˜๋‹ค. ์ด๋ฅผ ์œ„ํ•ด ํ˜„์žฌ Class์˜ ๋™์ž‘์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” test case๋ฅผ ๋งŒ๋“ค์–ด ๋†“์•˜๋‹ค. ์ด ์ฝ”๋“œ๋“ค์„ ํ†ต๊ณผํ•œ๋‹ค๋ฉด ์˜ฌ๋ฐ”๋กœ ๋™์ž‘ํ•œ๋‹ค๊ณ  ๋ด๋„ ๋ฌด๋ฐฉํ–ˆ๋‹ค.

์ด ๋‹ค์Œ์—์•ผ ์‹œ์Šคํ…œ์— ๋ณ€๊ฒฝ์„ ๊ฐ€ํ•˜๊ธฐ ์‹œ์ž‘ํ–ˆ๋‹ค. ๋จผ์ €, ArgumentMarshaler์˜ ๊ณจ๊ฒฉ์„ ์ถ”๊ฐ€ํ–ˆ๋‹ค.

private class ArgumentMarshaler { 
    private boolean booleanValue = false;
 
    public void setBoolean(boolean value) { 
        booleanValue = value;
    }
    
    public boolean getBoolean() {return booleanValue;} 
}
 
private class BooleanArgumentMarshaler extends ArgumentMarshaler { }
private class StringArgumentMarshaler extends ArgumentMarshaler { }
private class IntegerArgumentMarshaler extends ArgumentMarshaler { }

๊ทธ ๋‹ค์Œ์œผ๋กœ

// AS-IS
 
public class Args {
    private Map<Character, Boolean> booleanArgs = new HashMap<Character, Boolean>();
 
    ... 
 
    private void parseBooleanSchemaElement(char elementId) { 
        booleanArgs.put(elementId, false);
    }
 
    ...
 
    private void setBooleanArg(char argChar, boolean value) { 
        booleanArgs.put(argChar, value);
    }
 
    ...
 
    public boolean getBoolean(char arg) { 
        return falseIfNull(booleanArgs.get(arg));
    }
}
 
// TO-BE
public class Args {
    private Map<Character, ArgumentMarshaler> boolean booleanArgs = new HashMap<Character, ArgumentMarshaler>();
 
    ... 
 
    private void parseBooleanSchemaElement(char elementId) {
        booleanArgs.put(elementId, new BooleanArgumentMarshaler());
    }
    
    ...
 
    private void setBooleanArg(char argChar, boolean value) {
        booleanArgs.get(argChar).setBoolean(value);
    }
    
    ...
 
    public boolean getBoolean(char arg) {
        Args.ArgumentMarshaler am = booleanArgs.get(arg);
        return am != null && am.getBoolean();
    }
}

String ์ธ์ˆ˜

์ฒ˜์Œ Bool์ด ํ†ต๊ณผํ–ˆ๋‹ค๋ฉด String ์ฒ˜๋ฆฌ๋Š” ๊ฐ„๋‹จํ•˜๋‹ค. ์œ ์‚ฌํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•ด์ฃผ๋ฉด ๋œ๋‹ค. parse~, set~, get~ ํ•จ์ˆ˜๋ฅผ ์ƒˆ๋กœ ๋งŒ๋“  ArguementMarshaler๋ฅผ ๊ตฌํ˜„ํ•œ StringArguementMarshaler๋กœ ํƒœ์ฒดํ•˜์ž.

์ด๋ ‡๊ฒŒ ์œ ์‚ฌํ•œ ๋ถ€๋ถ„์„ ์ฐพ์•„์„œ ๊ตฌํ˜„์„ ์™„๋ฃŒํ–ˆ๋‹ค๋ฉด ๋‹ค์Œ ๋ฌธ์ œ๋Š” Exception์ด๋‹ค. Exception์€ ์ •๋ง ๊ผด๋ดฌ๊ธฐ ์‹ซ์€ ์ฝ”๋“œ์ด๋‹ค. ์ž˜ ์‚ดํŽด๋ณด๋ฉด, Exception ์ฝ”๋“œ๋Š” Args์— ์†ํ•˜์ง€ ์•Š๋Š” ์ฝ”๋“œ์ด๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ์ด ๋ชจ๋“  ์˜ˆ์™ธ๋ฅผ ํ•œ๋ฐ๋กœ ๋ชจ์•„ Args ๋ชจ๋“ˆ๊ณผ ์™„๋ฒฝํ•˜๊ฒŒ ๋ถ„๋ฆฌ์‹œํ‚ค์ž. ์ด๋ ‡๊ฒŒ ํ•ด์„œ ์œ„์˜ ์ฝ”๋“œ๊ฐ€ ๋‚˜์˜ค๊ฒŒ ๋˜์—ˆ๋‹ค.

๊ฒฐ๋ก 

๊ทธ์ € ๋Œ์•„๊ฐ€๋Š” ์ฝ”๋“œ๋งŒ์œผ๋กœ๋Š” ๋ถ€์กฑํ•˜๋‹ค. ๋‹จ์ˆœํžˆ ๋Œ์•„๊ฐ€๋Š” ์ฝ”๋“œ์— ๋งŒ์กฑํ•˜๋Š” ํ”„๋กœ๊ทธ๋ž˜๋จธ๋Š” ์ „๋ฌธ๊ฐ€ ์ •์‹ ์ด ๋ถ€์กฑํ•˜๋‹ค. ๋‚˜์œ ์ฝ”๋“œ๋งŒํผ ์ง€๋ฐฐ์ ์œผ๋กœ ๊ฐœ๋ฐœ ํ”„๋กœ์ ํŠธ์— ์•…์˜ํ–ฅ์„ ๋ฏธ์น˜๋Š” ์š”์ธ๋„ ์—†๋‹ค. ๋‚˜์œ ์ผ์ •์€ ๋‹ค์‹œ ์งœ๋ฉด ๋œ๋‹ค. ๋‚˜์œ ์š”๊ตฌ์‚ฌํ•ญ์€ ๋‹ค์‹œ ์ •์˜ํ•˜๋ฉด ๋œ๋‹ค. ๋‚˜์œ ํŒ€ ์—ญํ•™์€ ๋ณต๊ตฌํ•˜๋ฉด ๋œ๋‹ค. ํ•˜์ง€๋งŒ ๋‚˜์œ ์ฝ”๋“œ๋Š” ์ฉ์–ด ๋ฌธ๋“œ๋Ÿฌ์ง„๋‹ค. ์ ์  ๋ฌด๊ฒŒ๊ฐ€ ๋Š˜์–ด๊ฐ€ ๋ฐœ๋ชฉ์„ ์žก๋Š”๋‹ค.

์ฝ”๋“œ๋„ ์ •๋ฆฌํ•  ์ˆ˜๋Š” ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ๋น„์šฉ์ด ํฌ๋‹ค. ์˜ค๋ž˜๋œ ์˜์กด์„ฑ์„ ๊นจ๊ธฐ ์œ„ํ•ด์„œ๋Š” ์ƒ๋‹นํ•œ ์‹œ๊ฐ„๊ณผ ์ธ๋‚ด์‹ฌ์ด ๋“ ๋‹ค. ๋ฐ˜๋ฉด ์ฒ˜์Œ๋ถ€ํ„ฐ ๊นจ๋—ํ•œ ์ƒํƒœ๋กœ ์œ ์ง€ํ•˜๋ ค๋Š” ์Šต๊ด€์„ ๊ฐ–๋Š”๋‹ค๋ฉด ์–ด๋ ต์ง€ ์•Š๋‹ค. ์–ธ์ œ๋‚˜ ์ฝ”๋“œ๋Š” ์ตœ๋Œ€ํ•œ ๊น”๋”ํ•˜๊ณ  ๋‹จ์ˆœํ•˜๊ฒŒ ์ •๋ฆฌํ•˜์ž.

Reference