Comments 25
Это все вопрос философии. Сами фреймвоки на Android подталкивают разработчика к работе через исключения.
На тему того, что не надо ловить исключения — можно поспорить. Не до конца понятно, что мешает отправить себе стек и сообщение о критической ситуации не роняя приложение. И действительно ли так много ситуаций, когда нельзя позволить продолжить пользоваться приложением?
На тему того, что не надо ловить исключения — можно поспорить. Не до конца понятно, что мешает отправить себе стек и сообщение о критической ситуации не роняя приложение. И действительно ли так много ситуаций, когда нельзя позволить продолжить пользоваться приложением?
Надо ловить исключения.
Надо продолжать работу, если исключение не критичное.
Речь исключительно о пустых проверках. Лучше не делать вообще ничего, чем максировать ошибку бесполезной проверкой.
Пост и не стал бы писать, но количество примеров с пустыми максировками просто зашкаливает. Каждый автор примеров считает своим долгом напихать в код проверок, которые там не нужны.
Проверка из приведенного примера плоха в своей сути.
Нет никакого смысла писать ошибку в лог. Надо либо возбуждать исключение, либо посылать отчет либо еще как-то реагировать. Но уж точно не ограничиваться выходом с записью в лог, которую никто не увидит.
Надо продолжать работу, если исключение не критичное.
Речь исключительно о пустых проверках. Лучше не делать вообще ничего, чем максировать ошибку бесполезной проверкой.
Пост и не стал бы писать, но количество примеров с пустыми максировками просто зашкаливает. Каждый автор примеров считает своим долгом напихать в код проверок, которые там не нужны.
Проверка из приведенного примера плоха в своей сути.
Нет никакого смысла писать ошибку в лог. Надо либо возбуждать исключение, либо посылать отчет либо еще как-то реагировать. Но уж точно не ограничиваться выходом с записью в лог, которую никто не увидит.
Надо ловить исключения.
Разные типы исключений требуют разной реакции. Например, ArgumentException не нужно ни глотать, ни пробрасывать — нужно менять вызывающий код.

Если полетело исключение, которого вы не ожидали — вы ничего не можете сказать о текущем состоянии процесса. Какие объекты вообще валидны? Что с ними можно сделать?
В этой ситуации, пытаться «выкарабкаться» — это потенциальное открывание кучи векторов для атаки вашего кода. Любой баг — это потенциальная дыра в безопасности приложения. Попытка работать с багом — это еще большая дыра.
В этой ситуации, пытаться «выкарабкаться» — это потенциальное открывание кучи векторов для атаки вашего кода. Любой баг — это потенциальная дыра в безопасности приложения. Попытка работать с багом — это еще большая дыра.
Пользуюсь следующим switch'ем:
Вариант 1. Метод всегда при любых условиях должен возвращать корректное значение. Проверка не нужна. Assert тоже не нужен.
Вариант 2. Метод может вернуть некорректное значение, но логика приложения построена таким образом, что этой ситуации в принципе не должно происходить. Сталю Assert — программа упадёт сразу после нарушения инварианта.
Вариант 3. Метод может вернуть некорректное значение при одном из допустимых вариантов использования. Нужно обработать этот случай.
Дополнительно: метод (или некоторый кусок кода) уже обработан по одному из вышеперечисленных сценариев, но я знаю, что этот код в принципе может упасть (ну вот кажется, что такое возможно). Однако я могу локализовать проблему вида «этот кусок кода полностью не сработал» и обработать её именно в таком представлении. В этом случае я ставлю на весь этот кусок дополнительный try-catch (Throwable) с логированием исключения и обработкой ситуации «не понятно, что случилось, но этот кусок кода не сработал». Соответственно, если мы имеем дело с мобильным приложением, то в этом месте также нужно реализовать отправку на сервер разработчика этого события.
Вариант 1. Метод всегда при любых условиях должен возвращать корректное значение. Проверка не нужна. Assert тоже не нужен.
Вариант 2. Метод может вернуть некорректное значение, но логика приложения построена таким образом, что этой ситуации в принципе не должно происходить. Сталю Assert — программа упадёт сразу после нарушения инварианта.
Вариант 3. Метод может вернуть некорректное значение при одном из допустимых вариантов использования. Нужно обработать этот случай.
Дополнительно: метод (или некоторый кусок кода) уже обработан по одному из вышеперечисленных сценариев, но я знаю, что этот код в принципе может упасть (ну вот кажется, что такое возможно). Однако я могу локализовать проблему вида «этот кусок кода полностью не сработал» и обработать её именно в таком представлении. В этом случае я ставлю на весь этот кусок дополнительный try-catch (Throwable) с логированием исключения и обработкой ситуации «не понятно, что случилось, но этот кусок кода не сработал». Соответственно, если мы имеем дело с мобильным приложением, то в этом месте также нужно реализовать отправку на сервер разработчика этого события.
Действую примерно таким же способом.
Елинственное, не понятно, почему вы шлете репорт только на мобильной версии.
На десктопе это даже актуальнее, потому что на декстопе отсутствуют встроенные инстурменты оповещения об ошибке.
Елинственное, не понятно, почему вы шлете репорт только на мобильной версии.
На десктопе это даже актуальнее, потому что на декстопе отсутствуют встроенные инстурменты оповещения об ошибке.
Статья слишком сумбурная, очень много воды.
Очевидно, что нужно перехватывать ошибки там, где это нужно.
Если метод не должен отвечать за определенное поведение — не нужно туда его прибивать.
Очевидно, что передавая строку, которая хранит имя метода, который вы из C++ хотите вызвать, вы полагаете, что он и вызовется.
Но такой код может НЕ сработать только в одном случае — неверные исходные данные (скорее всего ошибка даже одной буквой, даже капсом в переданной строке) и в продакшн никогда не попадет, именно по этой причине мы просто говорим в логах об ошибке и выходим (повторюсь, такой результат будет только при разработке приложения).
Для продакшна можно вообще убрать все эти if-return и ничего не изменится.
Очевидно, что нужно перехватывать ошибки там, где это нужно.
Если метод не должен отвечать за определенное поведение — не нужно туда его прибивать.
Очевидно, что передавая строку, которая хранит имя метода, который вы из C++ хотите вызвать, вы полагаете, что он и вызовется.
Но такой код может НЕ сработать только в одном случае — неверные исходные данные (скорее всего ошибка даже одной буквой, даже капсом в переданной строке) и в продакшн никогда не попадет, именно по этой причине мы просто говорим в логах об ошибке и выходим (повторюсь, такой результат будет только при разработке приложения).
Для продакшна можно вообще убрать все эти if-return и ничего не изменится.
Полный код, который вы критикуете
Тут явно делается попытка вызова по переданному указателю на строку. Любой if-return можно заменить на «Девелопер накосячил и такого метода мы не находим, поправьте имя и запустите еще раз».
static void callback_handler(char *s) {
int status;
JNIEnv *env;
bool isAttached = false;
status = gJavaVM->GetEnv((void **) &env, JNI_VERSION_1_4);
if(status < 0) {
LOGE("callback_handler: failed to get JNI environment, "
"assuming native thread");
status = gJavaVM->AttachCurrentThread(&env, NULL);
if(status < 0) {
LOGE("callback_handler: failed to attach "
"current thread");
return;
}
isAttached = true;
}
/* Construct a Java string */
jstring js = env->NewStringUTF(s);
jclass interfaceClass = env->GetObjectClass(gInterfaceObject);
if(!interfaceClass) {
LOGE("callback_handler: failed to get class reference");
if(isAttached) gJavaVM->DetachCurrentThread();
return;
}
/* Find the callBack method ID */
jmethodID method = env->GetStaticMethodID(
interfaceClass, "callBack", "(Ljava/lang/String;)V");
if(!method) {
LOGE("callback_handler: failed to get method ID");
if(isAttached) gJavaVM->DetachCurrentThread();
return;
}
env->CallStaticVoidMethod(interfaceClass, method, js);
if(isAttached) gJavaVM->DetachCurrentThread();
}
«В продакшн не попадет» — откуда такое утверждение?
У нас как раз код с похожей ошибкой мог уйти в продакшн, если бы его замаскировали проверкой и выводом в лог.
Метод активирующий вибрацию содержал ошибку в названии. На устройствах программистов просто нет вибрации и ошибку никто не засек.
Засекли, когда на устройстве тестера игра свалилась в исключение.
Была бы там маскировка и вывод в лог — вполне могли пропустить в продакшн, тесте вполне мог не обратить внимание что вибрация не срабатывает когда должна, а уж лог анализировать в его задачи не входит.
А так исключение, баг репорт, фикс. Все счастливы.
Каким образом вы гарантируете, что баг с такой маскировкой не пройдет мимо тестера?
И вообще, зачем молчать о баге на этапе тестирования?
По поводу конкретного примера — как я уже сказал и в этом примере маскировка совершенно лишняя.
Ну и в целом пример значения не имеет. Примеров таких миллиард и простые эникейщики перетаскивают эти првоерки даже не задумываясь как они будут работать в реальном продукте.
У нас как раз код с похожей ошибкой мог уйти в продакшн, если бы его замаскировали проверкой и выводом в лог.
Метод активирующий вибрацию содержал ошибку в названии. На устройствах программистов просто нет вибрации и ошибку никто не засек.
Засекли, когда на устройстве тестера игра свалилась в исключение.
Была бы там маскировка и вывод в лог — вполне могли пропустить в продакшн, тесте вполне мог не обратить внимание что вибрация не срабатывает когда должна, а уж лог анализировать в его задачи не входит.
А так исключение, баг репорт, фикс. Все счастливы.
Каким образом вы гарантируете, что баг с такой маскировкой не пройдет мимо тестера?
И вообще, зачем молчать о баге на этапе тестирования?
По поводу конкретного примера — как я уже сказал и в этом примере маскировка совершенно лишняя.
Ну и в целом пример значения не имеет. Примеров таких миллиард и простые эникейщики перетаскивают эти првоерки даже не задумываясь как они будут работать в реальном продукте.
Нужно разделять баги от криворукости программиста.
Человеку дана задача — прикрутить вибрацию. Он поставил результат данного таска в complete, даже не проверив — это вопрос компетенции. В продакшне этот код (пусть даже УЖЕ) никогда не вызовет ошибку. Значит данные перехваты исключений были написаны лишь для удобства отладки, о чем я и писал. Но компетентному сотруднику будет достаточно и тех же if-return. Я как программист гарантирую, что такого рода баги не дойдут до тестировщика.
Возможно, вы слишком неудачно выбрали пример.
Если бы такой if-return применили внутри метода saveUserDataToSdCard(), т.е. данные не записались, а я ничего об этом не знаю — я бы огорчился.
Человеку дана задача — прикрутить вибрацию. Он поставил результат данного таска в complete, даже не проверив — это вопрос компетенции. В продакшне этот код (пусть даже УЖЕ) никогда не вызовет ошибку. Значит данные перехваты исключений были написаны лишь для удобства отладки, о чем я и писал. Но компетентному сотруднику будет достаточно и тех же if-return. Я как программист гарантирую, что такого рода баги не дойдут до тестировщика.
Возможно, вы слишком неудачно выбрали пример.
Если бы такой if-return применили внутри метода saveUserDataToSdCard(), т.е. данные не записались, а я ничего об этом не знаю — я бы огорчился.
Повторюсь
Очевидно, что нужно перехватывать ошибки там, где это нужно.
Если метод не должен отвечать за определенное поведение — не нужно туда его прибивать.
Если метод не должен отвечать за определенное поведение — не нужно туда его прибивать.
Ну это клево, когда фирма может для разработки мелких андроид игр нанимать профессионалов с зарплатой в 2-3 000$. Но не все так могут. Гораздо проще научиьт человека выдавать код легко проверяемый тестами, чем нанимать супер профи.
Смею предположить что не только мы используем эникейщиков для простых задач… Судя по тому, как много косяк в существующем софте. Видимо программисты пищущие фотошоп, open office и другой софт не настолько круты и не могут гарантировать что их ошибки не дойдут до продакшена. :)
Смею предположить что не только мы используем эникейщиков для простых задач… Судя по тому, как много косяк в существующем софте. Видимо программисты пищущие фотошоп, open office и другой софт не настолько круты и не могут гарантировать что их ошибки не дойдут до продакшена. :)
Обожаю приложения, которые исходя из этой логики сносят результат работы, потому что не смогли обработать какую-то мелочь. Приложение ошибки может и логгировать, и разработчику слать, но падать — это дурной тон. Особенно, если приложение statefull. Падающий фанарик ещё пережить можно, падающее приложения для фотографий, которое скопычивается не сохранив Главную Фотку Всей Жизни — это подлость и гадость.
Пусть лучше оно падает, чем делает вид, что все нормально и при этом не сохраняет Главную Фотку Всей Жизни. Имеено об этом автор и попытался рассказать.
Оно не должно падать. Оно должно выдать сообщение «не могу сохранить фото»! Или даже пусть «произошла ошибка доступа к файловой системе. Попробуйте повторить». И параллельно отправить отчет разработчику. Если вы знаете, что если вот тут прилетят кривые данные на вход, то приложение упадёт — надо этот момент обработать, а не оставлять в виде «когда упадёт, тогда и раскопаем».
Понятно, что так надо, никто и не говорит что так не надо. Однако мы живем в несовершенном мире и вы не представляете сколько раз я видел следующее:
1. пустые catch в которых просто ничего не делается
2. как в примере логирование и возврат из функции
3. просто игнорирование возвращаемого значения (кстати это один из пунктов почему исключения лучше)
и тп.
Вообще говоря создание приложения которое правильно обрабатывает даже большую часть ошибок и при этом продолжает работать очень не простое дело и что характерно достаточно дорогое.
В итоге, выбирая из 2х зол меньшее, лучше пусть программисты вообще не обрабатывают ошибки, чем маскируют их.
1. пустые catch в которых просто ничего не делается
2. как в примере логирование и возврат из функции
3. просто игнорирование возвращаемого значения (кстати это один из пунктов почему исключения лучше)
и тп.
Вообще говоря создание приложения которое правильно обрабатывает даже большую часть ошибок и при этом продолжает работать очень не простое дело и что характерно достаточно дорогое.
В итоге, выбирая из 2х зол меньшее, лучше пусть программисты вообще не обрабатывают ошибки, чем маскируют их.
Как-то статья ни о чем… попробую пересказать кратко:
1) не делайте «заглушки» на перехваченные исключения — их надо обработать как-то в логике приложения. Кэп плачет от умиления.
2) позволяйте приложению падать по любому поводу. Тут не согласен — нужно не падать, а слать сообщение разработчику, плюс первый пункт — как-то отработать такую ситуацию, а не просто её игнрировать.
1) не делайте «заглушки» на перехваченные исключения — их надо обработать как-то в логике приложения. Кэп плачет от умиления.
2) позволяйте приложению падать по любому поводу. Тут не согласен — нужно не падать, а слать сообщение разработчику, плюс первый пункт — как-то отработать такую ситуацию, а не просто её игнрировать.
Как правило, заглушки как раз и появляются, когда программист пытается «как-то отработать». Пишется код вызова какой-то функции, по описанию видится, что она какое-то там исключение генерирует, пишется тривиальный обработчик этого исключения в надежде актуализировать его потом… и тут релиз.
Соответственно, слать разработчику, если это возможно, надо, но делать это надо, всё-таки, с последующим повторным киданием исключения.
А препятствовать всплыванию исключения только в том случае, если точно знаешь, что это за исключение такое и чем его игнорирование/обработка в данном месте грозит.
Соответственно, слать разработчику, если это возможно, надо, но делать это надо, всё-таки, с последующим повторным киданием исключения.
А препятствовать всплыванию исключения только в том случае, если точно знаешь, что это за исключение такое и чем его игнорирование/обработка в данном месте грозит.
В свое время для своих команд я разрабатывал стандарты кодирования, включая как правильно использовать исключения. По итогу мы пришли к двум пунктам:
— использовать исключения только для исключительных ситуаций, а не для логики. Например, правильно — проверить существование файла и открывать его, иначе писать сообщение об ошибке (для ситуаций, когда файл может не существовать на диске, например, если мы используем данные, введенные пользователем). Неправильно — сразу пытаться открыть файл, и проверять существование файла обработчиком исключений. Конечно, это очень соблазнительно — все сконцентрировать в одном месте, однако это чревато для сопровождения, потому что будущие разработчики могут подумать, что в данной функции всегда предполагается существование файла. То же самое касается проверки кодов ошибок — если проверка является частью логики (например, для программирования OpenSSL это единственный вариант), тогда надо ее использовать. Если нет — тогда позволять возникать исключениям.
— исключения должны перехватываться только там, где мы знаем, что с ними делать. Писать в лог только для того, что бы что-то написать практически бесполезно, и в основном используется только для отладки. Правильное решение — это выкидывать исключения наверх до тех пор, пока мы не сможем сделать что-то полезное с ними. Например, мы ничего не сможем сделать с исключением «метод не найден» (потому что это косяк программиста), но мы сможем что-то сделать с исключением «нехватка памяти» (например, выгрузить что-то из памяти, освободить кэши, но для этого надо чтобы менеджер кэшей получил это исключение). Для кодов ошибок это не так тривиально, поэтому иногда приходится создавать собственную иерархию исключений.
Теперь конкретно по примерам Android NDK — изначально исключения не поддерживаются, надо специально менять mk-файлы. Также в основном примеры для C, в котором нет исключений. Проверка ошибки в вышеприведенном примере в идеале не нужна, вы правильно отметили, однако она нужна для отладки и в целом для облегчения жизни программистов: JNI — не самая удобная платформа для взаимодействия Java с native-кодом, особенно учитывая магию типа названий native-функций Java_<class_path_goes_here>_<method_name_goes_here>.
— использовать исключения только для исключительных ситуаций, а не для логики. Например, правильно — проверить существование файла и открывать его, иначе писать сообщение об ошибке (для ситуаций, когда файл может не существовать на диске, например, если мы используем данные, введенные пользователем). Неправильно — сразу пытаться открыть файл, и проверять существование файла обработчиком исключений. Конечно, это очень соблазнительно — все сконцентрировать в одном месте, однако это чревато для сопровождения, потому что будущие разработчики могут подумать, что в данной функции всегда предполагается существование файла. То же самое касается проверки кодов ошибок — если проверка является частью логики (например, для программирования OpenSSL это единственный вариант), тогда надо ее использовать. Если нет — тогда позволять возникать исключениям.
— исключения должны перехватываться только там, где мы знаем, что с ними делать. Писать в лог только для того, что бы что-то написать практически бесполезно, и в основном используется только для отладки. Правильное решение — это выкидывать исключения наверх до тех пор, пока мы не сможем сделать что-то полезное с ними. Например, мы ничего не сможем сделать с исключением «метод не найден» (потому что это косяк программиста), но мы сможем что-то сделать с исключением «нехватка памяти» (например, выгрузить что-то из памяти, освободить кэши, но для этого надо чтобы менеджер кэшей получил это исключение). Для кодов ошибок это не так тривиально, поэтому иногда приходится создавать собственную иерархию исключений.
Теперь конкретно по примерам Android NDK — изначально исключения не поддерживаются, надо специально менять mk-файлы. Также в основном примеры для C, в котором нет исключений. Проверка ошибки в вышеприведенном примере в идеале не нужна, вы правильно отметили, однако она нужна для отладки и в целом для облегчения жизни программистов: JNI — не самая удобная платформа для взаимодействия Java с native-кодом, особенно учитывая магию типа названий native-функций Java_<class_path_goes_here>_<method_name_goes_here>.
Для исключений(именно как средство отлова ошибок) видел вот такой метод:
Решение более чем спорное, но интересное и результат дает — на выходе отчет об ошибке с call stack внутри.
Хотя мне видится, что включить исключения является значительно более корректным решением.
class cExceptionHack{
protected:
int m_Data;
public:
void throwException(){
m_Data++;
};
};
cExceptionHack* Exception = NULL;
Exception->throwException();
Решение более чем спорное, но интересное и результат дает — на выходе отчет об ошибке с call stack внутри.
Хотя мне видится, что включить исключения является значительно более корректным решением.
Например, правильно — проверить существование файла и открывать его, иначе писать сообщение об ошибке (для ситуаций, когда файл может не существовать на диске, например, если мы используем данные, введенные пользователем). Неправильно — сразу пытаться открыть файл, и проверять существование файла обработчиком исключений.
В момент между проверкой на существование и чтением файл вполне может исчезнуть. Например, пользователь удалил файл или вообще выдернул диск. Так что обрабатывать такие исключения нужно в любом случае.
Кстати, именно поэтому в Java исключения делятся на проверяемые и непроверяемые. Причину непроверяемого исключения (например, нулевую ссылку) можно проверить в самой программе. Поэтому в нормальных условиях они возникать не должны. Проверяемые же исключения таким образом предупредить невозможно — они обычно вызваны внешними событиями.
То, о чем Вы написали, имеет название — Fail fast.
Sign up to leave a comment.
Исключение — твой друг