[JS] 함수형 프로그래밍의 이해

    728x90

    함수형 프로그래밍

    왜 우리(웹 프론트)는 함수형 프로그래밍을 배워야 할까요?

    우리는 javascript를 써야 하는 운명

    프로그래밍 방법에는 여러가지 패러다임이 존재합니다. 절차식 프로그래밍, 객체지향 프로그래밍, 함수형 프로그래밍 이런것들은 한번씩은 접해봤을법한 용어들입니다.

    함수형 프로그래밍이 객체지향 프로그래밍보다 더 낫기 때문에 함수형 프로그래밍을 배워야 할까요? 아닙니다.

    웹 개발을 하기 위해서 우리는 언어의 선택권이 없이 javascript를 해야만 하기 때문입니다.

    javascript 탄생비화

    javascript를 창시한 Brendan Erich는 언어를 개발할 당시 유행하던 객체지향에 한계를 느끼고 LISP, scheme등 함수형 프로그래밍에 관심을 가지고 있었기에 함수형 프로그래밍의 형태로 언어를 만들고 싶어 했습니다. 하지만 Netscape의 그의 상사는 당시 개발자들이 제일 많이 쓰던 Java와 같은 문법으로 만들기 요구했기 때문에 결국 둘의 혼종의 형태로 세상에 나오게 되었습니다. :)

    결국 javascript에는 언어의 태생부터 함수형 프로그래밍의 개념들이 녹아있고 동시에 객체지향의 가치는 다소 희석이 되어 있는 형태의 언어였습니다.

    당시 javascript는 사람들에게 이도저도 아닌 언어라며 놀림을 받았지만 지금은 가장 인기 있는 언어에 당당히 1위를 한 언어가 되었습니다.

    그래서 javascript는 멀티 패러다임 랭귀지다!

    javascript의 설명을 보면 multi-paradigm language 라고 되었습니다. 즉, 함수형과 객체지향이 혼종된 형태란 말이죠.

    결국 우리는 javascript를 쓸 수 밖에 없기에 객체지향이냐 함수형이냐 패러다임을 선택해서 깊게 파야 하는 것이 아니라

    javascript 그 자체를 잘 하기 위해서 javascript의 함수형과 객체지향을 둘 다 알아야 합니다.

    javascript에서 함수형 프로그래밍이 주목받기 시작한 이유

    Lodash, Ramda 등 정통 함수형 라이브러리가 존재했지만 대중에게 함수형 프로그래밍이 유명해진 이유는 따로 있었다.

    Redux의 순수함수

    지금은 아니지만 Redux가 대세론이 자리잡던 시기가 있었습니다. 그리고 빠짐없이 나왔던 키워드가 바로 "순수 함수"입니다. (후에 순수함수에 대해 자세히 기술하겠습니다.)

    불변성과 순수함수는 함수형 프로그래밍에서 아주 중요한 키워드이며 많은 사람들이 함수형 프로그래밍은 잘 몰라도 순수 함수나 불변성에 대해서는 다들 알게된 계기입니다.

    React의 함수형 컴포넌트

    React가 16.8.0 함수형 컴포넌트와 hook을 출시하면서 class 컴포넌트의 시대가 막을 내리고 함수형 컴포넌트 시대가 시작합니다. 이때부터 함수형이라는 키워드와 함께 HoC와 같은 함수형 프로그래밍의 용어들이 주요 키워드가 되면서 자연스레 프론트엔드에서 함수형 프로그래밍의 키워드 주목도가 올라가게 됩니다.

    새삼 프론트엔드에서 React의 영향력을 느낍니다.

    함수형 프로그래밍이 뭔가요?

    목적과 방향성

    깔끔하고 예측가능하며 테스트 하기 쉽고 안전한 코드를 만들기 위해서,

    부수 효과(Side-effect)가 있는 함수와 그렇지 않은 순수 함수(Pure Function)를 구분하여 문제를 최대한 작게 만들고 이를 조립하여 해결하는 방식

    부수효과(Side-Effect)란, 함수 내부에서 동작하는 행동들이 함수 외부로 영향을 끼치는 것을 의미합니다.

    대표적으로 1) 외부 값의 변화 2) API를 이용한 I/O(input, output) 등이 있습니다.

    순수함수와 불변성

    부수효과와 그렇지 않은 것을 구분해야 합니다. 순수함수란 부수효과가 없는 함수를 의미합니다.

    순수함수가 되려면 다음 조건을 만족해야 합니다.

    동일한 인자를 넣을 경우 항상 같은 값을 반환해야 한다.함수가 호출되고 나서 아무런 변화가 없어야 한다.

    그래서 조금더 자세히 적어보자면,

    1-1. 함수의 인자가 아닌 외부 변수를 사용하지 않아야 합니다. (상수는 OK)

    1-2. 함수 내부에서 Math.random()이나 file I/O등 호출때마다 달라지는 값이 없어야 합니다.

    2-1. 외부 변수의 값을 수정하지 않아야 합니다.

    2-2. 인자로 넘어온 Object나 Array, Date와 같은 값들의 필드를 내부에서 변경하지 않아야 합니다.

    2-2. 콘솔, 네트워크, 기타 DOM API등을 사용하지 않아야 합니다.

    2-3. try ~ catch등 같은 에러 처리 로직을 사용하지 않아야 한다.

    이런 것들을 다 안하면 어떻게 프로그램이 될까 싶지만 다시 읽어보면 목적은 이 둘의 구분입니다.

    순수함수는 항상 같은 값을 반환하기 때문에 테스트하기가 쉽고, 순수함수 + 순수함수 = 순수함수 가 되므로 순수함수로 조립된 최종값만 부수효과가 있는 함수를 통해 처리하고 다시 그 결과를 순수함수를 통해 조립을 하는 방식으로 개발을 하면 우리 목적을 달성할 수 있게 됩니다.

    🤨 흠.. 그래서 뭘 어떻게 하라는 거죠?

    좋은 javascript 코드를 작성하기 위한 코딩 습관

    let보다 가급적 const를 사용해라.

    함수형 프로그래밍은 불변성의 유지로 부터 시작합니다. 코드에서 let의 사용을 줄이고 const로 개발하는 습관을 가져보세요. 수정된 값이 필요하면 새로운 변수를 만들어서 사용해 보세요.

    Do Not This:

    let foo = 100
    ...
    foo = somthing(foo, "bar") 
    

    Do This:

    const foo = 100
    ...
    const new_foo = somthing(foo, "bar")
    

    Array, Date의 Mutation Method는 가급적 사용 금지

    push, pop, shift, sort, reverse 등 객체를 변하는 Method를 가급적 spread operator로 대체하거나 값을 복사해서 사용하세요.

    Do Not This:

    const example = (arr:number[], date:Date) => {
      arr.push(4)
      arr.sort()
      date.setMonth(10)
    }
    

    Do This:

    const example = (arr:number[], date:Date) => {
      const new_arr = [...arr, 4]
      const sorted_new_arr = [...new_arr].sort()
      const new_date = new Date(date).setMonth(10)
      return [new_arr, new_date]
    }
    

    가급적 Object의 필드에 대입 연산자를 쓰지 마세요.

    Do Not This:

    const example = (obj:Object) => {
      obj.foo = 200
    }
    

    Do This:

    const example = (obj:Object) => {
      return {...obj, foo: 200}
    }
    

    '가급적' 이라는 의미를 잘 생각하고 값을 변경할 때에는 항상 scope에 대해 생각하며 코딩하세요.

    본말을 전도해서 극단적으로 let을 쓰지 않고 Array push를 쓰지 않고 Object의 값을 변경할때 항상 복사를 하지는 마세요.

    값을 변경할때 변수가 선언된 위치에서 부터 3~7줄 범위내에서 수정되며 선언된 함수의 scope를 벗어나지 않는 값이라면 복사해서 값을 옮기는 행위는 리소스 낭비입니다.

    pure function과 effect를 확실하게 구분해서 작성하세요.

    일부만 Pure하면 Pure한게 아니다.

    const와 spread operator를 통해서 불변성을 지키는듯 보이는 코드들 안에 부수효과를 유발하는 코드가 있다면 pure하지 않습니다. 분리할 수 있는 방법을 고민하세요. pure function은 크지 않아도 좋습니다.

    그래도 요구사항대로 구현해서 코드가 작동하는게 제일이다!

    위에서 저렇게 말은 했지만 javascript는 멀티 패러다임 랭귀지이기에 함수형을 쓰던 객체지향으로 개발하던 잘 돌아가기만 한다면 일단 OK입니다. 언젠가는 차츰차츰 각 영역의 좋은점만 찾아서 영리하게 잘 이용하시리라 믿어요.

    마치며

    과거 디자인 패턴을 배우고 나서는 모든 구현 과제에 어떤 패턴으로 만들어야 맞는 것인지 모든 것이 패턴으로 풀어보려는 디자인 패턴병에 걸렸던 적이 생각이 납니다.

    그리고 함수형 프로그래밍도 배우기 시작하고 다 함수형으로 만들어보려는 함수형 병도 잠깐 앓고 지나 갔었습니다.

    여기서 이제 한발자국만 더 나가면 함수와 함수를 어떻게 결합할 수 있는지를 시작으로 객체지향의 디자인 패턴과 같은 것들이 나오기 시작하고 그런 것들을 익히다 보면 가끔 본질을 까먹을 때가 있습니다.

    우리가 함수형 프로그래밍을 해야 하는 이유는 깔끔하고 예측가능하며 테스트 하기 쉽고 안전한 코드를 만들기 위해서이며

    javascript가 태생이 완전한 객체지향도 완전한 함수형도 아닌 밸런스를 가지고 있는 언어이기 때문입니다.

    그래서 기법 수준의 내용이 들어가기 전 내용들을 모아 일단 정리해보았습니다.

    끝으로 드리고 싶은 말은, 프론트엔드에서 패러다임의 논쟁은 객체지향이 좋다 vs 함수형이 좋다 vs 그냥 취향 차이 아닌가? 가 아니라

    javascript의 특성에 맞는 객체지향의 좋은 점, 함수형 프로그래밍의 좋은 점 골라서 둘 다 내 것으로 해야 한다.

    라고 생각합니다.

    728x90

    'JS' 카테고리의 다른 글

    자바스크립트 동작원리  (0) 2024.03.16

    댓글