Программа обработки ошибок
Обновлено: 23.11.2024
Крупномасштабным проектам по программному обеспечению все равно, сколько модульных тестов вы поместите в свой код. Или насколько сложен ваш конвейер CI/CD. Или насколько надежно вы запускаете сине-зеленые развертывания, чтобы облегчить ввод только что развернутого кода. Эти проекты неизбежно будут подвержены влиянию ваших пользователей, которые обнаружат ошибки, которые ваша команда не обнаружила и даже не подумала проверить.
Крупномасштабное программное обеспечение слишком сложное, чтобы в нем не было ошибок, и вы не можете протестировать все различные способы взаимодействия пользователей с вашим приложением. Это не умаляет важности надежных наборов для тестирования — они бесценны для обнаружения серьезных ошибок в критически важных приложениях или инфраструктуре — а скорее в том, что ваш жизненный цикл разработки программного обеспечения также должен учитывать мониторинг ошибок.
Проактивно предотвращая ошибки с помощью тестов и создавая полнофункциональную систему для отслеживания ошибок и исключений, которых вы не ожидали или которые не были обнаружены при тестировании, вы поддерживаете здоровый цикл разработки для своей команды. Это неизбежно означает более надежное приложение для ваших конечных пользователей.
Почему просто тестирования недостаточно
Даже если ваша команда тестирует ваше приложение, просматривает бесчисленное количество записей Hotjar о взаимодействии реальных пользователей и думает, что они все протестировали, вы все равно ограничены своим предубеждением в отношении того, как работает приложение или как команда разработчиков определила свои действия. истории пользователей.
Реальность крупномасштабных кодовых баз отличается. Когда тысячи пользователей одновременно изучают ваше приложение, они находят способы обойти все барьеры, которые могла установить ваша команда, будь то неожиданные взаимодействия или использование устройств, которые вы не планировали. Вы даже можете обнаружить, что промежуточный сервер, протестированный только вашей командой разработчиков, ведет себя совсем иначе, чем ваша производственная инфраструктура при большой нагрузке или задержке.
Как правильно обрабатывать ошибки приложения
Неправильный способ обработки ошибок приложения — переложить отчеты на пользователей. Вы, наверное, видели эти всплывающие окна «Отправить отчет об ошибке» при сбое одного из ваших приложений. Вы когда-нибудь нажимали на нее и соглашались на отправку (потенциально конфиденциальных) данных разработчику? Вы когда-нибудь слышали от них о расследовании, которое они провели, и об исправлении, которое они внедрили, чтобы облегчить вашу жизнь? Возможно нет. Нет уверенности в отправке отчетов об ошибках, а собственное исследование Raygun показывает, что только 1 % пользователей на самом деле сообщают об ошибках, с которыми они столкнулись.
Вместо этого вам нужно использовать упреждающий подход для выявления ранее неизвестных ошибок и исключений, не требуя прямой обратной связи от ваших занятых пользователей.
Мы рекомендуем начать с разработки архитектуры вашего программного обеспечения, чтобы улучшить отчеты об ошибках. Установите соглашения об именах, соответствующие бизнес-логике или связанным системам, а не капризам отдельных разработчиков, чтобы любой, кто работает с платформой мониторинга ошибок, мог быстро понять, откуда возникает проблема и какого пользователя она затрагивает. Кроме того, вы всегда должны включать информацию о версии, указывать функцию или модульный тест, связанный с ней, и создавать стандарт обработки исключений, который четко доведен до сведения всей организации.
Ошибки и исключения: что это такое?
Мы много говорим как об ошибках, так и об исключениях, поэтому имеет смысл потратить некоторое время на определение каждого из них. Для ясности и возможности адаптации для максимально возможного числа команд разработчиков мы будем придерживаться определений, не зависящих от языка.
Ошибки
Ошибка – это серьезная проблема, которую приложение обычно не решает без происшествий. Ошибки вызывают сбой приложения и, в идеале, отправляют сообщение об ошибке с некоторыми предложениями по устранению проблемы и возврату в нормальное рабочее состояние, например, просят пользователей перезапустить приложение, обновить вкладку браузера или выйти из системы и снова войти в нее.< /p>
Невозможно справиться с ошибками «вживую» или в производственной среде — единственное решение — обнаружить их с помощью мониторинга ошибок и отслеживания ошибок и направить одного или двух разработчиков для сортировки кода.
Исключения
Исключения, с другой стороны, представляют собой исключительные условия, которые, как разумно ожидать, должно обрабатываться приложением. Языки программирования позволяют разработчикам включать операторы try…catch для обработки исключений и применять последовательность логики для обработки ситуации вместо сбоя.
И когда приложение сталкивается с исключением, для которого нет обходного пути, это называется необработанным исключением, что равносильно ошибке.
Почему мониторинг ошибок так важен для крупномасштабных программных проектов
Многие разработчики думают, что путь к программному обеспечению без ошибок заключается в том, чтобы просто перехватывать каждое исключение. В таком образе мышления есть несколько недостатков, особенно когда речь идет о крупномасштабных кодовых базах и сложных приложениях.
Во-первых, у разработчиков и разработчиков продукта нет разумного способа придумать все возможные исключения в вашем приложении и разработать жизненно важную логику в коде. Рабочая нагрузка разработки была бы феноменально утомительна и отвлекала бы разработчиков от более важной работы, даже если бы вы могли.
Во-вторых, исключения могут маскировать реальные проблемы в вашем коде, применяя вторичную или третичную логику вместо функции, которую вы намеревались запустить в большинстве случаев. Если вы поймаете каждое исключение и ничего не сделаете с этой ошибкой, вы потеряете эту информацию и никогда не сможете устранить основную ошибку. Отслеживание ошибок даст вам четкое представление об их первопричине.
Как запрограммировать приложение для самостоятельного восстановления
Создание и перехват исключений — это разумный способ позволить приложению самостоятельно восстанавливаться в непредвиденных ситуациях, предотвращать состояния ошибок и предоставлять конечным пользователям более содержательную обратную связь.
Например, предположим, что пользователь вашего приложения SaaS пытается пригласить коллегу в аккаунт своей организации. Когда они вводят адрес электронной почты на страницу приглашения, они случайно пропускают символ @, что делает адрес недействительным.
Пользователь только что создал исключение. Предположим, что нет никакой логики, чтобы справиться с этим. В этом случае это необработанное исключение (ошибка), которое либо ставит приложение в неисправимую ситуацию, либо постоянно просит ваш внутренний почтовый сервер отправлять электронные письма на бессмысленный адрес. В любом случае пользователь не получает того, что хотел, и приложение не дает ему никакой полезной информации о том, что произошло. Вы можете совершенно не знать, что произошла любая из этих ситуаций.
Но вместо того, чтобы создавать ошибки, вы можете добавить на эту страницу приглашения обработку исключений для работы с недействительными сообщениями электронной почты. Вы можете проверить строку электронной почты на определенные условия (попытка) и ответить полезными сообщениями о том, что может пойти не так (уловка). Ваша обработка исключений распознает отсутствующий символ @ и мягко напомнит пользователю через пользовательский интерфейс формы о том, что ему необходимо внести изменение.
Почему важно указывать, какой тип исключения перехватывать?
При построении обработки исключений всегда следует создавать стандарты и следить за тем, чтобы все в вашей команде их принимали. То, как вы создаете эти стандарты, зависит от выбранного вами языка программирования и того, что вы пытаетесь помочь пользователям достичь.
В конце концов, цель состоит не в том, чтобы устранить все потенциальные ошибки с помощью феноменально сложных цепочек обработки исключений. Вместо этого постарайтесь решить простые проблемы и убедитесь, что знаете о сложных.
Одна из причин, по которой вам нужны стандарты для обработки исключений, и вы не хотите напрасно следовать им всем, заключается в том, что некоторые исключения могут повредить данные и оставить ваше приложение в еще худшем состоянии ошибки. Этот пример формы приглашения может быть не худшим сценарием, но логика отслеживается — если ваше приложение отправляет недействительное электронное письмо в вашу базу данных, и у пользователя нет возможности удалить или отредактировать ожидающее приглашение, они будут вынужден обратиться за помощью в службу поддержки клиентов.
По мере того, как ваша команда работает над обработкой ошибок/исключений, вы должны тщательно продумать любую логику, которая записывает данные в вашу базу данных или радикально изменяет пользовательский интерфейс, чтобы создать удобные ограждения для ваших пользователей.
Решение для обработки необработанных исключений с отслеживанием ошибок
Первое, что вам нужно сделать, это зарегистрировать необработанные исключения, используя ваши хорошо продуманные стандарты, которые будут включать важную контекстную информацию, помогающую диагностировать ошибку в вашем коде. Для небольших развертываний и кодовых баз этого уровня отчетов об ошибках может быть достаточно для обнаружения критически важных ошибок.
Но для крупномасштабных программных проектов вам нужно место для просмотра зарегистрированных ошибок и исключений, чтобы понять, как часто они происходят. Лучшее решение — это платформа мониторинга ошибок, такая как Raygun Crash Reporting, которая объединяет все ваши исключения в единую панель инструментов для тщательного наблюдения и составления отчетов о тенденциях с течением времени. Например, теперь Raygun позволяет независимо отслеживать обработанные исключения, необработанные исключения и ошибки, приводящие к остановке приложения.
Сочетая эти ресурсы вместе и благодаря надежной цепочке контекстной информации, вы можете начать расставлять приоритеты, что и когда исправлять. Вот несколько рекомендаций:
- Что нужно сделать: отдавайте предпочтение ошибкам/исключениям, обнаруженным на рабочих серверах, по сравнению с ошибками, обнаруженными на тестовом сервере.
- Нельзя: сосредотачивайтесь на тривиальных ошибках, даже если они создают много журналов исключений, только для того, чтобы все исправить.
- Что нужно сделать: работайте с исключениями, которые напрямую влияют на взаимодействие с пользователем.
- Нельзя: создавайте ключевые показатели эффективности для уменьшения количества ошибок или исключений на определенный процент или для достижения определенного числа.
- Что нужно сделать: определите приоритетность ошибок, связанных с личной идентифицируемой информацией вашего пользователя, платежным циклом и функциями, которые могут повредить вашу базу данных.
- Нельзя. Игнорируйте ошибку только потому, что пользователь не упомянул о ней в вашей службе поддержки клиентов, не разместил снимок экрана на вашем форуме или не отправил об этом недовольный твит. Помните, что только 1% пользователей сообщают об ошибках!
- Что нужно сделать: проверьте свою платформу мониторинга ошибок на наличие внезапных скачков определенных ошибок/исключений или даже типов, которые могут сигнализировать о проблеме с недавним развертыванием.
Теперь, когда у вас есть полное представление об ошибках и исключениях, вы, наконец, можете начать видеть, как ваши пользователи на самом деле проводят время в вашем приложении. Не нужно пытаться писать модульные тесты для каждого пограничного случая, не записывать каждый сеанс пользователя, чтобы понять его причуды, и не гнаться за несбыточной мечтой о приложении, на 100 % свободном от ошибок.
Ошибки и исключения неизбежны, так почему бы не использовать их для улучшения кодовой базы? Вместо того, чтобы тратить время на бесполезный поиск ошибок и беспокоиться о каждом возможном цикле try-catch, вы превращаете ошибки в возможности для улучшения взаимодействия с пользователем.
Производительность имеет значение
Лучшие статьи о производительности программного обеспечения со всего Интернета, доставляемые вам каждую неделю.
Написание программ, которые работают, когда все идет как положено, — хорошее начало. Сделать так, чтобы ваши программы вели себя правильно при возникновении непредвиденных условий, — это действительно сложная задача.
Проблемные ситуации, с которыми может столкнуться программа, делятся на две категории: ошибки программиста и реальные проблемы. Если кто-то забывает передать требуемый аргумент функции, это пример проблемы первого типа. С другой стороны, если программа просит пользователя ввести имя, а возвращает пустую строку, это то, что программист не может предотвратить.
Как правило, с ошибками программиста сталкиваются путем их обнаружения и исправления, а с подлинными ошибками - путем проверки кода и выполнения некоторых подходящих действий для их исправления (например, повторный запрос имени) или, по крайней мере, потерпеть неудачу четко определенным и понятным образом.
4.1. Ошибки и исключения¶
Важно решить, к какой из этих категорий относится определенная проблема. Например, рассмотрим нашу старую функцию power:
Когда какой-нибудь гик пытается вызвать power("Rabbit", 4) , это совершенно очевидно ошибка программиста, но как насчет power(9, 0.5) ? Функция не может работать с дробными показателями, но с математической точки зрения возведение числа в половинную степень вполне разумно ( Math.pow может с этим справиться). В ситуациях, когда не совсем ясно, какие входные данные принимает функция, часто бывает полезно явно указать тип допустимых аргументов в комментарии.
Если функция сталкивается с проблемой, которую не может решить сама, что ей следует делать? В разделе Структуры данных: объекты и массивы мы написали функцию между :
Если данные start и end не встречаются в строке, indexOf вернет -1 и эта версия between вернет много чепухи: between("Твоя мама!", "") возвращает "нашу мать" .< /p>
Когда программа запущена и функция вызывается таким образом, код, вызвавший ее, получит строковое значение, как и ожидалось, и с радостью продолжит что-то с ним делать. Но значение неправильное, поэтому все, что оно в конечном итоге сделает с ним, также будет неправильным. И если вам не повезет, эта неправильность вызовет проблему только после того, как вы пройдете через двадцать других функций. В таких случаях крайне сложно выяснить, откуда возникла проблема.
В некоторых случаях вы будете настолько безразличны к этим проблемам, что не будете возражать против неправильного поведения функции при неверном вводе. Например, если вы точно знаете, что функция будет вызываться только из нескольких мест, и вы можете доказать, что эти места дают ей достойные входные данные, как правило, не стоит делать функцию больше и уродливее, чтобы она могла обрабатывать проблемные случаи.
Но в большинстве случаев функции, которые «тихо» отказывают, сложны в использовании и даже опасны. Что, если код, вызывающий between, хочет знать, все ли прошло хорошо? На данный момент он ничего не может сказать, кроме как заново проделать всю работу, которую проделал between, и сверить результат between со своим собственным результатом. Это плохо. Одним из решений является заставить between возвращать особое значение, например false или undefined , в случае сбоя.
Вы видите, что проверка ошибок обычно не делает функции лучше. Но теперь код, который вызывает between, может делать что-то вроде:
Во многих случаях возврат специального значения является отличным способом указать на ошибку. Однако у него есть свои недостатки. Во-первых, что, если функция уже может возвращать все возможные типы значений? Например, рассмотрим эту функцию, которая получает последний элемент массива:
Итак, был ли в массиве последний элемент?Глядя на значение, которое возвращает lastElement, это невозможно сказать.
Вторая проблема с возвратом специальных значений заключается в том, что иногда это может привести к большому беспорядку. Если фрагмент кода вызывает between десять раз, он должен десять раз проверить, был ли возвращен undefined. Кроме того, если функция вызывает between, но не имеет стратегии восстановления после сбоя, она должна будет проверить возвращаемое значение between , и если оно undefined , эта функция может затем вернуть undefined или другое особое значение вызывающей стороне, которая, в свою очередь, также проверяет это значение.
Иногда, когда происходит что-то странное, было бы целесообразно просто прекратить делать то, что мы делаем, и немедленно вернуться туда, где знают, как справиться с проблемой.
Что ж, нам повезло, многие языки программирования предоставляют такую возможность. Обычно это называется обработкой исключений, а «плохая» вещь, происходящая в программе, называется исключением.
4.2. попробовать , бросить и поймать ¶
Теория обработки исключений выглядит следующим образом: код может вызвать (или выдать) исключение, которое является значением. Вызов исключения чем-то напоминает перезаряженный возврат из функции — он не просто выпрыгивает из текущей функции, но и из ее вызывающих объектов, вплоть до вызова верхнего уровня, который запустил текущее выполнение. Это называется раскручиванием стека. Возможно, вы помните стек вызовов функций, который упоминался в разделе Функции. Исключение уменьшает этот стек, отбрасывая все встречающиеся контексты вызовов.
Если бы они всегда приближались к основанию стека, от исключений не было бы толку. Они просто предоставили бы новый способ взорвать вашу программу. К счастью, в стеке можно установить препятствия для исключений. Они «перехватывают» исключение по мере его уменьшения и могут что-то с ним сделать, после чего программа продолжает работу с того места, где было перехвачено исключение.
throw — это ключевое слово, которое используется для создания исключения. Ключевое слово try создает препятствие для исключений: когда код в блоке после него вызывает исключение, будет выполнен блок catch. Переменная, указанная в скобках после слова catch, — это имя, присвоенное значению исключения внутри этого блока.
Обратите внимание, что функция lastElementPlusTen полностью игнорирует возможность того, что lastElement может пойти не так. В этом большое преимущество исключений — код обработки ошибок необходим только в момент возникновения ошибки и в момент ее обработки. Промежуточные функции могут забыть об этом.
Рассмотрите следующее. Функция processThing хочет установить переменную верхнего уровня currentThing так, чтобы она указывала на определенную вещь во время выполнения ее тела, чтобы другие функции могли иметь доступ к этой вещи тоже. Обычно вы, конечно, просто передаете это в качестве аргумента, но предположите на мгновение, что это непрактично. Когда функция завершится, для currentThing должно быть установлено значение null .
Но что, если сложная обработка вызовет исключение? В этом случае вызов processThing будет исключен из стека из-за исключения, а currentThing никогда не будет сброшен в null .
За операторамиtry также может следовать ключевое слово finally, которое означает «независимо от того, что происходит, запустить этот код после попытки запустить код в блоке try». Если функция должна что-то очистить, код очистки обычно следует помещать в блок finally:
Из-за большого количества ошибок в программах среда JavaScript вызывает исключение. Например:
В подобных случаях создаются специальные объекты ошибок. У них всегда есть свойство message, содержащее описание проблемы. Вы можете создать похожие объекты, используя ключевое слово new и конструктор Error:
Когда исключение проходит весь путь до конца стека, не будучи перехваченным, оно обрабатывается средой. То, что это означает, различается в разных браузерах, иногда описание ошибки записывается в какой-то журнал, иногда появляется всплывающее окно с описанием ошибки.
Ошибки, возникающие при вводе кода в консоли на этой странице, всегда перехватываются консолью и отображаются среди других выходных данных.
Большинство программистов рассматривают исключения исключительно как механизм обработки ошибок. По сути, однако, это просто еще один способ повлиять на поток управления программой. Например, их можно использовать как своего рода оператор break в рекурсивной функции. Вот немного странная функция, которая определяет, содержит ли объект и объекты, хранящиеся внутри него, по крайней мере семь значений true:
Внутренняя функция count рекурсивно вызывается для каждого объекта, являющегося частью аргумента. Когда переменная counted достигает семи, нет смысла продолжать подсчет, но просто возврат из текущего вызова к count не обязательно остановит подсчет, так как могут быть больше звонков под ним. Итак, что мы делаем, так это просто выбрасываем значение, которое заставит элемент управления перескакивать прямо из любых вызовов count и приземляться в блоке catch.
Но просто возвращать true в случае исключения неправильно. Что-то еще может пойти не так, поэтому мы сначала проверяем, не является ли исключением объект FoundSeven , созданный специально для этой цели. Если это не так, этот блок catch не знает, как с этим справиться, поэтому он вызывает его снова.
Этот шаблон также часто встречается при работе с ошибочными состояниями — вы должны убедиться, что ваш блок catch обрабатывает только те исключения, которые он знает, как обрабатывать. Генерация строковых значений, как это делают некоторые примеры в этой главе, редко бывает хорошей идеей, поскольку затрудняет распознавание типа исключения. Лучше использовать уникальные значения, такие как объект FoundSeven, или ввести новый тип объектов, как описано в разделе Объектно-ориентированное программирование.
4.3. Глоссарий¶
исключение Особое условие (обычно плохое), которое изменяет нормальный ход выполнения программы. обработка исключений. Конструкция языка программирования, используемая для обработки особых условий (исключений), которые изменяют обычный поток управления программой.
Разработчики пишут программу для решения некоторых проблем. Каждая проблема имеет свой набор входных данных и набор ожидаемых выходных данных. А как быть с теми входами, у которых нет выхода. Как ваше программное обеспечение отреагирует на такую ситуацию?
Нет идеального кода. Писать без ошибок — это не только миф, но и плохая практика. Если в вашем коде нет ошибок, очевидно, вы его пропустили. В каждом коде есть ошибки и уязвимости, или они могли быть. Но на вас ложится ответственность сделать ваше программное обеспечение таким, чтобы оно могло справляться с такими проблемами. Эта возможность и область известны как обработка ошибок.
Обработка ошибок — это когда ваше программное обеспечение способно противодействовать или конфисковывать ошибки, которые могли быть вызваны ошибкой программиста или реальной проблемой. Обработка ошибок является фундаментальной, а также сложной темой. Ошибки обычно случаются при самом программировании, когда разработчики забывают ожидать неожиданного при написании. Если бы кто-то додумался до этого раньше, то код был бы чистым и пригодным для использования, по крайней мере, какое-то время. Что, если ошибка не ошибка программирования, а случай, который случается редко, но иногда может случиться? Нам нужно создать разницу в таких случаях, чтобы больше узнать об этом.
Обработка ошибок и обработка исключений
Ошибка – это программный сбой, и когда он возникает, не остается ничего, кроме как вмешаться, изменить и исправить этот сбой. Но в то же время ваше программное обеспечение должно быть настроено таким образом, чтобы оно могло обрабатывать эту ошибку, не создавая других фатальных ошибок. Обработка ошибок проще в небольшом ПО, но в крупномасштабном ПО иногда это пустая трата времени. Это момент, когда обработка ошибок преобразуется в обработку исключений.
Обработка исключений — это форма обработки ошибок, с той лишь разницей, что ваш код не переходит в состояние ошибки при возникновении какой-либо проблемы. Скорее, он считывает проблему как состояние исключения и возвращает предопределенный вывод. Рассмотрим пример: программа запрашивает у вас загрузку файла, но не может его открыть и создает исключение FileNotFoundException для вашего кода. Теперь либо ваше программное обеспечение может выйти из строя, когда процесс достиг берега моря, либо вы можете заранее предвидеть эту проблему и построить мост.
Мостом может быть что-то, что пропускает этот шаг и переходит к заключению, если это мягкий процесс в реальном времени. Или может вернуть уведомление, чтобы повторить попытку еще раз или позже, если это трудный процесс в реальном времени. Построение этого случая обработки исключений важно, потому что трудно понять, когда и как может возникнуть исключение. Но иметь в коде процессы обработки исключений и ошибок очень важно.
Как реализовать обработку ошибок, не усложняя код?
Мы установили, что наш код должен иметь возможность самовосстановления в случае возникновения каких-либо проблем, и давайте теперь посмотрим, как это можно сделать, не увеличивая сложности.
Исключения так же стары, как и само программирование. Необработанное исключение может вызвать неожиданное поведение, и результаты могут быть впечатляющими. Со временем эти ошибки создали впечатление, что исключения — это плохо. Но исключения — фундаментальный элемент современного программирования. Вместо того, чтобы бояться исключений, мы должны принять их и научиться извлекать из них пользу.В этой статье мы обсудим, как элегантно управлять исключениями и использовать их для написания чистого кода, который легче поддерживать.
Ахмед — разработчик бэкенда (API), который любит создавать полезные и интересные инструменты. У него также есть опыт веб-разработчика.
Исключения так же стары, как и само программирование. В те дни, когда программирование выполнялось на аппаратном уровне или с помощью низкоуровневых языков программирования, исключения использовались для изменения потока программы и предотвращения аппаратных сбоев. Сегодня Википедия определяет исключения как:
аномальные или исключительные условия, требующие специальной обработки — часто изменяющие нормальный ход выполнения программы…
И для их обработки требуется:
специализированные конструкции языка программирования или аппаратные механизмы компьютера.
Таким образом, исключения требуют специальной обработки, а необработанное исключение может привести к неожиданному поведению. Результаты часто впечатляющие. В 1996 году знаменитый сбой запуска ракеты Ariane 5 был приписан необработанному исключению переполнения. Наихудшие ошибки программного обеспечения в истории содержат некоторые другие ошибки, которые могут быть связаны с необработанными или неправильно обработанными исключениями.
Со временем эти и бесчисленное множество других ошибок (которые, возможно, были не такими драматичными, но все же катастрофическими для причастных к ним лиц) создавали впечатление, что исключения — это плохо.
Но исключения — фундаментальный элемент современного программирования; они существуют, чтобы сделать наше программное обеспечение лучше. Вместо того, чтобы бояться исключений, мы должны принять их и научиться извлекать из них пользу. В этой статье мы обсудим, как элегантно управлять исключениями и использовать их для написания чистого кода, который легче поддерживать.
Обработка исключений: это хорошо
С появлением объектно-ориентированного программирования (ООП) поддержка исключений стала важнейшим элементом современных языков программирования. В настоящее время в большинство языков встроена надежная система обработки исключений. Например, Ruby предоставляет следующий типичный шаблон:
В предыдущем коде нет ничего плохого. Но чрезмерное использование этих шаблонов вызовет запах кода и не обязательно принесет пользу. Точно так же их неправильное использование может нанести большой вред вашей кодовой базе, сделать ее хрупкой или запутать причину ошибок.
Клеймо, окружающее исключения, часто заставляет программистов чувствовать себя растерянными. Это факт жизни, что исключений нельзя избежать, но нас часто учат, что с ними нужно справляться быстро и решительно. Как мы увидим, это не обязательно верно. Скорее, нам следует научиться изящно обрабатывать исключения, делая их гармоничными с остальной частью нашего кода.
Ниже приведены некоторые рекомендуемые методы, которые помогут вам использовать исключения и использовать их и их возможности, чтобы сделать ваш код удобным для сопровождения, расширяемым и читабельным:
- ремонтопригодность: позволяет нам легко находить и исправлять новые ошибки, не опасаясь нарушить текущую функциональность, внести дополнительные ошибки или полностью отказаться от кода из-за возрастающей сложности с течением времени.
- расширяемость: позволяет нам легко расширять нашу кодовую базу, внедряя новые или измененные требования, не нарушая существующие функциональные возможности. Расширяемость обеспечивает гибкость и обеспечивает высокий уровень повторного использования нашей кодовой базы.
- Читаемость: позволяет нам легко читать код и понимать его назначение, не тратя слишком много времени на копание. Это очень важно для эффективного обнаружения ошибок и непроверенного кода.
Эти элементы являются основными факторами того, что мы могли бы назвать чистотой или качеством, что само по себе не является прямым показателем, а представляет собой совокупный эффект предыдущих пунктов, как показано в этом комиксе:
С учетом сказанного давайте углубимся в эти методы и посмотрим, как каждый из них влияет на эти три показателя.
Примечание. Мы приведем примеры из Ruby, но все продемонстрированные здесь конструкции имеют эквиваленты в наиболее распространенных языках ООП.
Всегда создавайте собственную иерархию ApplicationError
В большинстве языков есть множество классов исключений, организованных в иерархию наследования, как и любой другой класс ООП. Чтобы сохранить удобочитаемость, ремонтопригодность и расширяемость нашего кода, рекомендуется создать собственное поддерево исключений для конкретных приложений, расширяющих базовый класс исключений. Потратить некоторое время на логическое построение этой иерархии может быть чрезвычайно полезно. Например:
Наличие расширяемого всеобъемлющего пакета исключений для нашего приложения значительно упрощает обработку таких ситуаций, связанных с приложением. Например, мы можем решить, какие исключения обрабатывать более естественным образом. Это не только повышает читабельность нашего кода, но и повышает удобство сопровождения наших приложений и библиотек (жемчужин).
С точки зрения удобочитаемости читать намного легче:
С точки зрения удобства сопровождения, например, мы реализуем JSON API и определили собственный ClientError с несколькими подтипами, которые будут использоваться, когда клиент отправляет неверный запрос. Если какой-либо из них возникает, приложение должно отобразить JSON-представление ошибки в своем ответе. Будет проще исправить или добавить логику в один блок, обрабатывающий ClientError , чем перебирать каждую возможную ошибку клиента и реализовывать один и тот же код обработчика для каждой. С точки зрения расширяемости, если позже нам придется реализовать другой тип ошибки клиента, мы можем быть уверены, что здесь она уже будет правильно обработана.
Более того, это не мешает нам реализовывать дополнительную специальную обработку определенных ошибок клиента ранее в стеке вызовов или изменять один и тот же объект исключения по пути:
Как видите, возбуждение этого конкретного исключения не помешало нам обрабатывать его на разных уровнях, изменять его, повторно вызывать и разрешать обработчику родительского класса.
- Не все языки поддерживают создание исключений из обработчика исключений.
- В большинстве языков вызов нового исключения из обработчика приведет к полной потере исходного исключения, поэтому лучше повторно вызвать тот же объект исключения (как в приведенном выше примере). чтобы не потерять первоначальную причину ошибки. (Если вы не делаете это намеренно).
Никогда не спасать исключение
То есть никогда не пытайтесь реализовать универсальный обработчик для базового типа исключения. Спасение или перехват всех исключений оптом никогда не является хорошей идеей на любом языке, будь то глобально на уровне базового приложения или в небольшом скрытом методе, используемом только один раз. Мы не хотим спасать Exception, потому что он запутает все, что произошло на самом деле, нанося ущерб как удобству сопровождения, так и расширяемости. Мы можем потратить огромное количество времени на отладку фактической проблемы, когда она может быть такой простой, как синтаксическая ошибка:
Возможно, вы заметили ошибку в предыдущем примере; возврат опечатан. Хотя современные редакторы обеспечивают некоторую защиту от этого конкретного типа синтаксической ошибки, этот пример иллюстрирует, как спасение Exception наносит вред нашему коду. Ни в коем случае фактический тип исключения (в данном случае NoMethodError ) не рассматривается и никогда не раскрывается разработчику, что может привести к тому, что мы будем тратить много времени на беготню по кругу.
Никогда не спасайте больше исключений, чем нужно
Предыдущий пункт является частным случаем этого правила: мы всегда должны быть осторожны, чтобы не слишком обобщать наши обработчики исключений. Причины те же; всякий раз, когда мы спасаем больше исключений, чем должны, мы в конечном итоге скрываем части логики приложения от более высоких уровней приложения, не говоря уже о подавлении способности разработчика самостоятельно обрабатывать исключение. Это серьезно влияет на расширяемость и удобство сопровождения кода.
Как мы увидим, часто существует другая часть приложения, которая лучше подходит для обработки определенных исключений более СУХИМ способом.
Итак, определите единственную ответственность вашего класса или метода и обработайте минимум исключений, которые удовлетворяют этому требованию ответственности. Например, если метод отвечает за получение биржевой информации из удаленного API, то он должен обрабатывать исключения, возникающие при получении только этой информации, и оставлять обработку других ошибок другому методу, разработанному специально для этих задач:< /p>
Здесь мы определили контракт для этого метода, чтобы получить информацию только об акциях. Он обрабатывает ошибки конечной точки, такие как неполный или искаженный ответ JSON. Он не обрабатывает случай, когда аутентификация не удалась или срок ее действия истек, или если акции не существует. Это чья-то ответственность, и они явно передаются вверх по стеку вызовов, где должно быть лучшее место для обработки этих ошибок СУХИМ способом.
Не поддавайтесь желанию немедленно обрабатывать исключения
Это дополнение к последнему пункту. Исключение можно обработать в любой точке стека вызовов и в любой точке иерархии классов, поэтому точное знание того, где его обрабатывать, может быть загадочным. Чтобы решить эту головоломку, многие разработчики предпочитают обрабатывать любое исключение, как только оно возникает, но время, потраченное на обдумывание этого, обычно приводит к поиску более подходящего места для обработки конкретных исключений.
Одним распространенным шаблоном, который мы видим в приложениях Rails (особенно в тех, которые предоставляют API только для JSON), является следующий метод контроллера:
(Обратите внимание, что хотя технически это не обработчик исключений, функционально он служит той же цели, поскольку @client.save возвращает false только при обнаружении исключения.)
Однако в этом случае повторение одного и того же обработчика ошибок в каждом действии контроллера является противоположностью DRY и ухудшает ремонтопригодность и расширяемость.Вместо этого мы можем использовать особый характер распространения исключений и обрабатывать их только один раз в классе родительского контроллера ApplicationController :
Таким образом, мы можем гарантировать, что все ошибки ActiveRecord::RecordInvalid правильно и DRY-ly обрабатываются в одном месте, на базовом уровне ApplicationController. Это дает нам свободу возиться с ними, если мы хотим обрабатывать определенные случаи на более низком уровне или просто позволить им корректно распространяться.
Не все исключения нуждаются в обработке
При разработке гемов или библиотек многие разработчики пытаются инкапсулировать функциональность и не допускать распространения каких-либо исключений за пределы библиотеки. Но иногда неясно, как обрабатывать исключение, пока не будет реализовано конкретное приложение.
В качестве примера идеального решения возьмем ActiveRecord. Библиотека предоставляет разработчикам два подхода для полноты. Метод save обрабатывает исключения, не распространяя их, просто возвращая false, а save! вызывает исключение при сбое. Это дает разработчикам возможность по-разному обрабатывать определенные случаи ошибок или просто обрабатывать любые сбои общим способом.
Но что, если у вас нет времени или ресурсов для такой полной реализации? В этом случае, если есть какая-либо неопределенность, лучше всего раскрыть исключение и выпустить его на свободу.
И вот почему: мы почти все время работаем с изменением требований, и решение о том, что исключение всегда будет обрабатываться определенным образом, может навредить нашей реализации, ухудшив расширяемость и ремонтопригодность, а также потенциально добавив огромный технический долг. особенно при разработке библиотек.
Возьмите более ранний пример, когда потребитель фондового API получает цены на акции. Мы решили обработать неполный и искаженный ответ на месте и решили повторить тот же запрос еще раз, пока не получим действительный ответ. Но позже требования могут измениться, и нам придется вернуться к сохраненным историческим данным о запасах, а не повторять запрос.
На этом этапе мы будем вынуждены изменить саму библиотеку, обновив способ обработки этого исключения, потому что зависимые проекты не будут обрабатывать это исключение. (Как они могли? Раньше им это никогда не было доступно.) Нам также придется информировать владельцев проектов, которые полагаются на нашу библиотеку. Это может стать кошмаром, если таких проектов много, поскольку они, скорее всего, были построены на предположении, что эта ошибка будет обрабатываться особым образом.
Теперь мы видим, куда мы движемся с управлением зависимостями. Перспектива нехорошая. Такая ситуация случается довольно часто, и чаще всего это снижает полезность, расширяемость и гибкость библиотеки.
Итак, вот суть: если неясно, как следует обрабатывать исключение, позвольте ему распространяться корректно. Есть много случаев, когда существует четкое место для внутренней обработки исключения, но есть и много других случаев, когда раскрытие исключения лучше. Поэтому, прежде чем вы решите обрабатывать исключение, просто подумайте еще раз. Хорошее эмпирическое правило — настаивать на обработке исключений только тогда, когда вы напрямую взаимодействуете с конечным пользователем.
Следуйте правилам
Реализация Ruby и тем более Rails следует некоторым соглашениям об именах, таким как различие между именами_методов и именами_методов! с «ударом». В Ruby челка означает, что метод изменит объект, который его вызвал, а в Rails это означает, что метод вызовет исключение, если он не сможет выполнить ожидаемое поведение. Старайтесь соблюдать то же соглашение, особенно если вы собираетесь открыть свою библиотеку.
Если бы мы написали новый метод! на ура в приложении Rails мы должны учитывать эти соглашения. Нет ничего, заставляющего нас генерировать исключение, когда этот метод терпит неудачу, но, отклоняясь от соглашения, этот метод может ввести программистов в заблуждение, полагая, что им будет предоставлена возможность самостоятельно обрабатывать исключения, хотя на самом деле это не так. р>
Еще одно соглашение Ruby, приписываемое Джиму Вейриху, заключается в том, чтобы использовать fail для указания на сбой метода и использовать raise только в том случае, если вы повторно вызываете исключение.
Кроме того, поскольку я использую исключения для обозначения сбоев, я почти всегда использую ключевое слово fail, а не ключевое слово повышения в Ruby. Fail и повышения являются синонимами, так что нет никакой разницы, за исключением того, что fail более четко сообщает, что метод дал сбой. Единственный раз, когда я использую повышение, — это когда я перехватываю исключение и повторно инициирую его, потому что здесь я не терплю неудачу, а явно и целенаправленно инициирую исключение. Это стилистическая проблема, которой я придерживаюсь, но я сомневаюсь, что многие другие люди ее понимают.
Многие другие языковые сообщества приняли подобные соглашения об обработке исключений, и игнорирование этих соглашений повредит удобочитаемости и ремонтопригодности нашего кода.
Logger.log(все)
Конечно, эта практика применима не только к исключениям, но если есть что-то, что должно всегда регистрироваться, так это исключение.
Ведение журнала чрезвычайно важно (достаточно важно, чтобы Ruby поставлял средство ведения журнала со своей стандартной версией). Это дневник наших приложений, и даже важнее, чем вести учет того, как наши приложения успешно работают, это регистрировать, как и когда они терпят неудачу.
Нет недостатка в библиотеках журналов, сервисах и шаблонах проектирования на основе журналов. Крайне важно отслеживать наши исключения, чтобы мы могли просмотреть, что произошло, и исследовать, если что-то не так. Надлежащие сообщения журнала могут указать разработчикам непосредственно на причину проблемы, что сэкономит им неизмеримое количество времени.
Уверенность в правильном коде
Исключения являются фундаментальной частью любого языка программирования. Они особенные и чрезвычайно мощные, и мы должны использовать их силу для повышения качества нашего кода, а не изнурять себя, борясь с ними.
В этой статье мы рассмотрели некоторые передовые методы структурирования наших деревьев исключений и то, как их логическая структура может быть полезна для удобочитаемости и качества. Мы рассмотрели различные подходы к обработке исключений либо в одном месте, либо на нескольких уровнях.
Мы увидели, что «поймать их всех» — это плохо, и что можно позволить им плавать и пузыриться.
Мы рассмотрели, где обрабатывать исключения СУХИМ способом, и узнали, что мы не обязаны обрабатывать их, когда или где они впервые возникают.
Мы обсудили, когда именно можно обрабатывать их, когда это плохо, и почему, если есть сомнения, лучше позволить им распространяться.
Наконец, мы обсудили другие моменты, которые могут помочь максимизировать полезность исключений, такие как соблюдение соглашений и регистрация всего.
Используя эти основные рекомендации, мы можем чувствовать себя намного более комфортно и уверенно при работе с ошибками в нашем коде и делать наши исключения действительно исключительными!
Особая благодарность Авди Гримму и его прекрасному выступлению Exceptional Ruby, которые очень помогли при написании этой статьи.
Читайте также: