【问题标题】:groovy null safe operator, identifying what was null?groovy null 安全运算符,识别什么是 null?
【发布时间】:2014-02-21 03:34:09
【问题描述】:

Groovy 中的 null 安全运算符非常适合减少代码并使内容更具可读性。我们可以从这里开始:

def customer = getCustomer(custNo)
if(!customer)
   throw new Exception("Invalid customer: ${custNo}")

def policy = customer.getPolicy(policyNo)
if(!policy)
   throw new Exception("Invalid policy: ${policyNo}")

def claim = policy.getClaim(claimNo)
if(!claim)
   throw new Exception("Invalid claim: ${claimNo}")

..到这个...

def claim = getCustomer(custNo)?.getPolicy(policyNo)?.getClaim(claimNo)

但没有什么是免费的;使用空/安全导航,如果 claim 为空,则不是很明显是什么导致它:custNopolicyNoclaimNo 可能无效。

我们可以返回并开始检查什么是 null,但这会适得其反,实际上,这甚至是不可能的,因为中间对象不存储在变量中。

所以问题是:是否可以在使用 null/安全导航链接方法调用时识别什么是 null?

更新

我使用dynamic method invocation 对此进行了另一次尝试。它需要一个 init 目标(通常是一个 dao)来初始化对象(在这种情况下是客户),以及一个包含方法名称作为字符串(以参数作为值)的映射。使用迭代器,invokeChain 简单地遍历映射(链);如果链中的任何内容返回 null,则识别导致它的方法变得微不足道。

  def invokeChain = { initTarget, chain ->
     def obj
     chain.eachWithIndex{ it, idx ->
        //init obj from dao on first iteration only,
        //remaining iterations get from obj itself
        obj = (!idx) ? initTarget."$it.key"(it.value) : obj?."$it.key"(it.value)            

        if(!obj)
           throw new Exception("${it.key}(${it.value}) returned null")           
     }
     obj
  }

用法

initTarget 模拟customer dao...我已插入null 作为getClaim() 的返回类型,这应该会引发异常。

   def static getCustomer = { custNo ->
      [ getPolicy: { p ->           
            [getClaim:{ c ->
                  null //"Claim #${c}"
               }]
         }]
   }

..使用invokeChain,很简单:

  def claim = invokeChain(this, [getCustomer:123, getPolicy:456, getClaim:789])

...按预期抛出异常:

Exception in thread "main" java.lang.Exception: getClaim(789) returned null

我喜欢这种方法,因为它紧凑、可读且易于使用;你怎么看?

【问题讨论】:

  • 我提供了一个有效的拦截示例,并稍微扩展了一个答案。感谢您对调查的有趣刺激。
  • 是的,这个解决方案非常好。它不是在拦截?语法,并且需要对现有代码进行一些更改。我可以注意到,映射顺序可能会改变方法调用,但由于 Groovy 默认使用 LinkedHashMap - 这似乎很好。我也尝试玩MOP,只是为了好玩,如果我取得了好成绩,会在这里发布。抱歉迟到了评论,我有点忙。

标签: groovy


【解决方案1】:

我认为没有明确的方法可以做到这一点。 我可能是错的,稍后会检查源代码,但安全导航是 if 语句的语法糖。

作为 hack,您可以使用 interceptor 包装您的代码,跟踪内部的最后一个方法调用,然后使用该信息来提供错误消息。

它不会便宜,并且会花费您一些代码来实现拦截和运行时的一些性能。但是你可以实现类似

    mayFail("getCusomer", "getPolicy", "getClaim") {
         getCustomer(custNo)?.getPolicy(policyNo)?.getClaim(claimNo)
    } == "getPolicy" // failed on second step

编辑: 正如 @tim_yates 证明的那样,?. 是一个语法糖,后面带有 if 构造。感谢 Vorg van Geir 提供链接,我在这里有copied 来回答。他说,它已经过时了,看起来他是对的。我已经设法使 ProxyMetaClass 工作(在 Groovy 2.0.6 中),所以让路并没有完全破坏。现在我需要指定要拦截的确切类,我找不到捕获继承方法调用的方法。(简单地拦截java.lang.Object

def logInterceptor = new TracingInterceptor()
logInterceptor.writer = new StringWriter()

def intProxy = ProxyMetaClass.getInstance(Integer)
def stringProxy = ProxyMetaClass.getInstance(String)
intProxy.setInterceptor(logInterceptor)
stringProxy.setInterceptor(logInterceptor)
intProxy.use {
    stringProxy.use {
       println(("Hello" + "world").size().hashCode())
}   }

println(logInterceptor.writer.toString())

所有这些地狱都可能包含在一些实用程序代码中,但我高度怀疑这样做的必要性。性能开销会很糟糕,并且会保留一些样板代码。

这游戏得不偿失。

【讨论】:

  • 或者只使用旧样式if( customer != null ) { policy = customer.getPolicy( policyNo ).......
  • @tim_yates 不是老式的东西,OP 想避免吗?
  • 是的,但 ?. 运算符是语法糖,而不是魔法 ;-)
  • > "作为一个黑客,你可以用拦截器包装你的代码"... Codehaus 页面上的代码适用于 Groovy 1.5,并且大部分不再有效(许多功能被添加到 Groovy 但只保留测试了一个版本,然后悄悄地忘记了)。我重新发布了the corrected doco for Groovy 1.7 elsewhere,但即使那个可能已经过时了。
  • @VorgvanGeir 感谢您的建设性反馈。我扩展了一个答案,并在其中包含了您的链接。
【解决方案2】:

归因和断言呢?

def policy = customer?.policy
def claim = policy?.claim
def number = claim?.number

assert customer, "Invalid customer"
assert policy, 'Invalid policy'
assert claim, 'Invalid claim'

更新:

您已经找到了解决方案,但我想贡献一个拦截器的想法:

模拟:

def dao = [
  getCustomer : { custNo ->
    [ getPolicy: { p ->           
      [getClaim:{ c ->
          null //"Claim #${c}"
      }]
    }]
  }
]

拦截器:

class MethodCallInterceptor {
  def delegate
  def invokeMethod(String method, args) {
    def result = delegate.invokeMethod(method, args)
    if (result == null) {
      throw new RuntimeException("$method returned null")
    }
    else {
      new MethodCallInterceptor(delegate: result)
    }
  }

  def getProperty(String property ) { 
    delegate[ property ]
  }

  void setProperty(String property, value) {
    delegate[ property ] = value
  }
}

测试:

def interceptedDao = new MethodCallInterceptor(delegate: dao)

try {
  interceptedDao.getCustomer(123).getPolicy(456).getClaim(789)
  assert false
} catch (e) {
  assert e.message.contains( 'getClaim returned null' )
}

【讨论】:

  • 这个问题是assert对于链中的每个元素,必须手动添加;我已经添加了解决这个问题的解决方案,见上文。
  • 我以为您想解决一个问题,而不是通用解决方案。有趣的解决方案。我正在考虑一个装饰器,但我想结果或多或少是一样的
  • @raffian,我提出了一个拦截器的想法 :-)
  • 谢谢,Will,我们的想法是一样的,无论哪种方式都很好,而且一般来说,这对我很重要,应该早点提到。
猜你喜欢
  • 2021-06-03
  • 2012-03-28
  • 2017-01-18
  • 2012-11-16
  • 2012-08-23
  • 1970-01-01
  • 1970-01-01
  • 2012-03-13
  • 1970-01-01
相关资源
最近更新 更多