
개발을 하면서 모든 개발자들이 그렇겠지만 늘 좋은코드란 무엇인가 고민하게됩니다. 클린코드에 대한 글들은 워낙 많아서 이미 아는 부분도 좀 있지만 아는것과 그것을 적용시키는건 일이더라구요.. 😅
요근래 신규 기능 개발건들이 휘몰아치면서 어찌저찌 개발을 끝내긴 했는데..
그러다 보니 '좋은 코드'에 대한 생각을 깊게 하지 못하고 끝나버린 느낌이라 반성겸 , 우연치않게 toss에서 제공해준 Frontend Fundamentals를 본겸 해서 상기시키기 위한 정리해보려합니다!
(이 말을 들었을때 오... 맞는말이다 라고 생각했습니다 ...
좋은 코드에 대한 말은 많지만 , 결국 개발자가 유지보수 하기 쉬운 , 변경이 쉬운 코드가 좋은코드이지 않겠어요?!ㅋㅋㅋ)

이렇게 viwer라는 권한을 가지고 있을때 파란색은 보기 전용 권한 / 빨간색은 일반 사용자일때 실행되는 코드를
function SubmitButton() {
const isViewer = useRole() === "viewer";
return isViewer ? <ViewerSubmitButton /> : <AdminSubmitButton />;
}
function ViewerSubmitButton() {
return <TextButton disabled>Submit</TextButton>;
}
function AdminSubmitButton() {
useEffect(() => {
showAnimation();
}, []);
return <Button type="submit">Submit</Button>;
}
이렇게 권한별 코드를 나눠서 관리하도록 지정해주자!
예를들어
// bad👎🏻
// 페이지가 필요한 모든 쿼리 파라미터를 관리하는 훅
import moment, { Moment } from "moment";
import { useMemo } from "react";
import {
ArrayParam,
DateParam,
NumberParam,
useQueryParams
} from "use-query-params";
const defaultDateFrom = moment().subtract(3, "month");
const defaultDateTo = moment();
export function usePageState() {
const [query, setQuery] = useQueryParams({
cardId: NumberParam,
statementId: NumberParam,
dateFrom: DateParam,
dateTo: DateParam,
statusList: ArrayParam
});
return useMemo(
() => ({
values: {
cardId: query.cardId ?? undefined,
statementId: query.statementId ?? undefined,
dateFrom:
query.dateFrom == null ? defaultDateFrom : moment(query.dateFrom),
dateTo: query.dateTo == null ? defaultDateTo : moment(query.dateTo),
statusList: query.statusList as StatementStatusType[] | undefined
},
controls: {
setCardId: (cardId: number) => setQuery({ cardId }, "replaceIn"),
setStatementId: (statementId: number) =>
setQuery({ statementId }, "replaceIn"),
setDateFrom: (date?: Moment) =>
setQuery({ dateFrom: date?.toDate() }, "replaceIn"),
setDateTo: (date?: Moment) =>
setQuery({ dateTo: date?.toDate() }, "replaceIn"),
setStatusList: (statusList?: StatementStatusType[]) =>
setQuery({ statusList }, "replaceIn")
}
}),
[query, setQuery]
);
}
따라서
// good 👍🏻
import { NumberParam, useQueryParam } from "use-query-params";
export function useCardIdQueryParam() {
const [cardId, _setCardId] = useQueryParam("cardId", NumberParam);
const setCardId = useCallback((cardId: number) => {
_setCardId({ cardId }, "replaceIn");
}, []);
return [cardId ?? undefined, setCardId] as const;
}
const matchedProducts = products.filter((product) => {
return product.categories.some((category) => {
const isSameCategory = category.id === targetCategory.id;
const isPriceInRange = product.prices.some(
(price) => price >= minPrice && price <= maxPrice
);
return isSameCategory && isPriceInRange;
});
});
const ANIMATION_DELAY_MS = 300; // function 밖에 작성하여 한번 호출되도록 하고 단위까지 깔끔 🩷
async function onLikeClick() {
await postLike(url);
await delay(ANIMATION_DELAY_MS);
await refetchPostLike();
}
const status = (() => {
if (A조건 && B조건) return "BOTH";
if (A조건) return "A";
if (B조건) return "B";
return "NONE";
})();
import { useQuery } from '@tanstack/react-query';
function useUser() {
const query = useQuery({
queryKey: ['user'],
queryFn: fetchUser
});
return query; // 1️⃣ 여기선 query 객체를 반환하고
}
function useServerTime() {
const query = useQuery({
queryKey: ['serverTime'],
queryFn: fetchServerTime
});
return query.data; // 2️⃣ 여기선 data를 반환한다?!
}
➡️ 데이터 형태 통일하기 !!
async function fetchBalance(): Promise<number> {
const balance = await http.get<number>("...");
logging.log("balance_fetched"); // 👎🏻 이 함수의 이름만 가지고는 로깅이 이루어지는지 알 수 없어요
return balance;
}
// 이렇게 분리하기 👍🏻
const balance = await fetchBalance();
logging.log("balance_fetched");
어떤걸 사용하는게 좋을지에 대한 요약은 아래와 같습니다 🙃
필드 단위 응집도: 독립적인 검증(이메일, 추천 코드)이나 재사용이 필요한 경우 적합.
폼 전체 단위 응집도: 모든 필드가 하나의 기능(결제, 배송)으로 연결되거나 단계별 입력이 필요한 경우 유용.
ContextAPI나 조합(Composition)패턴을 사용해보아요!function ItemEditModal({ open, items, recommendedItems, onConfirm, onClose }) {
const [keyword, setKeyword] = useState("");
return (
<Modal open={open} onClose={onClose}>
<ItemEditBody onClose={onClose}>
<ItemEditList
keyword={keyword}
items={items}
recommendedItems={recommendedItems}
onConfirm={onConfirm}
/>
</ItemEditBody>
</Modal>
);
}
// 이런식으로 children을 사용하기
function ItemEditBody({ children, onClose }) {
return (
<>
<div style="display: flex; justify-content: space-between;">
<Input
value={keyword}
onChange={(e) => onKeywordChange(e.target.value)}
/>
<Button onClick={onClose}>닫기</Button>
</div>
{children}
</>
);
}
토스에서도 위의 큰 4가지 기준을 한꺼번에 충족하긴 어렵다고 말합니다 😅‼️
🟢 예를들어 함수나 변수가 항상 같이 수정되기위해 공통화 및 추상화를 하면 응집도는 높아짐
🔴 but 코드가 한 차례 추상화되기때문에 가독성은 떨어짐
🟢 중복 코드를 허용하면, 코드의 영향범위를 줄일 수 있어서 결합도를 낮출수 있음
🔴 but 한쪽을 수정했을 때 다른 한쪽을 실수로 수정하지 못할 수 있어 응집도가 떨어짐
오늘 정리한 내용을 되새기며, 과거의 내 코드를 돌아보니 반성할 점도 보이네요. 😊... 쥬륵
변경하기 쉬운 코드를 만들기 위해 조금씩 더 생각하면서 코드를 짜야겠다는 생각을 했습니다.
내 동료들이 코드를 딱 봤을때 이해하기 쉽도록!!
짜보도록 노력해야겠어요!
참고적으로 frontend-fundamentals 커뮤니티에서
좋은 토론 모아보기나 AvsB 같은 페이지도 제공하는데 생각보다 좋은 고민들이 많이 있어 틈틈히 보면 좋을것 같다는 생각입니다!
( 제가 지금 개발 및 운영하고있는 댑댑댑 이라는 싸이트에서도 A vs B 와 비슷하게 픽픽픽 이라는 기능을 제공해주기에 여기도 한번 방문해보셔도 좋습니다 😄)