Groovy Scripts - Functions and Variables
In a previous post, we explored the simplicity of scripting mode offered by Groovy. We arrived at the following code.
String message = args.length > 0 ? "Hello ${args[0]}" : "Hello"
println message
Let's make the functionality slightly more sophisticated. Say, we want to send a custom message as the optional second argument. We arrive at the following code.
String message = args.length > 1 ? "Hello ${args[0]}, ${args[1]}" :
(args.length > 0 ? "Hello ${args[0]}" : "Hello")
println message
Now, this code is hard to read. Let's refactor the code and move the message construction responsibility into individual methods as follows.
String message = args.length > 1 ? greetWithCustomMessage() :
(args.length > 0 ? greet() : "Hello")
private String greet() {
"Hello ${args[0]}"
}
private String greetWithCustomMessage() {
"Hello ${args[0]}, ${args[1]}"
}
println message
Now it is better readable, yet works as before. Value of args
is available through binding.variables
to all the methods in the script. Hence I am not passing them as arguments to methods that construct the greeting messages.
Defining parameters to the methodsgreet
andgreetWithCustomMessage
to pass args would make it less coupled to the rest of the code. But, the only reason why the current code would break is ifgroovy.lang.Script
decides to changeargs
to something else, sayarguments
. Since it is less probable, I have decided to useargs
in those two methods directly.
Suppose we wish to replace "Hello" to "Hey" in our greetings, we would require code changes in three places. Gotcha! We have violated the DRY(Don't Repeat Yourself) principle!
Let's attempt to fix the violation of the DRY principle by extracting the String "Hello" into a variable as follows.
String defaultMessage = "Hello"
String message = args.length > 1 ? greetWithCustomMessage() :
(args.length > 0 ? greet() : defaultMessage)
private String greet() {
"$defaultMessage ${args[0]}"
}
private String greetWithCustomMessage() {
"$defaultMessage ${args[0]}, ${args[1]}"
}
println message
At this point, if you invoke the script with arguments, you will end up with the following error!
➜ demo groovy GreeterScript.groovy Raj "Have a great day"
Caught: groovy.lang.MissingPropertyException: No such property: defaultMessage for class: com.nareshak.demo.GreeterScript
groovy.lang.MissingPropertyException: No such property: defaultMessage for class: com.nareshak.demo.GreeterScript
at com.nareshak.demo.GreeterScript.greetWithCustomMessage(GreeterScript.groovy:12)
at com.nareshak.demo.GreeterScript.run(GreeterScript.groovy:4)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
We have already learned that the statements from our scripts are bundled into the run
method in the generated class that extends groovy.lang.Script
. While the methods we introduced in our script can be added to the generated class along with run
, there is a challenge with the variables. They can be added as local variables inside the run
method or as fields in the generated class. Groovy decides to add them as local variables. Hence, defaultMessage
is not available in the scope of greetWithCustomMessage
and greet
.
Does it mean that you cannot create variables at the script scope? You can create variables at the script scope by instructing Groovy through the AST transformation groovy.transform.Field
as follows.
import groovy.transform.Field
@Field
String defaultMessage = "Hello"
To summarise, we have seen how we can introduce additional functions and variables in our Groovy scripts to contain the complexity.