Skip to main content

이정우

1. 📌 핵심 개념 정리

✅ 요약하기

각자 해당 챕터에서 중요하다고 느낀 개념이나 아이디어를 간략하게 정리하고 개선 전, 후에 대한 예시 코드를 비교하며 개념을 설명합니다.

  1. Args 줄 요약 내용구현
  • 개선종종 명령행 인수의 구문을 분석할 필요가 생긴다.
  • 적절한 유틸리티가 없는 경우 직접 작성하게 된다.
  • Args는 생성자에 인수 문자열과 형식 문자열을 넘겨 Args 인스턴스 생성 후 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));
 } 
}

  • 위에서 아래로 코드가 바로 읽힌다.
  • 기본적으로 자바는 정적타입 언어이기 떄문에 타입 시스템을 만족하려면 많은 단어가 필요하다.
  • 어쩌면 프로그래밍은 과학보다 공예에 가까울지도 모른다.
  • 깨끗한 코드를 작성하기 위해서는 지저분한 코드를 작성하고 정리해야 한다.
  • 초안작성 -> 수정반복 -> 최종안 완성 의 단계적 개선 중요하다.

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 {
  } 
}
  • 코드는 정상적으로 돌아가지만, 엉망이다.

  • 인스턴스 변수의 개수가 압도적으로 많다.

  • TITL 같은 희한한 문자열, HashSets,Tree, 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);
       }
     }
    
    • 완벽하진 않지만 나름 괜찮은 코드이다.
    • 간결하고 단순하며, 이해하기가 쉽다.
    • 하지만 이후 엉망이 될 것만 같은 요소들이 눈에 띈다.
  • String, Integer 인수 유형 추가하면 코드가 엉망이 된다.

  • 새로운 인수 유형작성합니추가하려면 주요 지점 세 곳에 코드를 추가해야 한다.

    • 인수 유형에 해당하는 HashMap 선택을 위해 스키마 요소의 구문을 분석한다.
    • 명령 행 인수에서 인수 유형을 분석해 진짜 유형으로 변환한다.
    • getXXX 메서드를 구현해 호출자에게 진짜 유형을 반환한다.
      이를 바탕으로 `ArgumentMarshaler` 념의 탄생.```
      

점진적으로 개선하다.

  • 개선이라는 명목 하에 구조를 크게 개선하는 행위는 프로그램을 망친다.
  • 테스트 주도 개발 (Test-Driven Development,TDD)
    • 언제나 시스템이 돌아가야 한다는 원칙을 따른다.
    • 시스템을 망가뜨리는 변경을 허용하지 않는다.

결론

  • 그저 돌아가는 코드만으로는 부족하다.
  • 개선 후의

  • 나쁜 코드 개선하는 것은 비용과 시간의 지출이 크다.
  • 깨끗한 코드를 유지하는 것은 상대적으로 쉽다.
  • 코드의 개선은 최대한 설명을빠른 작성합니다.


    1. 한 줄 요약시일
      .
      진행해야 .
      한다.
  • 코드는
    언제나
    1. 최대깔끔하고 요약단순하게 내용
      정리해야 .
      .
      한다.

2. 🤔 이해가 어려운 부분

🔍 질문하기

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

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

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

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

3. 📚 참고 사항

📢 논의하기

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

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

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