使用try / catch防止应用崩溃


79

我一直在开发一个Android应用程序,该应用程序try/catch经常使用以防止它崩溃,即使在不需要的地方也是如此。例如,

在视图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

更新

由于这个问题引起了广泛关注,并且有些人误解了这个问题(也许是因为我没有清楚地表述),所以我将其重新表述。

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

  • 一个函数是经过编写和测试的,它可以是一个仅初始化视图的小函数,也可以是一个复杂的函数,在测试后将其包裹在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出于 与该问题不同的目的试图捕获异常。


11
不回答你的问题,但从来没有sliently吞下例外catch(Exception e){}-此评论编辑的问题之前,是有道理的
可怕的袋熊


29
啊,臭名昭著ON ERROR RESUME NEXT
重复数据删除器

评论不作进一步讨论;此对话已转移至聊天
巴尔加夫饶

1
这个问题让我开始思考,因为我在没时间的时候也多次尝试使用try {} catch(Exception e){}
Raut Darpan

Answers:


81

当然,规则总是有例外的,但是如果您需要经验法则,那么您是正确的。空捕获块是“绝对”的坏习惯。

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

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进行捕获,甚至在最高级别捕获某些错误,以确保没有迷路,永远)。

最后,引用沃德·坎宁安(Ward Cunningham):

您知道当您阅读的每个例程几乎都符合您的预期时,您正在使用干净的代码。当代码也使它看起来像是针对该问题编写的语言时,可以将其称为精美代码。

让它沉入其中并进行冥想。干净的代码并不会令你大吃一惊。您向我们展示的示例让每个人都惊讶。

更新,关于OP要求的更新

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

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

以上:

  • 在某处打印错误信息。完全不能保证此“某处”类似于合理的目的地。相反的。示例:在我正在使用的应用程序中,此类调用会神奇地出现在我们的跟踪缓冲区中。根据具体情况,有时我们的应用程序可能会向这些缓冲区中注入大量数据。每隔几秒钟进行一次缓冲修剪。因此,“仅打印错误”通常会翻译为:“仅丢失所有此类错误信息”。
  • 然后:您不可以尝试/捕获,因为可以。您之所以这样做,是因为您了解自己的代码在做什么;而且您知道:我最好在这里尝试一下/做对的事情(再次参见答案的第一部分)。

因此,将try / catch用作显示的“模式”;就像这样说:仍然不是一个好主意。是的,它可以防止崩溃;但会导致各种“未定义”行为。您知道,当您只是捕获异常而不是适当地处理它时;你打开一罐蠕虫;因为您可能会遇到无数后续错误,这些错误后来您都无法理解。因为您早些时候消耗了“根本原因”事件;印在某处 那个地方现在不见了。


1
如果我打印堆栈跟踪怎么办?我仍然认为我不需要在这里尝试/抓住。
mallaudin

3
我不得不承认我没有得到你的编辑。一个不真实的想法,你在那儿做什么?或您从中得到什么。
GhostCat

在一般情况下,@ mallaudin在崩溃程序的行之后的任何内容都不会执行(obvs),因此打印只是胡说八道。在这种特定情况下,即try + except,至少会显示一些内容,并且该异常不会被静音。
彼得·巴迪达

2
@GhostCat OP在另一个答案的评论中说,异常块已包含日志记录调用。这个问题曲解了实际发生的事情,因此我认为指责高级开发人员不称职在这里没有帮助。我认为除了OP给我们带来的好处以外,还有更多的事情要做。
jpmc26年

1
@GhostCat与回购无关,我只是意识到我的例子很糟糕。每个人都以为我要问的是空试/接球
Mallaudin

15

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,因为它包含Error异常)。这是非常危险的,因为这意味着在应用程序级错误处理中会捕获到您料想不到的异常(包括RuntimeExceptionslike ClassCastException)。

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

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

  • 一次尝试后,分别捕获每个异常作为单独的捕获块。这可能很尴尬,但仍然比捕获所有异常更好。
    由作者编辑:这是我的选择。 当心在catch块中重复太多代码。如果您使用的是Java 7或更高版本,请使用多重捕获以避免重复相同的捕获块。
  • 通过多个try块,重构代码以具有更细粒度的错误处理。从解析中分离出IO,分别处理错误。
  • 重新抛出异常。很多时候,您无论如何都不需要在此级别捕获异常,只需让该方法将其抛出即可。

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

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

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


1
如果您分别捕获每个异常,请确保避免在不确定的状态下继续执行(避免类似的事情Object a; try { a = thing(); } catch (Exception e) {...} try { a.action(); } catch (Exception e) {...}))
njzk2

公平地说,捕获通用名Exception作为“崩溃前日志”系统的一部分可能很有用,尽管在这种情况下应该重新抛出。
贾斯汀时间-恢复莫妮卡

9

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

您说的是不好的做法是正确的,实际上,您发布的内容显示了关于异常的不同类型的不好的做法。

  1. 缺乏错误处理
  2. 通用渔获
  3. 没有故意的例外
  4. 毛毯试穿

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

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

这可能会以几种方式失败,应该以不同的方式进行处理。

  1. 这些字段不会被填充,因此向用户显示一个空白屏幕,然后...什么?没什么-缺乏错误处理。
  2. 在不同类型的错误之间没有区别,例如Internet问题或服务器本身的问题(中断,请求中断,传输损坏等)-通用捕获。
  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异常,甚至只是返回。可能是用户未连接到互联网,可能是后端服务器发生了更改或已关闭,但您不知道是因为您执行catch(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();
}

我希望能解释一下。

至少作为一种快速解决方案,您可以做的是将事件发送给您的后端,并发送异常。例如通过firebase或crashlytics。这样一来,您至少可以看到类似((嘿,由于(4)之类的问题,主要活动无法为80%的用户加载)。


8

这绝对是不好的编程习惯。

从当前情况来看,如果有成百上千个try catch这样的应用程序,那么即使不调试应用程序,您甚至都不会知道异常发生在哪里,如果您的应用程序在生产环境中,那将是一场噩梦。

但是您可以包括一个记录器,以便您知道何时引发异常(以及原因)。它不会改变您的正常工作流程。

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

7

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

每个函数都有一个后置条件,即在该函数执行后必须满足的一系列条件。例如,从文件读取的函数具有后置条件,即文件中的数据将从磁盘读取并返回。然后,当函数无法满足其后置条件之一时,将引发异常。

通过忽略函数中的异常(或者甚至通过简单地记录异常来有效地忽略它),就是说您可以接受该函数,而不实际执行它同意做的所有工作。这似乎不太可能–如果某个函数无法正确运行,则无法保证随后的所有操作都将运行。并且,如果您的其余代码运行良好,不管某个特定功能是否运行完毕,那么人们都会想知道为什么您首先拥有该功能。

[现在在某些情况下可以使用空catches。例如,日志记录可能使您有理由将其包装在一个空的捕获中。即使某些日志无法编写,您的应用程序也可能会正常运行。但是这些都是特殊情况,您必须努力工作才能在普通应用中找到它。]

因此,重点是,这是一种不好的做法,因为它实际上并不能保持您的应用程序运行(这种样式的合理依据)。从技术上讲,也许操作系统还没有杀死它。但是,仅忽略异常后,该应用程序仍可能无法正常运行。在最坏的情况下,它实际上可能会造成危害(例如,损坏用户文件等)。


3

这是不好的,有多种原因:

  1. 您在做什么会findViewById引发异常?修正该问题(并告诉我,因为我从未见过),而不要抓住它。
  2. Exception当您可以捕获特定类型的异常时不要捕获。
  3. 有这样的信念,即好的应用程序不会崩溃。这不是真的。一个好的应用程序必须崩溃。

如果应用程序进入不良状态,则崩溃比在不可用状态下崩溃要好得多。当人们看到NPE时,不应该只是坚持一张空支票而走开。更好的方法是找出为什么某事物为null并阻止其为null,或者(如果null最终成为有效且预期的状态)检查null。但是您必须首先了解为什么会发生此问题。


2

在过去的4-5年中,我一直在开发android应用,但从未使用过尝试捕获来进行视图初始化。

如果它的工具栏是这样的

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是一个不好的做法。


在许多情况下,无需投射视图。您可以使用一些方法,这些方法View可能已经有用(例如setVisibility()),而无需确切指定使用哪个类。(例如,对于易于更改的版式而言)
njzk2

1

是的,try / catch用于防止应用程序崩溃,但是您当然不需要try / catch来从XML中获取问题中所描述的视图。

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


1

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

恕我直言,吞咽异常和冒着数据完整性,数据丢失的风险,或者只是让应用程序处于未定义状态,比让应用程序崩溃并且用户知道出了点问题然后可以重试更糟糕。与您的用户开始报告源自未定义应用程序状态的所有类型的问题相比,这还将导致报告的问题更好(问题更多的是根本原因),更少的不同症状。

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


1

让我补充一下观点,作为一个在公司移动开发行业工作超过十年的人。首先,关于异常的一些一般性提示,其中大多数都包含在上面的答案中:

  • 异常应用于异常,意外或不受控制的情况,而不是在整个代码中定期使用。
  • 程序员必须知道易引发异常的代码部分,然后尝试捕获它们,使其余代码尽可能干净。
  • 一般而言,不应将例外保持沉默。

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

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

因此,简短的答案是,您发现的代码不是一个好的实践示例,因为在不提高应用程序质量的情况下,维护它非常困难且成本很高。


1

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

所以问题是:

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

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

  1. 最重要的事情:这是糟糕的客户体验。我应该如何知道什么时候发生坏事?我的客户正在尝试使用我的应用程序,但没有任何效果。他们无法检查自己的银行帐户,支付账单,无论我的应用程序做什么。我的应用程序完全没用,但是,至少它没有崩溃!(我的一部分认为这位“高级”开发人员会因低崩溃率而获得布朗尼积分,因此他们正在对系统进行游戏。)

  2. 当我进行开发并编写错误的代码时,如果我只是在顶层捕获并吞下所有异常,则不会进行日志记录。我的控制台中什么也没有,我的应用程序无声地失败。据我所知,一切似乎都正常。因此,我提交了代码...事实证明,我的DAO对象始终为空,并且客户的付款从未真正在数据库中更新。哎呀!但是我的应用程序没有崩溃,所以这是一个加号。

  3. 扮演恶魔的拥护者,比方说我可以捕捉并吞下所有异常。这是非常容易写在Android的自定义异常处理程序。如果您真的只需要捕获每个异常,就可以在一个地方完成它,而不必try/catch遍及整个代码库。

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

我必须向他们保证,我们希望我们的应用程序崩溃。不,不稳定的应用程序并不可行,但是崩溃意味着我们做错了什么,我们需要对其进行修复。它崩溃的速度越快,我们越早找到它,修复起来就越容易。我能想到的唯一其他选择是允许用户继续进行中断的会话,这等同于激怒我的用户群。


0

使用这种做法是错误的做法,catch(Exception e){}因为您实际上忽略了该错误。您可能想做的更像是:

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

0

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

异常应该从不忽略。这是不好的编码习惯。如果未登录,维护代码的人将很难找到导致异常的代码部分。

我们Crashlytics用来记录异常。该代码不会崩溃(但是某些功能将被破坏)。但是您会在的信息中心中获得异常日志Fabric/Crashlytics。您可以查看这些日志并修复异常。

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

是。这就是人们在这里所做的事情,但我不同意。这就是为什么我发布问题。您能提出更合乎逻辑的东西吗?
mallaudin

4
@mallaudin不要发布错误地表示您所要求的代码的示例代码。它可以大大改变您获得的答案。
jpmc26年

@ jpmc26是的。我已经在答案中看到了。
mallaudin

0

尽管我同意其他答复,但在我反复遇到这种情况可容忍的情况下,有一种情况。假设有人为一个类编写了一些代码,如下所示:

private int foo=0;

    . . .

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

在这种情况下,“ getFoo()”方法不会失败-始终会返回私有字段“ foo”的合法值。 但是有人(可能是出于学究的原因)决定应将此方法声明为可能引发Exception。 如果您随后尝试在不允许抛出异常的上下文中(例如事件处理程序)调用此方法,则基本上会被迫使用此构造(即使如此,我同意至少应记录该异常只是万一)。每当我必须执行此操作时,我总是至少在“ catch”子句旁边添加一个粗大的注释“此无法发生”。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.