使用 try/catch 防止应用程序崩溃
Using try/catch for preventing app from crashes
我一直在开发一个 Android 应用程序,它经常使用 try/catch
来防止它在不需要的地方崩溃。例如,
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) {
}
整个应用程序都使用这种方法。堆栈跟踪没有打印出来,真的很难找到哪里出了问题。该应用程序突然关闭,没有打印任何堆栈跟踪。
我请学长给我解释,他说,
This is for preventing crashing in production.
我完全不同意。对我来说,这不是防止应用程序崩溃的方法。这表明开发人员 不 知道 he/she 在做什么并且有疑问。
这是业界用来防止企业应用程序崩溃的方法吗?
如果 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) {
}
}
It is not duplicate of Android exception handling best
practices, there OP is trying to catch exception for a different
purpose than this question.
当然,规则总有例外,但如果您需要一个经验法则 - 那么您是对的;空的 catch 块是 "absolutely" 不好的做法。
让我们仔细看看,首先从您的具体示例开始:
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) { }
但是,这又有什么用呢?!代码中的 communicate 和 propagate 错误情况存在例外情况。忽略错误很少是一个好主意。实际上,可以通过以下方式处理异常:
- 您给用户反馈; (例如:"the value you entered is not a string, try again");或者进行更复杂的错误处理
- 也许这个问题在某种程度上是预料之中的,并且可以缓解(例如,当某些 "remote search" 失败时给出 "default" 答案)
- ...
长话短说:最小 你做的有例外的事情是 log/trace 它;这样当你稍后调试一些问题时你就会明白 "OK, at this point in time that exception happened".
正如其他人所指出的那样:您通常也避免捕获 Exception(好吧,取决于层:可能有充分的理由捕获 Exception,甚至是最高级别的某些错误,以确保 nothing 丢失;ever ).
最后,让我们 quote 沃德·坎宁安:
当您阅读的每个例程都非常符合您的预期时,您就知道您正在使用干净的代码。当代码看起来像是为解决问题而设计的语言时,您可以称其为漂亮的代码。
让它沉入其中并进行冥想。整洁的代码 不会 让您感到惊讶。您向我们展示的示例让 每个人 都感到惊讶。
更新,关于 OP 询问的更新
try {
do something
}
catch(Exception e) {
print stacktrace
}
同样的答案:这样做 "all over the place" 也是 不好的 做法。因为这段代码 也 令人惊讶 reader.
以上:
- 在某处打印错误信息。 根本无法保证此"somewhere"类似于合理目的地。从相反的方面来说。示例:在我正在使用的应用程序中,此类调用会神奇地出现在我们的跟踪缓冲区中。根据上下文,我们的应用程序有时可能会将大量数据泵入这些缓冲区;每隔几秒制作这些缓冲区 p运行e 。所以 "just printing errors" 通常翻译为: "simply loosing all such error information".
- 然后:你不做 try/catch 因为你 可以 。你这样做是因为你了解你的代码在做什么;你知道:我最好在这里有一个 try/catch 来做正确的事情(再次查看我的回答的第一部分)。
所以,像您展示的那样使用 try/catch 作为 "pattern";正如所说:仍然不是一个好主意。是的,它 防止 崩溃;但会导致各种 "undefined" 行为。你知道,当你只是捕获异常而不是正确处理它时;你打开了一罐蠕虫;因为您可能 运行 陷入无数 后续 错误,而这些错误您以后都无法理解。因为您之前消费了 "root cause" 事件;打印在某处; 某处现在已经消失了。
这绝对是一种糟糕的编程习惯。
从目前的场景来看,如果有数百个这样的try
catch
,那么不调试应用程序你甚至都不知道异常发生在哪里,如果你的应用程序是一个噩梦应用程序在生产环境中。
但是您可以包含一个记录器,以便您了解何时抛出异常(以及原因)。它不会改变您的正常工作流程。
...
try {
View view = findViewById(R.id.toolbar);
}catch(Exception e){
logger.log(Level.SEVERE, "an exception was thrown", e);
}
...
在过去的 4-5 年里,我一直在开发 android 应用程序,但从未使用 try catch 进行视图初始化。
如果它的工具栏是这样的
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
例如:- 从视图中获取 TextView(fragment/dialog/any 自定义视图)
TextView textview = (TextView) view.findViewById(R.id.viewId);
TextView textview = (TextView) view.findViewById(R.id.viewId);
而不是这个
View view = findViewById(R.id.toolbar);
与其真实视图类型相比,视图对象具有最小范围。
注意:- 可能是因为加载了视图而崩溃。但是添加 try catch 是一种不好的做法。
是的,try/catch 用于防止应用程序崩溃,但您当然不需要 try/catch 从 XML 获取视图,如您的问题所述。
try/catch 通常在发出任何 http 请求时使用,同时将任何字符串解析为 URL、创建 URL 连接等,并确保打印堆栈跟踪。不打印它用 try/catch.
包围它没有多大意义
我们将其命名为 -
Don't Catch Generic Exception
It can also be tempting to be lazy when catching exceptions and do something like this:
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!
}
In almost all cases it is inappropriate to catch generic Exception
or Throwable (preferably not Throwable because it includes Error exceptions). It is very dangerous because it means that Exceptions you never expected (including RuntimeExceptions
like ClassCastException
) get caught in application-level error handling.
It obscures the failure handling properties of your code, meaning if someone adds a new type of Exception
in the code you're calling, the compiler won't help you realize you need to handle the error differently.
Alternatives to catching generic Exception:
- Catch each exception separately as separate catch blocks after a single try. This can be awkward but is still preferable to catching all Exceptions.
Edit by author: This one is my choice. Beware repeating too much code in the catch blocks. If you are using Java 7 or above, use multi-catch to avoid repeating the same catch block.
- Refactor your code to have more fine-grained error handling, with multiple try blocks. Split up the IO from the parsing, handle errors separately in each case.
- Re-throw the exception. Many times you don't need to catch the exception at this level anyway, just let the method throw it.
In most cases you shouldn't be handling different types of exception the same way.
此答案的格式/段落略有修改。
P.S。不要害怕异常!!他们是朋友!!!
我们几乎使用了与您相同的逻辑。使用 try-catch
防止生产应用程序崩溃。
异常应该从不忽略。这是一种糟糕的编码习惯。维护代码的人将很难本地化引发异常的代码部分(如果未记录)。
我们使用 Crashlytics
来记录异常。代码不会崩溃(但某些功能会中断)。但是您在 Fabric/Crashlytics
的仪表板中获得了异常日志。您可以查看这些日志并修复异常。
try {
codeThatCouldRaiseError();
} catch (Exception e) {
e.printStackTrace();
Crashlytics.logException(e);
}
使用 catch(Exception e){}
是不好的做法,因为您实际上忽略了错误。您可能想要做的更像是:
try {
//run code that could crash here
} catch (Exception e) {
System.out.println(e.getMessage());
}
我会把它作为对其他答案的评论,但我还没有这方面的声誉。
你说这是不好的做法是正确的,事实上你发布的内容显示了关于异常的不同类型的坏做法。
- 缺乏错误处理
- 通用捕获
- 没有故意的例外
- 毯子Try/catch
我会尝试通过这个例子来解释所有这些。
try {
User user = loadUserFromWeb();
if(user.getCountry().equals("us")) {
enableExtraFields();
}
fillFields(user);
} catch (Exception e) {
}
这可能会以多种方式失败,应该以不同的方式处理。
- 这些字段不会被填充,所以用户会看到一个空白屏幕,然后……什么?没有 - 缺少错误处理。
- 不同类型的错误之间没有区别,例如Internet 问题或服务器本身的问题(中断、请求中断、传输损坏等)- 一般问题。
- 您不能将例外用于您自己的目的,因为当前系统会对此进行干扰。 - 没有故意的例外
- 不必要的和意外的错误(例如 null.equals(...))会导致重要代码无法执行。 - 毯子 try/catch
解决方案
(1) 首先,默默地失败并不是一件好事。如果出现故障,该应用程序将无法运行。相反,应该尝试解决问题或显示警告,例如 "Could not load user data, maybe you're not connected to the Internet?"。如果该应用程序没有按预期运行,这比它自行关闭更让用户感到沮丧。
(4) 如果用户不完整,例如该国家/地区未知,returns 为空。 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% 的用户没有加载主 activity。
虽然我同意其他回答,但我曾多次遇到过这种主题勉强可以容忍的情况。假设有人为 class 写了一段代码如下:
private int foo=0;
. . .
public int getFoo() throws SomeException { return foo; }
在这种情况下,'getFoo()' 方法 不会失败 - 始终会返回私有字段 'foo' 的合法值。 然而有人 - 可能出于迂腐的原因 - 决定应将此方法声明为可能引发异常。 如果您随后尝试在上下文中调用此方法 - 例如事件处理程序 - 它确实不允许抛出异常,你基本上被迫使用这个结构(即便如此,我同意至少应该记录异常以防万一)。每当我必须这样做时,我总是至少在 'catch' 子句旁边添加一个大胖注释 'THIS CANNOT OCCUR'。
这是不好的做法。其他答案已经说过,但我认为重要的是退后一步并理解 为什么 我们首先有例外。
每个函数都有一个post-条件——一组在该函数执行后必须全部为真的事情。例如,从文件读取的函数具有 post 条件,文件中的数据将从磁盘读取并返回。然后,当函数无法满足其 post 条件之一时抛出异常。
通过忽略函数的异常(或者甚至通过简单地记录异常来有效地忽略它),您是在说您可以接受该函数实际上没有完成它同意做的所有工作。这似乎不太可能——如果一个函数没有正确 运行,则根本无法保证后面的内容会 运行。如果您的代码的其余部分 运行 可以正常完成某个特定功能 运行 ,那么有人想知道为什么您首先拥有该功能。
[现在有些情况下空 catch
es 是可以的。例如,日志记录是您可以证明用空捕获包装的东西。即使无法写入某些日志记录,您的应用程序也可能 运行 正常。但这些都是特殊情况,您必须非常努力地工作才能在普通应用程序中找到它们。]
所以关键是,这是一种不好的做法,因为它实际上并没有使您的应用程序保持 运行宁(这种风格的假定理由)。也许从技术上讲 OS 并没有杀死它。但是,在简单地忽略异常后,应用程序不太可能 运行 正常运行。在最坏的情况下,它实际上可能会造成伤害(例如损坏用户文件等)。
这很糟糕,原因有很多:
- 你在做什么
findViewById
抛出异常?修复它(并告诉我,因为我从未见过这个)而不是捕捉。
- 当您可以捕获特定类型的异常时,不要捕获
Exception
。
- 人们相信好的应用程序不会崩溃。这不是真的。一个好的应用程序如果必须崩溃。
如果一个应用程序进入了一个糟糕的状态,与其在无法使用的状态下继续运行,不如让它崩溃好得多。当看到 NPE 时,不应该只是坚持进行空检查然后走开。更好的方法是找出为什么某些东西为 null 并阻止它为 null,或者(如果 null 最终成为有效和预期的状态)检查 null。但是你首先要明白为什么会出现这个问题。
如前所述,不应该捕获一般异常,或者至少只在几个中心位置(通常位于framework/infrastructure代码中,而不是应用程序代码中) ).如果捕获一般异常并记录它,应用程序应该在之后关闭,或者至少应该通知用户应用程序可能处于不稳定状态并且可能发生数据损坏(如果用户选择继续执行)。因为如果您捕获所有类型的异常(例如内存不足)并使应用程序处于未定义状态,可能会发生这种情况。
恕我直言,吞下异常并冒着数据完整性、数据丢失或只是让应用程序处于未定义状态的风险比让应用程序崩溃并且用户知道出现问题并可以重试更糟糕。这也将导致报告更好的问题(更多是问题的根源),与您的用户开始报告源自未定义的应用程序状态的各种问题相比,不同的症状可能更少。
中央异常 handling/logging/reporting 和受控关闭到位后,开始重写异常处理以尽可能具体地捕获本地异常。尝试使 try{} 块尽可能短。
作为一个在企业移动开发行业工作了十多年的人,补充一下我的观点。首先,一些关于异常的一般提示,其中大部分包含在上面的答案中:
- 异常应该用于例外、意外或不受控制的情况,而不是在整个代码中定期使用。
- 程序员必须知道容易引发异常的代码部分并尝试捕获它们,让其余代码尽可能干净。
- 作为一般规则,例外情况不应保持沉默。
现在,当您不是为自己开发应用程序,而是为公司或企业开发应用程序时,通常会面临关于此主题的额外要求:
- "App crashes show a poor image of the company, so they are not acceptable"。然后,应该进行仔细的开发,甚至捕获不太可能的异常也可能是一种选择。如果是这样,则必须有选择地进行并保持在合理的范围内。请注意,开发不仅仅与代码行有关,例如,在这些情况下,密集的测试过程至关重要。然而,企业应用程序中的意外行为比崩溃更糟糕。因此,无论何时您在应用程序中捕获异常,您都必须知道要做什么、要显示什么以及应用程序下一步将如何运行。如果你无法控制,最好让应用程序崩溃。
- "Logs and stack traces may dump sensitive information to the console. That might be used for an attacker, so for security reasons they cannot be used in a production environment"。这个要求与开发人员不编写静默异常的一般规则冲突,因此您必须为它找到一种方法。例如,您的应用程序可以控制环境,因此它在非生产环境中使用日志和堆栈跟踪,而在生产环境中使用基于云的工具,如 bugsense、crashlitics 或类似工具。
所以,简短的回答是您找到的代码不是一个好的实践示例,因为如果不提高应用程序的质量,维护起来既困难又昂贵。
换个角度,作为每天编写企业软件的人,如果一个应用程序出现不可恢复的错误,我希望它崩溃。崩溃是可取的。如果它崩溃了,它会被记录下来。如果它在短时间内崩溃多次,我会收到一封电子邮件,说该应用程序正在崩溃,我可以验证我们的应用程序和我们使用的所有 Web 服务是否仍在运行。
所以问题:
是
try{
someMethod();
}catch(Exception e){}
好的做法?不!这里有几点:
最重要的一点:这是糟糕的客户体验。我怎么知道什么时候发生了不好的事情?我的客户正在尝试使用我的应用程序,但没有任何效果。他们不能检查他们的银行账户,支付他们的账单,无论我的应用程序做什么。我的应用程序完全没用,但是嘿,至少它没有崩溃! (我的一部分认为这个 "Senior" 开发人员因低崩溃次数而获得加分,所以他们在玩弄系统。)
当我在进行开发并且编写了糟糕的代码时,如果我只是在顶层捕获并吞下所有异常,我就没有日志记录。我的控制台中没有任何内容,我的应用程序静默失败。据我所知,一切似乎都正常。所以我提交了代码...原来我的 DAO 对象一直都是空的,而且客户的付款实际上从未在数据库中更新过。哎呀!但是我的应用程序没有崩溃,所以这是一个优点。
唱反调,假设我可以捕捉并吞下每一个异常。在 Android 中编写自定义异常处理程序 非常容易。如果你真的只是 来捕捉每一个异常,你可以在一个地方做,而不是在你的代码库中到处撒 try/catch
。
我过去合作过的一些开发人员认为崩溃很糟糕。
我必须向他们保证我们希望我们的应用崩溃。不,不稳定的应用程序不好,但崩溃意味着我们做错了什么,我们需要修复它。它崩溃得越快,我们越早发现它,就越容易修复。我能想到的唯一其他选择是允许用户在中断的会话中继续,我认为这等同于激怒我的用户群。
我一直在开发一个 Android 应用程序,它经常使用 try/catch
来防止它在不需要的地方崩溃。例如,
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) {
}
整个应用程序都使用这种方法。堆栈跟踪没有打印出来,真的很难找到哪里出了问题。该应用程序突然关闭,没有打印任何堆栈跟踪。
我请学长给我解释,他说,
This is for preventing crashing in production.
我完全不同意。对我来说,这不是防止应用程序崩溃的方法。这表明开发人员 不 知道 he/she 在做什么并且有疑问。
这是业界用来防止企业应用程序崩溃的方法吗?
如果 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) { } }
It is not duplicate of Android exception handling best practices, there OP is trying to catch exception for a different purpose than this question.
当然,规则总有例外,但如果您需要一个经验法则 - 那么您是对的;空的 catch 块是 "absolutely" 不好的做法。
让我们仔细看看,首先从您的具体示例开始:
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) { }
但是,这又有什么用呢?!代码中的 communicate 和 propagate 错误情况存在例外情况。忽略错误很少是一个好主意。实际上,可以通过以下方式处理异常:
- 您给用户反馈; (例如:"the value you entered is not a string, try again");或者进行更复杂的错误处理
- 也许这个问题在某种程度上是预料之中的,并且可以缓解(例如,当某些 "remote search" 失败时给出 "default" 答案)
- ...
长话短说:最小 你做的有例外的事情是 log/trace 它;这样当你稍后调试一些问题时你就会明白 "OK, at this point in time that exception happened".
正如其他人所指出的那样:您通常也避免捕获 Exception(好吧,取决于层:可能有充分的理由捕获 Exception,甚至是最高级别的某些错误,以确保 nothing 丢失;ever ).
最后,让我们 quote 沃德·坎宁安:
当您阅读的每个例程都非常符合您的预期时,您就知道您正在使用干净的代码。当代码看起来像是为解决问题而设计的语言时,您可以称其为漂亮的代码。
让它沉入其中并进行冥想。整洁的代码 不会 让您感到惊讶。您向我们展示的示例让 每个人 都感到惊讶。
更新,关于 OP 询问的更新
try {
do something
}
catch(Exception e) {
print stacktrace
}
同样的答案:这样做 "all over the place" 也是 不好的 做法。因为这段代码 也 令人惊讶 reader.
以上:
- 在某处打印错误信息。 根本无法保证此"somewhere"类似于合理目的地。从相反的方面来说。示例:在我正在使用的应用程序中,此类调用会神奇地出现在我们的跟踪缓冲区中。根据上下文,我们的应用程序有时可能会将大量数据泵入这些缓冲区;每隔几秒制作这些缓冲区 p运行e 。所以 "just printing errors" 通常翻译为: "simply loosing all such error information".
- 然后:你不做 try/catch 因为你 可以 。你这样做是因为你了解你的代码在做什么;你知道:我最好在这里有一个 try/catch 来做正确的事情(再次查看我的回答的第一部分)。
所以,像您展示的那样使用 try/catch 作为 "pattern";正如所说:仍然不是一个好主意。是的,它 防止 崩溃;但会导致各种 "undefined" 行为。你知道,当你只是捕获异常而不是正确处理它时;你打开了一罐蠕虫;因为您可能 运行 陷入无数 后续 错误,而这些错误您以后都无法理解。因为您之前消费了 "root cause" 事件;打印在某处; 某处现在已经消失了。
这绝对是一种糟糕的编程习惯。
从目前的场景来看,如果有数百个这样的try
catch
,那么不调试应用程序你甚至都不知道异常发生在哪里,如果你的应用程序是一个噩梦应用程序在生产环境中。
但是您可以包含一个记录器,以便您了解何时抛出异常(以及原因)。它不会改变您的正常工作流程。
...
try {
View view = findViewById(R.id.toolbar);
}catch(Exception e){
logger.log(Level.SEVERE, "an exception was thrown", e);
}
...
在过去的 4-5 年里,我一直在开发 android 应用程序,但从未使用 try catch 进行视图初始化。
如果它的工具栏是这样的
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
例如:- 从视图中获取 TextView(fragment/dialog/any 自定义视图)
TextView textview = (TextView) view.findViewById(R.id.viewId);
TextView textview = (TextView) view.findViewById(R.id.viewId);
而不是这个
View view = findViewById(R.id.toolbar);
与其真实视图类型相比,视图对象具有最小范围。
注意:- 可能是因为加载了视图而崩溃。但是添加 try catch 是一种不好的做法。
是的,try/catch 用于防止应用程序崩溃,但您当然不需要 try/catch 从 XML 获取视图,如您的问题所述。
try/catch 通常在发出任何 http 请求时使用,同时将任何字符串解析为 URL、创建 URL 连接等,并确保打印堆栈跟踪。不打印它用 try/catch.
包围它没有多大意义我们将其命名为 -
Don't Catch Generic Exception
It can also be tempting to be lazy when catching exceptions and do something like this:
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! }
In almost all cases it is inappropriate to catch generic
Exception
or Throwable (preferably not Throwable because it includes Error exceptions). It is very dangerous because it means that Exceptions you never expected (includingRuntimeExceptions
likeClassCastException
) get caught in application-level error handling.It obscures the failure handling properties of your code, meaning if someone adds a new type of
Exception
in the code you're calling, the compiler won't help you realize you need to handle the error differently.
Alternatives to catching generic Exception:
- Catch each exception separately as separate catch blocks after a single try. This can be awkward but is still preferable to catching all Exceptions.
Edit by author: This one is my choice. Beware repeating too much code in the catch blocks. If you are using Java 7 or above, use multi-catch to avoid repeating the same catch block.- Refactor your code to have more fine-grained error handling, with multiple try blocks. Split up the IO from the parsing, handle errors separately in each case.
- Re-throw the exception. Many times you don't need to catch the exception at this level anyway, just let the method throw it.
In most cases you shouldn't be handling different types of exception the same way.
此答案的格式/段落略有修改。
P.S。不要害怕异常!!他们是朋友!!!
我们几乎使用了与您相同的逻辑。使用 try-catch
防止生产应用程序崩溃。
异常应该从不忽略。这是一种糟糕的编码习惯。维护代码的人将很难本地化引发异常的代码部分(如果未记录)。
我们使用 Crashlytics
来记录异常。代码不会崩溃(但某些功能会中断)。但是您在 Fabric/Crashlytics
的仪表板中获得了异常日志。您可以查看这些日志并修复异常。
try {
codeThatCouldRaiseError();
} catch (Exception e) {
e.printStackTrace();
Crashlytics.logException(e);
}
使用 catch(Exception e){}
是不好的做法,因为您实际上忽略了错误。您可能想要做的更像是:
try {
//run code that could crash here
} catch (Exception e) {
System.out.println(e.getMessage());
}
我会把它作为对其他答案的评论,但我还没有这方面的声誉。
你说这是不好的做法是正确的,事实上你发布的内容显示了关于异常的不同类型的坏做法。
- 缺乏错误处理
- 通用捕获
- 没有故意的例外
- 毯子Try/catch
我会尝试通过这个例子来解释所有这些。
try {
User user = loadUserFromWeb();
if(user.getCountry().equals("us")) {
enableExtraFields();
}
fillFields(user);
} catch (Exception e) {
}
这可能会以多种方式失败,应该以不同的方式处理。
- 这些字段不会被填充,所以用户会看到一个空白屏幕,然后……什么?没有 - 缺少错误处理。
- 不同类型的错误之间没有区别,例如Internet 问题或服务器本身的问题(中断、请求中断、传输损坏等)- 一般问题。
- 您不能将例外用于您自己的目的,因为当前系统会对此进行干扰。 - 没有故意的例外
- 不必要的和意外的错误(例如 null.equals(...))会导致重要代码无法执行。 - 毯子 try/catch
解决方案
(1) 首先,默默地失败并不是一件好事。如果出现故障,该应用程序将无法运行。相反,应该尝试解决问题或显示警告,例如 "Could not load user data, maybe you're not connected to the Internet?"。如果该应用程序没有按预期运行,这比它自行关闭更让用户感到沮丧。
(4) 如果用户不完整,例如该国家/地区未知,returns 为空。 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% 的用户没有加载主 activity。
虽然我同意其他回答,但我曾多次遇到过这种主题勉强可以容忍的情况。假设有人为 class 写了一段代码如下:
private int foo=0;
. . .
public int getFoo() throws SomeException { return foo; }
在这种情况下,'getFoo()' 方法 不会失败 - 始终会返回私有字段 'foo' 的合法值。 然而有人 - 可能出于迂腐的原因 - 决定应将此方法声明为可能引发异常。 如果您随后尝试在上下文中调用此方法 - 例如事件处理程序 - 它确实不允许抛出异常,你基本上被迫使用这个结构(即便如此,我同意至少应该记录异常以防万一)。每当我必须这样做时,我总是至少在 'catch' 子句旁边添加一个大胖注释 'THIS CANNOT OCCUR'。
这是不好的做法。其他答案已经说过,但我认为重要的是退后一步并理解 为什么 我们首先有例外。
每个函数都有一个post-条件——一组在该函数执行后必须全部为真的事情。例如,从文件读取的函数具有 post 条件,文件中的数据将从磁盘读取并返回。然后,当函数无法满足其 post 条件之一时抛出异常。
通过忽略函数的异常(或者甚至通过简单地记录异常来有效地忽略它),您是在说您可以接受该函数实际上没有完成它同意做的所有工作。这似乎不太可能——如果一个函数没有正确 运行,则根本无法保证后面的内容会 运行。如果您的代码的其余部分 运行 可以正常完成某个特定功能 运行 ,那么有人想知道为什么您首先拥有该功能。
[现在有些情况下空 catch
es 是可以的。例如,日志记录是您可以证明用空捕获包装的东西。即使无法写入某些日志记录,您的应用程序也可能 运行 正常。但这些都是特殊情况,您必须非常努力地工作才能在普通应用程序中找到它们。]
所以关键是,这是一种不好的做法,因为它实际上并没有使您的应用程序保持 运行宁(这种风格的假定理由)。也许从技术上讲 OS 并没有杀死它。但是,在简单地忽略异常后,应用程序不太可能 运行 正常运行。在最坏的情况下,它实际上可能会造成伤害(例如损坏用户文件等)。
这很糟糕,原因有很多:
- 你在做什么
findViewById
抛出异常?修复它(并告诉我,因为我从未见过这个)而不是捕捉。 - 当您可以捕获特定类型的异常时,不要捕获
Exception
。 - 人们相信好的应用程序不会崩溃。这不是真的。一个好的应用程序如果必须崩溃。
如果一个应用程序进入了一个糟糕的状态,与其在无法使用的状态下继续运行,不如让它崩溃好得多。当看到 NPE 时,不应该只是坚持进行空检查然后走开。更好的方法是找出为什么某些东西为 null 并阻止它为 null,或者(如果 null 最终成为有效和预期的状态)检查 null。但是你首先要明白为什么会出现这个问题。
如前所述,不应该捕获一般异常,或者至少只在几个中心位置(通常位于framework/infrastructure代码中,而不是应用程序代码中) ).如果捕获一般异常并记录它,应用程序应该在之后关闭,或者至少应该通知用户应用程序可能处于不稳定状态并且可能发生数据损坏(如果用户选择继续执行)。因为如果您捕获所有类型的异常(例如内存不足)并使应用程序处于未定义状态,可能会发生这种情况。
恕我直言,吞下异常并冒着数据完整性、数据丢失或只是让应用程序处于未定义状态的风险比让应用程序崩溃并且用户知道出现问题并可以重试更糟糕。这也将导致报告更好的问题(更多是问题的根源),与您的用户开始报告源自未定义的应用程序状态的各种问题相比,不同的症状可能更少。
中央异常 handling/logging/reporting 和受控关闭到位后,开始重写异常处理以尽可能具体地捕获本地异常。尝试使 try{} 块尽可能短。
作为一个在企业移动开发行业工作了十多年的人,补充一下我的观点。首先,一些关于异常的一般提示,其中大部分包含在上面的答案中:
- 异常应该用于例外、意外或不受控制的情况,而不是在整个代码中定期使用。
- 程序员必须知道容易引发异常的代码部分并尝试捕获它们,让其余代码尽可能干净。
- 作为一般规则,例外情况不应保持沉默。
现在,当您不是为自己开发应用程序,而是为公司或企业开发应用程序时,通常会面临关于此主题的额外要求:
- "App crashes show a poor image of the company, so they are not acceptable"。然后,应该进行仔细的开发,甚至捕获不太可能的异常也可能是一种选择。如果是这样,则必须有选择地进行并保持在合理的范围内。请注意,开发不仅仅与代码行有关,例如,在这些情况下,密集的测试过程至关重要。然而,企业应用程序中的意外行为比崩溃更糟糕。因此,无论何时您在应用程序中捕获异常,您都必须知道要做什么、要显示什么以及应用程序下一步将如何运行。如果你无法控制,最好让应用程序崩溃。
- "Logs and stack traces may dump sensitive information to the console. That might be used for an attacker, so for security reasons they cannot be used in a production environment"。这个要求与开发人员不编写静默异常的一般规则冲突,因此您必须为它找到一种方法。例如,您的应用程序可以控制环境,因此它在非生产环境中使用日志和堆栈跟踪,而在生产环境中使用基于云的工具,如 bugsense、crashlitics 或类似工具。
所以,简短的回答是您找到的代码不是一个好的实践示例,因为如果不提高应用程序的质量,维护起来既困难又昂贵。
换个角度,作为每天编写企业软件的人,如果一个应用程序出现不可恢复的错误,我希望它崩溃。崩溃是可取的。如果它崩溃了,它会被记录下来。如果它在短时间内崩溃多次,我会收到一封电子邮件,说该应用程序正在崩溃,我可以验证我们的应用程序和我们使用的所有 Web 服务是否仍在运行。
所以问题:
是
try{
someMethod();
}catch(Exception e){}
好的做法?不!这里有几点:
最重要的一点:这是糟糕的客户体验。我怎么知道什么时候发生了不好的事情?我的客户正在尝试使用我的应用程序,但没有任何效果。他们不能检查他们的银行账户,支付他们的账单,无论我的应用程序做什么。我的应用程序完全没用,但是嘿,至少它没有崩溃! (我的一部分认为这个 "Senior" 开发人员因低崩溃次数而获得加分,所以他们在玩弄系统。)
当我在进行开发并且编写了糟糕的代码时,如果我只是在顶层捕获并吞下所有异常,我就没有日志记录。我的控制台中没有任何内容,我的应用程序静默失败。据我所知,一切似乎都正常。所以我提交了代码...原来我的 DAO 对象一直都是空的,而且客户的付款实际上从未在数据库中更新过。哎呀!但是我的应用程序没有崩溃,所以这是一个优点。
唱反调,假设我可以捕捉并吞下每一个异常。在 Android 中编写自定义异常处理程序 非常容易。如果你真的只是 来捕捉每一个异常,你可以在一个地方做,而不是在你的代码库中到处撒
try/catch
。
我过去合作过的一些开发人员认为崩溃很糟糕。
我必须向他们保证我们希望我们的应用崩溃。不,不稳定的应用程序不好,但崩溃意味着我们做错了什么,我们需要修复它。它崩溃得越快,我们越早发现它,就越容易修复。我能想到的唯一其他选择是允许用户在中断的会话中继续,我认为这等同于激怒我的用户群。