개발 R.I.P.

7.03 Dev.Feedback (Side Effect)

편행 2021. 7. 3. 21:39
반응형

Side Effects

우리가 작성한 코드 혹은 함수에 side effects가 있는지 어떻게 알 수 있을까?

 

먼저 side effects에 대해 짚어보기 전에 왜 side effects에 대해 알아야 하는지 짚어보고자 한다.

 

현대 코딩에서 선언형 프로그래밍의 패러다임이 주를 이루고 있다.  그 방식은 함수형 프로그래밍을 통해 이뤄지고 있는데, 이 함수형 프로그래밍은 명령형 프로그래밍으로 인해 생겼던 수 많은 side effects들을 줄여주게 되었다. 여기서 side effects는 외부 상태의 변화에만 국한되지 않고, Input과 Output, database, log system, API와 같은 것들과 상호작용하면서 영향을 줄 수 있는 경우들을 전부 가리킨다. 즉, 우리가 통제하고자 하는 모든 부분들에 side effects가 발생하게 될 가능성이 존재한다는 것이다.

 

함수형 프로그래밍의 룰 중 중요한 점은 우리가 어떤 기능을 만들 때, pure function으로 작성하는 것을 우선적으로 하여 외부와 상태를 공유하거나 data를 변경하면 안된다는 것, 즉 절대로 외부의 상태에 영향을 주면 안된다는 것이다. 만일 우리가 의도했든 의도하지 않았든, 함수가 외부의 상태에 변화를 주게되었다면 side effects로 간주하게 되며 함수형 프로그래밍의 가장 원론적인 존재 이유를 망가뜨리게 된다.

추가적으로 함수 본인이 외부에 영향을 받으면 안된다!! 함수형 프로그래밍의 Main concept

이렇게 보면, side effect 자체가 매우 안 좋은 것으로 보일지도 모른다. 하지만, 모든 side effect가 나쁜 것이 아니다. setTimeout(), sleep(), wait() 와 같은 함수들은 side effects를 발생시키지만, 멀티 스레드같은 기능을 구현해주는 이득을 가져다 주기도 한다. 즉 의도적인 side effect를 발현해서 멀티 스레드를 구현하는 것

 

위의 상황을 토대로 우리가 정확하게 정리할 수 있는 side effects가 안 좋게 여겨지는 경우는 그 side effects가 우리의 의도에 따라 발현이 되지 않았을 경우인 것이다. 이런 경우에는 예측 자체를 할 수 없게 되어 큰 문제를 발생하게 되고, 그 문제를 해결하기 위한 디버깅도 엄청나게 오래 걸리게 된다.

 

어떤 상황이 벌어졌을 때, side effect가 발생하게 됐다고 표현하는지 리스트로 정리해본다.

  • 변수의 값이 변경됨
  • 자료 구조를 제자리에서 수정함
  • 객체의 필드값을 설정함
  • 예외나 오류가 발생하며 실행이 중단됨
  • 콘솔 또는 파일 I/O가 발생함

그렇다면 우리가 의도하지 않은 side effect를 해결하는 방법을 찾아보자.

상태를 공유하게 된 경우

상태를 공유했다는 뜻은 객체, 변수 또는 메모리 자체를 변경하게 되는 상황이다. 이런 경우가 발생하면 클로저 함수 또는 클래스처럼 동일한 scope를 공유한 경우 혹은 전역 스코프에까지 영향을 미치게 되어 side effects를 발생하게 된다.

 

가장 일반적인 경우는 경쟁상태이다. 만일 사용자의 정보를 담은 객체를 변경하기 위해 saveUser()함수를 통해 어떤 API를 요청하는 동안, 정보를 변경하고 다시 saveUser()함수를 호출하게 되면 처음으로 요청한 정보가 업데이트 되기 전에 수정한 부분들만 업데이트되어 원하는 값을 제대로 꺼내지 못하는 상황이 벌어지게 되는 것.

 

또 다른 경우는 위의 변수를 변경시킴으로 인해 위에서부터 순차적으로 함수의 에러를 발생시키게 되는 것이다.

const state = {
  myVal: 1
};

const doSomething = () => state.myVal += 1;
const handleSomeEvent = () => state.myVal +=2;

doSomething(); // state.myVal = 2;
switch (state.myVal) {
  case 2:
    handleSomeEvent(); // state.myVal = 4;
    break;
}

console.log(state) // { myVal: 4 }

위의 코드에서 우리가 원하는 결과값은 4이다. 위의 순서대로 하면, 문제가 발생하지 않는다.

하지만 만일 doSomething함수를 아래로 내려버리면 어떻게 될까?

const state = {
  myVal: 1
};

const doSomething = () => state.myVal += 1;
const handleSomeEvent = () => state.myVal +=2;

switch (state.myVal) {
  case 2:
    handleSomeEvent(); // never gets called
    break;
}

doSomething(); // state.myVal = 2;

console.log(state) // { myVal: 2 }

우리가 원하는 값을 얻기 위한 handleSomeEvent함수가 실행이 되지 않게 되어 원하는 값을 얻지 못하게 된다.

 

순수함수 적용

이것을 해결하기 위해선 함수형 프로그래밍의 메인 컨셉 중에 하나인 순수함수를 사용해주면 된다.

const state = {
  myVal: 1
}

const f = (state) => ({ ...state, ...{ myVal: state.myVal + 1}});
const g = (state) => ({ ...state, ...{ myVal: state.myVal + 2}});

const newState = f(state);
console.log(state); // { myVal: 1 }
console.log(newState); // { myVal: 2}

const finalState = g(f(state));
console.log(state); // { myVal: 1 }
console.log(finalState); // { myVal: 4 }

 

Immutability (불변하는 값으로 만들기)

함수형 프로그래밍의 메인 컨셉중 하나인 Avoid mutate states를 적용하는 것이다. 먼저 const keyword를 통해 재할당이 이루어지지 않게 해주는 방식이 있다. 또한 객체 자체를 불변하게 만들어주는 방법도 있다. 아무리 우리가 const를 통해 변수에 재할당을 막아놔도 배열 또는 객체가 변경이 되는 경우가 있다. Object.freeze를 통해 1차원 깊이의 변경을 막아줄 수 있다. 또한 만일 불안하다면, 재귀함수로 Object.freeze를 적용하여 객체의 모든 깊이에 불변성을 부여해줄 수 있다.

 

https://immutable-js.com/

 

Immutable.js

Immutable collections for JavaScript Immutable data cannot be changed once created, leading to much simpler application development, no defensive copying, and enabling advanced memoization and change detection techniques with simple logic. Persistent data

immutable-js.com

라이브러리를 통해 구현하는 방식도 있다.

const state = Object.freeze({
  myVal: 1
});

state.myVal = 2;
console.log(state.myVal); // 1

const myObj = Object.freeze({
  nested: { myVal: 1}
});

myObj.nested.myVal = 2;
console.log(myObj.nested.myVal); // 2

멱함수(Idempotency)

컴퓨터 과학에서 멱함수로 여겨지는 조건이 있는데

  • 명령형 프로그래밍 체계에서 어떤 state가 사이드 이펙트를 가진 함수가 한 번 이상 실행되어도 계속 동일하게 유지되었을 때
  • 함수형 프로그래밍에서의 순수함수(pure function)
  • 수학적인 관점에서 봤을 때, 그 함수가 같은 값을 계속 return 해줄때
// Idempotent function
var x = 0;
function f(n) {
  x = n;
}

f(5); // x = 5
f(5); // x = 5
f(5); // x = 5

 

결론

side effects를 무조건 잘못됐다고 생각하는 것은 옳지 않다. 의도에 의한 side effects는 멀티 스레드를 구현해주기도 하기 때문이다.

결국 코드를 작성함에 있어 자신의 의도를 정확히 하고 코드를 구성하게 되면 우리의 인식에서 부정적인 부분들도 매우 유연하게 사용을 해줄 수 있는 것이다.

반응형

'개발 R.I.P.' 카테고리의 다른 글

7.05 Dev.Feedback ( React #7 컴포넌트 디자인)  (0) 2021.07.05
7.04 Dev.Feedback (Node.js #3)  (0) 2021.07.04
7.01 Dev.Feedback (미들웨어)  (0) 2021.07.01
6.30 Dev.Feedback (HTTP/ Network)  (0) 2021.06.30
6.29 Dev.Feedback (Promise)  (0) 2021.06.29