기본적인 객체 활용
// 개선된 방식
const STATUS_MESSAGES = {
loading: '데이터를 불러오는 중입니다...',
success: '성공적으로 처리되었습니다!',
error: '오류가 발생했습니다. 다시 시도해주세요.',
empty: '표시할 데이터가 없습니다.',
unauthorized: '권한이 없습니다.',
}
function getStatusMessage(status) {
return STATUS_MESSAGES[status] || '알 수 없는 상태입니다.'
}
모든 상태와 메시지가 한 곳에 모여있어 한눈에 파악되고, 새로운 상태 추가도 한 줄이면 됩니다. 객체 프로퍼티 접근은 O(1)이라 조건이 많아져도 성능에 영향이 없습니다.
함수를 값으로 넣기
단순 문자열 매핑을 넘어서 함수를 값으로 쓰면 더 복잡한 로직도 처리할 수 있습니다:
// 사용자 권한 처리 예시
const USER_PERMISSION_HANDLERS = {
admin: user => ({
...user,
canDelete: true,
canEdit: true,
canView: true,
canManageUsers: true,
}),
editor: user => ({
...user,
canDelete: false,
canEdit: true,
canView: true,
canManageUsers: false,
}),
viewer: user => ({
...user,
canDelete: false,
canEdit: false,
canView: true,
canManageUsers: false,
}),
guest: user => ({
...user,
canDelete: false,
canEdit: false,
canView: false,
canManageUsers: false,
}),
}
function getUserPermissions(user) {
const handler =
USER_PERMISSION_HANDLERS[user.role] || USER_PERMISSION_HANDLERS.guest
return handler(user)
}
// 사용 예시
const user = { name: 'John', role: 'editor', id: 123 }
const userWithPermissions = getUserPermissions(user)
console.log(userWithPermissions)
// { name: 'John', role: 'editor', id: 123, canDelete: false,
// canEdit: true, ... }
중첩 객체 활용
API 에러처럼 두 가지 키를 조합해야 할 때는 중첩 객체가 유용합니다:
const API_ERROR_HANDLERS = {
400: {
INVALID_REQUEST: '잘못된 요청입니다.',
MISSING_PARAMETER: '필수 매개변수가 누락되었습니다.',
VALIDATION_FAILED: '입력값 검증에 실패했습니다.',
},
401: {
UNAUTHORIZED: '인증이 필요합니다.',
TOKEN_EXPIRED: '토큰이 만료되었습니다.',
INVALID_TOKEN: '유효하지 않은 토큰입니다.',
},
403: {
FORBIDDEN: '접근 권한이 없습니다.',
INSUFFICIENT_PERMISSIONS: '권한이 부족합니다.',
},
404: {
NOT_FOUND: '요청한 리소스를 찾을 수 없습니다.',
ENDPOINT_NOT_EXISTS: '존재하지 않는 API 엔드포인트입니다.',
},
}
function getErrorMessage(statusCode, errorCode) {
const statusErrors = API_ERROR_HANDLERS[statusCode]
if (!statusErrors) return '알 수 없는 오류가 발생했습니다.'
return statusErrors[errorCode] || '처리되지 않은 오류입니다.'
}
// 사용 예시
console.log(getErrorMessage(401, 'TOKEN_EXPIRED'))
// '토큰이 만료되었습니다.'
실무 적용 예시
React 컴포넌트 렌더링 분기
// 기존 방식 - 복잡한 if-else
function renderContent(type, data) {
if (type === 'loading') {
return <LoadingSpinner />
}
if (type === 'empty') {
return <EmptyState message="데이터가 없습니다" />
}
if (type === 'error') {
return <ErrorMessage error={data} />
}
if (type === 'success') {
return <DataList items={data} />
}
return <div>알 수 없는 상태</div>
}
// 개선된 방식 - 객체 활용
const CONTENT_RENDERERS = {
loading: () => <LoadingSpinner />,
empty: () => <EmptyState message="데이터가 없습니다" />,
error: data => <ErrorMessage error={data} />,
success: data => <DataList items={data} />,
}
function renderContent(type, data) {
const renderer = CONTENT_RENDERERS[type]
return renderer ? renderer(data) : <div>알 수 없는 상태</div>
}
상태 머신 구현
const SHOPPING_CART_TRANSITIONS = {
empty: {
ADD_ITEM: 'has_items',
CLEAR: 'empty',
},
has_items: {
ADD_ITEM: 'has_items',
REMOVE_ITEM: (state, payload) => {
return state.items.length === 1 ? 'empty' : 'has_items'
},
CHECKOUT: 'checking_out',
CLEAR: 'empty',
},
checking_out: {
SUCCESS: 'order_completed',
FAILURE: 'has_items',
CANCEL: 'has_items',
},
order_completed: {
NEW_ORDER: 'empty',
VIEW_ORDER: 'order_completed',
},
}
function getNextState(currentState, action, payload = null) {
const transitions = SHOPPING_CART_TRANSITIONS[currentState]
if (!transitions) return currentState
const transition = transitions[action]
if (!transition) return currentState
return typeof transition === 'function'
? transition(currentState, payload)
: transition
}
폼 유효성 검사
const VALIDATION_RULES = {
email: {
required: value => (!value ? '이메일은 필수입니다.' : null),
format: value => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return !emailRegex.test(value) ? '올바른 이메일 형식이 아닙니다.' : null
},
},
password: {
required: value => (!value ? '비밀번호는 필수입니다.' : null),
minLength: value =>
value.length < 8 ? '비밀번호는 8자 이상이어야 합니다.' : null,
complexity: value => {
const hasNumber = /\d/.test(value)
const hasLetter = /[a-zA-Z]/.test(value)
return !hasNumber || !hasLetter
? '비밀번호는 숫자와 문자를 포함해야 합니다.'
: null
},
},
name: {
required: value => (!value?.trim() ? '이름은 필수입니다.' : null),
minLength: value =>
value.trim().length < 2 ? '이름은 2자 이상이어야 합니다.' : null,
},
}
function validateField(fieldName, value) {
const rules = VALIDATION_RULES[fieldName]
if (!rules) return null
for (const [ruleName, validator] of Object.entries(rules)) {
const error = validator(value)
if (error) return error
}
return null
}
다른 언어에서의 동일 패턴
Python 딕셔너리 활용
# Python에서의 구현
STATUS_HANDLERS = {
'loading': lambda: '로딩 중입니다...',
'success': lambda data: f'성공: {len(data)}개 항목',
'error': lambda error: f'오류 발생: {error.message}',
'empty': lambda: '데이터가 없습니다'
}
def handle_status(status, data=None):
handler = STATUS_HANDLERS.get(status)
return handler(data) if handler else '알 수 없는 상태'
TypeScript 타입 안전성과 함께
type StatusType = 'loading' | 'success' | 'error' | 'empty'
type StatusHandler<T> = (data?: T) => string
interface StatusHandlers {
loading: StatusHandler<never>
success: StatusHandler<any[]>
error: StatusHandler<Error>
empty: StatusHandler<never>
}
const STATUS_HANDLERS: StatusHandlers = {
loading: () => '로딩 중입니다...',
success: (data = []) => `성공: ${data.length}개 항목`,
error: error => `오류 발생: ${error?.message || '알 수 없는 오류'}`,
empty: () => '데이터가 없습니다',
}
function handleStatus<T>(status: StatusType, data?: T): string {
const handler = STATUS_HANDLERS[status]
return handler ? handler(data as any) : '알 수 없는 상태'
}
Java Map과 람다 활용
import java.util.Map;
import java.util.function.Function;
Map<String, Function<Object, String>> statusHandlers = Map.of(
"loading", data -> "로딩 중입니다...",
"success", data -> "성공적으로 처리되었습니다!",
"error", data -> "오류가 발생했습니다: " + data.toString(),
"empty", data -> "데이터가 없습니다"
);
public String getStatusMessage(String status, Object data) {
return statusHandlers
.getOrDefault(status, data -> "알 수 없는 상태입니다")
.apply(data);
}
언제 쓰면 좋을까?
조건이 3개 이상이고, 각 조건의 결과가 단순한 값이나 함수 호출 수준이고, 앞으로 조건이 더 추가될 것 같다면 객체 기반으로 바꾸는 게 낫습니다.
반면 조건이 두 개 이하거나, 조건들이 서로 의존적이거나, 판단 로직 자체가 복잡한 경우엔 그냥 if-else가 더 읽기 편할 수 있습니다:
// 이런 경우에는 if-else가 더 적합
function calculateDiscount(user, item, season) {
if (
user.membership === 'premium' &&
item.category === 'electronics' &&
season === 'holiday'
) {
// 복잡한 할인 계산 로직
return calculateComplexDiscount(user, item, season)
}
if (user.firstPurchase && item.price > 100000) {
// 첫 구매 할인 로직
return calculateFirstPurchaseDiscount(item)
}
return 0
}
이 패턴이 익숙해지면 Strategy Pattern, State Pattern 같은 디자인 패턴들이 사실 같은 아이디어의 연장선임을 느낄 수 있습니다.
댓글