Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

> most language do fine

No, they don't. Most languages turn dealing with code formatting, into an externality foisted upon either:

• the release managers (who have to set up automation to enforce a house style — but first have to resolve interminable arguments about what the given project's house style should be, which creates a disincentive to doing this automation); or

• the people reviewing code in the language.

In most languages, in a repo without formatting automation, reviewers are often passed these stupid messy PRs that intermingle actual semantic changes, with (often tons of) random formatting touch-ups — usually on just the files touched by the submitter's IDE.

There's a constant chant by code reviewers to "submit formatting fixes as their own PR if there are any needed" — but nobody ever does it.

Golang 1. fixes in place a single "house style", removing the speedbump in the way of automating formatting; and 2. pushes the costs of formatting back to the developer, by making most major formatting problems (e.g. unneeded imports) into compile-time errors — and also, building a formatter into the toolchain where it can be relied upon to be present and so used in pre-commit hooks, guaranteeing that the code in the repo never gets out of sync with proper formatting.

"Getting in the way of the users" is the right thing to do, when "the users" (developers) fan in 1000:1 with code reviewers and release managers, who have to handle any sloppiness they produce.

(Coincidentally, this is analogous to other Google thinking about scaling compute tasks. Paraphrasing: "don't push CPU-bottlenecked workloads from O(N) mostly-idle clients, out to O(log N) servers. Especially if the clients are just going to sit there waiting for the servers' response. Get as much of the compute done on the clients as possible; there's not only far more capacity there, but blocking on the client blocks only the person doing the heavy task, rather than creating a QoS problem." Also known as: "the best 'build server' is your own workstation. We gave you a machine with an i9 in it for a reason!")



> No, they don't.

Really, they do: there a millions of us coding in those other languages just fine, and automatic formatting has been a thing for decade, and I'm not aware of a single language out there that doesn't have such a formatting tool.

The only exception with Go is that you cannot change the default settings. But that's it. In any other language you can use a code formatter with the default settings and the “speedbump in the way of automating formatting” you talk about doesn't exist anywhere but in your mind.

> where it can be relied upon to be present and so used in pre-commit hooks

You know that a failing git hook aborts the commit? So that with any language, if the formatter isn't installed in the machine, the commit cannot be performed, which means that the formatter can actually be relied upon anyway. In practice, the hardest part is making sure people all have the git hook installed (that's not that hard but that's the hardest part).

As I said before, Go has many useful properties, but automatic formatting is definitely not what makes Go relevant, and the endless stream of Gophers who argue this are just ridiculing themselves in front of everybody else.


> You know that a failing git hook aborts the commit? So that with any language, if the formatter isn't installed in the machine, the commit cannot be performed, which means that the formatter can actually be relied upon anyway.

When making a trivial fix PR to an upstream FOSS project, if I find that a missing third-party linter install has force-rejected my commit (that I know has correct syntax)... then I just give up on making that PR. I can't be assed to install some random linter. (Third-party linters have a history of being horrible to install†.)

Small amounts of friction can be enough to shape behavior (see https://en.wikipedia.org/wiki/Nudge_theory.) Aggregated over a large project's entire community, this can make an appreciable difference in code quality over time.

† Mind you, a linter that exists as a library dev dependency of the project is fine, too. I had to pull the deps to build and run the tests, so the linter will be there by the time I attempt to commit. It's just linters that are their whole own projects that give me a jaw-ache.

> and the endless stream of Gophers who argue this are just ridiculing themselves in front of everybody else.

I don't even use Go! I mainly write Elixir, actually. Which also has a built-in auto-formatter.

To me, the nice thing about the formatter being built into Elixir (and of-a-piece with the compiler), is that when I use macros, the purely-in-memory generated-and-compiled code can be inspected in the REPL, and shows as formatted (because it passes through the auto-formatter), rather than looking like AST gunk. Without having had to pay that auto-formatting cost at compile time (because that would also be a cost you'd pay at runtime codegen time, which you might do a lot of if you've built a domain-specific JIT on top of the macro system.)


It's easy for programmers to focus on the technical details and forget the big picture. The technical aspects of automatically formatting code are relatively easy to solve. The difficulty is in the social parts. That's what Go solved by bundling gofmt with the language.

As a result, almost all Go code out there is formatted the exact same way and nobody has ever had to have the dreaded code formatting discussion about Go at their company. Eliminating such bikeshedding for every user of the language is a solid win.

That's why all the languages proceeding Go have adopted the same approach, e.g. Rust and Zig. Python's Black formatter has been directly inspired by gofmt as well.

What is provided by default really matters.


Ironic given how much effort is going into Bazel remote build executors.


Snarky response: that's more steps toward the long-held dream of the Google operations department: to be able to just issue all devs cheap commodity Chromebooks, because all the compute happens on a (scale-to-zero) Cloud Shell or Cloud Workstation resource.

Actual response:

• For dev-time iteration, you want local builds; for large software (e.g. Chrome), you make this work by making builds incremental. So it takes a few hours to build locally the first time you build, but then it's down to 30s to rebuild after a change.

• But for building releases, you can't rely on incremental builds; incremental builds (i.e. building on top of a cache from previous arbitrary builds) would be non-deterministic and give you non-reproducible builds, exactly what a release manager doesn't want. So releases, at least, are stuck needing to "build the world." You want to accelerate those — remote build infra is the way to go. Remote, distributed build infra, ideally (think: ccache on Flume.)

These remote/distributed builds do still cohere to the philosophy in the abstract, though — a remote build is not the same as a CI build, after all; the dev's own workstation is still acting as the planner and director of the build process.


Appreciate a proper response to my throw away comment :)

> incremental builds (i.e. building on top of a cache from previous arbitrary builds) would be non-deterministic and give you non-reproducible builds

Isn’t this exactly what Bazel solves?


It tries, but it's really more of an operational benefit (i.e. works to your advantage to enable build traceability and avoid compile-time Heisenbugs, when you the developer can hold your workstation's build-env constant) than a build-integrity one (i.e. something a mutually-untrustworthy party could use to audit the integrity of your build pipeline, by taking random sample releases and building them themselves to the same resulting SHA — ala Debian's deterministic builds.)

Bazel doesn't go full Nix — it doesn't capture the entire OS + build toolchain inside its content-fingerprinting to track it for changes between builds. It's more like Homebrew's build env — a temporary sandbox prefix containing a checkout of your project, plus symlinks to resolved versions of any referenced libs.

Because of this, you might build, upgrade an OS package referenced in your build or containing parts of your toolchain, and then build again, Bazel (used on its own) doesn't know that anything's different. But now you have a build that doesn't look quite like it would if you had built everything with the newest version of the package.

I'm not saying you can't get deterministic builds from Bazel; you just have to do things outside of Bazel to guarantee that. Bazel gets you maybe 80% of the way there. Running the builds inside a known fixed builder image (that you then publish) would be one way to get the other 20%.

I have a feeling that Blaze is probably better for this, though, given all the inherent corollary technologies (e.g. objFS) it has within Google that don't exist out here.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: