프로젝트/인공지능 동아리, AID

[팀프로젝트] AID 홈페이지 리뉴얼 - (2) 코드 가독성을 높이기 위한 전략

KimCookieYa 2023. 12. 28. 02:21

동아리 홈페이지 프로젝트의 코드 가독성을 높이기 위한 전략

 

이번에는 프로젝트의 코드 가독성을 높이기 위해 몇 가지 전략을 시도해보았다. "동아리" 홈페이지라는 특성 상, 이 프로젝트는 동아리가 살아있는 동안은 계속 존재하고 유지보수되어야 한다. 이 말은 "내가 이 프로젝트에서 나가고 다른 개발자가 맡았을 때 이해하기 쉬워야 한다"는 뜻이다. 이러한 시도가 효과적인지는 조만간 후배가 들어왔을 때 알 수 있을 것이다.

 

우리가 택한 전략은 다음과 같다.

  1. TypeScript
  2. TSDoc
  3. 시멘틱 태그

 

1. TypeScript

사실 이러한 이유 때문에 기존의 JS 프로젝트를 TS로 마이그레이션했다. 타입도 없는 레거시를 다른 개발자가 이어갈 수 있을까 의문이 들었고, 그래서 정적 타입가 가능한 TypeScript로 옮기기로 했다.

 

또한 각기 다른 위치에 커스텀 타입 선언이 생겨나서 관리가 힘들어지는 문제를 방지하기 위해 타입 관련 파일을 분리시켰다. @types 폴더 내에 declare 문법으로 타입을 선언했다. 절대 경로 import 하기 위해 ".d.ts" 확장자를 사용했다.

 

참고

 

  • src/@types/
// src/@types/router.d.ts
declare module 'router' {
  export type RouteItem = {
    id: number;
    path: string;
    label: string;
    withAuth: boolean;
    element: JSX.Element;
  };
}
// src/@types/form.d.ts
declare module 'form' {
  export interface InputFormProps {
    type: string;
    name: string;
    label: string;
    value: string;
    error: boolean;
    onChange: (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
    onBlur: (e: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
  }

  export interface UseFormProps {
    initialValues: UserForm;
    validate: (values: UserForm) => FormState;
    onSubmit: (values: UserForm) => void;
  }

  export interface UserForm {
    [key: string]: string;
    name: string;
    major: string;
    number: string;
    email: string;
    phone: string;
    url: string;
    bio: string;
  }

  export interface FormState {
    [key: string]: boolean;
  }
}

 

2. TSDoc

TypeScript 코드의 문서화를 위해 TSDoc을 도입했다. 함수를 작성한 나에게는 어떤 기능을 수행하는 함수인지 명확하지만, 이 함수를 처음보는 개발자에게는 어떤 기능을 수행하는지, 어떤 인자를 받아서 어떤 반환값을 리턴하는지, 구조는 어떤지 바로 알 수는 없을 것이다. 특히나 커스텀 훅의 경우, state와 같은 훅을 사용하여 더욱 복잡한 로직을 가지기에 이해하는 데에 시간이 걸릴 것이다. 이런 가독성 문제를 해결하기 위해 TSDoc을 적극 사용하였다.

 

// src/hooks/useForm.ts
/**
 * 폼 입력 처리를 위한 커스텀 훅입니다.
 *
 * 이 훅은 폼의 상태 관리를 쉽게 하기 위해 사용됩니다. 입력 값 관리, 유효성 검사,
 * 그리고 폼 제출 처리를 위한 로직을 포함하고 있습니다.
 *
 * @param initialValues - 폼의 초기값을 지정하는 객체입니다.
 * @param validate - 입력 값에 대한 유효성 검사 함수입니다.
 * @param onSubmit - 폼 제출 시 호출되는 콜백 함수입니다.
 * @returns 폼의 값(values), 오류 메시지(errors), 터치된 필드(touched),
 *          이벤트 핸들러(handleChange, handleBlur, handleSubmit)를 반환합니다.
 *
 * @example
 * const form = useForm({
 *   initialValues: { name: '', email: '' },
 *   validate: (values) => {
 *     const errors = {};
 *     if (!values.name) {
 *       errors.name = 'Name is required';
 *     }
 *     if (!values.email) {
 *       errors.email = 'Email is required';
 *     }
 *     return errors;
 *   },
 *   onSubmit: (values) => {
 *     console.log(values);
 *   },
 * });
 *
 * // 사용 예시
 * <form onSubmit={form.handleSubmit}>
 *   <input
 *     name="name"
 *     value={form.values.name}
 *     onChange={form.handleChange}
 *     onBlur={form.handleBlur}
 *   />
 *   {form.touched.name && form.errors.name && <div>{form.errors.name}</div>}
 *   <input
 *     name="email"
 *     value={form.values.email}
 *     onChange={form.handleChange}
 *     onBlur={form.handleBlur}
 *   />
 *   {form.touched.email && form.errors.email && <div>{form.errors.email}</div>}
 *   <button type="submit">Submit</button>
 * </form>
 */
function useForm({ initialValues, validate, onSubmit }: UseFormProps) {
	//...
}

 

3. 시멘틱 태그

웹페이지 구조의 의미를 명확하게 할 수 있는 시멘틱 태그를 도입했다. 이전까지는 상단바, 하단바, 네비게이터, 텍스트, 목차 등을 구현할 때 모조리 div를 사용했다. 사실 div를 사용하면서도 전부 div만 써도 되나.. 의구심이 들긴 했지만 개선하지는 않았다. header나 nav 태그를 특별한 기능이 있는 태그로 알고 있어서 사용하지 않았는데, 자세히 알아보니 이 태그들은 특별한 기능을 수행하지 않는 "시멘틱 태그"인 것을 알게 되었다.

 

시멘틱 태그는 그 자체로 의미를 갖기 때문에, 코드를 보는 사람들에게 각 요소의 역할을 명확하게 알려준다. 또한 시멘틱 태그를 사용함으로써 검색 엔진이 웹페이지의 구조를 더 잘 인덱싱한다. 이를 통해 SEO를 높인다.

 

  • src/components/common/Header.tsx
// src/components/common/Header.tsx

function Header() {
  const location = useLocation();
  const { scrollYProgress } = useScroll();
  const headerData = routerData.filter((item) => isNotAuthPage(item.path));

  return (
    <>
      <header className="fixed z-10 w-full h-16 bg-white">
        <div className="flex items-center w-full h-full p-7">
          {/* TODO: 로고 이미지로 대체해야함. */}
          <h3 className="flex justify-start flex-1" id="aid-logo">
            AI Developers
          </h3>
          <nav className="flex items-center grow justify-evenly">
            {headerData.map((item) => (
              <div className="" key={item.label}>
                <Link to={item.path}>
                  <Button label={item.label} />
                </Link>
              </div>
            ))}
          </nav>
          <div className="flex justify-end flex-1 gap-4 w-1/8">
            <button>Login</button> | <button>Register</button>
          </div>
        </div>
        <motion.div
          className="absolute bottom-0 w-full h-1"
          style={{
            scaleX: scrollYProgress,
            transformOrigin: 'left',
            backgroundColor: isDarkPage(location.pathname)
              ? colors.primary
              : 'black',
          }}
        />
      </header>
    </>
  );
}

export default Header;
  • 일반적인 컴포넌트의 경우 굳이 tsdoc을 작성하지 않는다.
  • 컴포넌트 이름에서 이름과 역할을 유추할 수 있기 때문.

 

결론

 

코드 가독성을 높이기 위해 여러 방법을 사용했지만, 걱정되는 부분도 존재한다. 프로젝트에 여러 전략이 사용될수록 후에 들어올 개발자가 "알아야 할 것"이 많아졌다. 타입스크립트를 제대로 사용하기 위해서는 @types 폴더와 declare에 대해 알아야하고, tsdoc은 무엇이고 어떻게 작성하는지, 시멘틱 태그는 일반적인 태그랑 무엇이 다르고 왜 쓰는지..

 

후배 개발자가 배워야 할 것들이 많아져서 진입장벽을 높게 친 것 같아서 조금 걱정이다. 이런 문제를 해결하기 위해서는 동아리 내 프론트엔드 커리큘럼을 개선해야 할 것 같다.