본문 바로가기
프로그래밍 놀이터/안드로이드, Java

[Java8 In Action] #8 리팩토링, 테스팅, 디버깅

by 돼지왕 왕돼지 2018. 12. 28.
반응형

[Java8 In Action] #8 리팩토링, 테스팅, 디버깅


Java8 In Action 내용을 보며 정리한 내용입니다.

정리자는 기존에 Java8 을 한차례 rough 하게 공부한 적이 있고, Kotlin 역시 공부한 적이 있습니다.

위의 prerequisite 가 있는 상태에서 추가적인 내용만 정리한 내용이므로, 제대로 공부를 하고 싶다면 책을 구매해서 보길 권장합니다!


andthen, Break, comparing, conditional deferred execution, continue, execute around, foreach, higher-order function, java8 in action, lambda stacktrace, Peek, reducing 연산, Return, shadow variable, short circuit, stack frame, strategy interface, successor, summingint, 게으름, 고차원 함수, 동작 파라미터화, 디버깅, 람다 디버깅, 람다 스택 트레이스, 람다 전략, 람다 테스팅, 람다의 this, 람다의 모호함, 리듀싱 연산, 리팩토링, 메서드 레퍼런스, 쇼트 서킷, 스택 트레이스, 스택 프레임, 스트림 파이프라인, 옵저버, 의무 체인, 익명클래스 this, 전략 구현체, 전략 사용 클라이언트, 전략 패턴, 최적화, 코드 유연성, 코드의 의도, 코드의 장황함, 콘텍스트 오버로딩, 테스팅, 템플릿 메서드, 함수형 인터페이스


8.1. 가독성과 유연성을 개선하는 리펙토링


-

람다 표현식은 익명 클래스보다 코드를 좀 더 간결하게 만든다.

람다 표현식은 동작 파라미터화(익명클래스와 동일 개념)의 형식을 지원하므로 람다 표현식을 이용한 코드는 더 큰 유연성을 갖출 수 있다.




8.1.1. 코드 가독성 개선


-

일반적으로 코드 가독성이 좋다는 것은 “어떤 코드를 다른 사람도 쉽게 이해할 수 있음” 을 의미한다.



-

자바8에서는 코드 가독성에 도움을 주는 다음과 같은 기능을 새롭게 제공한다.

    코드의 장황함을 줄여서 쉽게 이해할 수 있는 코드를 구현할 수 있다.

    메서드 레퍼런스와 스트림 API 를 이용해서 코드의 의도(코드가 무엇을 수행하려는 것인지)를 쉽게 표현할 수 있다.




8.1.2. 익명 클래스를 람다 표현식으로 리팩토링하기.


-

하나의 추상 메서드를 구현하는 익명 클래스는 람다 표현식으로 리팩토링할 수 있다.



-

익명 클래스에서 사용한 this 와 super 는 람다 표현식에서 다른 의미를 갖는다.

익명 클래스에서 this 는 익명 클래스 자신을 가리키지만 람다에서의 this는 람다를 감싸는 클래스를 가리킨다.



-

익명 클래스는 감싸고 있는 클래스 변수를 가릴 수 있다.(shadow variable).

하지만 람다 표현식으로는 변수를 가릴 수 없다.

int a = 10;
Runnable r1 = () -> {
    int a = 2; // compile error!!!
    System.out.println(a)
}

Runnable r2 = new Runnable(){
    public void run(){
        int a = 2;
        System.out.println(a);
    }
}



-

익명 클래스를 람다 표현식으로 바꾸면 콘텍스트 오버로딩에 따른 모호함이 초래될 수 있다.

익명 클래스는 인스턴스화할 때 명시적으로 형식이 정해지는 반면 람다의 형식은 콘텍스트에 따라 달라지기 때문이다.


람다를 사용할 때는 모호함이 발생하면 다음과 같이 casting 을 해주어야 한다.

doSomething( (Runnable)() -> System.out.println(“Gamza”) );



8.1.3. 람다 표현식을 메서드 레퍼런스로 리팩토링하기


-

sum, maximum 등 자주 사용하는 리듀싱 연산은 메서드 레퍼런스와 함께 사용할 수 있는 내장 헬퍼 메서드를 제공한다.


summingInt, comparing 등등




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


-

이론적으로는 반복자를 이용한 기존의 모든 컬렉션 처리 코드를 스트림 API 로 바꿔야 한다.

스트림 API 는 데이터 처리 파이프라인의 의도를 더 명확히 보여주기 때문이다.

스트림은 쇼트서킷과 게으름이라는 강력한 최적화뿐 아니라 멀티코어 아키텍쳐를 활용할 수 있는 지름길도 제공하기 때문이다.



-

명령형 코드의 break, continue, return 등의 제어 흐름문을 모두 분석해서 같은 기능을 수행하는 스트림 연산으로 유추해야 하므로 명령형 코드를 스트림 API 로 바꾸는 것은 쉬운 일이 아니다.




8.1.5. 코드 유연성 개선


* 함수형 인터페이스 적용


-

람다 표현식을 이용하려면 함수형 인터페이스가 필요하다. 따라서 함수형 인터페이스를 코드에 추가해야 한다.

조건부 연기 실행(conditional deferred execution)과 실행 어라운드(execute around)가 자주 사용되는 람다 표현식 리펙토링이다.



* 조건부 연기 실행



* 실행 어라운드





8.2. 람다로 객체지향 디자인 패턴 리펙토링하기


8.2.1. 전략


-

전략 패턴은 한 유형의 알고리즘을 보유한 상태에서 런타임에 적절한 알고리즘을 선택하는 기법이다.



-

전략 패턴은 세 부분으로 구성된다.

    알고리즘을 나타내는 인터페이스(Strategy interface)

    다양한 알고리즘을 나타내는 한 개 이상의 인터페이스 구현(ConcreteStrategyA, ConcreteStrategyB 같은..)

    전략 객체를 사용하는 한 개 이상의 클라이언트.



* 람다 표현식 사용




8.2.2. 템플릿 메서드


-

알고리즘의 개요를 제시한 다음에 알고리즘의 일부를 고칠 수 있는 유연함을 제공해야 할 때 템플릿 메서드 디자인 패턴을 사용한다.

템플릿 메서드는 “이 알고리즘을 사용하고 싶은데 그대로는 안 되고 고쳐야 하는” 상황에 적합하다.




8.2.3. 옵저버


-

어떤 이벤트가 발생했을 때 한 객체(주체, subject)가 다른 객체 리스트(옵저버, observer)에 자동으로 알림을 보내야 하는 상황에서 옵저버 디자인 패턴을 사용한다.



* 람다 표현식 사용하기




8.2.4. 의무 체인


-

작업처리 객체의 체인(동작 체인 등)을 만들 때는 의무 체인 패턴을 사용한다.

한 객체가 어떤 작업을 처리한 다음에 다른 객체로 결과를 전달하고, 다른 객체도 해야 할 작업을 처리한 다음에 또 다른 객체로 전달하는 식.



* 람다 표현식 사용


-

andThen 을 이용해서 successor 를 지정할 수 있다.




8.2.5. 팩토리


-

인스턴스화 로직을 클라이언트에 노출하지 않고 객체를 만들 때 팩토리 디자인 패턴을 사용한다.



* 람다 표현식 사용


-

final static Map<String, Supplier<Product>> map = new HashMap<>();
static{
    map.put(“loan”, Loan::new);
    map.put(“stock”, Stock::new);
    map.put(“bond”, Bond::new);
}






8.3. 람다 테스팅


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


-

람다는 익명이므로 람다를 테스트하는 것은 쉽지 않다. ( 함수 호출과는 다른 개념 )

따라서 필요하다면 람다를 필드에 저장해서 재사용할 수 있으며 람다의 로직을 테스트할 수 있다.



-

람다는 함수형 인터페이스의 인스턴스를 생성한다.




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


-

람다의 목표는 정해진 동작을 다른 메서드에서 사용할 수 있도록 하나의 조각으로 캡슐화하는 것이다.

세부 구현을 포함하는 람다 표현식을 공개하지 않고, 람다 표현식을 사용하는 메서드의 동작을 테스트함으로써 람다를 공개하지 않으면서도 람다 표현식을 검증할 수 있다.




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


-

복잡한 람다 표현식이 생겼을 때는 람다 표현식을 메서드 레퍼런스로 바꾸면 일반 메서드를 테스트하듯 람다 표현식을 테스트할 수 있다.




8.3.4. 고차원 함수 테스팅


-

함수를 인수로 받거나 다른 함수를 반환하는 메서드(고차원 함수, higher-order function)는 좀 더 사용하기 어렵다.

테스트해야할 메서드가 다른 함수를 반환한다면, 함수형 인터페이스의 인스턴스로 간주하고 함수의 동작을 테스트할 수 있다.





8.4. 디버깅


8.4.1. 스택 트레이스 확인


-

프로그램이 메서드를 호출할 때마다 프로그램에서의 호출 위치, 호출할 때의 인수값, 호출된 메서드의 지역 변수 등을 포함한 호출 정보가 생성되며 이들 정보는 스택 프레임(stack frame)에 저장된다.

프로그램이 멈추면 스택 트레이스(stack trace)를 얻을 수 있다.



* 람다와 스택 트레이스


-

람다 표현식은 이름이 없기 때문에 조금 복잡한 스택 트레이스가 생성된다.

람다 표현식은 이름이 없으므로 컴파일러가 람다를 참조하는 이름을 만들어내는 것이다.

그래서 클래스에 여러 람다 표현식이 있을 때는 골치 아픈 일이 벌어진다.


메서드 레퍼런스를 사용해도 이상한 정보가 출력된다.

단, 같은 클래스의 메서드 레퍼런스를 사용할 경우에는 제대로 표시된다.


이는 미래의 자바가 개선해야 할 문제라고 이야기한다….




8.4.2. 정보 로깅


-

peek 은 스트림의 각 요소를 소비한 것처럼 동작을 실행한다. 

하지만 forEach 처럼 실제로 스트림의 요소를 소비하지는 않는다.





8.5. 요약


-

람다 표현식으로 가독성이 좋고 더 유연한 코드를 만들 수 있다.



-

익명 클래스는 람다 표현식으로 바꾸는 것이 좋다. 하지만 이 때 this, variable shadow 등 미묘하게 의미상 다른 내용이 있음을 주의하자. 메서드 레퍼런스로 람다 표현식보다 더 가독성이 좋은 코드를 구현할 수 있다.



-

반복적으로 컬렉션을 처리하는 루틴은 스트림 API 로 대체할 수 있을지 고려하는 것이 좋다.



-

람다 표현식으로 전략, 템플릿 메서드, 옵저버, 의무 체인, 팩토리 등의 객체지향 디자인 패턴에서 발생하는 불필요한 코드를 제거할 수 있다.



-

람다 표현식도 단위 테스트를 수행할 수 있다. 하지만 람다 표현식 자체를 테스트하는 것보다는 람다 표현식이 사용되는 메서드의 동작을 테스트하는 것이 바람직하다.



-

복잡한 람다 표현식은 일반 메서드로 재구현할 수 있다.



-

람다 표현식을 사용하면 스택 트레이스를 이해하기 어려워진다.



-

스트림 파이프라인에서 요소를 처리할 때 peek 메서드로 중간값을 확인할 수 있다.




반응형

댓글