48 comments

[ 4.3 ms ] story [ 114 ms ] thread
Part of a good software team's toolbox.

Others:

https://direnv.net/ -- when you cd to a directory, do things like set variables.

https://asdf-vm.com/ -- manage and use specific versions of software. Can work with direnv too!

https://pre-commit.com/ -- git hooks that I personally found easier to manage than Husky.

https://github.com/qoomon/git-conventional-commits -- enforce standard commit messages. Works with pre-commit!

I used asdf for a long time and recently switched over to rtx (https://github.com/jdxcode/rtx) and so far it's been a better experience for me. I must have screwed up something years ago in my dotfiles, but when I setup asdf two or so years ago, I kept running into odd issues - the wrong version of ruby being used for running rubocop/rspec, vim being unable to autofix styling, misc other tools not working as expected when maintaining several versions of them. I installed rtx as kind of a hail Mary and it's been much more smooth so far.
Same, I went from volta -> asdf -> rtx, and find rtx to be the most seamless and well-thought-out option for me.
I just spent an hour or so today fighting with asdf Python installs. I’ll check out rtx.
I have to say I love editorconfig, I feel that once you start running code and managing tools it starts to get too much for me. At that point just spin up a VM or container so that the environment is yours.

I loathe pre-commit hooks. I often times just want to save work in progress or show someone what I have right now and they slow down my work or worse fail and lose my commit message and cause me to have to figure out how to disable them to continue. If you want to run checks for formatting or similar that is much better done in CI. I can get a reminder without breaking any dev workflows. Of course make fixing formatting a single command, and maybe an optional hook for those that prefer it. But when projects try to auto-install hooks it drives me crazy.

A compromise is to only enforce pre-commit rules when merging to main or some other important branch. Be as sloppy as you want on your own branches and make stuff proper when you're ready.
For us we did the main only pre commit thing and also a CI check that any MR marked as ready for review has to pass linting.

So you can go wild in your own branches and draft MRs but anything you want someone else to review has to pass.

Imo the pre-receive or update hook should be used on the server. For trunk based development I‘d also only check for (e.g) formatting on the main branch.

  git commit -n|--no-verify
I have it in shell history, so it's somethig like "^R ver".
By the time I realize I should have done that I have already been inconvinced. Also remember to copy .git/COMMIT_EDITMSG before you run it so that you don't loose your message.
Pre commit hooks that fail are trash, just absolutely the worst thing you can do to ruin your team's good practices of committing often & keeping flow high.

Any check that take more than 6 seconds can also go jump off a cliff as car as I'm concerned.

But I do greatly value fast fixing & feedback. Run format! (and don't print out unchanged things!) Run lint! Maybe run tests, if they're fast.

One of the constraints becomes that you are trying to make quick actionable easy to view data, and the output can become too long. We had some outside teams come in & deploy centralized & more updated lint & testing, and alas there's just multiple pages of yellow jest configuration warning as it starts, and it's just so much harder to quickly check status. These hooks need to be quick to read, as well as run. All of the hooks should run if they can, not stop on first problem.

Ideally the ci/CD system is a good fast way to see test results. Devs shouldn't even have to think about it, just commit & off board the running. I hear Facebook has very interesting systems where devs aren't even having to make commits for this, which sounds epic. Alas many ci/CD systems are doubly slow, which sucks: slow to start, slow execution speed, and most damming low caching, so they have to do a lot of from-scratch bring up that just takes a really long time versus what local dev has. Systems like nx in JS land are starting to change this more systematically, rather than being a lot of custom ci/CD tweaking.

Informational, not prescriptive! Use the tools to accelerate & guide. Right now we're all learning but tool heavy setups are for sure the way.

And not to forget:

- Pre-Commit hooks that fail to handle partial stages.

- Running CI as far away as possible to enlarge feedback time.

- Non-incremental builds.

One reason I love using yarn to cache my packages in JS-land. npm needs to catch up. Huge gains in deployment times.
Pre-commit hooks that can't fail are not very useful :) That is, they are not preventing you from committing something patently incorrect.

But pre-commit hooks should fail fast.

> https://direnv.net/ -- when you cd to a directory, do things like set variables.

Just use dotenv instead.

https://asdf-vm.com/ -- manage and use specific versions of software. Can work with direnv too!

May be useful in some cases, but adds a global dependency on asdf itself.

> https://pre-commit.com/ -- git hooks that I personally found easier to manage than Husky.

Git hooks are disturbing and slow down and/or break advanced git interactions.

> https://github.com/qoomon/git-conventional-commits -- enforce standard commit messages. Works with pre-commit!

Only if you want to be so pedantic about them. I find it mostly a waste of time to enforce commit message style.

Good tools, but I disagree with the last one, which feels more like an outlier. The feat, docs, whatever style is highly opinionated, and I have yet to encounter a need for something like this in a professional setting
What notable languages don't have an opinionated auto-formatter? Like Python/Black, Rust/rustfmt, JS/prettier, etc.
Java for sure. I don't think Google style is good enough to be called the default.
Somehow it doesn't seem to be such a big problem in Java land. I think there's some common baseline shared by basically everyone - I don't remember ever seeing Java classes which were not indented with 4 spaces for example.
Auto-formatting is also used for enforcing a line width limit while keeping the code readable, which is hard and tedious to do manually.
Yeah, this dates back pretty far: https://www.oracle.com/technetwork/java/codeconventions-1500...

While minor variations exist, virtually all Java code follows these conventions pretty closely.

Although I'll note it seems to get a bit more chaotic with regards to indenting newer constructions like streams and lambdas.

People like to pooh pooh Java in the circles I hang out in, but I’m always quick to point out stuff like this, things that are just basically mostly solved in Java land and no one really wastes time on anymore because some pretty good decisions were made 20-25 years ago. How to handle a lot of aspects of dependencies is another classic example.
C(++). There is clang-format which has good defaults, but it's highly configurable.
clang-format has so many useless options and yet it cannot do normal readable argument handling. At least not according to the documented options (as of a few versions back at least).

    function(
        arg,
        arg
    );
New line per argument, closing parenthesis on a new line. This is standard in most langauges, yet clang-format cannot handle it.

Then it also insists on aligning everything prettily. Most, but not all of it can be disabled though.

And lastly it doesn't do very well with tab indents. Most of it works perfectly, you just need to set both tab size and indent size, but then it decides to do its alignment thing and suddenly that line is all spaces, even the base indent.

Clang-format is honestly by far the worst formatter I've had the displeasure to fight. C/C++ still needs to be formatted manually

I think with this kind of stuff, it should be, give me a sane set of defaults and make me change it when they don’t make sense. Rather than go to make me search and configure and have to make 100 different decisions on minutiae and exhaust my brainpower on what I would consider important but relatively low impact work in the short term
Sounds like {AlignAfterOpenBracket: BlockIndent, BinPackArguments: false}
(EDIT: Newlines in the code example)

Oooh, that's much closer. Not perfect, but so much better. Do you have a solution for this as well?

    →(StructType){
    →····.a = 1,
    →····.b = 2,
    →};
It's not just this scenario, but this is the worst one. It does it regardless of useTab and what's worse is that it's four spaces when both indent width and tab size are set to 8
Weirdly, initializer indentation is controlled by `ContinuationIndentWidth` rather than by `IndentWidth`. That one I would agree is a clang-format bug, or a mistaken policy decision.

I haven't myself worked on a tabbed code base during clang-format's existence. You might want to look at Linux's settings for ideas, although they clearly differ from your preferences in other ways. https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/lin...

Seems like the point of this is to work across languages though. I'm not sure how I feel about it. Different languages have their own conventions. Often times there's a reason why one convention might be preferred in a language over a different convention in another
Elm even comes packaged with it's own auto-formatter
I really like the batteries included philosopy. I think golang really took the idea mainstream but deno, heavily inspired by go and that philosophy, also is a joy to use.
One of Haskell's most popular formatters, Formolu [1], is explicitly not opinionated and has custom whitespace as a goal:

> Let some whitespace be programmable. The layout of the input influences the layout choices in the output. This means that the choices between single-line/multi-line layouts in certain situations are made by the user, not by an algorithm. This makes the implementation simpler and leaves some control to the user while still guaranteeing that the formatted code is stylistically consistent.

Coq, Agda, etc. also don't have formatters either, partly because "user-defined layout", but also partly because their syntax is just so complicated and they have smaller ecosystems

[1] https://hackage.haskell.org/package/fourmolu

I care whether experts can understand each others’ reviewed work, not whether they would use exactly the same amount of whitespace. Don’t reformat code you aren’t rewriting, and definitely not with a tool that doesn’t understand what a developer was communicating when he laid it out that way.
I would say everything in the codebase should be auto formatted to a consistent style. This reduces whitespace noise in pull requests. No room for egotistical developers to take ownership of files in a shared codebase and waste time on petty formatting arguments. It keeps everything professional and tidy.

When someone commits changes to an existing file, they auto format before committing. If existing parts of the file already contain weird non standard formatting this creates distracting whitespace noise in the pull request that could have been prevented. It's a waste of time to not use standard formatting.

Layout is semantic, just as names and comments are. I don't want a tool to blindly stomp any of these when it lacks understanding how they benefit readers.

The answer to dirty diffs is to not make them. Every change should be intentional, and a reviewer should agree that it's important. "Reformat this block I haphazardly edited" is fine, but don't make the change bigger for no reason.

> I don't want a tool to blindly stomp any of these when it lacks understanding how they benefit readers

Some dev will have 1 newline after a method ends, another dev will have two newlines, some might have 3 etc. Should you as a code reviewer be wasting time looking for such issues and putting comments tell the other to fix such issues?

It’s not my job to make you write exactly what I would have. I don’t care about an extra blank line, and I expect a reviewer to accept that it doesn’t matter. I draw the line at layout that is misleading about the code (e.g., dangling else that doesn’t parse the way it’s indented).
> I don’t care about an extra blank line..I expect a reviewer to accept that it doesn’t matter.

You dont care about an extra blank line, what about 2 extra lines, what about three and so on. Do you see why there is a need for a consistent style?

If it gets to a dozen I’d grumble about wanting to fit more code on screen, but this just isn’t a problem we’ve ever had. I think the urge to enforce complete uniformity is bad for a collegial high-trust team.
> If it gets to a dozen I’d grumble about wanting to fit more code on screen

exactly, which is why such rules should be part of the code, no need to trust if such conventions are followed if the rule will take care of it.

You should spend your time doing some meaningful not wasting it on such issues.

(comment deleted)
Yes layout is semantic and the automatic layout configuration should not be random nonsense. Configure a style that makes sense and apply it.

I've never seen that code that needed to be weirdly formatted with its own rules separate from other files in the codebase in order to make sense. That just sounds like a developer being a prima donna with their code style, not something that genuinely improves understanding and collaboration.

I’m thinking of cases like

  System.arraycopy(src,  srcPos, // from
                   dest, destPos, len) // to
where I want to show the parallel relationships between src/srcPos and dest/destPos. Blind tools don’t know to do this, they either emit one long line or waste seven lines.

Tools also won’t know how to format a DSL well at all, which has already been a problem with Spark jobs in Scala.

Meh. That is so obvious to anyone who would use that function. Just put it on one line.

If your function has too many parameters then the code probably needs refactoring.

Yes agreed about DSL in Scala. Scala is complicated. I was a Scala dev for many years. We tried to avoid the more academic and overly abstract designs with that language.

Here we are in the current year and we still i/o source as raw form text. Personally, I’ve always hoped for standardized AST formats with comprehensive tooling, including source control. Many more problems vanish than are created with source as AST. For instance, this discussion.