VI Methods

54. Introduction

Methods (sometimes also called functions) are blocks of code that can be run more than once and encapsulate a segment of logic. We define a method by writing the code that will be run when the method is called. Calling a method is the process of your code asking the method to start.

Groovy, like Java, is object-oriented and works around classes. C and Pascal are procedural and work around functions. Whilst the methods described here may look a bit like C-style programming that lets you build libraries of functions, what is really happening is Groovy wraps your code in a class definition behind the scenes. You’re only likely to create “raw” methods, like the ones below, as a means to break up your scripts. More usually you’ll create methods within your classes.

Methods have a number of features:

  1. Methods have names
    • this allows you to call your method in a meaningful way
  2. Methods can accept parameters
    • these are inputs into your method that can affect how your method operates
  3. Methods can return a result value
    • this can be captured by a variable from the code calling the method
  4. Methods have their own scope
    • they can have their own variables and not inadvertently affect the rest of your program

We’ve already looked at various methods for use with variables such as lists and maps so you’ve seen methods being called throughout the previous chapters.

55. The Basics

Let’s start by examining a new method we’ll write to calculate the average of the numbers in a list:

def determineAverage(list) {
    return list.sum() / list.size()
}

Breaking that code up we can see:

  1. The def reserved word is used to commence the method declaration
    • Much like we use when defining a variable
  2. determineAverage is the name of the method
  3. The method accepts a single parameter, list
  4. A single value is returned using the return reserved word
    • In this case it’s the result of list.sum() / list.size()

The method name (determineAverage) may look a bit odd but it uses a naming strategy called “lower Camel Case”. The camel aspect is the use of upper-case letters to indicate individual words in the name (hence Average). The first word in the method name is a verb (determine) to indicate that a method “does” something.

Let’s return to that determineAverage method and get a complete script together - you can copy and paste this into groovyConsole and run it to experience the method first-hand:

The determineAverage method
def determineAverage(list) {
    return list.sum() / list.size()
}

def scores = [2, 7, 4, 3]
def result = determineAverage(scores)
println result

Let’s look at the main components of that script:

  1. The determineAverage method is defined as before
    • This can appear above or below the other code
  2. A new list of numbers is created: def scores = [2, 7, 4, 3]
  3. The method is called with the scores variable passed as a parameter
  4. The return value (result) of determineAverage is stored in the result variable.

In the example I called the method using determineAverage(scores) but, in many cases, I don’t need to use the parentheses and determineAverage scores would have also worked. That’s why println 'hello, world' works just fine. This works really well when you start to use Groovy to construct domain-specific languages.

56. Parameters

Let’s look at the last example from the previous chapter:

The determineAverage method
def determineAverage(list) {
    return list.sum() / list.size()
}

def scores = [2, 7, 4, 3]
def result = determineAverage(scores)
println result

You might be wondering what happened to the scores variable once it was passed to determineAverage as a parameter. Basically, Groovy gave it another name (list) for use within the method. Inside the method, list is just another variable. This means that if determineAverage changes list in some way, this is reflected in the scores variable used in the main script:

A poor example
def scores = [2, 7, 4, 3]
def result = determineAverage(scores)
println result
println scores

def determineAverage(list) {
    list << 9
    return list.sum() / list.size()
}

The code above is very poorly behaved - it modifies list by adding a new item. Unless you provided documentation that explicitly states that you will change a parameter, most developers will assume that their parameters will be safely untouched by your method.

Declaring data types for parameters

Groovy lets you designate a data type for your parameters:

Parameter with data type
def determineAverage(List list) {
    return list.sum() / list.size()
}

As you start to develop classes and larger programs, methods create your Application Programming Interface (API). Such methods can be called by other people’s code and they could be using another JVM language (such as Java). It can make their life a little easier if you indicate the data types you’re expecting for your parameters. Alternatively, you can stay true to dynamic typing and let people know through your documentation.

Multiple parameters

Let’s look at another method - one that needs several parameters:

Multiple parameters
def callFriend(name, phone, message) {
    println "Dialling $name on $phone"
    println "Hi, $name - $message"
}

Either of these calls would work - it just depends if you want to use the parentheses:

callFriend('Barry', '0400 123 456', 'Did you see that local sporting team?')

callFriend 'Alex', '07 3344 0000', 'Could you please check on my pets whilst I\'\
m away?'

Each parameter may be typed if needed:

def callFriend(String name, String phone, String message) {..}

You can provide a mix of typed and untyped parameters but this is a little messy and I think it’s bad form so I can’t be bothered encouraging such an action by providing an example.

57. Default Values for Parameters

One or more parameters can be defined with a default value. This can be really useful if most calls to the method will use the defaults:

Parameters with defaults
def displayMessage (message,
                    title = 'Important message:',
                    border = true) {

    def borderText = ''

    if (border) {
        borderText = '--------------------------'
    }

    println """\
	    $borderText
	    $title
	    \t $message
	    $borderText
	    """
}

displayMessage 'Preparing to shut down. Please save your work'

The displayMethod can be called in a number of ways:

  • displayMessage 'Preparing to shut down. Please save your work'
  • displayMessage 'The system appears to have crashed', 'Error!'
  • displayMessage 'Be prepared for the happiness patrol', 'Public announcement:', false

When you get to method overloading and other object-oriented concepts you’ll see that default parameter values can aid in reducing the variations of a method that you might need to provide.

58. Named Arguments

You can use named arguments by having the first parameter be a Map. Consider the method below:

Named parameters
def displayMessage (options, message) {

    def borderText = ''
    if (! options.containsKey('border') || options.border) {
        borderText = '--------------------------'
    }

    def title = 'Important message:'
    if (options.title) {
        title = options.title
    }

    println """\
	    $borderText
	    $title
	    \t $message
	    $borderText
	    """
}

displayMessage(title: 'Canberra', border: true, 'The capital of Australia')

The options parameter is actually a Map and this lets the caller use an interesting Groovy syntax when calling the method. Instead of passing a Map ([:]) to the options parameter, the caller can use the key: value format in their method call. This lets us call displayMessage in many ways, including:

  • displayMessage(title: 'Canberra', border: true, 'The capital of Australia')
  • displayMessage title: 'Time', "It is now ${new Date()}"
  • displayMessage border: false, 'Hang in there little buddy!'

My recommendation is to use named parameters for non-essential parameters and to make sure that your method can operate correctly if a named parameter is not provided.

If others are to be using your method or you need to remember which named parameters are available, then you’ll make sure that you add some useful documentation to the method.

59. Variable Arguments (Varargs)

There are times where we want to pass a variable number of parameters to the method. However, the parameter list for a method is fixed.

One approach is to use a list for a catch-all parameter, such as items does in the code below:

Using a list parameter
def buyGroceries(store, items) {
    println "I'm off to $store to buy:"
    for (item in items) {
        println "  -$item"
    }
}

buyGroceries 'The Corner Store', ['apples', 'cat food', 'cream']

Whilst the list path is an option, Groovy supports the use of variable arguments (varargs) using the “three-dot” (...) notation for the last (and only the last) parameter:

Using a varargs parameter
def buyGroceries(... items) {
    for (item in items) {
        println item
    }
}

buyGroceries 'apples', 'cat food', 'cream'

We can set a specific data type for the items parameter by placing the type before the ...:

Using a typed varargs parameter
def buyGroceries(String... items) {
    for (item in items) {
        println item
    }
}

buyGroceries 'apples', 'cat food', 'cream'

Let’s return to the first example in this chapter and rewrite it using varargs:

Varargs instead of a list
def buyGroceries(store, ... items) {
    println "I'm off to $store to buy:"
    for (item in items) {
        println "  -$item"
    }
}

buyGroceries 'The Corner Store', 'apples', 'cat food', 'cream'

So the items parameter is actually a list inside buyGroceries but the caller just passes a series of values to the method.

60. Return value

When we started this tutorial I provided a very basic method for calculating averages. I’ve rewritten it slightly to use varargs and this is a good starting point into using the return statement:

println determineAverage(10, 20, 30, 40)

def determineAverage(... list) { 
    return list.sum() / list.size()
}

In the code above I return the average to the caller so, instead of printing out the result I could also assign it to a variable: def avg = determineAverage(10, 20, 30, 40).

Using the return reserved word isn’t required as Groovy will return the result of the last statement:

println determineAverage(10, 20, 30, 40)

def determineAverage(... list) { 
    list.sum() / list.size()
}

You can use return to explicitly exit a method. By itself, return actually returns null. In the useless method I provide below, I explicitly provide return:

def printer(message) {
    println message
    return
}

printer('hello, world')

That use of return in the last bit of code was redundant as the method would exit anyway (it had nothing left to do). However, this can be handy if the last expression to run in a method returns a value that we don’t want as the return value for our method.

Anything after a return is inaccessible, as illustrated by my even more useless method:

def printer(message) {
    println message
    return
    println 'Help, I don\'t exist'
}

That last line in the method will never, ever, ever be called. But if I really wanted it to be called I can use the try-finally approach:

def printer(message) {
    try{
        println message
        return
    } finally {
        println 'Help, I don\'t exist'
    }
}

Now, that last bit of text will be displayed as it’s in a finally block. This example is rather daft but it serves to illustrate how finally can be used to clean up something like an open file before the return is actioned.

Multiple Returns

You can have more than one return statement in a method but only one will ever be evaluated. This is really handy as it localises the return and prevents the method from further evaluation. You can also place a return at the very end of the method block to ensure that the method always returns a value. In the code below I use two return statements in the switch but also have return false at the bottom of the method just in case something slips through (most likely when I add in code at a later date):

def checkAnimalAsPet(animal) {
    switch(animal){
        case 'dog':
        case 'cat':
            return true
        default:
            return false   
    }
    return false
}

assert checkAnimalAsPet('cat') 
assert checkAnimalAsPet('dog')
assert checkAnimalAsPet('lion') == false
assert checkAnimalAsPet('pterodactyl') == false

You’ll note that, in the checkAnimalAsPet method I have a switch with no breaks. Essentially, the return is breaking out of the switch and the method all at once.

Declaring data types for return values

A data type can be declared for the return value by replacing def with the class name: e.g. Number determineAverage(... list){..}:

Number determineAverage(... list) { 
    return list.sum() / list.size()
}

println determineAverage(10, 20, 30, 40)

This is very handy if your method is to be accessed as part of an API, especially by Java programs.

You may notice some methods defined with a return type of void. This indicates that the method won’t return a value:

void displayText() {
	println 'Hello, World'
}

I can still use return within the method - I just can’t return a value.

Sequential method calls

In many examples I have used a method’s returned value to set a value of a variable, in an assert or as the input to a println. As the return value has its own type, we can actually call a method straight from the method call. This can be useful if one method call is just an intermediary step towards our goal and we don’t want to explicitly store its return value.

In the example below I call the tokenize method which returns a List of the words in the poem I then call the size method for that list to determine how many words are in the poem:

def poem = '''\
Once a jolly swagman camped by a billabong
Under the shade of a coolibah tree,
And he sang as he watched and waited till his billy boiled:
"Who'll come a-waltzing Matilda, with me?"
'''

poem.tokenize().size()

61. Throwing an exception

A method is able to throw an exception just as we saw in the Exceptions tutorial. In the code below I throw an exception if the caller to determineAverage() tries to pass a String through as a parameter:

Throwing an exception from a method
def determineAverage(...values) throws IllegalArgumentException {
    for (item in values) {
        if (item in String) {
            throw new IllegalArgumentException()
        }
    }
    return values.sum() / values.size()
}

//This works:
assert determineAverage(12, 18) == 15

//This does not work - we get an exception
determineAverage(5, 5, 8, 'kitten')

None of that code is new to you except for the throws IllegalArgumentException that forms part of the method’s signature1. The use of throws helps describe our method a little better by making callers aware of what to expect.

Multiple exceptions can be listed against throws, as seen in the example below:

Throwing exceptions from a method
def determineAverage(...values)
        throws IllegalArgumentException, NullPointerException {
    for (item in values) {
        if (item in String) {
            throw new IllegalArgumentException()
        }
    }
    return values.sum() / values.size()
}

Groovy doesn’t require that you explicitly provide a throws listing if your method throws an exception or passes up an exception that it doesn’t handle. However, if your method is to be used by others, I’d suggest that including throws is worth the effort.

You may note that, in that last example, I placed throws on a second line - this helps readability as it breaks up the display of the signature just slightly.

  1. This is the section of the method definition contain the return type, method name, parameters and thrown exceptions. As always, Wikipedia has some further information

62. Documenting a method

The groovydoc command that comes with the Groovy installation can be used to generate HTML documentation from comments within your code. GroovyDoc is based on JavaDoc and uses much the same syntax.

Let’s look at a Groovy method that features GroovyDoc comments:

A method with doc comments
/**
 * Returns the average of the parameters
 *
 * @param values  a series of numerical values
 * @throws IllegalArgumentException if a values parameter is a String
 * @returns The average of the values
 */
def determineAverage(...values)
        throws IllegalArgumentException {
    for (item in values) {
        if (item in String) {
            throw new IllegalArgumentException()
        }
    }
    return values.sum() / values.size()
}

Taking a look at the commenting:

  • The multi-line comment block starts with /** to indicate that this is a GroovyDoc
  • The first piece of text provides the summary of the method. It’s one line and meant to be terse.
  • A set of @param tags can be provided to describe each parameter.
    • The format is @param <parameter name> <summary>
    • You don’t provide the parameter type even if you declare one
  • Each exception that can be thrown by the method is listed against a @throws tag and provides a summary as to when the exception may be thrown.
    • The format is @throws <exception class> <summary>
  • The @returns tag describes what the method will return
    • The format is @returns <summary>

If you copy the sample code into a file named Average.groovy you can then run the following command in your command line/terminal:

groovydoc -d doc Average.groovy

This will produce a directory named doc that contains a set of documentation files. Inside the doc directory you’ll see index.html - open this in a browser to view your documentation.

As you click through the various links you’ll find the documentation for the determineAverage() method. It’ll contain the following information (but look a lot prettier):


java.lang.Object determineAverage(java.lang.Object… values)

Returns the average of the parameters

throws:
IllegalArgumentException if a parameter is a String
returns:
The average of the values
Parameters:
values - a series of numerical values

If you keep clicking links in the html files but can’t find it, look in the DefaultPackage directory for a file name Average.html - that’ll be what you’re after.

63. Techniques

I’d like to tell you that your programming career will be all about writing perfect code that never has problems but I’d just be lying. Here are some techniques to help make sure your methods are more robust and helpful to other programmers.

Valid parameters

We understand that the method determineAverage(...values) is expecting a list of numbers and have used a reasonably clear naming strategy (determineAverage) to display that the method is number-oriented but what happens when our colleague gives us something like:

determineAverage(5, 5, 8, 'kitten')

Clearly, kittens aren’t something that the average calculation can understand1. If you try to run that code you’ll get a nasty error that basically says your code has failed. Don’t be too hard on your colleague though - perhaps they’ve loaded data from a file that’s become corrupted by felines.

Comment your method

Firstly, make sure that determineAverage has some useful documentation such as:

Provide comments
/**
 * Returns the average of the parameters
 *
 * @param values a series of numerical values
 * @returns The average of the values
 */
def determineAverage(... values) {
    values.sum() / values.size()
}

In the example above I’ve just added a GroovyDoc comment block that describes what the method does, its parameter and what it will return. At the very least, other developers will see how they should be using my method.

Check the parameters

Next, I can be more defensive in my coding and make sure that the method has a prerequisite that needs to be met before it attempts to run.

Check parameters
/**
 * Returns the average of the parameters
 *
 * @param values  a series of numerical values
 * @throws IllegalArgumentException if a parameter is a String
 * @returns The average of the values
 */
def determineAverage(...values)
        throws IllegalArgumentException {
    for (item in values) {
        if (item in String) {
            throw new IllegalArgumentException()
        }
    }
    values.sum() / values.size()
}

//This works:
assert determineAverage(12, 18) == 15

//This does not work - we get an exception
determineAverage(5, 5, 8, 'kitten')

The approach above checks to make sure that no parameter is a String - if you pass one to the method you’ll get an exception thrown back at you. In reality I should make sure that only numbers can be passed in and my check won’t pick up a Boolean value - more on this in a moment.

What do you think would happen if I called the method with no parameters - determineAverage()?

(pause)

Well, the division would attempt to divide by zero and that’s a fail so I need to also check that values isn’t empty (I’ll leave out the comments for brevity):

Check all parameters
def determineAverage(...values)
        throws IllegalArgumentException {
    for (item in values) {
        if (item in String) {
            throw new IllegalArgumentException()
        }
    }

    if (!values) {
        return 0
    }

    values.sum() / values.size()
}

assert determineAverage() == 0

Note that if no parameters are passed, I return 0. I really don’t like returning null from methods as it makes other developers then have to check for null. I also don’t want to raise an exception - I’m happy enough to say that the average of no values is 0.

Get really typed

If I really want to get specific with the data types I’ll take as parameters and return from the method then I can switch to static typing. I can make sure that all my parameters are of type Number (or one of its subtypes) and that I will return a value of type Number. The code below really gets specific about data types:

Use typed parameters
/**
 * Returns the average of the parameters
 *
 * @param values  a series of numerical values
 * @throws IllegalArgumentException if a parameter is a String
 * @returns The average of the values
 */
Number determineAverage(Number...values) {
    if (!values) {
        return 0
    }

    values.sum() / values.length
}

The following two calls to the method would work:

assert determineAverage(2, 7, 4, 4) == 4.25
assert determineAverage() == 0

…but the following two calls won’t work:

determineAverage(2, 7, 4, 4, 'kitten')
determineAverage(2, 7, 4, 4, true)

If you are writing a method that needs to be very specific about data types for parameters and/or return values then this is the way to go.

Testing

I’d get into a lot of trouble from experienced developers if I just left this chapter without mentioning testing. So, here’s a little example using Spock!

Firstly, this won’t run in your groovyConsole. You need to copy the code into the online Spock web console2 and then click on “Run Script”:

A Spock example
import spock.lang.Specification

class MathDemo {
    /**
     * Returns the average of the parameters
     *
     * @param values a series of numerical values
     * @throws IllegalArgumentException if a parameter is a String
     * @returns The average of the values
     */
    static determineAverage(... values)
            throws IllegalArgumentException {
        for (item in values) {
            if (!(item instanceof Number)) {
                throw new IllegalArgumentException()
            }
        }

        if (!values) {
            return 0
        }

        return values.sum() / values.size()
    }
}

class AvgSpec extends Specification {

    @Unroll
    def "average of #values gives #result"(values, result) {
        expect:
        MathDemo.determineAverage(*values) == result

        where:
        values         || result
        [ 1, 2, 3 ]    || 2
        [ 2, 7, 4, 4 ] || 4.25
        [ ]            || 0
    }

    @Unroll
    def "determineAverage called with #values throws #exception"(values, excepti\
on) {
        setup:
        def e = getException(MathDemo.&determineAverage, *values)

        expect:
        exception == e?.class

        where:
        values          || exception
        [ 'kitten', 1 ] || java.lang.IllegalArgumentException
        [ 99, true ]    || java.lang.IllegalArgumentException
        [ 1, 2, 3 ]     || null
    }

    Exception getException(closure, ... args) {
        try {
            closure.call(args)
            return null
        } catch (any) {
            return any
        }
    }
}

When you run this in the Spock web console you should get:

AvgSpec
 - average of [1, 2, 3] gives 2
 - average of [2, 7, 4, 4] gives 4.25
 - average of [] gives 0
 - determineAverage called with [kitten, 1] throws class java.lang.IllegalArgume\
ntException
 - determineAverage called with [99, true] throws class java.lang.IllegalArgumen\
tException
 - determineAverage called with [1, 2, 3] throws null

So there’s a lot going on here that we haven’t covered in the tutorials so far but let’s try to break it down:

  1. I wrapped the determineAverage() method in a class named MathDemo and made it a static method
    • I won’t explain this here - just trust me that you can call MathDemo.determineAverage()
    • But do note that I’ve changed determineAverage() to better check that the parameters are numbers.
  2. I then created a spock test Specification subclass called AvgSpec
    1. The first test is def "average of #values gives #result"(values, result)
      1. This runs a series of tests using the data table in the where: block
      2. Yes, that’s right, Groovy will let you use a string as the name of the method - that’s v.cool but you can’t use interpolation (${}).
    2. The second test is def "determineAverage called with #values throws #exception"(values, exception)
      1. This checks to make sure that the IllegalArgumentException is thrown for incorrect parameters

As I say, there are a number of topics such as classes and closures that I haven’t covered - this example was just a quick one and should make sense as you learn about those additional concepts.

  1. Pun intended
  2. I’ve published the code to make it easy for you but can’t promise that this link will always work.