파이썬 명령행 도구와 예약 실행 - argparse logging 과 cron 작업 스케줄러 (파이썬 자동화 7단원)
이 단원에서 배우는 것
지금까지 만든 스크립트는 내가 터미널 앞에 앉아 실행하고, 화면에 나온 글을 내가 읽는다는 전제로 만들었다. 자동화의 마지막 단계는 아무도 보고 있지 않을 때 저절로 돌게 하는 것이다. 그러려면 스크립트가 달라져야 한다. 옵션을 정확히 받아야 하고, 무슨 일이 있었는지 기록을 남겨야 하고, 실패했다는 사실을 다른 프로그램이 알 수 있게 알려야 한다.
argparse로 위치 인자·옵션·기본값·도움말을 갖춘 명령행 도구를 만든다.logging으로print대신 시각과 수준이 붙은 기록을 남긴다.- 종료 코드로 성공과 실패를 구분해 알린다.
- cron(맥·리눅스)과 작업 스케줄러(윈도우)에 매일 실행을 예약한다.
문제 상황
자동화 4단원의 로그 분석 스크립트를 개발 담당자가 매일 아침 손으로 돌리고 있다. 휴가를 가면 아무도 돌리지 않는다. 이제 이 일을 이렇게 바꾸려 한다.
- 매일 새벽 6시에 어제 하루치 ERROR를 모듈별로 세어
reports/errors-2026-09-22.txt같은 파일로 남긴다. - 특정 날짜를 다시 요약하고 싶을 때는
--date로 지정할 수 있다. - 설정을 바꿔 볼 때는
--dry-run으로 파일을 만들지 않고 화면으로만 확인한다. - 로그 폴더가 없거나 권한이 없으면 실패로 끝나고, 그 사실이 기록에 남는다.
완성 스크립트
daily_errors.py로 저장한다. 자동화 4단원의 스크립트에서 날짜 필터와 파일 저장만 남기고, 도구로서 갖춰야 할 부분을 더했다.
"""하루치 ERROR 로그를 요약 파일로 남기는 명령행 도구. 스케줄러에 걸어 매일 돌린다."""
import argparse
import logging
import re
import sys
from collections import Counter
from datetime import date, timedelta
from pathlib import Path
log = logging.getLogger("daily_errors")
LINE = re.compile(r"^(\d{4}-\d{2}-\d{2}) \d{2}:\d{2}:\d{2} ERROR +(\w+) (.*)$")
def parse_args(argv=None):
yesterday = date.today() - timedelta(days=1)
parser = argparse.ArgumentParser(description="하루치 ERROR 로그를 요약한다.")
parser.add_argument("log_dir", type=Path, help="*.log 파일이 있는 폴더")
parser.add_argument("--date", type=date.fromisoformat, default=yesterday,
help="요약할 날짜 YYYY-MM-DD (기본: 어제)")
parser.add_argument("--out", type=Path, default=Path("reports"),
help="요약 파일을 쓸 폴더 (기본: reports)")
parser.add_argument("--dry-run", action="store_true",
help="파일을 쓰지 않고 화면에만 출력한다")
parser.add_argument("-v", "--verbose", action="store_true",
help="진행 로그를 자세히 보여 준다")
return parser.parse_args(argv)
def setup_logging(verbose):
logging.basicConfig(
level=logging.DEBUG if verbose else logging.INFO,
format="%(asctime)s %(levelname)s %(message)s",
datefmt="%H:%M:%S",
)
def summarize(log_dir, day):
target = day.isoformat()
modules = Counter()
files = sorted(log_dir.glob("*.log"))
if not files:
raise FileNotFoundError(f"{log_dir} 에 .log 파일이 없습니다")
for path in files:
log.debug("읽는 중: %s", path.name)
with open(path, encoding="utf-8", errors="replace") as f:
for line in f:
m = LINE.match(line)
if m and m.group(1) == target:
modules[m.group(2)] += 1
lines = [f"{target} ERROR {sum(modules.values())}건"]
lines += [f" {name}: {count}" for name, count in modules.most_common()]
return "\n".join(lines) + "\n"
def run(args):
text = summarize(args.log_dir, args.date)
if args.dry_run:
log.info("dry-run: 파일을 쓰지 않습니다")
print(text, end="")
return
args.out.mkdir(parents=True, exist_ok=True)
path = args.out / f"errors-{args.date.isoformat()}.txt"
path.write_text(text, encoding="utf-8")
log.info("요약 저장: %s", path)
def main(argv=None):
args = parse_args(argv)
setup_logging(args.verbose)
try:
run(args)
except (FileNotFoundError, PermissionError) as e:
log.error("%s", e)
return 1
except Exception:
log.exception("요약 실패")
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
한 줄씩 해설
parse_args — 옵션 설계를 선언으로 적는다
지금까지는 sys.argv를 직접 뒤졌다. 옵션이 두세 개를 넘으면 순서, 빠진 값, 잘못된 형식을 전부 손으로 검사해야 한다. argparse는 "어떤 옵션이 있고 각각 무슨 형식인지"만 적으면 나머지를 처리해 준다.
parser.add_argument("log_dir", type=Path, help="*.log 파일이 있는 폴더")
이름이 -로 시작하지 않으면 위치 인자, 즉 반드시 줘야 하는 값이다. type=Path를 주면 문자열을 받아 Path로 바꿔서 넘겨준다.
parser.add_argument("--date", type=date.fromisoformat, default=yesterday, ...)
--로 시작하면 옵션이다. 주지 않으면 default 값이 쓰인다. type에는 "문자열 하나를 받아 원하는 값으로 바꾸는 함수"면 무엇이든 줄 수 있다. date.fromisoformat은 "2026-09-22"를 date 객체로 바꾸고, 형식이 틀리면 예외를 낸다. argparse는 그 예외를 받아 친절한 오류 메시지와 함께 종료 코드 2로 끝낸다. 날짜 검사 코드를 한 줄도 쓰지 않았는데 2026-9-31 같은 입력이 걸러진다.
parser.add_argument("--dry-run", action="store_true", ...)
action="store_true"는 값 없이 이름만 있는 스위치다. 주면 True, 안 주면 False다. 옵션 이름의 -는 속성 이름에서 _로 바뀐다. 그래서 코드에서는 args.dry_run으로 읽는다. -v와 --verbose처럼 짧은 이름과 긴 이름을 함께 줄 수도 있다.
parse_args(argv=None)로 인자 리스트를 받을 수 있게 한 것은 시험을 위해서다. None이면 sys.argv를 읽고, 리스트를 주면 그것을 읽는다. 코드 안에서 main(["logs", "--dry-run"])처럼 명령행 없이 도구를 불러 볼 수 있다.
setup_logging — print 대신 logging
print는 "지금 화면을 보는 사람"을 위한 것이다. 예약 실행에는 화면이 없다. logging은 기록 한 줄마다 시각과 수준을 붙이고, 수준에 따라 보여 줄지 말지를 한곳에서 정한다.
| 수준 | 쓰는 곳 | 이 스크립트의 예 |
|---|---|---|
DEBUG | 평소에는 필요 없는 세부 진행 | 읽는 파일 이름 |
INFO | 정상 동작의 주요 사건 | 요약 파일 저장 위치 |
WARNING | 동작은 했지만 확인이 필요함 | (연습 과제 2) |
ERROR | 이번 실행이 실패함 | 로그 파일이 없음 |
basicConfig(level=...)로 기준을 정하면 그보다 낮은 수준은 출력되지 않는다. -v를 주면 기준을 DEBUG로 낮춰 세부 진행까지 보여 준다. 코드 곳곳의 log.debug(...)를 지웠다 살렸다 할 필요가 없다. 로그 메시지에 값을 넣을 때 log.debug("읽는 중: %s", path.name)처럼 f-string 대신 %s와 인자를 쓰는 것이 logging의 관례다. 출력되지 않는 수준이면 문자열을 만드는 일 자체를 건너뛴다.
logging은 기본적으로 표준 오류(stderr)로 쓰고, 요약 본문은 print로 표준 출력(stdout)에 쓴다. 둘을 나눠 두면 python3 daily_errors.py logs --dry-run > 요약.txt처럼 결과만 파일로 받고 진행 기록은 화면에 남길 수 있다.
summarize와 run — 할 일과 결과 처리를 나눈다
summarize는 날짜를 받아 요약 문자열을 돌려주기만 한다. 파일을 쓰지도, 화면에 출력하지도 않는다. 그래서 dry-run과 실제 실행이 완전히 같은 요약을 쓴다는 것이 보장된다. 자동화 1단원에서 "보여 준 것 = 실행한 것"을 구조로 보장한 것과 같은 생각이다. 로그 파일이 하나도 없으면 빈 요약("ERROR 0건")을 만들지 않고 FileNotFoundError를 던진다. "오류가 0건이었다"와 "읽을 로그가 없었다"는 전혀 다른 상황이기 때문이다.
run은 요약을 받아 dry-run이면 화면에, 아니면 파일에 쓴다.
main — 모든 실패를 종료 코드로 바꾼다
try:
run(args)
except (FileNotFoundError, PermissionError) as e:
log.error("%s", e)
return 1
except Exception:
log.exception("요약 실패")
return 1
return 0
프로그램이 끝날 때 운영체제에 돌려주는 정수가 종료 코드다. 0은 성공, 그 밖은 실패라는 약속을 모든 도구가 따른다. 스케줄러, 셸 스크립트, CI 같은 다른 프로그램은 화면 글자를 읽지 않고 이 숫자만 본다. 파이썬은 예외로 죽으면 1을, 끝까지 가면 0을 돌려주지만, 이렇게 명시해 두면 "예상한 실패"와 "예상 못 한 실패"를 다르게 기록할 수 있다.
- 파일이 없거나 권한이 없는 것은 예상한 실패다. 원인이 분명하므로 한 줄로 기록한다.
- 그 밖의 예외는 버그일 가능성이 높다.
log.exception은ERROR수준으로 쓰면서 추적 정보 전체를 함께 남긴다. 새벽에 난 오류를 아침에 고치려면 이 정보가 꼭 필요하다.
맨 아래 sys.exit(main())이 그 숫자를 운영체제에 넘긴다. argparse가 쓰는 2(사용법 오류)까지 합치면, 이 도구의 종료 코드는 0(성공), 1(실행 실패), 2(명령을 잘못 입력함) 세 가지다.
그림 · 예약 실행 한 번의 흐름과 종료 코드 — 스케줄러가 작업 폴더에서 가상환경 파이썬으로 스크립트를 부르고, main 은 모든 결과를 종료 코드로 바꾼다. 출력은 파일에 모으고, 스케줄러는 종료 코드로 성공·실패를 기록한다.
실행 결과
실습용 applogs/app.log에는 9월 21일과 22일 로그가 섞여 있다. 먼저 도움말이다. 직접 쓴 적이 없는데 argparse가 만들어 준다.
$ python3 daily_errors.py --help
usage: daily_errors.py [-h] [--date DATE] [--out OUT] [--dry-run] [-v] log_dir
하루치 ERROR 로그를 요약한다.
positional arguments:
log_dir *.log 파일이 있는 폴더
options:
-h, --help show this help message and exit
--date DATE 요약할 날짜 YYYY-MM-DD (기본: 어제)
--out OUT 요약 파일을 쓸 폴더 (기본: reports)
--dry-run 파일을 쓰지 않고 화면에만 출력한다
-v, --verbose 진행 로그를 자세히 보여 준다
날짜를 지정하고 dry-run으로 확인한다. 진행 기록(시각이 붙은 줄)과 요약 본문이 함께 보인다.
$ python3 daily_errors.py applogs --date 2026-09-22 --dry-run
23:50:34 INFO dry-run: 파일을 쓰지 않습니다
2026-09-22 ERROR 24건
order: 10
payment: 9
search: 4
member: 1
이 원고를 검증한 날은 9월 23일이다. --date를 빼면 기본값인 어제(22일)가 쓰인다. -v로 세부 진행도 본다.
$ python3 daily_errors.py applogs -v
23:50:34 DEBUG 읽는 중: app.log
23:50:34 INFO 요약 저장: reports/errors-2026-09-22.txt
$ cat reports/errors-2026-09-22.txt
2026-09-22 ERROR 24건
order: 10
payment: 9
search: 4
member: 1
실패하는 경우도 확인한다. 로그 폴더가 없을 때와 날짜를 잘못 입력했을 때다. 셸에서 echo $?(윈도우 PowerShell은 $LASTEXITCODE)로 종료 코드를 볼 수 있으며, 둘은 각각 1과 2로 끝났다.
$ python3 daily_errors.py nolog
23:50:34 ERROR nolog 에 .log 파일이 없습니다
$ python3 daily_errors.py applogs --date 2026-9-31
usage: daily_errors.py [-h] [--date DATE] [--out OUT] [--dry-run] [-v] log_dir
daily_errors.py: error: argument --date: invalid fromisoformat value: '2026-9-31'
표 · daily_errors.py 의 실제 종료 코드
| 상황 | 명령 | 종료 코드 |
|---|---|---|
| 도움말 | python3 daily_errors.py --help | 0 |
| 미리보기 | python3 daily_errors.py applogs --date 2026-09-22 --dry-run | 0 |
| 기본 실행(어제 날짜) | python3 daily_errors.py applogs -v | 0 |
| .log 파일이 없는 폴더 | python3 daily_errors.py nolog | 1 |
| 잘못된 날짜 형식 | python3 daily_errors.py applogs --date 2026-9-31 | 2 |
| 그 밖의 예외 | 실행하지 않음 (log.exception 뒤 return 1) | 1 |
예약 실행 걸기
맥·리눅스 — cron
cron은 정해진 시각에 명령을 실행하는 프로그램이다. 터미널에서 crontab -e로 편집기를 열고 한 줄을 추가한다. 아래는 설정 예시이고, 경로는 자기 환경에 맞게 바꾼다.
# 분 시 일 월 요일 명령
0 6 * * * cd /home/seojeom/tools && /home/seojeom/tools/.venv/bin/python daily_errors.py /var/log/bookstore --out /home/seojeom/reports >> /home/seojeom/tools/cron.log 2>&1
앞의 다섯 칸이 "매일 6시 0분"이다. *는 "매번"이라는 뜻이다. 30 8 * * 1-5는 평일 8시 30분, */10 * * * *는 10분마다다. 명령 부분에서 지킬 것은 네 가지다.
- 모든 경로를 절대 경로로 쓴다. cron은 내 홈 폴더가 아니라 다른 곳에서 명령을 실행한다. 7단원에서 본 "상대 경로는 실행 위치 기준" 함정이 여기서 터진다. 앞에
cd를 두는 것도 같은 이유다. - 파이썬도 절대 경로로 쓴다. cron의
PATH는 아주 짧아서, 터미널에서 되던python3가 다른 버전이거나 아예 없을 수 있다. 14단원에서 만든 가상환경을 쓴다면 그 안의python을 직접 적는다. 이러면 가상환경을 켜는 명령도 필요 없다. - 출력을 파일로 모은다.
>> cron.log 2>&1은 표준 출력을 파일 끝에 덧붙이고(>>), 표준 오류도 같은 곳으로 보낸다(2>&1). 이걸 빼면logging이 남긴 기록이 어디에도 남지 않는다. - 시간대를 확인한다. 서버는 UTC로 설정된 경우가 많다. UTC 6시는 한국 시각 오후 3시다. 서버에서
date명령으로 현재 시간대를 먼저 본다. 13단원의 naive datetime 함정과 같은 뿌리다. 이 스크립트의 "어제" 역시 서버 시간대 기준으로 계산된다.
등록한 내용은 crontab -l로 확인한다. 처음 등록할 때는 시각을 2~3분 뒤로 잡아 한 번 돌아가는 것을 보고, cron.log에 기록이 남았는지 확인한 뒤 원래 시각으로 바꾼다. 맥에서는 cron이 다른 폴더(문서, 데스크톱 등)에 접근하려면 시스템 설정의 "전체 디스크 접근 권한"이 필요할 수 있다.
윈도우 — 작업 스케줄러
시작 메뉴에서 "작업 스케줄러"를 열고 기본 작업 만들기를 누른다. 트리거는 "매일", 시각은 06:00, 동작은 "프로그램 시작"을 고른다. 입력 칸은 세 개다.
| 칸 | 넣을 값 (예시) |
|---|---|
| 프로그램/스크립트 | C:\tools\.venv\Scripts\python.exe |
| 인수 추가 | daily_errors.py C:\logs\bookstore --out C:\reports |
| 시작 위치 | C:\tools |
시작 위치를 비워 두면 작업이 C:\Windows\System32에서 실행되어 상대 경로가 전부 어긋난다. cron의 cd와 같은 역할이니 꼭 채운다. 같은 설정을 명령 한 줄로 만들 수도 있다(명령 프롬프트에서 입력).
schtasks /Create /SC DAILY /ST 06:00 /TN "서점 오류 요약" /TR "C:\tools\run_daily.bat"
이때 run_daily.bat에는 cd /d C:\tools와 파이썬 실행 줄, 그리고 >> C:\tools\task.log 2>&1 출력 모으기를 적어 둔다. 작업 스케줄러의 "마지막 실행 결과" 칸에 보이는 숫자가 바로 스크립트의 종료 코드다. 0x0이면 성공, 0x1이면 우리 스크립트가 1을 돌려준 것이다. 노트북이라면 조건 탭의 "AC 전원을 사용하는 경우에만 시작" 선택을 해제해야 배터리 상태에서도 실행된다.
표 · 운영체제별 예약 실행 설정 비교 (설정 예시, 이 책에서 실행하지 않음)
| 항목 | cron (맥·리눅스) | 작업 스케줄러 (윈도우) |
|---|---|---|
| 등록 | crontab -e 에 한 줄 | 기본 작업 만들기 또는 schtasks /Create |
| 매일 6시 | 0 6 * * * | 트리거 매일, 시각 06:00 |
| 작업 폴더 | 명령 앞의 cd /home/seojeom/tools && | 시작 위치 칸 C:\tools |
| 파이썬 | .venv/bin/python 절대 경로 | C:\tools\.venv\Scripts\python.exe |
| 출력 기록 | >> cron.log 2>&1 | run_daily.bat 안의 >> task.log 2>&1 |
| 성공·실패 확인 | cron.log 의 ERROR 기록 | 마지막 실행 결과 0x0 / 0x1 |
| 겹침 방지 | flock -n (리눅스) | 설정 탭의 '이미 실행 중이면' 규칙 |
실무에서 자주 틀리는 것
1. 터미널에서는 되는데 예약 실행에서만 실패한다
원인은 거의 항상 환경 차이다. 실행 위치, PATH, 환경 변수(자동화 6단원의 API 키), 사용자 권한이 다르다. 확인하는 가장 빠른 방법은 스크립트 맨 앞에서 log.debug("cwd=%s python=%s", Path.cwd(), sys.executable)를 남기고 -v로 예약하는 것이다. 기록을 보면 어디서 어떤 파이썬으로 돌았는지 바로 드러난다.
그림 · 터미널에서 되던 스크립트가 예약 실행에서 실패하는 이유 — 예약 실행은 다른 폴더, 짧은 PATH, 빈 환경 변수로 시작한다. 경로와 파이썬을 절대 경로로 적고 시작 위치를 채우면 두 환경의 차이가 사라진다.
2. 실패해도 아무도 모른다
예약 실행의 가장 흔한 사고는 오류가 아니라 조용한 실패다. 석 달 전부터 요약 파일이 안 만들어지고 있었는데 아무도 몰랐다는 식이다. 최소한 두 가지를 한다. 첫째, 이 단원의 스크립트처럼 실패를 종료 코드와 ERROR 기록으로 확실히 남긴다. 둘째, 결과물이 있는지 사람이 보는 곳에서 확인한다. 예를 들어 매주 월요일에 지난주 요약 파일이 7개인지 세는 작은 스크립트를 하나 더 두거나, 실패하면 메일·메신저로 알림을 보내게 한다.
3. 앞선 실행이 끝나기 전에 다음 실행이 시작된다
10분마다 도는 작업이 어느 날 15분 걸리면 두 개가 동시에 같은 파일을 쓴다. 자동화 5단원의 백업처럼 같은 이름의 파일을 만드는 작업이면 서로 덮어쓴다. 리눅스에서는 cron 명령 앞에 flock -n /tmp/daily_errors.lock을 붙이면 이미 실행 중일 때 새 실행이 바로 끝난다. 운영체제 도구를 쓸 수 없으면 스크립트 시작 시 잠금 파일을 "x" 모드(7단원: 이미 있으면 FileExistsError)로 만들고 끝날 때 지우는 방법도 있다. 이 방법은 스크립트가 강제로 종료되면 잠금 파일이 남으므로, 잠금 파일에 시작 시각을 적어 두고 너무 오래된 것은 무시하게 한다. 가장 간단한 해법은 실행 간격을 작업 시간보다 넉넉히 잡는 것이다.
4. 로그 파일이 끝없이 커진다
>> cron.log는 덧붙이기만 하므로 1년이면 꽤 커진다. 리눅스라면 logrotate에 맡기고, 파이썬 안에서 해결하려면 logging.handlers.TimedRotatingFileHandler로 날짜별 로그 파일을 만들고 보관 개수(backupCount)를 정한다. 자동화 5단원의 보관 개수 관리와 같은 생각이다.
자동화 스크립트 점검표
일곱 단원에서 만든 스크립트들이 공통으로 지킨 원칙을 모으면 다음과 같다. 새 자동화 스크립트를 만들 때 이 표를 한 번씩 확인한다.
| 원칙 | 구현한 곳 |
|---|---|
| 기본은 미리보기, 실행은 명시적으로 | 자동화 1·2단원의 --apply, 이 단원의 --dry-run |
| 덮어쓰기 전에 검사하고, 되돌릴 기록을 남긴다 | 자동화 1단원 free_name, 자동화 2단원 check_plan·rename_log.csv |
| 틀린 입력은 건너뛰되 숨기지 않는다 | 자동화 3단원 errors.csv, 자동화 4단원 형식이 다른 줄 수 |
| 임시 이름으로 쓰고 다 끝나면 바꿔 끼운다 | 자동화 5단원 .zip.part, 자동화 6단원 .json.tmp |
| 네트워크에는 시간 제한과 재시도 | 자동화 6단원 timeout, 500번대만 재시도 |
| 여러 번 실행해도 결과가 같다(멱등성) | 자동화 1단원 재실행, 날짜별 결과 파일 |
| 실패는 종료 코드와 기록으로 알린다 | 이 단원의 main과 logging |
스스로 확인하기
- ERROR가 50건을 넘는 날에는 요약 파일은 정상으로 쓰되
WARNING기록을 남기고 싶다. 기준 값을--warn-over옵션(기본 50)으로 받으려면add_argument를 어떻게 쓰고, 어디서 검사하는가? --date에2026-09-31(9월 31일은 없다)을 주면 어떻게 되는가?2026-9-31과 같은 메시지가 나오는 이유는?- 동료가 cron에
0 6 * * * python3 daily_errors.py logs라고 등록했다. 적어도 세 가지 문제를 찾아라.
정답
parser.add_argument("--warn-over", type=int, default=50, help="ERROR가 이 수를 넘으면 경고")를 추가하고 코드에서는args.warn_over로 읽는다.summarize가 건수도 함께 돌려주게 바꾼 뒤run에서if total > args.warn_over: log.warning("ERROR %d건, 기준 %d건 초과", total, args.warn_over)를 남긴다. 경고는 실패가 아니므로 종료 코드는 0 그대로 둔다.date.fromisoformat("2026-09-31")은 형식은 맞지만 존재하지 않는 날짜라서ValueError를 낸다.argparse는type함수가ValueError를 내면 원인과 상관없이 "invalid fromisoformat value"라는 같은 형식의 메시지를 내고 종료 코드 2로 끝낸다. 형식 검사와 달력 검사를 한 번에 얻은 셈이다.- (1)
python3가 상대 이름이라 cron의 짧은PATH에서 다른 파이썬이 잡히거나 못 찾는다. (2)daily_errors.py와logs가 상대 경로라 cron의 실행 위치에서 찾지 못한다. 요약 파일도 기본값reports라서 엉뚱한 곳에 생긴다. (3) 출력을 모으지 않아 실패 기록이 남지 않는다. 덧붙여 서버 시간대가 UTC라면 6시가 한국 시각 15시다. 모든 경로를 절대 경로로 바꾸고, 앞에cd를 두고,>> cron.log 2>&1을 붙인다.
이것으로 Python 문법과 작은 자동화의 자동화 과정을 마친다. 이 책의 스크립트들은 모두 표준 라이브러리만 써서 파이썬만 설치된 컴퓨터라면 어디서든 돈다. 이제 직접 매주 반복하는 일 하나를 골라 보자. 점검표의 원칙대로 미리보기부터 만들고, 기록을 남기고, 예약을 거는 순서로 자동화하면 된다. 여기서 더 나아가 타입 힌트, 테스트, 패키징으로 스크립트를 튼튼하게 만드는 방법은 이어지는 타입·비동기·테스트 설계에서 다룬다.