본문 바로가기

프로그래밍/Book

[모던 자바 인 액션] 9장. 리팩터링, 테스팅, 디버깅

코드 가독성 개선

코드 가독성은 다른 사람이 보더라도 쉽게 이해할 수 있음을 의미. 코드 가독성을 개선한다는 것은 구현한 코드를 다른 사람이 쉽게 이해하고 유지보수할 수 있게 만드는 것.

익명 클래스를 람다 표현식으로 리팩터링

익명클래스는 람다 표현식으로 리팩터링 할 수 있는데 익명 클래스의 장황함을 람다 표현식으로 간결하게 구현할 수 있다.
하지만 불가능한 경우도 있는데 아래와 같다.

  • 익명 클래스에서 사용한 this와 super는 다른 의미를 가진다. 익명 클래스에서 this는 자기 자신, 람다는 람다를 감싸는 클래스를 가리킨다.
  • 익명 클래스는 감싸고 있는 클래스의 변수를 가리킬 수 있지만 람다는 변수를 가릴 수 없다.
       int a = 123;
    Runnable r1 = ()->{
      int a=2;    //컴파일 에러       
    };
  • 익명 클래스를 람다식으로 바꾸면 컨텍스트 오버로딩에 따른 모호함이 초래될 수 있다. 익명클래스는 인스턴스화할 때 명시적으로 형식이 정해지는 반면 람다의 형식은 콘텍스트에 따라 달라진다. (명시적 형변환을 하여 해결해야 한다.)

람다 표현식을 메서드 참조로 리팩터링하기

메서드 참조를 사용하여 가독성을 높일 수 있다.
람다 표현식을 별도의 메서드로 추출한 뒤 메서드 참조를 할 수 있다.

명령형 데이터 처리를 스트림으로 리팩터링하기

기존의 for-loop를 활용한 반복의 경우 전체 구현을 살펴본 후에 코드의 의도를 이해할 수 있다.

List<String> bookNames = new ArrayList<>();
for(Book book: bookList){
    if(book.getYear()>2000){
        bookNames.add(book.getName());
    }
}

반면 스트림은 의도를 들어내기 용이하다.

bookList.parallelStream()
        .filter(book -> book.getYear()>2000)
        .map(Book::getName)
        .collect(Collectors.toList());

코드 유연성 개선

조건부 연기 실행

클라이언트 코드에서 객체의 상태를 자주 확인하거나 일부 메서드를 호출하는 상황이라면 람다나 메서드 참조 인수로 사용하여 내부적으로 객체의 상태를 확인한 후 메서드를 호출하도록 구현

개선전

if(logger.isLoggable(Log.FINER)){
        logger.finer("problem: " + generateDiagnostic());
}
public void log(Level level, String msg){
        if(Logger.isLoggable(level)){
                //...
        }
}

실행 어라운드

매번 같은 준비, 종료 과정을 반복하는 코드 역시 람다로 변환할 수 있다. 준비, 종료 과정을 처리하는 로직을 재사용하여 코드를 줄일 수 있다.

람다 테스팅

람다식을 사용하여 간결하게 코드를 구현할 수도 있지만 제대로 동작하는 코드가 더 중요하다. 이를 위해 단위 테스팅을 진행해야 한다.
람다식 자체를 테스트하는 것 보다 람다 표현식이 사용되는 메서드의 동작을 테스트 해야 한다.

보이는 람다 표현식의 동작 테스팅

람다 표현식은 함수형 인터페이스의 인스턴스를 생성한다. 따라서 생성된 인스턴스의 동작으로 람다 표현식을 테스트할 수 있다.

람다를 사용하는 메서드의 동작에 집중하라

람다의 목표는 동작을 다른 메서드에서 사용할 수 있도록 캡슐화 하는 것. 따라서 세부 구현을 포함하는 람다 표현식을 공개하지 말아야 한다.
람다 표현식이 포함된 메서드가 존재하면 해당 메서드를 테스트하는 방법으로 람다 표현식을 대신 검증한다.

복잡한 람다를 개별 메서드로 분할하기

많은 로직을 포함하는 복잡한 람다 표현식이 있다면 이를 메서드 참조로 바꾸어 일반 메서드를 테스트하듯 람다 표현식을 테스트할 수 있다.

고차원 함수 테스팅

함수를 인수로 하거나 다른 함수를 반환하는 메서드를 고차원 함수라고 하는데 이 경우 다른 람다로 메서드의 동작을 테스트할 수 있다.
이 경우는 테스트와 디버깅을 통해 검증해야 한다.

디버깅

에러가 발생하였을 떄 개발자는 스택 트레이스와 로깅을 확인해야 한다.

스택트레이스 확인

프로그램이 예외 발생으로 중단되면 어디서 멈췄는지, 왜 멈췄는지에 대한 정보를 스택 프레임에서 얻을 수 있어서 먼저 스택 트레이스를 확인한다.
하지만 람다 표현식은 이름이 없어 복잡한 스택 트레이스가 생성되는데 람다를 메소드로 캡슐화 하면 해당 메소드명으로 스택 트레이스가 출력되어 디버깅이 쉬워진다.
변경 전 스택 트레이스

Exception in thread "main" java.lang.ArithmeticException: / by zero
    at modern_java.refactoring.Debuging.lambda$main$0(Debuging.java:17)     <- 생소한 이름
    at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
    ...

변경 후 스택 트레이스

Exception in thread "main" java.lang.ArithmeticException: / by zero
    at modern_java.refactoring.Debuging.divideByZero(Debuging.java:22)
    at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
        ...

정보 로깅

스트림 파이프라인 연산에서 디버깅을할 때 출력하여 사용할 수도 있지만 스트림이 소비된다.
peek을 사용하면 스트림의 요소를 소비하지 않고 결과를 확인할 수 있다.

List<Integer> collect = IntStream.rangeClosed(0,10)
        .peek(System.out::println)
        .limit(5)
        .boxed().collect(Collectors.toList());

참고문서

모던 자바 인 액션 - 라울 게이브리얼 우르마, 마리오 푸스코, 앨런 마이크로소포트