Как стать автором
Обновить

Комментарии 5

Неплохая статья, только непонятно, почему в тегах на первом месте Питон. Имхо, эти же паттерны вполне полезны и программистам на многих других языках. У меня, например, фортран, но практически все рекомендации вполне адекватны (с точностью до механизма реализации). Больше того, я начал ими пользоваться еще за несколько лет до появления Питона как языка ;-)

Единственное дополнение к статье - про взаимодействие с юзером. Иногда прога должна сообщить, что что-то пошло не так, но после этого можно продолжить работу (восстанавливаемая ошибка). Однако, если такие сообщения повторяются слишком часто, то это быстро начнет раздражать. В такой ситуации есть смысл ограничивать количество повторных сообщений.

Например, у меня прога рисует графики временных рядов, а по нажатию кнопочки накладывает на эти графики стрелочки землетрясений. Или не накладывает, если в окне нет подходящих событий для отображения. Во втором случае надо показать MessageBox, чтобы его действие не проваливалось в пустоту. Но обычно при просмотре графиков юзер все время смещает окно вправо-влево, разворачивает и сворачивает, и т.д. Землетрясения, естественно, перерисовываются в каждом новом окне заново. Постоянное появление подобного MessageBox начинает раздражать уже со второго-третьего раза. В такой ситуации я на второй-третий раз добавляю к основному тексту "Больше не буду об этом предупреждать" (отдельной строкой, чтобы изменение сообщения сразу бросалось в глаза), и затем выключаю его вплоть до переоткрытия экрана графиков.

Для примера, под спойлером функция на фортране, которая это делает. Если Вы не знакомы с современным фортраном, и любите острые ощущения, то очень советую заглянуть ;-) Вас ждет незабываемый когнитивный диссонанс между ожиданиями от кода на фортране (который по определению должен быть нечитаемым для непосвященных), и реальностью ;-)

Код
CCCCCC      SUBR DISPLAY_EVENTS(catalog_name,max_msg_repeater)          CCCCCCCCC
C                                    с*(*)       i*4                            С
C                                                                               С
C     Процедура читает события из файла  catalog_name = каталога землетрясений  C
C   и рисует стрелочки на экране с графиками временных рядов	                C
C     Входные параметры:                                                        C
C   catalog_name        - имя файла с каталогом зтр                             C
C   max_msg_repeater    - максимальное кол-во повторных сообщений               C
C     Используются глобальные настройки формата файла-каталога и критериев выборки зтр
C     Побочные эффекты:  в случае отсутствия  зтр  пишет в лог критерии выборки C
C...............................................................................C
      SUBROUTINE DISPLAY_EVENTS(catalog_name,max_msg_repeater)
      USE CATALOG_INC   ! Модуль с описанием объекта EVENT и функций для работы с ней (фактически = класс)
      character (len=*), intent(in) ::	catalog_name   ! Неизменяемый входной параметр
      integer*4, intent(in) :: max_msg_repeater        ! Неизменяемый входной параметр
      character (len=1024)  :: msg_text         ! Каталогов с длиной имени больше 512 символов не бывает ;-)
      integer*4, save :: no_events_counter=0    ! Счетчик- локальный, но сохраняет значение между вызовами
                                                ! При первом вызове функции он будет устанолвлен в 0
      integer*4 :: displayed_events             ! Счетчик нарисованных землетрясений
c
c
c.....1. Цикл читает файл каталога и рисует стрелочку события на графике,  ЕСЛИ
c     землетрясение попало в текущее временнОе окно  И  удовлетворяет 
c     текущим ограничениям на параметры отображаемых событий (магнитуда и др.):
      if (.not.open_catalog(catalog_name)) return    ! Обработка ошибок (= сообщение юзеру) внутри
      displayed_events=0
      do while (get_event(event))                    ! Побочный эффект get_event: close(catalog) по концу файла
        if (display_event(event)) displayed_events=displayed_events+1   ! функция вернет Ok (=True), если она что-то нарисовала
      end do
      if (displayed_events > 0) return
                                                     ! Каталог открывается явным вызовом, но закрывать его яно не нужно: это сделала  get_event
                                                     ! Такой стиль не приводит к ошибкам, т.к. параметры потока хрянятся в соотв.классе (он же
                                                     ! делает буферизацию и др.), и многократное закрытие одного и того же потока не грозит ошибками
                                                     ! Также он автоматически закроется при новом вызове open либо при выходе из экрана графиков
c
c
c.....999. Блок обработки ошибок. 
c     В более сложных функциях этот блок у меня обычно начинается с метки 999, 
c     а в теле функции можно местами встретить разные проверки с последующим GOTO 999
c     (перед каждым  goto  устанавливается номер ошибки и т.д.)
c     Да-да, я в курсе, что "goto" - это плохо ;-)
c     Но вот именно в таком контексте я считаю его использование оправданным 
c     и полезным, т.к. читаемость кода в результате лишь улучшается
c
c     Обработка ошибок: печатаем сообщение не более   Max_msg_repeater  раз:
      no_events_counter=no_events_counter+1
      if (no_events_counter > Max_msg_repeater) return      ! Уходим, не прощаясь
c
c     Собственно печать. Символ "|" у меня означает переход на новую строку:
      msg_text='В каталоге '//trim(catalog_name)//'|нет ни одного события, удовлетворяющего условиям выборки'
      if (no_events_counter == Max_msg_repeater) msg_text=trim(msg_text)//'||Больше не буду об этом предупреждать.' 
      call display_msg(msg_text)
      end

На самом деле реальная функция чуть сложнее (код здесь): она сообщает не только про отсутствие событий, но и поясняет, какой из критериев выборки сыграл фатальную роль ;-) Здесь я немного ее упростил для лучшего понимания главной идеи.

Хорошая статья, спасибо за перевод. Мне не очень понятно отличие завершения приложения на проде от "падения" со стектрейсом... В любом случае приложение завершается. Разве что, в случае контролируемого завершения приложение может как-то уведомить разработчиков, но это уже ответственность не приложения, а "оркестратора".

Хотелось бы ещё разобраться что такое raise from e и raise from None, где он полезен, где необходим, где вреден. Никак не получается это уложить в голове

Находясь в обработчике исключений у вас есть 5 вариантов, что вы можете сделать:

  1. Обработать исключение и подавить его

  2. Обработать исключение и пробросить дальше - просто вызвать raise

  3. Обработать исключение и выбросить новое исключение - вызвать raise Exception()

  4. Обработать исключение и выбросить новое исключение и сохранить информацию об оригинальном - вызвать raise Exception() from e

  5. Обработать исключение и выбросить новое исключение, но скрыть информацию об оригинальном исключении - вызвать raise Exception() from None

def func():
    raise ConnectionError

# 1 вариант - обрабатывает и подавляет исключение
try:
    func()
except ConnectionError as exc:
    print('exception: ', type(exc), exc)
# получите сообщение:
# exception:  <class 'ConnectionError'> 
    
# 2 вариант - обрабатывает и пробрасывает оригинальное исключение дальше
try:
    func()
except ConnectionError as exc:
    print(exc)
    raise
# получите следующее сообщение:
# exception:  <class 'ConnectionError'> 
# Traceback (most recent call last):
#   File ".\z.py", line 6, in <module>
#     func()
#   File ".\z.py", line 2, in func
#     raise ConnectionError
# ConnectionError

# 3 вариант - обработать и выбросить новое исключение из обработчика
# это будет воспринято, как произошло новое исключение, во время обработки оригинального
try:
    func()
except ConnectionError as exc:
    raise RuntimeError('Failed to open database')
# получите сообщение:
# Traceback (most recent call last):
#   File ".\z.py", line 6, in <module>
#     func()
#   File ".\z.py", line 2, in func
#     raise ConnectionError
# ConnectionError

# During handling of the above exception, another exception occurred:

# Traceback (most recent call last):
#   File ".\z.py", line 8, in <module>
#     raise RuntimeError('Failed to open database')# from None
# RuntimeError: Failed to open database

# 4 вариант - обработать и выбросить исключение (например, кастомное исключение вашей библиотеки)
# и сохранить информацию об оригинальном исключении
try:
    func()
except ConnectionError as exc:
    raise RuntimeError('Failed to open database') from exc

# получите следующее сообщение:
# Traceback (most recent call last):
#   File ".\z.py", line 6, in <module>
#     func()
#   File ".\z.py", line 2, in func
#     raise ConnectionError
# ConnectionError

# The above exception was the direct cause of the following exception:

# Traceback (most recent call last):
#   File ".\z.py", line 8, in <module>
#     raise RuntimeError('Failed to open database') from exc
# RuntimeError: Failed to open database

# Заметьте разницу в сообщении об оригинальном исключении:
# было During handling of the above exception, another exception occurred
# стало The above exception was the direct cause of the following exception
# разница я думаю понятна

# 5 вариант - обработать и выбросить исключение (например, кастомное исключение вашей библиотеки)
# и скрыть информацию об оригинальном сообщении:
try:
    func()
except ConnectionError as exc:
    raise RuntimeError('Failed to open database') from None

# получите следующее сообщение:
# Traceback (most recent call last):
#   File ".\z.py", line 8, in <module>
#     raise RuntimeError('Failed to open database') from None
# RuntimeError: Failed to open database

# как можете видеть - оригинальное исключение скрыто

PS. Простите за спам в почте - я победил редактор

Наверное, надо еще написать, где это полезно (на мой взгляд):

  1. Первый вариант (ИМХО) допустим на вершине стека, когда дальше передавать исключение некуда.

  2. Второй вариант - когда в данном месте вам нужна дополнительная обработка исключения (могу только придумать, насытить исключение дополнительным контекстом)

  3. Третий вариант - представьте что у вас в 1М строк кода и в логах вы начинаете видеть During handling of the above exception, another exception occurred - для вас как для инженера это означает одно - произошла еще одна ошибка при обработке существующей и не известно, все ли пошло по плану - соответственно надо разбираться, что за ошибка.

  4. Четвертый вариант - вы оборачиваете исключения либо в свои библиотечные, либо просто сокращаете количество исключений, которые могут от вас вылететь (снижая нагрузку на вызывающий код), но при этом сохраняете оригинальное исключение (например, чтобы оно попало в лог и потом кто-то его расследовал)

  5. Пятый вариант как четвертый, но оригинальное сообщение вам не нужно, потому что в новом достаточно информации.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории