관심사 분리에 대한 고민
안녕하세요 현재 볼드나인에서 프론트엔드 개발자로 일하고 있는 장예지라고합니다. 볼드나인에서는 빠르게 변화하는 비즈니스 요구사항을 만족시키기 위해 다양한 프로젝트를 진행하고 있습니다. 이런 환경에서 코드의 유지보수성과 확장성을 높이는 것이 중요하다는 것을 실감하고 있습니다. 특히, 여러 개발자들과 협업하며 프로젝트를 진행하다 보면, 코드가 복잡해지고 예상치 못한 버그가 발생하는 경우가 종종 있었습니다. 이러한 경험들은 자연스럽게 관심사 분리라는 주제에 관심을 갖게 되는 계기가 되었습니다.
관심사란 무엇일까?
제 생각에 관심사란 **무언가를 어떤 방식으로 분류하여 볼 것인가?**에 대한 개념이라고 생각합니다. 이 과정에서 분류 기준을 얼마나 세밀하게 나누는지에 따라, 같은 코드라도 관심사를 여러 개로 나눌 수도 있고 하나로 판단할 수도 있습니다. 그렇기 때문에 관심사를 정의하고 이를 기준으로 코드를 나눌 때, 사람마다 의견이 달라질 수밖에 없다고 생각합니다.
관심사라는 개념을 정의하는 기준은 무엇일까?
앞서 언급했듯이 관심사는 "무언가를 어떤 방식으로 분류하여 볼 것인가?"라는 개념으로 정의할 수 있습니다. 그렇다면 관심사라는 개념을 정의하는 기준은 무엇일까요? 저는 실용적인 관점에서 접근하고 싶습니다.
관심사를 정의하는 이유는 사람이 이해하기 쉬운 형태로 추상화를 진행하기 위해서라고 생각합니다. 추상화는 복잡함을 가리고 단순화하는 도구이기 때문에, 문제의 복잡도를 줄이는 데 필요한 과정이라고 봅니다. 따라서 문제의 복잡도가 관심사를 정의하는 주요 기준이 된다고 생각합니다.
사례: 복잡한 UI 컴포넌트의 설계
Dan Abramov의 "Presentational and Container Components"라는 멘탈 모델에 따르면, 컴포넌트의 계층은 아래와 같이 나눌 수 있습니다:
•
Container 컴포넌트: 비순수 로직과 상태를 가진 컴포넌트
•
Presentational 컴포넌트: 디자인과 UI를 담당하는 컴포넌트
이러한 관심사 분리를 통해 로직 담당과 디자인 담당이라는 두 계층으로 단순화할 수 있습니다. 이 추상화는 일반적인 사례에서는 매우 잘 작동합니다. 그러나 테이블, Drawer, Dialog 등 복잡한 UI를 다룰 때는 디자인 컴포넌트 내부에서도 동작과 웹 접근성 같은 관심사를 추가로 나누는 것이 유용할 수 있습니다.
결론적으로, 관심사를 정의하고 추상화를 할 때는 **현재 상황이 얼마나 복잡한가?**를 기준으로 삼으면 좋다고 생각합니다.
높은 응집도와 낮은 결합도의 가치
두 개념 모두 지향하는 가치는 "유지보수성"으로 요약할 수 있습니다.
•
결합도를 낮추는 이유: 모듈 변경 시 영향을 받는 범위를 줄이기 위해
•
응집도를 높이는 이유: 변경사항이 생길 때 수정할 위치를 명확히 하기 위해
이러한 가치는 소스 코드의 양이 많아질수록 더욱 중요해집니다. 작은 변경에도 예상치 못한 사이드 이펙트가 생기는 환경은 개발자에게 두려운 환경이기 때문입니다. 따라서 높은 응집도와 낮은 결합도는 소프트웨어를 소프트웨어답게 만드는 핵심 가치라고 생각합니다.
변경되기 쉬운 관심사와 어려운 관심사의 결합
이 문제에 대해서는 정답이 없다고 생각합니다. 잘 설계했다고 생각한 구조에서도 여러 이해관계로 인해 "파괴적 변경"이 필요한 경우가 종종 발생하기 때문입니다. 이는 개발자가 통제할 수 없는 영역이므로, 안 바뀌기를 기도하거나 변경을 최소화하는 것 외에는 답이 없다고 봅니다.
완충지대를 활용한 해결
개발자 입장에서 유용한 방법 중 하나는 완충지대를 두는 것입니다. 예를 들어, API의 Response 스펙이 자주 변경된다면 애플리케이션의 모든 부분이 API 응답 형태에 직접 의존하지 않고, 중간 매핑 계층을 추가하여 관리할 수 있습니다.
예시 코드
import { queryOptions } from "@tanstack/react-query";
type ExampleType = {
id: number;
title: string;
createAt: string;
};
type ExternalExampleType = {
ID: string;
title: string;
created_at: string;
};
const exampleMapper = (data: ExternalExampleType): ExampleType => {
return {
id: parseInt(data.ID),
title: data.title,
createAt: data.created_at,
};
};
const exampleFetch = async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/todos/1");
const json = await response.json();
return exampleMapper(json);
};
const exampleQueryOptions = () =>
queryOptions({
queryKey: ["example"],
queryFn: exampleFetch,
});
Plain Text
복사
이처럼 완충지대를 두면 API 스펙 변경이라는 큰 변경사항에도 기존 코드의 변경은 최소화할 수 있습니다. 하지만 매퍼 계층을 추가하면 복잡도가 증가하기 때문에 필요할 때만 추가하는 것이 좋습니다.
결론
관심사 분리는 코드의 유지보수성과 확장성을 위해 중요한 원칙입니다. 하지만 이를 적용하는 방법은 프로젝트와 팀의 상황에 따라 달라질 수 있습니다. 실용적인 기준을 바탕으로 복잡도를 줄이고, 변경에 유연하게 대응할 수 있는 구조를 설계하는 것이 중요합니다. 그렇게 함으로써 협업과 코드 품질 모두를 향상시킬 수 있을 것입니다.