使用try / catch来防止应用程序崩溃

我一直在研究一个经常使用try/catch的Android应用程序,以防止在没有需要的地方崩溃。 例如,

带有id = toolbar xml layout的视图被引用如下:

 // 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) { } 

这个方法在整个应用程序中使用。 堆栈跟踪没有打印,真的很难find出了什么问题。 应用程序突然closures,不打印任何堆栈跟踪。

我问我的前辈向我解释,他说,

这是为了防止生产中的崩溃。

我完全不同意 。 对我来说,这不是防止应用程序崩溃的方法。 这表明开发人员知道他/她在做什么,并且有疑问。

这是在业界用来防止企业应用崩溃的方法吗?

如果try/catch是真的,那么我们真的需要使用UI线程或其他线程来附加一个exception处理程序,并抓住所有东西? 如果可能的话,这将是一个更好的方法。

是的,空try/catch是不好的,即使我们打印堆栈跟踪或日志exception到服务器,在try/catch随机地包装代码块对所有的应用程序是没有意义的,例如,当每个函数被封闭try/catch

UPDATE

由于这个问题引起了很多的关注,有些人误解了这个问题(也许是因为我没有清楚expression出来),我将会重新解释这个问题。

这里是开发人员在这里做的

  • 一个函数是编写和testing的 ,它可以是一个小函数,只是初始化视图或复杂的一个,经过testing后,它是围绕try/catch块。 即使对于永远不会抛出任何exception的函数。

  • 整个应用程序都使用这种做法。 有时候会打印堆栈跟踪,有时候只是一个带有一些随机错误消息的debug log 。 这个错误消息不同于开发人员。

  • 有了这种方法,应用程序不会崩溃,但应用程序的行为变得不确定。 即使有时候也很难去追究哪里出了问题。

  • 我一直在问的真正的问题是; 行业中是否遵循了防止企业应用程序崩溃的做法? 我不是在问空试一下 。 是不是喜欢用不爱的应用程序比意想不到的应用程序? 因为它真的归结为要么崩溃,要么给用户一个空白的屏幕,或者用户不知道的行为。

  • 我在这里发布了一些真正的代码片段

      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) { } } 

不是 Androidexception处理最佳实践的重复,有OP是试图捕捉exception的不同于这个问题的目的。

当然,规则总是有例外,但是如果你需要一个经验法则 – 那么你是正确的; 空的catch块是“绝对”不好的做法。

让我们仔细看看,首先从你的具体例子开始:

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

所以,创造了一些东西的参考。 当失败的时候…没关系; 因为这个参考不是用在第一位! 上面的代码绝对是无用的线路噪声 。 或者编写代码的人最初是否认为第二个类似的调用会奇迹般地不再抛出exception?

也许这意味着看起来像:

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

但是,这又有什么帮助? 存在exception来分别传播代码中的错误情况。 忽略错误很less是一个好主意。 其实,一个exception可以像这样处理:

  • 您向用户提供反馈; (如:“你input的值不是一个string,再试一次”); 或从事更复杂的error handling
  • 也许问题是不知何故,并可以减轻(例如,当一些“远程search”失败时给予“默认”的答案)

长话短说:你做例外的最小的事情是logging/追踪它; 所以当你稍后debugging一些问题的时候,你会明白“OK,在这个时候,exception发生了”。

正如其他人所指出的那样:你也避免捕捉Exception (一般来说)(根据图层的不同,可能有很好的理由去捕捉Exception ,甚至是某些最高级别的错误,以确保没有什么会迷失的; 永远 )。

最后,我们引用 Ward Cunningham的话:

你知道你正在使用干净的代码,当你读的每个例程都是你所期望的。 当代码也使得它看起来像语言是为了解决问题时,你可以称它为漂亮的代码。

让它沉入其中,冥想。 干净的代码不会让你感到惊讶。 您向我们展示的例子让所有人都感到惊讶。

更新 ,关于OP询问的更新

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

同样的答案:做这个“遍地”也是不好的做法。 因为这个代码令读者惊讶。

以上:

  • 在某处打印错误信息。 一点也不能保证这个“某处”类似于一个合理的目的地。 与此相反的。 例如:在我正在使用的应用程序中,这样的调用会神奇地出现在我们的跟踪缓冲区中。 根据具体情况,我们的应用程序有时可能会将大量的数据抽到这些缓冲区中; 使这些缓冲区每隔几秒修剪一次。 所以“只是印刷错误”常常意味着:“简单地把所有这些信息都弄错了”。
  • 然后:你不尝试/抓住,因为你可以 。 你这样做是因为你明白你的代码在做什么; 你知道:我最好有一个尝试/抓住这里做正确的事情(再次看到我的答案的第一部分)。

所以,使用try / catch作为你所显示的“模式” 就像是说:还不是个好主意。 是的,它可以防止崩溃; 但会导致各种“未定义”的行为。 你知道,当你刚刚发现exception而不是正确地处理它时, 你打开一jar蠕虫; 因为你可能会遇到无数后续你不明白的后续错误。 因为你之前消耗了“根本原因”事件; 印在某处; 而那个地方现在已经消失了。

从Android文档 :

让我们把它作为 –

不捕捉通用exception

捕捉exception并执行如下操作时也可能很懒惰:

 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! } 

在几乎所有情况下,都不适合捕获genericsException或Throwable(最好不是Throwable,因为它包含错误exception)。 这是非常危险的,因为这意味着在应用程序级别的error handling中,您从未预料到的exception(包括RuntimeExceptionsClassCastException )。

它掩盖了代码的失败处理属性,这意味着如果某人在调用的代码中添加了新types的Exception ,编译器将无法帮助您意识到需要以不同方式处理错误

捕捉通用例外的替代方法:

  • 单次尝试后,将每个exception分别作为单独的catch块捕获。 这可能是尴尬的,但仍然可以捕获所有例外。
    通过作者编辑: 这是我的select。 当心在catch块中重复太多的代码。 如果您使用的是Java 7或更高版本,请使用multi-catch来避免重复相同的catch块。
  • 重构您的代码,以更多的细粒度的error handling ,多个try块。 从parsing中分解IO,分别处理错误。
  • 重新抛出exception 。 很多时候你不需要在这个级别捕捉exception,只要让方法抛出它。

在大多数情况下,您不应该以相同的方式处理不同types的exception。

格式/分段从这个答案的来源略有修改。

PS不要害怕例外! 他们是朋友!!!

我会把这个作为对其他答案的评论,但是我还没有这个声望。

你说这是不好的做法是正确的,事实上你发布的内容显示了不同types的例外情况。

  1. 缺乏error handling
  2. 通用捕捉
  3. 没有故意的例外
  4. 毯子Try / catch

我会尝试通过这个例子来解释所有这些。

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

这可能会在几个方面失败,应该以不同的方式处理。

  1. 这些字段不会被填充,所以用户被显示一个空的屏幕,然后…什么? 没有 – 没有error handling。
  2. 不同types的错误之间没有区别,例如互联网问题或服务器本身的问题(中断,请求中断,传输损坏等) – 通用捕获。
  3. 您不能为了自己的目的而使用exception,因为当前的系统会干扰这个exception。 – 没有故意的例外
  4. 不必要的和意想不到的错误(例如null.equals(…))可能导致基本代码不能执行。 – 毛毯尝试/抓住

解决scheme

(1)首先,失败并不是一件好事。 如果出现故障,该应用将无法工作。 相反,应该尝试解决问题或显示一个警告,例如“无法加载用户数据,也许你没有连接到互联网?”。 如果应用程序没有做到应有的function,那么对于用户来说,这比对自己closures更令人沮丧。

(4)如果用户不完整,例如国家未知,并且返回null。 equals方法将创build一个NullPointerException。 如果这个NPE只是像上面那样被引发和捕获,fillFields(user)方法将不会被调用,即使它仍然可以被执行而没有问题。 您可以通过包括空检查,更改执行顺序或调整try / catch范围来防止这种情况。 (或者你可以保存这样的代码:“我们”.equals(user.getCountry()),但我不得不提供一个例子)。 当然,其他的exception也会阻止fillFields()被执行,但是如果没有用户,你可能不希望它被执行。

(1,2,3)从web上加载经常抛出各种exception,从IOException到HttpMessageNotReadableexception甚至只是返回。 可能是用户没有连接到互联网,可能是后端服务器发生了变化,或者是已经closures了,但是你不知道,因为你确实发现了(Exception) – 相反,你应该捕获特定的exception。 你甚至可以像这样捕捉其中的几个

 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(); } 

我希望能解释一下。

至less作为一个快速修复,你可以做的是发送一个事件到你的后端,除了例外。 例如通过firebase或crashlytics。 这样,你至less可以看到类似的东西(嘿,主要活动不会因为像(4)这样的问题而占用我们80%的用户。

这绝对是一个糟糕的编程习惯。

从目前的情况来看,如果有几百个这样的try catch ,那么你甚至不会知道exception发生的地方而不debugging应用程序,如果你的应用程序在生产环境中,这是一场噩梦。

但是你可以包含一个logging器,以便你知道什么时候抛出exception(以及为什么)。 它不会改变你的正常工作stream程。

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

这是不好的做法。 其他答案已经说过,但我认为重要的是退后一步,理解为什么我们首先有例外。

每个函数都有一个后置条件 – 一组函数在执行后必须全部为真。 例如,从文件读取的函数具有后置条件,即文件中的数据将从磁盘读取并返回。 那么当一个函数不能满足其后置条件时,就抛出一个exception。

通过忽略函数中的exception(甚至通过简单地loggingexception,甚至有效地忽略它),你就是说,对于这个函数你没有做实际上做的所有工作。 这似乎是不太可能的 – 如果一个函数运行不正确,则不能保证随后会运行。 如果你的代码的其余部分运行良好,不pipe某个函数是否运行完成,那么你就会奇怪为什么你有这个函数。

[现在有些情况下,空catch是好的。 例如,日志logging是你可能需要将其封装在一个空的catch中的东西。 即使某些日志logging无法写入,您的应用程序也可能正常运行。 但是,这些特殊情况下,你必须在一个正常的应用程序中很难find。]

所以问题是,这是不好的做法,因为它并没有真正保持你的应用程序运行(这种风格的理由)。 也许在技术上操作系统还没有杀死它。 但是,在忽略exception之后,应用程序仍然不能正常运行。 而在最坏的情况下,它实际上可能会造成伤害(例如破坏用户文件等)。

由于多种原因,这是不好的:

  1. 你在做什么findViewById抛出一个exception? 修正(并告诉我,因为我从来没有见过这个),而不是捕捉。
  2. 当你可以捕获特定types的Exception时,不要捕获Exception
  3. 有这样的信念,好的应用程序不会崩溃。 这不是真的。 一个好的应用程序崩溃,如果必须的话。

如果一个应用程序进入一个糟糕的状态,那么它的崩溃比它在无法使用的状态下突然好多了。 当一个人看到一个NPE时,不应该坚持一张空头支票而走开。 更好的方法是找出为什么有些东西是空的,要么停止它为空,或者(如果null结束了有效和预期的状态)检查为空。 但你必须明白为什么这个问题首先发生。

我一直在开发过去4-5年的android应用程序,从来没有使用try 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); 

视图对象与其实际视图types相比具有最小范围。

注意: – 可能因为视图被加载而崩溃。 但添加try catch是一个不好的做法。

作为一个在企业移动开发行业工作十多年的人,让我补充一下我的观点。 首先,有关例外的一些常规提示,其中大部分都包含在上面的答案中:

  • 例外情况应该用于例外,意外或不受控制的情况,而不是在整个代码中定期的。
  • 程序员必须知道那些容易引发exception的代码部分,并试着去捕获它们,尽可能地保持代码的其他部分。
  • 作为一般规则,例外不应该保持沉默。

现在,如果您不是为自己开发应用程序,而是为公司或公司开发应用程序,那么通常会在此主题上面加上额外的要求:

  • “应用程序崩溃显示公司形象不好,所以他们不能接受”。 那么,应该谨慎发展,抓住甚至不可能的例外可能是一个select。 如果是的话,这必须有select地进行,并保持在合理的范围内。 并注意到开发并不全是关于代码行的,例如,密集的testing过程在这些情况下是至关重要的。 但是,企业应用程序出乎意料的行为比崩溃更糟糕。 所以,只要你在你的应用程序中发现exception,你就必须知道该做什么,该做什么以及应用程序接下来应该如何performance。 如果你无法控制,最好让应用程序崩溃。
  • “日志和堆栈跟踪可能会将敏感信息转储到控制台,这可能会被攻击者使用,所以出于安全原因,他们不能在生产环境中使用。 这个需求与开发人员不写静默exception的一般规则相冲突,所以你必须find一个办法。 例如,您的应用程序可以控制环境,因此它可以在非生产环境中使用日志和堆栈跟踪,而在生产环境中使用基于云的工具,如bugsense,crashlitics或类似工具。

所以,简单的答案是,你发现它的代码不是一个好的做法的例子,因为它是艰难的和昂贵的维护而不提高应用程序的质量。

另一个angular度,就像每天写企业软件的人一样,如果一个应用程序有一个不可恢复的错误,我想让它崩溃。 崩溃是可取的。 如果它崩溃,它会被logging。 如果在短时间内崩溃了几次,我会收到一封电子邮件,说这个应用程序正在崩溃,我可以validation我们的应用程序和我们使用的所有Web服务是否还在工作。

所以问题是:

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

良好的做法? 没有! 这里有几点:

  1. 最重要的是:这是一个糟糕的客户体验。 当有什么不好的事情发生时,我该怎么知道? 我的客户正在尝试使用我的应用程序,没有任何工作。 他们无法检查他们的银行账户,支付账单,不pipe我的应用程序。 我的应用程序是完全无用的,但嘿,至less它没有崩溃! (我的一部分人认为这个“高级”开发者为了低崩溃数而获得了布朗尼分数,所以他们正在玩这个系统。)

  2. 当我正在开发和编写错误的代码,如果我只是捕捉和吞咽顶层的所有exception,我没有日志logging。 我的控制台里没有任何东西,我的应用程序默默无闻。 从我可以告诉,一切似乎工作正常。 所以我提交了代码…发现我的DAO对象一直是空的,客户的付款从来没有真正在数据库中更新。 哎呦! 但我的应用程序没有崩溃,所以这是一个加号。

  3. 扮演魔鬼的拥护者,让我们说,我可以捕捉和吞咽每一个例外。 在Android中编写自定义exception处理程序非常简单 。 如果你真的需要捕捉每一个exception,你可以在一个地方做,而不是胡椒try/catch你的代码库。

过去曾经和我一起工作过的一些开发人员认为崩溃是不好的。

我必须向他们保证,我们希望我们的应用程序崩溃。 不,不稳定的应用程序不好,但崩溃意味着我们做错了,我们需要修复它。 它崩溃的速度越快,我们越早发现它就越容易修复。 我能想到的唯一的另一个select是允许用户继续在一个破坏的会议,我等同于撒尿我的用户群。

是的,try / catch用于防止应用程序崩溃,但您肯定不需要try / catch从XML中获取视图,如问题中所示。

try / catch通常用于制作任何http请求,同时parsing任何string到URL,创buildURL连接等,并确保打印堆栈跟踪。 用try / catch来包围它并不是很有意义。

catch(Exception e){}是不好的做法,因为你基本上忽略了错误。 你可能想要做的更像是:

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

我们非常使用相同的逻辑。 使用try-catch来防止生产应用程序崩溃。

例外情况不应忽视。 这是一个不好的编码习惯。 维护代码的人将非常难以定位引发exception的代码部分,如果他们没有被logging。

我们使用Crashlytics来loggingexception。 代码不会崩溃(但某些function会中断)。 但是你在Fabric/Crashlytics的仪表板中得到exception日志。 您可以查看这些日志并修复exception。

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

虽然我同意其他的回应,但是在这个主题勉强可以容忍的情况下,我经常遇到一种情况。 假设有人为一个类写了一些代码,如下所示:

 private int foo=0; . . . public int getFoo() throws SomeException { return foo; } 

在这种情况下,'getFoo()'方法不会失败 – 总是会有一个合法的私有字段'foo'值被返回。 然而,有人(可能出于迂腐的原因)决定应该声明这种方法可能抛出一个exception。 如果你尝试在一个上下文中调用这个方法 – 例如一个事件处理程序 – 它不允许引发一个exception,你基本上被迫使用这个构造(即使这样,我同意至less应该loggingexception在情况下) 。 每当我必须这样做的时候,我总是至less在“catch”子句旁增加一个大的注释“这是不可能的”。

如前所述,一般的例外不应该被捕获,或者至less只在less数中心地方(通常位于框架/基础结构代码而不是应用代码)。 如果捕获一般exception并logging日志,应该closures应用程序,或至less应该通知用户应用程序可能处于不稳定状态,并且可能发生数据损坏(如果用户select继续执行)。 因为这是如果你捕捉到所有types的exception(内存不足来命名)并且将应用程序置于未定义的状态。

恕我直言,吞噬exception和风险数据完整性,数据丢失,或简单地离开应用程序在一个未定义的状态比让应用程序崩溃,用户知道出了什么问题,可以再试一次更糟糕。 这也将导致更好的问题报告(更多问题的根源),可能比用户开始报告源于未定义的应用程序状态的所有types的麻烦更less的不同症状。

在中央exception处理/日志logging/报告和受控closures之后,开始重写exception处理以捕捉尽可能具体的本地exception。 尝试尽可能缩短try {}块。