[하루메이트] Clean Code 적용하기

2023. 8. 16. 22:59

프로젝트가 끝난 후 자유시간도 충분히 즐겼고 이제 리팩토링만 남았습니다.

자유시간 동안 클린 코드(Clean Code)라는 책을 읽었는데 이 책을 기반으로 리팩토링을 진행하려고 합니다. 

 

책에서 프로그래머들의 말을 빌려 깨끗한 코드(Clean Code)가 무엇인지 이야기합니다.

깨끗한 코드는 보기에 즐거워야하며(우아한) 효율적이어야 한다. 또한 가독성이 좋아야한다.

깨끗한 코드일 때 다른 사람이 고치기 쉽다.(여기서 읽기 쉬운 코드와 고치기 쉬운 코드는 다름)

TDD를 중요하게 이야기하는데 테스트 케이스가 없는 코드는 깨끗한 코드가 아니다.라고 적혀있을 정도입니다. 중복이 없어야 한다. 등등 정말 많은 내용이 있었습니다.. 

 

리팩토링을 하다 보면 항상 느끼는 거지만 첫 리팩토링에 클린 코드의 모든 조건을 만족하는 건 쉽지 않습니다. 저만 그런 거일 수도 있겠지만 대부분의 사람들이 그럴 거라고 생각합니다.

아직 실력이 부족한 것도 있고 지금 리팩토링한다고 해도 나중에 와서 봤을 때는 또 고칠 것들이 있을 것이기에 최대한 코드를 자주 보면서 주기적으로 리팩토링을 진행하려고 합니다.

아무튼 이 포스트가 반복적으로 수정될 것이라는 소리입니다.

 

의미 있는 이름 

변수나 함수명이 엉망인 게 많아서 기존보다 의미 있는 이름으로 변경하였습니다.

댓글과 관련된 프로퍼티들이 다 서버에서 answer~로 넘어오는데 이를 client에서 comment로 변경하였습니다.

//변경 전
export const DeleteComment = async ({ answerId }: { answerId: number }) =>
  instance.delete(`/api/answers/${answerId}`);
//변경 후
export const DeleteComment = async ({ commentId }: { commentId: number }) =>
  instance.delete(`/api/answers/${commentId}`);

중복되거나 컴포넌트를 보고 바로 파악하기 힘든 컴포넌트명들 역시 의미있는 이름으로 변경했습니다.

 

컴포넌트/함수 분리

comment 컴포넌트

Comment 컴포넌트에 포함돼있던 수정과 삭제 동작을 각각 컴포넌트(DeleteCommentButton, PatchCommentButton)로 분리하였습니다.

 //변경 전 Comment 컴포넌트
  const commentRef = useRef<HTMLTextAreaElement>(null);
  ...
  const queryClient = useQueryClient();
 //삭제와 수정 관련 동작이 작성되어 있음. 
  const delmutation = useMutation(DeleteComment, {
    onSuccess: () => queryClient.invalidateQueries(['communityDetail']),
  });
  const Patchmutation = useMutation(PatchComment, {
    onSuccess: () => queryClient.invalidateQueries(['communityDetail']),
  });
  const handleDeleteComment = () => {
    if (answerId) delmutation.mutate({ answerId });
  };
  const handlePatchComment = () => {
    if (
      commentRef.current!.value !== content &&
      commentRef.current!.value.trim().length > 0
    ) {
      if (answerId)
        Patchmutation.mutate({
          answerId,
          answerContent: commentRef.current!.value,
        });
    }
    setEditing(false);
  };
  useEffect(() => {
    if (isEditing && commentRef.current) commentRef.current.value = content;
  }, [isEditing, content]);

- 수정, 삭제 컴포넌트 분리

const DeleteCommentButton = ({
  commentId,
}: {
  commentId: number | undefined;
}) => {
  const queryClient = useQueryClient();
  const deleteMutation = useMutation(DeleteComment, {
    onSuccess: () => queryClient.invalidateQueries(['communityDetail']),
  });
  const handleDeleteComment = () => {
    if (commentId) deleteMutation.mutate({ commentId });
  };
  return <Button onClick={handleDeleteComment}>삭제</Button>;
};
const PatchCommentButton = ({
  previousComment,
  newComment,
  commentId,
  setEditing,
}: {
  previousComment: string;
  newComment: string;
  commentId: number | undefined;
  setEditing: React.Dispatch<React.SetStateAction<boolean>>;
}) => {
  const queryClient = useQueryClient();
  const patchMutation = useMutation(PatchComment, {
    onSuccess: () => queryClient.invalidateQueries(['communityDetail']),
  });
  const isSatisfiedPatchCommentConditions = () => {
    return newComment !== previousComment && newComment.trim().length > 0;
  };
  const handlePatchComment = () => {
    if (isSatisfiedPatchCommentConditions()) {
      if (commentId)
        patchMutation.mutate({
          commentId,
          answerContent: newComment,
        });
    }
    setEditing(false);
  };
  return <Button onClick={handlePatchComment}>수정완료</Button>;
};
 //변경된 Comment컴포넌트
  ...
  const commentRef = useRef<HTMLTextAreaElement>(null);
  const [newComment, setNewComment] = useState(previousComment);
  const [isEditing, setEditing] = useState<boolean>(false);
  const isSamePerson = userData
    ? userData.memberEmail === commenterEmail
    : false;
  //수정, 삭제 동작 분리
  useEffect(() => {
    if (isEditing && commentRef.current)
      commentRef.current.value = previousComment;
  }, [isEditing, previousComment]);

  return (
  ...

 

함수의 경우 명령과 조회를 분리했습니다. 그리고 조건에 대한 함수의 경우 is로 시작하도록 함수명을 변경하였습니다.

기존에는 버튼을 클릭했을 때 유효한 조건인지 판단하고 그 조건이 맞다면 객체 상태를 변경하는 동작을 하나의 함수 안에서 작성했으나 유효한 조건을 판단하는 부분을 새로운 함수(isTagNameSatisfiedCondition)로 만들어주었습니다. 

  const makeTag = (e: KeyboardEvent<HTMLInputElement>) => {
    if (e.key === 'Enter') {
      if (!inputRef.current) return;
      const { current } = inputRef;
      if (current && current?.value.trim().length > 0) {
        const newTag = current.value;
        if (!tags.find((tas) => tas === newTag)) {
          setTags((prev) => [...prev, newTag]);
        }
        current.value = '';
      }
    }
  };
 const isTagNameSatisfiedCondition = () => {
    if (!inputRef.current) return false;
    const newTag = inputRef.current.value ?? '';
    if (newTag.trim().length <= 0) return false;
    return !tags.find((tag: string) => tag === newTag);
  };

  const makeTag = (e: KeyboardEvent<HTMLInputElement>) => {
    if (e.key === 'Enter') {
      if (isTagNameSatisfiedCondition()) {
        const newTag = inputRef.current!.value;
        setTags((prev) => [...prev, newTag]);
      }
      inputRef.current!.value = '';

형식 맞추기

변수를 사용하는 위치에 가까이 선언했습니다. 사실 이건 조금 더 고민해봐야할 것 같습니다. 원래는 변수들을 맨 위에 모아놓는 스타일인데 제가 느끼기에 맨 위에 모아놓는 게 더 깔끔해보여서..이전 스타일은 깔끔해보이고 클린 코드에서 추구하는 스타일을 코드를 쉽게 이해하기에 좋은 것 같네요.. 나중에 시간이 지난 뒤에 다시 코드를 봤을 때 가독성이 떨어진다면 다시 수정해야겠습니다. 그리고 함수마다 빈 행을 추가하여 가독성을 높였습니다.

'토이프로젝트' 카테고리의 다른 글

[하루메이트] Custom Hook으로 리팩토링하기  (0) 2023.08.31
[하루메이트] 회고  (1) 2023.07.26

BELATED ARTICLES

more