Gofmt knows best
Everyone’s favourite
Gofmt’s style is no one’s favorite, yet gofmt is everyone’s favorite. — Rob Pike
Code formatting tools are nothing new but Go notches them up a level. Your Go installation comes with an automated formatter, gofmt, and this tool has been used to layout the entire code base. As a result, Go code looks uniform and familiar. The recommendation is to gofmt your own code, ideally on save and certainly before submitting for review.
(add-hook 'before-save-hook 'gofmt-before-save)
Formatting tools for other languages require configuration. You must instruct the tool about your preferences for spaces and braces. Gofmt is inflexible and this is a strength.
The language designers did Go a favour by supplying and promoting gofmt early on, before alternative style guides had been developed and adopted. There can be no more arguments about tabs vs spaces, for example. Code reviews must focus on content rather than layout.
There are also more subtle benefits. Certain kinds of manual layout are fragile: the insertion of space to vertically align assignment statements, for example. A code formatter gets it right every time.
Go knows best
As writers — and programmers are, fundamentally, writers — maybe we don’t want our work to look uniform or familiar. Maybe we prefer more control over what we produce. Maybe sometimes, we’d like to surprise or provoke.
Does the regularity gofmt imposes render our code bland, even boring?
I’ve heard this point extended to be a criticism of Go in general: that the language restricts what you can do because the language designers know best, and that it’s for your own good.
Well, if I’ve ever submitted extravagently formatted code for review, it’s been slapped back. Substantial programs are developed by teams who adopt idiom and share style rules for good reason. It’s how we work together. Imagine how much easier it would be if the third party code in your code base was also written to your preferred style. Go gives you that, so long as you prefer its native style.
Gofmt does not completely over-ride a programmer’s choices. Two programs which are semantically identical but laid out differently will probably still be laid out differently after a pass through gofmt.
When formatters fail
Formatting sometimes fails. Some examples follow.
Decluttered Java
Here’s some creatively formatted Java. Of course it’s a joke, and a funny one, but a language like Python, and indeed go, gets the last laugh. It is possible to get rid of braces and semicolons and your code will look better for it.
I finally figured out how to get those pesky semicolons and curly braces out of my Java code pic.twitter.com/Ns96HdCuKO
— Uncle Bob's Nephew (@thedirtycoder) February 22, 2015
Tetrominoes
Here’s an equally witty but perfectly sensible use of non-standard layout.
I always love it when code is formatted in the shape of the problem it's solving.pic.twitter.com/XC7J2cSENj
— Mathias Verraes (@mathiasverraes) November 25, 2015
Mathias is referring to some tetrominoes — tetris shapes — coded in elm by Joseph Collard.
-- A Tetromino is a list of Locations. By definition, a valid tetromino -- should contain exactly 4 different locations type Tetromino = [Location] line : Tetromino line = [(0,0), (1,0), (2,0), (3,0)] square : Tetromino square = [(0,0), (1,0), (0,1), (1,1)] spiece : Tetromino spiece = [ (1,0), (2,0), (0,1), (1,1)] jpiece : Tetromino jpiece = [ (1,0), (1,1), (0,2), (1,2)]
For brevity, I’ve left out the Z and L pieces. I’ve also omitted comments from the original, which I would suggest are redundant since the code has been painstakingly formatted to indicate the shape of the pieces.
The same tetrominoes in hand-formatted Go read:
package tetris type Tetronimo [4][2]int var ( Line = Tetronimo {{0, 0}, {1, 0}, {2, 0}, {3, 0}} Square = Tetronimo {{0, 0}, {1, 0}, {0, 1}, {1, 1}} Spiece = Tetronimo { {1, 0}, {2, 0}, {0, 1}, {1, 1}} Jpiece = Tetronimo { {1, 0}, {1, 1}, {0, 2}, {1, 2}} )
Gofmt spoils them:
package tetris type Tetronimo [4][2]int var ( Line = Tetronimo{{0, 0}, {1, 0}, {2, 0}, {3, 0}} Square = Tetronimo{{0, 0}, {1, 0}, {0, 1}, {1, 1}} Spiece = Tetronimo{{1, 0}, {2, 0}, {0, 1}, {1, 1}} Jpiece = Tetronimo{{1, 0}, {1, 1}, {0, 2}, {1, 2}} )
Obfuscation
Here’s a tiny rectangular program submitted by Thaddaeus Frogley to the International Obfuscated C Code Contest in 2001. It’s meant to be hard to read and the compact layout helps, by hindering.
/*(c) 2001 Thad */ #include<string.h> #include <stdio.h> #define abc stdout int main(int a,ch\ ar*b){char*c="??=" "??(??/??/??)??'{" "??!??>??-";while( !((a=fgetc(stdin)) ==EOF))fputc((b=s\ trchr(c,a))?fputc( fputc(077,abc),abc ),"=(/)'<!>""-"??( b-c??):a, abc);??>
In the author’s own words:
I wanted to create a piece of code that could be considered to be “art” on multiple levels.
He’s succeeded. It’s impossible to translate this program into Go so I cannot gofmt it. If, instead, I try clang-format, the resulting code no longer compiles! Obfuscation has bamboozled the formatter.
/*(c) 2001 Thad */ #include <stdio.h> #include <string.h> #define abc stdout int main(int a, ch\ ar *b) { char *c = "??=" "??(??/??/??)??'{" "??!??>??-"; while (!((a = fgetc(stdin)) == EOF))fputc((b=s\ trchr(c,a))?fputc( fputc(077,abc),abc ),"=(/)'<!>""-"??( b-c??):a, abc); ? ? >
Depth First Search
Finally, here’s a recursive depth first search from the boost graph library
template <class IncidenceGraph, class DFSVisitor, class ColorMap, class TerminatorFunc> void depth_first_visit_impl (const IncidenceGraph& g, typename graph_traits<IncidenceGraph>::vertex_descriptor u, DFSVisitor& vis, // pass-by-reference here, important! ColorMap color, TerminatorFunc func) { typedef typename graph_traits<IncidenceGraph>::vertex_descriptor Vertex; typedef typename property_traits<ColorMap>::value_type ColorValue; typedef color_traits<ColorValue> Color; typename graph_traits<IncidenceGraph>::out_edge_iterator ei, ei_end; put(color, u, Color::gray()); vis.discover_vertex(u, g); if (!func(u, g)) for (boost::tie(ei, ei_end) = out_edges(u, g); ei != ei_end; ++ei) { Vertex v = target(*ei, g); vis.examine_edge(*ei, g); ColorValue v_color = get(color, v); if (v_color == Color::white()) { vis.tree_edge(*ei, g); depth_first_visit_impl(g, v, vis, color, func); } else if (v_color == Color::gray()) vis.back_edge(*ei, g); else vis.forward_or_cross_edge(*ei, g); call_finish_edge(vis, *ei, g); } put(color, u, Color::black()); vis.finish_vertex(u, g); }
Clients of the function supply a visitor, vis
, which is called back as vertices and edges are discovered and classified. These callbacks are carefully placed on the right hand side, visually distinguishing the core traversal from associated events. Note too the alignment, with edge events indented to the right of vertex events. Again, a code formatter tramples over this elegantly shaped code:
template <class IncidenceGraph, class DFSVisitor, class ColorMap, class TerminatorFunc> void depth_first_visit_impl( const IncidenceGraph &g, typename graph_traits<IncidenceGraph>::vertex_descriptor u, DFSVisitor &vis, // pass-by-reference here, important! ColorMap color, TerminatorFunc func) { typedef typename graph_traits<IncidenceGraph>::vertex_descriptor Vertex; typedef typename property_traits<ColorMap>::value_type ColorValue; typedef color_traits<ColorValue> Color; typename graph_traits<IncidenceGraph>::out_edge_iterator ei, ei_end; put(color, u, Color::gray()); vis.discover_vertex(u, g); if (!func(u, g)) for (boost::tie(ei, ei_end) = out_edges(u, g); ei != ei_end; ++ei) { Vertex v = target(*ei, g); vis.examine_edge(*ei, g); ColorValue v_color = get(color, v); if (v_color == Color::white()) { vis.tree_edge(*ei, g); depth_first_visit_impl(g, v, vis, color, func); } else if (v_color == Color::gray()) vis.back_edge(*ei, g); else vis.forward_or_cross_edge(*ei, g); call_finish_edge(vis, *ei, g); } put(color, u, Color::black()); vis.finish_vertex(u, g); }
Conclusion
Creative code formatting does have a place. Generally, though, gofmt gets it right: an automated tool can and should take care of formatting our code base, and the benefits are amplified if the tool is inflexible. With formatting solved we can focus our creative energy where it counts.