Go for Java Programmers: Formatting Rules and Surprises

Go for Java Programmers: Formatting Rules and Surprises

Part of the Go for Java Programmers series

Go (Golang) is more opinionated than Java about best practices in writing code.  This avoids a lot of problems in general, and makes Go code much more uniform and easy to understand by other developers.  However, it’s a significant adjustment for many Java programmers.  In this article, we will look at the formatting of Go source code, and other surprising rules that are enforced by the compiler.

Go Has Standard Formatting

Java and other C-derived languages are fairly flexible when it comes to formatting your source code.  As long as you enclose blocks of code in curly-braces, and terminate statements with semicolons, you can generally do whatever you like.

You can put your curly braces on their own separate lines, in Linus Torvalds-style:

for(LineItem lineItem : order.getLineItems())
{
   totalShippingWeight += lineItem.getShippingWeight();
}

… or you can use some variation of classic K&R style

if(order.getAmount() > 50)) {
   shippingCharge = 0;
} else {
   shippingCharge = 5;
}

You can indent with tabs, or with space characters.  You can write your entire application on one line, if you like.  Go crazy!

The problem with having many options is that they lead to many arguments.  Some developers get really worked up over evangelizing their preferred curly-brace style, for example.  When multiple developers on a team code with completely different styles, it makes the code more messy.  It also interferes with your ability to distinguish substantive changes from mere formatting changes when comparing revisions over time in source control.

Most large organizations deal with this by writing a style guide (see Google’s for an example), and using plugins such as Checkstyle to force all developers to use that style.

The creators of Go took this same step at the outset.  The Go compiler ships with a standard utility called gofmt, which basically serves the same function as Java’s Checkstyle.  It re-formats your source code to fit the standard Go conventions.  The gofmt utility can be called directly, or through the main Go executable (i.e. “go fmt“).  It’s also bundled with every IDE or plugin that I know of for Go, and typically re-formats your code on the fly as you type.

Standard formatting rules to note

  • Go uses the “One True Brace” variant of K&R style (“OTBS”). In classic K&R, the opening brace for a function goes on its own separate line.  For control structures (e.g. if, for, etc.), it goes on the same line as the control keyword.  With OTBS, however, an opening brace is always on the same line as any statement preceding it.  Fortunately, this is the style used by the Java standard library source code, and is probably the most common style in the Java community anyway, so you are no doubt already accustomed to seeing it.
func doSomething() {
   if loggingEnabled == true {
      log.Println("Starting doSomething()")
   }
   ...
}
  • It was mentioned earlier in this series in the article on Go’s control structures, but it’s worth re-iterating here.  Java allows you to omit the curly braces when the body of a control structure contains only one line (e.g. if(x > 5) return false;).  Go always requires braces around a block, even it’s just one line.  This is partly due to way in which the Go compiler implicitly adds semicolons to your code, and partly from a belief that this rule simply makes code easier to read.

Not actually rules, but conventions applied by gofmt

  • Use tabs rather than multiple spaces to indent code.
  • When you import multiple packages, gofmt will re-arrange the imports in alphabetical order.
  • We’ll talk about custom types in the next article, but note for now that when you create a type, gofmt will line up the names and types of its fields in clean columns:
type Order struct {
   orderId      uint64
   customerId   uint64
   date         Time
   lineItems    []LineItem
}

Dead Code is Not Allowed

If you use any of the major Java IDE’s, such as IntelliJ, Eclipse, or NetBeans, you are probably accustomed to seeing warnings about unused code.  If you add an import statement but then don’t use the imported class, or declare a variable or method that is never called, then the IDE will politely make note of it even though the code continues to compile.

One of the biggest shocks for Go newcomers is that unused code is not allowed.  Period.  It’s not a warning at the IDE level, it’s an error at the language level.  Your code will NOT compile if it declares something and then doesn’t use it.

Adjusting your work process

At first glance, this seems like a nice idea that encourages good clean code.  However, it does fundamentally change the way that you work.  Sure, good Java programmers clean up those “usused code” warnings before committing to source control anyway.  But we typically do so at the end of a work session, just before committing.  What if those warnings weren’t allowed even during your work session?

Declare things near their use

If you continue using a typical Java work process, it will drive you crazy.  It is common to declare a lot of variables up-front when you start writing a class or method, even though you haven’t written the code to use them yet.  When writing Go, you should be much more mindful about declaring variables as close as possible to their point of first use (which is generally a good coding practice anyway).

Develop in small chunks

When debugging or testing during development, you usually comment-out one or more lines of code temporarily.  In Go, if you comment-out the only line of code that uses a variable or function, then you must also comment-out it’s declaration if you want to compile.

What’s more, commenting-out code can trigger a chain reaction.  Let’s say you comment out the only line of code that uses a JSON parser.  If you want to compile, you now have to comment-out the declaration of that JSON parser.  But you initialized the parser with a file handle, that was the only of that file handle.  So now you have to comment-out the file handle, too.  Opening that file handle was the only use of the “os” package that you imported.  So now you have to comment-out that package import.  So on, and so on.

This restriction pushes you toward a style of work that is loosely similar to test-driven development (TDD).  You’ll want to develop in small chunks, being comfortable with each small chunk of code before moving on the next.  It’s a excellent coding practice, which you can carry back to your Java work as well.  However, it can be a frustrating adjustment at first.

The blank identifier as a loophole

The exception to the “no-dead-code” rule is the blank identifier.  When you assign a value to the underscore character (“_“), Go silently discards that value and does not complain about it being unused.  This is a common technique when you call a function that returns multiple results, and you don’t actually need all of the results.

if _, err := os.Stat(path); os.IsNotExist(err) {
   fmt.Printf("%s does not exist\n", path)
}

In the above canonical example from Effective Go, we’re checking to see whether a file exists.  The os.State() function returns a FileInfo type for a given file path, and an error object that will be populated if no file could be found.  In this case, we don’t actually care about the FileInfo return value.  We’re only interested in whether or not the error return type gets populated.  So we assign the first of the two return types to the underscore blank identifier, to prevent compile errors due to never using it.

Using the blank identifier in package imports

It is sometimes helpful to use the blank identifier with packages that your code imports.  This tells Go that you are importing a package only for the side-effects of initializing that package, and not because you intend to actually use it directly in your own code.

For a Java developer, this concept should be familiar from using JDBC.

import java.sql.DriverManager;
import java.sql.Connection;

public class DatabaseExample {

   public static void main(String args[]) {
      Class.forName("org.postgresql.Driver");
      Connection connection = DriverManager.getConnection(
         "jdbc:postgresql://localhost/test",
         "postgres",
         "postgres"
      );
      ...
   }

}

With JDBC, you import the relevant standard library classes as you would anything else (lines 1 and 2).  However, you load the actual JDBC driver implementation through the special Class.forName() construct (line 7).  Whichever implementation class you’re using, you don’t actually reference it anywhere else directly in your code.

We’ll talk about database access more thoroughly in a later article, but the basics of Go’s database/sql package are very similar to JDBC.  You import the standard library’s database/sql package for direct use in your code, and you import a particular driver package only for the side-effect of loading that driver under-the-covers:

package main

import (
   "database/sql"
   _ "github.com/lib/pq"
)

func main() {
   db, err := sql.Open("postgres", "user=postgres password=postgres host=localhost dbname=test sslmode=disable")
   ...
}

Here, the database/sql package is actually used directly in the application’s code.  The github.com/lib/pq package is never referenced directly, it is simply loaded for the side-effect of making a PostgreSQL driver available to database/sql.  Adding the blank identifier to the import on line 5 prevents Go from complaining about the fact that it’s never directly used.

Conclusion

Go is a bit more restrictive in enforcing “best practices” compared to Java.  In many cases, this is not such a major change.  If you use Java in a large team environment, then you are probably already familiar with tools like Checkstyle enforcing a style guide adopted by an architect at your company.  The only difference with Go is that the architects of the language itself wrote the style guide up-front.

However, some unique characteristics of Go, such as the restriction on unused code, are major departures.  Adjusting to those differences will require a new level of discipline… keeping declarations close to where they are used, and working in small tight chunks of logic.  This discipline can help you with your Java coding skills as well.

In the next article we will look at data types in Go, starting with Go’s built-in primitive types and how they compare with their Java counterparts.