-
개발자를 위한 수학 마스터 3회차: 함수와 알고리즘 - 수학 함수와 프로그래밍 함수[프로그램]/Maths 2025. 9. 11. 14:00728x90

"매일 작성하는 함수, 수학적 원리를 알면 코드가 달라집니다!"
"드모르간 법칙 적용했더니 코드리뷰에서 '이해하기 쉽다'는 피드백을 받았어요!"
"복잡한 if문이 한눈에 들어오니까 버그도 줄어든 것 같아요."오늘은 개발자가 가장 자주 사용하는 함수(Function)에 대해 알아보겠습니다.
“함수는 그냥 코드 묶어놓은 거 아닌가?”라고 생각하시는 분들을 위해 준비했어요.
수학적 함수 개념을 이해하면 더 안전하고 예측 가능한 코드를 작성할 수 있습니다.
함수, 정말 제대로 알고 쓰고 계신가요?
실제 코드리뷰에서 발견한 문제
// 😵 이 함수는 언제 어떤 결과를 낼지 예측하기 어려워요 let processCount = 0; let lastProcessedUser = null; function processUser(user) { processCount++; // 전역 변수 변경 lastProcessedUser = user; // 전역 변수 변경 if (Math.random() > 0.1) { // 랜덤한 결과 console.log(`Processing ${user.name}...`); // 부수 효과 // 때때로 외부 API 호출 (브라우저 환경 예시) if (user.needsValidation) { fetch('/api/validate', { method: 'POST', body: JSON.stringify(user) }); } return user.name.toUpperCase(); } return user.name; // 때로는 원본, 때로는 대문자 } // 같은 입력인데 다른 결과가...? 🤯 console.log(processUser({ name: "김철수", needsValidation: true })); console.log(processUser({ name: "김철수", needsValidation: true }));- 같은 입력에 대해 다른 결과가 나올 수 있음
- 전역 변수를 변경하여 예측 불가능한 부수 효과 발생
- 외부 시스템에 의존하여 테스트하기 어려움
- 함수의 책임이 너무 많아서 재사용하기 어려움
이 문제들을 수학이 어떻게 해결해줄까요?
수학에서 함수는 매우 엄격한 규칙을 가지고 있습니다. 이 규칙들을 프로그래밍에 적용하면 위 문제들이 자연스럽게 해결돼요!
수학 함수의 핵심 개념
1) 함수의 정의: f(x) = y
- 정의역(Domain): 입력 가능한 값들의 집합
- 공역(Codomain): 이론적으로 출력될 수 있는 값들의 집합
- 치역(Range/Image): 실제 출력되는 값들의 집합
- 함수 관계: 정의역의 각 원소가 치역의 정확히 하나의 원소와 대응
※ 본 글에서는 이해를 돕기 위해 “치역 = 실제 결과값의 범위”로 설명합니다.
function square(x) { // 정의역: 모든 숫자 // 치역: 0 이상의 숫자 // 관계: 하나의 입력 → 하나의 출력 (항상 동일) return x * x; } console.log(square(3)); // 항상 92) 순수함수 (Pure Function)
// ✅ 순수함수의 특징들 function addTax(price, taxRate) { // 1. 동일한 입력 → 동일한 출력 (결정적) // 2. 외부 상태 변경 없음 (부수 효과 없음) // 3. 외부 상태에 의존하지 않음 return price * (1 + taxRate); } console.log(addTax(1000, 0.1)); // 1100// ❌ 순수함수가 아닌 예시 let globalDiscount = 0.1; function addTaxWithDiscount(price, taxRate) { globalDiscount += 0.01; // 외부 상태 변경 - 부수 효과! return price * (1 + taxRate - globalDiscount); // 외부 상태 의존! }함수의 성질별 분류와 프로그래밍 응용
1) 단사함수 (Injective)
function createUserId(email) { let hash = 0; for (let i = 0; i < email.length; i++) { hash = ((hash << 5) - hash + email.charCodeAt(i)) & 0xffffffff; } return Math.abs(hash).toString(36); }2) 전사함수 (Surjective)
function scoreToGrade(score) { if (score >= 90) return 'A'; if (score >= 80) return 'B'; if (score >= 70) return 'C'; if (score >= 60) return 'D'; return 'F'; }3) 전단사함수 (Bijective)
function simpleEncrypt(text, shift = 3) { return text.split('').map(char => { if (char.match(/[a-z]/)) { return String.fromCharCode((char.charCodeAt(0) - 97 + shift) % 26 + 97); } return char; }).join(''); } function simpleDecrypt(encryptedText, shift = 3) { return encryptedText.split('').map(char => { if (char.match(/[a-z]/)) { return String.fromCharCode((char.charCodeAt(0) - 97 - shift + 26) % 26 + 97); } return char; }).join(''); } const original = "hello world"; const encrypted = simpleEncrypt(original); const decrypted = simpleDecrypt(encrypted); console.log(original); // "hello world" console.log(encrypted); // "khoor zruog" console.log(decrypted); // "hello world" - 완벽하게 복원!함수 합성 (Function Composition)
const trim = str => str.trim(); const toLowerCase = str => str.toLowerCase(); const removeSpaces = str => str.replace(/\s+/g, ''); const addPrefix = prefix => str => prefix + str; // 합성을 위한 헬퍼 const compose = (...fns) => (v) => fns.reduceRight((acc, fn) => fn(acc), v); const processUsername = compose( addPrefix('user_'), removeSpaces, toLowerCase, trim ); console.log(processUsername(" John Doe Smith ")); // "user_johndoesmith"Map-Reduce 패턴 (실무 활용)
const calculateSubtotal = items => items.reduce((sum, item) => sum + (item.price * item.qty), 0); const calculateTotal = (subtotal, taxRate) => subtotal * (1 + taxRate); const processOrder = order => { const subtotal = calculateSubtotal(order.items); // ✅ 중복 계산 제거 return { id: order.id, subtotal, tax: order.tax, total: calculateTotal(subtotal, order.tax) }; };함수의 부수효과와 불변성
부수효과가 많은 코드의 문제점
아래 예시는 브라우저 환경 기준입니다. Node.js 환경에서는 localStorage가 없으므로 대체 스토리지를 사용해야 합니다.
// ❌ 예측하기 어려운 코드 (부수효과 많음) let globalConfig = { theme: 'light', language: 'ko' }; let userSessions = []; function loginUser(username, password) { // 1. 전역 상태 변경 globalConfig.lastLoginTime = new Date(); // 2. 배열 직접 수정 userSessions.push({ username, loginTime: new Date() }); // 3. 외부 의존성 console.log(`${username} 로그인 성공`); localStorage.setItem('currentUser', username); // 4. 네트워크 호출 fetch('/api/analytics', { method: 'POST', body: JSON.stringify({ event: 'login', user: username }) }); return { success: true, user: username }; }함수형 접근으로 개선하기
// ✅ 순수함수로 나누고, 부수효과를 가장자리로 분리 const updateConfig = (config, updates) => ({ ...config, ...updates }); const addUserSession = (sessions, newSession) => [...sessions, newSession]; const createLoginSession = (username) => ({ username, loginTime: new Date(), sessionId: Math.random().toString(36).slice(2, 11) }); // (부수효과) 실제 입출력은 별도 함수로 const logLoginAttempt = (username) => console.log(`${username} 로그인 시도`); const saveToStorage = (key, value) => localStorage.setItem(key, JSON.stringify(value)); const sendAnalytics = (event, data) => fetch('/api/analytics', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ event, ...data }) }); // (핵심 비즈니스 로직) 순수함수 function processLogin(currentConfig, currentSessions, username, password) { const isValidUser = Boolean(username && password); if (!isValidUser) { return { success: false, error: '잘못된 사용자 정보', newConfig: currentConfig, newSessions: currentSessions }; } const newConfig = updateConfig(currentConfig, { lastLoginTime: new Date(), lastUser: username }); const newSession = createLoginSession(username); const newSessions = addUserSession(currentSessions, newSession); return { success: true, user: username, session: newSession, newConfig, newSessions }; } function executeLoginSideEffects(username, session) { logLoginAttempt(username); saveToStorage('currentUser', { username, sessionId: session.sessionId }); sendAnalytics('login', { user: username, timestamp: session.loginTime }); }연습 문제: 직접 도전해보세요!
🎯 초급
힌트: 상태는 외부에서 관리하고, 로깅은 별도 함수로 분리let counter = 0; function incrementAndLog() { counter++; console.log(`현재 카운터: ${counter}`); return counter; }🎯 중급
힌트: map 으로 불변 업데이트, Date 생성은 호출 시점 한 번만const users = [ { id: 1, name: '김철수', email: 'kim@test.com', active: true }, { id: 2, name: '이영희', email: 'lee@test.com', active: false }, { id: 3, name: '박민수', email: 'park@test.com', active: true } ]; function updateUserStatus(userId, newStatus) { for (let i = 0; i < users.length; i++) { if (users[i].id === userId) { users[i].active = newStatus; users[i].lastModified = new Date(); break; } } return users; }🎯 고급
힌트: trim, split, toLowerCase, 함수 합성을 단계별로 적용const rawData = [ " JOHN DOE, 25, DEVELOPER, john@company.com ", "jane smith, 30, designer, JANE@DESIGN.CO", " Bob Johnson, 35, manager, bob.johnson@corp.org " ]; // 목표: // [ // { name: "John Doe", age: 25, role: "developer", email: "john@company.com", domain: "company.com" }, // { name: "Jane Smith", age: 30, role: "designer", email: "jane@design.co", domain: "design.co" }, // { name: "Bob Johnson", age: 35, role: "manager", email: "bob.johnson@corp.org", domain: "corp.org" } // ]실무 적용 가이드라인
- 단일 책임 원칙 — 하나의 함수는 하나의 일만
- 순수함수 우선 — 같은 입력 → 같은 출력, 부수효과 최소화
- 작고 명확한 함수 — 10~15줄, 매개변수 ≤ 3
- 불변성 유지 — 기존 데이터 변경 금지, 새 객체/배열 반환
실습 프로젝트: 해시 함수 구현
직접 실행해보고 싶다면, 아래 데모 페이지에서 해시 함수의 결정성과 단방향성을 확인할 수 있습니다 👇
마무리 및 다음 예고
핵심 포인트 정리
- 순수함수: 같은 입력 → 같은 출력
- 함수의 성질: 단사, 전사, 전단사
- 함수 합성: 작은 함수들을 조합
- 불변성: 기존 데이터를 변경하지 않음
🗓️ 다음 회차 예고 — “이산수학 기초 - 개발자가 알아야 할 핵심 개념”
- 순열과 조합: 패스워드 강도 계산
- 점화식과 동적 프로그래밍: 피보나치, 최적화 문제
- 수학적 귀납법: 재귀 함수의 정확성 증명
- 비둘기집 원리: 해시 충돌과 메모리
*일부 코드(예: fetch, localStorage)는 브라우저 환경 기준입니다. Node.js에서는 대체 수단을 사용하세요.
728x90