Я знаю о «всегда в операциях DML». Но для чего? Что мы получаем? Код все равно рухнет, и мы можем найти ошибку в логах даже без этой конструкции. В чем суть try/catch? И что именно мы можем сделать в catch, кроме System.debug() или отправки оповещения по электронной почте с сообщением об ошибке? Буду благодарен за ответы с примерами.

18
Kris Goncalves 17 Апр 2020 в 15:57
3
try-catch предназначен не для разработчиков, а для конечных пользователей. Это делается для того, чтобы мы, разработчики, могли предоставлять удобные сообщения, которые они могут понять. Такие ошибки, как NullPointerException: ..., почти нечитаемы для обычного пользователя.
 – 
Drew Kennedy
24 Фев 2020 в 22:01
2
Я отозвал свой закрытый голос и призываю других сделать то же самое - это превратилось в очень хорошее обсуждение использования try/catch.
 – 
battery.cord
25 Фев 2020 в 00:48
@battery.cord Я не vtc, но я полагаю, что это все еще не по теме, поскольку ответ на этот вопрос много раз обсуждался на переполнении стека и, возможно, на нескольких других сайтах SE. То, что это try-catch на SF, ничего не меняет.
 – 
Aequitas
25 Фев 2020 в 07:55
1
Это "крах", а не "крах".
 – 
JRE
26 Фев 2020 в 13:36

5 ответов

В чем суть try/catch?

Чтобы поймать и обработать исключение. Обработка является ключом.

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

  • Целостность задействованных данных.
  • Опыт пользователя.
  • Результат процесса, если это возможно.

Давайте посмотрим на пару примеров.

Триггеры

Вы пишете триггер. Триггер принимает данные, измененные пользователем, обрабатывает их и обновляет в другом месте. Он выполняет DML и не использует, например, Database.update(records, false), что означает, что сбои вызовут исключение. (Если вы используете методы с частичным успехом, применяются те же принципы, просто они работают по-другому, потому что ошибки приходят к вам в объектах Result вместо исключений).

Здесь вы должны ответить как минимум на два важных вопроса:

  • Поддаются ли устранению сбои, с которыми я сталкиваюсь?
  • Каков характер манипуляций с данными, которые я делаю? Если это не удается, означает ли это, что вся операция (включая изменение, внесенное пользователем) недействительна? То есть, если я разрешаю пользовательские изменения выполняться без работы, которую я делаю, не нарушил ли я целостность пользовательских данных?

Эти вопросы определяют, как вы будете реагировать на исключение.

Если вы знаете, что конкретное исключение может быть вызвано способом, который вы можете исправить, ваш обработчик должен просто исправить его и повторить попытку. Это было бы настоящей «обработкой» исключения. Однако в Apex, где исключения обычно не используются для управления потоком, такая ситуация несколько менее распространена, чем, например, в. Питон. Тем не менее, один пример, где я лично реализовал такой обработчик, находится в Queueable, который пытается заблокировать запись FOR UPDATE. В той ситуации, когда мне нужно было избежать потенциального состояния гонки, перехват QueryException по истечении времени ожидания этого запроса и просто повторная постановка Queueable в очередь для повторной попытки были правильным шаблоном.

Но в большинстве случаев это не ваша ситуация при создании в Apex. Это второй вопрос, который, как правило, определяет подходящий шаблон реализации, и именно поэтому я стараюсь избегать обработчиков исключений во многих случаях.

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

Итак, еще раз, чтобы сделать это конкретным: вы создаете триггер, задачей которого является обновление долларовой стоимости возможности, когда пользователь обновляет связанный платеж. Ваше обновление возможности может вызвать DmlException; Что вы делаете?

Задайте вопросы: Можно ли решить проблему в Apex самостоятельно? Нет. Если вы допустите сбой обновления возможности, в то время как обновление платежа успешно, вы потеряете целостность данных? да.

Пусть исключение будет поднято и обработано или позволено вызвать откат на более высоком уровне.

Некритическая внутренняя функциональность

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

  • Могу ли я решить проблему? Нет.
  • Влияет ли проблема на целостность данных, если я позволю ей продолжить? Также нет.

Итак, вот ситуация, когда может иметь смысл обернуть код отправки в блок try/catch и регистрировать исключения, связанные с электронной почтой, с помощью структуры ведения журнала качества. Затем не вызывайте повторно — используйте исключение и разрешите продолжение транзакции.

Вы можете не захотеть блокировать обновление дела, потому что у какого-то пользователя в системе неверный адрес электронной почты!

Функциональность, ориентированная на пользователя

Теперь переверните страницу на Lightning и Visualforce. Здесь вы строите уровень контроллера, интерпретируя ввод данных пользователем и базу данных.

Вы представляете кнопку, которая позволяет пользователю выполнить некоторую сложную операцию, которая может вызвать несколько видов исключений. Какова ваша стратегия обращения?

Здесь гораздо чаще и даже предпочтительнее использовать широкие обработчики исключений, которые на самом деле не обрабатывают исключение, но выполняют откат для сохранения целостности данных, а затем отображают понятное сообщение об ошибке для пользователя.

Например, в Visualforce вы можете сделать что-то вроде этого:

Database.Savepoint sp = Database.setSavepoint();
try {
    doSomeVeryComplexOperation(myInputData);
} catch (Exception e) { // Never otherwise catch `Exception`!
    Database.rollback(sp); // Preserve integrity of the database.
    ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.FATAL, 'An exception happened!'));
}

Это удобно для пользователя — показывает, что семантическая операция более высокого уровня, которую они попытались выполнить, потерпела неудачу (и вы, возможно, захотите включить фактическую ошибку, чтобы дать им шанс ее исправить) — и это также удобно для базы данных, потому что вы убедитесь, что ваша обработка исключения не влияет на целостность данных.

Еще лучше было бы конкретизировать сбой, используя несколько блоков catch (где это применимо):

Database.Savepoint sp = Database.setSavepoint();
try {
    doSomeVeryComplexOperation(myInputData);
} catch (DmlException e) { 
    Database.rollback(sp); // Preserve integrity of the database.
    ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.FATAL, 'Unable to save the data. The following error occurred: ' + e.getMessage()));
} catch (CalloutException e) {
    Database.rollback(sp); // Preserve integrity of the database.
    ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.FATAL, 'We could not reach the remote system. Please try again in an hour.');
}

В контроллерах Lightning (Aura) вместо этого вы повторно бросите AuraHandledException.

Одна вещь, которую нельзя делать

Этот:

try {
    // do stuff
} catch (Exception e) {
    System.debug(e);
}

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

Если вам не нужно предпринимать какие-либо действия в отношении исключения, зарегистрируйте его таким образом, чтобы пользователь мог просмотреть его и принять соответствующие меры. Никогда не глотайте его.

Подумайте, как вы должны были бы ответить на приведенные выше вопросы, чтобы сделать этот образец хорошим. Вы хотели бы проглотить такое исключение, когда:

  • То, что вы делаете, не влияет на целостность данных.
  • Никто не заметит и не будет заботиться о том, что функциональность не работает.

Если и то, и другое имеет место, я подозреваю, что в системе есть конструктивная ошибка!

Этот паттерн также делает отладку мучительной, потому что ваш код тихо дает сбой, когда он должен кричать в виде исключения.

40
David Reed 25 Фев 2020 в 00:28
4
Это очень хороший ответ
 – 
cropredy
24 Фев 2020 в 22:56
1
Приятно видеть лучшие практики в разных контекстах научной фантастики :bow:
 – 
Brian Miller
24 Фев 2020 в 22:59
Каков наилучший подход к обработке исключений сценария после установки в управляемом пакете? Похоже на то, что мы хотели бы catch, чтобы это не препятствовало установке в целом (постустановка не критична, и можно использовать ручные постустановочные шаги), но я также не решается выполнить какие-либо команды отправки электронной почты на тот случай, если эти настройки электронной почты отключены (например, настройки по умолчанию в песочнице...)
 – 
Brian Miller
25 Фев 2020 в 00:38
1
Это отличный вопрос, и, честно говоря, я не думаю, что написал достаточно сценариев после установки (1? может быть?), чтобы правильно ответить на него.
 – 
David Reed
25 Фев 2020 в 00:49
1
Как клиент, я бы предпочел, чтобы установка завершилась неудачно с каким-то сообщением о действиях, чем чтобы пакет был установлен в недопустимом состоянии.
 – 
battery.cord
25 Фев 2020 в 00:56

всегда в операциях DML

Это распространенное заблуждение. Если вы используете частичное сохранение (например, Database.insert(records, false);), вам никогда не понадобится try-catch, так как любое исключение изящно преобразуется во что-то, что ваш код может обработать (кроме LimitException, которое в любом случае бесконтрольно уничтожит транзакцию). ).

Вы должны поймать DmlException, если не используете частичное сохранение. Когда это происходит, вы хотите отобразить соответствующую ошибку пользователю и, при желании, зарегистрировать ошибку/отправить электронное письмо/и т. д. Вы определенно не должны не использовать try-catch, если единственное, что вы собираетесь сделать, это System.debug, так как это скрывает ошибки и создает проблемы в долгосрочной перспективе.

На самом деле я поддерживаю философию «как можно меньше блоков try-catch». Если у вас нет причин пытаться поймать, не пытайтесь поймать. Это зависит от контекста. В триггере всегда следует использовать частичные сохранения и откаты. В контроллере вы должны попытаться поймать, чтобы предотвратить сдувание ввода данных пользователя, если не используются частичные сохранения. В контроллере Aura вы должны использовать try-catch для преобразования в AuraHandledException (необязательно, если вы просто используете частичные обновления для начала).

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

16
sfdcfox 24 Фев 2020 в 22:24

Кажется, у вас есть некоторые неправильные представления об исключениях.

  • Я не знаю, сказал бы я, что все операции DML должны быть внутри блока try
  • Исключения могут содержать полезную информацию для отладки, но если вы «проглотите» исключение (имеете блок catch, который ничего не делает), это усложнит отладку.
  • Исключение не обязательно означает конец вашего исполнения. Вы потенциально можете восстановить и продолжить

Как и все остальное, исключения — это инструмент, предназначенный для использования в определенных ситуациях.

По исключениям

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

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

Есть лучшие способы справиться с этой конкретной ситуацией (использование исключений вместо операторов управления потоком, таких как if/else, for, while и т. д., является плохой практикой), но это иллюстрирует суть.

Еще одно применение, которое я нашел, - это предотвращение утечки внутренних данных в стороннее приложение (ну, во всяком случае, в большинстве случаев). Эта сторонняя система ничего не может сделать с исключением Salesforce, поэтому я фиксирую его и пытаюсь представить более разумную ошибку (например, «Не удалось найти этот идентификатор в Salesforce, вы уверены, что отправили его нам раньше?» )

Особенности отдела продаж

Недостаток, добавленный Salesforce, заключается в том, что некоторые исключения предназначены для того, чтобы их нельзя было отловить, чтобы не допустить, чтобы неверный код одного клиента повлиял на других клиентов (путем использования непропорционального объема вычислительных ресурсов).

Я не думаю, что есть какие-либо ограничения на код, который можно запустить в блоке catch. Ведение журнала и другие уведомления являются наиболее распространенными случаями, но есть и другие хорошо зарекомендовавшие себя способы использования, такие как обработка частичного сбоя DML.

6
Community 15 Июн 2020 в 11:11

Очень распространенный вариант использования — это циклы, которые должны продолжаться, даже если 1 элемент выдает ошибку.

Пример в peusdo cose:

for each user of users {
    try {
        send mail to user
    } catch (Exception e) {
        do whatever you want with the error
    }
}

Для 10 пользователей, если у вас был тот же код без try/catch, и он выдает ошибку для пользователя с индексом 3, то пользователи с индексом от 4 до 9 не получат свою электронную почту.

0
Vincent 25 Фев 2020 в 18:02
2
Я думаю, стоит отметить, что для таких операций, как добавление или отправка электронных писем, оба метода имеют параметр «Все или ничего», который был бы лучшим решением для такого случая. Перехват исключений — дорогостоящая операция, поэтому попытки и перехват в таком цикле for — это не то, что вы хотели бы делать для больших наборов данных.
 – 
nbrown
25 Фев 2020 в 18:38

Try/catch следует использовать высоко в коде, чтобы прервать разумную единицу приложения, чтобы другие подобные единицы все еще могли быть обработаны.

  • Для веб-сервера это чаще всего запрос к серверу.
  • Для GUI это ответ на одно действие пользователя, такое как нажатие клавиши или выбор меню.
  • Для такой команды, как приложение, часто может быть завершено все приложение.

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

Код обработки ошибок редко заботится о том, что именно не удалось - просто зарегистрируйтесь и прервите работу. В Java была сложная иерархия исключений с объявлениями о том, кто что может генерировать, вероятно, предполагая, что разные исключения будут обрабатываться какими-то очень индивидуальными способами. С другой стороны, в C# нет даже ключевого слова throws, вы можете генерировать любое исключение из любого места. Наконец, у Google Go есть только одно исключение на все случаи жизни (паника), и этого кажется достаточно. Его еще можно поймать.

Код, который не использует исключения, вместо этого имеет множество операторов return, что затрудняет выход из глубоко вложенных вызовов.

0
h22 25 Фев 2020 в 20:09
3
Добро пожаловать в СФСЭ. Я думаю, что этот ответ будет улучшен с помощью конкретного приложения для платформы Salesforce и языка программирования Apex. Сейчас неясно, как применить этот ответ для работы на платформе Salesforce.
 – 
David Reed
25 Фев 2020 в 20:14