DOCS/_archive/docs/guide/functional-programing/함수형_적용_가이드라인.md
happybell80 9f2b6517af docs: 함수형 프로그래밍 가이드라인에 로컬/서버 테스트 전략 추가
- 개발 환경의 현실적 제약 설명
- 로컬 테스트 가능 영역 vs 서버 필요 영역
- 올바른 개발 워크플로우 정의
- 분산 아키텍처 설계 의도 명시
2025-08-07 13:15:25 +09:00

14 KiB

tags, date
tags date
함수형프로그래밍, 적용가이드, 순수함수, 실전전략, 로빙시스템 2025-07-04

함수형 프로그래밍 적용 가이드라인

요약

로빙 프로젝트에서 함수형 프로그래밍을 실전 적용할 때의 구체적인 기준과 가이드라인을 제시합니다. 각 구성요소별 순수 함수 가능성을 수치화하여 실용적인 적용 전략을 제공합니다.


1. 함수형 적용 원칙

기본 철학

  • 점진적 도입: 한 번에 모든 것을 바꾸지 않고 단계별 전환
  • 실용적 균형: 이론적 완성도보다 팀 생산성과 코드 안정성 우선
  • 명확한 경계: 순수 함수 영역과 부작용 영역의 명확한 분리

전체 프로젝트 권장 비율

함수형 40% : 명령형 60%
영역 함수형 권장 비율 이유
데이터 변환 로직 80-100% 가독성, 재사용성, 테스트 용이
비즈니스 로직 50-70% 추상화 효과, 단 과도하면 이해도 저하
상태 변경/IO 0-20% 명령형이 더 명확하고 디버깅 쉬움

2. 로컬 개발과 서버 테스트 전략

2.1 개발 환경의 현실적 제약

로빙 시스템은 분산 아키텍처로 설계되어 로컬에서 전체 실행이 불가능합니다:

구분 로컬 환경 서버 환경
순수 함수 테스트 가능 테스트 가능
외부 서비스 접근 불가 모두 연결
데이터베이스 서버 데이터 실제 데이터
스킬 서비스 포트 8015 없음 skill-embedding 실행

2.2 함수형 프로그래밍의 장점 활용

로컬에서 테스트 가능한 영역:

# ✅ 순수 함수들 - 외부 의존성 없음
from app.llm.mistral import create_intent_analysis_prompt
from app.core.memory.scoring import calculate_time_decay
from app.core.emotion import EmotionState

# 모듈별 단위 테스트
prompt = create_intent_analysis_prompt("안녕하세요")
decay = calculate_time_decay(3600)  # 1시간
emotion = EmotionState(valence=0.5, arousal=0.3)

서버에서만 테스트 가능한 영역:

# ❌ I/O 함수들 - 외부 서비스 필요
- ChromaDB 쿼리 (서버 persistent storage)
- skill-embedding:8015 (79MB ONNX 모델)
- Gemini/Mistral API (API  필요)
- Slack 웹훅 (실제 워크스페이스)

2.3 올바른 개발 워크플로우

graph LR
    A[로컬 개발] --> B[모듈 테스트]
    B --> C[Git Push]
    C --> D[Gitea Actions]
    D --> E[서버 배포]
    E --> F[통합 테스트]
  1. 로컬 개발: 코드 작성, 문법 검증, 모듈 테스트
  2. Push: Git 커밋, Gitea에 푸시
  3. 서버: 자동 배포, 통합 테스트, 실제 작동 확인

2.4 아키텍처 설계 의도

왜 이렇게 설계했는가?

  • 로빙은 가벼운 본체: 118MB 메모리만 사용
  • 스킬은 공유 서비스: 100개 로빙이 1개 스킬 서비스 공유
  • 확장성 우선: 로빙 수가 늘어도 스킬 서비스는 하나

이것이 바로 함수형 아키텍처의 핵심입니다:

  • 테스트 가능한 작은 단위
  • 외부 의존성 최소화
  • 명확한 경계 분리

3. 로빙 구성요소별 순수 함수 가능성 분석

2.1 스탯 시스템 (평균 60%)

스탯 순수 함수 가능성 함수형 구조화 가능성 주요 부작용 요소
연산 (Compute) 80% 90% 없음 (정량 계산)
공감 (Empathy) 70% 85% 감정 회신 시 상태 영향
기억 (Memory) 60% 80% 저장/삭제 I/O
통솔 (Leadership) 50% 75% 팀 상황·우선순위 반영
반응 (React) 40% 60% 실시간 이벤트 처리

구현 예시

# ✅ 순수 함수: 연산 스탯 계산
def calculate_compute_stat(interactions: List[Interaction], response_times: List[float]) -> int:
    """연산 능력 계산 - 순수 함수"""
    avg_response_time = sum(response_times) / len(response_times) if response_times else 5.0
    interaction_bonus = len(interactions) // 100
    
    if avg_response_time < 1.0:
        speed_bonus = 3
    elif avg_response_time < 3.0:
        speed_bonus = 1
    else:
        speed_bonus = 0
    
    return min(100, 5 + interaction_bonus + speed_bonus)

# 🔄 혼합 방식: 기억 스탯 (순수 + 부작용)
def calculate_memory_priority(content: str, user_context: Dict) -> float:
    """저장 우선도 계산 - 순수 함수"""
    keywords = ['중요', '기억', '저장', '프로젝트']
    priority = sum(1 for keyword in keywords if keyword in content)
    return min(1.0, priority * 0.3)

async def update_memory_stat(user_id: str, content: str, context: Dict) -> StatUpdateResult:
    """기억 스탯 업데이트 - 부작용 포함"""
    # 순수 계산
    priority = calculate_memory_priority(content, context)
    
    if priority > 0.6:
        # 부작용: DB 저장
        await save_memory(user_id, content, priority)
        stat_change = StatChange(memory=1, reason="중요한 기억 저장")
        return await update_user_stats(user_id, stat_change)
    
    return StatUpdateResult.success_result(await get_user_stats(user_id), StatChange())

2.2 스킬 시스템 (평균 77%)

스킬 순수 함수 가능성 함수형 구조화 가능성 주요 부작용 요소
Thread Digest 95% 100% 없음 (순수 언어 처리)
Action Extractor 90% 95% 없음 (추출 규칙 기반)
PDF Summarizer 85% 90% 파일 처리 오류
Emotion Tracker 75% 90% 감정 모델 차이
Meeting Transcriber 40% 60% 외부 STT API 의존

구현 예시

# ✅ 높은 순수성: Thread Digest
def extract_key_messages(messages: List[str], max_count: int = 5) -> List[str]:
    """중요 메시지 추출 - 순수 함수"""
    def calculate_importance_score(message: str) -> float:
        keywords = ['중요', '긴급', '결정', '마감', '회의']
        score = sum(1.0 for keyword in keywords if keyword in message)
        score += min(len(message) / 100, 2.0)  # 길이 보너스
        return score
    
    scored_messages = [(calculate_importance_score(msg), msg) for msg in messages]
    scored_messages.sort(key=lambda x: x[0], reverse=True)
    return [msg for score, msg in scored_messages[:max_count]]

def summarize_conversation(messages: List[str]) -> str:
    """대화 요약 생성 - 순수 함수"""
    key_messages = extract_key_messages(messages)
    combined_text = " ".join(key_messages)
    sentences = combined_text.split('.')
    return '. '.join(sentences[:3]) + '.'

# 🔄 오케스트레이터: 부작용 분리
async def process_thread_digest(thread_id: str, user_id: str) -> SkillExecutionResult:
    """스레드 요약 처리 - 부작용 포함"""
    try:
        # 1. 데이터 가져오기 (부작용)
        messages = await fetch_thread_messages(thread_id)
        
        # 2. 순수 계산
        summary = summarize_conversation(messages)
        actions = extract_action_items(messages)
        
        # 3. 결과 저장 (부작용)
        result = {'summary': summary, 'actions': actions}
        await save_digest_result(user_id, thread_id, result)
        
        return SkillExecutionResult.success_result(result)
    except Exception as e:
        return SkillExecutionResult.error_result(str(e))

2.3 아이템 시스템 (평균 32%)

아이템 순수 함수 가능성 함수형 구조화 가능성 주요 부작용 요소
ChromaDB 50% 60% 벡터 저장/검색 IO
Whisper STT 40% 50% 외부 API 호출
Notion API 30% 40% 문서 쓰기/업데이트
Gmail API 20% 30% API 호출, OAuth
Slack API 20% 30% 메시지 전송

구현 예시

# ❌ 낮은 순수성: 대부분 IO 중심
async def send_slack_message(channel: str, text: str) -> bool:
    """Slack 메시지 전송 - 명령형 적합"""
    try:
        response = await slack_client.chat_postMessage(
            channel=channel,
            text=text
        )
        return response["ok"]
    except Exception:
        return False

# 🔄 부분적 함수형화: 데이터 처리 부분만
def format_slack_message(summary: str, actions: List[str]) -> str:
    """Slack 메시지 포맷팅 - 순수 함수"""
    formatted_actions = '\n'.join(f"• {action}" for action in actions)
    return f"""
📝 **대화 요약**
{summary}

✅ **액션 아이템**
{formatted_actions}
    """.strip()

async def send_digest_to_slack(channel: str, summary: str, actions: List[str]) -> bool:
    """요약 전송 - 혼합 방식"""
    # 순수 함수: 메시지 포맷팅
    formatted_message = format_slack_message(summary, actions)
    
    # 부작용: 실제 전송
    return await send_slack_message(channel, formatted_message)

3. 핵심 모듈별 가이드라인

3.1 기억 모듈 (65% 순수)

# ✅ 순수 함수: 저장 우선도 판단
def calculate_memory_priority(content: str, keywords: List[str], user_context: Dict) -> float:
    """기억 저장 우선도 계산"""
    keyword_score = sum(1 for keyword in keywords if keyword in content.lower())
    length_score = min(len(content) / 1000, 1.0)
    context_score = user_context.get('importance_multiplier', 1.0)
    
    return min(1.0, (keyword_score * 0.4 + length_score * 0.3) * context_score)

# 🔄 부작용 분리: 실제 저장
async def store_memory_if_important(user_id: str, content: str, context: Dict) -> bool:
    priority = calculate_memory_priority(content, ['중요', '프로젝트'], context)
    
    if priority > 0.7:
        await save_to_chroma_db(user_id, content, priority)
        return True
    return False

3.2 감정 모듈 (70% 순수)

# ✅ 순수 함수: 감정 분석
def analyze_emotion_vector(text: str) -> Dict[str, float]:
    """감정 벡터 계산"""
    emotion_keywords = {
        'joy': ['기쁨', '행복', '좋아', '만족'],
        'anger': ['화나', '짜증', '분노', '불만'],
        'sadness': ['슬픔', '우울', '아쉬움'],
        'fear': ['걱정', '불안', '두려움']
    }
    
    emotion_scores = {}
    for emotion, keywords in emotion_keywords.items():
        score = sum(1 for keyword in keywords if keyword in text) / len(keywords)
        emotion_scores[emotion] = min(1.0, score)
    
    return emotion_scores

# 🔄 상태 반영: 감정 히스토리 업데이트
async def update_emotion_history(user_id: str, text: str) -> Dict[str, float]:
    current_emotions = analyze_emotion_vector(text)
    await save_emotion_log(user_id, current_emotions, datetime.now())
    return current_emotions

3.3 윤리 모듈 (60% 순수)

# ✅ 순수 함수: 윤리 위반 판단
def check_ethical_violations(content: str, policies: List[str]) -> List[str]:
    """윤리 정책 위반 검사"""
    violations = []
    
    sensitive_patterns = [
        (r'개인정보.*공유', '개인정보 보호 위반'),
        (r'비밀.*유출', '기밀 정보 유출'),
        (r'허위.*정보', '허위 정보 유포')
    ]
    
    for pattern, violation_type in sensitive_patterns:
        if re.search(pattern, content):
            violations.append(violation_type)
    
    return violations

# 🔄 정책 적용: 실제 차단 처리
async def enforce_ethical_policy(user_id: str, content: str) -> bool:
    violations = check_ethical_violations(content, await get_user_policies(user_id))
    
    if violations:
        await log_violation(user_id, violations, content)
        return False  # 차단
    return True  # 허용

4. 실전 적용 전략

4.1 함수형 우선 적용 영역

  1. 데이터 변환 스킬 (Thread Digest, Action Extractor)
  2. 계산 중심 스탯 (연산, 공감)
  3. 분석 모듈 (감정 분석, 윤리 검사)

4.2 명령형 유지 영역

  1. 외부 API 연동 (Slack, Gmail, Notion)
  2. 실시간 반응 (알림, 이벤트 처리)
  3. 상태 변경 (DB 저장, 설정 변경)

4.3 혼합 접근 패턴

# 권장 패턴: 순수 함수 + 오케스트레이터 분리
async def skill_orchestrator(input_data, user_id):
    """스킬 실행 오케스트레이터"""
    try:
        # 1. 순수 계산
        result = pure_skill_function(input_data)
        
        # 2. 부작용 처리
        await save_result(user_id, result)
        await update_stats(user_id, calculate_stat_change(result))
        await send_notification(user_id, format_response(result))
        
        return SkillExecutionResult.success_result(result)
    except Exception as e:
        return SkillExecutionResult.error_result(str(e))

5. 판단 기준 체크리스트

함수형 적용 가능 신호

  • map, filter, reduce로 로직이 간결해진다
  • 디버깅 없이도 결과를 바로 예측할 수 있다
  • 중간 단계가 없고 한 방향 흐름이다
  • 같은 입력에 항상 같은 출력이 나온다
  • 외부 상태에 의존하지 않는다

함수형 피해야 할 신호

  • 중간값을 계속 찍어봐야 한다
  • 사이드 이펙트가 많다 (파일, 네트워크 등)
  • 팀원이 "이게 뭐 하는 건데?"라고 묻는다
  • 시간이나 외부 상태에 따라 결과가 달라진다
  • 에러 처리가 복잡하다

6. 성공 지표

개발 생산성

  • 테스트 커버리지: 순수 함수 스킬 90% 이상
  • 버그 발생률: 함수형 적용 영역 50% 감소 목표
  • 코드 리뷰 시간: 순수 함수 부분 30% 단축

시스템 안정성

  • 에러 격리: 한 스킬의 실패가 전체 시스템에 영향 없음
  • 성능 유지: 함수형 적용 후에도 응답 시간 3초 이내 유지
  • 확장성: 새로운 스킬 추가 시 기존 코드 영향 최소화

결론

로빙의 함수형 프로그래밍 도입은 스킬 중심의 점진적 적용이 가장 효과적입니다. 순수성이 높은 스킬(77%)부터 시작하여, 계산 중심 스탯, 분석 모듈 순으로 확장하되, IO 중심의 아이템은 명령형을 유지하는 것이 실용적입니다.

핵심 원칙: "완벽한 함수형보다는 안정적이고 확장 가능한 혼합 아키텍처"

이를 통해 로빙은 예측 가능하고 테스트 가능한 디지털 존재로 성장할 수 있습니다.