자바스크립트 퀴즈북 리마인드 Day 10: 함수는 값이고 경계다.
고차 함수, 순수 함수, 부수 효과, async 함수의 Promise 반환을 코드 리뷰에서 어떻게 읽을지 정리합니다.
오늘의 질문: “함수를 인자로 넘기는 코드가 항상 더 함수형이고 안전할까?”
function withLoading(run) {
setLoading(true);
return run().finally(() => {
setLoading(false);
});
}
withLoading(() => fetchUser(userId));겉으로는 단순한 고차 함수입니다. 그런데 리뷰할 때는 한 가지를 더 봐야 합니다.
run은 값을 계산하는 함수일까, 아니면 네트워크와 UI 상태를 건드리는 부수 효과 묶음일까?함수는 자바스크립트에서 값입니다. 변수에 담고, 인자로 넘기고, 반환할 수 있습니다. 그래서 함수는 추상화를 만들기 쉽지만, 동시에 부수 효과를 예쁘게 숨기기도 쉽습니다.
함수로 감싸는 것 자체는 설계가 아닙니다. 좋은 함수는 호출자가 무엇을 넣고 무엇을 받는지, 그리고 어디에서 부수 효과가 생기는지 설명할 수 있어야 합니다.
function getDisplayName(user) {
console.log("render user", user.id);
localStorage.setItem("last-user", user.id);
return `${user.name} (${user.role})`;
}이 함수는 이름만 보면 문자열을 만드는 순수 계산처럼 보입니다. 하지만 로그와 storage write가 섞여 있습니다. 테스트에서는 문자열만 검증하고 넘어가도, 실제 앱에서는 렌더마다 storage가 갱신되는 문제가 생길 수 있습니다.
리뷰에서는 먼저 이렇게 나눕니다.
function formatDisplayName(user) {
return `${user.name} (${user.role})`;
}
function recordUserView(user) {
console.log("render user", user.id);
localStorage.setItem("last-user", user.id);
}순수 계산과 부수 효과를 분리하면 함수의 역할이 작아지고, 테스트도 명확해집니다.
고차 함수는 함수를 받거나 함수를 돌려주는 함수입니다. 중요한 점은 “멋진 문법”이 아니라 반복되는 관계를 드러내는 데 있습니다.
const byStatus = (status) => (item) => item.status === status;
const visibleTodos = todos.filter(byStatus("visible"));
const archivedTodos = todos.filter(byStatus("archived"));여기서는 status 조건을 만드는 반복 규칙이 드러납니다. 반대로 모든 코드를 콜백으로 감싸서 흐름을 끊는다면 오히려 읽기 어려워집니다.
run(() => validate(() => submit(() => notify())));이런 코드는 “단계가 있다”는 사실은 보이지만, 각 단계의 입력과 실패 처리가 흐려집니다. 고차 함수는 흐름을 감추는 장식이 아니라, 반복되는 호출 규칙을 이름 붙이는 도구로 쓰는 편이 안전합니다.
async function은 항상 Promise를 반환합니다.
async function loadUser(id) {
return fetch(`/api/users/${id}`).then((r) => r.json());
}
const user = loadUser("1");여기서 user는 실제 사용자 객체가 아니라 Promise입니다. 간단하지만 리뷰에서 자주 보이는 버그입니다. 특히 상태 업데이트와 섞이면 문제가 커집니다.
function renderProfile(id) {
const user = loadUser(id);
return `<h1>${user.name}</h1>`;
}이 코드는 타입스크립트를 쓰면 잡히기 쉽지만, 자바스크립트 코드나 느슨한 경계에서는 그대로 들어올 수 있습니다. 리뷰 질문은 “이 함수가 동기 값을 반환하는가, 비동기 작업의 약속을 반환하는가?”입니다.
프론트엔드 코드에서는 완전히 순수한 앱을 만들 수 없습니다. fetch, DOM, storage, analytics, timer가 필요합니다. 다만 부수 효과를 어디에 둘지는 선택할 수 있습니다.
| 위치 | 좋은 신호 | 위험 신호 |
|---|---|---|
| 순수 계산 함수 | 입력만으로 결과가 결정됨 | 내부에서 storage, DOM, log를 만짐 |
| 이벤트 핸들러 | 부수 효과 시작점이 명확함 | 계산 로직까지 길게 섞임 |
| API wrapper | 네트워크/파싱/에러 모양이 드러남 | 호출자가 모르는 retry/cache가 숨음 |
| 고차 함수 | 반복되는 호출 규칙에 이름을 붙임 | callback nesting으로 흐름이 흐려짐 |
| async 함수 | Promise 반환과 실패 처리가 명확함 | 동기 값처럼 호출하거나 에러를 삼킴 |
핵심은 “부수 효과를 없애자”가 아닙니다. 부수 효과가 시작되는 위치를 코드에서 알아볼 수 있게 하자입니다.
함수 이름이 반환값과 부수 효과를 솔직하게 드러내는가? 값 계산 함수 안에 fetch, DOM, storage, analytics, timer가 섞이지 않았는가? 고차 함수가 반복 규칙을 줄였는가, 아니면 호출 흐름만 숨겼는가? async 함수 반환값을 동기 값처럼 사용하지 않았는가? Promise 실패 경로가 호출자에게 전달되는가, 내부에서 조용히 삼켜지는가? 테스트가 순수 계산만 보는지, 부수 효과 shell까지 필요한 만큼 검증하는지 분리되어 있는가?
함수는 작은 문법 조각이 아니라 아키텍처의 경계입니다. 값을 계산하는 core와 세상을 바꾸는 shell을 나눌수록 리뷰할 때 “무엇이 바뀌고 무엇이 계산되는지”가 선명해집니다.
"function getTitle(post) {\n localStorage.setItem('last-post', post.id);\n return post.title.trim();\nPost Q&A
자바스크립트 퀴즈북 리마인드 Day 10: 함수는 값이고 경계다 전체를 기준으로 질문과 피드백을 받아요.답을 본 뒤에는 이 내용을 댓글로 달아서 서징에게도 물어볼 수 있어요. 작성자가 직접 볼 수 있어요!
this 바인딩을 정의 위치가 아니라 호출 형태, 메서드 분리, arrow function, bind 기준으로 읽는 방법을 코드 리뷰 관점에서 정리합니다.