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

时间:2017-03-22 04:47:20

标签: java android design-patterns coding-style try-catch

我一直在开发一款经常使用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

中时

更新

由于这个问题引起了很多关注,而且有些人误解了这个问题(也许是因为我还没有明确表达过这个问题),我将重新解释它。

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

  • 编写一个函数并且测试,它可以是一个只是初始化视图或复杂视图的小函数,在测试后它被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正在尝试捕获不同的异常   目的不是这个问题。

14 个答案:

答案 0 :(得分: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 ,甚至是最高级别的某些错误,以确保没有丢失; 永远)。

最后,让我们quote Ward Cunningham:

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

让它陷入其中并沉思其中。清洁代码让您大吃一惊。您向我们展示的示例让所有人都感到惊讶。

更新,关于OP询问的更新

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

同样的答案:“到处都是”这样做也是糟糕练习。因为此代码令读者感到惊讶。

以上:

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

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

答案 1 :(得分:15)

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

在几乎所有情况下,捕获泛型Exception或Throwable(最好不是Throwable,因为它包含Error异常)是不合适的。这是非常危险的,因为它意味着你从未预料到的异常(包括像RuntimeExceptions这样的ClassCastException)会陷入应用程序级错误处理。

     

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

  

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

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

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

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

P.S。不要害怕例外!!他们是朋友!!!

答案 2 :(得分:9)

我会将此作为对其他答案的评论,但我还没有获得声誉。

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

  1. 缺乏错误处理
  2. Generic Catch
  3. 无故意例外
  4. Blanket Try / catch
  5. 我试图通过这个例子来解释所有这些。

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

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

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

      (1)首先,默默失败并不是一件好事。如果发生故障,该应用程序将无法运行。相反,应该尝试解决问题或显示警告,例如&#34;无法加载用户数据,也许您没有连接到互联网?&#34;。如果应用程序没有按照它应该做的那样做,那对用户来说比对它自己关闭更令人沮丧。

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

      (1,2,3)从Web加载经常抛出各种异常,从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。这样你至少可以看到类似的东西(嘿,由于像(4)这样的问题,主要活动不会为80%的用户加载。

答案 3 :(得分:8)

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

从目前的情况来看,如果有这样的数百try catch,那么你甚至不知道在没有调试应用程序的情况下发生异常的地方,如果你的话,这是一场噩梦应用程序在生产环境中。

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

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

答案 4 :(得分:7)

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

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

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

[现在某些情况下空catch可以。例如,日志记录是您可以证明包装在空捕获中的原因。即使无法写入某些日志记录,您的应用程序也可能正常运行。但这些是特殊情况,你必须在普通应用程序中很难找到。]

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

答案 5 :(得分:3)

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

  1. 你在做什么findViewById抛出异常?解决这个问题(告诉我,因为我从未见过这个),而不是捕捉。
  2. 当你能捕捉到特定类型的异常时,不要抓住Exception
  3. 有一种观点认为好的应用程序不会崩溃。那不是真的。一个好的应用程序如果必须崩溃。
  4. 如果一个应用程序进入一个糟糕的状态,它崩溃要比它在无法使用的状态下突然好得多。当人们看到NPE时,不应该只是坚持一个空检查并走开。更好的方法是找出为什么某些东西为null并且阻止它为null,或者(如果null最终成为有效和预期的状态)检查null。但你必须首先理解为什么会出现这个问题。

答案 6 :(得分:2)

过去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);

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

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

答案 7 :(得分:1)

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

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

答案 8 :(得分:1)

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

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

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

答案 9 :(得分:1)

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

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

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

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

因此,简短的回答是,您找到的代码不是一个好的实践示例,因为在不提高应用程序质量的情况下维护代码很难且成本高昂。

答案 10 :(得分:1)

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

所以问题是:

try{
  someMethod();
}catch(Exception e){}
好的做法?没有!以下是几点:

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

  2. 当我正在进行开发并编写错误的代码时,如果我只是捕获并吞掉顶层的所有异常,我就没有记录。我的控制台中没有任何内容,我的应用程序无声地失败。从我所知,一切似乎都可行。所以我提交了代码......结果我的DAO对象一直是null,并且客户的付款从未在DB中实际更新过。哎呦!但我的应用程序没有崩溃,所以这是一个加号。

  3. 扮演魔鬼的拥护者,让我说我可以抓住并吞下每一个例外。在Android中编写自定义异常处理程序非常容易。如果您真的只是 来捕获每个异常,那么您可以在一个地方进行,而不是在代码库中使用try/catch

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

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

答案 11 :(得分:0)

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

例外应该从不忽略。这是一个糟糕的编码实践。如果没有记录代码,维护代码的人将非常难以本地化引发异常的代码部分。

我们使用Crashlytics来记录异常。代码不会崩溃(但某些功能会中断)。但是您在Fabric/Crashlytics的仪表板中获得了异常日志。您可以查看这些日志并修复例外情况。

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

答案 12 :(得分:0)

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

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

答案 13 :(得分:0)

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

private void comboBox1_SelectedIndexChanged(object sender, SelectionChangedEventArgs e)
{
    if(comboBox1.SelectedItem.ToString() == "W")
    {
        listBox.DataSource= WList;
    }
    else if(comboBox1.SelectedItem.ToString() == "X")
    {
        listBox.DataSource= XList;
    }
    else if (comboBox1.SelectedItem.ToString() == "Y")
    {
        listBox.DataSource= YList;
    }
    else if (comboBox1.SelectedItem.ToString() == "Z")
    {
        listBox.DataSource= ZList;
    }
}

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