우리 제품 체험 페이지, 다음 주에 데모 가능할까? (React Joyride 사용기 + 페어프로그래밍 경험기)

우리 제품 체험 페이지, 다음 주에 데모 가능할까? (React Joyride 사용기 + 페어프로그래밍 경험기)

안녕하세요, 디어코퍼레이션 프론트엔드 개발자 유영인입니다.
저는 지난 1월 디어에 합류한 뒤, 약 3개월 동안 물류팀에서 화물 주선사를 위한 정산 SaaS 캐리를 개발했습니다.

이 글에서는 최근에 사용자 튜토리얼을 구현하면서 겪었던 문제와 그 문제를 해결한 과정을 소개해 드리려고 합니다. 특히 빠른 구현을 위해 React Joyride 라이브러리를 도입하고 적용한 과정을 모두 정리해 볼 예정이니 비슷한 기능을 구현하려는 분들에게 큰 도움이 되면 좋겠습니다.

추가로 이번 작업을 하면서 처음으로 페어 프로그래밍을 제대로 경험했는데, 이 경험 자체가 디어의 개발 문화를 잘 보여주는 사례인 것 같아서 함께 공유하려고 합니다.

TL;DR

  • 물류팀은 제품의 가치를 빠르게 전달하기 위해 프로덕트 투어(Product Tour)를 만들기로 결정했습니다.
  • React Joyride 라이브러리와 전역 상태 관리 도구 Zustand를 활용해서 일주일 만에 프로덕트 투어를 구현했습니다.
  • 유지보수성을 높이기 위해 제품과 투어의 의존성을 완전히 분리했습니다.
  • 페어 프로그래밍과 뽀모도로 기법을 활용해서 생산성을 극단적으로 끌어올렸습니다.

프로덕트 투어 만들기

“캐리 진짜 좋은 거 같은데, 이걸 고객들에게 어떻게 알려주지?”

3월 초까지 물류팀은 캐리를 2~3개의 고객사에만 제공하면서 Do things that don't scale을 실행하고 있었습니다. 그러던 중 이제는 제품을 더 많은 고객사에 제공해서 이 제품이 'scalable'한지 확인할 때가 되었다는 의견이 나왔습니다.

다음 단계를 진행하기로 결정하면서 발견한 첫 번째 문제는 고객이 제품의 가치를 확인할 때까지 거쳐야 하는 단계가 너무 많다는 것이었습니다. 캐리가 B2B SaaS 제품인 만큼 아웃바운드 영업을 통해 고객을 만날 필요가 있는 것도 맞지만, 당시에는 영업 사원 없이 고객을 만날 방법이 전혀 없는 상황이었습니다. 즉, 캐리에는 말로 하는 영업 없이도 제품의 가치를 전달할 방법이 필요했습니다.

팀이 가진 개발 역량에 대한 확신이 있었기 때문에 저희는 이 문제를 제품 차원에서 풀어보려고 했고, 논의 끝에 고객이 스스로 제품을 체험해 볼 수 있는 사용자 튜토리얼을 만들기로 결정했습니다.

리서치하던 중에 해외에서는 프로덕트 투어(Product Tour)라는 명칭이 훨씬 일반적으로 사용된다는 것을 알게 되어서, 저희는 우선 용어부터 정리했습니다.

이제부터 글의 나머지 부분에서는 사용자가 우리 제품의 가치를 빠르게 느낄 수 있도록 양방향 상호작용을 제공하는 가짜 프로덕트라는 의미로 투어라는 용어를 사용하겠습니다.

ReactJoyride 사용하기

라이브러리 선택

저희는 빠른 투어 구현을 위해 프로덕트 투어 라이브러리를 사용하기로 했습니다. React 진영의 프로덕트 투어 라이브러리는 크게 reactourreact-joyride가 있습니다. reactour는 사용법이 간단한 대신 커스터마이즈를 하기 힘들었습니다. 따라서 비교적 복잡한 프로덕트 투어를 만들어야 했던 저희는 react-joyride를 사용하기로 결정했습니다.

controlled 예제

react-joyride는 다양한 예시 코드를 제공하는데, 그중에 눈여겨봐야 할 것은 ‘controlled’ 예제입니다. 여기서 controlled라는 의미는 프로덕트 투어를 진행하는 단계를 react-joyride 내부가 아닌 외부에서 처리하고 react-joyride는 이 외부의 상태에 따라 통제(controlled)되는 형태를 말합니다.

모달 내부의 컴포넌트 같이 특정 상황에서 mount 되는 컴포넌트를 프로덕트 투어에 포함하려면 react-joyride 외부에서 프로덕트 투어의 진행상태를 관리해야 합니다.

zustand store 생성 hook을 생성하는 함수

캐리는 zustand를 전역 상태관리 라이브러리로 사용하고 있습니다. 프로덕트 투어 코드는 제품 여기저기에 작성할 수밖에 없기 때문에 프로덕트 투어의 상태는 전역 상태로 관리하는 게 좋습니다. 여러 프로덕트 투어에서 공통적으로 사용하는 상태관리 코드를 재사용할 수 있도록 작성했습니다.

import { create } from 'zustand';

export type TourData<T> = {
  displayTour: boolean;

  data?: T;
};

export type TourState<T> = TourData<T> & {
  currentStep: number;

  showTour: () => void;
  hideTour: () => void;
  resetTour: () => void;
  nextStep: () => void;
  setStep: (step: number) => void;
  addData: (data: T) => void;
};

/**
 * 각각의 tour를 만들 때 재사용할 수 있는 useTourStore hook을 생성하는 함수.
 * @param initialData tour의 초기 데이터.
 */
export const returnUseTourStore = <T>(initialData: TourData<T>) =>
  create<TourState<T>>()((set) => {
    return {
      ...initialData,
      currentStep: 0,

      showTour: () => set({ displayTour: true, currentStep: 0 }),
      hideTour: () => set({ displayTour: false, currentStep: 0 }),
      resetTour: () => set({ currentStep: 0 }),
      nextStep: () =>
        set((state) => {
          if (!state.displayTour) {
            return state;
          }

          return { currentStep: state.currentStep + 1 };
        }),
      setStep: (step) => set({ currentStep: step }),
      addData: (data) =>
        set((state) => ({
          ...state,
          data: { ...state.data, ...data },
        })),
    };
  });

일반적으로 zustand를 사용할 때는 초기 데이터를 hook 내부에 작성해야 합니다. store의 초기 데이터를 동적으로 받아서 store를 생성할 수 없기 때문에, store를 생성하는 함수 자체를 반환하는 고계함수를 만들어 store 생성 함수가 초기 데이터를 클로저로 잡고 있도록 만들었습니다. (returnUseTourStore)

이 고계함수를 사용해서 간단한 Todo 앱을 위한 투어를 만들어 보겠습니다. 할 일 목록에 할 일을 추가하는 과정을 안내하는 투어입니다.

export type AddTodoTourData = {  
   addTodoButton?: React.RefObject<HTMLDivElement>;  
   todoInput?: React.RefObject<HTMLInputElement>;  
   submitButton?: React.RefObject<HTMLButtonElement>;  
   todoList?: React.RefObject<HTMLDivElement>;  
   updateTodoData?: (data: Todo[]) => void;  
};

export const useAddTodoTourStore = returnUseTourStore<AddTodoTourData>({  
   displayTour: false,  
});

타입 파라미터에 AddTodoTourData 타입을 넣어줬습니다. react-joyride는 프로덕트 투어용 tooltip이 나타날 곳을 RefObject로 지정할 수 있습니다. store를 생성할 때는 아직 RefObject가 지정되지 않은 상태고, 프로덕트 투어를 진행하면서 RefObject를 넣어준 뒤 tooltip이 올라오도록 코드를 작성하면 됩니다.

updateTodoData는 제품의 상태를 업데이트하는 함수입니다. 프로덕트 투어를 진행할 때는 따로 만든 가짜 데이터가 업데이트되어야 하므로 가짜 데이터가 UI 컴포넌트에 침투하지 않도록 업데이트하는 함수를 투어용 스토어에 넣고 투어를 진행하면서 컴포넌트 바깥에서 가짜 데이터를 넣어줍니다(=의존성 주입).

const tourStore = useAddTodoTourStore();  

const [todosForTour, setTodosForTour] = useState<Todo[]>([]);

useEffect(() => {  
   tourStore.addData({ updateTodoData: setTodosForTour });  
   
   // tourStore를 의존성에 추가하면 무한로딩이 발생한다.  
   // eslint-disable-next-line react-hooks/exhaustive-deps
},[]);
   

위의 예시와 같이 todoStore 를 사용하는 곳에서 useEffect hook을 이용해서 의존성을 연결합니다. 이때 의존성에 tourStore 를 추가하면 무한로딩이 발생하기 때문에, 주석에 경고 문구를 적고 린트 경고메시지를 표시하지 않도록 설정했습니다.

Step 객체

Step객체에는 RefObejct를 넣어줘야 합니다. react-joyride는 RefObject를 강조하는 툴팁을 렌더링합니다.

Step 배열 데이터를 생성하는 함수는 아래와 같습니다.

import React from 'react';  
import { Step } from 'react-joyride';

export const createAddTodoTourSteps = (  
   data: Pick<  
      Required<AddTodoTourData>,  
      'addTodoButton' | 'todoInput' | 'submitButton' | 'todoList'  
   > &  
      Pick<TourState<unknown>, 'nextStep'>,  
): Step[] => [  
   {  
      target: data.addTodoButton.current!,  
      content: '할 일을 추가할 거에요.\\n버튼을 눌러주세요.',  
      disableBeacon: true,  
      disableOverlayClose: true,  
      hideCloseButton: true,  
      hideFooter: true,  
      spotlightClicks: true,  
      spotlightPadding: 2,  
      disableCloseOnEsc: true,  
   },  
   {  
      target: data.todoInput.current!,  
      content: '할 일을 입력해주세요.',  
      disableBeacon: true,  
      disableOverlayClose: true,  
      hideCloseButton: true,  
      hideFooter: true,  
      spotlightClicks: true,  
      spotlightPadding: 2,  
      disableCloseOnEsc: true,  
      hideBackButton: true,  
   },  
   {  
      target: data.submitButton.current!,  
      content: '버튼을 눌러 작성한 할 일을 목록에 추가하세요.',  
      disableBeacon: true,  
      disableOverlayClose: true,  
      hideCloseButton: true,  
      hideFooter: true,  
      spotlightClicks: true,  
      spotlightPadding: 2,  
      disableCloseOnEsc: true,  
   },  
   {  
      target: data.todoList.current!,  
      content: '할 일이 추가 되었어요! 🎉',  
      disableBeacon: true,  
      disableOverlayClose: true,  
      hideCloseButton: true,  
      hideFooter: true,  
      spotlightClicks: false,  
      spotlightPadding: 2,  
      disableCloseOnEsc: true,  
   },  
];

커스텀 Tooltip

react-joyride에서 기본으로 제공하는 tooltip은 padding이 너무 크고 padding 수치를 커스터마이즈할 수 없어서 tooltip용 컴포넌트를 작성했습니다. https://github.com/gilbarbara/react-joyride-demo/blob/main/src/CustomComponents/index.tsx#L110 의 Tooltip 컴포넌트를 참고했습니다.

import React from 'react';  
import { TooltipRenderProps } from 'react-joyride';  
  
const JoyrideTooltip = (props: TooltipRenderProps): JSX.Element => {  
   const {  
      backProps,  
      continuous,  
      index,  
      size,  
      isLastStep,  
      primaryProps,  
      skipProps,  
      step,  
      tooltipProps,  
   } = props;  
  
   return (  
      <div className={'rounded bg-gray-50 py-2 px-4'} {...tooltipProps}>  
         {step.title && (  
            <div className={'cursor-default select-none whitespace-pre'}>{step.title}</div>  
         )}  
         {step.content && (  
            <div className={'cursor-default select-none whitespace-pre'}>{step.content}</div>  
         )}  
         {step.hideFooter || (  
            <div className={'mt-1 flex justify-between'}>  
               <div>                  
               {!isLastStep && step.showSkipButton && (  
                     <button {...skipProps} className={'border-0 bg-transparent'}>  
                        건너뛰기  
                     </button>  
                  )}  
               </div>  
               <div>                  
               {index > 0 && !step.hideBackButton && (  
                     <button {...backProps} className={'border-0 bg-transparent'}>  
                        뒤로  
                     </button>  
                  )}  
                  <button {...primaryProps}>  
                     {continuous  
                        ? step.showProgress  
                           ? `다음 ${index + 1}/${size}`  
                           : '다음'  
                        : '닫기'}  
                  </button>  
               </div>            
            </div>         
            )}  
      </div>  
   );  
};  
  
export default JoyrideTooltip;

ReactJoyride 컴포넌트 사용하기

ReactJoyride 컴포넌트는 프로덕트 투어를 시작하는 컴포넌트에서 사용하면 됩니다. Next.js와 함께 사용할 경우에는 서버사이드렌더링하지 않기 위해 ReactJoyride를 dynamic import 해야 합니다. 참고 링크

const ReactJoyride = dynamic(() => import('react-joyride'), { ssr: false });

const TodoListPage: NextPage = () => {   
   const [todos, setTodos] = useState<Todo[]>([]);  
  
   const [isAdding, setIsAdding] = useState(false);  
  
   const goCreate = () => {  
      setIsAdding(true);  
      setTimeout(() => {  
         tourStore.nextStep();  
      }, 0);  
   };  
  
   const tourStore = useAddTodoTourStore();  
  
   const tourData: Parameters<typeof createAddTodoTourSteps>[0] = {  
      addTodoButton: useRef<HTMLDivElement>(null),  
      todoInput: useRef<HTMLInputElement>(null),  
      submitButton: useRef<HTMLButtonElement>(null),  
      todoList: useRef<HTMLDivElement>(null),  
      nextStep: tourStore.nextStep,  
   };  
  
   useEffect(() => {  
      tourStore.addData(tourData);  
      tourStore.addData({ updateTodoData: setTodos });  
  
      setTimeout(() => {  
         tourStore.showTour();  
      }, 1000);  
      // tourStore를 의존성에 추가하면 무한로딩이 발생한다.  
      // eslint-disable-next-line react-hooks/exhaustive-deps   
   }, [tourStore.addData]);  
  
   return (  
      <>  
         <ReactJoyride            
            continuous={true}  
            run={tourStore.displayTour}  
            stepIndex={tourStore.currentStep}  
            steps={createAddTodoTourSteps(tourData)}  
            tooltipComponent={JoyrideTooltip}  
         />  
         <PageTitle title={'Todo-list'} />  
         <TodoList            
            todos={todos}  
            goCreate={goCreate}  
            isAdding={isAdding}  
            setIsAdding={setIsAdding}  
         />
      </>   
    );  
};  
  
export default TodoListPage;

TodoList 내부에서는 강조하려는 요소를 ref로 지정해 줍니다.


<>
	<div className={'flex w-full flex-col'} 
	ref={tourStore.data?.todoList}>  
	   {todoList}  
	</div>  
	<div className={'box-border flex'} ref={tourStore.data?.addTodoButton}>  
	   <CircleButton name={'+'} onClick={goCreate} />  
	</div>  
	{isAdding && (  
	   <div className={'flex h-3 gap-2 pt-4'}>  
	      <input         
          	 ref={tourStore.data?.todoInput}  
	         name={'title'}  
	         type={'text'}  
	         className="input input-bordered input-sm"  
	         value={data?.title}  
	         onChange={(e) => setData(data && { ...data, title: e.target.value })}  
	         maxLength={20}  
	      />  
	      <button         
			ref={tourStore.data?.submitButton}  
	         className={'btn btn-primary btn-sm'}  
	         onClick={() => {  
	            tourStore.data?.updateTodoData?.([data]);  
	            tourStore.nextStep();  
	            setIsAdding?.(false);  
	         }}  
	         disabled={confirmDisabled}> 	 
	         확인  
	      </button>  
	   </div>)}
</>

완성된 투어는 아래와 같이 동작합니다.

0:00
/0:07

투어와 제품의 의존성 분리하기

투어 작업을 시작하면서 가지고 있던 계획은 기존 제품의 UI 컴포넌트를 그대로 활용하는 것이었습니다. 즉, 기존 제품 위에 투어를 덮어씌우는 방식으로 구현한 것입니다. 이런 방식을 선택한 근거는 크게 두 가지였습니다.

  1. 코드가 조금 복잡해질 수는 있지만 빠르게 구현하기 위해서는 어쩔 수 없다.
  2. react query를 사용하는 hook 내부에서만 분기하면 UI 컴포넌트에 영향을 주지 않으니까, 복잡도를 어느 정도 선에서 관리할 수 있을 것이다.

아래와 같은 3개의 레이어로 투어 관련 코드를 관리하기로 했습니다.

1번 레이어에 해당하는 것이 returnUseTourStore 이고, 2번 레이어의 예시가 useAddTodoTourStore 입니다. 이렇게 하면 UI의 분기 처리를 최소화하고, 2번 레이어의 hook을 호출하는 곳을 추적해서 해당 투어와 관련된 모든 코드를 찾을 수 있기 때문에 복잡도를 일정 수준 이하로 관리할 수 있을 것으로 생각했습니다.

그런데 작업을 진행하다보니 점점 문제점이 발견되기 시작했습니다.

우선, 제품의 핵심 가치를 보다 빠르게 전달하기 위해 투어에서는 핵심 유저 플로우에 불필요한 요소들을 보이지 않게 처리하고 싶어졌습니다. 즉, 투어인지 아닌지에 따라 UI를 분기 처리하는 일이 예상보다 훨씬 빈번하게 발생하게 된 것입니다. 그러다보니 점점 UI 코드의 복잡도가 올라가고 가독성이 떨어졌습니다.

더욱 치명적이었던 문제는 작업을 진행할수록 제품과 투어가 지나치게 결합하고 있다는 것이었습니다. 이것은 결국 앞으로 제품을 개선할 때마다 투어에 문제가 생기지 않는지 함께 확인해야 한다는 것을 의미했습니다.

이런 문제점들을 발견한 후에 함께 모여서 각 방식의 장단점을 정리했고, 결국 투어를 기존 제품에서 완전히 분리하기로 결정했습니다.

이미 투어의 구현 자체는 어느 정도 완료된 상태였기 때문에, 기존 동작을 유지하면서 점진적으로 분리를 진행했습니다. 분리한 과정은 다음과 같습니다.

  1. src 디렉토리 내부에 tour 디렉토리를 만듭니다.
  2. 투어 진행에 필요한 컴포넌트를 tour 디렉토리에 복사해서 집어넣습니다. 이때 투어용으로 만든 컴포넌트의 이름에는 Tour prefix를 넣습니다. (e.g. CompareTableContainer -> TourCompareTableContainer)
  3. 기존 컴포넌트에서 if-else로 분기처리한 부분을 전부 삭제합니다.
  4. 투어용 컴포넌트에서도 if-else로 분기처리한 부분을 제거하고, 투어에 필요한 핵심 요소만 남깁니다.
  5. 2번~4번 과정을 투어 진행에 필요한 모든 컴포넌트에 반복합니다.

작업이 완료된 이후의 디렉토리 구조는 이런 모습입니다.


src
├── common
│   ├── api
│   ├── components
│   ├── hooks
│   └── ...
├── settlement
│   ├── components
│   ├── containers
│   └── ...
├── ...
│ 
└── tour
    ├── common
    │   └── components
    └── settlement
        ├── components
        └── containers
        

이렇게 투어를 제품에서 완전히 분리하고 모듈화함으로써, 코드가 훨씬 깔끔해지고 가독성이 올라갔습니다. 또 제품을 개선할 때 투어에 영향을 줄지도 모른다는 걱정을 완전히 해소할 수 있었습니다.
앞으로 제품은 투어보다 훨씬 자주, 그리고 더 많이 변경될 것이기 때문에 이 작업의 의미는 시간이 지날수록 점점 더 커질 것으로 생각합니다.

페어 프로그래밍으로 생산성 극대화하기

페어 프로그래밍을 선택한 이유

저희 팀은 지금까지 소개한 프로덕트 투어 개발을 처음부터 페어 프로그래밍으로 진행했습니다.

이런 선택을 한 데에는 크게 3가지 이유가 있었습니다.

1) 한정된 시간

투어를 만들기로 결정하고 빠르게 프로토타입을 만들어서 시연하기까지 저희에게 주어진 시간은 딱 1주일이었습니다. 저희 팀에는 프론트엔드 개발 가능 인원이 개발 리드를 포함해서 3명 있었는데, 이 중에 프로덕트 투어를 개발해 본 경험이 있는 사람은 없었습니다. 따라서 이 3명이 모든 역량을 집중해서 일주일 안에 어떻게든 동작하는 투어를 개발해야 하는 상황이었습니다.

2) 분업하기 어려운 투어 작업의 특성

프로덕트 투어 관련 코드는 본질적으로 제품 여러 곳에 흩뿌려질 수밖에 없었습니다. 최악의 상황을 피하고자 작업을 시작하기 전에 일관된 방식으로 작업하기 위한 문서를 작성했지만, 여전히 나뉘어 작업한 부분이 각자만 알아보는 피곤한 코드가 될 위험이 커 보였습니다. 이런 이유 때문에 작업량은 많은데 그걸 나눠서 처리하기는 어려운 상황이었습니다.

3) 페어 프로그래밍을 적극 권장하는 디어의 개발 문화

CTO 광일이형의 인터뷰에서 확인할 수 있듯이 디어는 페어 프로그래밍을 적극 권장하고 있습니다. 지적 겸손함과 신뢰의 문화가 밑바탕에 있기 때문에, 페어 프로그래밍으로 더욱 큰 성과를 낼 수 있는 환경이라고 자신 있게 말할 수 있습니다. 물류팀의 경우에는 여건상 모빌리티 본부에 비해 페어 프로그래밍을 자주 진행하지 못하고 있었는데, 이번 작업이야말로 페어 프로그래밍을 진행하기 딱 적합하겠다는 판단을 내렸습니다.

어떻게 작업했는지

작업을 시작하면서 개발 리드 명재형이 시간 관리 기법인 뽀모도로를 활용하자는 제안했습니다.

뽀모도로는 높은 집중력을 유지하기 위한 시간 관리 기법입니다. 기본 골자는 25분 동안 집중해서 작업하고 5분 동안 쉬는 것을 반복하는 것입니다. 저희는 뽀모도로 타이머를 켜두고, 1뽀모도로(25+5) 마다 역할을 번갈아 가면서 페어 프로그래밍을 진행했습니다.

라운지에 있는 커다란 모니터에 코드를 작성하는 드라이버의 컴퓨터를 연결하고, 나머지 사람들은 같이 화면을 보면서 네비게이터 역할을 수행했습니다. 라운지에 있다 보니 다른 팀원들도 오가며 구경했는데, 특히 개발에 관심이 많은 CEO 동은이형은 한동안 앉아서 구경하다 가기도 했습니다. 정말 재밌고 특별한 경험이었습니다.

결과

이렇게 집중도 높게 작업한 끝에, 저희는 약속했던 일주일 만에 투어를 만들어서 시연하는 데 성공했습니다. 빠르게 프로토타입을 만드는 데 성공한 덕분에, 곧바로 사용자 피드백을 받고 제품을 개선할 시간을 확보할 수 있었습니다.

최종 결과물인 제품 체험 페이지는 https://www.carri.to/ 에서 확인할 수 있습니다.

후기

저는 팀 내에서 개발 경험이 가장 부족해서, 페어를 요청하는 것이 다른 개발자들의 리소스를 빼앗는 것이 아닌가 하는 두려움을 무의식중에 가지고 있었던 것 같습니다. 그런데 이번 기회에 직접 경험해 보니 페어 프로그래밍이 개인이 성장하기에 좋은 방법일 뿐 아니라, 단기간에 팀 전체의 생산성을 끌어올리는 데에도 아주 효과적인 방법이라는 것을 체감할 수 있었습니다.

서로를 완전히 신뢰하는 디어의 문화도 페어 프로그래밍이 원활히 진행되는 데 중요한 역할을 한 것 같습니다. 작업 중에 제 의견을 적극적으로 내는 것이 전혀 두렵지 않았고, 팀원들의 의견을 듣는 것도 실시간으로 배움이 쌓이는 기분이라서 즐거웠습니다.

물론 혼자 작업할 때보다 일이 쉽게 느껴진 것은 아니었습니다. 오히려 계속 대화하면서 코드를 작성하다 보니 매우 빠르게 체력적, 정신적 에너지가 소모되는 것을 느꼈습니다. 하지만 그만큼 제 생산성이 그 어느 때보다 높다는 것을 작업하는 내내 실감할 수 있었습니다. 하루 동안 목표한 만큼의 작업을 쳐낸 뒤에는 온몸이 녹초가 된 기분이 들었지만, 그렇게 몰입하면서 시간을 보낸 것 자체가 뿌듯하고 재밌었습니다.

'은 총알은 없다'는 말처럼 저희 팀이 적용한 방법이 어느 팀에나 똑같이 적용될 수 있는 방법론이라고 생각하지는 않습니다. 그래도 팀의 성장과 생산성 향상을 위해 충분히 시도해 볼 만하다는 생각이 듭니다. 제 경우에는 확실히 이번 경험을 통해 좀 더 자주 페어 프로그래밍을 요청해도 괜찮겠다는 자신감을 얻었습니다.

여러분의 팀 내에서도 주저 없이 페어 프로그래밍을 시도해 보시기 바랍니다.

감사합니다.

-->