본문 바로가기

[IT/Programming]/HTML related

프론트엔드 관점에서의 SOLID 원칙

반응형
# 프론트엔드 관점에서의 SOLID 원칙 SOLID 원칙은 객체 지향 설계(Object-Oriented Design, OOD)에서 코드의 유지보수성과 확장성을 높이기 위해 제안된 5가지 설계 원칙입니다. 원래는 백엔드 개발과 소프트웨어 공학에서 주로 논의되었지만, 프론트엔드 개발에서도 특히 리액트(React)와 같은 컴포넌트 기반 프레임워크를 사용할 때 적용할 수 있습니다. 프론트엔드 관점에서 SOLID 원칙을 설명하고, 리액트와 같은 환경에서 어떻게 활용되는지 예시와 함께 풀어보겠습니다. ## PH
  • 2025-03-27 : First posting.
## TOC ## 단일 책임 원칙 (Single Responsibility Principle, SRP) 정의: 한 클래스(또는 컴포넌트)는 단 하나의 책임만 가져야 하며, 그 책임이 변경될 이유도 단 하나여야 한다. 프론트엔드에서의 의미: 컴포넌트가 UI 렌더링, 데이터 fetching, 상태 관리 등 여러 역할을 동시에 수행하지 않도록 분리해야 한다. 예시: ```[.linenums.lang-jsx] // SRP 위반: UI 렌더링과 데이터 fetching을 동시에 수행 function UserProfile() { const [user, setUser] = useState(null); useEffect(() => { fetch('/api/user') .then(res => res.json()) .then(data => setUser(data)); }, []); return <div>{user ? user.name : 'Loading...'}</div>; } // SRP 준수: 데이터 fetching과 UI 렌더링 분리 function useUserData() { const [user, setUser] = useState(null); useEffect(() => { fetch('/api/user') .then(res => res.json()) .then(data => setUser(data)); }, []); return user; } function UserProfile() { const user = useUserData(); return <div>{user ? user.name : 'Loading...'}</div>; } ```/ 장점: 컴포넌트가 단일 책임을 가지면 재사용성이 높아지고, 테스트와 디버깅이 쉬워진다. ## 개방-폐쇄 원칙 (Open/Closed Principle, OCP) 정의: 소프트웨어 개체(클래스, 모듈, 함수 등)는 확장에 열려 있어야 하고, 수정에는 닫혀 있어야 한다. 프론트엔드에서의 의미: 기존 컴포넌트를 수정하지 않고도 새로운 기능을 추가할 수 있어야 한다. props나 컴포지션을 활용해 확장성을 확보한다. 예시: ```[.linenums.lang-jsx] // OCP 위반: 버튼 스타일을 추가하려면 컴포넌트 수정 필요 function Button() { return <button style={{ background: 'blue', color: 'white' }}>Click</button>; } // OCP 준수: props로 스타일 확장 가능 function Button({ style, children }) { return <button style={{ background: 'blue', color: 'white', ...style }}>{children}</button>; } // 사용 <Button style={{ borderRadius: '5px' }}>Click Me</Button> ```/ 장점: 새로운 요구사항(예: 다른 스타일의 버튼)이 생겨도 기존 코드를 수정하지 않고 확장 가능. ## 리스코프 치환 원칙 (Liskov Substitution Principle, LSP) 정의: 자식 클래스는 부모 클래스를 대체할 수 있어야 하며, 프로그램의 동작이 변하지 않아야 한다. 프론트엔드에서의 의미: 컴포넌트의 계층 구조나 인터페이스를 사용할 때, 하위 컴포넌트가 상위 컴포넌트의 역할을 문제없이 수행할 수 있어야 한다. 예시: ```[.linenums.lang-jsx] // LSP 위반: 하위 컴포넌트가 상위의 기대를 어김 function BaseComponent({ data }) { return <div>{data.title}</div>; } function SpecialComponent({ data }) { if (!data) return null; // data가 없으면 렌더링 안 함 (기대와 다름) } // LSP 준수: 하위 컴포넌트가 상위와 동일한 동작 보장 function SpecialComponent({ data }) { return <div>{data.title} (Special)</div>; } function App() { const component = Math.random() > 0.5 ? BaseComponent : SpecialComponent; return React.createElement(component, { data: { title: 'Hello' } }); } ```/ 장점: 컴포넌트를 교체해도 일관된 동작을 보장해 코드 안정성이 높아진다. ## 인터페이스 분리 원칙 (Interface Segregation Principle, ISP) 정의: 클라이언트는 자신이 사용하지 않는 인터페이스에 의존하지 않아야 한다. 프론트엔드에서의 의미: 컴포넌트가 필요 없는 props나 기능을 강제로 받지 않도록 설계한다. 필요한 기능만 제공하는 작은 단위로 나눈다. 예시: ```[.linenums.lang-jsx] // ISP 위반: 불필요한 props까지 강제 전달 function BigComponent({ name, age, email, onClick }) { return <button onClick={onClick}>{name}</button>; } // ISP 준수: 필요한 props만 사용 function SimpleButton({ label, onClick }) { return <button onClick={onClick}>{label}</button>; } // 사용 <SimpleButton label="Click" onClick={() => console.log('clicked')} /> ```/ 장점: 컴포넌트가 불필요한 의존성을 갖지 않아 간결하고 유연해진다. ## 의존성 역전 원칙 (Dependency Inversion Principle, DIP) 정의: 고수준 모듈은 저수준 모듈에 의존하지 않아야 하며, 둘 다 추상화에 의존해야 한다. 프론트엔드에서의 의미: 컴포넌트가 구체적인 구현(예: 특정 API 호출)에 직접 의존하지 않고, 추상화된 인터페이스나 함수에 의존하도록 설계한다. 예시: ```[.linenums.lang-jsx] // DIP 위반: 컴포넌트가 구체적인 fetch에 의존 function UserList() { const [users, setUsers] = useState([]); useEffect(() => { fetch('/api/users').then(res => res.json()).then(setUsers); }, []); return <ul>{users.map(user => <li>{user.name}</li>)}</ul>; } // DIP 준수: 데이터 fetching을 추상화에 의존 function useData(fetchFn) { const [data, setData] = useState([]); useEffect(() => { fetchFn().then(setData); }, [fetchFn]); return data; } function UserList({ fetchUsers }) { const users = useData(fetchUsers); return <ul>{users.map(user => <li>{user.name}</li>)}</ul>; } // 사용 const fetchUsers = () => fetch('/api/users').then(res => res.json()); <UserList fetchUsers={fetchUsers} /> ```/ 장점: 데이터 소스를 변경(예: API에서 로컬 데이터로)해도 컴포넌트를 수정할 필요가 없어 유연성이 높아진다. ## 프론트엔드에서 SOLID 적용의 현실적 고려사항 컴포넌트 분리: SRP와 ISP를 준수하려면 컴포넌트를 작고 단일 목적으로 유지하는 것이 중요. 예를 들어, <Form><Input>을 분리. 훅 활용: useEffect, useState 같은 훅을 통해 로직을 분리하면 DIP와 SRP를 쉽게 적용 가능. 타입스크립트와 결합: TypeScript의 인터페이스를 사용하면 OCP와 LSP를 더 명확히 구현할 수 있음. 현실적 한계: 프론트엔드는 UI와 밀접하게 연관되어 있어 완벽한 객체 지향 설계보다 실용성이 우선될 때도 있음. 과도한 추상화는 코드 복잡성을 높일 수 있으니 균형이 필요. ## 결론 프론트엔드에서 SOLID 원칙을 적용하면 재사용성, 유지보수성, 확장성이 높은 코드를 작성할 수 있습니다. 리액트에서는 컴포넌트와 훅을 활용해 이 원칙을 자연스럽게 구현할 수 있으며, 특히 대규모 프로젝트에서 코드 구조를 체계적으로 관리하는 데 큰 도움이 됩니다. 다만, 프론트엔드의 특성상 UI와의 결합도를 완전히 없애기는 어렵기 때문에, 원칙을 맹목적으로 따르기보다는 프로젝트의 요구사항에 맞춰 적절히 조정하는 것이 중요합니다!
반응형