在当前的Java项目中,我们的代码类似于以下示例:
try {
doSomeThing(anObject);
}
catch (SameException e) {
// Do nothing or log, but don't abort current method.
}
try {
doOtherThing(anObject);
}
catch (SameException e) {
// Do nothing or log, but don't abort current method.
}
// ... some more calls to different method ...
try {
finallyDoYetSomethingCompletelyDifferent(anObject);
}
catch (SameException e) {
// Do nothing or log, but don't abort current method.
}
正如您所看到的,使用完全相同的对象调用了几个不同的方法,并且对于每个调用,捕获并处理相同的异常(或以非常类似的方式)。异常不会被重新抛出,但可能只会被记录然后被丢弃。
每个方法都有try-catch
的唯一原因是,无论以前执行的方法是否失败,都要始终执行所有方法。
我根本不喜欢上面的代码。它占用了大量空间,非常重复(尤其是在catch
- 块中完成的日志记录;此处未显示)并且看起来很糟糕。
我可以想到其他一些编写这段代码的方法,但也不太喜欢它们。我想到了以下选项:
循环切换顺序/案例范例
(请参阅Wikipedia或The Daily WTF)
for (int i = 0; i <= 9; i++) {
try {
switch (i) {
case 0:
doSomeThing(anObject); break;
case 1:
doOtherSomeThing(anObject); break;
// ...More cases...
case 9:
doYetSomethingCompletelyDifferent(anObject); break;
}
}
catch (SameException e) {
// Do nothing or log, but don't abort current method.
}
}
这显然是错误的代码,非常容易出错并且看起来很业余。
反射
使用反射来获取方法的Method
个对象,并按照它们应该执行的顺序将它们存储在列表中。然后迭代此列表并使用anObject
作为唯一参数调用该方法。异常在循环内部处理。
我不喜欢这种方法,因为错误(例如方法名称中的拼写错误)仅在运行时弹出,并且Reflection API有些繁琐。
函子
像这样创建一个Functor类:
private class Functor
{
void doStuff(MyObject object) throws SameException;
}
然后创建一个调用方法的Functor
个对象列表。像这样:
List<Functor> functors = new ArrayList<Functor>();
functors.add(new Functor() {
@Override
public void execute(MyObject anObject) {
doSomeThing(anObject);
}
});
functors.add(new Functor() {
@Override
public void execute(MyObject anObject) {
doOtherSomeThing(anObject);
}
});
稍后,迭代此列表并在每个execute()
对象上调用Functor
。
我可以用两个词来总结我对这种方法的感觉:代码膨胀。
由于我不喜欢这四种方法,我想在这里讨论这个问题。你觉得最好的方法是什么?你是怎么解决过去的类似问题的?是否有一个我完全错过的更简单的解决方案?
答案 0 :(得分:6)
我会提倡重构方法(或“我们为什么首先来到这里?”):
考虑为什么单个方法在使用myObject执行“stuff”之后可以抛出异常,然后可以安全地忽略该异常。由于异常会转义该方法,因此myObject必须处于未知状态。
如果忽略异常是安全的,那么肯定是错误的方式来传达每种方法中出错的地方。
相反,也许每个方法都需要对失败进行一些记录。如果不使用静态记录器,则可以将记录器传递给每个方法。
答案 1 :(得分:5)
仿函数方法对我来说是最好的方法 - 遗憾的是Java没有更好的方式来表示闭包或委托。这基本上就是你真正之后的,而在C#(以及许多其他语言)中,这将是微不足道的。
你可以通过使用类似的东西来减少物理臃肿:p>
Functor[] functors = new Functor[] {
new Functor() { @Override public void execute(MyObject anObject) {
doSomeThing(anObject);
}},
new Functor() { @Override public void execute(MyObject anObject) {
doSomeOtherThing(anObject);
}}
};
这里的空白崩溃很可能违背你正在使用的风格指南,但我认为它使代码更容易实际阅读,因为你可以更容易地看到肉。
最好开始游说Java 8中的闭包;)
答案 2 :(得分:3)
初看起来,我同意PeterR:如果轻易忽略异常是安全的,也许该方法根本不应该抛出异常。
但是,如果您确定这正是您想要的,也许您正在使用第三方库中坚持抛出特定异常的方法,我会选择以下方法:
创建一个包含所有可以调用的方法的接口:
public interface XyzOperations { public void doSomething(Object anObject); public void doOtherThing(Object anObject); ... public void finallyDoYetSomethingCompletelyDifferent(Object anObject);
为这些方法创建一个默认的实现类适当的方法,可能会从其他地方重构它们:
public class DefaultXyzOperations implements XyzOperations { ... }
使用Java Proxy类在XyzOperations
上创建一个动态代理,它将所有方法委托给DefaultXyzOperations
,但是,它会在{{3}中集中处理异常处理}}。我没有编译以下内容,但这是一个基本的大纲:
XyzOperations xyz = (XyzOperations)Proxy.newProxyInstance( XyzOperations.class.getClassLoader(), new Class[] { XyzOperations.class }, new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { method.invoke(new DefaultXyzOperations(), args); } catch(SameException e) { // desired exception handling } } });
从那时起使用该代理实例,只需调用所需的方法
或者,您可以使用InvocationHandler或类似的AOP解决方案向XyzOperations
的所有方法添加建议,并在那里进行异常处理。
是否要引入新的依赖项,或者手动编写代理,取决于您的个人偏好以及您需要此类行为的代码总量。
答案 3 :(得分:2)
我同意PeterR。我发现很难相信你真的想在抛出异常后继续执行某些事情。如果你这样做,那么可能并没有发生异常事件。
换句话说,异常不应用于日志记录或流量控制。它们只应在异常发生的情况下使用,即抛出异常的级别的代码无法处理。
因此,我会内化日志消息并删除正在抛出的异常。
至少,我认为您需要返回并重新理解代码尝试执行的操作以及正在实施的业务价值或规则。正如PeterR所说,试着理解“我们为什么一开始就来到这里?”部分代码以及异常的含义究竟是什么。
答案 4 :(得分:2)
您控制的方法是否在您的控制之下?如果是这样,在这种特殊情况下,返回错误代码(或对象)可能会产生比使用异常更好的整体设计:
handle(doSomething(anObject));
handle(doOtherThing(anObject));
// some more calls to different methods
handle(finallyDoYetSomethingCompletelyDifferent(anObject));
与
private void handle(ErrorCode errorCode) {
// Do something about it
}
和
private ErrorCode doSomething(Object anObject) {
// return ErrorCode describing the operation's outcome
}
虽然不是DRY,但这似乎不那么冗长。
或者,您使用一些AOP机制拦截对doSomething
,doOtherThing
和finallyDoYetSomethingCompletelyDifferent
的调用,并使用一个首先处理然后丢弃异常的Around Advice。将它与RuntimeExceptions和基于一些很好的描述性注释的切入点相结合,你可以完美地捕捉到某种隐藏的横切关注点。
我承认我喜欢Functor方法,等等。
编辑:刚刚看到你对其中一个答案的评论。在那种情况下,我可能会采用AOP方法。
答案 5 :(得分:1)
不考虑重构问题,AspectJ(或类似)似乎是透明地捕获/报告这些异常的最简单方法。您应该能够进行配置,即使您添加新的方法调用,它也会围绕方法调用编织try / catch(我怀疑,如果某人在没有完全理解的情况下修改代码,那么上面的代码块会非常脆弱它背后的基本原理)
答案 6 :(得分:0)
如果你要使用不同的代码风格路线,我会坚持一个简单的方法:
try { doSomeThing(anObject); } catch (SameException e) { Log(e); }
try { doOtherThing(anObject); } catch (SameException e) { Log(e); }
// ... some more calls to different method ...
更新:我没有看到像Functor的方法这样的语法如何减少所涉及的任何代码。正如Jon所提到的,java不支持简单的语法来进一步减少它。如果它是c#,那么你可以做很多变化,这就是没有太多额外的语法来组合这样的方法,例如像 actions.Add(()=&gt; doSomething(anObject)这样的表达式);
答案 7 :(得分:0)
让我详细说明仿函数方法......
根据应用程序的复杂程度,有时将部分业务逻辑移至conf文件是值得的,可以更恰当和简洁地表达。通过这种方式,您可以将业务逻辑中的技术细节(函子创建/调用,异常处理)分开 - 定义哪些方法以及调用的顺序。
最简单的形式可能是这样的:
mypackage.Action1
mypackage.Action2
...
其中ActionX是实现Functor类(或接口)的类。