Skip to main content

진소희

1. 📌 핵심 개념 정리

✅ 요약하기

프로그램을 짜다 보면 종종 명령행 인수의 구문을 분석할 필요가 생긴다. 편리한 유틸리티가 없다면 main 함수로 넘어오는 문자열 배열을 직접 분석하게 된다. 내 사정에 딱 맞는 유틸리티가 없다면? 직접 짜겠다고 결심한다. 새로 짤 유틸리티를 Args라 부르겠다.

  1. Args 구현
    • 잘 짜여진 Args 예시
 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);
     parseArgumentStrings(Arrays.asList(args)); 
   }

   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); 
   }

   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));
   } 
 }
  • 코드가 위에서 아래로 읽힌다.

  • 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 ""; 
      }
    }
    
  • 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
      }
    }
    
  • 자바는 정적 타입 언어라서 타입 시스템을 만족하려면 많은 단어가 필요하다.
  • 깨끗한 코드를 짜려면 먼저 지저분한 코드를 짠 뒤정리해야  설명을 작성합니다.


    1. Args: 1차 초안
      • 지저분한 Args 예시
         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 { } }
      • 이 코드는 미완성이고, 인스턴스 변수 개수도 압도적으로 많다.
        TILT와 같은 희한한 문자열, HashSets와 TreeSets, try-catch-catch블록 등 모두가 지저분한 코드에 기여하는 요인이다.
      • Boolean 인수만 지원하던 초기 버전 예시
         package com.objectmentor.utilities.getopts;
        
         import java.util.*;
        
         public class Args {
           private String schema;
           private String[] args;
           private boolean valid;
           private Set<Character> unexpectedArguments = new TreeSet<Character>();
           private Map<Character, Boolean> booleanArgs = new HashMap<Character, Boolean>();
           private int numberOfArguments = 0;
        
           public Args(String schema, String[] args) {
             this.schema = schema;
             this.args = args;
             valid = parse();
           }
        
           public boolean isValid() {
             return valid;
           }
        
           private boolean parse() {
             if (schema.length() == 0 && args.length == 0)
               return true;
             parseSchema();
             parseArguments();
             return unexpectedArguments.size() == 0;
           }
        
           private boolean parseSchema() {
             for (String element : schema.split(",")) {
               parseSchemaElement(element);
             }
             return true;
           }
        
           private void parseSchemaElement(String element) {
             if(element.length() == 1) {
               parseBooleanSchemaFlement(element);
             }
           }
        
           private void parseBooleanSchemaFlement(String element) {
             char c = element.charAt(0);
             if (Character.isLetter(c)) {
               booleanArgs.put(c, false);
             }
           }
        
           private boolean parseArguments() {
             for (String arg : args)
               parseArgument(arg);
             return true;
           }
        
           private void parseArgument(String arg) {
             if (arg.startsWith("-"))
               parseElements(arg);
           }
        
           private void parseElements(String arg) {
             for (int i = 1; i < arg.length(); i++)
               parseElement(arg.charAt(i));
           }
        
           private void parseElement(char argChar) {
             if (isBoolean(argChar)) {
               numberOfArguments++;
               setBooleanArg(argChar, true);
             } else
               unexpectedArguments.add(argChar);
           }
        
           private void setBooleanArg(char argChar, boolean value) {
             booleanArgs.put(argChar, value);
           }
        
           private boolean isBoolean(char argChar) {
             return booleanArgs.containsKey(argChar);
           }
        
           public int cardinality() {
             return numberOfArguments;
           }
        
           public String usage() {
             if (schema.length() > 0)
               return "-["+schema+"]";
             else
               return "";
           }
        
           public String errorMessage() {
             if (unexpectedArguments.size() > 0) {
               return unexpectedArgumentMessage();
             } else
               return "";
           }
        
           private String unexpectedArgumentMessage() {
             StringBuffer message = new StringBuffer("Argument(s) -");
             for (char c : unexpectedArguments) {
               message.append(c);
             }
             message.append(" unexpected.");
        
             return message.toString();
           }
        
           public boolean getBoolean(char arg) {
             return booleanArgs.get(arg);
           }
         }
        

    2. 🤔 이해가 어려운 부분

    🔍 질문하기

    책을 읽으며 이해하기 어려웠던 개념이나 명확하지 않았던 내용을 정리합니다.

    1. 개념 또는 원칙의 이름
      • 어려웠던 부분
        해당 개념이 헷갈리거나 명확하지 않았던 점을 구체적으로 설명합니다.
      • 궁금한 점
        해당 개념이 어떤 원리로 동작하는지, 실무에서 어떻게 활용되는지 등을 질문 형태로 정리합니다.

    1. 개념 또는 원칙의 이름
      • 어려웠던 부분
        .
      • 궁금한 점
        .

    1. 개념 또는 원칙의 이름
      • 어려웠던 부분
        .
      • 궁금한 점
        .

    3. 📚 참고 사항

    📢 논의하기

    관련된 자료가 있다면 공유하고, 더 깊이 논의하고 싶은 아이디어나 의견을 정리합니다.

    1. 관련 자료 공유
      • 추가 자료
        관련 블로그 글이나 공식 문서 링크를 제공합니다.

    1. 논의하고 싶은 주제
      • 주제
        논의하고 싶은 내용을 간략히 정리합니다.
      • 설명
        논의하고 싶은 이유를 작성합니다.