Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

groovy null safe operator, identifying what was null?

Tags:

groovy

The null safe operator in Groovy is great for reducing code and making things more readable. We can go from this:

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}")

..to this...

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

But nothing's for free; using null/safe navigation, if claim is null, it's not immediately obvious what caused it: either custNo, policyNo, or claimNo may be invalid.

We could go back and start checking what's null, but that's counterproductive, and actually, it's not even possible since intermediate objects are not stored in variables.

So the question is: Is it possible to identify what was null when chaining method calls using null/safe navigation?

UPDATE

I took another stab at this using dynamic method invocation. It takes an init target (usually a dao) to initialize the object (customer in this case), and a map containing method names as strings (with arguments as values). Using an iterator, invokeChain simply traverses the map (chain); if anything in the chain returns null, identifying the method that caused it becomes trivial.

  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
  }

Usage

Mock a customer dao for initTarget...I've inserted null as return type for getClaim(), which should throw an exception.

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

..using invokeChain, easy as pie:

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

...throws exception, as expected:

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

I like this approach because it's compact, readable, and easy to use; what do you think?

like image 835
raffian Avatar asked Mar 21 '23 12:03

raffian


1 Answers

I think there is no clear way to do so. I can be wrong, will check sources later, but safe navigation is a syntax sugar for if statement.

As a hack you can wrap your code with interceptor, trace last method call inside, and then use that info to proide error message.

It will not be cheap, and will cost you some code to realize interception and some performance while running. But you can achieve something like

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

EDIT: As @tim_yates proved, ?. is a syntax sugar with if construction behind. Thanks Vorg van Geir for the link, I have copied it here, to an answer. He say, it's outdated, and it looks like he is right. I have managed to make ProxyMetaClass work(in Groovy 2.0.6), so given way isn't totally broken. Now I need to specify exact classes to intercept, and I can not find a way to catch inherited method calls.(to simply intercept 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())

All that hell may be wrapped in some utility code, but I highly doubt in necessarity of this. Performance overhead will be awful and some boilerplate code will remain.

The game is not worth the candle.

like image 108
Seagull Avatar answered Apr 07 '23 06:04

Seagull