SerialDate 리팩터링(2)

책너두 5기 38일차 로버트 C. 마틴의 클린코드 p. 352~ p.362 내용정리 16. SerialDate 리팩터링 리팩터링의 기술 명명법을 통해 명확히 메서드와 함수의 역할을 파악할 수 있도록 한다. 불필요한 주석은 삭제한다. 변수의 public/private 필요 여부를 파악한다. 변수는 최대한 사용 위치에 가깝에 옮긴다. 읽고나서 JAVA는 어려워.

2023년 9월 19일 · 1 분 · 배준수

SerialDate 리팩터링(1)

책너두 5기 37일차 로버트 C. 마틴의 클린코드 p. 344~ p.351 내용정리 16. SerialDate 리팩터링 남의 코드를 리팩터링 하는 것은 편안하게 여겨야 할 활동이다. 남이 내게 해준다면 감사히 반겨야 할 호라동이다. 비판이 있어야만 발전이 가능하다. 첫째, 돌려보자 테스트 케이스가 모든 경우를 점검하는지 확인하자. 필요하다면 단위 테스트가 실행하는 코드와 실행하지 않는 코드를 조사해보자. 높은 테스트 커버리지를 달성하자. 둘째, 고쳐보자 코드를 고칠때마다 단위 테스트를 실행해서 확인하자. 읽고 나서 오늘도 리팩터링은 계속 된다.

2023년 9월 18일 · 1 분 · 배준수

JUnit 들여다보기(2)

책너두 5기 36일차 로버트 C. 마틴의 클린코드 p. 333~ p.342 내용정리 15. JUnit 들여다보기 리팩터링 하기 명명법 제대로 해서 이해하기 쉽게 하기 숨겨진 시간적인 결합(hidden temporal coupling) 확인하기 고치면서 조건문의 조건을 변겨앻야 하는지 확인하기 연산자에 등호 포함 여부 확인하기 세상에 개선이 불필요한 모듈은 없다. 코드를 처음보다 조금 더 깨끗하게 만드는 책임은 우리 모두에게 있다.

2023년 9월 16일 · 1 분 · 배준수

JUnit 들여다보기(1)

책너두 5기 35일차 로버트 C. 마틴의 클린코드 p. 324~ p.332 34일차는 없습니다. 내용정리 15. JUnit 들여다보기 JUnit : 가장 유명한 자바 프레임워크 JUnit 프레임워크 과거의 모듈을 리팩토링해보자. ComparisonCompactor 모듈은 문자열 비교 오류를 파악할 때 쓴다. 원본은 다음과 같다. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 package junit.framework; public class ComparisonCompactor { private static final String ELLIPSIS = "..."; private static final String DELTA_END = "]"; private static final String DELTA_START = "["; private int fContextLength; private String fExpected; private String fActual; private int fPrefix; private int fSuffix; public ComparisonCompactor(int contextLength, String expected, String actual) { fContextLength = contextLength; fExpected = expected; fActual = actual; } public String compact(String message) { if (fExpected == null || fActual == null || areStringsEqual()) { return Assert.format(message, fExpected, fActual); } findCommonPrefix(); findCommonSuffix(); String expected = compactString(fExpected); String actual = compactString(fActual); return Assert.format(message, expected, actual); } private String compactString(String source) { String result = DELTA_START + source.substring(fPrefix, source.length() - fSuffix + 1) + DELTA_END; if (fPrefix > 0) { result = computeCommonPrefix() + result; } if (fSuffix > 0) { result = result + computeCommonSuffix(); } return result; } private void findCommonPrefix() { fPrefix = 0; int end = Math.min(fExpected.length(), fActual.length()); for (; fPrefix < end; fPrefix++) { if (fExpected.charAt(fPrefix) != fActual.charAt(fPrefix)) { break; } } } private void findCommonSuffix() { int expectedSuffix = fExpected.length() - 1; int actualSuffix = fActual.length() - 1; for (; actualSuffix >= fPrefix && expectedSuffix >= fPrefix; actualSuffix--, expectedSuffix--) { if (fExpected.charAt(expectedSuffix) != fActual.charAt(actualSuffix)) { break; } } fSuffix = fExpected.length() - expectedSuffix; } private String computeCommonPrefix() { return (fPrefix > fContextLength ? ELLIPSIS : "") + fExpected.substring(Math.max(0, fPrefix - fContextLength), fPrefix); } private String computeCommonSuffix() { int end = Math.min(fExpected.length() - fSuffix + 1 + fContextLength, fExpected.length()); return fExpected.substring(fExpected.length() - fSuffix + 1, end) + (fExpected.length() - fSuffix + 1 < fExpected.length() - fContextLength ? ELLIPSIS : ""); } private boolean areStringsEqual() { return fExpected.equals(fActual); } } 가장 먼저 멤버 변수 앞에 붙인 접두어 f를 삭제하자. 과거의 습관이다. 이후 compact 함수 시작부에 캡슐화되지 않은 조건문을 메서드로 뽑아내 적절한 이름을 붙인다. f를 빼면서 생긴 비슷한 이름들을 다시 명명한다. if 부정문을 긍정문으로 바꾸어 가독성을 높인다. ...

2023년 9월 15일 · 3 분 · 배준수

점진적인 개선(5)

책너두 5기 33일차 로버트 C. 마틴의 클린코드 p. 305~ p.321 내용정리 14. 점진적인 개선 지금까지의 리팩터링 결과 인수 유형을 추가하는 것이 매우 쉬워졌다. 이를 확인하기 위해 우선 시스템이 double 인수 유형을 제대로 받아들이는지 확인할 테스트 케이스를 추가한다. 1 2 3 4 5 6 7 public void testSimpleDoublePresent() throws Exception { Args args = new Args("x##", new String[]{"-x", "42.3"}); assertTrue(args.isValid()); assertEquals(1, args.cardinality()); assertTrue(args.has('x')); assertEquals(42.3, args.getDouble('x'), .001); } 스키마 구문분석 코드를 정리하고 ## 감지 코드를 추가한다. ##는 double 인수 유형을 뜻한다. ...

2023년 9월 13일 · 12 분 · 배준수

점진적인 개선(4)

책너두 5기 32일차 로버트 C. 마틴의 클린코드 p. 296~ p.304 내용정리 14. 점진적인 개선 1차 리팩터링 이후지만 여전히 고쳐야할 부분들이 많이 있다. 첫머리에 나오는 변수, setArgument에 일일이 유형을 확인하는 코드, set 함수들, 오류 처리 코드 등을 개선해야한다. setArgument에서 일일이 유형을 확인하는 코드 => ArgumentMarshaler.set만 호출하면 충분하도록 변경 => setIntArg, setStringArg, setBooleanArg를 해당 ArgumentMarshaler 파생 클래스로 내려야 함 하지만 setIntArg는 args와 currentArgument라는 인스턴스 변수 두 개가 쓰임. setIntarg를 intArgumentMArshaler로 내리려면 둘 다 변수로 내려야 하는데 코드가 지저분해 진다. args 배열을 list로 변환한 후 iterator를 set 함수로 전달하여 해결. ...

2023년 9월 12일 · 1 분 · 배준수

점진적인 개선(4)

책너두 5기 31일차 로버트 C. 마틴의 클린코드 p. 288~ p.295 내용정리 14. 점진적인 개선 1차 리팩터링한 결과물은 다음과 같다. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 package com.objectmentor.utilities.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, ArgumentMarshaler> marshalers = new HashMap<Character, ArgumentMarshaler>(); 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)) marshalers.put(elementId, new BooleanArgumentMarshaler()); else if (isStringSchemaElement(elementTail)) marshalers.put(elementId, new StringArgumentMarshaler()); else if (isIntegerSchemaElement(elementTail)) { marshalers.put(elementId, new IntegerArgumentMarshaler()); } 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 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 { ArgumentMarshaler m = marshalers.get(argChar); try { if (m instanceof BooleanArgumentMarshaler) setBooleanArg(m); else if (m instanceof StringArgumentMarshaler) setStringArg(m); else if (m instanceof IntegerArgumentMarshaler) setIntArg(m); else return false; } catch (ArgsException e) { valid = false; errorArgumentId = argChar; throw e; } return true; } private void setIntArg(ArgumentMarshaler m) throws ArgsException { currentArgument++; String parameter = null; try { parameter = args[currentArgument]; m.set(parameter); } catch (ArrayIndexOutOfBoundsException e) { errorCode = ErrorCode.MISSING_INTEGER; throw new ArgsException(); } catch (ArgsException e) { errorParameter = parameter; errorCode = ErrorCode.INVALID_INTEGER; throw e; } } private void setStringArg(ArgumentMarshaler m) throws ArgsException { currentArgument++; try { m.set(args[currentArgument]); } catch (ArrayIndexOutOfBoundsException e) { errorCode = ErrorCode.MISSING_STRING; throw new ArgsException(); } } private void setBooleanArg(ArgumentMarshaler m) { try { m.set(" true"); } catch (ArgsException e) { } } 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(); } public boolean getBoolean(char arg) { Args.ArgumentMarshaler am = marshalers.get(arg); boolean b = false; try { b = am != null && (Boolean) am.get(); } catch (ClassCastException e) { b = false; } return b; } public String getString(char arg) { Args.ArgumentMarshaler am = marshalers.get(arg); try { return am == null ? "" : (String) am.get(); } catch (ClassCastException e) { return ""; } } public int getInt(char arg) { Args.ArgumentMarshaler am = marshalers.get(arg); try { return am == null ? 0 : (Integer) am.get(); } catch (Exception e) { return 0; } } public boolean has(char arg) { return argsFound.contains(arg); } public boolean isValid() { return valid; } private class ArgsException extends Exception { } private abstract class ArgumentMarshaler { public abstract void set(String s) throws ArgsException; public abstract Object get(); } private class BooleanArgumentMarshaler extends ArgumentMarshaler { private boolean booleanValue = false; public void set(String s) { booleanValue = true; } public Object get() { return booleanValue; } } private class StringArgumentMarshaler extends ArgumentMarshaler { private String stringValue = ""; public void set(String s) { stringValue = s; } public Object get() { return stringValue; } } private class IntegerArgumentMarshaler extends ArgumentMarshaler { private int intValue = 0; public void set(String s) throws ArgsException { try { intValue = Integer.parseInt(s); } catch (NumberFormatException e) { throw new ArgsException(); } } public Object get() { return intValue; } } } 읽고나서 솔직히 java를 모르는 나는 이해할 리가 없지만.. 언젠가는.. ...

2023년 9월 11일 · 5 분 · 배준수

점진적인 개선(3)

책너두 5기 30일차 로버트 C. 마틴의 클린코드 p. 273~ p.288 내용정리 14. 점진적인 개선 String 인수 HashMap을 변경한 후 parse, set, get 함수를 고쳤다. 각 인수 유형을 처리하는 코드를 ArgumentMarshaler 클래스에 넣고 나서 ArgumentMarshaler 파생 클래스를 만들어 코드를 분리하는 것이 의도였다. 이 후 int 인수 기능을 ArgumentMarshaler로 옮겼다. 모든 논리를 ArgumentMarshaler로 옮긴 뒤 파생 클래스를 만들어 기능을 분산한다. setBoolean 함수를 BooleanArgumentMarshaler로 옮긴 후 올바로 호출되는지 확인한다. ArgumentMarshaler 클래스에 추상 메서드 set을 만든다. BooleanArgumentMarshaler 클래스에 set 메서드를 구현한다. sertBoolean 호출을 set 호출로 바꾼다. 테스트를 통과하면 ArgumentMarshaler에서 setBoolean 메서드를 제거한다. get 메서드를 BooleanArgumentMarshaler로 옮긴다. ArgumentMarshaler에서 get을 추상 메서드로 만든 후 BooleanArgumentMarshaler에 구현한다. ArgumentMarshaler에서 getBoolean 함수를 제거한 후 booleanValue를 BooleanArgumentMarshaler로 내려 private 변수로 선언한다. 같은 방식으로 Strings 인수 유형도 변경한다. 마지막으로 integer 인수 유형에도 같은 과정을 반복한다. 이제 isBooleanArg를 다음과 같이 변경한다 ...

2023년 9월 9일 · 2 분 · 배준수

점진적인 개선(2)

책너두 5기 29일차 로버트 C. 마틴의 클린코드 p. 255~ p.272 내용정리 14. 점진적인 개선 초안 작성 Args.java (Boolean만 지원하는 버전) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 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) { parseBooleanSchemaElement(element); } } private void parseBooleanSchemaElement(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 인수 유형을 추가하여 아래 코드가 된다. ...

2023년 9월 8일 · 8 분 · 배준수

점진적인 개선(1)

책너두 5기 28일차 로버트 C. 마틴의 클린코드 p. 246~ p.254 내용정리 14. 점진적인 개선 출발은 좋았으나 확장성이 부족했던 모듈을 소개하고, 이를 개선하고 정리하는 단계를 살펴본다. 프로그램을 짜다 보면 내 사정에 딱 맞는 유틸리티가 없어서 직접 짜게 된다. 이를 Args라고 부르겠다. 이 유틸리티는 명령행 인수의 구문을 분석하기 위해 main함수로 넘어오는 문자열 배열을 직접 분석하는 유틸리티다. Args 생성자에 (입력으로 들어온) 인수 문자열과 형식 문자열을 넘겨 Args 인스턴스를 생성한 후 Args 인스턴스에다 인수 값을 질의한다. 다음을 살펴보자 ...

2023년 9월 7일 · 6 분 · 배준수