如何不在 catch 块中重复代码?

Posted

技术标签:

【中文标题】如何不在 catch 块中重复代码?【英文标题】:How to not repeat code within catch blocks? 【发布时间】:2014-10-05 05:22:27 【问题描述】:

我很难不在我目前正在开发的 Java 程序中重复自己。

比方说,我需要声明很多方法,这些方法基本上是按以下方式构造的:

public SomeEntity doSomething (String someAttribute, String anotherAttribute) 
    EntityManager em = this.createEntityManager();

    EntityTransaction tx = null;
    try 

        /*
         * ... independent logic ...
         */

        tx = em.getTransaction();
     catch (RuntimeException e) 
        if (tx != null && tx.isActive())  
            tx.rollback();
        
        throw e;
     finally 
        em.close();
    

    return something;

所有方法的方法体都需要包含资源管理的这些元素。

“独立逻辑”本身也会相当复杂,因此将 try/catch 语句放在单独的方法中实际上是行不通的。

我想避免重复这段代码。在这些情况下应用的最佳做法是什么?

【问题讨论】:

也许 try-with-resources 可以帮助你 -- docs.oracle.com/javase/tutorial/essential/exceptions/… 这是“执行周围”模式可以提供帮助的地方:***.com/questions/341971/… 嘿@AndersR.Bystrup,非常感谢。我不得不承认我不知道,try-with-resources 存在。不幸的是javax.persistence.EntityManager 没有实现AutoClosable 然后将其包装到实现AutoClosable 的对象中:)。 IE。您的createEntitiyManager 方法返回ClosableEntitiyManager,它是实现AutoClosableEntityManager 的包装,您就完成了。 解决这个问题的另一种方法是使用 Spring。您声明了一个带有@Transactional 注释的方法,所有事务工作都为您完成。 【参考方案1】:

我会创建一个独立逻辑的抽象,比如Job,而doSomething() 将在Service 类中变为processJob()。您将在每次处理时调用您的processJob(),并且除了independent logic 之外的示例中的所有代码都将只编写一次。

编辑:nos 在评论中建议:What is the "Execute Around" idiom?

【讨论】:

【参考方案2】:

你可以让你的方法签名返回异常

 public SomeEntity doSomething (String someAttribute, String anotherAttribute) throws RuntimeException 
// your independent logic


 public SomeEntity doSomethingElse (String someAttribute, String anotherAttribute) throws RuntimeException 
// your independent logic


 public SomeEntity doSomethingDifferent (String someAttribute, String anotherAttribute) throws RuntimeException 
// your independent logic

然后你可以用单独的方法处理它:

   public String yourHandleMethod()
String something = "";
EntityManager em = this.createEntityManager();

    EntityTransaction tx = null;
try
 doSomething();
 doSomethingElse();
 doSomethingDifferent();
 tx = em.getTransaction();
     catch (RuntimeException e) 
        if (tx != null && tx.isActive())  
            tx.rollback();
        
        throw e;
     finally 
        em.close();
    

    return something;

【讨论】:

嗨@Arno_Geismar,非常感谢!不过,问题实际上并不在于方法的顺序执行。【参考方案3】:

如果您所有的finally 子句都用于关闭Streams 等(任何实现AutoCloseable 的东西),您可以使用try-with-resources(如其中一个cmets 中所建议的那样)来摆脱@ 987654325@ 子句。

但是,如果您需要更通用的解决方案,并且在 finally 子句中捕获了相同类型的 Exceptions 和相同类型的处理,则可以创建一个抽象类,例如:

abstract class SensitiveBlockHandler 
    public void handle() 
        try 
            doHandling();
         catch (SomeException | AnotherException e) 
            // TODO: handle exceptions here ...
         finally 
            // TODO: cleanup here ...
        
    

    protected abstract void doHandling();

然后,您可以创建内部类来处理不同的情况,无论是否作为匿名类。代码应该类似于:

public SomeEntity doSomething (String someAttribute, String anotherAttribute) 
    new SensitiveBlockHandler() 
        protected void doHandling() 
            /*
             * ... independent logic ...
             */
        
    .handle();

    return something;

【讨论】:

你如何看待有一个 Exception 类型的局部变量,它最初为空,然后有一个 catch 块设置变量并重新抛出?然后finally 块可以执行应该无条件执行的逻辑,仅在succeed 情况下,并且仅在fail 情况下,以任何需要的顺序。请注意,编译器最终会复制 finally 块的代码,但至少不必在源代码中复制它。【参考方案4】:

创建接口:

public interface EntityManagerAction 
   public void execute(EntityManager em);

还有一个实用类:

public class EntityUtil 
  public static void executeWithEntityManager(EntityManagerAction action) 
    EntityManager em = someHowCreateEntityManager();

    EntityTransaction tx = null;
    try 
        action.execute(em);
        tx = em.getTransaction();
     catch (RuntimeException e) 
        if (tx != null && tx.isActive())  
            tx.rollback();
        
        throw e;
     finally 
        em.close();
    
  

现在您可以在 EntityUtil 类中重复使用样板,您的代码变为:

public SomeEntity doSomething (String someAttribute, String anotherAttribute) 
   Something something; 
   EntityUtil.executeWithEntityManager(new EntityManagerAction() 
        public void execute(EntityManager em ) 

        /*
         * ... independent logic ...
         */
         //use the passed in 'em' here.
        
    );

    return something;

另见What is the "Execute Around" idiom?

【讨论】:

OK - 一个明显的问题...当任何异常发生时 tx 怎么会不为空???可能发生异常的最后一个地方是assignemnt em.getTransaction() 如果这个抛出,em 仍然为空,如果不是你的try-block 结束了o_O 是的 - 确实是这样 - 尽管这部分是从原始代码逐字记录的 - 所以 OP 必须将其构建成实际需要的结构。 值得注意的是,something 将无法从execute 中访问。 在 Java 8 中,您可以将 new EntityManagerAction() public void execute(EntityManager em) ... 替换为 (em) -> ... ,因为 EntityManagerAction 是一个函数式接口。或者,您可以将方法引用 this::doStuff 传递给它,其中 doStuff 看起来像 void doStuff(EntityManager em) ... 【参考方案5】:

您可以实现类似于 Spring Transaction Template 的机制。

首先,实现一个回调,它将为每个业务操作实现,提供实体管理器和事务:

public static interface TransactionCallback<R> 
  R doInTransaction(EntityManager em, EntityTransaction tx);

然后,使用样板代码创建一个泛型方法:

public <T> T execute(TransactionCallback<T> callback) 
  EntityManager em = this.createEntityManager();
  EntityTransaction tx = null;

  try 
    tx = em.getTransaction();
    return callback.doInTransaction(em, tx);
   catch (RuntimeException e) 
    if (tx != null && tx.isActive())  
        tx.rollback();
    
    throw e;
   finally 
    em.close();
  

最后,你可以创建类似这样的业务逻辑:

public SomeEntity doSomething(String someAttribute, String anotherAttribute) 
  return execute(new TransactionCallback<SomeEntity>() 
    @Override
    public SomeEntity  doInTransaction(EntityManager em, EntityTransaction tx) 
      // do something here
    
  );

这种方法有几个优点。它很干净,您可以在一个地方拦截所有操作 - 例如:添加日志记录等。

对于任何语法错误,我很抱歉,我是在没有 IDE 的情况下编写的。但你明白了。

编辑:在 JDK 8 中使用 lambda 可能会更短,因为 TransactionCallback 可能是函数式接口 :)。

【讨论】:

【参考方案6】:

我们最近遇到了这样的问题,并决定采用callback 设计模式。

所以,如果所有方法都有相似的代码,只是独立逻辑不同,您可以创建一个接口,其实现处理独立代码。所以是这样的:

public SomeEntity doSomething (String someAttribute, String anotherAttribute) 
       return (SomeEntity)callbackMethod(someAttribute, anotherAttribute, new IndependentCodeInterfaceImpl1());


public SomeOtherEntity doSomethingElse (String someAttribute, String anotherAttribute) 
    return (SomeOtherEntity)callbackMethod(someAttribute, anotherAttribute, new IndependentCodeInterfaceImpl2());


private Object callbackMethod(String someAttribute, String anotherAttribute, IndependentCodeInterface independent) 
    EntityManager em = this.createEntityManager();
    EntityTransaction tx = null;
    Object response = null;
    try 
        response = independent.execute(someAttribute, anotherAttribute, em);
        tx = em.getTransaction();
     catch (RuntimeException e) 
        if (tx != null && tx.isActive())  
            tx.rollback();
        
        throw e;
     finally 
        em.close();
    
    return response;

【讨论】:

【参考方案7】:

如果您使用的是 JavaEE 应用程序服务器,那么您的代码可以通过使用无状态会话 bean 大大简化:

@Stateless
public class SomethingFactory 

    @PersistenceContext
    private EntityManager em;

    public SomeEntity doSomething (String someAttribute, String anotherAttribute) 
       /*
        * ... independent logic ...
        */
       return something;
    


容器将为您处理所有事务管理语义。

【讨论】:

【参考方案8】:

为了完整起见 - 有 Template Method Pattern。它允许您从一个过程中抽象出所有的管理代码,并只实现核心功能。当您有许多元需求(例如在正确处理异常时关闭内容)时,此模式特别有用。

我将此模式用于运行数据库查询等,因为所有的规则都是确保关闭所有ResultSets 和Connections 同时仍能正确管理PreparedStatements。

abstract class DoSomethingWithEntity 

    public SomeEntity doSomething(String someAttribute, String anotherAttribute) 
        SomeEntity something;
        EntityManager em = createEntityManager();

        EntityTransaction tx = null;
        try 
            tx = em.getTransaction();
            // Call the abstract stuff that can be different.
            something = doIt(em, tx);
         catch (RuntimeException e) 
            if (tx != null && tx.isActive()) 
                tx.rollback();
            
            throw e;
         finally 
            em.close();
        

        return something;
    

    // The bit you want to write yourself. Make it abstract so you have to write it.
    abstract SomeEntity doIt(EntityManager em, EntityTransaction tx) throws Exception;


public void test() 
    SomeEntity e = new DoSomethingWithEntity() 

        @Override
        SomeEntity doIt(EntityManager em, EntityTransaction tx) 
            // Do your stuff here.
            return new SomeEntity();
        

    .doSomething("someAttribute", "anotherAttribute");

请注意,您不必将所有异常都写入RuntimeException - 您可以明确定义允许抛出哪些异常。

【讨论】:

【参考方案9】:

仅供参考 - JDK 1.8 中的新 lambda 表达式功能现在是解决此问题的最优雅的方法。你的 DoSomething 方法应该是这样的:

DataExecutor.execute(() -> 
    // let's say your "independent logic" is the following three statements
    statementA;
    statementB;
    statementC;
);

您的 DataExecutor.execute 方法看起来像这样(您可能需要一个返回类型):

public static void execute(work)

    EntityManager em = this.createEntityManager();

    EntityTransaction tx = null;
    try 

        // this is the "independent logic" you passed in via the "work" lambda
        work.doWork();

        tx = em.getTransaction();
     catch (RuntimeException e) 
        if (tx != null && tx.isActive())  
            tx.rollback();
        
        throw e;
     finally 
        em.close();
    

最后,您需要定义一个(或使用现有的)功能接口:

@FunctionalInterface
public interface DataFuncVoid 
    public abstract void doWork();

【讨论】:

以上是关于如何不在 catch 块中重复代码?的主要内容,如果未能解决你的问题,请参考以下文章

Try-catch-finally-return 澄清 [重复]

在iOS中捕获崩溃和异常[重复]

如何使用 sed 从块中删除重复行 [重复]

如果在 catch 块中抛出异常,是不是会执行 finally 块? [复制]

如何检查线程代码是不是在 try-catch 块中运行

在scala for-comprehension块中执行副作用代码[重复]