반응형 프로그래밍

Uno·2022년 10월 2일
0

반응형 프로그래밍

이 글을 쓰는 이유

코딩을 할 때, 내 머리속은 어떤 사고체계로 코드를 작성해야 할까? 그냥 기능 적당히 붙이다가 코드정리나 한번씩 하고, 그러면 되는 건가?

어느날과 다름없이 코드를 작성하던 저는 위와같은 질문을 맞닥드린 적이 있습니다. 그리고 제가 과거에 유사한 고민을 하던 때가 생각났습니다.

다른 사람은 세상을 살아갈 때, 어떤식으로 바라보고 어떤식으로 생각을 하는 것일까?

저는 위 질문에 대해 답을 찾기위해서 철학자들이 규정한 세계관을 공부해봤었습니다. 다른 말로, 그 사람이 제시한 패러다임을 이해해보려고 노력했었죠.

그래서 이전에 성공적으로 질문을 해결했던 방식으로 소프트웨어에서의 패러다임을 공부해보고 싶었습니다. 그래서 이글을 작성합니다.


패러다임이란?

반응형 프로그래밍을 다루기 앞서, 반응형 프로그램의 상위 주제인 "패러다임" 에 대해서 이해할 필요가 있어서 먼저 설명합니다.

"패러다임은 어떤 한 시대 사람들의 견해나 사고를 근본적으로 규정하고 있는 테두리로서의 인식 체계, 또는 사물에 대한 이론적인 틀이나 체계를 의미하는 개념이다. 토마스 쿤이 제안하였다."
위키피디아

과학 발전의 역사를 보면, 하나의 패러다임에서 또 다른 패러다임으로 넘어가면서 점진적으로 발전해왔다고 합니다. 소프트웨어 역시 지금까지는 그렇다고 전 생각합니다.

명령형 프로그래밍 -> 객체지향 -> 함수형 .. 등등

여기서 패러다임이라는 것은, "문제를 인식하는 틀" 혹은 "특정 문제를 바라보는 패턴(혹은 관점)" 정도로 이해하시면 됩니다.

예를들면, 천동설이 진리로 받아들여지던 시절에는, 지구를 중심으로 모든 천문현상을 설명하려고 했습니다. 하지만 이에 대한 반증이 나타나게되고, 그 반증이 쌓여감에 따라서 새로운 패러다임이 등장했죠. 바로 지동설 입니다. 지구가 중심이 아니라는 이론이죠. 그리고 그 주장을 강화하는 증거들이 쌓여갑니다.

이것을 프로그래밍에 빗대어 생각해보면, 명령어로 순차적으로 작성하던 방식이 맞다고 생각하던 패러다임이 있었고, 그에 대한 반증으로 코드를 오래 유지하기 어렵다라는 점이나 협업 시 많은 이슈가 생겼겠죠. 그리고 이에 대한 새로운 주장으로 객체지향이 나타났다고 제멋대로 이해할 수 있습니다.

반응형 프로그래밍

이제 반응형 프로그래밍은 하나의 패러다임임을 이해했을 겁니다. 그러면 이제 반응형 프로그래밍 그 자체에 대해서 이야기해보죠.

도와줘 위키피디아 (반응형 프로그래밍이라고 검색하면 위키에 없고 reactive promgramming 이라고 검색하면 나옵니다.)

In computingreactive programming is a declarative programming paradigm concerned with data streams and the propagation of change.

해석)
반응형 프로그래밍이란 것은 데이터 흐름(Data Streams) 와 변경사항의 전달(Propagation of change) 에 중점을 준 선언적 프로그래밍 패러다임(Declarative Programming Paradigm) 입니다.

그러면, 이제 우리가 알아야할 것들은 "데이터흐름", "변경사항의 전달" 그리고 "선언적 프로그래밍" 을 알게되면, 반응형 프로그래밍을 이해할 수 있겠네요. 차근차근 설명해보겠습니다.

선언적 프로그래밍

선언적 이라고 하니까, 독립선언문이나, 군대에서 하던 선서같은 것들이 생각납니다.
한국어로 번역하는 과정에서 맥락 전달이 빠지고 단어만 남아서 오해될 우려가 있어서 어원을 살펴봤습니다.

Declare; 단언하다, 공언하다

라는 뜻이죠.

de = 완전히
clar = 맑고 투명하게, 숨김없이 밝히는
de + clar = 완전히 숨김없이 밝히는

어원을 조합하면 위 처럼 해석할 수 있죠.
제가 이해하기엔, 이렇습니다. "선언적" 이라는 의미는 수식을 작성하는 것과 유사하다고 봅니다.

A + B = 결과

라고 제가 선언합니다. 그러면 앞으로 "결과" 라는 친구는 A + B 연산을 한 결과입니다. 이와 다르게 "한정적" 의 의미가 있는 Define 은 다음과 같습니다.

결과 = 2

결과는 이제 다른 가능성이 없고 한정해버리는 겁니다. "결과 = 2" 를 "선언" 했다고 생각할 수 있지만, 그 의미가 아니라 이건 "정의" 했다고 저는 생각합니다.

그래서 선언적이라는 것은 각자의 상황에 맞게 선언문을 읽고 해석하겠죠. 하지만 그 선언문은 지키는 겁니다. 정의는 각자의 상황에 맞게 해석하지 않습니다. 그냥 앞으로 그 단어는 정의된 뜻으로 고정되는 것이죠.

그래서 "선언적" 프로그래밍 이라는 것은 수식을 만드는 것이라고 생각합니다. (결과는 아직 안나온 상태)

제가 참고한 글에서는 (https://yozm.wishket.com/magazine/detail/1334/)
스프레드 시트의 예시를 듭니다.

셀 칸 중에 D1 칸에 = A1 + B1 라고 선언을 하게되면 A1 과 B1 을 더한 값이 D1 에 들어갑니다. A1 이 변경되면 그에 맞게 변경되죠(상황에 맞게 해석을 다시 합니다.)

이러한 사고체계를 코딩에 반영하고자 함이 선언적 프로그래밍이죠. 예시를 하나 들어보겠습니다.

의도) 구매하기 버튼을 누르면, 구매목록에 데이터가 추가되었으면 좋겠다.
구현) 구매목록 데이터를 바라보고 있다가 생명주기에 따라 생성되는 시점에 해당 데이터를 가져와서 연산한 다음 UI 로 보여준다.

이제 구매하기를 누를 때마다, 구매목록 데이터가 추가되겠죠. 하지만 제가 특정 물품만 구매하기로 "한정" 하지 않았고, 구매목록에 추가된 데이터에 따라서 해당 UI 가 결정되도록 "선언" 했습니다.

이야기를 듣다보니 위 과정에서 은근슬쩍 추가된 과정이 하나 있습니다.

"데이터를 가져와서"

이 부분입니다. 그러면 데이터는 어떻게 가져와야 하는 걸까요? 그러면 만약에 제대로 데이터를 "가져오지" 못한다면, 잘못된 데이터에 UI 에 반영되겠죠. 위 예시는 데이터가 하나니까 까먹을 염려가 없지만, 데이터가 정말 많아서 까먹게 되면 문제가 될 수 있습니다.

변경사항의 전파

이걸 하나하나 가져오지 말고, 우리 변경되면 알아서 해당 데이터 쓰는 UI 에 전달해주자!

(참고글: https://taenami.tistory.com/123)

라는 아이디어가 떠오르게 되었습니다. 즉, (데이터를 소비하는 객체가) 데이터를 Pull 하지 말고, (데이터를 가지고 있는 객체) 가 Push 해서 UI 를 다시 그리도록 하자. 라는 아이디어 입니다.

마치 데이터의 변경 사항에 "반응" 하게 되죠. 해당 데이터를 가져오도록 "명령" 하는 것이 아니라.

정리하면 다음과 같습니다.

Pull

  • 데이터가 필요한 시점에 필요한 데이터를 요청한다.
  • 그래서 필요한 데이터를 그때그때, 내가 다 알고 있어야하고, 그떄그때, 정확히 반영해줘야한다.
    (개발자가 직접)

Push

  • 데이터가 변경되면, 해당 데이터를 사용하는 객체에 전달한다.
  • 필요한 데이터를 "구독" 하게되면, 데이터 변경 시, 알아서 자동으로 전달받게 된다.
    (마치 유튜브 구독하면 홈 화면에 이제 해당 유튜브 영상을 자동으로 전달 받는 것처럼)

Push 로 데이터 변경사항을 전달받게되면, 다음과 같은 그림으로 구성될겁니다.
(출처 : https://taenami.tistory.com/123 + 우아한Tech)

보시면, 관찰자들이 View 라고 생각하시면되고, 우아한Tech 가 데이터를 가지고 있는 ViewModel 정도로 이해하시면 됩니다. 자신을 구독하는 View 에게 데이터를 전달하고 있습니다. (Push)

여기서 데이터를 "알림!" 이 부분을 Event 가 전달된다고 표현합니다. 그리고 이를 구독하고 있는 객체를 Listener 라고 칭합니다.

이 과정에서 제어의 역전이 나타납니다.

뷰가 뷰모델에 대해서 의존관계를 가지고 있는데, 제어는 뷰모델 -> 뷰 로 역전되게 됩니다. (이 부분은 따로 글을 작성할 예정)

퉁쳐서 이해하면, 내가 생성한 객체를 내가 컨트롤 하는 것이 아니라 생성당한 객체가 상위 객체를 컨트롤 하는 상황입니다.

이부분이 이해가 되지 않는다면 제어의 역전, 옵저버 패턴 부분을 보시고 오시면 됩니다. 추후에 내용 추가예정

어째든 이러한 아이디어로 부터 "반응형 프로그래밍" 으로의 패러다임이 전환됩니다.

반응형 프로그래밍

그래서 반응형 프로그래밍이 무엇이냐...
(참고한 글의 설명을 빌리겠습니다. https://yozm.wishket.com/magazine/detail/1334/)
감자합니다.

"무엇을 할지 선언을 하고 변경된 데이터를 감지하여 전파하도록 설계하는 프로그래밍"

여기에는 이미 선언적 프로그래밍의 속성이 포함되어 있죠.

초기의 반응형 프로그래밍은 View 에만 적용되었습니다. 이후에 단순히 뷰 뿐만 아니라 "비즈니스 로직" 에도 반영하는 방향으로 발전하게 됩니다.

그래서 처음 공부하는 사람이 Rx 를 배우면, 모든걸 선언적이고 반응형으로 작성하게 되서 어려움에 깜짝 놀라죠.

결국 반응형 프로그래밍을 통해 구성하고 싶은 것은 다음과 같습니다.

데이터의 변경을 감지해서 전파 -> 선언적으로 구성된 로직에 데이터를 전달 -> 반영(UI 든 뭐든)

여기까지 읽다보면 이런생각을 하실 수 있습니다.

(아니시에이팅) 아니~~ 반응형이고 선언형이고 좋은 것 알겠다고, 근데 그거 없이도 잘 살아왔는데, 굳이~~~ 반응형배울 필요가 뭐가 있냐고...

사실 저도 이 생각을 가지고 반응형이 단순히 멋진 코딩 스타일이 아닐까라고 생각한 적이 있습니다. 하지만 프로젝트를 하면 할수록, 필요함을 느꼈습니다. 필요함을 느낀 순간은 "비동기 프로그래밍" 을 다루는 시점 이였죠.

비동기 프로그래밍

비동기 프로그래밍이란?

하나의 테스크가 끝나기를 기다리지 않고 다음 테스크를 진행하도록 프로그래밍하는 것

정확한 설명은 아니지만, 위 정의로 퉁치고 말하고자 하는 것을 설명할게요. 해당 설명을 좀더 보고 싶으시면 아래 자료를 추천합니다.

참고하면 좋은 자료

비동기 프로그래밍은 고려할 것들이 많습니다. 예를들면, 사용자가 버튼을 광클해서 여러번 요청이 될 수도 있고, 그 버튼에 있던 동작이 하필 오래걸리는 테스크(서버에서 데이터를 가져오는데 그 용량이 많음) 일 경우, 다른 동작에 영향이 갈 수 있습니다. 그런데 이런 코드가 한 줄이 아니라, 엄청나게 많을 수 있겠죠.

  • 서버에서 데이터를 받아오는 동안 로딩인디케이터를 보여준다. 끝나면 로딩인디케이터를 제거하고 서버에 온 데이터를 랜더링 한다.
  • (사진필터앱) 이미지 프로세싱을 하는 동안 사용자에게 광고화면을 출력한다.
  • 앱을 실행하는 동안 리소스를 다운로드 하도록 하고 그동안 온보딩 화면을 보여주다가, 리소스 다운로드가 끝나게 되면, 사용자에게 이를 전달하는 UI를 출력하는데, 아직 온보딩을 보고 있다면, 보여주지 않고 마지막 화면에서 보여준다.
    ...등등

정리하면, 비동기 프로그래밍이 어려운 이유는 아래와 같습니다.

- 사용자가 어떻게 행동할지 규정할 수 없다.
- 코드를 작성한 순서대로 동작하지 않는다. (하나의 테스크가 끝나고 다음 동작을 하지 않고 병렬적으로 진행하므로)
- 여러 비동기 동작이 겹치면 어떤 문제가 생길지 모른다. (상태값이 변경된 경우)

어떤분은 위 문제를 보고 이렇게 말씀하실 수 있습니다.

그냥 콜백함수 떄려 넣어서 동작 끝난다음에 할 동작을 정의하면 되겠네. ㅅㄱ 안읽음

func 동기부여(@escaping 다끝나면호출할함수: () -> Void) {
	이거안하면망하는동작1() {
		이거안하면망하는동작2() {
			이거안하면망하는동작3()
				이거안하면망하는동작4() {
					다끝나면호출할함수()
				}
		}
	}
}

위 코드의 예시는 Swift 이긴 하지만, 다른 언어도 유사할겁니다. 파라미터로 함수를 받을 것이고, 해당 언어에 맞는 콜백함수를 작성합니다.

이 모습을 "콜백지옥" 이라고 이야기 하곤 합니다.

그래서 JavaScript 에서는 Promise, async / await 로 이 문제를 처리하려고 했습니다. 현재 Swift 와 dart 에서도 볼 수 있구요.

void actionMethod() async {
	await 첫번째TodoList();
	await 두번째TodoList();
	await 세번째TodoList();
	...
}

이렇게되면, 반응형 따위 안써도 되는걸까요? 아래 문제를 해결해보면서 생각해보죠.

<검색화면>

  • 타이핑을 할 때마다 서버에서 데이터를 요청하세요.
  • 요청할 때, 매번 요청하면 과부하가 우려되니, 입력하고 1초 이후에 요청하세요.
  • 이전 검색결과랑 같으면 하지마세요 + 띄어쓰기 제거
  • 요청을 했는데, 실패하거나 응답이 없으면(3초동안) 3번 더 요청하고 그래도 실패하면 사용자에게 에러 메시지를 보여주세요.("네트워크 상태를 확은해주세요." 와 같은)
  • 데이터를 받아오면 캐싱해주시고, 해당 데이터를 먼저 보여주세요. 그리고 요청에 의한 새로운 데이터를 받게되면, 교체해주세요.
  • 타이핑이 멈춘 것이 아니라 검색버튼을 누르면, 이전까지의 검색요청은 무효화하고 검색버튼 누른 시점의 데이터 결과만 요청해서 보여주세요.

검색 기능을 구현하면 보통 위와 같은 요구사항을 만족시켜야 합니다. 결론먼저 말씀드리면, 위 로직을 반응형 없이 구현하면, 상당한 공수 + 보기어려운 코드 라는 결과물이 나올겁니다.

왜냐하면, 적절한 시점에 데이터를 확인해서 분기처리해주고, 다시 데이터를 Pull 하고 해당 데이터에 따라 다시 분기처리의 반복일겁니다.

이러한 문제가 왜 발생했는지 생각해보면, 앱 혹은 웹에서는 고려사항이 2 곳이기 때문입니다. 바로 사용자와 서버 입니다. 전통적인 서버의 경우, 요청에 집중하면 되죠.

이러한 문제는 어떤 솔루션을 쓰면 좋을까요? 해서 나온 것이

Rx 그리고 반응형 프로그래밍

입니다.

이제 반응형 프로그래밍을 써야겠다는 생각이 조금은 드시나요? 그렇다고해주3

정리

  • 비동기 프로그래밍은 복잡한 환경이라 다루기 까다롭다.
  • 그래서 이를 간결하게 개발할 새로운 패러다임이 필요했다. 그래서 반응형 프로그래밍이 나타났다.
  • 반응형 프로그래밍은 "변경사항의 전파" 와 "데이터 흐름" 을 중심으로한 선언적 프로그래밍이다.
  • 변경사항의 전파는 Pull -> Push 로의 전환이 발생했다.
  • 데이터 흐름이 Event 방식으로 제어의 역전이 발생했다.

정리의 정리)

  • 변경사항 전파를 하기위해서 제어의 역전이 발생 -> 이벤트를 발송하고 이를 듣는 Listener 관계 형성
  • 여기에 + 선언적 프로그래밍 = 반응형 프로그래밍

이후에

  • Event + 제어의 역전
  • Stream
  • 선언형 프로그래밍 작성
  • Rx 맛보기
  • Data flow 프로그래밍
  • 함수형 프로그래밍

내용을 섞어서 2 부로 작성하겠습니다.

읽어주셔서 감사합니다.

참고문헌)

profile
iOS & Flutter

0개의 댓글