04-automatic-static-optimization

원본 저장소:

next.js/docs/03-pages/01-building-your-application/02-rendering/04-automatic-static-optimization.mdx at canary · vercel/next.js

Next.js kr 저장소: https://github.com/Nextjs-kr/Nextjs.kr

내 저장소: https://github.com/jlee0505/Nextjs.kr

참고 저장소들:

https://github.com/haileyport/Nextjs.kr

'Extracurricular > 활동' 카테고리의 다른 글

새싹톤 우수상 회고  (0) 2024.03.25
[FECrash] 함수형 프로그래밍 스터디 중간 회고  (0) 2023.01.08

https://velog.io/@jlee0505/goorm-x-SeSAC-x-%EC%84%9C%EC%9A%B8%EA%B2%BD%EC%A0%9C%EC%A7%84%ED%9D%A5%EC%9B%90-%EC%83%88%EC%8B%B9%ED%86%A4-%EC%9A%B0%EC%88%98%EC%83%81-%ED%9B%84%EA%B8%B0-%EC%96%B4%EC%84%9C%EC%99%80-%ED%95%B4%EC%BB%A4%ED%86%A4%EC%9D%80-%EC%B2%98%EC%9D%8C%EC%9D%B4%EC%A7%80

목차

1. Redux 선택 이유

2. Redux와 contextAPI 구조적 차이

3. Redux 사용 후기(contextAPI와의 비교)

4. 마무리


1. Redux 선택 이유

아직 후기를 쓰진 못했지만 지난 미션에서는 상태관리로 Context API를 사용했었다. Context API를 사용하며 처음 전역상태관리의 개념, Context API와 다른 전역상태관리 라이브러리의 차이점에 대해 공부했었는데, 이 과정에서 전역상태관리 개념의 최초에 redux가 있다는 걸 알게 되었다. 

 

redux에서 처음 나왔다고 알려져있는 reducer와 dispatch의 개념은 언제나 헷갈리는 개념이었다. 또 React에서 상태를 관리하는 방법에는, 요즘 뜨는 recoil, 네이티브 contextAPI, 기타 mobx... 정말 다양하다고 알려져 있지만, 앞으로 어떤 회사에 가서 어떤 상태관리 라이브러리를 사용하게 되든 근본인 redux를 배워두면 컨셉을 이해하는데 유용하지 않을까 생각했다. 

 

그래서 사실 이번 미션의 필수 요구 사항은 오직 React(contextAPI)로만 상태관리를 하는 것이었음에도 불구하고  redux 를 택하게 되었다. 반항심리는 아니었고 근-본은 과연 어떤지, 네이티브 전역상태관리 기능인 contextAPI와는 어떤 차이가 있는지 직접 써보며 확인하고 싶었다.

 

2.  Redux와 ContextAPI의 차이

아래는 한 블로그에서 하나의 도표로 정리한 Redux vs ContextAPI의 차이점으로 분석하며 내 경험과 비교해보았다.(🔗 Redux vs Context API: When to use them, Tapajyoti Bose)

  Redux  Context API
1. 설치 추가 설치 필요, 최종 번들 크기 증가 React와 함께 제공되는 기본 제공 도구
2. 설정 React 애플리케이션과 통합하려면 광범위한 설정이 필요 최소한의 설정 필요
3. 데이터 정적 데이터동적 데이터 모두에 적합 자주 새로 고치거나 업데이트하지 않는 정적 데이터를 위해 특별히 설계
4. 확장성 초기 설정 후 새로운 데이터/액션을 쉽게 추가할 수 있어 쉽게 확장 가능 새로운 컨텍스트를 추가하려면 처음부터 새로 만들어야 함
5. 디버깅 개발 도구에서도 컴포넌트 구조 디버깅을 쉽게 해주는 놀랍도록 강력한 Redux 개발 도구  고도로 중첩된 React에서는 디버깅이 어려울 수 있음.
6. 코드 구성 UI 로직과 상태 관리 로직을 분리하여 코드 구성 개선 UI 로직과 상태 관리 로직이 동일한 구성 요소에 있음

1. 설치

Redux는 npm i redux redux-react @reduxjs/toolkit  으로 redux포함 3가지 라이브러리를 설치해야한다. 이는 최종 프로그램 번들 사이즈의 향상으로 이어진다. 반면 Context API는 다른 설치 없이 사용 가능하다.

 

2. 설정

Redux는 reducer, slice, store, Provider로 감싸기 등 프로그램 사용을 위해 초기 설정해줘야하는 게 많다. 반면 Context API는 context생성 Provider로 감싸주기로 초기 설정이 간단하다.

 

3. 데이터

Redux는 정적 및 동적 데이터 모두에 적합하다고 하는데, 그 이유는 Redux가 불변성을 유지하고 예측 가능한 방식으로 상태를 관리하도록 설계되었기 때문이다(Thinking in Redux, redux). 

 

반면, Context API는 정적 데이터를 위해 것으로 예를 들면, 테마나 인증 정보, 사용자 언어와 같은 수준의 잘 변하지 않는 데이터를 전역적으로 공유할 때 유용하다고 한다.(when to use context, react) 따라서 Redux와 달리 복잡한 상태관리를 위한 툴은 아니라고 할 수 있겠다.  

 

4. 확장성

Redux는 새로운 데이터나 액션을 추가할 때, 서브store 개념인 slice나 새로운 액션인 reducer를 새로 추가하면 되는 반면, context는 서브context 개념이 따로 없어 수정이 어렵다는 말로 들린다. 

 

하지만  Context API가 보통 가장 상단이 되는 컴포넌트를 감싸는 식으로 사용되는 것과 달리, 컴포넌트와 상태를 별도의 파일로 분리해 보다 확장성 있게 사용하는 방법도 있다고 한다(How to use context effectively, Kent C. Dodds) 따라서 새로운 데이터나 액션을 추가하기 위해 컨텍스트를 처음부터 새로 짜야만한다는 이 부분에 대해서는 의구심이 든다. 

 

5. 디버깅

Redux에서는 리덕스 개발자 도구를 처음 개발하고 배포한 개발자 중 한 명인 Zalmoxisus라는 개발자가 만든 크롬 확장 Redux DevTools이 존재한다. 아래와 같은 디버깅을 위한 다양한 기능을 제공하고, 이를 이용해 훨씬 쉽게 디버깅을 할 수 있다:

  • 시간여행(time-travel) 기능: 과거 상태로 돌아가서 상태 변경 과정을 디버깅할 수 있습니다.
  • 상태 스냅샷 기능: 현재 상태를 스냅샷으로 저장하여 나중에 다시 볼 수 있습니다.
  • 액션 필터링 기능: 특정 액션만 필터링하여 디버깅할 수 있습니다.

반면 Context API는 Provider Hell을 야기할 수 있고(🔗 Context API가 존재하지만 여전히 사람들이 redux와 전역 상태관리 라이브러리를 쓰는 이유, nanalog) 이러한 고도의 중첩의 경우 디버깅이 상당히 복잡하고 짜증나는 작업이 될 수 있다.

 

6. 코드 구성

Redux는 UI 로직과 상태 관리 로직을 분리하여 보다 클린한 코드 구성이 가능하하지만, context API는 UI를 업데이트하는 로직과 상태 관리 로직이 동일한 구성 요소에 있다고 한다. 

 

하지만 이 역시 context에서 reducer 함수나 custom hook의 사용을 통해 충분히 분리할 수 있을 거라 생각해 공감이 가진 않는다.

// 1. useReducer 사용해 UI로직과 상태관리 로직 분리하기

import React, { useReducer } from "react";

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + 1 };
    case "decrement":
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

export const CounterContext = React.createContext();

function CounterProvider(props) {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <CounterContext.Provider value={{ state, dispatch }}>
      {props.children}
    </CounterContext.Provider>
  );
}

export default CounterProvider;
// 2. custom hook(useCounter) 사용해 UI로직과 상태관리 로직 분리하기

import React, { createContext, useContext, useState } from "react";

const CounterContext = createContext();

function CounterProvider(props) {
  const [count, setCount] = useState(0);

  const increment = () => setCount(count + 1);
  const decrement = () => setCount(count - 1);

  const value = { count, increment, decrement };

  return <CounterContext.Provider value={value} {...props} />;
}

function useCounter() {
  const context = useContext(CounterContext);
  if (context === undefined) {
    throw new Error("useCounter must be used within a CounterProvider");
  }
  return context;
}

export { CounterProvider, useCounter };
// 이렇게 하면, useCounter hook을 사용하여 UI를 업데이트하는 로직과 상태 관리 로직을 분리할 수 있다.

import React from "react";
import { CounterProvider, useCounter } from "./CounterContext";

function Counter() {
  const { count, increment, decrement } = useCounter();

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
}

function App() {
  return (
    <CounterProvider>
      <Counter />
    </CounterProvider>
  );
}

export default App;

 

3. 마무리하며

 

처음 상태관리 라이브러리로 redux를 직접 사용해보며, 시중에 나와있는 redux vs contextAPI 글을 분석해봤다. 4번과 6번 특징에 대해서는 동의하지 않지만, 그외의 특징에는 동의한다. 특히

 

1. 초기 세팅이 너무 많다는 점에 동의한다.

이를 위해 reduxjs/toolkit이 등장했다고 해도 여전히 contextAPI보다는 초기 설정 너무 많아 번거로움.

 

2. 디버깅이 쉽다는 점에 동의한다.

redux 개발자 도구를 사용해서 전역 관리 상태를 가시적으로 볼 수 있는 점이 너무너무 편리했다. redux 자체보다는 redux 개발자 도구을 쓸 수 있다는 점이 좋았다. 

 

3. 단순한 상태라면 redux를 사용할 필요는 없다.

redux는 초기 설정이 번거롭지만, 이후 데이터 확장성이 좋고 보다 복잡하고 많은 상태를 관리할 수 있다는 장점이 있다고 한다. 하지만 굳이 cartSlice 하나의 상태로도 충분했던 이번 미션에서 ContextAPI로도 충분했으리라는 생각이 든다. 

 

'Extracurricular > 교육' 카테고리의 다른 글

[NEXTSTEP] CDD with React 온보딩 미션 회고  (0) 2023.02.12
[글또 8기] 글또를 시작하며  (0) 2023.01.31

[NEXTSTEP] TDD with React

 

들어가며

 NEXTSTEP 에서 진행하는 TDD with React 2기 프로그램에 운이 좋게 참가하게 되었다. 

 잘 모르시는 분들을 위해 간단한 설명을 덧붙이자면, NEXTSTEP은 개발 교육 프로그램으로, 우아한형제들 개발자분들이 연사로 참여하신다. 내가 참여한 이번 TDD with React 코스는 2월 6일(월) ~ 3월 28일(화)까지 총 8주간 진행되는교육프로그램으로, TDD(Test-Driven Development)와 CDD(Component-Driven Development) 등 React로 클린코드 개발 학습을 목표로 한다.

 

 이번주는 온보딩 미션 기간이었다. 이번 글에서는 온보딩 미션으로 리액트로 계산기 구현 미션을 진행하며 배운 것을 정리해보고자 한다. 

 

+ 추가로 파이널 미션이 있다.

 

미션을 진행하며 내가 배운 것들 - React 관련

이번 미션의 리뷰어님은 오태은님이셨다. 리뷰어님이 남기신 코멘트에서 다시 찾아봐야겠다 싶은 부분 중 리액트 관련 내용만 추려봤다:

1) props 에서 익명함수 가능한 자제하기

2) 컴포넌트 분리 (a.k.a 선언적이다의 뜻)

 

1)  props 에서 익명함수 가능한 자제하기

1-1) 불필요한 레퍼런스 변경 막기 

 리액트에서 이벤트를 핸들링할 때 인자가 필요한 콜백함수를 넘겨주어야하는 경우가 있다. 콜백함수를 인자를 넘겨주는 방법에는 익명함수, 이벤트 객체 사용으로 2가지가 있지만, 그중 익명함수를 쓰는 것이 대표적이라고 한다. 따라서, 인자를 넘겨주는 콜백함수를 쓰는 경우를 대비해 익명함수로 콜백을 넘겨주는 걸 습관으로 들였었다.

 하지만 콜백을 익명함수로 넘길 경우 JSX가 호출될 때마다 새로운 함수를 생성하게 된다고 한다. 즉, 그 때마다 새로운 레퍼런스를 참조하게 되는 것이다. 계속해서 새로운 레퍼런스를 참조하게 되면 vdom을 실제dom에 반영하는 과정이나 useMemo를 사용하는 경우에 좋지 않다고 한다. 따라서 인자를 넘겨주어야 하는 콜백이 아니면 익명함수로 넘기지 않는 것이 성능상 좋다.

 

1-2) 객체 타입은 useMemouseCallback 사용하기

 첨언으로 useMemo와 useCallback에 대해서도 언급해주셨는데, 이부분은 아직 다루지 않은 개념이라 낯설어 찾아봤다. 간단히 말해, useMemo는 한 번 계산한 값을 다시 쓰는 것이고 useCallback은 한 번 사용한 함수 결과를 저장했다 재사용하는 것이다. 

 JSX 호출마다 객체에 대한 참조값도 계속 바뀌게 되는데, 호출마다 새로운 레퍼런스를 참조하는 것은 앞서 언급했듯 앱의 성능을 낮춘다. 따라서, object 나 array 같이 객체 사용에 있어서 useMemo 를 사용하는 것이 좋다.

 

2) 컴포넌트 분리  (a.k.a 선언적이다의 뜻)

리액트를 제대로 공부한지 얼마안됐지만 항상 언급되는 것이 제대로된 '컴포넌트의 분리'인듯 하다. 리액트의 핵심 개념인 컴포넌트를 어떻게 재사용가능하게 분리하는 지가 중요한가보다. 이와 관련해서는 그냥 (1)컴포넌트는 재사용이 가능해야 하고, (2)앱 전반적 비즈니스 로직별로 굵직하게 나누는게 좋다고 알고 있다. 그런데 이걸 의도해서 적용한 건 아니었고...주어진 기본 HTML의 구조가 크게 4개로 나누어지는 것 같아, 그 역할대로 나누었을 뿐이었다. 그런데 '선언적'이라는 용어를 쓰시며 뜻밖의 칭찬을 들었다. 그래서 찾아보게 되었다.

 

2-1) 리액트 컴포넌트 분리는 어떤 기준으로 하는 게 좋을까?

 

 해당 물음에 대한 답을 찾아보다 FEConf2021의 원지혁님 강의와 토스슬래시의 한재엽님의 강의를 보게 되었다. 두 분의 의견에 따르면, "컴포넌트를 쪼개는 절대적 기준 같은 것은 없다. 하지만 상대적으로 잘 쪼개는 컴포넌트 분리 기준은 존재한다."는 것이었다.두 분 모두 '유지보수를 쉽게 하는'것에 기준을 두고 컴포넌트 분리를 설명하신 것이 인상 깊었다. 유지보수를 쉽게 하려면 어떻게 해야할까? 미래에 변할 수 있는 것과 상대적으로 잘 변하지 않을 것 구분하는 것이다.

 

 원지혁님은 컴포넌트의 구성을 스타일, 로직, 전역상태, 리모드 데이터 스키마(데이터)로 분리하셨다. 유지보수를 위해 변화가 가장 잘 일어나는 데이터 모델에 따라 컴포넌트 분리를 해주는 것을 추천하셨다. 한재엽님은 변하는 것에 앞서 설명된 데이터를 데이터계산과 상호작용으로 나누셨다. 결국 둘다 데이터 관리부분인데 처음부터 제공되는 데이터냐, 사용자의 액션이 있어야 계산되는 데이터냐에 따라 한 번 더 나누신듯하다. 변하는 부분을 데이터, 변하지 않는 부분을 UI로 잡고 분리한다는 점에서 원지혁님과 의견이 같았다. 두 분 다 컴포넌트 분리의 기준을 유지보수로 잡고 계시고, 상대적으로 잘 변하는 것, 즉 컴포넌트가 다루는 데이터를 기준으로 컴포넌트를 분리하는 것을 제안하신다는 데에서 동일했다.

 

 이것을 이번 계산기 모델에 적용해보았다. Total, Digits, Modifier, Operations 4가지 컴포넌트가 모두 useCalculator를 통해 calculator 데이터 모델 하나만 필요하다. calculator 모델에는 4개의 메서드 -숫자입력(addDigit), 연산자입력(addOperation), 계산(caculate), 리셋(reset)-상태관리 부분-현재 상태(state)와 현재 상태를 변경하는 calcReducer-가 있다. 각 컴포넌트 Total -> state.total, Digits -> addDigit, Operations -> addOperation, calculate, Modifier -> reset 에 의존하고 있다. 아마 데이터 모델을 calculator 하나에 다 때려박지 않고 각각 나눠서 정의했다고 친다면, 필요한 데이터에 따라 컴포넌트를 잘 나눈 것이기 때문에 칭찬을 받은 게 아닐까? 다만 이번 계산기 미션은 온보딩이고 작은 앱인만큼, 굳이 데이터를 여러개로 나눌 필요가 없다고 생각하셔서 데이터 모델을 분리하는 것까지는 언급하지 않으셨던게 아닐까하는 생각이 든다. 

 

2-2) 리액트 컴포넌트 분리를 '선언적'으로 한다는 것은 어떤 의미일까?

 

"...컴포넌트는 선언적이다: 컴포넌트는 개발자로 하여금 어떻게(HOW)UI를 그려야 할지에 대한 고민 없이 주어진 상태에 기반해서 어(What) UI가 생겨야하는지 적기만 하면 되도록 도와준다. - Thinking in Relay

 의도한 것은 아니었지만 '선언적이다'는 용어의 의미를 좀 더 명료하게 알기 위해 찾아보다보니 원지혁님의 강의가 상단에 떴다.

 

 결국 선언적이란 건 무엇인지에 대해서만 짧게 추리자면, 선언적(Declarative)의 뜻은 누가봐도 명료하도록 컴포넌트의 분리를 나눈 것이다. 반대 개념으로 언급되는 명령형(Impertaive)가  로직의 순서대로 구현하여 흐름을 파악하기 위해 순서대로 코드를 전부 읽어야 한다면, 선언적이라는 것은 그중 공통된 주제(데이터)를 가진 로직(HOW)를 엮어 주제별(WHAT)으로 잘 뭉쳤다는 뜻인 것 같다. 

 

 사실 이렇게 컴포넌트를 나눈 것은 다른 미션수행자의 안목을 빌려온 것인데, 다음 미션부터는 어떻게 데이터를 관리하고 유저와 상호작용할 지에 대한 HOW를 고민해보고, 그 중 공통된 HOW들을 뭉쳐 WHAT 으로 잘 선언하는 설계하는 고민에 시간을 들여야겠다고 생각했다.

 

미션을 진행하며 내가 배운 것들 - React 외의 것들

리액트와 관련된 부분 이외의 자잘한 부분들을 정리하면 아래와 같다.

1) 왜 jsx 는 확장자를 js 로 해도 돌아가나

2) 정규식을 제대로 사용하기

3) 에러핸들링을 통해 사용자 경험 높이기

4) prettier 설정, 정확히 알고 하기:

4-1) 디폴트 설정 굳이 중복하지 말기 4-2) extend: [prettier: recommend] 4-3) VSCode 에서이 설정과 프로젝트별 설정

정규식 공부하자
에러핸들링 공부하자

 

마무리하며

 간단한 미션인데도 복습하고 공부할 게 꽤 많았다.

 

 리액트 문법에 대해서만 배웠던 현재 단계에서, 한 단계 더 들어가 1)리액트 최적화 2)리액트 컴포넌트 분리 등 어떻게 더 '잘' 짤 것인지에 대한 고민과 공부를 할 수 있었던 시간이었다. 이러한 면에서 리액트 '문법만' 다룰 줄 아는 학습 단계에 계신 분들께 감히 추천하고 싶다.

 

 또 각 미션별로 이렇게 회고를 쓰는 것이 학습에 정말 많이 도움이 되었다는 구하지 않은 조언도 덧붙이고 싶다. 회고를 쓰며 '이 미션에서 얻은 것'을 정리하기 위해 리뷰를 학습 방향 지표로 삼아, 추가적으로 공부할 수 있었다. 아니었더라면 단순히 리뷰를 읽고 끝냈을 것이다...

 

 이제 겨우 온보딩일 뿐이지만 배운 것이 많은 한 주였다. 우선 미션을 하고, 그 후 리뷰에 따라 학습하고 수정해나가나는 자세에서 진정한 Learn by Doing 을 느낄 수 있었다. 그냥 Doing 만 하면 학습효과를 이만큼 못가져간다는 걸 회고를 쓰며 깨달았다. 마지막까지도 꾸준히 배우고 회고하는 자세를 가져갈 수 있도록 노력해야겠다. 

 


2023.03.19 Updated

안녕하세요,

글쓰는 또라이,
일명 글또 8기를 시작하게 되었습니다.

 

글또 공식 노션 보러가기

글또 페이스북 보러가기

 

왜 글또를 시작했나?

개발을 공부한지 1년, 다른 분야보다도 기술 분야는 글로 하는 소통이 더 활발할 분야라는 인상을 받았다.

짧은 프로젝트 하나를 하더라도 노션을 통해 서로의 작업 현황을 공유하는 부분이 인상적이었고,

서로 다른 시간에 작업하더라도 언제든 확인할 수 있게끔 기록을 남겨두는 것이란 생각이 들었다.

- 또 개인적으로는 전문적인 기술 내용의 공유는 말로 하기 보다 잘 정돈된 글로 공유할 때 더 효과적이라고 생각한다.

마지막으로 프로젝트가 끝난 후에도 '회고'라는 글쓰기 활동을 통해 스스로와 팀원들에게 소통하는 시간을 가지는 점에서도

개발 분야에서 글쓰기란, 개인성장과 동료 간의 소통을 위해 필수적인 역량이란 생각이 들었다.

 

추가적으로 개인 브랜딩과 홍보 수단으로써 글의 힘이 크다.

벨로그라는 개발자들을 위한 블로그 플랫폼도 따로 있을 정도로...

어느 개발 모임에 가도 '파워블로거'가 가지는 영향력이 크다는 것도 꽤 인상적이었다.

이런 점들을 고려할 때 꾸준한 글쓰기를 통해 미리 글쓰기 역량을 키워놓아야겠다는 생각이 들었다. 

 

하지만 이런 생각을 하고 야심차게 시작한 블로그를 방치한지 어느덧 1년이 다되었다.

무엇이든 습관이 되지 않은 것을 습관으로 만드는 데는 꾸준함이 필요한 법인데, 그게 참 혼자서 하기가 쉽지 않다.

그래서 글또를 신청하게 되었다.

뭐든 함께 하면 꾸준히 하기 쉽기에...글을 쓰지 않으면 자극해줄 누군가가 필요했다.

 

 

글또는 이런 곳이었다

개발자들이 모여 2주에 한 번씩 글을 쓰고 피드백을 받을 수 있는 커뮤니티.

예치금 10만원을 넣고 글을 한 번씩 쓰지 않을 때마다 돈이 차감된다.

영리 목적 모임은 아니기 때문에 이렇게 걷힌 금액은 기부된다고...

글의 양식은 자유! 회고를 써도 좋고 기술 정보 나누는 글을 써도 좋다고 한다.

무엇보다 좋은 점은 글또콘 등 다른 개발분야의 분들과 활발히 네트워킹 할 수 있는 곳이 있어 

지속적으로 영감을 받을 수 있는 장이 있다는 것이 매력적이다.

 

지원부터 OT까지, '개발자'들의 글쓰는 커뮤니티라 개발 관련 활동 질문이 많을 거란 예상과 달리

"너는 누구냐"는 철학적인(?) 질문이 많았다는 것이 의외였다.

그도 그럴 것이  글또는 지원서에서부터 "삶의 지도"를 그려보는 항목이 있었는데,

내 삶을 돌아보며 현재 나를 만든 과거의 일련의 사건들을 나열하고 성찰을 적는 것이었다.

보통 기술적인 질문을 묻는 것이 아니라 새로웠다. 이 시간을 통해 기술에 집중된 글을 쓰는 것도 중요하지만,

계속해서 내가 지금 어디에서 무엇을 하고 있는지를 인지하고

거시적인 방향성을 잃지 않는 것이 훨씬 중요하다는 생각을 하게 되었다.

 

 

"나는 어떤 유형의 사람인가요? 어떤 유형의 사람과 만날 때 시너지가 나나요?"

 

반년간 4회정도 진행 예정이라는 커피드백 시간을 위한 설문조사에서 역시 "내가 어떤 사람인가"에 대한 질문이 주를 이뤘다.

그 중 "타인과의 관계 속의 나" 는 어떤 사람인지,를 돌아보게 해준 사진 속 질문이 인상 깊었다.

 

좋은 질문들 덕에 나에 대한 진솔한 성찰을 가질 수 있는 시간이었다.

앞으로는 자율적인 글쓰기 시간이 되기에, 위 질문들만큼 의미 있는 주제를 떠올릴 수 있을지 모르겠다.

그래도 글또가 단순 정보 전달보다 성찰을 통한 성장에 취지를 두고 만들어진 커뮤니티라는 걸 알 수 있었고,

이러한 모임에서의 교류를 통해 더 많은 영감을 받을 수 있으리라.

 

앞으로 글또에서

그렇게 시작한 2023 상반기, 글또와 함께하면서 계획한 일들을 회고하는 시간으로 가져가고자 한다.

 2023년 상반기 우선순위는 역시: 취업 준비, 그리고 방통대 학점 관리이다. 이에 대한 회고가 주를 이루는 주제가 될 것이다.

진솔한 성찰을 통해 배운 것을 소화하고 체화하고 싶다.

 

이를 위해 스스로를 위해 몇 가지 규칙을 세워보았다.

1. '매주 토요일 오전'은 글을 읽고 쓰는 시간으로 정한다.

2. 피드백을 활용한다. 내 머릿속에서 나오는 건 거기서 거기다.

매주 팀원들에게 피드백을 달고 받으며 피드백 시간을 활용하자.

3. 글쓰기가 다가 아니다. 적극적인 커피챗 참여를 통해 사람을 통해서도 영감 얻기.

4. 담백하게 쓴다. 쓸데없는 과장, 수사 x

5. 글쓰기 전 반드시 사실 확인을 한다.

6. 문체를 통일하여 일정한 문체를 유지한다.

7. 글은 올린 후에도 계속해서 퇴고한다.

8. 링크드인과 글또 슬랙을 이용해 다른 이들의 글을 읽는 시간을 가진다.

 

얼마나 지킬 수 있을지 모르겠지만 반 년간 한 번 열심히 해보자!

 

 

 

 

 

 

FECrash 함수형 프로그래밍 스터디 1부가 끝이 났다


오늘로 <쏙쏙 들어오는 함수형 코딩>책과 함께한 1부가 끝났다. 

카카오 엔터프라이즈의 테오가 강의하고, 넥스트 유니콘 파랑이 주최한 스터디.

강의 2~30분, 팀별활동 1시간 30분, 총 2시간씩

매주 목요일 저녁 진행되었다. -> 깃헙 보러가기

 

모두가 알찬 내용을 기대하고 온 만큼 열정적으로 참여하셨다.

예상 스터디 기간은 8~10주로, 1부와2부로 나뉘어 진행된다고 한다.

1부함수형 프로그래밍의 개념 및 필요성, 기초 활용법에 대해 팀으로 예제를 풀며 익히는 식으로 진행되었다.

2부부터는 쿼리 등 함수형 프로그래밍을 위해 더 함수를 잘 분리하는 심화 방법을 배운다고 한다.

매주 리팩토링 과제가 진행되며, 마지막에는 라이브러리를 만들어 보는 것으로 마무리 된다고 한다.

 

오늘(1/5, 2023)을 기준으로 1부가 끝이 났다.

지난 5주간 무엇을 배웠는지 돌아보고자 중간회고를 남기고자 한다.

 

테오가 강의하고 파랑이 리딩하는 함수형 프로그래밍 스터디

 

 

함수형 프로그래밍은 무엇이고,
우리는 왜 배워야 하는가.


 

자바스크립트는 사실 함수형 프로그래밍 언어이다.

 

"정확히는 우리가 아는대로 멀티 패러다임 언어입니다 ... javascript를 창시한 Brendan Erich는 언어를 개발할 당시 유행하던 객체지향에 한계를 느끼고 LISP, scheme등 함수형 프로그래밍에 관심을 가지고 있었기에 함수형 프로그래밍의 형태로 언어를 만들고 싶어 했습니다. 하지만 Netscape의 그의 상사는 당시 개발자들이 제일 많이 쓰던 Java와 같은 문법으로 만들기 요구했기 때문에 결국 둘의 혼종의 형태로 세상에 나오게 되었습니다. :) 결국 javascript에는 언어의 태생부터 함수형 프로그래밍의 개념들이 녹아있고 동시에 객체지향의 가치는 다소 희석이 되어 있는 형태의 언어였습니다."

출처 - 테오가 쓴 글

 

 

글에는 나오지 않았지만 강의에서 테오는 자바스크립트ES6에 추가된

this가 없는 "화살표 함수" 등은 함수형 프로그래밍에 적합한 문법이라고 할 수 있다고 덧붙이셨다.

 

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

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

 

 

그래서
함수형 프로그래밍이
뭔데?

함수형 프로그래밍은 결국 "범위(scope)"를 기준으로 한 패러다임 중 하나이다.

 

지역 변수 사용은 나쁘다고 한다.

여러 파일에서 참조 및 변경할 수 있어 에러 발생률이 높이기 때문이다.

그래서 그 범위를 "클래스"로 한정해 사용하자, 는 것이 "객체 지향 프로그래밍",

이 범위를 더 좁혀 "함수"로 한정해 사용하자, 는 것이 "함수형 프로그래밍"이다.

 

함수로 범위를 좁힌 변수(지역 변수)를 사용하며,

이러한 함수들의 집합으로 프로그래밍을 완성하는 것,

그것이 함수형 프로그래밍이라 할 수 있다.

 

함수형 프로그래밍을 할 때 주의해야 할 점은,

이러한 전체 프로그래밍을 이루는 작은 구성원인 함수

가능한 작고, 테스트가 쉬우며, 분리와 재조립이 쉬운 형태로 만드는 것이다.

 

이러한 함수를 만드는 법

바로 함수(범위) 밖의 요소에 영향을 받지도 주지도 않는,

즉, "순수함수"로 만드는 것이다.

 

그리고 책에서는 함수를 "순수함수"로 만드는 방법에 대해 알기 쉽게 소개한다.

 

 

 

순수함수가 핵심이다

 

 

순수하지 않은 함수, 즉 외부에 영향을 받거나 주는 테스트가 어려운 함수를

"순수함수"라고 한다고 했다.

하지만 순수함수를 어떻게 만드는데? 라는 질문에는

선뜻 대답하기 쉽지 않은데 그 방법이 다양할 수 있기 때문이라 생각한다.

 

하지만 이 책에서는 한 가지 방식을 제안하는데,

바로 바뀌는 변수는 오직 인자로만 받고(명시적 입력), 전역 변수나 dom 등 다른 범위의 변수는 일정 변경하지 않는 것이다.

이 과정에서 변경하는 데이터는 복사를 이용한다.

또한 그렇게 함수내에서 변경된 자료는 return 으로 출력한다(명시적 출력).

 

보다 구체적으로 알아보자.

저자는 바꾸는 법을 전보다 구체적으로 전달하기 위해

함수를 역할에 따라 액션, 계산, 데이터로 3가지로 분류한다.

책에서 말하는 액션, 계산, 데이터란 아래와 같다:

 

 액션  - 외부에 영향을 받거나 주는 함수(즉, 비순수 함수
 계산 - 외부에 영향을 받거나 주지 않는 함수(순수 함수) + 명시적인 입출력
 데이터 - 그냥 데이터 

 

결국 좋은 함수형 프로그래밍이란

비순수함수(액션)을 최대한 없애고,

순수함수(계산)을 최대한으로 만들어

테스트와 유지보수가 쉬운 코드를 만드는 것이다.

 

또 저자는 이 과정을 보다 직관적으로 설명하기 위해

명시적 입/출력, 암묵적 입/출력의 개념을 사용한다.

 

 명시적 입력 - 인자(parameter) 

 암묵적 입력 - 인자 외의 모든 입력 (ex. 지역 변수 사용) 

 명시적 출력 - return 

 암묵적 출력 - return 외의 모든 출력 (ex. alert ) 

 

명시적 입출력은 필요하다

1. 테스트를 쉽게 할 뿐 아니라,

2. 테스트가 없는 경우에도 개발자에게 예측가성성이 높은 코드를 주기 때문이다. 

 

구체적인 방법은 간단하다:

1. 계산 코드를 찾아 빼낸다.

2. 새 함수에 암묵적 입력과 출력을 찾는다.

3. 암묵적 입력은 인자로 암묵적 출력은 리턴값으로 바꾼다.

 

예시는 아래와 같다.

 

한 쇼핑몰에서 쇼핑 카트 안의 아이템들에 따라 세금을 추가하는 아래와 같은 코드가 있다.

function update_tax_dom () {
	set_tax_dom(shopping_cart_total * 0.10);
}

이 코드를 테스트 함수를 짠다고 생각해보라. 힘들다.

이 함수는 shopping_cart_total 이라는 외부 변수(지역 변수)를 건드리는 액션(비순수함수)이기 때문이다.

위의 코드에서 테스트 코드를 짤 수 있는 순수함수는 0개다.

 

이 중에서 계산(순수함수)로 나눌 수 있는 방법은 없는지 고민해보면 아래와 같다.

// 액션 - 외부데이터(dom 등)을 변경한다.
function update_tax_dom() {
	set_tax_dom(calc_tax(shopping_cart_total));
}

//계산 - 외부데이터를 일절 건드리지 않는다.(순수함수)
function calc_tax (amount) {
	return amount * 0.10;
}

이렇게 분리하면 calc_tax 는 순수함수이므로 테스트 코드를 짜기 쉽다.

이제 테스트 코드를 짤 수 있는 순수함수가 1개이다.

(순수함수를 포함하고 있지만 dom도 건드리는 비순수함수도 포함하고 있는 update_tax_dom 은 비순수함수이다.

비순수 함수를 하나라도 포함하는 함수는 비순수함수이다.)

 

이처럼 기존 테스트가 어려운(지역변수를 건드리고, dom을 건드리는) 함수를 테스트가 쉬운 함수(인자로 받아온 것만 변경, 명확한 결과값 return) 로 바꾸는 것. 이것이 1부 동안 리팩토링을 하며 배운 함수형 프로그래밍의 기초이다.

 

다시 한 번 말하지만, 액션(비순수함수)의 사용은 불가피하다.

프로그래밍의 목적은 데이터를 가공해 새로운 데이터를 만들어내는 데에 있기 때문이다.

그러나 유지보수가 쉽고 테스트가 쉬운 코드를 위해서는

이렇게 외부 데이터를 건드리는 함수인 액션의 사용을 '최소한'으로 줄이고, 

가능한 순수함수들로 채워넣는 데에 그 목적이 있다. 

 

 

데이터의 불변성

 

카피-온-라이트 - 데이터 변경 시 반드시 복사한다는 원칙

 

데이터를 바꾸지 않고 불러오기만 하는 것을 읽기(read)라고 한다.

데이터를 변경 하는 것을 쓰기(write)이라고 한다. 

카피-온-라이트(copy-on-write)는 이러한 데이터 변경 시 반드시 복사를 하여,

원본은 유지하고 복사본을 변경하라는 원칙이다.

이렇게 함으로써 원본 데이터는 불변성을 잃지 않을 수 있다.

 

데이터의 불변성을 지키는 것은 중요하다고 모두가 말한다.

불변성을 지키지 않는다면 사용할 데이터가 어디서 어떻게 바뀌어가는지 흐름을 쫓아가기 어렵고,

이는 곧 예기치 못한 side effects나 버그로 이어지게 만들기 때문이다.

 

이러한 불변성을 지켜주는 카피-온-라이트 방식을 사용하는 법은 간단하다:

 

1. 복사본 만들기

2. 복사본 변경하기(write)

3. 복사본 리턴하기

 

예시와 함께 보자.

아래는 메일링 리스트에 연락처를 추가하는 코드이며,

이메일 주소를 전역변수인 리스트에 추가한다.

const mailing_list = [];

const add_contact = (email) => {
	mailing_list.push(email);
}

const submit_form_handler = (event) => {
	const form = event.target;
    const email = form.elements["email"].value;
    add_contact(email);
}

전역 변수(외부 데이터)를 변경하는 것은

데이터 불변성에 어긋난다.

카피-온-라이트 방식을 이용해 불변성을 지켜보자.

const mailing_list = [];

const add_contact = (email) => {
	const list_copy = [...mailing_list]; // 1. 복사한다.
	list_copy.push(email); // 2. 복사본을 원하는만큼 변경한다.
    return list_copy;		// 3. 복사본을 리턴한다.
}

const submit_form_handler = (event) => {
	const form = event.target;
    const email = form.elements["email"].value;
    add_contact(email);
}

이제 전역 변수 mailing_list 의 원본은 변경되지 않는다.

 

+

"복사를 하면 비용이 많이 들지 않나요?" 라는 반박에 저자는 이렇게 대처한다. 

카피-온-라이트에서 사용하는 복사는 얕은 복사(shallow copy)이기 때문에 생각보다 비용이 많이 들지 않는다고.

이는 복사를 하지 않아 데이터의 불변성을 지키지 못할 경우 발생가능한 오류를 수정하는 비용보다 적을 것이라고 말이다.

하지만 데이터를 변경할 지도 모르는 믿을 수 없는 외부 함수(ex.남이 짠 레거시 코드)를 사용해야만 한다면

조금 비싸더라도 깊은 복사(deep copy)를 사용할 것을 저자는 권한다.

* 얕은 복사 vs 깊은 복사

 

솔직히 이 부분에 대해서는 잘 이해가 안간다. 

신뢰할 수 없는 코드가 참조를 가지고 있으면 안되기 때문에 참조를 쓰지 않고 전체를 전부 복사하는 깊은 복사를 쓰는 것이라고 한다. 

일단은

1. 자스에서는 lodash 를 이용해 깊은 복사 사용하면 편하며,

2. 신뢰할 수 없는 코드로 들어가는/오는 데이터는 나갈때,들어올 때 모두 깊은 복사로 감싸주어야 한다.

만 그렇구나~ 하고 받아들이기로 했다.

 

 

계층의 분리

 

계층이란 결국 추상화의 정도이다.

목적이나 추상의 정도가 같은 함수들을 같은 계층이라고 본다.

 

즉,

같은 계층 = 

1) 같은 목적

2) 같은 구체화 수준

라고 이해했다.

 

계층을 나누는 데에 정답은 없다.

각자의 기준과 이유가 분명하면 된다.

 

계층을 나누는 감각을 기르다보면

코드를 설계하는 감각을 기를 수 있다고 한다.

 

아래는 우리팀이 진행한 계층의 분리이다.

이를 바탕으로 최종 리팩토링을 진행하였다.

 

 

6팀

 

 

 

 

회고 글을 쓰면서,

1. 헷갈리던 함수형 프로그래밍의 개념에 대해 제대로 이해할 수 있었다.

2. 내가 무엇을 모르는지(깊은 복사가 필요한 이유)를 확실히 할 수 있었다. 

3. 계층형 설계 개념을 대충 읽었는데 다시 한 번 복습할 수 있었다. 

4. 못다한 리팩토링을 회고를 쓰면서 마무리하였다.

5. 중간에 정리하는 시간을 가지며, 복습 및 정비하는 시간을 가질 수 있었다.

 

'Extracurricular > 활동' 카테고리의 다른 글

[Next.js] 번역 컨트리뷰터  (0) 2024.03.25
새싹톤 우수상 회고  (0) 2024.03.25

+ Recent posts