추상화 벽이란?
추상화 벽(abstraction barrier)은 코드의 한 계층에서 다른 계층으로의 의존성을 최소화하기 위해 설계된 경계다. 추상화 벽은 내부 구현 세부사항을 숨기고, 명확한 인터페이스만을 통해 다른 계층과 상호작용하도록 한다. 이를 통해 코드는 더 읽기 쉬워지고, 유지보수와 확장이 용이해진다.
예시
React 컴포넌트에서 API 호출 로직을 분리한다고 가정해보자. UI 로직과 API 호출 간에는 추상화 벽이 존재해야 한다. 추상화 벽은 API 호출 인터페이스를 통해 컴포넌트가 API의 세부사항에 의존하지 않도록 보장한다.
코드를 모듈화하기 위해 추상화 벽을 만드는 방법
문제 상황
React 애플리케이션에서 API 데이터를 가져와 목록을 렌더링하는 컴포넌트를 작성해야 한다고 가정해보자. 기존의 코드에서는 API 호출, 데이터 변환, 그리고 UI 렌더링 로직이 하나의 컴포넌트에 모두 포함되어 있다.
개선된 코드
추상화 벽을 만들기 위해 API 호출 로직을 별도의 모듈로 분리한다.
API 호출 계층
export async function fetchUsers() {
const response = await fetch('https://api.example.com/users');
const data = await response.json();
return data;
}
JavaScript
복사
데이터 변환 계층
export function transformUserData(rawData) {
return rawData.map(user => ({
id: user.id,
name: user.name.toUpperCase(), // 데이터 변환 예시
age: user.age || 'Unknown', // 기본값 처리
}));
}
JavaScript
복사
React 컴포넌트
import React, { useEffect, useState } from 'react';
import { fetchUsers } from './apiClient';
import { transformUserData } from './dataTransform';
function UserList() {
const [users, setUsers] = useState([]);
useEffect(() => {
async function loadUsers() {
const rawData = await fetchUsers();
const transformedData = transformUserData(rawData);
setUsers(transformedData);
}
loadUsers();
}, []);
return (
<ul>
{users.map(user => (
<li key={user.id}>
{user.name} - Age: {user.age}
</li>
))}
</ul>
);
}
export default UserList;
JavaScript
복사
이 접근 방식은 API 호출 로직과 UI 로직 간에 명확한 추상화 벽을 제공한다.
•
추상화 벽: fetchUsers와 transformUserData를 통해 데이터 가져오기와 변환을 분리하여, API나 데이터 구조가 변경되더라도 UI 컴포넌트는 영향을 받지 않는다.
•
순수 함수: transformUserData는 입력에 의존하고 출력이 결정적이므로 테스트가 용이하다.
•
불변성: 데이터 변환 로직은 불변성을 유지한다.
좋은 인터페이스란 무엇이고, 어떻게 찾는가?
좋은 인터페이스의 특징
1.
간결성: 필요한 기능만 노출한다.
2.
일관성: 함수나 메서드의 이름과 동작이 예상 가능해야 한다.
3.
구현 독립성: 내부 구현의 변경이 외부에 영향을 주지 않아야 한다.
4.
확장성: 새로운 요구사항을 쉽게 추가할 수 있어야 한다.
좋은 인터페이스를 설계하는 방법
•
사용자 관점에서 생각하기: 인터페이스를 사용하는 개발자가 어떤 정보를 필요로 하고, 어떤 작업을 수행하려고 하는지 고려한다.
•
작은 단위로 시작하기: 필요한 최소한의 기능부터 설계하고, 이후 요구사항에 따라 확장한다.
•
명세 작성하기: 인터페이스의 입력, 출력, 동작을 명확히 정의한다.
예시: 컴포넌트 상태 관리 인터페이스 설계
나쁜 설계
function useCustomHook(initialState, reducer, actions) {
// 복잡한 로직
}
JavaScript
복사
너무 많은 매개변수로 인해 사용자가 실수할 가능성이 높다.
좋은 설계
function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue);
const increment = () => setCount(prev => prev + 1);
const decrement = () => setCount(prev => prev - 1);
return { count, increment, decrement };
}
JavaScript
복사
간결하고, 기본값을 제공하며, 필요한 기능만 노출한다.
계층형 설계가 유지보수, 테스트, 재사용에 도움이 되는 이유
유지보수성 향상
추상화 벽은 코드 변경의 영향을 최소화한다. 예를 들어, API가 변경되더라도 API 호출 계층만 수정하면 된다.
테스트 용이성
각 계층을 독립적으로 테스트할 수 있다. API 호출 계층을 모의(Mock) 객체로 대체하여 컴포넌트를 테스트할 수 있다.
재사용성 증가
분리된 계층은 다른 프로젝트나 모듈에서도 재사용할 수 있다. 예를 들어, API 호출 계층은 동일한 API를 사용하는 다른 컴포넌트에서도 사용할 수 있다.
결론
계층형 설계와 추상화 벽은 복잡한 시스템을 단순화하고, 유지보수성과 확장성을 높이는 핵심적인 원칙이다. 이를 통해 개발자는 코드의 복잡성을 효과적으로 관리하고, 더 나은 품질의 소프트웨어를 개발할 수 있다. 인터페이스 설계에 신중을 기하고, 계층 간의 의존성을 최소화하는 노력을 기울이는 것이 중요하다.