Использование try/catch для предотвращения сбоя приложения

Я работаю над Android-приложением, которое часто использует try/catch, чтобы предотвратить его сбой даже в тех местах, где нет необходимости. Например,

Вид в xml layout с id = toolbar ссылается как:

// see new example below, this one is just confusing
// it seems like I am asking about empty try/catch
try {
    View view = findViewById(R.id.toolbar);
}
catch(Exception e) {
}

Этот подход используется во всем приложении. Трассировка стека не печатается, и очень сложно найти, что пошло не так. Приложение неожиданно закрывается, не распечатывая трассировку стека.

Я попросил моего старшего объяснить это мне, и он сказал:

Это для предотвращения сбоев в производстве.

Я полностью не согласен с ним. Для меня это не способ предотвратить сбои приложений. Он показывает, что разработчик не знает, что он/она делает и сомневается.

Этот подход используется в промышленности для предотвращения сбоев корпоративных приложений?

Если try/catch действительно, действительно ли наша потребность в этом случае, можно ли связать обработчик исключений с потоком пользовательского интерфейса или другими потоками и поймать все там? Это будет лучший подход, если это возможно.

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

UPDATE

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

Вот что здесь делают разработчики

  • Функция написана и проверена, она может быть небольшой функцией, которая только инициализирует представления или сложную, после тестирования она обернута вокруг блока try/catch. Даже для функции, которая никогда не вызовет каких-либо исключений.

  • Эта практика используется во всем приложении. Иногда выполняется трассировка стека, а иногда только debug log с некоторым случайным сообщением об ошибке. Это сообщение об ошибке отличается от разработчика разработчиком.

  • При таком подходе приложение не падает, но поведение приложения становится неопределенным. Даже когда-то трудно следить за тем, что пошло не так.

  • Реальный вопрос, который я задавал; Является ли практика в отрасли предотвращением сбоев корпоративных приложений?, и я не спрашиваю о пустой try/catch. Похоже, пользователи любят приложение, которое не падает, чем приложения, которые ведут себя неожиданно? Потому что это действительно сводится к тому, чтобы либо свернуть его, либо представить пользователю пустой экран, или пользователь, о котором не знает поведение, не знает.

  • Я размещаю несколько фрагментов из реального кода здесь

      private void makeRequestForForgetPassword() {
        try {
            HashMap<String, Object> params = new HashMap<>();
    
            String email= CurrentUserData.msisdn;
            params.put("email", "blabla");
            params.put("new_password", password);
    
            NetworkProcess networkProcessForgetStep = new NetworkProcess(
                serviceCallListenerForgotPasswordStep, ForgotPassword.this);
            networkProcessForgetStep.serviceProcessing(params, 
                Constants.API_FORGOT_PASSWORD);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
     private void languagePopUpDialog(View view) {
        try {
            PopupWindow popupwindow_obj = popupDisplay();
            popupwindow_obj.showAsDropDown(view, -50, 0);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    void reloadActivity() {
        try {
            onCreateProcess();
        } catch (Exception e) {
        }
    }
    

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

Ответ 1

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

Давайте посмотрим поближе, сначала начнем с вашего конкретного примера:

try {
  View view = findViewById(R.id.toolbar);
}
catch(Exception e) { }

Итак, ссылка на что-то создана; и когда это терпит неудачу... это не имеет значения; потому что эта ссылка не используется в первую очередь! Приведенный выше код абсолютно бесполезный шум линии. Или человек, который написал этот код, изначально предполагал, что второй подобный вызов волшебным образом больше не вызовет исключения?!

Может быть, это должно было выглядеть так:

try {
  View view = findViewById(R.id.toolbar);
  ... and now do something with that view variable ...
}
catch(Exception e) { }

Но опять же, что это помогает?! Существуют исключения для связи и распространения ошибок в вашем коде. Игнорирование ошибок редко является хорошей идеей. На самом деле, исключение может рассматриваться как:

  • Вы даете отзыв пользователю; (например: "введенное вами значение не является строкой, попробуйте снова"); или заниматься более сложной обработкой ошибок
  • Может быть, проблема как-то ожидается и может быть смягчена (например, путем предоставления ответа "по умолчанию", когда какой-то "удаленный поиск" не удался)
  • ...

Короче говоря: минимум, что вы делаете с исключением, это регистрировать/отслеживать его; так что когда вы позже зайдете в отладку какой-то проблемы, вы поймете: "Хорошо, в этот момент произошло исключение".

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

Наконец, позвольте процитировать Уорда Каннингема:

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

Позвольте этому погрузиться и размышлять об этом. Чистый код вас не удивит. Пример, который вы показываете нам, удивляет всех.

Обновление, касающееся обновления, о котором спрашивает ОП

try {
  do something
}
catch(Exception e) { 
  print stacktrace
}

Тот же ответ: делать это "повсюду" - тоже плохая практика. Потому что этот код также удивляет читателя.

Выше:

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

Таким образом, используя try/catch в качестве "шаблона", как вы показываете; как сказано: все еще не очень хорошая идея. И да, это предотвращает сбои; но приводит ко всем видам "неопределенного" поведения. Вы знаете, когда вы просто поймаете исключение вместо того, чтобы правильно с ним справиться; вы открываете банку с червями; потому что вы можете столкнуться с множеством последующих ошибок, которые вы позже не поймете. Потому что вы использовали событие "первопричина" ранее; напечатал это где-то; и что где-то сейчас нет.

Ответ 2

Из Документация для Android:

Позвольте ему указать -

Не вызывать общее исключение

Также может возникнуть соблазн быть ленивым при перехвате исключений и сделать что-то вроде этого:

try {
    someComplicatedIOFunction();        // may throw IOException
    someComplicatedParsingFunction();   // may throw ParsingException
    someComplicatedSecurityFunction();  // may throw SecurityException
    // phew, made it all the way
} catch (Exception e) {                 // I'll just catch all exceptions
    handleError();                      // with one generic handler!
}

Почти во всех случаях нецелесообразно ловить общий Exception или Throwable (желательно не Throwable, потому что он включает исключения ошибок). Это очень опасно, потому что это означает, что исключения, которые вы никогда не ожидали (включая RuntimeExceptions like ClassCastException), попадают в обработку ошибок на уровне приложений.

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

Альтернативы ловушке общего исключения:

  • Поймайте каждое исключение отдельно как отдельные блоки catch после одной попытки. Это может быть неудобно, но по-прежнему предпочтительнее поймать все Исключения.
    Редактировать автор: Это мой выбор. Остерегайтесь повторять слишком много кода в блоках catch. Если вы используете Java 7 или выше, используйте multi-catch, чтобы избежать повторения одного и того же блока catch.
  • Обновите свой код, чтобы иметь более мелкозернистую обработку ошибок, с несколькими блоками try. Разделите IO от разбора, обрабатывайте ошибки отдельно в каждом случае.
  • Отбросить исключение. Много раз вам не нужно ломать исключение на этом уровне, так что пусть метод бросит его.

В большинстве случаев вы не должны обрабатывать разные типы исключений одинаково.

Форматирование/абзац слегка изменен из источника для этого ответа.

P.S. Не бойтесь Исключения!! Они друзья!!!

Ответ 3

Я бы поставил это как комментарий к другому ответу, но у меня пока нет репутации.

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

  • Отсутствие обработки ошибок
  • Общий улов
  • Без преднамеренных исключений
  • Одеяло Try/catch

Я попытаюсь объяснить все это в этом примере.

try {
   User user = loadUserFromWeb();     
   if(user.getCountry().equals("us")) {  
       enableExtraFields();
   }
   fillFields(user);   
} catch (Exception e) { 
}

Это может быть сбой несколькими способами, с которыми нужно обращаться по-разному.

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

Решения

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

(4) Если Пользователь неполный, например, страна не известна и возвращает null. Метод equals создаст исключение NullPointerException. Если этот NPE просто выброшен и пойман, как указано выше, метод fillFields (user) не будет вызываться, даже если он может быть выполнен без проблем. Вы можете предотвратить это, включив нулевые проверки, изменив порядок выполнения или изменив область try/catch. (Или вы можете сохранить кодировку следующим образом: "us".equals(user.getCountry()), но я должен был привести пример). Конечно, любое другое исключение также предотвратит выполнение функции fillFields(), но если нет пользователя, вы, вероятно, не хотите, чтобы он выполнялся в любом случае.

(1, 2, 3). Загрузка из Интернета часто выдает множество исключений, от исключения IOException до HttpMessageNotReadable, даже когда возвращается. Может быть, пользователь не подключен к Интернету, может быть, произошел сбой на сервере или он не работает, но вы не знаете, потому что вы поймаете (исключение) - вместо этого вы должны поймать определенные исключения. Вы даже можете поймать несколько таких, как это.

try{
   User user = loadUserFromWeb(); //throws NoInternetException, ServerNotAvailableException or returns null if user does not exist
   if(user == null) { 
       throw new UserDoesNotExistException(); //there might be better options to solve this, but it highlights how exceptions can be used.
   }
   fillFields(user);
   if("us".equals(user.getCountry()) {
       enableExtraFields();
   }
} catch(NoInternetException e){
    displayWarning("Your internet conneciton is down :(");
} catch(ServerNotAvailableException e){
    displayWarning("Seems like our server is having trouble, try again later.");
} catch(UserDoesNotExistException e){
    startCreateUserActivity();
}

Я надеюсь, что это объяснит.

По крайней мере, как быстрое исправление, то, что вы можете сделать, это отправить событие на ваш сервер с исключением. Например, через firebase или crashlytics. Таким образом, вы можете хотя бы увидеть такие вещи, как (эй, основная активность не загружается для 80% наших пользователей из-за проблемы вроде (4).

Ответ 4

Это определенно плохая практика программирования.

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

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

...
try {
    View view = findViewById(R.id.toolbar);
}catch(Exception e){
    logger.log(Level.SEVERE, "an exception was thrown", e);
}
...

Ответ 5

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

Каждая функция имеет пост-условие – набор вещей, которые должны быть истинными после выполнения этой функции. Например, функция, которая читает из файла, имеет условие post, что данные в файле будут считаны с диска и возвращены. Тогда возникает исключение, когда функция не может выполнить одно из своих пост-условий.

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

[Теперь есть некоторые случаи, когда пустые catch es в порядке. Например, ведение журнала - это то, что вы могли бы оправдать обертывание пустым уловом. Ваше приложение, вероятно, будет работать нормально, даже если некоторые записи не могут быть записаны. Но это особые случаи, когда вам приходится очень тяжело работать в обычном приложении.]

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

Ответ 6

Это плохо по нескольким причинам:

  • Что вы делаете, что findViewById выбрасывает исключение? Исправьте это (и скажите, потому что я этого никогда не видел) вместо того, чтобы ловить.
  • Не улавливайте Exception, когда вы можете поймать определенный тип исключения.
  • Существует такое убеждение, что хорошие приложения не сбой. Это неправда. Хорошее приложение падает, если это необходимо.

Если приложение переходит в плохую ситуацию, для него намного лучше, чем для того, чтобы его можно было разрезать в непригодном для него состоянии. Когда вы видите NPE, вы не должны просто придерживаться нулевой отметки и уходить. Лучший подход заключается в том, чтобы выяснить, почему что-то является нулевым и либо остановить его от null, либо (если значение null заканчивается как действительное и ожидаемое состояние), проверьте значение null. Но вы должны понять, почему проблема возникает в первую очередь.

Ответ 7

Я занимаюсь разработкой приложений для Android за последние 4-5 лет и никогда не использовал попытку catch для просмотра.

Если его панель инструментов выглядит так:

Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);

например: - Чтобы получить TextView из представления (фрагмент/диалог/любое пользовательское представление)

TextView textview = (TextView) view.findViewById(R.id.viewId);

TextView textview = (TextView) view.findViewById(R.id.viewId);

вместо этого

View view = findViewById(R.id.toolbar);

Объект представления имеет минимальный охват по сравнению с его реальным видом.

Примечание. - Может быть, он разбился, потому что представление было загружено. Но добавление try catch - это плохая практика.

Ответ 8

Да, try/catch используется для предотвращения сбоя приложения, но вам, разумеется, не нужен try/catch для извлечения представления из XML, как показано в вашем вопросе.

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

Ответ 9

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

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

После того, как центральная обработка исключений/регистрация/отчетность и контролируемое завершение работы на месте, начните переписывать обработку исключений, чтобы ловить локальные исключения как можно более конкретными. Попытайтесь сделать блок try {} как можно короче.

Ответ 10

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

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

Теперь, когда вы не разрабатываете приложение для себя, а для компании или корпорации, обычно сталкиваются с дополнительными требованиями по этой теме:

  • "Аварии приложений показывают плохой образ компании, поэтому они неприемлемы". Затем следует выполнить тщательную разработку, и выбор возможных исключений может быть одним из вариантов. Если это так, это должно быть сделано выборочно и храниться в разумных пределах. И обратите внимание, что разработка не все о линиях кода, например, интенсивный процесс тестирования имеет решающее значение в этих случаях. Однако неожиданное поведение в корпоративном приложении хуже, чем сбой. Таким образом, всякий раз, когда вы обнаруживаете исключение в своем приложении, вы должны знать, что делать, что показывать и как приложение будет вести себя следующим образом. Если вы не можете контролировать это, лучше разрешите приложение сбой.
  • "Журналы и трассировки стека могут удалять конфиденциальную информацию на консоль. Это может быть использовано для злоумышленника, поэтому по соображениям безопасности они не могут использоваться в рабочей среде". Это требование противоречит общему правилу, чтобы разработчик не писал тихие исключения, поэтому вам нужно найти способ для него. Например, ваше приложение может управлять средой, поэтому оно использует журналы и трассировки стека в непроизводственных средах, используя при этом облачные инструменты, такие как bugsense, crashlitics или аналогичные для производственных сред.

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

Ответ 11

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

Итак, вопрос:

Является

try{
  someMethod();
}catch(Exception e){}

хорошая практика? Нет! Вот несколько моментов:

  • Самое важное: это плохой опыт работы с клиентами. Как я должен знать, когда происходит что-то плохое? Мои клиенты пытаются использовать мое приложение, и ничего не работает. Они не могут проверять свой банковский счет, оплачивать свои счета, независимо от того, что делает мое приложение. Мое приложение совершенно бесполезно, но эй, по крайней мере, это не сбой! (Часть меня считает, что этот "старший" разработчик получает точки коричневого цвета для низких номеров сбоев, поэтому они играют в систему.)

  • Когда я занимаюсь разработкой, и я пишу плохой код, если я просто поймаю и проглотил все исключения на верхнем слое, у меня нет регистрации. У меня нет ничего в моей консоли, и мое приложение терпит неудачу. Из того, что я могу сказать, все работает нормально. Поэтому я фиксирую код... Оказывается, мой объект DAO был пустым все время, и платежи клиентов никогда не обновлялись в БД. Упс! Но мое приложение не разбилось, так что плюс.

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

Некоторые из разработчиков, с которыми я работал в прошлом, думали, что сбой был плохим.

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

Ответ 12

Мы довольно много используем вашу логику. Используйте try-catch, чтобы предотвратить сбои производственных приложений.

Исключения должны быть НИКОГДА не учитываться. Это плохая практика кодирования. Ребятам, поддерживающим код, будет очень сложно локализовать часть кода, которая вызвала исключение, если они не вошли в систему.

Мы используем Crashlytics для регистрации исключений. Код не будет разбиваться (но некоторые функции будут нарушены). Но вы получите журнал исключений на панели управления Fabric/Crashlytics. Вы можете посмотреть эти журналы и исправить исключения.

try {
    codeThatCouldRaiseError();
} catch (Exception e) {
    e.printStackTrace();
    Crashlytics.logException(e);
}

Ответ 13

Плохая практика использовать catch(Exception e){}, потому что вы по существу игнорируете ошибку. То, что вы, вероятно, хотите сделать, это нечто большее:

try {
    //run code that could crash here
} catch (Exception e) {
    System.out.println(e.getMessage());
}

Ответ 14

Хотя я согласен с другими ответами, есть одно обстоятельство, с которым я неоднократно сталкивался, где этот мотив является незначительно допустимым. Предположим, кто-то написал немного кода для класса следующим образом:

private int foo=0;

    . . .

public int getFoo() throws SomeException { return foo; }

В этом случае метод getFoo() не может терпеть неудачу - всегда будет возвращено законное значение частного поля 'foo'. Однако кто-то, возможно, по педантичным причинам, решил, что этот метод следует объявить потенциально бросающим Исключение. Если вы затем попытаетесь вызвать этот метод в контексте - например, обработчик событий, который не позволяет бросать исключение, вы в основном вынуждены использовать эту конструкцию (даже тогда я согласен с тем, что нужно как минимум регистрировать исключение в случае). Всякий раз, когда я должен это делать, я всегда, по крайней мере, добавляю большой толстый комментарий "ЭТО НЕ МОЖЕТ ПРОИСХОДИТЬ" рядом с предложением "улов".