[개발 서적] 쏙쏙 들어오는 함수형 코딩(4)
🔗 들어가며
함수형 코딩을 다루는 마지막 게시글이다. 이번 게시글에서는 타임라인
에 대해 다뤄볼 것이다. 타임라인 다이어그램을 통해 액션 사이 순서를 파악하고, 코드를 개선해보자!
🔗 타임라인 다이어그램?
타임라인?
- 액션을 순서대로 나열한 것
액션은
1️⃣ 순서대로 실행되거나,
2️⃣ 동시에 실행된다.
따라서 이를 통해 타임라인을 그릴 때는 다음 두 가지 원칙을 따라야 한다.
- 액션이 순서대로 나타나면 같은 타임라인에 넣는다.
- 액션이 동시에 실행되거나 순서를 예상할 수 없다면 분리된 타임라인에 넣는다.
- 액션이
서로 다른 스레드나 프로세스 / 비동기 콜백
에서 실행된다면 서로 다른 타임라인으로 표시해야 한다.
- 액션이
다음 코드와 이미지를 살펴보자!
fun calc(num: Int) {
add(num)
minus(num)
pow(num)
}
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가지 액션을 타임라인 다이어그램에 넣어보자!
우선 modifyWorkingDay
는 콜백 함수이므로 새로운 타임라인에서 실행된다. 그 속의 calcTotalWorker
, calcTotalSalary
역시 콜백 안에서 실행되기 때문에 또 다른 타임라인에서 실행된다. 하지만 두 함수는 순서대로 실행되기 때문에 동일한 타임라인에 넣어준다.
🔗 타임라인 단순화하기: 액션 / 타임라인 통합
1️⃣ 액션 통합
위 calcTotalWorker
과 calcTotalSalary
는 순서대로 실행된다. 이때 이 함수들 사이 다른 함수가 끼어들 수 없다고 가정해보자. 이처럼 하나의 타임라인에서 순서대로 실행되는 액션들 사이에 다른 함수가 끼어들지 않는다면 타임라인 다이어그램 상에 하나의 박스로 표시해 단순화할 수 있다.(위의 노란 박스!)
2️⃣ 타임라인 통합
또한, 위 사진을 본다면 세 개의 타임라인 끝에 오직 하나의 타임라인만이 존재한다는 것을 알 수 있다. 이럴 경우, 타임라인 자체를 통합해 다이어그램을 단순화할 수 있다.
✏️ 언어의 스레드 모델 이해하기
- 순서대로 or 동시 실행 여부, 다른 함수가 끼어드는지에 대한 여부를 파악하기 위해서는 언어의 스레드 모델을 이해해야 한다.
- 멀티 스레드, 단일 스레드 등에 따라 적절히 타임라인을 그리고 단순화해야 한다.
🔗 좋은 타임라인 원칙
좋은 타임라인 원칙을 적용하면 더 이해하기 쉬운 코드를 작성하는 데 도움이 된다.
- 타임라인 수를 줄인다.
- 타임라인 길이를 줄인다.
- 타임라인 안 액션을 줄이자. 즉, 액션을
계산
으로 바꾸자.(앞 게시글에서 다룬 암묵적 입출력 없애기 활용)
- 타임라인 안 액션을 줄이자. 즉, 액션을
- 타임라인간 공유하는 자원을 줄인다.
- 자원을 공유한다면 타임라인을 조율해 안전하게 자원을 공유하자.
🔗 타임라인 사이 자원이 공유될 경우: 불필요한 자원 공유 없애기
위에서 동시에 실행되는 타임라인의 경우, 실행되는 순서를 알 수 없다는 것을 확인하였다. 만약 자원을 공유하지 않는다면 액션들이 어떤 순서로 실행되는지는 중요하지 않다.
그렇다면 실행 순서를 알 수 없는 타임라인 사이에서 자원을 공유하게 되면 어떻게 될까? 예상치 못한 결과가 발생할 수 있다. 이러한 경우 코드를 개선할 수 있는 여러 방안이 존재한다! 함께 살펴보자!
전역변수를 지역변수 / 인자로!
전역변수를 읽고, 쓰는 것은 액션
에 해당한다. 따라서 전역변수보다는 지역변수나 인자를 활용하는 것이 더 좋다.
fun plusOne() {
num++ // num은 전역변수
}
위 코드에서는 전역변수 num
에 접근해 값을 읽고 쓰고 있다. 이럴 경우, 여러 타임라인에서 전역변수에 접근하게 되면 생각치 못한 결과가 나타날 수 있다. 여기에서는 전역변수를 인자로 바꿔보겠다!
fun plusOne(num: Int) {
return num + 1
}
✏️ 물론, 지역변수를 선언하더라도 이러한 지역변수를 서로 다른 타임라인에서 공유한다면 이 또한 문제가 될 수 있다. 즉, 지역변수를 어떻게 사용하느냐에 따라 버그 발생 여부가 결정된다.
🔗 타임라인 사이 자원이 공유될 경우: 타임라인 조율하기
액션에 대한 타임라인 다이어그램을 그려본다면 코드 상에 문제를 보다 쉽게 파악할 수 있다. 위에서는 타임라인 사이 자원이 공유될 때 불필요한 자원 공유를 없애는 방법에 대해 살펴보았다.
하지만 자원 공유가 불가피한 경우가 있다. 이럴 때는 타임라인 조율을 통해 버그를 예방할 수 있다. 다음의 경우를 한 번 살펴보자!
왼쪽 타임라인을 본다면 2번의 읽기, 2번의 쓰기가 완료된 후 그 값을 읽어 update가 진행되어야 올바른 결과를 얻을 수 있다. 하지만 읽기/쓰기를 진행하는 타임라인 안에 update가 함께 있어 2번의 읽기와 쓰기가 완료된 후 값을 읽을 것이라는 보장이 없다.
따라서 오른쪽 타임라인들과 같이 2번의 읽기, 쓰기가 진행된 후 update가 진행되도록 순서를 제한
하면 어떠한 상황이라도 원하는 결과를 얻을 수 있게 된다.
타임라인을 조율하는 방법은 언어마다 다르다.
자바스크립트와 같이 단일 스레드를 가지는 언어는 큐를 통해 순서를 제한할 수 있다. 멀티 스레드에서는 락과 같은 도구를 활용해 순서를 제한할 수 있다. 따라서 언어에 맞는 도구를 잘 활용해 타임라인을 조율하자.
🔗 마무리하며
지금까지 총 4개의 게시글을 통해 함수형 프로그래밍
에 대해 다뤄보았다. 함수형 프로그래밍 언어인 kotlin
을 사용하면서도 정작 함수형 프로그래밍에 대해서는 자세히 알지 못했던 것 같다. 함수형 프로그래밍에 대해 딥-다이브🌊하는 시간을 통해 지금까지 짜왔던 내 코드에 대해서도 다시 한 번 생각해보는 시간을 가질 수 있었다. 또한, 앞으로 kotlin을 잘 활용할 수 있는 방법에 대해서도 알아보는 시간이었던 것 같다.
요즘 사두었던 개발 서적을 하나씩 읽어보고 있다. 새로운 지식, 새로운 기술을 학습하는 것을 잠깐 멈추고 코딩을 하는 데 있어 필요한 기본을 조금 더 단단히 다지고자 한다. 객체 지향 프로그래밍
에 이어 함수형 프로그래밍
에 대해 학습하면서 나의 코드 스타일에 대해 고민해볼 수 있어서 너무 뜻깊은 시간이었다.
취준 기간 동안 집에 있는 개발 서적을 다 읽는 것이 목표이다. 이 시간을 잘 활용해 기본이 더욱 단단한 개발자로 성장하자!
댓글남기기