Last year I blogged about Jumping into Go. I did it because I wanted a CLI tool that would let me invoke Maven or Gradle builds using their respective command wrapper or the actual command. This tool is known as Gum. Decided to write it in Go because a) the tool is CLI and would need platform specific hooks, b) Go produces small binaries for all target platforms, and c) to force to me actually learn and use the language. As explained at the first post my experience with Go was quite similar to what I went through when I embarked into Groovy many years ago. Now, a year later I've got a bit of experience and a few insights as a Java developer getting into Go.
The first shock was the IDE of choice. In the Java world there's no shortage of options to perform a particular feat, when it comes to IDEs the discussion of using one IDE over the other can become quite heated. Personally I moved away from Eclipse 11 years ago to IntelliJ and I'm not looking back. I'm quite aware that JetBrains offers both a Go plugin for IntelliJ and a Go specific IDE (Goland) and that would had been perhaps the natural choice as a Java developer, but no, I chose Vs.Code.
Why? Because the large majority of the Go community has rallied behind Vs.Code. If I were to encounter a problem when using the language and its build toolchain it's very likely that the answer would be somehow tied to Vs.Code. At first I found the IDE to be lacking in some areas (we Java devs take pride in our tools) and of course wondered many times why a particular key shortcut wouldn't work (muscle memory obviously). However in the past 12 months I've seen Vs.Code grow by leaps and bounds in terms of Go support. I'm quite fond of it now. I still prefer IntelliJ for writing Java code but for Go? Vs.Code, no doubt about it.
Oh, how much has been written about the Go syntax. The good news is that it does not matter if you like it or not, there's only one syntax. Accept it and move on. Tools will automatically format your code to conform to the syntax. Automated refactoring and code upgrades via
gofix, yeah, I can get behind that.
As Java developers we're quite happy with all the JARs at our disposal. Eugene Ciurana once said "if you can think of it, there's bound to be a JAR for it". We're also quite fond of refactoring code to remove duplication and increase code reuse. At first I was shocked that I could not find a library similar to Apache Commons for String and File utilities, or Log4j/SLF4j for logging. As it turns out the standard package is quite good at providing the basics, but if you pop the hood you'll find so much more. If you're writing a small tool (like I do) or library and you happen to have lots of dependencies then it's likely that you're doing it wrong. The picture is different if you're writing a framework of course.
This is perhaps a hard pill to swallow as a Java developer as you don't have to hunt for a JAR at Maven Central, but rather browse the standard Go packages and you'll likely find what you need. If not, then building what you need usually requires a few lines of code. Why then are those few lines not packaged as a reusable unit? It could be because the language lacks generics (something that soon will no longer be a problem).
Invoking the build is so quick that I had to double check a couple of times If I had actually typed enter to run it. Executing tests cases, linting, checking for code complexity, code coverage, the works. The build tool chain is just fast. By the time Maven (or Gradle) have decided what needs to be done the Go build has finished by comparison.
Speaking of the build, there's just one tool that you ever need: the standard build tool. It can be "enhanced" by adding external binaries such as golint (for linting the code), and gocyclo (for checking code complexity), lll, gocover, and more. There's no need to shop for a build tool; there are no holy wars between build tools. However there are some limitations such as packaging and publishing binaries to different distribution channels. This where I found another life changing tool: GoReleaser.
This is it, GoReleaser is the tool that showed me how to get things done. The Go build is capable of cross compiling binaries for a wide variety of platforms. You'd think it would be enough to configure a script that setups all possible combinations, then upload all generated files to a particular public destination where users can pick the files they need. Automation is the name of the game and GoReleaser delivers.
It not only performs the previously mentioned tasks but can also calculate checksums, sign artifacts with PGP signatures, create a Git release (GitHub/GitLab/Gitea), package the binaries with popular package managers such as Homebrew, Snapcraft, Scoop, and most recently announce a release on Twitter. And to get this done you only need 1 configuration file, it felt like using a Maven pom or a Gradle build file.
Bonus: GoReleaser is written in Go as well, so if you have a question on how command line flags should be parsed, or handling files or OS resources, network operations (REST APIs anyone?) then its codebase is a good place to start.
Also, after enjoying GoReleaser I experienced feature envy and longed for a similar experience for Java projects. Given that no tool like that existed I decided to create my own: JReleaser. It works for Java and non Java projects. Give it a try today.
I find writing Go code refreshing, just like switching between Groovy and Java. I don't find the often mentioned "limitations" of the language/syntax as such, rather if I catch myself writing a very long method it makes me think twice, perhaps I should look at the problem with a different perspective. I'm happy with JVM, both Java and Groovy deliver all the features I require to build what I want when targeting the JVM as a platform, however I'm also enjoying my time writing Go code. Looking forward to what I'll lean next.
Keep on coding!
Image by Lubos Houska from Pixabay
What is your opinion about mixing error handling with the normal (happy) program flow? I was so happy with getting rid of that (in my C code, from back when).
Go’s approach to handling errors is syntactically different from Java’s yet similar: either you care about the error and must handle it (like checked exceptions) or ignore it by assigning the error to _ (like we ignore runtime exceptions) [warning: gross over-simplification].
Thus, looking at errors with such perspective you get to decide if an error must be handled immediately, ignored, or bubbled up (by returning it). Sadly (?) there’s no automatic error bubbling, you must do so explicitly.