메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

FRP로 이벤트 처리의 여섯 재앙 퇴치하기(1/2)

한빛미디어

|

2017-08-03

|

by 오현석

8,004

소프트웨어 개발은 이벤트 기반의 비동기 방식으로 빠르게 옮겨가고 있다. 수십 년 동안 관찰자 패턴이 이를 위한 이벤트 인프라를 책임져왔지만, 한편으로는 버그의 온상이 되기도 했다. 함수형 반응형 프로그래밍(Functional Reactive Programming, FRP)은 관찰자 패턴을 대체히며 이벤트 기반 코드의 품질을 대폭 끌어올린다.

 

이 글에서는 FRP의 개념과 리스너(콜백 혹은 관찰자 패턴) 방식 이벤트 처리의 문제점을 알아보며, FRP가 이 문제들을 어떻게 해결하는지 간략히 알아본다.

 

 

1. 리스너는 이벤트 처리의 기둥!

 

리스너(콜백 혹은 관찰자 패턴)는 오늘날 소프트웨어에서 이벤트를 전파하는 가장 주된 방식이다. 하지만 이 방식만 있던 것은 아니다.

옛날 흑벽돌로 집을 짓던 시절, 벽 안에 살던 쥐(마우스)들은 오늘날 우리가 아는 이벤트 소스가 아니라 작은 동물에 지나지 않았고 리스트 박스(list box)도 아직 발명되지 않았다. 프로그램에서 값을 전달하고 싶을 때는 그 값을 사용하려는 모든 장소를 호출해 값을 직접 전달해야 했다. 당시에는 생산자(producer)가 소비자(consumer)에 의존했다. 이벤트에 새로운 소비자를 추가하고 싶다면 생산자가 그 소비자도 호출하도록 만들어야 했다. 프로그램은 한 덩어리였으며 이벤트를 만들어내는 코드(이를테면 리스트 박스)를 재사용하고 싶어도 그 코드가 프로그램의 나머지 부분과 직접 연결돼 있었기에 처리해야 할 일이 많았다.

 

22.jpg

 

리스트 박스를 재사용 가능한 소프트웨어 컴포넌트로 다루자는 아이디어는 리스트 박스가 자신의 소비자들을 미리 알고 있지 못하면 제대로 작동할 수 없다. 그래서 관찰자 패턴이 발명됐다. 특정 이벤트 생산자를 관찰하려면 아무 때나 새로운 소비자(또는 리스너)를 등록한다. 그러면 그 후로 이벤트가 발생할 때마다 해당 소비자가 호출된다. 관찰을 중단하고 싶다면 다음 코드와 같이 소비자를 생산자로부터 등록 해제하면 된다.

 


public class ListBox {

    public interface Listener {

        void itemSelected(int index);

    }

 

    private List<Listener> listeners = new ArrayList<>();

    public void addListener(Listener l) {

        listeners.add(l);

    }

    public void removeListener(Listener l) {

        listeners.remove(l);

    }

    protected void notifyItemSelected(int index) {

        for (l : listeners) l.itemSelected(index);

    }

}


 

이런 방식으로 리스너는 원래의 의존성을 뒤집었다. 앞서와 반대로 소비자는 이제 생산자에 의존한다. 이렇게 하면 프로그램을 확장하기 쉽고 구성요소 간의 연결이 느슨해져서 모듈화하기 좋다.

 

 

2. 하지만…

 

이렇게 멋진 관찰자 패턴에서 대체 잘못될 수 있는 게 뭐가 있을까? 음... 그렇다. 우리는 리스너 버그의 근원 여섯 가지를 정리했다. 그리고 FRP, 즉 함수형 반응형 프로그래밍은 이 모두를 퇴치해준다.

 

33.png

 

  • 예측 불가능한 순서: 리스너가 복잡하게 얽혀있다면 이벤트가 도착하는 순서는 여러분이 리스너를 등록한 순서에 따라 정해지는데, 사실 이 방식은 도움이 되지 않는다. FRP에서는 이벤트의 처리 순서를 감지할 수 없기 때문에 이벤트 처리 순서가 문제가 되지 않는다.
  • 첫 번째 이벤트 소실: 생산자가 첫 번째 이벤트를 생산하기 전에 리스너가 등록되리라 보장하기 어렵다. FRP는 트랜잭션 방식이기 때문에 이를 보장할 수 있다.
  • 지저분한 상태: 콜백으로 인해 코드가 전통적인 상태 머신 형태로 바뀌며 지저분해지기 쉽다. FRP는 질서를 가져온다.
  • 스레드 문제: 리스너를 스레드 안전(thread-safe)하게 만들려다가 교착상태(deadlock)를 일으킬 수 있다. 또한 리스너가 등록 해제된 다음에는 호출되지 않도록 보장하기도 어려울 수 있다. FRP는 이런 문제를 제거해준다.
  • 콜백 누수: 리스너를 해제하는 것을 잊는다면 메모리 누수가 발생한다. 리스너는 자연적인 데이터 의존성을 뒤집어주지만, 우리의 희망과 다르게 생존(keep-alive) 의존성을 뒤집어주지는 않는다. FRP는 메모리 누수를 방지한다.
  • 의도치 않은 재귀: 로컬 상태 갱신과 리스너 통지의 순서가 매우 중요한데 순서를 정할 때 실수하기 쉽다. FRP는 그런 문제를 없애준다.

 

 

3. 함수형? 반응형? 함수형 반응형?

 

지나친 단순화처럼 들리겠지만 실제 FRP는 함수형 프로그래밍과 반응형 프로그래밍의 교집합이다.

 

44.png

 

  • 함수형 프로그래밍(FP): 수학적 의미의 함수에 기반한 프로그래밍 방식 또는 패러다임이다. 함수형 프로그래밍은 공유된 변경 가능한 상태(mutable state)를 의도적으로 회피한다. 그에 따라 불변(immutable) 데이터 구조를 사용한다. 함수형 프로그래밍은 합성성을 강조한다.
  • 반응형 프로그래밍(RP): 프로그램을 전통적인 제어의 흐름이 아니라 ‘이벤트 기반’의 ‘입력에 반응하며 동작’하는 ‘데이터의 흐름’으로 보는 것을 뜻하는 광범위한 용어다. 이 용어는 이러한 목적을 어떻게 달성해야 하는지를 기술하고 있지 않다. 반응형 프로그래밍은 프로그램 구성요소 간의 더 느슨한 결합을 허용하며 그에 따라 코드가 더 모듈화된다.
  • 함수형 반응형 프로그래밍(FRP): 반응형 프로그래밍을 달성하기 위해 함수형 프로그래밍의 규칙을 따르도록 강제하는 구체적인 방법을 말한다. 특히 합성성이란 특성을 강조한다.

 

_합성성이란?

합성성(compositionality)은 소프트웨어 설계에서 자주 요구되는 조합성(composability) 개념을 수학적으로 더 정확하게 표현한 형태로, 복잡도를 효율적으로 처리해주는 강력한 아이디어다.

 

딱딱한 설명이 잘 와닿지 않는다면 다음 설명은 어떤가?

 

예를 들어보자. 나는 컴퓨터 앞에 앉아서 7월 중순이면 방송을 시작할 <왕좌의 게임> 시즌 7을 보기 전에 다시 시즌 6를 복습하고 있다가 문득 맥주와 팝콘이 생각났다. 초등학교 3학년짜리 막내한테 명령을 내렸다. “오정원! 아빠 맥주랑 팝콘 좀!” 짠 하고 맥주와 팝콘이 나타나면 행복하겠지만 막내는 그 정도 능력이 없다. 막내가 과업을 완수하게 하려면 “냉장고 열면 맥주가 있으니 (중간절차 생략) 올 때 팝콘 담을 그릇도 하나 가져와야 해”라고 하나하나 구체적으로 이야기해야 하는데, 1살부터 호주에서 살아서 한국말에 약한 막내가 그 지시를 제대로 이해하고 실행할 리 만무하다. 중2짜리 큰애가 집에 있었다면 “맥주랑 팝콘!”이라고만 해도 충분한 서비스를 받을 수 있었을 것이다.

 

컴퓨터 프로그램이 정말 ‘컴퓨터에 내리는 일련의 명령’일까? 아니다. 컴퓨터에 일련의 명령을 내려야 하는 이유는 컴퓨터가 멍청하고, 프로그램을 작성하는 프로그래밍 언어가 원시적이기 때문이다. 최근의 프로그래밍 흐름은 추상화의 수준을 높여서 개발자가 밑바탕에 있는 실제 기계에 대해 덜 신경 쓰게 하는 것이다. 프로그램이 ‘어떻게’가 아니라 ‘무엇’을 기술하는 선언적인 문서여야 한다는 주장을 받아들이는 사람이 점점 많아지고 있다.

 

66.png

 

그런 흐름으로 인해 최근 가장 각광받는 언어가 함수형 언어다. 함수형 언어를 사용하면 코드를 좀 더 선언적인 방식으로 작성할 수 있다. 특히 컬렉션을 활용하면 여러 값의 모음을 한꺼번에 처리하는 로직을 기술할 수 있고, 원소를 하나하나 처리하는 것보다 더 높은 시각에서 프로그램을 기술할 수 있다. 이런 함수형 언어를 이벤트 처리에 결합한 것이 함수형 반응형 프로그래밍이다. 기존 이벤트 프로그래밍의 기반에는 이벤트가 발생하면 리스너를 호출한다는 개념이 자리 잡고 있지만, FRP에서는 이벤트를 값의 흐름(스트림)으로 보고, 각종 콤비네이터(combinator, 고차 함수)를 사용해서 값을 어떻게 변환할지를 기술한다.

 

 

B3673113778_l.jpg

 

해당 글은 『함수형 반응형 프로그래밍』에서 발췌하였습니다. :-)

 

댓글 입력
자료실