토스 slash 24를 시작으로 타임라인을 따라 slash 21까지 보게 되며
인상 깊었던 세션이 있었습니다.
진유림 님이 발표하신 토스ㅣSLASH 21 - 실무에서 바로 쓰는 Frontend Clean Code에서
응집도와 결합도, 단일 책임 원칙을 기준으로 클린 코드를 작성하는 방법이었는데요.
세션에서 언급한 내용을 기반으로 실무에 작성했던 저의 코드들을 돌아보며
난잡하기 짝이 없다는 것을 깨닫게 되었습니다.
실무에서 언제든지 적용할 수 있을 때까지 저의 기록을 보며 공부하기 위해 포스팅하게 되었습니다.
응집도는 뭐고 결합도는 뭐야?
📒 응집도 (Cohesion)
응집도는 특정 모듈(예: 함수나 클래스)이 얼마나 잘 관련된 기능을 가지고 있는지를 나타내는 척도입니다.
쉽게 말해, 하나의 모듈이 특정한 작업이나 역할을 얼마나 잘 수행하는지를 의미하는데요.
응집도가 높으면 그 모듈은 특정한 목적에 집중하고 있다는 뜻입니다.
예시:
하나의 함수가 사용자 정보를 처리하는 데만 집중하고 있다면,
그 함수는 응집도가 높다고 할 수 있습니다.
반면에, 사용자 정보 처리와 동시에 UI 요소도 변경하는 경우라면 응집도가 낮아지겠죠?
📒 결합도 (Coupling)
결합도는 서로 다른 모듈이 얼마나 의존하고 있는지를 나타내는 척도입니다.
결합도가 낮으면, 한 모듈이 다른 모듈에 크게 영향을 미치지 않고 독립적으로 동작할 수 있는데요.
결합도가 높으면, 한 모듈의 변경이 다른 모듈에 영향을 미칠 가능성이 커집니다.
예시:
두 개의 모듈이 서로 많은 정보를 주고받는다고 가정하겠습니다.
하나의 모듈이 변경되면 다른 모듈도 함께 변경해야 한다면, 결합도가 높다고 할 수 있어요.
반면에, 각각의 모듈이 독립적으로 동작할 수 있다면 결합도가 낮은 거죠.
중간 요약을 하자면
응집도: 한 모듈이 얼마나 잘 특정 기능에 집중하고 있는가.
결합도: 모듈 간의 의존성이 얼마나 강한가.
한 줄 요약: 응집도를 높이고 결합도를 낮추는 것이 좋다는 겁니다.
응집도와 결합도를 코드에 적용해 보자.
이전에 제가 구현했던 멀티파트 업로드 로직이 적절한 예제라 생각되어 가져왔는데요.
(급하다는 핑계로 차일피일 코드 리팩토링을 미루다 이제야 진행하게 되었습니다.)
Before
interface IFileUploadInfo {
name: string;
size: number;
path: string;
}
...
const multipartUpload = async (
uploadedFiles: File[],
chatId: number,
message: string,
) => {
const mediaSets: IFileUploadInfo[] = [];
const fileUploadPromises = uploadedFiles.map(async (singleFile) => {
const chunkedFile = await makeChunk(singleFile, CHUNK_SIZE);
const uploadStartInfo = await bringMultipartUploadId.mutateAsync(singleFile.name);
const { objectKey, uploadId } = uploadStartInfo;
const s3UploadInfo = chunkedFile.map(async (chunk, index) => {
const s3UploadUrl = await bringS3UploadUrl.mutateAsync({
objectKey,
partNumber: index + 1,
uploadId,
});
const { ETag } = await sendS3UploadChunk.mutateAsync({
s3UploadUrl,
chunk,
fileType: singleFile.type,
});
return {
ETag,
PartNumber: index + 1,
};
});
try {
const completedParts = await Promise.all(s3UploadInfo);
const completeRes = await completeMultipart.mutateAsync({
objectKey,
uploadId,
parts: completedParts,
});
mediaSets.push({
name: file.name,
size: file.size,
path: completeRes,
});
} catch (error) {
// 추가적인 에러 핸들링 로직
}
});
await Promise.all(fileUploadPromises);
// 최종적으로 메시지를 전송.
await createChat({
data: {
chatId,
text: message ? message : null,
mediaSet: mediaSets,
},
});
};
multipartUpload라는 하나의 함수에 많은 책임이 주어져 있는 것을 볼 수 있습니다.
응집도는 낮고 결합도는 높은 상태인데요, 단일 책임 원칙을 적용을 통해 코드를 분리하여 개선시켜 보았습니다.
After
interface IFileUploadInfo {
name: string;
size: number;
path: string;
}
export const useUploadMultiPartToS3 = () => {
const {
multipartUploadMutation,
uploadUrlChunkMutation,
uploadChunkMutation,
completeMultipartMutation,
} = useMultipartUploadMutation();
const CHUNK_SIZE = 20 * Math.pow(1024, 2); // 20MB
const uploadChunk = async (
chunk: Blob,
objectKey: string,
uploadId: string,
partNumber: number,
) => {
const uploadUrl = await uploadUrlChunkMutation.mutateAsync({
objectKey,
partNumber,
uploadId,
});
const { ETag } = await uploadChunkMutation.mutateAsync({
uploadUrl,
chunk,
fileType: chunk.type,
});
return {
ETag,
PartNumber: partNumber,
};
};
const uploadFile = async (file: File): Promise<IFileUploadInfo> => {
const chunkedFile = await createChunkedArray(file, CHUNK_SIZE);
const uploadInfo = await multipartUploadMutation.mutateAsync(file.name);
const { objectKey, uploadId } = uploadInfo;
const completedParts = await Promise.all(
chunkedFile.map((chunk, index) =>
uploadChunk(chunk, objectKey, uploadId, index + 1),
),
);
const completeRes = await completeMultipartMutation.mutateAsync({
objectKey,
uploadId,
parts: completedParts,
});
return {
name: file.name,
size: file.size,
path: completeRes,
};
};
const multipartFile = async (files: File[]) => {
const mediaSets: IFileUploadInfo[] = await Promise.all(
files.map((file) => uploadFile(file)),
);
return mediaSets;
};
return {
multipartFile,
multipartUploadMutation,
uploadUrlChunkMutation,
uploadChunkMutation,
completeMultipartMutation,
};
};
청크 단위 업로드를 담당하는 uploadChunk 함수와
단일 파일 업로드 프로세스를 담당하는 uploadFile 함수,
여러 파일 처리를 담당하는 multipartFile 함수.
단일 책임 원칙을 적용해 각 함수가 하나의 책임만 가지도록 분리하며
응집도와 결합도를 개선해 보았습니다.
하지만 동료분의 리뷰와 GPT를 사용한 리팩토링을 확인해 보니 개선해야 할 코드가 많은데요.
하나씩 학습하고 포스팅해 보도록 하겠습니다.
Reference
토스ㅣSLASH 21 - 실무에서 바로 쓰는 Frontend Clean Code
'TIL' 카테고리의 다른 글
yarn classic에서 pnpm으로의 시작 (1) | 2024.12.27 |
---|---|
<img/> vs <Image />, 유연하게 대처하기 (0) | 2024.12.07 |
reset.css 설정으로 react-markdown을 통한 마크다운 적용이 안될 때 (0) | 2024.11.13 |
실패한 2년차 경력직 인터뷰 경험 (1) | 2024.11.12 |
서비스에 GA를 도입해보자 (0) | 2024.10.11 |