I found my Java knowledge to be a crutch when writing Groovy scripts. Luckily, I was able to use this crutch less and less as I gained more exposure. I was asked about this today so I decided to write up a blog on my journey. If you know how to run a Maven project and want to follow along, I’ve included instructions at the end.
Why I started using Groovy
Jenkins and Hudson are both continuous integration servers that provide a Groovy console for writing queries and updates against the object model. This is really cool as you can do something with/to hundreds of jobs with marginal effort. I had read “Making Java Groovy“, but needed this real scenario to jump into it.
Phase 1: The “problem” with Groovy (and Java 8)
A valid Java program *is* a valid Groovy program. This is nice and convenient because you can call Java methods. It’s also a crutch because you can write Java methods rather than actually using Groovy. My first Groovy program in Jenkins was something like this:
def jobs = Jenkins.instance.items; for (job in jobs) { if (job.isDisabled() ) { println(job.getName()) } }
At least it uses two pieces of Groovy. “def” to define a variable of unknown type. And “in” within the for loop. But this is an imperative program. I loop through the jobs checking a condition and printing as I go.
Incidentally, I didn’t have this problem in Clojure or Lisp when taking classes. There was nothing to fall back TO.
Phase 2: Translating after the fact
My next phase along the way to functional programming was translating my “imperative” program into functional programming. Now this step may seem like a waste of time given that I had a working program. I did it anyway so I would expose myself to the idioms of how I *wanted* to be coding this. At the time, I was hoping it would take. And it did after a while.
But for phase 2, I was doing it very incrementally. First I made my imperative code one line. Still just as imperative. It still has a loop and if statement.
Jenkins.instance.items.each({ j -> if (j.isDisabled()) println(j.getName()) })
Then I started thinking about operations. It’s still not great because it still has “each”, but at least I’ve gotten the “if” statement out of there. I’m using “grep” and thinking about filtering first rather than looping.
Jenkins.instance.items.grep({ j -> j.isDisabled() }).each( { j-> println(j.getName()) })
My next step felt like a nice mental jump. I was able to think about the entire task in functional programming. I filter the list and then I translate to the field I want. And I’ve finally succeeded in getting the print statement out of my logic and just happening outside of it!
print(Jenkins.instance.items.grep({ j -> j.isDisabled() }).collect( { j-> j.getName() }))
Phase 3: Getting rid of some more Java syntax
Next I focused on getting rid of stray Java syntax. A lot of things required in Java are redundant in Groovy. I had omitted semi-colons from day 1. Getting rid of the rest took more time.
First I trained myself to get rid of the unneeded parens. I already have {}. No need to have parens there too. The parens make it a method call. But in Groovy, they are implied.
print(Jenkins.instance.items.grep{ j -> j.isDisabled() }.collect { j-> j.getName() })
Then I trained myself to use the shorter form of method names. Groovy automatically adds is/get to your method names so you can write them as if they are properties.
print(Jenkins.instance.items.grep{ j -> j.disabled }.collect { j-> j.name })
Finally I trained myself to indent consistently. I don’t know if this is the best way, but it helps me remember my chain of actions:
print(Jenkins.instance.items .grep{ j -> j.disabled } .collect { j-> j.name })
Phase 4: Thinking functionally
For a while, I’d be able to write easy code like this functionally, but have to fall back to imperative programming for debugging. Then I got used to adding print statements within steps to be able to continue on from wherever I was. This let me think of the whole exercise as a series of steps and start coding them functionally. When I got stuck, I’d just proceed within an each statement to figure out what to do. Then I’d translate the each statement into the correct method.
They key is that I didn’t start thinking in loops anymore. i started with what I needed to do. “I want to find all the disabled jobs and get their names.” It’s a long way from “I want to loop through all the jobs and print the names of the disabled ones.”
For those wanting to run this with Maven
Jenkins is open source and has a robust plugin model. There’s lots of documentation for writing a plugin, but you don’t need to know any of it to run the console.
1) Create a Maven project/file with this pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>jb</groupId> <artifactId>play-jenkins-plugin</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>hpi</packaging> <parent> <groupId>org.jenkins-ci.plugins</groupId> <artifactId>plugin</artifactId> <version>1.573</version> </parent> <repositories> <repository> <id>jenkins-releases</id> <url>http://repo.jenkins-ci.org/releases/</url> </repository> </repositories> </project>
2) mvn install (wait a long time for Maven to “download the internet”)
3) mvn hpi:run
4) http://localhost:8080/jenkins
5) Click Manage Jenkins
6) Click Script Console
7) Play!