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

mal*_*din 77 java android design-patterns coding-style try-catch

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

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) {
}
Run Code Online (Sandbox Code Playgroud)

整个应用程序都使用此方法.堆栈跟踪没有打印,很难找到出错的地方.应用程序突然关闭而不打印任何堆栈跟踪.

我让我的大四学生向我解释,他说,

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

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

这是行业中使用的方法,以防止企业应用程序崩溃?

如果try/catch是真的,那么我们真的需要可以附加一个带有UI线程或其他线程的异常处理程序并捕获所有内容吗?如果可能的话,那将是更好的方法.

是的,空try/catch是坏的,即使我们将堆栈跟踪或日志异常打印到服务器,try/catch在所有应用程序中随机包装代码块对我来说没有意义,例如当每个函数都包含在a中时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) {
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

不是Android异常处理最佳实践的重复,OP试图捕获异常以达到 此问题不同的目的.

Gho*_*ica 78

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

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

try {
  View view = findViewById(R.id.toolbar);
}
catch(Exception e) { }
Run Code Online (Sandbox Code Playgroud)

因此,创建了对某事物的引用; 当它失败时......没关系; 因为首先没有使用该引用!以上代码绝对是无用的线路噪音.或者编写该代码的人最初是否认为第二次类似的调用会神奇地不再抛出异常?!

也许这看起来像是这样的:

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

但是,这又有什么帮助?!也有例外,以传达分别传播你的代码中的错误情况.忽略错误很少是个好主意.实际上,可以通过以下方式处理异常:

  • 您向用户提供反馈; (例如:"您输入的值不是字符串,请再试一次"); 或参与更复杂的错误处理
  • 也许问题是以某种方式预期的并且可以减轻(例如,当某些"远程搜索"失败时给出"默认"答案)
  • ...

长话短说:你做异常的最小事情就是记录/追踪; 所以,当你稍后调试一些问题时,你会理解"好的,此时异常发生了".

正如其他人已经指出的那样:你也总是避免捕获Exception(好吧,取决于层:可能有充分的理由为Exception捕获一些,甚至是最高级别的某些错误,以确保什么不会丢失; 永远).

最后,让我们引用 Ward Cunningham:

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

让它沉入并沉思它.干净的代码并不会令你大吃一惊.你向我们展示的例子让所有人都惊讶不已.

更新,关于OP询问的更新

try {
  do something
}
catch(Exception e) { 
  print stacktrace
}
Run Code Online (Sandbox Code Playgroud)

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

以上:

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

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

  • 我不得不承认我没有得到你的编辑.不知道你在那里做什么; 或者你在哪里得到这个. (3认同)
  • @GhostCat OP在另一个答案的评论中说,异常块已经包含日志记录调用.这个问题歪曲了实际发生的事情,所以我认为指责高级开发人员不称职是有帮助的.我认为还有更多的事情发生在OP给我们的东西上. (2认同)

Par*_* P. 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!
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

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

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


Syz*_*ygy 9

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

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

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

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

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

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

  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异常甚至只返回.可能是用户没有连接到互联网,可能是后端服务器发生了变化,或者它已经关闭,但你不知道因为你确实捕获了(异常) - 相反,你应该捕获特定的异常.你甚至可以像这样抓住其中几个

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();
}
Run Code Online (Sandbox Code Playgroud)

我希望能够解释它.

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


Ram*_*asi 8

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

从当前场景中,如果有数百个try catch这样的情况,那么在不调试应用程序的情况下,您甚至不知道异常发生在哪里,如果您的应用程序处于生产环境中,这将是一场噩梦.

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

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


Jos*_*G79 7

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

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

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

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

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