[파이썬 100강] 99강. 프로덕션 체크리스트로 파이썬 자동화 시스템 완성하기

[파이썬 100강] 99강. 프로덕션 체크리스트로 파이썬 자동화 시스템 완성하기

[파이썬 100강] 99강. 프로덕션 체크리스트로 파이썬 자동화 시스템 완성하기

이번 강의는 production checklist를 실무에서 바로 쓰는 관점으로 정리합니다. 초보자가 이 구간에서 가장 자주 하는 오해는 "작동만 하면 됐다"는 생각입니다. 하지만 운영 환경에서는 입력 데이터가 흔들리고, 예외가 발생하며, 사람이 코드를 교체합니다. 그래서 이번 강의 목표는 개념 이해 + 재현 가능한 예제 + 실수 회피 패턴까지 한 번에 잡는 것입니다.


핵심 개념

  • production checklist는 "기능 사용법"보다 "운영에서 실패를 줄이는 규칙"과 함께 이해해야 가치가 커집니다.
  • 코드 한 줄이 아니라 입력 검증 → 처리 → 기록 → 복구 가능성의 흐름으로 설계하면 유지보수 비용이 크게 내려갑니다.
  • 실무 코드의 품질은 정상 케이스보다 비정상 케이스를 어떻게 처리하는지에서 결정됩니다.

초보자 입장에서는 문법을 익히는 데 집중하다 보니, 실패 시나리오를 나중으로 미루는 경우가 많습니다. 그런데 실제 서비스나 팀 자동화에서는 "실패가 반드시 발생"합니다. 그래서 처음부터 실패를 전제로 설계해야 합니다. 이번 강의의 모든 예제는 단순히 동작 여부를 보여주는 수준을 넘어, 입력이 누락되거나 값이 비정상일 때도 코드가 어떻게 반응해야 하는지 함께 설명합니다. 또한 코드 블록마다 왜 이런 구조를 택했는지, 무엇을 로그로 남겨야 하는지, 팀원에게 전달 가능한 형태인지까지 확인합니다. 이 흐름을 습관화하면 다음 프로젝트에서도 복붙이 아니라 원칙 기반으로 코드를 작성할 수 있습니다.

기본 사용

예제 1) 가장 기본 패턴

>>> # lesson 99 핵심 기능 데모
>>> profile = {"env": "prod", "retry": 3, "timeout": 5}
>>> profile["retry"]
3
>>> isinstance(profile, dict)
True

해설:

  • 먼저 가장 단순한 자료구조로 핵심 데이터를 표현해 흐름을 명확히 잡습니다.
  • 출력이 단순해 보여도, 키 존재 여부와 타입 검증은 다음 단계의 안정성에 직접 연결됩니다.

예제 2) 조건/반복/조합 확장

>>> jobs = [
...     {"name": "sync-users", "enabled": True},
...     {"name": "sync-orders", "enabled": False},
...     {"name": "sync-logs", "enabled": True},
... ]
>>> [j["name"] for j in jobs if j.get("enabled")]
['sync-users', 'sync-logs']
>>> sum(1 for j in jobs if j.get("enabled"))
2

해설:

  • 반복문 내부에서 조건을 명시적으로 분리하면 디버깅이 쉬워집니다.
  • 실무에서는 "활성 작업만 추출" 같은 필터링이 빈번하므로, 리스트 컴프리헨션과 get 사용 습관이 중요합니다.

예제 3) 실전형 미니 케이스

>>> from datetime import datetime
>>> def run_with_audit(task_name, ok, reason=""):
...     return {
...         "task": task_name,
...         "ok": ok,
...         "reason": reason,
...         "ts": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
...     }
...
>>> run_with_audit("sync-users", True)["ok"]
True
>>> run_with_audit("sync-orders", False, "timeout")["reason"]
'timeout'

해설:

  • 결과를 구조화(dict)해 남기면 로그·리포트·재시도 로직으로 쉽게 확장됩니다.
  • "성공/실패 + 이유 + 시각"을 함께 기록하는 습관은 장애 분석 시간을 크게 줄여 줍니다.

예제 4) 운영용 래퍼 함수 패턴

>>> def guarded_run(task, *, dry_run=True):
...     if not isinstance(dry_run, bool):
...         raise TypeError("dry_run must be bool")
...     if dry_run:
...         return f"[DRY] {task}"
...     return f"[EXEC] {task}"
...
>>> guarded_run("daily-report")
'[DRY] daily-report'
>>> guarded_run("daily-report", dry_run=False)
'[EXEC] daily-report'

해설:

  • 운영 코드의 기본값은 보수적으로 두는 것이 안전합니다.
  • 키워드 전용 인자(*)를 쓰면 호출부에서 인자의 의미가 명확해져 협업 오류가 줄어듭니다.

자주 하는 실수

실수 1) 입력 형식을 신뢰하고 바로 접근

>>> payload = {"retry": "3"}
>>> payload["timeout"]
Traceback (most recent call last):
...
KeyError: 'timeout'

원인:

  • 외부 입력(JSON/TOML/CLI 인자)은 누락·오타가 빈번한데, 필수 키 검증 없이 바로 접근했습니다.

해결:

>>> def read_timeout(data):
...     value = data.get("timeout", 10)
...     if not isinstance(value, int):
...         raise ValueError("timeout must be int")
...     return value
...
>>> read_timeout({"timeout": 5})
5
>>> read_timeout({})
10

실수 2) 예외를 통째로 삼켜 원인 추적 불가

  • 증상: 작업이 실패했는데 로그에는 "완료"만 남아 원인을 찾지 못합니다.
  • 원인: except Exception: pass로 실패 정보를 버렸습니다.
  • 해결: 예상 예외만 잡고, 실패 맥락(작업명/입력값/에러 메시지)을 함께 기록합니다.
>>> def safe_div(a, b):
...     try:
...         return a / b
...     except ZeroDivisionError as e:
...         return f"fail: {e}"
...
>>> safe_div(10, 2)
5.0
>>> safe_div(10, 0)
'fail: division by zero'

실수 3) 재현 정보 없이 "로컬에서만 됨" 상태로 종료

>>> env = {"python": "3.11.14", "os": "macOS/Linux"}
>>> sorted(env.keys())
['os', 'python']

원인:

  • 의존 버전, 실행 옵션, 입력 샘플을 남기지 않으면 같은 문제를 다시 재현할 수 없습니다.

해결:

  • 실행 명령, 샘플 입력, 출력 포맷, 실패 예시를 문서에 고정합니다.
  • 최소한 "파이썬 버전/OS/실행 인자" 3가지는 항상 기록합니다.

실무 패턴

  • 입력 검증 규칙:
    • 필수 키 목록을 명시하고, 누락 시 즉시 실패시키는 "빠른 실패(fail-fast)" 패턴을 씁니다.
    • 숫자/불리언/경로 등 도메인 타입을 초반에 강제 변환해 뒤쪽 로직을 단순화합니다.
  • 로그/예외 처리 규칙:
    • 시작 로그(입력), 결과 로그(성공/실패), 요약 로그(건수/소요 시간)를 구분해 남깁니다.
    • 예외를 잡을 때는 처리 가능한 예외만 잡고, 나머지는 상위로 올려 알람 체계와 연결합니다.
  • 재사용 함수/구조화 팁:
    • I/O(파일·네트워크)와 순수 로직을 분리하면 테스트 작성이 쉬워집니다.
    • 공통 유틸(검증/파싱/포맷)을 작은 함수로 분리해 중복을 줄입니다.
  • 성능/메모리 체크 포인트:
    • 큰 리스트를 한 번에 만들기보다 제너레이터/스트리밍 처리로 메모리 피크를 낮춥니다.
    • 병목 구간만 측정하고 개선합니다. 모든 코드를 과최적화하면 유지보수성이 떨어집니다.

실무에서 코드 품질은 "멋진 문법"보다 "예측 가능한 동작"으로 평가됩니다. 팀원이 새벽에 장애를 받았을 때, 로그와 문서만으로 문제를 재현할 수 있어야 진짜 운영 가능한 코드입니다. 그래서 이번 강의에서도 반복해서 강조한 것은 검증, 기록, 복구 가능성입니다. 기능 구현은 출발점일 뿐이고, 운영 안정화가 종착점입니다. 처음에는 다소 번거롭게 느껴질 수 있지만, 이 패턴을 한 번 몸에 익히면 이후 프로젝트에서 오류 수정 시간이 눈에 띄게 줄어듭니다.

오늘의 결론

한 줄 요약: production checklist 학습의 핵심은 문법 암기가 아니라 운영 실패를 줄이는 구조화 습관입니다.

기억할 것:

  • 입력 검증은 선택이 아니라 필수입니다.
  • 실패 정보는 숨기지 말고 구조화해서 남겨야 합니다.
  • "로컬에서 됨"이 아니라 "팀이 재현 가능"한 상태를 목표로 합니다.

연습문제

  1. 입력 dict를 받아 필수 키(name, enabled, retry)를 검증하고, 누락 키 목록을 반환하는 함수를 작성해 보세요.
  2. 작업 결과 리스트에서 성공률(%)을 계산하고, 80% 미만이면 경고 문자열을 반환하도록 작성해 보세요.
  3. dry_run일 때와 실제 실행일 때 로그 메시지 포맷이 달라지도록 래퍼 함수를 개선해 보세요.

이전 강의 정답

  1. 필수 키 검증 함수 예시
>>> def validate_required(data, required):
...     missing = [k for k in required if k not in data]
...     return missing
...
>>> validate_required({"name": "job1", "enabled": True}, ["name", "enabled", "retry"])
['retry']
  1. 성공률 계산 함수 예시
>>> def success_rate(results):
...     total = len(results)
...     if total == 0:
...         return 0.0
...     ok = sum(1 for r in results if r.get("ok"))
...     return round(ok * 100 / total, 1)
...
>>> success_rate([{"ok": True}, {"ok": False}, {"ok": True}])
66.7
  1. dry-run 메시지 분기 예시
>>> def format_run_message(task, dry_run):
...     return f"[DRY] {task}" if dry_run else f"[EXEC] {task}"
...
>>> format_run_message("daily-sync", True)
'[DRY] daily-sync'
>>> format_run_message("daily-sync", False)
'[EXEC] daily-sync'

실습 환경/재현 정보

  • 실행 환경: conda env python100 (Python 3.11.14)
  • 가정한 OS: macOS/Linux 공통
  • 권장 검증 순서:
    • REPL에서 예제 함수 단위 실행
    • 실패 케이스(누락 키, 0 나누기, 타입 오류) 재현
    • 로그/리턴 값이 문서와 동일한지 확인
  • 재현 체크포인트:
    • 코드블록 결과 문자열 일치 여부
    • 예외 타입/메시지 일치 여부
    • dry_run 기본값 동작 여부