我一直在研究一款经常使用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) {
}
整个应用程序都使用此方法。堆栈跟踪没有打印,很难找到出错的地方。应用程序突然关闭而不打印任何堆栈跟踪。
我让我的大四学生向我解释,他说,
这是为了防止生产中的崩溃。
我完全不同意它。对我而言,这不是阻止应用程序崩溃的方法。它表明开发人员不知道他/她在做什么,并且有疑问。
这是行业中使用的方法,以防止企业应用程序崩溃?
如果真的是try/catch
,那么我们真的需要用UI线程或其他线程附加异常处理程序并捕获所有内容吗?如果可能的话,那将是更好的方法。
是的,空的try/catch
是坏的,即使我们打印堆栈跟踪或记录异常到服务器,在所有应用程序中随机包装try/catch
中的代码块对我来说没有意义,例如当每个函数都包含在try/catch
中时。
UPDATE
由于这个问题引起了很多关注,有些人误解了这个问题(也许是因为我没有明确表达过),我将把它改写一下。
这是开发人员在这里做的事情
try/catch
块中。即使是永远不会抛出任何异常的函数。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) {
}
}
它不是Android exception handling best practices的重复,OP试图捕获异常的目的不同于这个问题。
当然,规则总是有例外,但如果你需要一个经验法则 - 那么你是对的;空抓块是“绝对”不好的做法。
让我们仔细看看,首先从您的具体示例开始:
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作为“模式”,就像你正在展示的那样;如上所述:仍然不是一个好主意。是的,它可以防止崩溃;但会导致各种“未定义”的行为。你知道,当你抓住一个例外而不是妥善处理它时;你打开一罐虫子;因为你可能会遇到许多后来你不理解的后续错误。因为您之前消耗了“根本原因”事件;把它印在某处;那个地方现在已经消失了。
让我加入我的观点,作为一个在企业移动开发行业工作十多年的人。首先,一些关于例外的一般提示,其中大部分都包含在上面的答案中:
现在,当您不是为自己开发应用程序,而是为公司或公司开发应用程序时,通常会遇到有关此主题的其他要求:
因此,简短的回答是,您找到的代码不是一个好的实践示例,因为在不提高应用程序质量的情况下进行维护是非常困难和昂贵的。
另一种观点,作为每天编写企业软件的人,如果应用程序有不可恢复的错误,我希望它崩溃。崩溃是可取的。如果它崩溃,它会被记录。如果它在短时间内崩溃了几次,我会收到一封电子邮件,说应用程序崩溃,我可以验证我们的应用程序和我们使用的所有Web服务仍在运行。
所以问题是:
是
View view = findViewById(R.id.toolbar);
好的做法?没有!以下是几点:
try{
someMethod();
}catch(Exception e){}
。我过去曾与之合作的一些开发人员认为崩溃很糟糕。
我必须向他们保证,我们希望我们的应用程序崩溃。不,一个不稳定的应用程序不行,但崩溃意味着我们做错了什么,我们需要修复它。崩溃越快,我们越早发现它就越容易解决。我能想到的唯一另一个选择是允许用户继续在一个破碎的会话中,我等同于惹恼我的用户群。
使用try/catch
是不好的做法,因为你基本上忽略了这个错误。您可能想要做的更像是:
catch(Exception e){}
我们非常使用相同的逻辑。使用try {
//run code that could crash here
} catch (Exception e) {
System.out.println(e.getMessage());
}
防止生产应用程序崩溃。
应该永远不要忽视例外情况。这是一个糟糕的编码实践。维护代码的人将非常难以本地化部分代码,如果他们没有被记录则引发异常。
我们使用try-catch
来记录异常。代码不会崩溃(但某些功能会中断)。但是你在Crashlytics
的仪表板中得到了异常日志。您可以查看这些日志并修复异常。
Fabric/Crashlytics
虽然我同意其他答复,但我反复遇到的一个情况是这个主题是可以容忍的。假设有人为类编写了一些代码,如下所示:
try {
codeThatCouldRaiseError();
} catch (Exception e) {
e.printStackTrace();
Crashlytics.logException(e);
}
在这种情况下,'getFoo()'方法不会失败 - 总是会返回私有字段'foo'的合法值。然而有人 - 可能是出于迂腐的原因 - 决定将此方法声明为可能引发异常。如果你试图在一个上下文中调用这个方法 - 例如一个事件处理程序 - 它不允许抛出异常,你基本上被迫使用这个构造(即便如此,我同意一个人应该至少记录异常只是如果)。每当我必须这样做时,我总是至少在“捕获”条款旁边添加一个很大的评论“这不能发生”。
来自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,在每种情况下分别处理错误。
- 重新抛出异常。很多时候你不需要在这个级别捕获异常,只需让方法抛出它。
在大多数情况下,您不应该以相同的方式处理不同类型的异常。
从此答案的来源略微修改格式/段落。
附:不要害怕例外!!他们是朋友!!!
我会把这作为对其他答案的评论,但我还没有这个名声。
你说这是不好的做法是正确的,实际上你发布的内容显示了异常方面不同类型的不良做法。
我将尝试通过此示例解释所有这些。
Exception
这可能会以多种方式失败,应以不同方式处理。
解决方案
(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%的用户加载。
这绝对是一个糟糕的编程习惯。
从目前的情况来看,如果有这样的数百个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
这是不好的做法。其他答案已经说过,但我认为重要的是退后一步,理解为什么我们首先有例外。
每个函数都有一个后置条件 - 一组在该函数执行后必须全部为真的东西。例如,从文件读取的函数具有post条件,即文件中的数据将从磁盘读取并返回。因此,当函数无法满足其后置条件之一时,抛出异常。
通过忽略函数中的异常(或者甚至通过简单地记录异常来有效地忽略它),你说你对这个函数没有用,它实际上没有做它同意做的所有工作。这似乎不太可能 - 如果一个函数没有正确运行,则无法保证后面的内容将会运行。如果代码的其余部分运行正常,无论某个特定函数是否运行完成,那么人们就会想知道为什么你首先拥有该函数。
[现在有些情况下空的...
try {
View view = findViewById(R.id.toolbar);
}catch(Exception e){
logger.log(Level.SEVERE, "an exception was thrown", e);
}
...
es可以。例如,日志记录是您可以证明包装在空捕获中的原因。即使无法写入某些日志记录,您的应用程序也可能正常运行。但这些是特殊情况,你必须在普通应用程序中很难找到。]
所以重点是,这是不好的做法,因为它实际上并没有使你的应用程序运行(假设这种风格的理由)。也许技术上操作系统还没有杀死它。但是,在忽略异常之后,应用程序仍然不太可能正常运行。在最坏的情况下,它实际上可能会造成伤害(例如,破坏用户文件等)。
由于多种原因,这很糟糕:
catch
抛出一个例外?修复(并告诉我,因为我从未见过这个)而不是捕捉。findViewById
。如果一个应用程序进入一个糟糕的状态,它崩溃比它在无法使用的状态下突然好得多。当人们看到一个NPE时,不应该只是坚持一个空检查并走开。更好的方法是找出为什么某些东西为null并且阻止它为null,或者(如果null最终成为有效和预期的状态)检查null。但你必须首先理解问题的原因。
我在过去的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是一种不好的做法。
是的,try / catch用于防止应用程序崩溃,但您肯定不需要try / catch从XML中获取视图,如您的问题所示。
try / catch通常在进行任何http请求时使用,同时将任何String解析为URL,创建URL连接等,并确保打印堆栈跟踪。使用try / catch环绕它并没有多大意义。
如前所述,不应该捕获一般异常,或者至少仅在少数中心位置(通常位于框架/基础结构代码中,而不是应用程序代码中)。如果捕获一般异常并记录它,则应该在之后关闭应用程序,或者至少应该通知用户应用程序可能处于不稳定状态并且可能发生数据损坏(如果用户选择继续执行)。因为如果您捕获所有类型的异常(内存不足以命名一个)并使应用程序处于未定义状态,这可能会发生。
恕我直言,更糟糕的是吞下异常并冒险数据完整性,数据丢失,或者只是让应用程序处于未定义状态,而不是让应用程序崩溃,用户知道出现了问题,可以再试一次。这也将导致报告更好的问题(更多问题的根源),可能比用户开始报告源自未定义的应用程序状态的所有类型的麻烦更少的不同症状。
在中央异常处理/记录/报告和受控关闭到位后,开始重写异常处理以捕获尽可能具体的本地异常。尽量让try {}块尽可能短。