Using Optional Method Arguments

Using optional, named method arguments on your helper functions can make your code easier to read and more self-documenting. For example, consider a global helper function queryRows() that simplifies common querying use cases.

Sometimes your calling code only requires a select list and a from clause:

def rates = adf.util.queryRows(select: 'FromCurrency,ToCurrency,ExchangeRate',
                                 from: 'DailyRates_c')
On other occasions, you may need a where clause to filter the data and an orderBy parameter to sort it:
def euroRates = adf.util.queryRows(select: 'FromCurrency,ToCurrency,ExchangeRate',
                                     from: 'DailyRates_c',
                                    where: "FromCurrency = 'EUR'",
                                  orderBy: 'ExchangeRate desc')
By using optional, named arguments, your calling code specifies only the information required and clarifies the meaning of each argument. To adopt this approach, use a single parameter of type Map when defining your function:
// Global Function 
List queryRows(Map options)
Of course, one way to call the queryRows() function is to explicitly pass a Map as its single argument like this:
// Passing a literal Map as the first argument of queryRows()
def args = [select: 'FromCurrency,ToCurrency,ExchangeRate',
              from: 'DailyRates_c']
def rates = adf.util.queryRows(args) 

You can also pass a literal Map inline without assigning it to a local variable like this:

// Passing a literal Map inline as the first argument of queryRows()
def rates = adf.util.queryRows([select: 'FromCurrency,ToCurrency,ExchangeRate',
                                  from: 'DailyRates_c']) 
However, when passing a literal Map directly inside the function call argument list you can omit the square brackets. This makes the code easier to read:
// Passing a literal Map inline as the first argument of queryRows()
// In this case, Groovy allows removing the square brackets
def rates = adf.util.queryRows(select: 'FromCurrency,ToCurrency,ExchangeRate',
                                 from: 'DailyRates_c') 
The Map argument representing your function's optional parameters must be first. If your function defines additional parameters, then when calling the function, pass the values of the other parameters first followed by any optional, named parameters you want to include. For example, consider the signature of following findMatchingOccurrences() object function that returns the number of strings in a list that match a search string. The function supports three optional boolean parameters caseSensitive, expandTokens, useRegExp.
Long findMatchingOccurrences(Map options, List stringsToSearch, String searchFor) 
Calling code passes optional, named arguments after values for stringsToSearch and searchFor as shown below:
// Use an object function to count how many emails
// are from .org or .edu sites
def nonCommercial = findMatchingOccurrences(emails,'.*.org|.*.edu',
                                            caseSensitive: true,
                                                useRegExp: true)

Regardless of the approach the caller used to pass in the key/value pairs, your function body works with optional, named arguments as entries in the leading Map parameter. Be aware that if no optional argument is included, then the leading Map parameter evaluates to null. So assume the options parameter might be null and handle that case appropriately.

Your code should validate incoming optional arguments and, where appropriate, provide default values for options the caller did not explicitly pass in. The example below shows the opening lines of code for the queryRows() global function. Notice it uses the safe-navigation operator (?.) when referencing the select property of the options parameter just in case it might be null and signals an error using another global function named error().
// Global Function: List queryRows( Map options )
// ---------------
// The options Map might be null if caller passes no named parameters
// so check uses the safe-navigation operator to gracefully handle the
// options == null case, too. We're assuming another global helper function
// named 'error()' exists to help throw exception messages. 
if (!options?.select) {
  adf.util.error("Must specify list of field names in 'select' parameter")
}
if (!options?.from) {
  adf.util.error("Must specify object name in 'from' parameter")
}
// From here, we know that some options were supplied, so we do not
// need to continue using the "?." operator when using options.someName
def vo = newView(options.from)
// etc.