함수형 프로그래밍으로 리팩토링.
카테고리가 기존 함수형 방법론 으로 분류해서
포스팅을 하고 있지만 계속 공부하는 책인 JAVA8 in Action 을 보며, 포스팅을 하고 있습니다.
분류의 목적을 따지자면, JAVA8 에서 가장 중요하다고 생각할 만한 튜토리얼은 Lambda 와 Stream API 라고 할 수 있겠는데요.
이와 관련된 기초 튜토리얼은 모두 끝났기 때문에 조금 더 More 하게 공부를 할 부분이기 때문에 나누었습니다.
More 한 카테고리에서의 첫 포스팅에서는 함수형 프로그래밍으로 리팩토링하기를 소개하려 합니다.
소개하는 내용은 JAVA8 in Action 의 리팩토링 관련 목록을 따라가지만 중요하다 싶은 부분만 소개하려 합니다.
1. Stream 을 통한 컬렉션 처리 리팩토링.
JAVA8 in Action 에 따르면, 모든 Collection 처리는 Stream API 로 바꿔야 한다고 하고 있습니다.
Stream API 는 데이터 처리 파이프라인의 의도를 더 명확히 하여 가독성 향상에 도움을 줍니다.
또한 쇼트서킷 처리와 Lazy 처리로 최적화 되어 있으며, 쉽게 병렬로 처리하도록 변경할 수 있습니다.
그러나 모든 Collection 처리를 Stream API 로 변경하는 것은 쉽지 않으며, 특히 break, continue, return 등 제어 흐름문에 대한 연산 역시 쉽지는 않아보입니다.
하지만 우회하는 법은 언제나 존재합니다. 다음과 같이 말이죠.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | // break 리팩토링. { for (Integer a : numberList) { if (a % 10 == 0 && a > 55) { System.out.println(a); break; } } // findFirst 활용 numberList.stream().filter(a -> a % 10 == 0 && a > 55).findFirst().ifPresent(System.out::println); } // continue 리팩토링. { int size = 0; for (Integer a : numberList) { if (a % 10 == 0 && a > 55) { continue; } ++size; } // 해당 조건이 아닌 것에 대한 Predicate 정의. numberList.stream().filter(a -> !(a % 10 == 0 && a > 55)).collect(Collectors.counting()); } // return /** * 리턴 for-loop * * @param numberList * @return */ public Integer returnExampleForLoop(List<Integer> numberList) { for (Integer a : numberList) { if (a % 10 == 0 && a > 55) { return a; } } return null; } /** * 리턴 Stream API * * @param numberList * @return */ public Optional<Integer> returnExampleStream(List<Integer> numberList) { return numberList.stream().filter(a -> !(a % 10 == 0 && a > 55)).findFirst(); } | cs |
2. 조건부 연기
예를 들어 아래와 같은 코드가 있다고 합시다.
1 2 3 4 5 6 7 | Data data = new Data(); data.setNumber(12); if (object == null) { String message = RollBaMessage(); throw new MesseageException(message); } | cs |
객체의 null 체크를 하는 유효성 검사 로직입니다.
객체가 Null 일 때, 지정된 Roll back message 를 예외 메시지로 입력합니다.
하지만, Null 을 체크하는 유효성 검사 모듈은 범용적으로 쓰이고 싶어 메소드를 만들어 재활용을 하고 싶었습니다.
그래서 만들었죠.
1 2 3 4 5 6 7 | public static void NullCheck(Object object, String message) throws MesseageException{ if (object == null) { throw new MesseageException(message); } } NullCheck(data, RollBaMessage()); | cs |
각 비지니스 상황에 따라 여러 에러 메시지를 유연하게 보낼 수 있게 되었습니다.
호스트코드에서는 단지 NullCheck 메소드만 불러주면될 것 같아 보입니다.
그런데, 특정 메시지를 보내기 위한 새로운 메소드 RollBackDelayedMessage 를 제작했습니다.
미리 만들어둔 NullCheck 유효성 검사 모듈이 있으니 다음과 같이 사용을 할 생각입니다.
1 2 3 4 5 6 7 | public static void NullCheck(Object object, String message) throws MesseageException{ if (object == null) { throw new MesseageException(message); } } NullCheck(data, RollBackDelayedMessage()); | cs |
그런데 문제가 생겼습니다. RollBackDelayedMessage 는 실행시간이 10초나 걸립니다.
게다가 이 로직은 에러 메시지를 출력하기 전에 비지니스 로직을 수행을 하는데, 그 로직은 null 일때만 해야하죠..
if문을 사용하여 따로 제작을 해서 중복코드를 만들어야 하는건가요?
아닙니다! 메소드 파라미터화를 시켜서 넘기면되죠. 아래와 같이 말이죠.
1 2 3 4 5 6 7 8 | // 파라미터에 String 대신, String 을 출력하는 공급자를 언급 public static void NullCheck(Object object, Supplier<String> messageSupplier) throws MesseageException{ if (object == null) { throw new MesseageException(messageSupplier.get()); } } NullCheck(data, () -> RollBackDelayedMessage()); | cs |
이제 Null Check 유효성 검사 모듈에서 실패할 때만 RollBackDelayedMessage 을 실행할 수 있습니다. 즉 위와 같이 특정 상황에서만 실행하도록 하는 조정하는 것을 조건부 연기라고 합니다.
3. 의무 체인
Lambda 에 대하여 포스팅할 때, 두 Lambda 식을 조합할 때 사용하는 andThen, compose 메소드를 본 적이 있습니다.
해당 메소드를 이용하면 두 함수를 엮어 새로운 기능을 만들어낼 수 있었습니다.
정확히 말하면, 한 객체가 어떤 작업을 처리한 다음에 다른 객체로 결과를 전달하고, 다른 객체도 작업을 처리한 다음 또 다른 객체로 전달하는 등 기능들을 엮는 것이며, 이를 의무 체인이라고 합니다.
예를 들어 다음과 같이 함수형 인터페이스를 제작하고, 의무체인을 할 수 있는 여지를 남길 수 있습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | @FunctionalInterface interface IProcesser<T> { T accept(T t); default IProcesser<T> chain(IProcesser<T> t1) { return t -> { T result = accept(t); return t1.accept(result); }; } } IProcesser<Integer> t1 = n -> n + 5; IProcesser<Integer> t2 = n -> n - 2; IProcesser<Integer> t3 = t1.chain(t2); System.out.println(t3.accept(10)); // result : 13 | cs |
인터페이스에는 JAVA8 부터 사용가능한 디폴트메소드를 사용하여, 각 함수들을 체인으로 걸도록 하였습니다.
위와 같이 Lambda 를 사용하여 리팩토링할 수 있는 여지들을 보았습니다.
리팩토링을 통하여, 우리의 코드는 보다 더 가독성이 개선되며, 유지보수에 유연해질 수 있을 것입니다.
리팩토링 하고 싶어지지 않나요? :-)
|
'개발이야기 > 함수형 방법론' 카테고리의 다른 글
CompleteableFuture 를 이용한 비동기 처리 조합 (0) | 2017.03.24 |
---|---|
CompleteableFuture 를 이용한 비동기 처리 (0) | 2017.03.23 |
Null 대신 Optional! (0) | 2017.03.14 |
디폴트 메소드와 다중상속 (0) | 2017.03.13 |
JAVA8 에서의 인터페이스 변화 (디폴트 메소드) (2) | 2017.03.10 |