Skip to main content

진소희

1. 📌 핵심 개념 정리

✅ 요약하기

  1. JUnit 프레임워크
  • 문자열 비교 오류를 파악할 때 유용한 코드. Com-parisonCompactior라는 모듈로, 영리하게 짜인 코드이다. 아래는 테스트 코드이다.
  • ComparisonCompactorTest.java
    package junit.tests.framework;
    
    import junit.framework.ComparisonCompactor;
    import junit.framework.TestCase;
    
    public class ComparisonCompactorTest extends TestCase {
    
        public void testMessage() {
            String failure = new ComparisonCompactor(0, "b", "c").compact("a");
            assertTrue("a expected:<[b]> but was:<[c]>".equals(failure));
        }
    
        public void testStartSame() {
            String failure = new ComparisonCompactor(1, "ba", "bc").compact(null);
            assertEquals("expected:<b[a]> but was:<b[c]>", failure);
        }
    
        public void testEndSame() {
            String failure = new ComparisonCompactor(1, "ab", "cb").compact(null);
            assertEquals("expected:<[a]b> but was:<[c]b>", failure);
        }
    
        public void testSame() {
            String failure = new ComparisonCompactor(1, "ab", "ab").compact(null);
            assertEquals("expected:<ab> but was:<ab>", failure);
        }
    
        public void testNoContextStartAndEndSame() {
            String failure = new ComparisonCompactor(0, "abc", "adc").compact(null);
            assertEquals("expected:<...[b]...> but was:<...[d]...>", failure);
        }
    
        public void testStartAndEndContext() {
            String failure = new ComparisonCompactor(1, "abc", "adc").compact(null);
            assertEquals("expected:<a[b]c> but was:<a[d]c>", failure);
        }
    
        public void testStartAndEndContextWithEllipses() {
            String failure = new ComparisonCompactor(1, "abcde", "abfde").compact(null);
            assertEquals("expected:<...b[c]d...> but was:<...b[f]d...>", failure);
        }
    
        public void testComparisonErrorStartSameComplete() {
            String failure = new ComparisonCompactor(2, "ab", "abc").compact(null);
            assertEquals("expected:<ab[]> but was:<ab[c]>", failure);
        }
    
        public void testComparisonErrorEndSameComplete() {
            String failure = new ComparisonCompactor(0, "bc", "abc").compact(null);
            assertEquals("expected:<[]...> but was:<[a]...>", failure);
        }
    
        public void testComparisonErrorEndSameCompleteContext() {
            String failure = new ComparisonCompactor(2, "bc", "abc").compact(null);
            assertEquals("expected:<[]bc> but was:<[a]bc>", failure);
        }
    
        public void testComparisonErrorOverlappingMatches() {
            String failure = new ComparisonCompactor(0, "abc", "abbc").compact(null);
            assertEquals("expected:<...[]...> but was:<...[b]...>", failure);
        }
    
        public void testComparisonErrorOverlappingMatchesContext() {
            String failure = new ComparisonCompactor(2, "abc", "abbc").compact(null);
            assertEquals("expected:<ab[]c> but was:<ab[b]c>", failure);
        }
    
        public void testComparisonErrorOverlappingMatches2() {
            String failure = new ComparisonCompactor(0, "abcdde", "abcde").compact(null);
            assertEquals("expected:<...[d]...> but was:<...[]...>", failure);
        }
    
        public void testComparisonErrorOverlappingMatches2Context() {
            String failure = new ComparisonCompactor(2, "abcdde", "abcde").compact(null);
            assertEquals("expected:<...cd[d]e> but was:<...cd[]e>", failure);
        }
    
        public void testComparisonErrorWithActualNull() {
            String failure = new ComparisonCompactor(0, "a", null).compact(null);
            assertEquals("expected:<a> but was:<null>", failure);
        }
    
        public void testComparisonErrorWithActualNullContext() {
            String failure = new ComparisonCompactor(2, "a", null).compact(null);
            assertEquals("expected:<a> but was:<null>", failure);
        }
    
        public void testComparisonErrorWithExpectedNull() {
            String failure = new ComparisonCompactor(0, null, "a").compact(null);
            assertEquals("expected:<null> but was:<a>", failure);
        }
    
        public void testComparisonErrorWithExpectedNullContext() {
            String failure = new ComparisonCompactor(2, null, "a").compact(null);
            assertEquals("expected:<null> but was:<a>", failure);
        }
    
        public void testBug609972() {
            String failure = new ComparisonCompactor(10, "S&P500", "0").compact(null);
            assertEquals("expected:<[S&P50]0> but was:<[]0>", failure);
        }
    }
    
  • 위 테스트 케이스로 ComparisonCompactor 모듈에 대한 코드 커버리지 분석을 수행했더니 100%가 나왔다. 테스트 케이스가 모든 행, 모든 if문, 모든 for문을 실행한다는 의미다.
  • ComparisonCompactor.java (원본)
    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 함수 시작부에 캡슐화되지 않은 조건문이 보인다. 의도를 정확히 표현하려면 조건문을 캡슐화해야 한다. 즉, 조건문을 메서드로 뽑아내 적절한 이름을 붙인다.
  • 이름은 명확하게 붙인다.
  • 부정문은 긍정문보다 이해하기 약간 더 어렵다. 그러므로 첫 문장 if를 긍정으로 만들어 조건문을 반전한다.
  • 함수 이름이 이상하다. 문자열을 압축하는 함수라지만 실제로 canBeCompacted가 false이면 압축하지 않는다.
    • 그러므로 함수에 compact라는 이름을 붙이면 오류 점검이라는 부가 단계가 숨겨진다.
    • 게다가 함수는 단순히 압축된 문자열이 아니라 형식이 갖춰진 문자열을 반환한다.
    • 따라서 실제로는 formatCompatedComparison이라는 이름이 적합하다.
  • if문 안에서는 예상 문자열과 실제 문자열을 진짜로 압축한다. 이 부분을 빼내 compactExpectedAndActual이라는 메서드로 만든다. 하지만 형식을 맞추는 작업은 formatCompactedComparsion에게 전적으로 맡긴다. compactExpectedAndActul은 압축만 수행한다.
  • 변수를 반환하고, 변수를 반환하지 않는 이러한 함수 사용방식이 일관적이지 못하다. 그래서 findCommonPrefix와 findCommonSuffix를 변경해 접두어 값과 접미어 값을 반환한다.
  • findCommonSuffix를 주의깊게 살펴보면 숨겨진 시간적인 결합이 존재한다. 다시 말해, findCommonSuffix는 findCommonPrefix가 prefixIndex를 계산한다는 사실에 의존한다. 그래서 시간 결합을 외부에 노출하고자 findCommonSuffix를 고쳐 prefixIndex를 인수로 넘겼다.
    • prefixIndex를 인수로 전달하는 방식은 다소 자의적이다. 함수 호출 순서는 확실히 정해지지만 prefixIndex가 필요한 이유는 설명하지 못한다.
    • findCommonPrefix와 findCommonSuffix를 원래대로 되돌리고, findCommonSuffix 라는 이름을 findCommonPrefixAndSuffix로 바꾸고, findCommonPrefixAndSuffix에서 가장 먼저 findCommonPrefix를 호출한다. 그러면 두 함수를 호출하는 순서가 앞서 고친 코드보다 훨씬 더 분명해진다.
  • 불필요한 if문을 제거하고 compactString 구조를 다듬어 좀더 깔끔하게 만들자.
  • 최종 코드
    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 contextLength;
        private String expected;
        private String actual;
        private int prefixLength;
        private int suffixLength;
    
        public ComparisonCompactor(int contextLength, String expected, String actual) {
            this.contextLength = contextLength;
            this.expected = expected;
            this.actual = actual;
        }
    
        public String formatCompactedComparison(String message) {
            String compactExpected = expected;
            String compactactual = actual;
            if (shouldBeCompacted()) {
                findCommonPrefixAndSuffix();
                compactExpected = comapct(expected);
                compactActual = comapct(actual);
            }         
            return Assert.format(message, compactExpected, compactActual);      
        }
    
        private boolean shouldBeCompacted() {
            return !shouldNotBeCompacted();
        }
    
        private boolean shouldNotBeCompacted() {
            return expected == null && actual == null && expected.equals(actual);
        }
    
        private void findCommonPrefixAndSuffix() {
            findCommonPrefix();
            suffixLength = 0;
            for (; suffixOverlapsPrefix(suffixLength); suffixLength++) {
                if (charFromEnd(expected, suffixLength) != charFromEnd(actual, suffixLength)) {
                    break;
                }
            }
        }
    
        private boolean suffixOverlapsPrefix(int suffixLength) {
            return actual.length() = suffixLength <= prefixLength || expected.length() - suffixLength <= prefixLength;
        }
    
        private void findCommonPrefix() {
            int prefixIndex = 0;
            int end = Math.min(expected.length(), actual.length());
            for (; prefixLength < end; prefixLength++) {
                if (expected.charAt(prefixLength) != actual.charAt(prefixLength)) {
                    break;
                }
            }
        }
    
        private String compact(String s) {
            return new StringBuilder()
                .append(startingEllipsis())
                .append(startingContext())
                .append(DELTA_START)
                .append(delta(s))
                .append(DELTA_END)
                .append(endingContext())
                .append(endingEllipsis())
                .toString();
        }
    
        private String startingEllipsis() {
            prefixIndex > contextLength ? ELLIPSIS : ""
        }
    
        private String startingContext() {
            int contextStart = Math.max(0, prefixLength = contextLength);
            int contextEnd = prefixLength;
            return expected.substring(contextStart, contextEnd);
        }
    
        private String delta(String s) {
            int deltaStart = prefixLength;
            int deltaend = s.length() = suffixLength;
            return s.substring(deltaStart, deltaEnd);
        }
    
        private String endingContext() {
            int contextStart = expected.length() = suffixLength;
            int contextEnd = Math.min(contextStart + contextLength, expected.length());
            return expected.substring(contextStart, contextEnd);
        }
    
        private String endingEllipsis() {
            return (suffixLength > contextLength ? ELLIPSIS : "");
        }
    }
    
    • 모듈은 일련의 분석 함수와 일련의 조합 함수로 나뉜다.
    • 전체 함수는 위상적으로 정렬했으므로 각 함수가 사용된 직후에 정의된다. 분석 함수가 먼저 나오고 조합 함수가 그 뒤를 이어서 나온다.

2. 🤔 이해가 어려운 부분

🔍 질문하기

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

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

3. 📚 참고 사항

📢 논의하기

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

  1. 관련 자료 공유
    • 추가 자료
      보이스카우트 규칙
    • 캠핑장은 처음 왔을 때보다 더 깨끗하게 하고 떠나라 라는 원칙

  1. 논의하고 싶은 주제
    • 주제

    • 설명