18  기술 보고서와 문서화

중요코드에서 문서로

API 문서, 기술 보고서, 정부 제출 보고서까지 코드와 함께 자동 생성되는 살아있는 문서를 만들어봅시다.

18.1 Living Documentation의 힘

18.1.1 기존 기술 문서의 문제점

  • 낡은 문서: 코드는 업데이트되었지만 문서는 6개월 전 버전
  • 분산된 정보: API 명세, 사용 가이드, 예제가 각각 다른 곳에
  • 수동 업데이트: 개발자가 기억해서 문서를 수정해야 함
  • 형식 불일치: 팀원마다 다른 스타일의 문서

18.1.2 Document as Code 해결책

graph TD
    A[소스코드] --> B[자동 문서 추출]
    B --> C[쿼토 문서]
    C --> D[다중 출력]
    
    E[API 코드] --> F[OpenAPI 스펙]
    F --> C
    
    G[테스트 코드] --> H[예제 추출]
    H --> C
    
    I[GitHub Actions] --> J[자동 배포]
    D --> J
    
    D --> K[HTML 웹사이트]
    D --> L[PDF 보고서]
    D --> M[Word 문서]

18.2 API 문서 자동 생성

18.2.1 FastAPI 기반 자동 문서화

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Optional
import uvicorn

app = FastAPI(
    title="Text Classification API",
    description="Document as Code 패러다임을 적용한 텍스트 분류 API",
    version="1.0.0",
    contact={
        "name": "이광춘",
        "email": "kwangchun@r2bit.com",
        "url": "https://r2bit.com"
    },
    license_info={
        "name": "MIT License",
        "url": "https://opensource.org/licenses/MIT"
    }
)

class TextInput(BaseModel):
    """텍스트 분류를 위한 입력 모델
    
    Attributes:
        text: 분류할 텍스트 (최대 1000자)
        model: 사용할 모델명 (기본값: 'random_forest')
    """
    text: str
    model: Optional[str] = "random_forest"
    
    class Config:
        schema_extra = {
            "example": {
                "text": "이것은 분류할 텍스트 예시입니다.",
                "model": "random_forest"
            }
        }

class ClassificationResult(BaseModel):
    """분류 결과 모델
    
    Attributes:
        category: 예측된 카테고리
        confidence: 예측 신뢰도 (0-1)
        model_used: 사용된 모델명
        processing_time: 처리 시간 (초)
    """
    category: str
    confidence: float
    model_used: str
    processing_time: float

@app.post(
    "/classify",
    response_model=ClassificationResult,
    summary="텍스트 분류",
    description="입력된 텍스트를 사전 학습된 모델로 분류합니다."
)
async def classify_text(input_data: TextInput):
    """
    텍스트를 분류하여 카테고리를 예측합니다.
    
    - **text**: 분류할 텍스트 (필수)
    - **model**: 사용할 모델명 (선택, 기본값: random_forest)
    
    지원 모델:
    - `random_forest`: Random Forest 분류기
    - `logistic_regression`: 로지스틱 회귀
    - `transformer`: Transformer 기반 모델
    
    Returns:
        ClassificationResult: 분류 결과와 메타데이터
    """
    
    # 실제 분류 로직은 여기에 구현
    import time
    start_time = time.time()
    
    # 모의 분류 결과
    result = ClassificationResult(
        category="positive",
        confidence=0.85,
        model_used=input_data.model,
        processing_time=time.time() - start_time
    )
    
    return result

@app.get(
    "/models",
    response_model=List[str],
    summary="사용 가능한 모델 목록",
    description="API에서 사용 가능한 모든 분류 모델의 목록을 반환합니다."
)
async def list_models():
    """사용 가능한 분류 모델 목록을 반환합니다."""
    return ["random_forest", "logistic_regression", "transformer"]

18.2.2 자동 생성된 API 문서 통합

library(jsonlite)
library(knitr)
library(gt)

# OpenAPI 스펙 읽기
openapi_spec <- fromJSON("http://localhost:8000/openapi.json")

# API 엔드포인트 정보 추출
endpoints <- openapi_spec$paths

# 엔드포인트 테이블 생성
endpoint_info <- map_dfr(names(endpoints), function(path) {
  methods <- names(endpoints[[path]])
  
  map_dfr(methods, function(method) {
    info <- endpoints[[path]][[method]]
    
    tibble(
      path = path,
      method = toupper(method),
      summary = info$summary %||% "",
      description = str_trunc(info$description %||% "", 100)
    )
  })
})

endpoint_info %>%
  gt() %>%
  tab_header(
    title = "API 엔드포인트 목록",
    subtitle = "자동 생성된 API 문서"
  ) %>%
  cols_label(
    path = "경로",
    method = "메서드", 
    summary = "요약",
    description = "설명"
  ) %>%
  fmt_markdown(columns = everything()) %>%
  tab_style(
    style = cell_text(weight = "bold"),
    locations = cells_column_labels()
  )

18.3 정부 보고서 템플릿

18.3.1 한국 정부 보고서 표준 양식

---
title: "AI 기반 텍스트 분류 시스템 개발"
subtitle: "2024년도 디지털 혁신 과제 최종보고서"

# 정부 보고서 메타데이터
report_meta:
  project_name: "AI 기반 텍스트 분류 시스템 개발"
  project_code: "2024-DT-001"
  period: "2024년 3월 ~ 2024년 12월"
  budget: "50,000만원"
  
  # 참여기관
  institutions:
    lead: "공익법인 한국 R 사용자회"
    partners: 
      - "한국대학교 AI연구소"
      - "㈜데이터테크"
  
  # 연구진
  researchers:
    pm: "이광춘 (공익법인 한국 R 사용자회)"
    members:
      - "김연구 (한국대학교)"
      - "박개발 (㈜데이터테크)"

format:
  pdf:
    documentclass: article
    fontsize: 12pt
    geometry: 
      - left=3cm
      - right=2cm
      - top=2.5cm
      - bottom=2.5cm
    number-sections: true
    toc: true
    toc-depth: 3
    fig-pos: 'H'
    include-in-header:
      - text: |
          \usepackage{kotex}
          \usepackage{fancyhdr}
          \pagestyle{fancy}
          \fancyhead[L]{2024년도 디지털 혁신 과제}
          \fancyhead[R]{최종보고서}
          \fancyfoot[C]{\thepage}
  
  html:
    theme: cosmo
    css: styles/gov-report.css
    number-sections: true
    toc: true

lang: ko-KR
bibliography: references.bib
---

18.3.2 정부 보고서 본문 구조

# 연구개발 과제 개요

## 연구개발 목표

### 최종 목표
AI 기반 텍스트 분류 시스템을 개발하여 정부 문서 처리의 자동화와 효율성을 향상시킨다.

### 세부 목표
1. **기술적 목표**
   - 한국어 텍스트 분류 정확도 95% 이상 달성
   - 실시간 처리 성능 1,000건/분 이상
   - Document as Code 패러다임 적용

2. **활용적 목표**
   - 정부 민원 자동 분류 시스템 구축
   - 정책 문서 자동 태깅 시스템 개발
   - 공공 데이터 품질 향상

## 연구개발 필요성

### 기술적 필요성
현재 정부 기관에서 사용하는 문서 분류 시스템의 한계...

### 경제적 필요성  
수작업 문서 분류로 인한 연간 인건비 손실 규모...

### 사회적 필요성
시민 서비스 품질 향상과 행정 효율성 증대...

# 연구개발 내용 및 방법

## 연구개발 추진전략

### 단계별 추진 계획

gantt
    title 연구개발 추진 일정
    dateFormat  YYYY-MM-DD
    section 1단계
    요구사항 분석     :done, req, 2024-03-01, 2024-04-30
    기술 검토        :done, tech, 2024-04-01, 2024-05-31
    section 2단계  
    시스템 설계      :active, design, 2024-05-01, 2024-07-31
    프로토타입 개발   :proto, after design, 60d
    section 3단계
    시스템 구현      :impl, after proto, 90d
    성능 최적화      :opt, after impl, 30d
    section 4단계
    시험 운영        :test, after opt, 60d
    최종 평가        :eval, after test, 30d

18.3.3 기술 개발 방법론

Document as Code 적용

18.4 기업 기술 문서

18.4.1 소프트웨어 아키텍처 문서

import diagrams
from diagrams import Diagram, Cluster, Edge
from diagrams.aws.compute import ECS, Lambda
from diagrams.aws.database import RDS, ElastiCache
from diagrams.aws.network import ELB, CloudFront
from diagrams.aws.storage import S3

with Diagram("텍스트 분류 시스템 아키텍처", show=False, direction="TB"):
    
    with Cluster("사용자 인터페이스"):
        web_app = CloudFront("Web Application")
        
    with Cluster("API 레이어"):
        api_gateway = ELB("API Gateway")
        api_servers = [
            ECS("API Server 1"),
            ECS("API Server 2")
        ]
    
    with Cluster("비즈니스 로직"):
        ml_service = Lambda("ML Service")
        
    with Cluster("데이터 저장소"):
        database = RDS("PostgreSQL")
        cache = ElastiCache("Redis Cache")
        storage = S3("Model Storage")
    
    # 연결 관계
    web_app >> api_gateway >> api_servers
    api_servers >> ml_service
    ml_service >> [database, cache, storage]

18.4.2 코드 문서화

class TextClassifier:
    """텍스트 분류를 위한 메인 클래스
    
    이 클래스는 Document as Code 패러다임을 적용하여
    모델 훈련부터 예측까지의 전체 파이프라인을 관리합니다.
    
    Attributes:
        model_type (str): 사용할 모델 타입
        is_trained (bool): 모델 훈련 상태
        
    Example:
        >>> classifier = TextClassifier(model_type="random_forest")
        >>> classifier.train(X_train, y_train)
        >>> predictions = classifier.predict(X_test)
    """
    
    def __init__(self, model_type: str = "random_forest"):
        """분류기 초기화
        
        Args:
            model_type: 사용할 모델 타입. 
                      'random_forest', 'logistic_regression', 
                      'transformer' 중 선택
        """
        self.model_type = model_type
        self.is_trained = False
        self._model = None
        
    def train(self, X, y, **kwargs):
        """모델 훈련 메서드
        
        Args:
            X: 훈련 특성 데이터
            y: 훈련 라벨 데이터
            **kwargs: 모델별 추가 파라미터
            
        Returns:
            self: 메서드 체이닝을 위한 self 반환
            
        Raises:
            ValueError: 지원하지 않는 모델 타입인 경우
            
        Example:
            >>> classifier.train(X_train, y_train, n_estimators=100)
        """
        
        if self.model_type == "random_forest":
            from sklearn.ensemble import RandomForestClassifier
            self._model = RandomForestClassifier(**kwargs)
        elif self.model_type == "logistic_regression":
            from sklearn.linear_model import LogisticRegression
            self._model = LogisticRegression(**kwargs)
        else:
            raise ValueError(f"지원하지 않는 모델 타입: {self.model_type}")
            
        self._model.fit(X, y)
        self.is_trained = True
        
        return self
    
    def predict(self, X):
        """텍스트 분류 예측
        
        Args:
            X: 예측할 특성 데이터
            
        Returns:
            numpy.ndarray: 예측 결과 배열
            
        Raises:
            RuntimeError: 모델이 훈련되지 않은 경우
        """
        if not self.is_trained:
            raise RuntimeError("모델이 훈련되지 않았습니다. train() 메서드를 먼저 실행하세요.")
            
        return self._model.predict(X)

18.4.3 자동 코드 문서 생성

library(reticulate)

# Python 모듈 임포트
py_run_string("
import inspect
import ast

def extract_docstrings(source_file):
    '''파이썬 소스 파일에서 독스트링 추출'''
    
    with open(source_file, 'r', encoding='utf-8') as f:
        source = f.read()
    
    tree = ast.parse(source)
    
    docstrings = {}
    
    for node in ast.walk(tree):
        if isinstance(node, (ast.FunctionDef, ast.ClassDef)):
            docstring = ast.get_docstring(node)
            if docstring:
                docstrings[node.name] = {
                    'type': 'class' if isinstance(node, ast.ClassDef) else 'function',
                    'docstring': docstring,
                    'lineno': node.lineno
                }
    
    return docstrings

# 사용 예
docs = extract_docstrings('text_classifier.py')
")

# R에서 추출된 독스트링 처리
docstrings <- py$docs

# 문서화 테이블 생성
doc_table <- map_dfr(names(docstrings), function(name) {
  info <- docstrings[[name]]
  
  tibble(
    이름 = name,
    타입 = info$type,
    설명 = str_trunc(str_split(info$docstring, "\n")[[1]][1], 100),
    라인 = info$lineno
  )
})

doc_table %>%
  gt() %>%
  tab_header(
    title = "자동 추출된 코드 문서",
    subtitle = "Python 독스트링 기반 API 문서"
  ) %>%
  cols_label(
    이름 = "함수/클래스명",
    타입 = "타입",
    설명 = "설명",
    라인 = "라인번호"
  )

18.5 성능 모니터링 보고서

18.5.1 실시간 대시보드

library(plotly)
library(DT)

# 성능 데이터 시뮬레이션
generate_performance_data <- function(days = 30) {
  
  dates <- seq.Date(Sys.Date() - days, Sys.Date(), by = "day")
  
  tibble(
    date = rep(dates, each = 24),
    hour = rep(0:23, times = length(dates)),
    requests_per_hour = rpois(length(dates) * 24, 100),
    avg_response_time = rnorm(length(dates) * 24, 200, 50),
    error_rate = pmax(0, rnorm(length(dates) * 24, 0.02, 0.01)),
    cpu_usage = pmax(0, pmin(100, rnorm(length(dates) * 24, 65, 15))),
    memory_usage = pmax(0, pmin(100, rnorm(length(dates) * 24, 70, 20)))
  ) %>%
    mutate(
      datetime = as.POSIXct(paste(date, paste0(hour, ":00:00"))),
      avg_response_time = pmax(50, avg_response_time),
      error_rate = pmax(0, pmin(0.1, error_rate))
    )
}

perf_data <- generate_performance_data()

# 요청량 트렌드
p1 <- perf_data %>%
  group_by(date) %>%
  summarise(daily_requests = sum(requests_per_hour), .groups = "drop") %>%
  plot_ly(x = ~date, y = ~daily_requests, type = 'scatter', mode = 'lines+markers',
          name = '일일 요청수', line = list(color = '#1f77b4')) %>%
  layout(
    title = "일일 API 요청량 추이",
    xaxis = list(title = "날짜"),
    yaxis = list(title = "요청수")
  )

# 응답시간 분포
p2 <- perf_data %>%
  plot_ly(y = ~avg_response_time, type = 'box', name = '응답시간') %>%
  layout(
    title = "API 응답시간 분포",
    yaxis = list(title = "응답시간 (ms)")
  )

# 에러율 추이
p3 <- perf_data %>%
  group_by(date) %>%
  summarise(daily_error_rate = mean(error_rate), .groups = "drop") %>%
  plot_ly(x = ~date, y = ~daily_error_rate, type = 'scatter', mode = 'lines+markers',
          name = '에러율', line = list(color = '#ff7f0e')) %>%
  layout(
    title = "일일 에러율 추이", 
    xaxis = list(title = "날짜"),
    yaxis = list(title = "에러율 (%)", tickformat = ".1%")
  )

# 대시보드 조합
subplot(p1, p2, p3, nrows = 3, shareX = FALSE) %>%
  layout(title = "시스템 성능 모니터링 대시보드")

18.5.2 SLA 준수 보고서

library(tibble)

# SLA 기준
sla_targets <- tibble(
  metric = c("가용성", "응답시간", "에러율", "처리량"),
  target = c("99.9%", "< 300ms", "< 1%", "> 1000 req/min"),
  unit = c("percent", "milliseconds", "percent", "requests")
)

# 실제 성능 계산
actual_performance <- perf_data %>%
  summarise(
    availability = 0.998,  # 99.8%
    avg_response = mean(avg_response_time),
    avg_error_rate = mean(error_rate),
    avg_throughput = mean(requests_per_hour) * 60
  )

# SLA 비교 테이블
sla_report <- tibble(
  지표 = c("가용성", "평균 응답시간", "평균 에러율", "평균 처리량"),
  목표 = c("99.9%", "< 300ms", "< 1%", "> 1000 req/min"),
  실제값 = c(
    paste0(round(actual_performance$availability * 100, 1), "%"),
    paste0(round(actual_performance$avg_response, 1), "ms"),
    paste0(round(actual_performance$avg_error_rate * 100, 2), "%"),
    paste0(round(actual_performance$avg_throughput, 0), " req/min")
  ),
  준수여부 = c(
    ifelse(actual_performance$availability >= 0.999, "✅", "❌"),
    ifelse(actual_performance$avg_response <= 300, "✅", "❌"),
    ifelse(actual_performance$avg_error_rate <= 0.01, "✅", "❌"),
    ifelse(actual_performance$avg_throughput >= 1000, "✅", "❌")
  )
)

sla_report %>%
  gt() %>%
  tab_header(
    title = "SLA 준수 현황 보고서",
    subtitle = paste("기준일:", Sys.Date() - 30, "~", Sys.Date())
  ) %>%
  tab_style(
    style = cell_text(weight = "bold"),
    locations = cells_column_labels()
  ) %>%
  tab_style(
    style = cell_fill(color = "lightgreen"),
    locations = cells_body(
      columns = 준수여부,
      rows = 준수여부 == "✅"
    )
  ) %>%
  tab_style(
    style = cell_fill(color = "lightcoral"),
    locations = cells_body(
      columns = 준수여부, 
      rows = 준수여부 == "❌"
    )
  )
표 18.1: SLA 준수 현황

18.6 CI/CD 파이프라인 문서화

18.6.1 GitHub Actions 워크플로우

# .github/workflows/docs.yml
name: Documentation Build and Deploy

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build-docs:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Quarto
      uses: quarto-dev/quarto-actions/setup@v2
      
    - name: Setup R
      uses: r-lib/actions/setup-r@v2
      
    - name: Setup Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.11'
        
    - name: Install R dependencies
      run: |
        install.packages(c("rmarkdown", "knitr", "gt", "plotly"))
      shell: Rscript {0}
      
    - name: Install Python dependencies
      run: |
        pip install -r requirements.txt
        
    - name: Render documentation
      run: |
        quarto render
        
    - name: Deploy to GitHub Pages
      if: github.ref == 'refs/heads/main'
      uses: peaceiris/actions-gh-pages@v3
      with:
        github_token: ${{ secrets.GITHUB_TOKEN }}
        publish_dir: ./docs

18.6.2 배포 프로세스 추적

library(gh)
library(lubridate)

# GitHub API를 통한 배포 이력 조회
get_deployment_history <- function(repo, days = 30) {
  
  deployments <- gh(
    "GET /repos/{owner}/{repo}/deployments",
    owner = str_split(repo, "/")[[1]][1],
    repo = str_split(repo, "/")[[1]][2],
    per_page = 100
  )
  
  map_dfr(deployments, function(deploy) {
    statuses <- gh(
      "GET /repos/{owner}/{repo}/deployments/{deployment_id}/statuses",
      owner = str_split(repo, "/")[[1]][1],
      repo = str_split(repo, "/")[[1]][2], 
      deployment_id = deploy$id
    )
    
    latest_status <- statuses[[1]]
    
    tibble(
      deployment_id = deploy$id,
      environment = deploy$environment,
      created_at = ymd_hms(deploy$created_at),
      status = latest_status$state,
      description = latest_status$description %||% ""
    )
  }) %>%
    filter(created_at >= Sys.Date() - days) %>%
    arrange(desc(created_at))
}

# 배포 이력 테이블
deployment_history <- get_deployment_history("username/text-classifier-api")

deployment_history %>%
  gt() %>%
  tab_header(
    title = "배포 이력",
    subtitle = "최근 30일간 배포 현황"
  ) %>%
  fmt_datetime(
    columns = created_at,
    date_style = 6,
    time_style = 3
  )

18.7 다음 단계

다음 장에서는 이러한 기술 문서들을 프레젠테이션과 교육 자료로 변환하는 방법을 배워보겠습니다. 같은 콘텐츠로 다양한 형태의 교육 자료를 만드는 효율적인 방법을 알아보세요.


힌트실습 과제

현재 담당하고 있는 프로젝트나 시스템의 기술 문서를 Document as Code 방식으로 작성해보세요. 코드에서 자동으로 추출되는 정보와 수동으로 작성해야 하는 내용을 구분하여 관리하는 것이 핵심입니다.

경고보안 주의사항

자동 문서 생성 시 코드에서 API 키나 비밀번호 등의 민감한 정보가 노출되지 않도록 주의하세요. 환경변수나 설정 파일을 통해 민감한 정보를 분리하는 것이 중요합니다.