Search
Duplicate
📒

[Java Study] 11-x. 리팩토링, 테스팅, 디버깅

상태
미진행
수업
Java Study
주제
Stream
연관 노트
3 more properties
참고

코드 가독성 개선

NOTE
자바 8에서는 코드 가독성에 도움을 주는 다음과 같은 기능을 제공한다!
람다를 활용한 단축코드
메서드 참조, 스트림 API를 활용한 코드의 의도를 쉽게 표현

익명 클래스 → 람다 표현식 리팩토링

NOTE
// 익명 클래스를 사용한 코드 Runnable r1 = new Runnable() { public void run() { System.out.println("Hello"); } }; // 람다 표현식을 사용한 코드 Runnable r2 = () -> System.out.println("Hello");
Java
복사
하지만 모든 익명 클래스를 람다 표현식으로 변환할 수 있는 것은 아니다.
익명 클래스에서 사용한 this와 super는 람다 표현식에서는 다른 의미를 가진다.
익명클래스 this → 자기자신
람다 표현식 this → 감싸는 클래스
익명 클래스는 감싸고 있는 클래스의 변수를 가릴 수 있다. 하지만 람다는 불가능하다.
int a = 10; // 람다 표현식(외부에 a가 선언되서 불가) Runnable r1 = () -> { int a = 2; // 컴파일 에러!!!! System.out.println("Hello"); }; // 익명 클래스 ( 함수를 선언하고 내부에 작성하기에 a사용 가능) Runnable r2 = new Runnable() { public void run() { int a = 2; System.out.println("Hello"); } };
Java
복사
익명 클래스를 람다 표현식으로 바꾸면 오버로딩에 따른 모호함이 초래될 수 있다.
interface Task { public void execute(); } // 같은 타입을 다른 람다 표현식으로 구현 public static void doSomething(Runnable r){ r.run(); } public static void doSomething(Task a){ a.execute(); } // Task를 구현하는 익명 클래스를 전달할 수 있다. doSomething(new Test() { public void execute() { System.out.println("Danger danger!!"); } }); // 람다 표현식으로는 어떤 인터페이스를 사용하는지 알 수 없다. doSomeThing(() -> System.out.println("Danger danger!!")); // 명시적 형변환을 이용해서 모호함을 제거할 수 있다. doSomeThing((Task)() -> System.out.println("Danger danger!!"));
Java
복사

람다 표현식 → 메서드 참조 리팩토링

NOTE
// 람다로 인해 코드가 길어짐 Map<CaloricLevel, List<Dish>> dishedByCaloricLevel = menu.stream() .collect( groupingBy(dish -> { if (dish.getCalories() <= 400) return CaloricLevel.DIET; else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL; else return CaloricLevel.FAT; })); // 외부 함수를 사용해서 코드단 Map<CaloricLevel, List<Dish>> dishedByCaloricLevel = menu.stream() .collect( groupingBy(dish::getCaloricLevel));
Java
복사
람다가 아닌, 외부에서 함수를 작성해서 사용하게 하는 것
// 비교 구현에 신경써야 한다. inventory.sort( (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())); // 코드가 문제 자체를 설명한다. inventory.sort(comparing(Apple::getWeight));
Java
복사
자주 사용되는 리듀싱 연산은 내장 헬퍼 메서드(comparing, maxBy)를 사용하는것이 좋다

명령형 데이터 → 스트림 리팩토링

NOTE
스트림은 쇼트서킷, 게으름이라는 최적화뿐 아니라, 멀티코어 아키텍쳐를 활용할 수 있는 지름길을 제공한다!
하지만 명령형 코드의 break, continue, return 등의 제어 흐름문을 모두 분석해서 같은 기능으로 바꾸는건 쉬운일이 아니다.
람다 표현식을 사용하기 위해선 함수형 인터페이스가 필요하다. 따라서 함수형 인터페이스를 코드에 추가해야 한다.
// 조건이 참일 경우에만 로그 메시지를 작성하도록 함 if (logger.isLoggable(Log.FINER)) { logger.finer("Problem: " + generateDiagnostic()); }
Java
복사
조건부 연기 실행 - logger의 상태가 isLoggable이라는 메서드에 의해 클라이언트에게 노출된다. - 로깅마다 logger의 상태를 매번 확인해야 하나?
public void log(Level level, Supplier<String> msgSupplier){ if(logger.isLoggable(level)){ log(level, msgSupplier.get()); // 람다 실행 } } Logger.log(Level.FINER, () -> "Problem: " + generateDiagnostic());
Java
복사
로깅 이전에 미리 적절한 수준인지 내부적으로 확인하게한다.

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

전략 패턴

NOTE
한 유형의 알고리즘을 보유한 상태에서 런타임에 적절한걸로 선택하는 기법!
// 알고리즘을 나타내는 인터페이스 public interface ValidationStrategy { boolean execute(String s); } // 다양한 알고리즘을 나타내는 1개 이상의 인터페이스 구현 클래스 public class IsAllLowerCase implements ValidationStrategy { public boolean execute(String s) { return s.matches("[a-z]+"); } } public class IsNumeric implements ValidationStrategy { public boolean execute(String s) { return s.matches("\\d+"); } } // 전략 객체를 사용하는 클라이언트 public class Validator { private final ValidationStrategy strategy; public Validator(ValidationStrategy strategy) { this.strategy = strategy; } public boolean validate(String s) { // 어떤 전략을 구현했냐에 따라 동작이 달라짐 return this.strategy.execute(s); } } // 실제 사용코드 Validator numericValidator = new Validator(new IsNumeric()); boolean b1 = numericValidator.validate("aaaa"); // false Validator lowerCaseValidator = new Validator(new IsAllLowerCase()); boolean b2 = lowerCaseValidator.validate("bbbb"); // true
Java
복사
Validator numericValidator = new Validator((String s) -> s.matches("[a-z]+")); boolean b1 = numericValidator.validate("aaaa"); // true Validator lowerCaseValidator = new Validator((String s) -> s.matches("\\d+")); boolean b2 = lowerCaseValidator.validate("bbbb"); // false
Java
복사
전략 구현체를 만들지 않고 람다 표현식으로 생성!

템플릿 메서드 패턴

NOTE
알고리즘의 개요를 제시한 다음에 알고리즘의 일부를 고칠 수 있는 기능을 제공한다!
abstract class OnlineBanking { public void processCustomer(int id) { Customer c = Database.getCustomerWithId(id); makeCustomerHappy(c); } abstract void makeCustomerHappy(Customer c); }
Java
복사
온라인 뱅킹 애플리케이션 동작코드 ⇒ makeCustomerHappy를 구현방식에 따라 다르게 동작시킬 수 있음
public class OnlineBankingLamda { public void processCustomer(int id, Consumer<Customer> makeCustomerHappy) { Customer c = Database.getCustomerWithId(id); makeCustomerHappy.accept(c); } } new OnlineBankingLamda().processCustomer(1337, (Customer c) -> System.out/println("Hello "+ c.getName());
Java
복사
추상 메서드가 아닌, 함수객체를 인자로 받아서 람다로 구현한다!

옵저버 패턴

NOTE
이벤트 발생했을 때 한 객체(주제 subject)가 다른 객체 리스트(옵저버)에 자동으로 알림을 보내주는 패턴!
불나면 알람보내
interface Observer { void notify(String tweet); } // 트윗에 포함된 다양한 키워드에 다른 동작을 수행할 수 있는 여러 옵저버를 정의할 수 있다. class NYTimes implements Observer { public void notify(String tweet) { if(tweet != null && tweet.contains("money")) { System.out.println("Breaking news in NY! " + tweet); } } } class Guardian implements Observer { public void notify(String tweet) { if(tweet != null && tweet.contains("queen")) { System.out.println("Yet another new in London... " + tweet); } } } class LeMonde implements Observer { public void notify(String tweet) { if(tweet != null && tweet.contains("wine")) { System.out.println("Today cheese, wine and news! " + tweet); } } } // Subject 인터페이스의 정의다. interface Subject { void registerObserver(Observer o); void notifyObservers(String tweet); } class Feed implements Subject { private final List<Observer> observers = new ArrayList<>(); // 새로운 옵저를 등록한다. public void registerObserver(Observer o) { this.observers.add(o); } // 트윗을 등록한 옵저들에게 알린다. public void notifyObservers(String tweet) { observers.forEach(o -> o.notify(tweet)); } } Feed f = new Feed(); // 구독할 옵저버를 등록한다. f.registerObserver(new NYTimes()); f.registerObserver(new Guardian()); f.registerObserver(new LeMonde()); // 옵저버들에게 메시지를 전송한다. f.notifyObservers("The queen said her favorite book is Java 8 in Action!");
Java
복사
Feed f = new Feed(); f.registerObserver((String tweet) { if(tweet != null && tweet.contains("money")) { System.out.println("Breaking news in NY! " + tweet); } }); f.registerObserver((String tweet) { if(tweet != null && tweet.contains("queen")) { System.out.println("Yet another new in London... " + tweet); } });
Java
복사

팩토리 패턴

NOTE
인스턴스화 로직을 클라이언트에 노출하지 않고 객체를 만들 때 팩토리 패턴을 사용한다!
public class ProductFactory { public static Product createProduct(String name) { switch (name) { case "loan": return new Loan(); case "stock": return new Stock(); case "bond": return new Bond(); default: throw new RuntimeException("No such product " + name); } } }
Java
복사
new 로직이 클라이언트에게 노출되지 않는다!
Supplier<Product> loanSupplier = Loan::new; Loan loan = loanSupplier.get(); 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); } public static Product createProduct(String name) { Supplier<Product> p = map.get(name); if(p != null) return p.get(); throw new IllegalArgumentException("No such product " + name); }
Java
복사
현재 경우는 생성자에 파라미터가 없어서 쉽게 되지만 파라미터가 있다면 인터페이스로는 불가능