함수형 프로그래밍에서 부수효과(side effect)를 다루는 방법 2 - 또다른 비유

Raymond Yoo·2023년 12월 11일
0
post-thumbnail

Task 예시코드 - taskX, taskY, taskZ 순서대로 실행

이번엔 Optional 말고
다른 wrapper 타입을 예시로 들어서 살펴보자.
어떤 작업을 비동기적으로 실행한 후에 결과값이 돌아왔을때
그 출력값으로 다음 작업을 진행하는 것도
부수효과(side effect)이다.
함수 스코프를 벗어난 위치에 영향을 주기 때문이다.
이런 부수효과를 보다 편하게 다루기 위해서
Promise, Task, Future, Completable 등등
다양한 wrapper 타입이 존재한다.
위의 그림에서는 세 개의 태스크 X, Y, Z 를 순서대로 실행한다.
태스크 X 실행이 성공하면 다음 태스크 Y 를 실행하고
태스크 Y 실행이 섵공하면 다음 태스크 Z 를 실행하는 방식이다.
중간에 하나라도 실패한다면 그 결과값은 null 이 된다.
이를 위해서는 실패하는 경우에 대해서 예외처리를 해야하므로
그림과 같이 계속에서 인덴트가 안으로 들어가서
죽음의 삼각형이 만들어진다.
앞에서 설명한 원리를 여기에도 적용하면
코드를 한결 정제된 상태로 개선할 수 있다.

철길 들어가는 길은 하나, 나오는 길은 둘

그림과 같은 철길을 생각해보자.
기차가 달리고 있을때 갈림길에서
선로가 어느 방향으로 조정되어 있느냐에 따라서
기차가 위쪽 철길을 탈지 아래쪽 철길을 탈지가 달라진다.
함수도 이와 마찬가지이다.
어떤 함수를 실행하면 성공할 수도 있고 실패할 수도 있다.
성공하는 케이스를 초록색 철길을 타는 경우,
예외를 던지거나 오류가 발생하는 경우를
빨간색 철길로 가는 거라고 생각하자.

철길 두 개를 나란히 두면

앞에서 세 개의 태스크 X, Y, Z 를 차례로 실행할때
앞에 태스크가 성공해야만 다음 태스크를 실행한다고 했다.
여기 철길 그림에서도 마찬가지이다.
앞쪽 갈림길에서 초록색 길로 가야만 다음 갈림길에 도달할 수 있다.

철길 세 개 합치기

궁극적으로는 이런식으로 태스크 X, Y, Z 를 합쳐두는게 목적이다.

composing switches is not allowed

여기에는 문제가 있다.
여러개의 철로를 문제없이 이으려면
앞에 철로 갈림길과 뒤에 철로가 갈림길의
입구 개수와 출구 개수가 같아야 한다.
그래서 위의 그림과 같은 형태의 철로는 서로 연결할 수가 없다.

bind 예시

이런 문제를 해결해주는 것은 바로 Bind 이다.
그림 속 bind 함수 구현에서
Success 케이스는 위쪽의 초록색 철로를 타는 것이고
Failure 케이스는 아래쪽의 빨간색 철로를 타는 것과 같다.

taskExample 원래 코드와 새로 추가하는 taskBind 함께 보여주기

앞에서 세 개의 태스크 X, Y, Z 실행하는 코드를 다시 살펴보면
whenFinished 처리하는 부분을
bind 함수로 옮겨서 처리할 수 있을 것 같다.
조금 전에 살펴본 bind 라는 아이디어를 적용해서
taskBind 라는 함수를 구현해보자.

taskBind 적용해서 리팩터링

새롭게 추가한 taskBind 라는 적용해서
기존의 코드를 리팩터링하면 이렇게 된다.
바로 이전 포스팅에서 자주 본 것처럼
함수 합성을 통해서 한 줄로 깔끔하게
정리한 것과 같은 효과가 나타난다.

updateCustomer 예시코드

조금 더 현실적인 가상의 요구조건에
기반한 예시코드를 살펴보자.
클라이언트에서 HTTP 요청을 받은 후
HTTP 요청의 형태가 유효한 형식인지 확인하고
이메일 문자열을 전처리하고
사용자 요청한 내용을 데이터베이스에 저장한 후에
사용자에게 성공적으로 처리 완료했다고 이메일로 알리는
서비스를 만들었다.
지금 코드도 충분히 좋다.
직관적으로 비즈니스 로직을 파악할 수 있다.

updateCustomer 예외처리 추가한 예시코드

하지만 프로덕션 릴리즈를 하려면
예외처리를 통해서 코드를 안정화해야 한다.
validation 로직에서 오류가 발생할 수 있다.
데이터베이스 update 쿼리 실행중에 오류가 발생할 수 있고
sendEmail 과정에서 오류가 발생할 수 있다.
그러므로 이런식으로 예외처리 코드를 추가하는 것은
너무나 당연해보인다.

하지만 이런식으로 예외처리를 하나 둘 추가하다보면
나중에는 중간중간 디테일이 너무 많이 끼어들어서
유지보수하는 개발자들이 로직의 전체적인 플로우를
파악하기 어렵게 만든다.

validateInput 함수에 bind 원리 적용한 예시

이런 어려움을 극복하기 위해서
앞에서 살펴본 bind 원리를 적용할 수 있다.
validateInput 이라는 함수는
성공하거나 실패할 수 있는 철로 갈림길이다.
그러므로 예외처리 로직을 함수 내부로 끌고 온 후
실행이 성공하는 경우 Success 결과값을 반환하고
실행중에 오류가 발생하면 Failure 케이스에 맞는
처리를 하도록 변경한다.

validate, updateDb, sendEmail 모두 철로 갈림길로 비유하기

에외가 발생할 가능성이 있는 모든 함수를
이런식으로 철로 갈림길 다루듯이
bind 로직을 적용하고 나면

깔끔하게 one-liner 로 만든 결과

다시 비즈니스 로직을 파악하기 쉬운 형태가 된다.

함수형 프로그래밍에서는
bind, >=>(kleisli arrow), flatMap 세 가지가
모두 동일한 개념을 가리킨다.
이런식으로 함수형 프로그래밍의 원리를 적절하게 적용하면
각자가 다루는 코드베이스를 개선할 수 있다.
이해하기 전에는 너무 어려워보이지만
일단 한번 패턴을 익히고 나면
함수형 프로그래밍의 원리를 코드에 적용하는 것은
일관성만 지킨다면 그다지 어렵지 않다.

<참조>
유뷰트 영상, Functional Design Patterns - Scott Wlaschin

profile
세상에 도움이 되고, 동료에게 도움이 되고, 나에게 도움이 되는 코드를 만들고 싶습니다.

0개의 댓글