Skip to content
isdnetworks
Go back

하나 고쳤더니 다음 것이 나왔다

한글이 깨지는 문제를 고쳤다. 돌려 보니 이번엔 경로 오류가 났다.

경로를 고쳤더니 그다음엔 권한 오류가 났다.

Table of contents

Open Table of contents

앞의 문제에서 멈추고 있었다

처음 실행하면 cp949 로 잡힌 출력 인코딩에서 죽었고 그 뒤 코드는 아예 안 돌았다.

인코딩을 고치니 다음 줄로 갔고 거기서 /data/tmp 가 없다는 문제가 나왔다. 그것을 고치니 또 그다음 줄로 갔다.

앞의 문제가 뒤의 문제를 가리고 있었던 것이다. 뒤의 것이 새로 생긴 것이 아니라 도달을 못 했을 뿐이다.

그러니 만나는 순서는 문제가 코드에 놓인 순서일 뿐이다. 어느 것이 더 중요한지와는 아무 관계가 없었다.

남은 개수를 알 수 없는 구조

한 번에 하나씩 나오니 언제 끝날지를 말할 수 없었다. 이번이 마지막인지 아직 다섯이 남았는지 알 방법이 없다.

mysqldump 를 부르는 구간이 있어 한 번 돌리는 데 몇 분이 걸렸다. 그 시간이 오류 수만큼 쌓이는데 그 수를 모르니 일정도 못 잡는다.

세 번째 오류가 났을 때 방식을 바꿨다. 실행해서 만나는 대신 실행 전에 확인할 것을 훑기로 했다.

사전 준비 — 실행 전에 훑기

필요한 것을 한 번에 보는 함수를 만들었다.

def preflight():
    problems = []

    if sys.stdout.encoding.lower() not in ('utf-8', 'utf8'):
        problems.append(f"출력 인코딩: {sys.stdout.encoding}")

    for p in (DATA_DIR, LOG_DIR, TMP_DIR):
        if not os.path.isdir(p):
            problems.append(f"경로 없음: {p}")
        elif not os.access(p, os.W_OK):
            problems.append(f"쓰기 불가: {p}")

    for cmd in ('ffmpeg', 'mysqldump'):
        if shutil.which(cmd) is None:
            problems.append(f"명령 없음: {cmd}")

    return problems

preflight 는 확인만 하고 아무것도 실행하지 않는다. 그래서 중간에 안 멈추고 끝까지 훑는다.

이것을 시작할 때 부른다.

probs = preflight()
if probs:
    for p in probs:
        print(f"[사전확인] {p}")
    sys.exit(1)

problems 에 담긴 것을 전부 찍고 나서 sys.exit 한다. 하나 찾고 멈추는 것과 여기가 다르다.

한 번에 전부 나왔다

돌려 보니 네 개가 한꺼번에 나왔다.

[사전확인] 출력 인코딩: cp949
[사전확인] 경로 없음: /data/tmp
[사전확인] 쓰기 불가: /var/log/collect
[사전확인] 명령 없음: ffmpeg

하나씩 고치고 다시 돌리는 대신 네 개를 한 번에 처리했다. 네 번 돌릴 것을 한 번으로 줄인 셈이다.

실행하며 만나는 것과 preflight 로 미리 확인하는 것은 걸리는 시간이 다르고 본 작업이 오래 걸릴수록 그 차이가 커진다.

판단 기준 — 무엇을 확인할지

전부 확인할 수는 없으니 대상을 가르는 기준을 뒀다.

확인한다
  외부에 의존하는 것 (경로, 명령, 접속, 환경 변수)
  없으면 반드시 실패하는 것

확인 안 한다
  실행해 봐야 아는 것 (자료 내용, 계산 결과)
  없어도 일부만 실패하는 것

이 기준으로 추린 것은 대부분 DATA_DIR 같은 경로와 ffmpeg 같은 명령이었다. 자료 내용이 맞는지는 돌려 봐야 아는 것이라 preflight 에 넣지 않았다.

확인 자체가 오래 걸리면 본 작업만큼 느려지므로 os.path.isdirshutil.which 로 있는지만 보는 선을 넘지 않게 했다.

경고와 중단을 나눴다

걸린 것 중에 없어도 도는 것이 섞여 있었다. 하나 때문에 시작조차 못 하면 그것대로 곤란하다.

probs = preflight()
fatal = [p for p in probs if p.startswith(('경로 없음', '쓰기 불가'))]
warn  = [p for p in probs if p not in fatal]

for p in warn:
    print(f"[경고] {p}")
if fatal:
    for p in fatal:
        print(f"[중단] {p}")
    sys.exit(1)

fatal 에 걸리면 멈추고 warn 은 알리기만 하고 계속 간다. ffmpeg 은 영상 처리에만 쓰므로 없어도 나머지가 도는 쪽이었다.

startswith 로 가르는 기준을 코드에 적어 두니 다음 사람이 판단할 것이 없고 새 항목은 어느 쪽인지만 정하면 된다.

고친 것을 되짚었다

네 개를 고치고 나서 앞의 셋도 다시 봤다. 한글 문제를 고친 방식이 다른 자리에도 필요한지가 궁금했다.

# 출력 인코딩 고정
sys.stdout.reconfigure(encoding='utf-8')

sys.stdout 만 고정하면 끝이 아니었고 파일을 읽고 쓰는 자리에도 같은 명시가 필요했다.

open(path, encoding='utf-8')

encoding 을 안 준 open 이 여섯 군데 있었다. 그중 셋은 아직 안 걸렸을 뿐 언젠가 걸릴 자리였다.

하나를 고칠 때 같은 종류를 찾아보는 편이 낫다. 나중에 하나씩 만나면 그때마다 오늘 한 일을 다시 하게 된다.

검증 — 확인 결과를 기록

사전 확인이 통과했을 때도 로그를 남기게 했다.

[사전확인] 통과 (경로 3, 명령 2, 환경변수 4)

무엇을 몇 개 확인했는지가 함께 남는다. 확인 항목이 0개인데 통과로 찍히는 상황을 이것으로 거른다.

통과라는 글자만 남기면 problems 가 빈 것이 확인의 결과인지 확인을 안 한 결과인지 구분이 안 된다. 센 개수가 있어야 그 통과의 근거를 안다.

정리


Share this post on:

Previous Post
마커를 맨 마지막에 쓴다
Next Post
쓰기 전에 지워야 한다