기존 코드를 가지고 새로운 프로젝트를 시작하는 상황을 가졌을 때 코드를 깔끔하게 만들고 다른 사람이 쉽게 이해할 수 있도록 만들려면 어떻게 해야 될까? 즉, 람다 표현식이나 스트림을 이용해 기존 코드들을 리팩터링해야 한다.
9.1 가독성과 유연성을 개선하는 리팩터링
코드 가독성이란 어떤 코드든 다른 사람이 볼때 쉽게 이해할 수 있음을 의미한다.
코드의 가독성을 높이려면 코드의 문서화를 잘하고, 표준 규칙을 지키려는 노력을 기울여야 한다.
- 익명 클래스를 람다 표현식으로 리팩토링
- 람다 표현식을 메서드 참조로 리팩토링
- 명령형 데이터 처리를 스트림으로 리팩토링
9.1.1 익명 클래스를 람다 표현식으로 리팩터링 하기
하나의 추상 메서드를 구현하는 익명 클래스는 람다 표현식으로 리팩터링 할 수 있다.
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("Hello");
}
};
Runnable r2 = () -> System.out.println("Hello");
하지만 모든 익명 클래스를 람다 표현식으로 변환할 수 있는 것은 아니다.
- 익명 클래스에서 사용한 this, super는 람다 표현식에서 다른 의미를 갖는다.
- 익명 클래스는 감싸고 있는 클래스의 변수를 가릴 수 있다. 하지만 다음 코드에서 보여주는 것처럼 람다 표현식으로 변수를 가릴 수 없다.
int a = 10;
Runnable r1 = new Runnable() {
int a = 20;
@Override
public void run() {
System.out.println("Hello");
}
};
Runnable r2 = () -> {
int a = 20; // 컴파일 에러
System.out.println("Hello");
};
9.1.2 람다 표현식으로 메서드 참조로 리팩터링 하기
람다 표현식 대신 메서드 참조를 이용하면 메서드 참조의 메서드명으로 코드의 의도를 명확하게 알릴 수 있다.
Map<CaloricLevel, List<Dish>> dishByCaloricLevel = menu.stream()
.collect(groupingBy(dish -> {
if (dish.getCalories() <= 400) return CaloricLevel.DIET;
else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
else return CaloricLevel.FAT;
}));
public CaloricLevel getCaloricLevel() {
if (this.calories <= 400) return CaloricLevel.DIET;
else if (this.calories <= 700) return CaloricLevel.NORMAL;
else return CaloricLevel.FAT;
}
Map<CaloricLevel, List<Dish>> dishByCaloricLevel = menu.stream()
.collect(groupingBy(Dish::getCaloricLevel));
9.1.3 명령형 데이터 처리를 스트림으로 리팩터링 하기
스트림 API는 데이터 처리 파이프라인의 의도를 더 명확하게 보여준다.
List<String> dishNames = new ArrayList<>();
for (Dish dish : menu) {
if (dish.getCalories() > 300) {
dishNames.add(dish.getName());
}
}
menu.stream()
.filter(d -> d.getCalories() > 300)
.map(Dish::getName)
.collect(toList());
9.1.4 코드 유연성 개선
함수형 인터페이스를 적용하여 코드를 개선해 보자.
함수형 인터페이스 적용
- 조건부 연기 실행
- 실행 어라운드 패턴
조건부 연기 실행
if (logger.isLoggable(Log.FINER)) {
logger.finer("Problem: " + generateDiagnostic());
}
- 위 코드는 다음과 같은 사항에 문제가 있다.
- logger의 상태가 isLoggable이라는 메서드에 의해 클라이언트로 노출된다.
- 메시지를 로깅할 때마다 logger 객체의 상태를 매번 확인해야 한다.
- 다음처럼 메시지를 로깅하기 전에 logger 객체가 적절한 수준으로 설정되었는지 내부적으로 확인하는 log 메서드를 사용하는 것이 바람직하다.
logger.log(Level.FINER, "Problem: " + generateDiagnostic());
불필요한 if문을 제거하였지만 여전히 항상 로깅 메시지를 보게 된다.
Java8 에서부터 제공해 주는 Supplier를 이용하여 제거해 보자.
public void log(Level level, Supplier<String> msgSupplier);
logger.log(Level.FINER, () -> "Problem: " + generateDiagnostic());
public vodid log(Level level, Supplier<String> msgSupplier) {
if (logger.isLoggable(level)) {
log(level, msgSupplier.get()); //람다 실행
}
}
- log 메서드는 조건에 맞는 경우에만 실행되고 인수로 넘겨진 람다를 내부적으로 실행할 수 있게 변경되었다.
- 이러한 방법으로 객체 상태를 자주 확인하는 상황이나 객체의 일부 메서드를 확인한 다음에 메서드를 호출하도록 새로운 메서드를 구현하는 것이 좋다.
- 코드의 가독성이 좋아질 뿐만 아니라 캡슐화(객체 상태가 클라이언트로 노출되지 않는다)가 강화된다.
실행 어라운드
String oneLine = processFile((BufferedReader b) -> b.readLine()); // 람다 전달
String twoLine = processFile((BufferedReader b) -> b.readLine() + b.readLine()); // 다른 람다 전달
public static String processLine(BufferedReaderProcessor p) throws IOException {
try (BufferdReader br = new BufferedReader(new FileReader("file.txt"))) {
return p.process(br); // 인수로 전달된 BufferedReaderProcessor 실행
}
}
public interface BufferedReaderProcessor {
String process(BufferedReader b) throws IOException;
}
- 람다로 BufferedReader 객체의 동작을 결정할 수 있는 것은 함수형 인터페이스 BufferedReaderProcessor 덕분이다.
9.3 람다 테스팅
일반적으로 프로그램이 의도대로 동작하는지 확인할 수 있는 방법은 단위 테스트를 작성하는 것이다.
보이는 람다 표현식의 동작 테스팅
public static final Comparator<Point> compareByXAndThenY = comparing(Point::getX).thenComparing(Point::getY);
Point.compareByXAndThenY.compare(p1, p2);
람다를 사용하는 메서드의 동작에 집중
- 람다의 목표는 정해진 동작을 다른 메서드에서 사용할 수 있도록 하나의 조각으로 캡슐화하는 것이다.
- 람다 표현식을 사용하는 메서드의 동작을 테스트 함으로써 람다 표현식을 검증할 수 있다.
복잡한 람다를 개별 메서드로 분할
- 복잡한 로직이 포함된 람다를 구현하게 된다면 로직을 분리하거나 메서드 레퍼런스를 활용하도록 하자.
고차원 함수 테스팅
- 함수를 인수로 받거나 다른 함수를 반환하는 메서드는 사용하기도, 테스트하기도 어렵다.
- 메서드가 람다를 인수로 받는다면 다른 람다로 메서드의 동작을 테스트 할 수 있다.
- 테스트해야 하는 함수가 다른 함수를 반환한다면 앞서 Comparator와 비슷하게 함수형 인터페이스의 인스턴스로 간주하고 테스트할 수 있다.
9.4 디버깅
문제가 발생하는 코드를 디버깅할 때는 다음 두 가지를 가장 먼저 확인해야 한다.
- 스택 트레이스
- 로깅
프로그램이 멈췄다면 프로그램이 어디서 멈췄는지 메서드 호출 리스트를 통해 문제를 파악하자.
'자바 > 모던 자바 인 액션' 카테고리의 다른 글
[모던 자바 인 액션] 11장. null 대신 Optional 클래스 (0) | 2023.01.07 |
---|---|
[모던 자바 인 액션] 10장. 람다를 이용한 도메인 전용 언어 (0) | 2022.12.27 |
[모던 자바 인 액션] 8장. 컬렉션 API 개선 (0) | 2022.11.10 |
[모던 자바 인 액션] 7장. 병렬 데이터 처리와 성능 (0) | 2022.10.24 |
[모던 자바 인 액션] 6장. 스트림으로 데이터 수집(2) (0) | 2022.10.24 |