8.리팩토링, 테스팅, 디버깅

대부분의 프로젝트는 예전 자바로 구현된 기존 코드를 기반으로 새로운 프로젝트를 시작.

기존 코드를 이용해 새로운 프로젝트를 시작하는 상황을 가정.

람다 표현식을 이용해서 가독성과 유연성을 높이면서 기존 코드를 어떻게 리팩토링해야하는지 설명.

람다 표현식으로 전략, 템플릿 메서드, 옵저버, 의무 체인, 팩토리 등의 객체지향 디자인 패턴 간소화 가능.


1. 가독성과 유연성을 개선하는 리팩토리

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

- 동작 파라미터화의 형식을 지원하므로 유연성을 갖춤.


1.1 코드 가독성 개선

- 코드 가독성이 좋다는 것은 추상적 표현으로 정확하게 정의하기 어려움.

- 일반적으로 "어떤 코드를 다른 사람도 쉽게 이해할 수 있음"을 의미.

> 즉, 코드 가독성을 개선한다는 것은 우리가 구현한 코드를 다른 사람이 쉽게 이해하고 유지보수할 수 있게 만드는 것을 의미.

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

> 코드의 장황함을 중려서 쉽게 이해할 수 있는 코드 구현 가능.

> 메서드 레퍼런스와 스트림 API를 이용해 코드의 의도 쉽게 표현 가능.

- 람다, 메서드 레퍼런스, 스트림을 활용한 코드 가독성 세가지 리팩토링.

> 익명 클래스 -> 람다

> 람다 -> 메서드 레퍼런스

> 명령형 데이터 처리 -> 스트림


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

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

- 익명 클래스를 람다로 리팩토링 하는 이유는?

> 람다 표현식이 더 간결하고, 가독성이 좋음.

- 모든 익명 클래스를 람다로 변환할 수 있는 것은 아님.

> 1) 익명 클래스에서 사용한 this와 super는 람다에서는 다른 의미.

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

> 2) 일명 클래스는 감싸고 있는 클래스의 변수를 가릴수 있음(쉐도우 변수)

* 람다 표현식으로는 변수를 가릴 수 없음.

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

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


1.3 람다 표현식을 메서드 레퍼런스로 리팩토링

- 람다 표현식을 쉽게 전달할 수 있은 잛은 코드.

- 람다 표현식 대신 메서드 레퍼런스를 이용할 경우 가독성 증가.

- comparing와 maxBy같은 정적 헬퍼 메서드와 메서드 레퍼런스는 조화를 이룸.

- 람다 표현식보다 코드의 의도를 더 명확히 보여줌.


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

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

- 스트림 API는 데이터 처리 파이프라인의 의도를 더 명확하게 보여줌.

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

- 명령형 코드의 break, continue, return 등의 제어 흐름문을 모두 분석해서 같은 기능을 수행하는 스트림 연산으로 유추해야 하므로 명령어 코드를 스트림으로 바꾸는 것은 쉽지 않음.

> 다행히 명령형 코드를 스트림으로 바꾸도록 도움을 주는 도구가 존재.

* http://goo.gl/Ma15w9 참고


1.5 코드 유연성 개선

- 람다 표현식을 이용하면 동작 파라미터화를 쉽게 구현 가능.

> 즉, 다양한 람드를 전달해서 다양한 동작 표현 가능.


2. 람다로 객체지향 디자인 패턴 리팩토링

- 언어에 새로운 기능이 추가되면서 기존 코드 패턴이나 관용코드의 인기가 식기도 함.

- 자바5에서 추가된 for-each루프는 에러 발생률이 적으며 간결하므로 기존의 반복자 코드를 대체.

- 자바7에 추가된 다이아몬드 연산자 <> 때문에 기존으 제네릭 인스턴스를 명시적으로 생성하는 빈도가 줄어듬.

- 다양한 패턴을 유형별로 정리한 것이 디자인 패턴.

- 디자인 패턴은 공통적인 소프트웨어 문제를 설계할 때 재사용할 수 있는, 검증된 청사진을 제공.

- 디자인 패턴에 람다 표현식이 더해지면 색다른 기능을 발휘.

> 즉, 람다를 이용하면 이전에 디자인 패턴으로 해결하던 문제를 더 쉽고 간단히 해결 가능.


2.1 전략 패턴

- 한 유형의 알고리즘을 보유한 상태에서 런타임에 적절한 알고리즘을 선택하는 기법.

- 다양한 기준을 갖는 입력값을 검증하거나, 다양한 파싱 방법을 사용하거나, 입력 형식을 설정하는 등 다양한 시나리에 전략 패턴 활용 가능.

- 다양한 전략을 구현하는 새로운 클래스를 구현할 필요 없이 람다 표현식을 직접 전달하면 코드가 간결해짐.

- 람다 표현식은 코드 조각(또는 잔략)을 캡슐화 함.


2.2 템플릿 메서드

- 알고리즘의 개요를 제시한 다음에 알고리즘의 일부를 고칠 수 있는 유연함을 제공해야 할때 사용하는 패턴.

> 템플릿 메서드는 "이 알고리즘을 사용하고 싶은데 그대로는 안 되고 조금 고쳐야 하는"상황에 적합

- 예로, 온라인 뱅킹 어플리케이션 동작.

> 은행마다 다양한 온라인 뱅킹 어플리케이션을 사용하며 동작 방법도 조금씩 차이가 있음.

- 람다 표현식을 이용해 템플릿 메서드 디자인 패턴에서 발생하는 자잘한 코드 제거 가능.


2.3 옵저버

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

- GUI 어플리케이션에서 옵저버 패턴이 자주 등장.

- 예로, 다양한 신문매체가 뉴스 트윗을 구독하고 있으며 특정 키워드를 포함하는 트윗이 등록되면 알림을 받아야 하는 경우.

- 람다 표현식을 이용해 불필요한 코드 제거 가능.

> 단, 옵저버가 상태를 가지며, 여러 메서드를 정의하는 등 복잡하다면 람다 표현식보다 기존의 클래스 구현 방식을 고수하는 것이 바람직 할 수도 있음.


2.4 의무 체인

- 작업처리 객체의 체인을 만들 때는 의무 체인 패턴 사용.

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

- andThen 메서드를 조합해 체인 생성 가능.


2.5 팩토리

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

- 예로, 은행에서 취급하는 대출, 채권, 주식 등 다양한 상품을 만들어야 할때 사용 가능.

- 생성자와 설정을 외부로 노출하지 않음으로써 클라이언트가 단순하게 상품을 생상할 수 있음.


3. 람다 테스팅

- 개발자의 최종 업무 목표는 제대로 작동하는 코드를 구현하는 것이지 깔끔한 코드를 구현하는 것이 아님.

- 일반적으로 좋은 소프트웨어 공학자라면 프로그램이 의도대로 동작하는지 확인할 수 있는 단위 테스팅을 진행.

- 소스 코드의 일부가 예상된 결과를 조출할 것이라 단언하는 테스트 케이스를 구현.


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

- 람다는 익명(결국 익명 함수)이므로 테스트 코드 이름을 호출할 수 없음.

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

- 람다 표현식은 함수형 인터페이스의 인스턴스를 생성하며 생성된 인스턴스의 동작으로 람다 표현식의 테스팅이 가능.


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

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

> 세부 구현을 포함하는 람다 표현식을 공개하지 말아야 함.

- 람다 표현식을 사용하는 메서드의 동작을 테스트함으로써 람다를 공새하지 않으면서도 람다 표현식을 검증할 수 있음.


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

- 람다 표현식을 메서드 레퍼런스로 바꾸는 것.

> 일반 메서드를 테스트하듯 람다 표현식 테스트가 가능해짐.


3.4 고차원 함수 테스팅

- 메서드가 람다를 인수로 받는다면 다른 람다로 메서드의 동작 테스트 가능.


4. 디버깅

- 문제가 발생한 코드를 디버깅할 때 개발자는 다음 주 가지를 가장 먼저 확인.

> 스택 트레이스

> 로깅

- 하지만 람다 표현식과 스트림은 기존의 디버깅 기법을 무력화 함.


4.1 스택 트레이스 확인

- 예외 발생으로 프로그램 실행이 갑자기 중단되었다면 먼저 어디에서 멈췄고 어떻게 멈추게 되었는지 확인 필요.

> 바로 스택 프레임에서 이 정보 획득 가능.

* 프로그램에서의 호출 위치, 호출할 때의 인수값, 호출된 메서드의 지역변수 등을 포함한 호출 정보가 스택 프레임에 저장됨.

- 프로그램이 멈췄다면 프로그램이 어떻게 멈추게 되었는지 프레임별로 보여주는 스택 트레이스 획득 가능.

- 람다 표현식의 경우 익명이기 때문에 조금 복잡한 스택 트레이스가 생성.

> ex) Debugging.lambda$main$0. // $0은 무슨 의미??

> ex) Debugging$$Lambda%5/28472098.apply(Unknwon Source) //무슨 의미??

- 이상한 문자는 람다 표현식 내부에서 에러가 발생했음을 가리킴.

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

- 메서드 레퍼런스를 사용해도 스택 트레이스에 메서드명이 나타나지 않음.

- 메서드 레퍼런스를 사용하는 클래스와 같은 곳에 선언되어 있는 메서드를 참조할 때는 메서드 레퍼런스 이름이 스택 트레이스에 나타남.

> 람다 표현식과 관련한 스택 트레이스는 이해하기 어려울 수 있음.

> 향후 자바 컴파일러가 개선해야 할 부분.


4.2 정보 로깅

- 스트림의 파이프라인 연산을 디버깅 상황 가정.

> forEach로 스트림 결과를 출력

* forEach를 호출하는 순간 전체 스트림이 소비 됨.

* 스트림 파이프라인에 적용된 각각의 연산(map, filter, limit)이 어떤 결과를 도출하는지 확인 필요.

* peek 연산을 활용.

* peek은 스트림의 각 요소를 소비한 것처럼 동작을 실행. forEach처럼 실제로 스트림의 요소를 소비하지 않음.

* peek은 자신이 확인한 요소를 파이프라인의 다음 연산으로 그대로 전달.


5. 요약

- 람다 표현식으로 가독성이 좋고 더 유연한 코드 생성 가능.

- 익명 클래스는 람다 표현식으로 바꾸는 것이 좋음.

> 단, 이때 this, 변수 쉐도우 등 미묘하게 의미상 다른 내용이 있음을 주의.

> 메서드 레퍼런스로 람다 표현식보다 더 가독성이 좋은 코드 구현 가능.

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

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

- 람다 표현식도 단위 테스트 수행 가능.

> 단, 람다 표현식 자체를 테스트하는 것보다는 람다 표현식이 사용되는 메서드의 동작을 테스트 하는 것이 바람직.

- 복잡한 람다 표현식은 일반 메서드로 재구현 가능.

- 람다 표현식을 사용하면 스택 트레이스 이해가 어려워짐.

- 스트림 파이프라인에서 요소를 처리할 때 peek 메서드로 중간값 확인 가능.

'Java8 > Java 8 in Action' 카테고리의 다른 글

null 대신 Optional  (0) 2017.07.12
디폴트 메서드  (0) 2017.07.05
병렬 데이터 처리와 성능  (0) 2017.06.29
스트림으로 데이터 수집  (0) 2017.06.29
스트림 활용 예제 소스  (0) 2017.06.26

+ Recent posts