item 39 incheol - JAVA-JIKIMI/EFFECTIVE-JAVA3 GitHub Wiki

Effective Java 3e μ•„μ΄ν…œ 39λ₯Ό μš”μ•½ν•œ λ‚΄μš© μž…λ‹ˆλ‹€.

μ „ν†΅μ μœΌλ‘œ λ„κ΅¬λ‚˜ ν”„λ ˆμž„μ›Œν¬κ°€ νŠΉλ³„νžˆ 닀뀄야 ν•  ν”„λ‘œκ·Έλž¨ μš”μ†Œμ—λŠ” λ”± κ΅¬λΆ„λ˜λŠ” λͺ…λͺ… νŒ¨ν„΄μ„ μ μš©ν•΄μ™”λ‹€.

public class HelloTest extends TestCase {
	public void testGetUsers() {
		return getUsers();
	}
}

κ·ΈλŸ¬λ‚˜ μ΄λŸ¬ν•œ λͺ…λͺ…νŒ¨ν„΄μ€ λͺ‡κ°€μ§€ 단점을 가지고 μžˆλ‹€.

  • 첫 번째, μ˜€νƒ€κ°€ λ‚˜λ©΄ μ•ˆ λœλ‹€.
  • 두 번째, μ˜¬λ°”λ₯Έ ν”„λ‘œκ·Έλž¨ μš”μ†Œμ—μ„œλ§Œ μ‚¬μš© 되리라 보증할 방법이 μ—†λ‹€. μ˜ˆμ»¨λŒ€ 클래슀 이름을 TestSafetyMechanisms둜 지어 JUnit에 던져 쀬닀고 ν•˜λ©΄ JUnit은 κ²½κ³  λ©”μ‹œμ§€μ‘°μ°¨ 좜λ ₯ν•˜μ§€ μ•ŠμœΌλ©΄μ„œ μ˜λ„ν•œ ν…ŒμŠ€νŠΈκ°€ μ „ν˜€ μˆ˜ν–‰λ˜μ§€ μ•Šμ„ 것이닀.
  • μ„Έ 번째, ν”„λ‘œκ·Έλž¨ μš”μ†Œλ₯Ό 맀개 λ³€μˆ˜λ‘œ 전달할 λ§ˆλ•…ν•œ 방법이 μ—†λ‹€. νŠΉμ • μ˜ˆμ™Έλ₯Ό λ˜μ Έμ•Όλ§Œ μ„±κ³΅ν•˜λŠ” ν…ŒμŠ€νŠΈκ°€ μžˆλ‹€κ³  κ°€μ • ν–ˆμ„ λ•Œ, μ˜ˆμ™Έμ˜ 이름을 ν…ŒμŠ€νŠΈ λ©”μ„œλ“œ 이름에 λ§λΆ™μ΄λŠ” 방법도 μžˆμ§€λ§Œ 보기도 λ‚˜μ˜κ³  깨지기도 쉽닀. μ»΄νŒŒμΌλŸ¬λŠ” λ©”μ„œλ“œ 이름에 덧뢙인 λ¬Έμžμ—΄μ΄ μ˜ˆμ™Έλ₯Ό κ°€λ₯΄ν‚€λŠ” 지 μ•Œ 도리가 μ—†λ‹€.

μ—λ„ˆν…Œμ΄μ…˜μ€ 이 λͺ¨λ“  문제λ₯Ό ν•΄κ²°ν•΄μ£ΌλŠ” κ°œλ…μœΌλ‘œ Junit도 버전 4λΆ€ν„° μ „λ©΄ λ„μž…ν•˜μ˜€λ‹€.

/**
 * ν…ŒμŠ€νŠΈ λ©”μ„œλ“œμž„μ„ μ„ μ–Έν•˜λŠ” μ• λ„ˆν…Œμ΄μ…˜μ΄λ‹€.
 * λ§€κ°œλ³€μˆ˜ μ—†λŠ” 정적 λ©”μ„œλ“œ μ „μš©μ΄λ‹€.
 */
@Retention(RetentionPolicy.RUNTIME) // @Testκ°€ λŸ°νƒ€μž„μ—λ„ μœ μ§€λ˜μ–΄μ•Ό ν•œλ‹€λŠ” ν‘œμ‹œμ΄λ‹€. 
@Target(ElementType.METHOD) // @Testκ°€ λ°˜λ“œμ‹œ λ©”μ„œλ“œ μ„ μ–Έμ—μ„œλ§Œ μ‚¬μš©λΌμ•Ό ν•œλ‹€. 
public @interface Test { // @Test
}

마컀 μ• λ„ˆν…Œμ΄μ…˜μ„ λ™μž‘ν•˜λŠ” ν”„λ‘œκ·Έλž¨μ„ μž‘μ„±ν•΄λ³΄μž.

public class RunTests {
    public static void main(String[] args) throws Exception {
        int tests = 0;
        int passed = 0;
        Class<?> testClass = Class.forName(args[0]);
        for (Method m : testClass.getDeclaredMethods()) {
            if (m.isAnnotationPresent(Test.class)) {
                tests++;
                try {
                    m.invoke(null);
                    passed++;
                } catch (InvocationTargetException wrappedExc) {
                    Throwable exc = wrappedExc.getCause();
                    System.out.println(m + " μ‹€νŒ¨: " + exc);
                } catch (Exception exc) {
                    System.out.println("잘λͺ» μ‚¬μš©ν•œ @Test: " + m);
                }
            }
        }
        System.out.printf("성곡: %d, μ‹€νŒ¨: %d%n",
                passed, tests - passed);
    }
}

이 ν…ŒμŠ€νŠΈ λŸ¬λ„ˆλŠ” λͺ…λ Ήμ€„λ‘œλΆ€ν„° μ™„μ „ μ •κ·œν™”λœ 클래슀 이름을 λ°›μ•„, κ·Έ ν΄λž˜μŠ€μ—μ„œ @Test μ—λ„ˆν…Œμ΄μ…˜μ΄ 달린 λ©”μ„œλ“œλ₯Ό μ°¨λ‘€λ‘œ ν˜ΈμΆœν•œλ‹€.

λ‹€μŒμ€ νŠΉμ • μ˜ˆμ™Έλ₯Ό λ˜μ Έμ•Όλ§Œ μ„±κ³΅ν•˜λŠ” ν…ŒμŠ€νŠΈλ₯Ό μ§€μ›ν•˜λ„λ‘ ν•΄λ³΄μž.

/**
 * λͺ…μ‹œν•œ μ˜ˆμ™Έλ₯Ό λ˜μ Έμ•Όλ§Œ μ„±κ³΅ν•˜λŠ” ν…ŒμŠ€νŠΈ λ©”μ„œλ“œμš© μ• λ„ˆν…Œμ΄μ…˜
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
    Class<? extends Throwable> value();
}

@ExceptionTest μ• λ…Έν…Œμ΄μ…˜μ— λŒ€ν•œ λ‚΄λΆ€ κ΅¬ν˜„μ€ μ•„λž˜μ™€ 같이 λ˜μ–΄ μžˆλ‹€.

public class RunTests {
    public static void main(String[] args) throws Exception {
        int tests = 0;
        int passed = 0;
        Class<?> testClass = Class.forName(args[0]);
        for (Method m : testClass.getDeclaredMethods()) {
            if (m.isAnnotationPresent(Test.class)) {
                tests++;
                try {
                    m.invoke(null);
                    passed++;
                } catch (InvocationTargetException wrappedExc) {
                    Throwable exc = wrappedExc.getCause();
                    System.out.println(m + " μ‹€νŒ¨: " + exc);
                } catch (Exception exc) {
                    System.out.println("잘λͺ» μ‚¬μš©ν•œ @Test: " + m);
                }
            }

            if (m.isAnnotationPresent(ExceptionTest.class)) {
                tests++;
                try {
                    m.invoke(null);
                    System.out.printf("ν…ŒμŠ€νŠΈ %s μ‹€νŒ¨: μ˜ˆμ™Έλ₯Ό λ˜μ§€μ§€ μ•ŠμŒ%n", m);
                } catch (InvocationTargetException wrappedEx) {
                    Throwable exc = wrappedEx.getCause();
                    Class<? extends Throwable> excType =
                            m.getAnnotation(ExceptionTest.class).value();
                    if (excType.isInstance(exc)) {
                        passed++;
                    } else {
                        System.out.printf(
                                "ν…ŒμŠ€νŠΈ %s μ‹€νŒ¨: κΈ°λŒ€ν•œ μ˜ˆμ™Έ %s, λ°œμƒν•œ μ˜ˆμ™Έ %s%n",
                                m, excType.getName(), exc);
                    }
                } catch (Exception exc) {
                    System.out.println("잘λͺ» μ‚¬μš©ν•œ @ExceptionTest: " + m);
                }
            }
        }

        System.out.printf("성곡: %d, μ‹€νŒ¨: %d%n",
                passed, tests - passed);
    }
}

Throwable을 ν™•μž₯ν•œ 클래슀의 Class κ°μ²΄λΌλŠ” 뜻이며 λͺ¨λ“  μ˜ˆμ™Έ(와 였λ₯˜) νƒ€μž…μ„ λ‹€ μˆ˜μš©ν•œλ‹€.

public class Sample2 {
    @ExceptionTest(ArithmeticException.class)
    public static void m1() {  // 성곡해야 ν•œλ‹€.
        int i = 0;
        i = i / i;
    }
    @ExceptionTest(ArithmeticException.class)
    public static void m2() {  // μ‹€νŒ¨ν•΄μ•Ό ν•œλ‹€. (λ‹€λ₯Έ μ˜ˆμ™Έ λ°œμƒ)
        int[] a = new int[0];
        int i = a[1];
    }
    @ExceptionTest(ArithmeticException.class)
    public static void m3() { }  // μ‹€νŒ¨ν•΄μ•Ό ν•œλ‹€. (μ˜ˆμ™Έκ°€ λ°œμƒν•˜μ§€ μ•ŠμŒ)
}

이 μ˜ˆμ™Έ ν…ŒμŠ€νŠΈ μ˜ˆμ—μ„œ ν•œ 걸음 더 λ“€μ–΄κ°€, μ˜ˆμ™Έλ₯Ό μ—¬λŸ¬ 개 λͺ…μ‹œν•˜κ³  그쀑 ν•˜λ‚˜κ°€ λ°œμƒν•˜λ©΄ μ„±κ³΅ν•˜κ²Œ λ§Œλ“€ μˆ˜λ„ μžˆλ‹€.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
    Class<? extends Exception>[] value();
}

λ°°μ—΄ λ§€κ°œλ³€μˆ˜λ₯Ό λ°›λŠ” μ• λ„ˆν…Œμ΄μ…˜μš© 문법은 μ•„μ£Ό μœ μ—°ν•˜λ‹€. 단일 μ›μ†Œ 배열에 μ΅œμ ν™”ν–ˆμ§€λ§Œ, μ•žμ„œμ˜ @ExceptionTest듀도 λͺ¨λ‘ μˆ˜μ • 없이 μˆ˜μš©ν•œλ‹€.

@ExceptionTest({IndexOutOfBoundsException.class, NullPointerException.class})
public static void doublyBad() {   // 성곡해야 ν•œλ‹€.
    List<String> list = new ArrayList<>();

    // μžλ°” API λͺ…세에 λ”°λ₯΄λ©΄ λ‹€μŒ λ©”μ„œλ“œλŠ” IndexOutOfBoundsExceptionμ΄λ‚˜
    // NullPointerException을 던질 수 μžˆλ‹€.
    list.addAll(5, null);
}

μžλ°” 8μ—μ„œλŠ” μ—¬λŸ¬ 개의 값을 λ°›λŠ” μ• λ„ˆν…Œμ΄μ…˜μ„ λ‹€λ₯Έ λ°©μ‹μœΌλ‘œλ„ λ§Œλ“€ 수 μžˆλ‹€. λ°°μ—΄ λ§€κ°œλ³€μˆ˜λ₯Ό μ‚¬μš©ν•˜λŠ” λŒ€μ‹  μ• λ„ˆν…Œμ΄μ…˜μ— @Repeatable 메타 μ• λ„ˆν…Œμ΄μ…˜μ„ λ‹€λŠ” 방식이닀. @Repeatable을 단 μ• λ„ˆν…Œμ΄μ…˜μ€ ν•˜λ‚˜μ˜ ν”„λ‘œκ·Έλž¨ μš”μ†Œμ— μ—¬λŸ¬ 번 달 수 μžˆλ‹€.

@Repeatable은 μ£Όμ˜ν•΄μ•Ό 할점이 μžˆλ‹€.

  • Repeatable을 단 μ• λ„ˆν…Œμ΄μ…˜μ„ λ°˜ν™˜ν•˜λŠ” 'μ»¨ν…Œμ΄λ„ˆ μ• λ„ˆν…Œμ΄μ…˜'을 ν•˜λ‚˜ 더 μ •μ˜ν•˜κ³ , @Repeatable에 μ»¨ν…Œμ΄λ„ˆ μ• λ„ˆν…Œμ΄μ…˜μ˜ class 객체λ₯Ό λ§€κ°œλ³€μˆ˜λ‘œ 전달해야 ν•œλ‹€.
  • μ»¨ν…Œμ΄λ„ˆ μ• λ„ˆν…Œμ΄μ…˜μ€ λ‚΄λΆ€ μ• λ„ˆν…Œμ΄μ…˜ νƒ€μž…μ˜ 배열을 λ°˜ν™˜ν•˜λŠ” value λ©”μ„œλ“œλ₯Ό μ •μ˜ν•΄μ•Ό ν•œλ‹€.
  • μ»¨ν…Œμ΄λ„ˆ μ—λ„ˆν…Œμ΄μ…˜ νƒ€μž…μ—λŠ” μ μ ˆν•œ 보쑴 μ •μ±…(@Retention)κ³Ό 적용 λŒ€μƒ(@Target)을 λͺ…μ‹œν•΄μ•Ό ν•œλ‹€.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Repeatable(ExceptionTestContainer.class)
public @interface ExceptionTest {
    Class<? extends Throwable> value();
}

// μ»¨ν…Œμ΄λ„ˆ μ• λ„ˆν…Œμ΄μ…˜
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTestContainer {
		ExceptionTest[] value();
}

반볡 κ°€λŠ₯ μ• λ„ˆν…Œμ΄μ…˜μ„ μ‚¬μš©ν•΄ ν•˜λ‚˜μ˜ ν”„λ‘œκ·Έλž¨ μš”μ†Œμ— 같은 μ• λ„ˆν…Œμ΄μ…˜μ„ μ—¬λŸ¬ 번 달 λ•Œμ˜ μ½”λ“œ 가독성을 λ†’μ—¬λ³΄μ˜€λ‹€. λ‹€λ₯Έ ν”„λ‘œκ·Έλž˜λ¨Έκ°€ μ†ŒμŠ€μ½”λ“œμ— μΆ”κ°€ 정보λ₯Ό μ œκ³΅ν•  수 μžˆλŠ” 도ꡬλ₯Ό λ§Œλ“œλŠ” 일을 ν•œλ‹€λ©΄ μ λ‹Ήν•œ μ• λ„ˆν…Œμ΄μ…˜ νƒ€μž…λ„ ν•¨κ»˜ μ •μ˜ν•΄ μ œκ³΅ν•˜μž. μ• λ„ˆν…Œμ΄μ…˜μœΌλ‘œ ν•  수 μžˆλŠ” 일을 λͺ…λͺ… νŒ¨ν„΄μœΌλ‘œ μ²˜λ¦¬ν•  μ΄μœ λŠ” μ—†λ‹€.

μžλ°” ν”„λ‘œκ·Έλž˜λ¨ΈλΌλ©΄ μ˜ˆμ™Έ 없이 μžλ°”κ°€ μ œκ³΅ν•˜λŠ” μ• λ„ˆν…Œμ΄μ…˜ νƒ€μž…λ“€μ€ μ‚¬μš©ν•΄μ•Ό ν•œλ‹€.

⚠️ **GitHub.com Fallback** ⚠️