요즘 프로젝트를 진행하면서 클린코드에 대해 관심이 많아졌습니다.
클린코드란?
"클린 코드"는 프로그래밍과 소프트웨어 개발에서 매우 중요한 개념 중 하나로, 소스 코드의 가독성, 유지보수성, 품질, 효율성, 그리고 버그의 최소화를 목표로 하는 개념을 나타냅니다. 클린 코드는 다른 개발자가 이해하고 협업하기 쉽고, 오랫동안 유지보수할 수 있는 코드를 작성하는 방식입니다. - ChatGPT
Chat GPT 선생님께선 가독성, 유지보수성, 품질, 효율성 등등등... 이 조건들을 잘 지키는 것이 클린코드라고 합니다..
지극히 주관적인 제 기준에서의 클린코드란 남이 봤을 때 이쁘게 짠 코드 즉 술술 잘 읽히는 코드라고 생각됩니다. 코드가 잘 읽힌다는 것은 가독성 있게 잘 짠 것일 테고 잘 읽힌다면 전체적인 구조를 빠르게 파악할 수 있어 유지보수에 큰 비용을 들이지도 않게 될 것입니다.
사이드 프로젝트를 진행하면서 제 나름대로 어떻게 클린코드에 대해 어떻게 신경 썼는지 글로 남겨보겠습니다.
변수이름은 변수역할에 맞게 객관적으로 정하자.
오픈채팅방에서 방장은 스케줄을 추가할 수 있는데 스케줄 옵션 중 하루종일이라는 체크박스가 있습니다. 해당 옵션 상태를 관리하는 코드는 원래 아래와 같았습니다.
const [isChecked, setIsChecked] = useState(false);
<label
htmlFor="allday"
className="flex items-center self-start gap-2 cursor-pointer"
>
<input
type="checkbox"
id="allday"
className="w-6 h-6 rounded-full checked:bg-primary-200 checked:hover:bg-primary-200"
onChange={(e) => setIsChecked(e.target.checked)}
/>
<p className="body1">하루종일</p>
</label>
부끄럽게도 기능을 구현하는 게 앞서서 변수명은 일단 지었고 기능을 구현다 한 후 다시 코드를 살펴보니 변수명이 직관적이지 않았습니다.
isChecked 라면 무엇을 체크하는 거지? 즉 만약 해당코드의 전체적인 맥락을 모르는 사람이 해당코드를 본다면 isChecked는 무엇을 관리하는 상태인 건지 알아야 하기에 그만큼 시간비용이 들게 됩니다.
const [isAlldayChecked, setIsAlldayChecked] = useState(false);
<label
htmlFor="allday"
className="flex items-center self-start gap-2 cursor-pointer"
>
<input
type="checkbox"
id="allday"
className="w-6 h-6 rounded-full checked:bg-primary-200 checked:hover:bg-primary-200"
onChange={(e) => setIsAlldayChecked(e.target.checked)}
/>
<p className="body1">하루종일</p>
</label>
위와 같이 조금 더 직관적으로 변수명을 리팩토링 해주어 해당상태는 어떤 상태를 관리하는 건지 한 번에 알 수 있도록 하였습니다.
복잡한 조건문은 변수로 만들자
오픈채팅방을 생성할 수 있는 모달입니다. 해당 기능중 해시태그를 추가 삭제 할 수 있는 기능이 있는데 이 기능에선 어떻게 제 나름대로 클린코드하려 애썼는지 살펴보겠습니다.
const addTagByEnter = (event: KeyboardEvent) => {
if (event.key === 'Enter') {
if (!tagList.includes(tag) && tag.trim() !== '' && !tagList.length >= 10) {
setTagList([...tagList, tag]);
reset();
}
}
};
봤을 때 단숨에 바로 어떤 조건문인지 파악을 하실 수 있었나요? 물론 엄청 복잡한 조건문은 아니라서 파악하는데 시간은 오래 걸리지 않을 것입니다. 하지만 만약 조건이 또 추가되면 어떻게 될까요? 아니면 만약 위 조건보다 엄청 복잡한 조건이 있었다면 어떻게 됐을까요? 당연히 어떤 조건문인지 파악하기 힘들게 됩니다.
const addTagByEnter = (event: KeyboardEvent) => {
const isHashTagNumOver = tagList.length >= 10;
const isNotDuplicateTag = !tagList.includes(tag);
const isNonEmptyTag = tag.trim() !== '';
if (event.key === 'Enter') {
if (isNotDuplicateTag && isNonEmptyTag && !isHashTagNumOver) {
setTagList([...tagList, tag]);
reset();
}
}
};
if문안에 들어가는 조건들을 변수로 만들어 사용했습니다. 위 코드보다 가독성이 한결 나아졌으며 어떤 조건들을 통과해야 다음코드가 실행되는지 단숨에 파악하기 쉬워졌습니다.
데이터와 UI의 분리
스케줄 일정을 선택할 수 있는 캘린더 컴포넌트입니다. 해당 컴포넌트는 어떻게 데이터와 UI를 분리했는지 살펴보겠습니다.
import {
addDays,
endOfMonth,
endOfWeek,
startOfMonth,
startOfWeek,
eachDayOfInterval,
format,
addMonths,
startOfYear,
} from 'date-fns';
const useCalender = (selectedDate: Date) => {
const weekDays = [];
const weekStartDate = startOfWeek(new Date());
for (let day = 0; day < 7; day += 1) {
weekDays.push(format(addDays(weekStartDate, day), 'EEEEE'));
}
const allMonth = [];
const startMonth = startOfYear(selectedDate);
for (let month = 0; month < 12; month += 1) {
allMonth.push(addMonths(startMonth, month));
}
const 현재달의시작날짜 = startOfMonth(selectedDate);
const 현재달의마지막날짜 = endOfMonth(selectedDate);
const 현재달의첫주의시작날짜 = startOfWeek(현재달의시작날짜);
const 현재달마지막주의끝날짜 = endOfWeek(현재달의마지막날짜);
const currentMonthAllDates = eachDayOfInterval({
start: 현재달의첫주의시작날짜,
end: 현재달마지막주의끝날짜,
});
return { weekDays, currentMonthAllDates, allMonth };
};
export default useCalender;
캘린더를 그릴 수 있도록 데이터를 생성하는 건 커스텀훅으로 만들어 분리하였습니다. 위와 같이 만들면 만약 달력 디자인이 바뀌어도 데이터를 받아오는 건 변하지 않기 때문에 컴포넌트 디자인에만 오로지 신경 쓸 수 있습니다.
import { format, isSameDay, isSameMonth, addMonths, subMonths } from 'date-fns';
import useCalender from '@/hooks/common/useCalender';
import { DatePickerProps } from '.';
export default function Date({
selectedDate,
setSelectedDate,
onChangePickerType,
}: DatePickerProps) {
const { currentMonthAllDates, weekDays } = useCalender(selectedDate);
const nextMonth = () => {
setSelectedDate(addMonths(selectedDate, 1));
};
const prevMonth = () => {
setSelectedDate(subMonths(selectedDate, 1));
};
const onChangeDate = (date: Date) => {
setSelectedDate(date);
};
return (
<div className="flex flex-col gap-1">
{...기타코드}
<div className="grid grid-cols-7 place-items-center">
{weekDays.map((days, index) => (
<div key={index}>{days}</div>
))}
</div>
<div className="grid grid-cols-7 ">
{currentMonthAllDates.map((date, index) => (
<button
key={index}
className={`p-2 rounded-full
${isSameMonth(selectedDate, date) ? '' : 'text-grey-200'}
${
isSameDay(selectedDate, date)
? 'bg-primary-200 text-[#FFFFFF]'
: 'hover:bg-primary-50 hover:text-primary-200'
}`}
type="button"
onClick={() => onChangeDate(date)}
>
{date.getDate()}
</button>
))}
</div>
</div>
);
}
데이터를 관리하는 파일, UI를 담당하는 파일이 분리되어 있기 때문에 유지보수, 리팩토링 관점에서도 훨씬 유연하게 대응할 수 있게 되었습니다.
글을 작성하며 느낀 점
나만의 규칙을 어느 정도 확립한 것 같아서 마음 한편 속이 후련해진 것 같은 기분이 듭니다. 더 나아가서 다른 개발자들의 코드들도 보고 배우고 싶은 마음이 솟구치는... 얼른 취업하고싶네영😀
'FrontEnd' 카테고리의 다른 글
리액트 Stompjs,Sockjs 실시간 채팅 구현 - (2) (0) | 2023.10.20 |
---|---|
리액트 Stompjs,Sockjs 실시간 채팅 구현 - (1) (0) | 2023.10.16 |
FrontEnd - WebSocket 에 대하여 (0) | 2023.08.03 |
FrontEnd - Tailwindcss 동적 스타일링 하기 (0) | 2023.07.27 |
FrontEnd - 재사용성 높인 버튼 공통컴포넌트 만들어보기 with.tailwindcss (0) | 2023.07.09 |