【问题标题】:Can Java exception safety be made less ugly?Java 异常安全可以变得不那么难看吗?
【发布时间】:2012-06-10 23:42:23
【问题描述】:

给定以下代码:我们需要锁定模型,然后启动一个事务(这可能会引发异常,因此我们必须确保释放锁),然后执行类似获取数据库连接的操作(这可能抛出异常),然后做一些可能抛出需要恢复事务的异常的事情。这是 Java 6,所以我们没有可用的 Java 7 好东西。

SomeClass someMethod() 
throws SomeException {
  acquireWriteLock();
  try {
    startTransaction();
    try {
      DBConnection d = openDBConnection();
      try {
        doStuff(d);
        commitTransaction();
      } finally {
        d.close();
      }
    } catch (SomeException e) {
      handleSomeException(e);
      revertTransaction();
      throw e;
    } catch (Throwable t) {
      revertTransaction();  // Error: method must return a value of SomeClass
    }
  } finally {
    releaseWriteLock();
  }
}

是否可以将其重写为更具可读性和更少冗长?

只是为了好玩:当您看到以下内容时,您会怎么做?

DBConnection d = null;
try {
  acquireWriteLock();
  startTransaction();
  d = openDBConnection();
  try {
    doStuff(d);
    commitTransaction();
  } catch (SomeException e) {
    handleSomeException(e);
    revertTransaction();
  }
} finally {
  d.close();
  releaseWriteLock();
}

【问题讨论】:

  • 除非您升级到 Java 7,否则您无能为力。
  • 在发生异常的情况下,您的第一个代码块是否不会在事务可以恢复之前关闭 dtb 连接?在这方面,我认为第二个代码块看起来更好,更安全。还是我错过了什么?
  • 说实话,比起升级到 Java 7,我宁愿在 Erlang 中重做整个事情,使用无共享通信顺序进程,再也不用担心线程安全。但这也不会发生。
  • 方法名称已更改,原因与专有信息有关;它根本不是真正的 DBConnection,所以很抱歉误导人们认为问题可以通过让数据库处理事务来轻松解决。
  • @MarkLutton 对。不过,我会成为好奇的人。如果您有一两分钟的空闲时间,您能否向我解释一下为什么第一个街区比第二个街区更受欢迎?是因为openDBConnection() 可能会抛出一些必须处理的有意义的东西吗?如果是这种情况,它可能只是在第二个块中向下推一行,它会是一样的。这让我难以置信,我只是想得到它:)。

标签: java raii exception-safety


【解决方案1】:

如果您切换到 Java 7,则可以大大简化它。否则,您可能应该对此做些什么。

可以尝试以下想法:

  • 将DB连接+事务管理+锁管理重构为通用基类中的通用方法,每个事务类型都有一个子类

  • 将DB连接+事务管理+锁管理重构为final类中的通用方法,事务类型有一个接口,每个都有一个实现类。

但是,除非这种特定模式重复很多很多次,否则这种重构可能是个坏主意。 (您将创建一个本地成语,并且读者必须在含义变得清晰之前学习该成语。)

【讨论】:

  • 当地成语有什么不好?一旦这种模式出现几次,我会说值得封装这种行为。 Java 中只有 PITA 没有一流的函数,因此有必要与 OOP 恶作剧作斗争。
【解决方案2】:

在不升级到 Java 7 的情况下,您可以组合其中的一些,因此您只需要一个 try-catch。

SomeClass someMethod() throws SomeException {
   boolean committed = false;
   DBConnection d = null;
   acquireWriteLock();
   try {
      startTransaction();
      d = openDBConnection();
      doStuff(d);
      commitTransaction();
      committed = true;
   } catch (SomeException e) {
      handleSomeException(e);
      throw e;
   } finally {
     if( d != null ) d.close();
     if( !committed ) revertTransaction();
     releaseWriteLock();
   }
}

诀窍是确保 revertTransaction() 和 DBConnection.close() 不抛出任何东西,但如果你不能这样做,那么你可以将 releaseWriteLock() 放在另一个 finally 子句中。如果你真的想进一步改进,你可以做一些 Stephen C 提到的重构。将获取/释放writeLock、启动/回滚事务、打开/关闭DBConnection等操作放在基类或一些抽象操作类中。

【讨论】:

  • 我不会这样重构。您的代码行数较少,但这些行需要仔细阅读才能验证正确性。
  • 嗯,我想这是一个品味问题,因为我认为通过将 3 个嵌套的 try-catch-finally 语句减少到一个 try-catch-finally 中,这看起来更容易。最后,两者都必须仔细验证。但是,这会将所有清理工作移到代码的底部并移出代码的主要流程,这是最初引入异常的目的,因为将错误处理代码与逻辑的控制流程分开。
  • Lambda 表达式会很好。 writeLockWrap(new Runnable(){public void run(){do.stuff();}}, new Runnable(){public void run(){/* 最后代码放在这里 */}});
  • 确实如此。但是在可以添加闭包语法(Java 8)之前,我们一直坚持使用匿名内部类 + 接口或抽象类。这在 lambdas 的表现力上并没有那么糟糕,但在代码大小方面有点烦人。一个简单的可能是 new DBoperation() { public Object execute(DBConnection db) { return doStuff( db ); } }.run();
猜你喜欢
  • 1970-01-01
  • 2017-04-10
  • 2010-09-16
  • 2011-02-22
  • 1970-01-01
  • 2011-02-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多