gofmt ends the style wars

Station 0.5 8 min Interchange: PSR-12 & PHP-CS-Fixer → gofmt

PHP's style history is a saga. PSR-1, then PSR-2, then PSR-12, then PER Coding Style. PHP_CodeSniffer, PHP-CS-Fixer, Laravel Pint with its own presets. A .php-cs-fixer.dist.php in every repo, each one slightly different, and at least one pull request thread per year about whether the opening brace of a closure belongs on the same line.

Go skipped all of that. It ships with gofmt, a formatter with no configuration at all. Not "sensible defaults": no options. Every Go file in every Go project is formatted the same way, by the same tool.

What gofmt decides for you

  • Tabs for indentation, spaces for alignment. The tabs-vs-spaces war ended in 2009. Tabs won, and nobody had to fight. Your editor can display a tab at whatever width you like.
  • Braces, spacing and blank lines around imports and declarations.
  • Alignment of struct fields, comments and composite literals, which PHP devs normally line up by hand or with a fixer rule.
  • No line length limit. Wrap long lines yourself where it helps readability; gofmt won't do it for you, and won't complain either.

There's a Go proverb about it: gofmt's style is no one's favourite, yet gofmt is everyone's favourite. You give up a little personal taste, and in exchange every codebase you open reads like your own.

In practice, you never run it

Every Go editor setup (VS Code's Go extension, GoLand, Neovim with gopls) runs gofmt when you save. Most people actually use goimports, which is gofmt plus adding and removing imports for you. That makes the "unused import" error from 0.2 far less annoying.

In CI, gofmt -l . lists any files that aren't formatted, so you can fail the build on them. In this course, the Format button (or Ctrl+S) runs gofmt on your code.

Formatting that tells you something

gofmt doesn't just tidy whitespace. It also uses spacing to show operator precedence: it keeps spaces around lower-precedence operators and removes them around higher ones.

total := travel+dwell * stops  // looks like (travel+dwell) * stops
total := travel + dwell*stops  // after gofmt: it never was

(:= declares a variable and assigns to it in one go. Unit 1 covers it; for now, read it as $total =.)

Mind the gap

Spacing in PHP is decoration: $travel+$dwell * $stops and $travel + $dwell*$stops read differently but mean the same thing, and nothing points it out. In Go, gofmt makes the spacing match what the code actually does. If the formatted version surprises you, believe the formatted version, and add parentheses.

What gofmt won't do

It only formats code that parses: a syntax error means no formatting. It won't rename get_user_name to userName, reorder your logic, or fix bugs. go vet (0.3) and linters (5.7) handle more of that. The formatter's job is to make sure you never discuss whitespace in code review again.

Recap

  • gofmt is built in and has no configuration: tabs, spacing, braces and alignment are decided for you.
  • Editors run it (or goimports) on save; CI checks with gofmt -l ..
  • gofmt's spacing shows precedence: a + b*c. If the formatted code surprises you, it's telling you something.

1The configuration

PHP.php-cs-fixer.dist.php
<?php
return (new PhpCsFixer\Config())
    ->setRules([
        '@PSR12' => true,
        'array_syntax' => ['syntax' => 'short'],
        'binary_operator_spaces' => ['default' => 'align_single_space_minimal'],
        'ordered_imports' => true,
        'no_unused_imports' => true,
    ])
    ->setFinder(PhpCsFixer\Finder::create()->in(__DIR__ . '/src'));
GO(there is no config file)
# That's the whole setup. No rules, no presets.
gofmt -l .          # list files that need formatting (CI)
gofmt -w .          # rewrite them in place
goimports -w .      # same, plus fix imports

2Alignment

PHPalign.php
<?php
$config = [
    'host'    => 'db.local',   // aligned by hand,
    'port'    => 5432,         // or by a fixer rule
    'timeout' => 30,
];
GOalign.go
cfg := Config{
	Host:    "db.local", // aligned by gofmt,
	Port:    5432,       // every time, for everyone
	Timeout: 30,
}

Mind the gap

In PHP, alignment like this is a fixer rule your team voted on. In Go it's what gofmt always does, using tabs to indent and spaces to align.

3Spacing that shows precedence

PHPprecedence.php
<?php
$minutes = $travel+$dwell * $stops;
// Reads like ($travel + $dwell) * $stops.
// It's $travel + ($dwell * $stops), and nothing tells you.
GOprecedence.go
minutes := travel+dwell * stops // what you typed
minutes := travel + dwell*stops // what gofmt shows you

minutes := (travel + dwell) * stops // what you meant

Mind the gap

gofmt removes the spaces around * because it binds tighter than +. The formatting now shows what the code does, and here that's a bug.