The Most Diabolical Python Antipattern (번역)

잘못된 코드를 작성하는 방법은 여러가지가 있습니다. 그러나 파이썬에서는 특히 하나가 더 문제입니다.

원문 : https://realpython.com/the-most-diabolical-python-antipattern/

우리는 지쳐 있었지만 기뻐했습니다. 다른 두 명의 엔지니어들이 신비한 유니코드 버그를 해결하기 위해 각각 3일 동안 노력했지만 결국 포기했고, 저는 하루 만에 원인을 격리했습니다. 그리고 10분 후, 우리는 해결 후보를 갖게 되었습니다.

비극적인 것은 우리가 7일 동안의 시간을 낭비했으며 10분 만에 문제를 해결할 수 있었다는 것이다.

여기 핵심 내용이 있다. 다음 코드 조각은 파이썬 개발자가 쓸 수 있는 가장 자기 파괴적인 것 중 하나이다

try:
    do_something()
except:
    pass

예외를 포착하는 다른 방식들도 있지만 결과적으로 같은 문제를 야기합니다. 예를 들어 except Exception: 또는 except Exception as e: 와 같은 코드입니다. 이러한 코드들은 모두 똑같이 매우 나쁜 영향을 미칩니다: 에러 상황을 조용히 그리고 눈에 띄지 않게 숨기기 때문에, 그 에러들을 빨리 발견하고 처리하기 어렵게 만듭니다.

  • 사람들은 특정 에러만 발생할 것으로 예상하고 Exception을 잡았지만, 실제로는 예기치 못한 모든 에러를 숨기게 됩니다.
  • 버그가 프로덕션 환경에서 발생하게 되는데 너무 자주 발견되면 코드 베이스의 어느 부분에서 잘못되는지 거의 또는 전혀 모를 수 있습니다. try 블록에서 오류가 발생하는지 알아내는데 엄청난 시간이 걸릴 수 있다.
  • 에러가 발생한 곳을 찾았다 해도, 중요 정부가 모두 유실되어서 트러블 슈팅하는데 방해가 된다. Error/Exception class 가 뭔지? 무엇이 호촐되었고 변수 데이터 구조는 무엇이었는지? 라인은 몇번째 줄이었는데 무슨 파일인지, 누가 호출한 것인지?
  • 스택 트레이스라는 가치 있는 정보를 버리게 되어, 버그 수정에 며칠이 아닌 몇 분만에 해결할 수도 있는 기회를 놓치게 됩니다.
  • 가장 나쁜 점은 엔지니어들의 사기와 자존감에 해가 갈 수 있다는 것입니다. 문제 원인을 찾는데 많은 시간이 걸려 자신을 나쁜 프로그래머라고 생각하게 되지만, 사실 이렇게 예외를 잡는 방식 자체가 근본적으로 디버깅을 어렵게 만듭니다.

파이썬 애플리케이션 개발 경험 10년 가까이 쌓은 저의 경험에서 이 패턴은 개발자 생산성과 애플리케이션 안정성을 가장 크게 저하시키는 단일 요소로 돋보입니다… 특히 장기적으로는 더욱 그렇습니다. 혹시 더 나쁜 패턴이 있다고 생각하신다면, 꼭 알려주세요.

Why Do We Do This To Ourselves?

물론, 아무도 의도적으로 다른 개발자를 괴롭히거나 애플리케이션의 안정성을 저하시키도록 코드를 작성하지 않습니다. 우리가 모든 예외를 잡아 처리하는 (except Exception:) 패턴을 사용하는 이유는 try 블록 안의 코드가 일반적인 작업 중에 때때로 특정한 방식으로 예외를 발생시킬 수 있기 때문입니다. 낙관적인 시도와 예외 처리 조합은 이러한 상황에 접근하기 위한 훌륭하고 완벽하게 파이썬적인 방법입니다.

하지만, 모든 예외를 잡아서 아무런 처리 없이 계속 진행하는 것은 처음에는 그리 나쁜 생각이 아닌 것처럼 보일 수 있습니다. 하지만 이런 방식으로 코드를 작성하는 순간 실제 개발 과정에서 찾아내기 어려운 최악의 종류의 버그를 만들어 낼 수 있습니다.

  • 개발 중에 탐지를 피하고 라이브 프로덕션 시스템으로 푸시 할 수있는 버그.
  • 버그가 하루 종일 발생했음을 깨닫기 전에 몇 분, 몇 시간, 며칠 또는 몇 주 동안 프로덕션 코드로 사용할 수있는 버그.
  • 문제를 해결하기 어려운 버그.
  • 억제 된 예외가 제기되는 위치를 알고 있어도 수정하기 어려운 버그.

나는 절대로 예외를 잡지 말라고 말하는 것이 아닙니다. 그곳에 있다 예외를 잡은 다음 계속해야 할 좋은 이유-단지 아니 조용히. 좋은 예는 단순히 내려 가고 싶지 않은 미션 크리티컬 프로세스입니다. 똑똑한 패턴은 예외를 포착하는 try 절을 주입하고 전체 스택 추적 심각도 logging.ERROR 또는 더 크고 계속하십시오.

The Solutions

우리가 예외를 붙잡고 싶지 않다면 대신 어떻게해야합니까? 두 가지 선택이 있습니다.

대부분의 경우 최선의 선택은보다 구체적인 예외를 잡는 것입니다. 

try:
    do_something()
# Catch some very specific exception - KeyError, ValueError, etc.
except ValueError:
    pass

이것이 가장 먼저 시도해야 할 것입니다. 호출 된 코드를 약간 이해해야하므로 어떤 유형의 오류가 발생할 수 있는지 알 수 있습니다. 다른 사람의 코드를 정리하는 대신 코드를 처음 작성할 때 더 잘 수행 할 수 있습니다.

만약 어떤 코드가 무조건 모든 익셉션을 잡아야 하는 경우 – 예를 들어 top level 의 코드 (rest API 등) – 은 full stack trace 를 기록해야 한다. 만약 logging module 사용중이라면 매우 쉽다.

import logging

def get_number():
    return int('foo')
try:
    x = get_number()
except Exception as ex:
    logging.exception('Caught an error')

logging.exception 은 trace 를 출력한다.

ERROR:root:Caught an error
Traceback (most recent call last):
  File "example-logging-exception.py", line 8, in <module>
    x = get_number()
  File "example-logging-exception.py", line 5, in get_number
    return int('foo')
ValueError: invalid literal for int() with base 10: 'foo'

What You Can Do Now

Explicitly Prohibit It In Your Coding Guidelines

너의 팀이 코드 리뷰를 한다면, 아마도 코딩 가이드라인 문서가 있을 것이다. 없다고 해도 시작하는 것은 쉽니다. 새로운 위키를 만들어서 새로운 가이드를 만들 된다. 이것만 추가 하자.

  • 어떤 코드가 반드시 모든 Exception 을 잡아야 한다면 – top-level class 이며, 장기적으로 도는 코드인 경우, rest api – 반드시 full stack trace 를 남기도록 한다. 메세지만 남기지 말고!
  • 이외 모든 에러에 대해서 – 실제로 대다수가 따라야 하는 코드 – catch 하는 Exception 은 반드시 구체적이어야 한다. KeyError 또는 ConnectionTimeout 등.

Create Tickets For Existing Overbroad Except Clauses

물론입니다. 해당 내용을 한국어로 번역하면 다음과 같습니다:

기존의 광범위한 예외 잡기(Overbroad Except Clauses)에 대한 티켓 생성

위의 방법은 새로운 문제가 코드 베이스에 들어가는 것을 방지할 수 있습니다. 하지만 기존의 광범위한 예외 잡기들은 어떻게 해야 할까요? 간단합니다: 버그 추적 시스템에 티켓이나 이슈를 생성하여 수정하도록 합니다. 이는 문제를 해결하고 잊어버리지 않을 가능성을 크게 높이는 쉬운 액션 단계입니다. 진지하게, 지금 바로 할 수 있습니다.

저는 각 저장소나 애플리케이션별로 하나의 티켓을 만들어 코드를 감사하여 Exception이 잡히는 모든 곳을 찾는 것을 권장합니다. (코드 베이스에서 “except:”와 “except Exception”을 grep하여 찾을 수 있을 것입니다.) 각각의 발생 지점에 대해, 매우 구체적인 예외 유형을 잡도록 변환하거나, 어떤 예외를 잡아야 할지 명확하지 않다면 except 블록에서 전체 스택 트레이스를 로그로 기록하도록 수정합니다.

선택적으로, 감사 개발자는 특정 try/except 블록에 대해 추가 티켓을 생성할 수 있습니다. 예외 클래스를 더 구체적으로 만들 수 있다고 생각되지만 해당 코드 부분을 충분히 잘 모르는 경우, 이렇게 하는 것이 좋습니다. 이 경우, 전체 스택 트레이스를 로그로 기록하는 코드를 넣고, 추가 조사를 위한 별도의 티켓을 생성한 후 해당 부분을 잘 아는 사람에게 할당합니다. 특정 try/except 블록에 대해 5분 이상 생각하게 된다면, 이렇게 하고 다음 부분으로 이동하는 것을 권장합니다.

의견: 이 방식은 기존의 문제를 체계적으로 해결하는 데 도움이 될 것 같습니다. 티켓을 만들어 추적하면 문제가 잊혀지거나 방치되는 것을 방지할 수 있습니다. 또한 감사 과정에서 우선순위를 정하고 적절한 담당자에게 할당하여 효율적으로 처리할 수 있을 것입니다.

Why Log The Full Stack Trace?

  1. 예외 메시지만 로깅하면 예외가 발생한 파일과 라인만 알 수 있습니다. 하지만 오류의 근본 원인이 어디에서 비롯되었는지는 알 수 없습니다. 일반적으로 완전히 다른 파일이나 모듈에서 발생할 수 있으며, 원인을 추측하기 어렵습니다.
  2. 실제 애플리케이션에서는 다중 코드 경로가 예외를 일으키는 블록을 호출할 수 있습니다. 메시지만 로깅하면 어떤 경로에서 오류가 발생했는지 구분할 수 없습니다.
  3. 50명 규모의 엔지니어링 팀에서 일할 때, 유니코드 버그 때문에 4개월 넘게 당직 엔지니어를 깨워야 했습니다. 예외는 잡혔지만 메시지만 로깅되었고, 두 명의 시니어 엔지니어가 며칠씩 작업한 끝에 포기했습니다.
  4. 절망적으로 제게 넘긴 후, 스택 트레이스를 얻기 위해 6시간 작업했습니다. 스택 트레이스를 얻자마자 10분 만에 해결책을 찾을 수 있었습니다. 처음부터 스택 트레이스를 로깅했다면 엔지니어 1주일 분량의 시간을 절약할 수 있었습니다.
  5. 이런 경험 때문에 파이썬에 대해 더 공부하게 되었고, 엔지니어로서 효과적으로 활용하는 방법을 글로 쓰기 시작했습니다.

스택 트레이스는 버그를 며칠이 아닌 몇 분 만에 해결할 수 있게 해주는 가치 있는 정보입니다. 로깅에 조금 더 노력을 기울여 전체 스택 트레이스를 기록하는 것이 개발 생산성과 애플리케이션 안정성 측면에서 이점이 큽니다

Leave a Reply

Your email address will not be published. Required fields are marked *