리액트, AHA Programming?

창건·2022년 12월 24일
1

얼마전 회사 동료로 부터 다음과 같은 코드리뷰를 받았습니다.

form의 필드들이 모두 같고, api 요청하는 부분만 다르니, 두 컴포넌트를 하나로 추상화하는 게 어떨까요?

회사에서 짠 코드를 가져올 순 없으니, 어떤 form이었는지 예시를 간단히 만들어 보았습니다.
CreateUserForm과 EditUserForm입니다.

추상화 해보기

CreateUserForm과 EditUserForm는 같은 input 필드들을 가지고 있고, 같은 button을 가지고 있습니다.

다른점은 제목(Create User / Edit User), submitHandler(post / patch)입니다. 그리고 EditUserForm은 prop으로 user를 전달받아 form의 input value들을 초기화 시켜주겠죠.

공통되는 부분이 너무나 많기 때문에, 충분히 추상화할 수 있을 것 같습니다. 아니, 하고 싶을 겁니다. 개발자라면 누구나 DRY원칙(Don't Repeat YourSelf)을 알테니까요.

추상화하는 과정은 간단합니다. user를 prop으로 전달받으면 EditUserForm을, 전달받지 못하면 CreateUserForm을 렌더링하면 됩니다. submitHandler도 같이 전달받구요.

import { useState } from "react";

export default function CreatOrEditUserForm({ user, handleSubmit }) {
  const [userName, setUserName] = useState(user?.name);
  const [userAge, setUserAge] = useState(user?.age);

  const handleCancel = () => {
    //...
    console.log("canel");
  };

  return (
    <form onSubmit={handleSubmit}>
      <h2>{user ? 'Edit User' : 'Creat User'}</h2>
      <div>
        <label>Name:</label>
        <input
          type="text"
          name="name"
          value={userName}
          onChange={(e) => setUserName(e.target.value)}
        />
      </div>
      <div>
        <label>Age:</label>
        <input
          type="number"
          name="age"
          value={userAge}
          onChange={(e) => setUserAge(e.target.value)}
        />
      </div>
      <button type="submit">Submit</button>
      <button type="button" onClick={handleCancel}>
        cancel
      </button>
    </form>
  );
}

이제 하나의 컴포넌트로 두개의 케이스를 모두 커버할 수 있게 됬습니다. 그런데 이렇게 코드를 작성하고 나니 뭔가 찝찝합니다.

추상화로 인한 문제점

우리가 추상화하여 만든 CreatOrEditUserForm은 두 가지 문제점이 있습니다.

첫번째, 변경에 취약합니다.

  • 어느날 기획에 변경이 생겨, user를 edit할 때 name은 수정할 수 없게되었다고 해보겠습니다. 그럼 우리는 input에 disabled 속성을 추가해야 합니다. 그냥 추가하는 게 아니라, editUserForm인 경우에만 추가해야 겠죠.

  • 어느날 기획자가 user를 Create할 때 email정보를 같이 받아야 한다고 전해옵니다. 그런데 editUserForm에는 email 필드가 보이지 않아야 된다고 하네요. 다시 한번 분기 처리를 하고, 필드를 추가해봅니다..

  • 몇 달이 지난 후, 엉망이 된 코드를 보고 한숨을 쉬게 됩니다. 😂

모든 소프트웨어는 변화하기 마련입니다. 우리가 회사에서 만드는 웹앱도, 사이드 프로젝트도 모두 변화하게 됩니다. 리팩토링을 거치거나, 기능을 추가하거나 어떤 이유로든 코드가 수정되게 됩니다. 물론 저는 토이 프로젝트를 수정한 적이 없습니다..

두번째, 추상화를 했지만, 유스케이스(usecase)가 너무 적습니다.

CreateOrEditUserForm은 이름에서 알 수 있듯이, CreateUserForm 혹은 EditUserForm이라는 단 두가지 유스케이스밖에 없습니다.

추상화를 한다는 것은 결국 재사용성을 염두해 둔 것인데, 딱 두번의 재사용을 위해서 앞서 말한 단점을 감수할 필요는 없을 것입니다.
UI 라이브러리에서 제공하는 컴포넌트들도 결국 추상화된 것인데, 이들 컴포넌트는 수백가지의 케이스가 있을 것입니다. 물론 우리 웹에서 수백가지의 케이스를 지원하는 컴포넌트를 만드는 것은 불가능하겠지만, 적어도 '여러가지'의 케이스를 추상화할때 추상화가 의미가 있다고 생각합니다.

AHA 원칙을 소개합니다.

Avoid Hasty Abastraction

AHA 원칙은 개발자 Cher Scarlett가 DRY원칙에 '대응'하여 주창한 프로그래밍 원칙입니다.

직역하자면, '조급한 추상화를 피해라!'입니다. 저는 이것을 '코드 중복을 너무 두려워 하지 마라'는 뜻으로 받아들이고 있습니다.

코드 중복이 항상 나쁜 것은 아닙니다. 오히려, 우리가 봤던 예시 처럼 추상화된 컴포넌트보다 변화에는 좀 더 유연하게 대처할 수 있습니다. 반면, 추상화된 컴포넌트는 변화에 대응하기가 까다롭죠

특히, 프론트엔드처럼 코드 수정이 빈번하게 일어나는 분야라면 더더욱 그렇겠죠. 제가 이글을 작성하기 위해 레프런스 삼은 글들의 저자는 Cher Sacrlett이나, Kent C. Dodds인데, 모두 공교롭게도 프론트엔드 엔지니어들입니다.

그래서 나온 애매한 결론

결론은 다소 애매합니다. 결국 개발자가 알아서 DRY원칙을 적용할지, AHA 원칙을 적용할지 결정해야겠죠.

다만, 이제 더 이상 중복 된 코드를 보고 "어 여기 수정해야 되!"라는 강박을 가질 필요가 없습니다. 오히려, 중복 된 코드를 그대로 둘지, 추상화를 할지 옆의 동료들과 충분히 논의를 해봐야 겠죠.

profile
피곤한만큼 성장할 수 있으면

0개의 댓글