Give us a call +386 1 542 06 00

Razum

Get a proposal

Blog

Groovy has some very nice constructs to make code shorter and safer. Take with for example: it saves us from declaring temporary variables that will be used for one call only thus making the code more understandable and prevents us from mistakingly...

Use with with care by Gregor Petrin

Groovy has some very nice constructs to make code shorter and safer. Take with for example: it saves us from declaring temporary variables that will be used for one call only thus making the code more understandable and prevents us from mistakingly using that temporary variable somewhere in code long after it had outlived its purpose.

But what with also does is unconditionally hijack the delegate of the method in which we want to use our not-really-a-variable - even if we provide a variable name to use for it.

A funny bug we tried to understand today (example is abbreviated):

class ImportExternalDataService {
          private static final String EXTERNAL_SYSTEM_ID = "kittens"
      
          List<PendingSyncRecord> getLocations(String xmlText) {
              new XmlSlurper().parseText(xmlText).with { rootNode ->
                  rootNode.location.collect { locationNode ->
                      new PendingSyncRecord(
                          externalSystem: EXTERNAL_SYSTEM_ID,
                          externalId: locationNode.@id
                      )
                  }
              }
          }
      }
      
      class LocationParserSpec extends Specification {
          static String xmlSample = """<?xml version="1.0"?><locations><location id="1"><title>XML!</title></locations>"""
      
          def "getLocations tags the external system as kittens"(){
              expect:
              service.getLocations(xmlSample).every { it.externalSystem == "kittens" }
          }
      }
      

The test was failing, insisting the externalSystem property is an empty string. What the…?!

What was happening was that in the with closure we were really calling rootNode.EXTERNAL_SYSTEM_ID! This was delegated to XmlSlurper.methodMissing and because there was no XML node with that name the result was a missing node.. whose toString() method produces an empty string.

The fix was to call ImportExternalDataService.EXTERNAL_SYSTEM_ID - to which IntelliJ IDEA objected with an UnnecessaryQualifiedReference warning and offered itself to break my code again..

There was a lot of headscratching while trying to understand what was going on, however! As an example, the following code would pass every time..

"abc".with { assert "hello, $EXTERNAL_SYSTEM_ID" == "hello, kittens" }
      

..while this code would only pass when EXTERNAL_SYSTEM_ID was declared final:

[].with { assert "hello, $EXTERNAL_SYSTEM_ID" == "hello, kittens" }
      

.. which is probably connected to this bug report. Using a non empty array would make the code pass again.

[what-why.gif]

If anyone knows what makes the empty array so special please fill us in, we’re dying to know :)

< Back to article list