[개발 서적] 쏙쏙 들어오는 함수형 코딩(4)

5 분 소요

🔗 들어가며

함수형 코딩을 다루는 마지막 게시글이다. 이번 게시글에서는 타임라인에 대해 다뤄볼 것이다. 타임라인 다이어그램을 통해 액션 사이 순서를 파악하고, 코드를 개선해보자!


🔗 타임라인 다이어그램?

타임라인?

  • 액션을 순서대로 나열한 것

액션은

1️⃣ 순서대로 실행되거나,

2️⃣ 동시에 실행된다.

따라서 이를 통해 타임라인을 그릴 때는 다음 두 가지 원칙을 따라야 한다.

  1. 액션이 순서대로 나타나면 같은 타임라인에 넣는다.
  2. 액션이 동시에 실행되거나 순서를 예상할 수 없다면 분리된 타임라인에 넣는다.
    • 액션이 서로 다른 스레드나 프로세스 / 비동기 콜백에서 실행된다면 서로 다른 타임라인으로 표시해야 한다.

다음 코드와 이미지를 살펴보자!

fun calc(num: Int) {
  add(num)
  minus(num)
  pow(num)
}

add_minus_pow

add와 minus / pow가 단순 액션이라면 순서대로 호출된다. 따라서 동일한 타임라인에 순서대로 넣으면 된다. 하지만 만약 세 가지 함수가 모두 비동기 호출이라면 어느 것이 먼저 실행되고, 끝날지 모르기 때문에 서로 다른 타임라인에 넣어야 한다.

🔗 동시에 실행되는 타임라인의 순서 알아보기!

add / minus / pow는 서로 다른 타임라인에서 동시에 실행된다. 그렇다면 세 함수의 실행 순서를 알 수 있을까? 정답은 NO이다. 위와 같이 세 개의 액션이 동시에 실행된다면 가능한 순서는 다음과 같다.

  • add -> minus -> pow / add -> pow -> minus
  • minus -> add -> pow / minus -> pow -> add
  • pow -> add -> minus / pow -> minus -> add

즉, 모든 순서가 가능하기 때문에 동시에 실행되는 액션이 있을 경우, 이를 고려해 코드를 작성해야 한다.


🔗 놓칠 수 있는 액션 순서 짚고 가기!

타임라인은 액션을 순서대로 나열한 것이라고 했다. 타임라인 다이어그램을 그리기 위해 여러 액션을 파악하다보면 쉽게 놓칠 수 있는 액션 사이 순서가 존재한다.


1️⃣ ++, +=는 읽기/쓰기를 모두 포함한다!

++, +=는 얼핏보면 하나의 액션으로 파악하기 쉽다. 하지만 정확히는 값 읽기, 값 계산하기, 값 쓰기의 세 단계 액션이다. 따라서 액션을 표시할 때, 읽기, 쓰기를 따로 타임라인에 표현해야 한다.(계산 제외)

num++(num 전역변수라고 가정)

// 위 연산은 결국 다음의 세 단계를 의미한다.
var tmp = num
tmp = tmp + 1
num = tmp


2️⃣ 함수 호출 전 인자 읽기 먼저!

add(num) (위의 전역변수 num 넘겨줌)

// 위 코드는 결국 다음의 두 단계를 의미한다.
val tmp = num
add(tmp)

그렇다면 함수를 실행할 때 전역변수를 넘겨준다면 함수 실행과 전역변수 읽기 중에 어느 것이 먼저 실행될까?

위 코드에서 알 수 있듯이 전역변수 읽기가 먼저 실행된 후 해당 함수가 호출된다.


🔗 타임라인 그리기

이제 직접 타임라인을 그려보자! 다음의 코드를 바탕으로 타임라인을 그려보자!

// 새로운 worker 데이터베이스에 저장(비동기)
updateWorkerInfo(newWorker) {
  // 새로운 woker 포함 일하는 날 재배정(비동기)
  modifyWorkingDay() {
    // workder수 계산
    calcTotalWorker()
    // 각 workder salary 재계산
    calcTotalSalary()
  }
}

우선 위 코드에서 액션을 파악해보자.

  • updateWorkerInfo(), modifyWorkingDay, calcTotalWorker(), calcTotalSalary()

그렇다면 위 4가지 액션을 타임라인 다이어그램에 넣어보자!

updateWorkerInfo

우선 modifyWorkingDay는 콜백 함수이므로 새로운 타임라인에서 실행된다. 그 속의 calcTotalWorker, calcTotalSalary 역시 콜백 안에서 실행되기 때문에 또 다른 타임라인에서 실행된다. 하지만 두 함수는 순서대로 실행되기 때문에 동일한 타임라인에 넣어준다.

🔗 타임라인 단순화하기: 액션 / 타임라인 통합

1️⃣ 액션 통합

calcTotalWorkercalcTotalSalary는 순서대로 실행된다. 이때 이 함수들 사이 다른 함수가 끼어들 수 없다고 가정해보자. 이처럼 하나의 타임라인에서 순서대로 실행되는 액션들 사이에 다른 함수가 끼어들지 않는다면 타임라인 다이어그램 상에 하나의 박스로 표시해 단순화할 수 있다.(위의 노란 박스!)

2️⃣ 타임라인 통합

또한, 위 사진을 본다면 세 개의 타임라인 끝에 오직 하나의 타임라인만이 존재한다는 것을 알 수 있다. 이럴 경우, 타임라인 자체를 통합해 다이어그램을 단순화할 수 있다.

unify_timeline

✏️ 언어의 스레드 모델 이해하기

  • 순서대로 or 동시 실행 여부, 다른 함수가 끼어드는지에 대한 여부를 파악하기 위해서는 언어의 스레드 모델을 이해해야 한다.
  • 멀티 스레드, 단일 스레드 등에 따라 적절히 타임라인을 그리고 단순화해야 한다.


🔗 좋은 타임라인 원칙

좋은 타임라인 원칙을 적용하면 더 이해하기 쉬운 코드를 작성하는 데 도움이 된다.

  1. 타임라인 수를 줄인다.
  2. 타임라인 길이를 줄인다.
    • 타임라인 안 액션을 줄이자. 즉, 액션을 계산으로 바꾸자.(앞 게시글에서 다룬 암묵적 입출력 없애기 활용)
  3. 타임라인간 공유하는 자원을 줄인다.
  4. 자원을 공유한다면 타임라인을 조율해 안전하게 자원을 공유하자.


🔗 타임라인 사이 자원이 공유될 경우: 불필요한 자원 공유 없애기

위에서 동시에 실행되는 타임라인의 경우, 실행되는 순서를 알 수 없다는 것을 확인하였다. 만약 자원을 공유하지 않는다면 액션들이 어떤 순서로 실행되는지는 중요하지 않다.

그렇다면 실행 순서를 알 수 없는 타임라인 사이에서 자원을 공유하게 되면 어떻게 될까? 예상치 못한 결과가 발생할 수 있다. 이러한 경우 코드를 개선할 수 있는 여러 방안이 존재한다! 함께 살펴보자!

전역변수를 지역변수 / 인자로!

전역변수를 읽고, 쓰는 것은 액션에 해당한다. 따라서 전역변수보다는 지역변수나 인자를 활용하는 것이 더 좋다.

fun plusOne() {
  num++ // num은 전역변수
}

위 코드에서는 전역변수 num에 접근해 값을 읽고 쓰고 있다. 이럴 경우, 여러 타임라인에서 전역변수에 접근하게 되면 생각치 못한 결과가 나타날 수 있다. 여기에서는 전역변수를 인자로 바꿔보겠다!

fun plusOne(num: Int) {
  return num + 1
}

✏️ 물론, 지역변수를 선언하더라도 이러한 지역변수를 서로 다른 타임라인에서 공유한다면 이 또한 문제가 될 수 있다. 즉, 지역변수를 어떻게 사용하느냐에 따라 버그 발생 여부가 결정된다.


🔗 타임라인 사이 자원이 공유될 경우: 타임라인 조율하기

액션에 대한 타임라인 다이어그램을 그려본다면 코드 상에 문제를 보다 쉽게 파악할 수 있다. 위에서는 타임라인 사이 자원이 공유될 때 불필요한 자원 공유를 없애는 방법에 대해 살펴보았다.

하지만 자원 공유가 불가피한 경우가 있다. 이럴 때는 타임라인 조율을 통해 버그를 예방할 수 있다. 다음의 경우를 한 번 살펴보자!

control_timeline

왼쪽 타임라인을 본다면 2번의 읽기, 2번의 쓰기가 완료된 후 그 값을 읽어 update가 진행되어야 올바른 결과를 얻을 수 있다. 하지만 읽기/쓰기를 진행하는 타임라인 안에 update가 함께 있어 2번의 읽기와 쓰기가 완료된 후 값을 읽을 것이라는 보장이 없다.

따라서 오른쪽 타임라인들과 같이 2번의 읽기, 쓰기가 진행된 후 update가 진행되도록 순서를 제한하면 어떠한 상황이라도 원하는 결과를 얻을 수 있게 된다.

타임라인을 조율하는 방법은 언어마다 다르다.

자바스크립트와 같이 단일 스레드를 가지는 언어는 큐를 통해 순서를 제한할 수 있다. 멀티 스레드에서는 락과 같은 도구를 활용해 순서를 제한할 수 있다. 따라서 언어에 맞는 도구를 잘 활용해 타임라인을 조율하자.


🔗 마무리하며

지금까지 총 4개의 게시글을 통해 함수형 프로그래밍에 대해 다뤄보았다. 함수형 프로그래밍 언어인 kotlin을 사용하면서도 정작 함수형 프로그래밍에 대해서는 자세히 알지 못했던 것 같다. 함수형 프로그래밍에 대해 딥-다이브🌊하는 시간을 통해 지금까지 짜왔던 내 코드에 대해서도 다시 한 번 생각해보는 시간을 가질 수 있었다. 또한, 앞으로 kotlin을 잘 활용할 수 있는 방법에 대해서도 알아보는 시간이었던 것 같다.

요즘 사두었던 개발 서적을 하나씩 읽어보고 있다. 새로운 지식, 새로운 기술을 학습하는 것을 잠깐 멈추고 코딩을 하는 데 있어 필요한 기본을 조금 더 단단히 다지고자 한다. 객체 지향 프로그래밍에 이어 함수형 프로그래밍에 대해 학습하면서 나의 코드 스타일에 대해 고민해볼 수 있어서 너무 뜻깊은 시간이었다.

취준 기간 동안 집에 있는 개발 서적을 다 읽는 것이 목표이다. 이 시간을 잘 활용해 기본이 더욱 단단한 개발자로 성장하자!

댓글남기기