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

问题描述 投票:77回答:14

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

xml layoutid = 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,那么我们真的需要用UI线程或其他线程附加异常处理程序并捕获所有内容吗?如果可能的话,那将是更好的方法。

是的,空的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 exception handling best practices的重复,OP试图捕获异常的目的不同于这个问题。

java android design-patterns coding-style try-catch
14个回答
78
投票

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

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

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捕获一些,甚至是最高级别的某些错误,以确保什么都不会丢失;永远)。

最后,让我们来看看Qazxswpoi Ward Cunningham:

你知道,当你阅读的每个例程都是你所期望的那样时,你正在使用干净的代码。当代码看起来像是为问题编写的语言时,你可以称之为漂亮的代码。

让它沉入并沉思它。清洁代码不会让您感到惊讶。你向我们展示的例子让所有人都惊讶不已。

更新,关于OP询问的更新

quote

同样的答案:做到“到处都是”也是不好的做法。因为这段代码也让读者感到惊讶。

以上:

  • 在某处打印错误信息。完全没有保证这个“某处”类似于合理的目的地。与此相反的。示例:在我正在使用的应用程序中,此类调用将神奇地出现在我们的跟踪缓冲区中。根据具体情况,我们的应用程序有时会将大量数据和大量数据输入这些缓冲区;每隔几秒使这些缓冲区修剪。所以“只是打印错误”经常转化为:“简单地丢失所有这些错误信息”。
  • 然后:你没有尝试/捕获,因为你可以。你这样做是因为你了解你的代码正在做什么;而且你知道:我最好在这里尝试/捕捉做正确的事情(再次参见我的答案的第一部分)。

因此,使用try / catch作为“模式”,就像你正在展示的那样;如上所述:仍然不是一个好主意。是的,它可以防止崩溃;但会导致各种“未定义”的行为。你知道,当你抓住一个例外而不是妥善处理它时;你打开一罐虫子;因为你可能会遇到许多后来你不理解的后续错误。因为您之前消耗了“根本原因”事件;把它印在某处;那个地方现在已经消失了。


1
投票

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

  • 异常应该用于特殊,意外或不受控制的情况,而不是在整个代码中定期使用。
  • 程序员必须知道易于抛出异常的代码部分并尝试捕获它们,使其余代码尽可能干净。
  • 作为一般规则,例外情况不应该保持沉默。

现在,当您不是为自己开发应用程序,而是为公司或公司开发应用程序时,通常会遇到有关此主题的其他要求:

  • “应用程序崩溃显示公司形象不佳,因此无法接受”。然后,应该进行仔细的开发,并且可以选择捕获甚至不可能的异常。如果是这样,必须有选择地进行,并保持合理的限度。请注意,开发并不仅仅是代码行,例如,在这些情况下,密集的测试过程至关重要。但是,企业应用程序中的意外行为比崩溃更糟糕。因此,每当您在应用程序中捕获异常时,您必须知道该做什么,要显示什么以及该应用程序的下一步行为。如果您无法控制,请让应用程序崩溃。
  • “日志和堆栈跟踪可能会将敏感信息转储到控制台。这可能会用于攻击者,因此出于安全考虑,它们无法在生产环境中使用”。此要求与开发人员不编写静默异常的一般规则冲突,因此您必须找到一种方法。例如,您的应用程序可以控制环境,因此它在非生产环境中使用日志和堆栈跟踪,同时在生产环境中使用基于云的工具,如bugsense,crashlitics或类似工具。

因此,简短的回答是,您找到的代码不是一个好的实践示例,因为在不提高应用程序质量的情况下进行维护是非常困难和昂贵的。


1
投票

另一种观点,作为每天编写企业软件的人,如果应用程序有不可恢复的错误,我希望它崩溃。崩溃是可取的。如果它崩溃,它会被记录。如果它在短时间内崩溃了几次,我会收到一封电子邮件,说应用程序崩溃,我可以验证我们的应用程序和我们使用的所有Web服务仍在运行。

所以问题是:

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

好的做法?没有!以下是几点:

  1. 最重要的是:这是一个糟糕的客户体验。我怎么知道什么时候发生了不好的事情?我的客户正在尝试使用我的应用程序,但无效。无论我的应用是什么,他们都无法查看他们的银行账户,支付账单。我的应用程序完全无用,但嘿,至少它没有崩溃! (我的一部分人认为这个“高级”开发人员因低崩溃数而获得布朗尼点,因此他们正在游戏系统。)
  2. 当我正在进行开发并编写错误的代码时,如果我只是捕获并吞掉顶层的所有异常,我就没有记录。我的控制台中没有任何内容,我的应用程序无声地失败。从我所知,一切似乎都可行。所以我提交了代码......结果我的DAO对象一直是null,并且客户的付款从未在DB中实际更新过。哎呦!但我的应用程序没有崩溃,所以这是一个加号。
  3. 扮演魔鬼的拥护者,让我说我可以捕捉并吞下每一个例外。在Android中编写自定义异常处理程序非常容易。如果你真的必须捕获每个异常,你可以在一个地方做,而不是在你的代码库中胡椒try{ someMethod(); }catch(Exception e){}

我过去曾与之合作的一些开发人员认为崩溃很糟糕。

我必须向他们保证,我们希望我们的应用程序崩溃。不,一个不稳定的应用程序不行,但崩溃意味着我们做错了什么,我们需要修复它。崩溃越快,我们越早发现它就越容易解决。我能想到的唯一另一个选择是允许用户继续在一个破碎的会话中,我等同于惹恼我的用户群。


0
投票

使用try/catch是不好的做法,因为你基本上忽略了这个错误。您可能想要做的更像是:

catch(Exception e){}

0
投票

我们非常使用相同的逻辑。使用try { //run code that could crash here } catch (Exception e) { System.out.println(e.getMessage()); } 防止生产应用程序崩溃。

应该永远不要忽视例外情况。这是一个糟糕的编码实践。维护代码的人将非常难以本地化部分代码,如果他们没有被记录则引发异常。

我们使用try-catch来记录异常。代码不会崩溃(但某些功能会中断)。但是你在Crashlytics的仪表板中得到了异常日志。您可以查看这些日志并修复异常。

Fabric/Crashlytics

0
投票

虽然我同意其他答复,但我反复遇到的一个情况是这个主题是可以容忍的。假设有人为类编写了一些代码,如下所示:

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

在这种情况下,'getFoo()'方法不会失败 - 总是会返回私有字段'foo'的合法值。然而有人 - 可能是出于迂腐的原因 - 决定将此方法声明为可能引发异常。如果你试图在一个上下文中调用这个方法 - 例如一个事件处理程序 - 它不允许抛出异常,你基本上被迫使用这个构造(即便如此,我同意一个人应该至少记录异常只是如果)。每当我必须这样做时,我总是至少在“捕获”条款旁边添加一个很大的评论“这不能发生”。


15
投票

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

让我们认为 -

不要捕获通用异常

在捕获异常时也可能很懒惰,并执行以下操作:

Android documentation

在几乎所有情况下,捕获通用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! } 或Throwable都是不合适的(最好不要Throwable因为它包含Error异常)。它非常危险,因为它意味着您从未预料到的异常(包括像Exception这样的RuntimeExceptions)会陷入应用程序级错误处理。

它模糊了代码的失败处理属性,这意味着如果有人在您调用的代码中添加了一种新类型的ClassCastException,编译器将无法帮助您意识到您需要以不同方式处理错误。

捕获通用异常的替代方法:

  • 单次尝试后,将每个异常单独捕获为单独的catch块。这可能很尴尬,但仍然比捕获所有异常更可取。 按作者编辑:这是我的选择。注意在catch块中重复过多的代码。如果您使用的是Java 7或更高版本,请使用multi-catch以避免重复相同的catch块。
  • 重构您的代码以使用多个try块进行更细粒度的错误处理。从解析中分离出IO,在每种情况下分别处理错误。
  • 重新抛出异常。很多时候你不需要在这个级别捕获异常,只需让方法抛出它。

在大多数情况下,您不应该以相同的方式处理不同类型的异常。

从此答案的来源略微修改格式/段落。

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


9
投票

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

你说这是不好的做法是正确的,实际上你发布的内容显示了异常方面不同类型的不良做法。

  1. 缺乏错误处理
  2. 通用捕获
  3. 没有故意的例外
  4. 毯子尝试/捕获

我将尝试通过此示例解释所有这些。

Exception

这可能会以多种方式失败,应以不同方式处理。

  1. 这些字段不会被填充,因此用户会看到一个空屏幕然后......什么?没什么 - 缺乏错误处理。
  2. 不同类型的错误之间没有区别,例如互联网问题或服务器本身的问题(停机,破坏请求,传输损坏,......) - 通用捕获。
  3. 您不能为自己的目的使用异常,因为当前系统会干扰它。 - 无故意例外
  4. 不必要的和意外的错误(例如null.equals(...))可能导致基本代码无法执行。 - 毯子尝试/捕获

解决方案

(1)首先,默默失败并不是一件好事。如果出现故障,该应用程序将无法运行。相反,应该尝试解决问题或显示警告,例如“无法加载用户数据,可能您没有连接到Internet?”。如果应用程序没有按照预期进行操作,那么对于用户而言,这比仅关闭自己更令人沮丧。

(4)如果用户不完整,例如该国家未知并返回null。 equals方法将创建NullPointerException。如果刚刚抛出NPE并且如上所述捕获,则不会调用fillFields(user)方法,即使它仍然可以毫无问题地执行。您可以通过包括空检查,更改执行顺序或调整try / catch范围来防止这种情况。 (或者你可以像这样保存编码:“us”.equals(user.getCountry()),但我必须提供一个例子)。当然,任何其他异常也会阻止fillFields()被执行,但如果没有用户,你可能不希望它被执行。

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

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

我希望能够解释它。

至少作为一个快速解决方案,你可以做的是向你的后端发送一个事件,但有例外。例如通过firebase或crashlytics。这样你至少可以看到类似的东西(嘿,由于像(4)这样的问题,主要活动不会为80%的用户加载。


8
投票

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

从目前的情况来看,如果有这样的数百个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(); } try,那么在不调试应用程序的情况下,你甚至不知道异常发生在哪里,如果你的应用程序在生产环境中,这是一场噩梦。

但是你可以包含一个记录器,这样你就可以知道什么时候抛出异常(以及为什么)。它不会改变您的正常工作流程。

catch

7
投票

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

每个函数都有一个后置条件 - 一组在该函数执行后必须全部为真的东西。例如,从文件读取的函数具有post条件,即文件中的数据将从磁盘读取并返回。因此,当函数无法满足其后置条件之一时,抛出异常。

通过忽略函数中的异常(或者甚至通过简单地记录异常来有效地忽略它),你说你对这个函数没有用,它实际上没有做它同意做的所有工作。这似乎不太可能 - 如果一个函数没有正确运行,则无法保证后面的内容将会运行。如果代码的其余部分运行正常,无论某个特定函数是否运行完成,那么人们就会想知道为什么你首先拥有该函数。

[现在有些情况下空的... try { View view = findViewById(R.id.toolbar); }catch(Exception e){ logger.log(Level.SEVERE, "an exception was thrown", e); } ... es可以。例如,日志记录是您可以证明包装在空捕获中的原因。即使无法写入某些日志记录,您的应用程序也可能正常运行。但这些是特殊情况,你必须在普通应用程序中很难找到。]

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


3
投票

由于多种原因,这很糟糕:

  1. 你在做什么catch抛出一个例外?修复(并告诉我,因为我从未见过这个)而不是捕捉。
  2. 当你能捕到特定类型的异常时,不要抓住findViewById
  3. 有这样的信念,好的应用程序不会崩溃。这不是真的。一个好的应用程序如果必须崩溃。

如果一个应用程序进入一个糟糕的状态,它崩溃比它在无法使用的状态下突然好得多。当人们看到一个NPE时,不应该只是坚持一个空检查并走开。更好的方法是找出为什么某些东西为null并且阻止它为null,或者(如果null最终成为有效和预期的状态)检查null。但你必须首先理解问题的原因。


2
投票

我在过去的4到5年里一直在开发Android应用程序,并且从未使用try catch进行视图初始化。

如果它的工具栏是这样的

Exception

例如: - 从视图中获取TextView(片段/对话框/任何自定义视图)

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

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

而不是这个

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

与实际视图类型相比,视图对象具有最小范围。

注意: - 因为视图已加载,可能会崩溃。但添加try catch是一种不好的做法。


1
投票

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

try / catch通常在进行任何http请求时使用,同时将任何String解析为URL,创建URL连接等,并确保打印堆栈跟踪。使用try / catch环绕它并没有多大意义。


1
投票

如前所述,不应该捕获一般异常,或者至少仅在少数中心位置(通常位于框架/基础结构代码中,而不是应用程序代码中)。如果捕获一般异常并记录它,则应该在之后关闭应用程序,或者至少应该通知用户应用程序可能处于不稳定状态并且可能发生数据损坏(如果用户选择继续执行)。因为如果您捕获所有类型的异常(内存不足以命名一个)并使应用程序处于未定义状态,这可能会发生。

恕我直言,更糟糕的是吞下异常并冒险数据完整性,数据丢失,或者只是让应用程序处于未定义状态,而不是让应用程序崩溃,用户知道出现了问题,可以再试一次。这也将导致报告更好的问题(更多问题的根源),可能比用户开始报告源自未定义的应用程序状态的所有类型的麻烦更少的不同症状。

在中央异常处理/记录/报告和受控关闭到位后,开始重写异常处理以捕获尽可能具体的本地异常。尽量让try {}块尽可能短。

© www.soinside.com 2019 - 2024. All rights reserved.