프론트엔드 개발, 특히 React와 같은 라이브러리에서 코드의 관리성을 높이고 유지보수를 용이하게 하는 핵심 요소 중 하나는 '함수의 적절한 분리'다. 이는 코드의 재사용성을 높이고, 가독성을 향상시키며, 버그 발생 가능성을 줄이는 데 기여한다.
개발에서 함수가 잘 분리되어있다는 것은 곧 코드의 이해가 쉽고 유지보수가 수월해진다는 것이다. 복잡한 로직을 수행하는 함수를 작은 단위로 나누는 것은 가독성을 향상시킨다.
예를 들어, 사용자의 입력을 검증하고 데이터베이스에 저장하는 함수가 있다고 가정하자. 이 함수를 두 부분으로 나눌 수 있다. 입력 검증(validateInput)과 데이터베이스 저장(saveToDatabase). 이렇게 분리하면 각 함수의 목적이 명확해지고, 테스트가 용이해진다.
함수 분리를 이해하는 데 있어 '순수 함수'와 '부수 효과'라는 개념을 먼저 알아야한다. 순수 함수는 입력에 따라 항상 같은 출력을 반환하며 외부 상태를 변경하지 않는 함수다. 반면, 부수 효과를 가진 함수는 외부 상태에 영향을 주며, 때로는 예측 불가한 결과를 반환할 수 있다.
순수 함수의 예로는 다음과 같은 간단한 덧셈 함수가 있다.
function add(a, b) {
return a + b;
}
이 함수는 항상 동일한 입력에 대해 동일한 출력을 반환한다. 반면, 다음과 같은 외부 API 호출 함수는 순수 함수가 아니다.
async function fetchUserData(userId) {
const response = await fetch(`https://api.com/users/${userId}`);
return response.json();
}
이 함수는 네트워크 상태나 외부 API의 상태에 따라 결과가 달라질 수 있다.
함수를 분리하는 구체적인 방법으로는 '데이터', '계산', '액션'을 구분하는 것이 있다. '데이터'는 이벤트에 대한 사실이나 값을 의미하고, '계산'은 입력으로부터 출력을 도출하는 순수 함수다. '액션'은 외부와의 소통을 담당하며 부수 효과를 일으키는 함수다.
'데이터'로는 사용자의 입력 값을 들 수 있다.
'계산'의 예로는 아래과 같은 입력 값의 유효성을 검사하는 함수가 있다.
function isValidEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
'액션'으로는 유효한 데이터를 바탕으로 서버에 데이터를 저장하는 함수를 예로 들 수 있다.
async function saveUser(email) {
if (!isValidEmail(email)) {
throw new Error("Invalid email");
}
await fetch('https://api.example.com/users', {
method: 'POST',
body: JSON.stringify({ email }),
});
}
예를 들어, 투두리스트 컴포넌트에서 '할 일 추가', '완료 상태 토글'과 같은 기능을 구현할 때, 이들을 '액션'으로 구분하고, 할 일 목록 필터링이나 검색과 같은 기능을 '계산'으로 분리한다.
할 일을 추가하는 함수는 '액션'으로 분류되며, 이 함수는 내부 상태를 변경한다.
function addTodo(todos, todo) {
return [...todos, todo];
}
반면, 특정 조건에 따라 할 일 목록을 필터링하는 함수는 '계산'으로 분류되며, 이 함수는 어떤 외부 상태도 변경하지 않고, 입력된 데이터에 기반하여 새로운 데이터를 생성한다.
function filterTodos(todos, filter) {
return todos.filter(todo => todo.status === filter);
}
함수를 효과적으로 분리하는 것은 프론트엔드 개발에서 매우 중요하다. 순수 함수와 부수 효과를 이해하고, 데이터, 계산, 액션을 명확히 구분함으로써, 개발자는 보다 유지보수가 쉽고, 오류가 적은 코드를 작성할 수 있다. 이러한 접근은 코드의 품질을 높이고, 개발자의 생산성을 향상시킨다.