I updated a ~3k line Python project with this the other day. I was using v0.15.x beforehand. It didn't take too long and the new rules do improve code quality. It caught quite a few things previous versions didn't.
Tbh I was super enthusiastic about Ty but it’s just so far behind basedpyright I had to switch off it. The lack of checks wasn’t the issue, it was the false positives which were the real dealbreaker. That and it doesn’t have support for baselining, which is such an insanely awesome feature for larger codebases. uv and ruff are incredible though. Maybe someday on Ty.
The amount of fascination that people have with these "grammar nazi" bots -- some of them implementing completely arbitrary "rules" and some of them disagreeing with others on what "good" Python code should look like -- doesn't stop to amaze me.
Here's "bad" code:
important_numbers = {
'x': 3,
'y': 42, # Answer to the Ultimate Question!
'z': 2
}
Here's what "good" code should look like:
important_numbers = {"x": 3, "y": 42, "z": 2} # Answer to the Ultimate Question!
Which completely misses the writer's intent. But did you notice that there are two spaces before the pound now? Also, the quotes are now double because apparently it's somehow more kosher! So much improvement!
The actual problems in the code I work with are not the spaces at the end of line or imports in non-alphabetic order, it's the 10-line list comprehensions that are so long that they're impossible for me to parse. None of these tools catch that. My place used pylint, flake8, black, ruff -- with hundreds of commits on every change. All that energy could be better spent elsewhere.
The point of the "grammar nazi" bots is to focus on the actual problems: if a bot is deciding about linting, you don't have to waste brain power to discuss it in PRs.
It is what it is, everyone gets the same, shut up and work on what matters.
I'm surprised you consider it a lot of energy spent, I tend not to spend any on this, it just runs automatically on my code and I drop out of pretty much every discussions about linting as it's good enough with automated tools.
I also used to work in places where there was no such tools, and there I had to actually spend time discussing and thinking about linting.
Which gets rid of the discussion, but not the problem coding style rules are supposed to fix: Code looks the same, regardless of who wrote it. That's the whole point of code style guidelines like that as there's no functional reason for them.
That's why I like Go, every piece of code looks the same, there's one default enforced linter and this discussion (or discussion if discussion should be allowed or prohibited) doesn't even cross anyones mind.
My argument is more to the tune of "everybody’s code looking slightly different is not a problem in practice as long as I can read and understand it."
However since you've asked so nicely here you go: everybody’s code looking different is because all humans are different. It's what makes us human. I am very serious about my craftsmanship, and I bring my "humanity" to it: sometimes I include a cultural reference (as in the example above), or an internal joke in the name of a (very long) variable, or vent my frustration in a comment.
My first ten years of writing Python were uneventful; nobody commented about my style and I never commented about others'. With the advent of grammar nazi bots, everyone is supposed to now please them by writing completely bland code, which in my opinion degrades me from a craftsman to a code-monkey. This is dehumanising, in a certain sense.
But your choice of funny variable names is not a craft, and you’re not a craftsman if you think it matters. What matters is extensibility, maintainability and value delivered.
I care how the food tastes, not that the chef has a really cool Japanese knife and is really fast at cutting onions.
I think you miss the point, but using your foodie reference: would you rather go to a couple of Michelin restaurants -- where each piece brings a reflection of the chef, the geographic area, and what quality ingredients were available on that day -- or would you rather only eat at McDonald's for an experience that is extremely consistent across days, seasons, and continents?
Now imagine a chef who has a nice little restaurant but is now being sent a couple of "quality assurance" guys from McDonald's who tell him that his choice of potato variety for chips does not exactly conform to the "standards" defined at the mothership.
A chef in a michelin restaurant, unless he/she is the sole employee (very rare) is likely in charge of a team of line cooks and while they may get some time and place to experiment you better believe it will not be when preparing the chefs carefully curated menu and recipes for paying customers who have been on a reservation waitlist for months. Bob decided to cook the broccoli with a blowtorch and substituted some ingredients in table 9s dinner. There is a fire in the kitchen and the NYT food critic is going into anaphylactic shock but Bob at least feels like an artist so we will let it slide.
The one day, your chef leaves, the food goes to shit and productivity/output/morale drops. When the new chef arrives, then everyone has to relearn everything.
I too want to be an artist, but I work in an economy that requires me to be an artist mostly in my off hours.
And honestly, this is such a weird hill to die on. Lint rules when automated are basically pure win in most scenarios, and for your example, just exclude the rule for this block.
We want maintainable code we can quickly onboard in, and be able to modify and extend. Your artistry is getting in the way of other people getting stuff done.
The hidden implication in your extension of the foodie analogy is that consistent food must have worse quality.
That's not true, and if anything in software having consistent software delivery makes it more likely to achieve Michelin-graded level of quality, not less.
I'd go so far as to argue that person to person differences are helpful to a reader. When I am dealing with a long term code base it's easy to develop a feel for who is responsible for different constructs without needing to rely on the git history. This gives me immediate information on what to expect.
I can - in fact, I have to, a lot more than I'd like - when someone makes changes with different code style settings in their IDE or tooling, and that affects unrelated parts of the code, and suddenly the PR contains tons of code that just looks subtly different but still does the same thing.
If sieving the spam from ham in code reviews is your thing, go have fun.
I personally prefer code that is automatically and unconditionally kept in the exact same shape, preserving only the intended changes to stop the team from wasting time on formatting or reviewing.
That seems like a problem caused by an autoformatted, not solved by one. Obviously having reviewable commits is a reasonable expectation but that doesn't seem like a justification at all.
Pretty much all tools developers use to write code format code to some extent while editing, but often with individual configuration knobs or default settings that are different between all ICs.
Having a single tool that runs before committing, ideally as a git hook, ensures a single, uniform style across all files touched.
You are not making the strongest point by using a relatively subjective rule like that. It includes indentation, how to structure functions and the parameters and many more that in sum make it very easy to jump between projects (Internal company projects, dependencies, other open source projects) without ever getting used to a new style. This makes reading and contributing very easy.
Compare that with other languages where you need to load a different set of prettier rules, code formatting tools and follow contribution guidelines on how things should look like depending on the opinion of the maintainers.
I've never found this to be the case having jumped companies a fair bit. I can switch programming languages easily enough, surely I can handle formatting.
The value isn't just in the lack of discussion, but in amplifying the signal to noise ratio of diffs. with ruff or gofmt or whatever, it’s pretty much guaranteed that a change of code is an intentional part of the proposed change. with multiple authors with variable code styles touching the same project, there is a much higher chance of a diff containing lines that preserve semantics but change the code.
>Which gets rid of the discussion, but not the problem coding style rules are supposed to fix: Code looks the same, regardless of who wrote it.
Which might be more overrated OCD than anything worth it. If you like that, you can add a formatter at the end of the chain or even just when reviewing.
Is that really a problem that needs solving though? The easiest way to not spend time bikeshedding is to just not bikeshed. Don't make guidelines. Don't run tools to check them. Don't comment on them in PRs. None of it matters.
Historically, yes. At some point, one person who likes to work with narrow terminal windows gets fed up with long lines and starts reformatting stuff as they go. Another person with a widescreen editor hates looking at code clustered around the left edge of the screen gets fed up and starts reformatting stuff as they go. It's just a mess. You end up with PRs where it's hard to see the 3 things that changed because it contains 200 lines of "my IDE would prefer to format things this way instead".
I have never seen this not happen, barring using autoformatters. I think Go and Rust have had great success avoiding all these dumb arguments by having built-in opinionated formatters from nearly the beginning.
Aren't you supporting the GP's point though? If you auto-format all the things, they will likely not be right for people either, neither for the narrow terminal window, not for the widescreen full size window enjoyer. And there was energy spent on making it so, without it being a clear winner. Instead we could just be respectful and leave such things as they are, and not have to discuss this at all.
No. Auto-formatters have a way of getting people out of the mindset of arguing about it and just accepting that's the way it is. It's not that different from, say, using a compiled language instead of assembler. You and I might have differing ideas about how to write the best assembly code. If we standardize on C, it's likely neither of us will love the compiler's output, but short of outright bugs we'd move past that and go back to writing code.
> That's why I like Go, every piece of code looks the same, there's one default enforced linter and this discussion (or discussion if discussion should be allowed or prohibited) doesn't even cross anyones mind
I remember people saying this exact same thing about Python ~20 years ago.
Using a consistent string delimiter has value: if you search for ['foo'] you will find all instances of the string foo. With inconsistent delimiters, you better have a single canonical 'foo' in your project or you're going to run into problems.
So your argument is that your freedom to use whatever string delimiter you want (remember that ''', """, ` and even weird unicode glyphs are valid in many languages) is worth forcing other engineers on the team to know all valid string delimiters and remember to use the right regex to account for all possible weirdo choices?
My argument is that searching for \bfoo\b will produce all the results you want.
Python was designed from the start so that 'foo' and "foo" are equal. It also worked like that for 30 years or so. This has not been an issue in these 30 years. But then someone came with an opinion that one of them is better than the other.
\bfoo\b will produce noisy results. People have been pushing linting rules that force a single consistent choice of string delimiters since at least 2010 when Python went from a cute academic language to a real industry workhorse.
I would hate to work on that codebase. We learn to parse code more quickly over years of looking at code written with the same conventions and style. There’s a reason why Google are so serious about following their style guides.
Even better: if someone feels like the syntax must be different, they can pick it up at a centralized repository with more people that specialize in syntax.
Afterwards it will affect production code everywhere on the planet. :)
This is the way, and why I wrote https://pypi.org/project/ssort/. Unfortunately you need to follow it up with shameless self-promotion or it doesn't work.
>The point of the "grammar nazi" bots is to focus on the actual problems: if a bot is deciding about linting, you don't have to waste brain power to discuss it in PRs.
And yet, as per the example, the bot force worse linting decisions, and even pushes towards more bugs (e.g. future readers missing the comment intention since it now applies to all 3).
the claim we are arguing here is not that “linters never make mistakes” it is that “linters are generally beneficial and avoid mostly useless debates we previously spent an inordinate amount of time on”
Most linters have an escape hatch for exactly this which is usually a one line disable comment
There is no point discussing these minutia, ever. Just ban discussing them in PRs and then you don't need an opinionated bot rife with false positives to "fix" them.
I've never seen this debate take place outside of internet forums, almost always as a joke. It's been about 15 years since I even remember it coming up much as a joke, frankly.
Okay, so if the battles are won why do I have to care? What is the point of having so many strict, enforced style rules if your exemplar has already been long dealt with?
Pretend they don’t exist as much as you’d like, tell everyone their lived experience is wrong, that the tools arose from delusion and not any real world need, and that they benefit no one.
I’ll keep using them though, and never suffer from someone writing typescript with 4 spaces, leading commas, and no semicolons again.
I'm not saying that lived experiences are wrong, I'm saying that I don't believe that these problems would exist under the current software engineering culture to the degree that people seem to insist upon.
These used to happen all the time. In one (otherwise great) programming course in university you would even get points subtracted if you did not follow the lecturer's preferred style.
(I think that in their case it was partially because uniformity made correcting faster.)
Oh, absolutely. I started writing Python professionally in 2010. Whenever a new group of people started working on a Python project, one of the first things they had to decide was tabs versus spaces. Everyone would begin by saying that the question was unimportant and silly, but then immediately add a “but” followed by a detailed justification of their personal preference. That would lead to people spending two full days exchanging arguments and links to blog posts.
Worse, sometimes you would start working on a project without having that discussion first, only to discover a few days later that the code was failing because different people had different indentation settings in their IDEs, both regarding tabs versus spaces and the number of spaces. You would then have to pause the work, go back, have the discussion, and decide who was going to fix all the code committed so far.
This only stopped when linters became popular. They may be a little like vaccines: when they work, you do not see them working, so it is easy to forget what things were like before. But believe me, when they appeared, they were a breath of fresh air. We could finally focus on discussing the work rather than the conventions.
And tabs versus spaces is only one example. There used to be PR discussions about countless style issues throughout the entire lifetime of a project. That was not entirely unreasonable, because a consistent style genuinely makes a project easier to work on. But when every individual convention has to be negotiated by humans, it takes enormous amounts of energy and becomes tedious very quickly.
> only to discover a few days later that the code was failing because different people had different indentation settings in their IDEs, both regarding tabs versus spaces and the number of spaces.
yeah, why Guido got away with the "we don't need deliminators, white space is just fine" for so long has always baffled me.
Because somehow a special character that marks the start and end of a block is hard for people to understand??
I think that tabs vs spaces is a rather extreme example because it has unexpected parser semantics - that is, it's not a style choice. So I don't think it's actually relevant to a conversation about conventions/ style.
It definitely is relevant to a discussion about linters. And as I said on my comment - it's just an example, I mentioned other types of discussions as well.
Silicon Valley (the TV show) memed about it with its tabs vs spaces bit. It used to be a thing for sure. It has been a good 10 or 15 years since I had such a discussion. Automated linting and formatting tools largely killed it in my experience.
I think the culture has just shifted. I haven't even sniffed a whiff these conversations since roughly 15 years ago. I've never, ever seen them at work.
I also never have these conversations at work…because every repo uses a linter and the engineers are mature enough to recognize most of these debates as bikeshedding. Every once in a while someone will suggest turning on or enabling a new custom rule with some justification and there is a brief discussion and it is enabled or not. Outside of work in a consulting gig I didn’t have a linter in the codebase and the owner brought on a new engineer, he wrote if else statements all on one line no matter how long they were and removed spaces everywhere because he thought shorter code was better. He balked hard at any feedback about code style and I was told I couldn’t enforce code style or install a linter. I think you will surprised how much you care if someone with a truly weird style starts putting it in your codebase, I always told myself I didn’t before this. After more dumb waste of time clashes like that I quit the project. Now even personal projects get a linter :).
> this seems like a made up problem or something that predates my career
I can believe the latter, and I can assure you this was a Big Deal back in the day. There's a reason Go ships with gofmt, and gofmt was somewhat revolutionary for being a built-in "you gotta do it like this" back in the day.
Others may remember it differently, but I feel like things started to change somewhere between 2009 to 2012? People were still hand-indenting code back then, and your personal style felt like something you had a bit of ownership of or was a creative outlet for you. I think we are in a much better place now where the editor + a central config file owns the whole thing, and I'd be surprised to see a modern codebase where a formatter's defaults weren't being used.
Dunno, now the workflow is like agent makes code changes, ruff complains, agent fixes complaints at the cost of code bloat, agent makes a PR, another agent reviews the PR, agent makes changes, PR is approved and merged. Nobody reads PRs anymore with the agentic development velocity.
(I intentionally switched to double quotes since that really is a stylistic choice in Python, you can escape in both, and if you use double quotes inside of single quotes ruff leaves it as-is)
I personally like double quotes, because in so many other languages they are for strings, while single quotes are often for other things. But somehow many people have a single quotes obsession in Python. I am guessing, that it is because of ease of typing them on a US keyboard layout.
Not my problem. My problem is that I want to use them both, as I've been able to for many years.
I'll always use double to surround "can't" and single to surround '8.5" x 14"'. I'll use a context-appropriate pair when adding to existing code. But for all the other cases, I don't want to expend any energy on this. It's a complete non-issue.
> ease of typing them on a US keyboard layout
On both the US and European keyboards single-quotes are easier: no need to press Shift.
I've read through all the sub-comments you've left here.
In short, you seem to have a grievance against linters that goes way past reasonable. You assert this example you give is "perfect" below. You also complain linters waste time and commits despite able evidence in the field they do the opposite.
You seem willing to add drama, grievance, and personal bias to what could have been a rather straightforward technical discussion. The sum of which is: linters sometimes make changes to code that I don't like.
I'd encourage you to reconsider what's really important when it comes to team development and try listening more to those around you. You seem overly focused on asserting your opinion instead of having a discussion.
These types of exchanges are usually very unproductive and stressful in a team environment. They are evidence, IMO, of a dev having the wrong priorities. I'd encourage you to step back and really consider how you view craftsmanship in a team dynamic and whether or not you are fighting the right battles.
Yeah, I'm in the "not interested" and "don't care" camp, with the addition of "please don't come at me with your bots who would tell me what to believe and what not to believe".
If someone wants to use a linter, I have no problem with that. I don't, and I don't want others forcing one on me, especially so as most of its "results" are straight in "this doesn't matter either way" category (in my example: two spaces instead of one, replacing quotes) and the rest make my code verifiably worse (folding a list in one line).
I think, generally speaking, the decision to enforce a linter in CI/CD pipelines is well within a project lead’s or technical director’s purview, and there seems to be a pretty broad consensus that they add value, so they’ll almost always be in CI/CD pipelines. So just use the same linter locally and format on save so you catch any issues like your comment getting folded example.
My issue with your comment is not that it seems to offer unsolicited psychological advice completely out of the blue; it's that it leaves me with an uncanny feeling that it was written by an LLM.
In a thread where I complain about the de-humanisation of my profession by the bots, the irony of having a bot's psychological advice (and so, de-humanising the discussion too) is not lost on me.
If you are really concerned about my being a bot, a quick look at my comment history, going back over a decade, should dispel that concern.
Edit: I once had someone apply for a job and their written communication was pretty caustic. After rejecting their application, I wrote and explained how unprofessional I found their interactions, encouraging them to step back and reconsider how their communication appears from the other side.
To my great surprise, they wrote back and said they had never had anyone give them such feedback and they agreed with my assessment of their interactions. They just had never seen it before. They thanked me for the feedback.
Such interactions are exceedingly rare. But I sometimes offer unsolicited advice on the off chance that said advice can make a difference.
You neither usually write in that style, nor go full shrink mode on people you talk to, so your original comment here is quite unusual in both the contents and the style.
It’s because you forgot a comma after the last item. If you had kept that the items wouldn’t have been compacted (at least in black, I’ve stopped linking my code because it breaks intends of formatting more often than it helps)
Sorry, but I did not "forg[e]t a comma after the last item". I intentionally did not put a comma there. The list of the "important numbers" in my example is already complete and perfect, and is not supposed to ever change. I wanted to emphasise this fact by specifically omitting the comma there, in the hope that intelligent people in the future would get this message exactly as I intended for it to be.
I think all of us have worked on code that other people thought was perfect or will never change. Personally I love this rule because it saves me from looking at one extra commit (adding the comma when the next value gets added) when I'm trying dig through line history.
The trailing comma rule is to avoid errors when devs copy/paste a line or dump a new line at the end. This matters particularly for arrays in languages with implicit string concatenation, where the missing comma won't trigger a compile error.
Those tools actually save team energy. Without them any programmer has different opinion on formating, code quality, what is readable etc. You can discuss it endlesly or you can just use ruff.
We can replace the endless and useless discussion on how to format code with an endless and useless discussion on what linting tool to use.
Another option would be to leave both topics alone and go on with our lives, improving the product, fixing bugs, implementing new features, and generally giving customers a better product and shareholders more value, while respectfully agreeing to disagree on the issues of style.
People tend to use tools set up in CICD and/or precommit. Without rules everyone uses their tool and is unwilling to compromise. Even if they are willing it is hard to setup tools to do exactly what you want. Default configuration is always the way.
So to the practical question: what we're gonna do today in CICD and/or precommit? "Default configuration is always the way" but which one: `ruff<0.16.0` or `ruff=0.16.0`?
Gods forbid I talk to people I work with, learn their preferences, and figure out the way we can accommodate each other. Sounds like such a waste of energy. It’s so much better if we all acquiesce to the preferences of some people neither of works with!
How does that work when hundreds of people are working on a project, with a few new people joining every week?
It boggles me that anyone would argue against standardization, or believe that “just get hundreds of people to agree today and tomorrow and every future day” is a better solution.
Why though? I've yet to come across an auto formatter that formats things the way I personally like. But that's fine. I do whatever the hell I want until I'm ready to open a PR, then bang it through the auto formatter. And you know what? I can also go the other direction and run a home-brew auto formatter to reshape things the way I personally want on my local machine.
I'm not sure why, but apparently my experience is unwelcome enough to be downvoted. Drive by changes are really annoying, IMO, and as the GP shows, the formatting tool outcome differs. So there are many "valid" versions of the same program logic. You have therefore not do what these formatters pretend to do, namely take this variable out of the equation. Rather, the wrong auto format can look much worse than the original.
I'm not sure why people felt formatters were necessary. but now they're part of the zeitgeist and there is no going back.
This seems like a strange hill to die on. Linters don’t consume energy, that’s the entire point. They get everyone on the same standard so that no energy is wasted by anyone on having to discuss, debate, and implement these standards. And yes, there are instances where someone’s non-standard coding style is not a problem. But there are instances where it absolutely can be, and these tools help there.
I’m also not sure I understand the “ My place used pylint, flake8, black, ruff -- with hundreds of commits on every change”. These tools don’t add extra commits. You run them prior to your PR, get them aligned with the linting, and then commit the change you were already going to make. Thats 0 extra commits.
For someone ultimately arguing that there is too much effort spent on people’s coding styles, you are spending a lot of effort arguing about people’s coding styles. These tools are some of the most set it and forget it things around.
When you replace tool x with tool y, you need to fix your code, because these tools do not agree on what your code should look like, and what was acceptable for tool x simply isn't for tool y.
One of these changes I mentioned brought a massive reformatting of all code base, as the new tool failed 'import' statements unless they were in alphabetical order.
Sounds like the issue is not the tools themselves, but that the project management / stewardship allows someone to replace tools willy-nilly leaving others to pay the cost of migration?
At $WORK we have "code guardians" whose responsibility is do to this kind of migrations strategically in the background. The intent is that most other developers should not need to do any (or very little) extra work beyond the task they are actively working on.
The linked article says that ruff now has 413 default rules -- instead of 59 previously. Do I understand it right that all the projects that were using ruff were 100% conformant to 59 rules yesterday but likely not conformant to 413 rules today, which will cause breakage all across the board? Likely immediately followed by a swarm of pull requests all formatting their code in conformance to whatever these new (413-59) rules are?
Well, that's the danger of using software without version pinning.
Also, usually tools like Ruff can be configured to enable/disable project-specific formatting rules (via a config file), allowing you to suppress the defaults you disagree with, or whose consequence you don't want to deal with _right now_. Some tools might even support suppressing specific rules per-file, so you can migrate codebase over time instead of in one go.
Having a config that explicitly defines the rules enforced in the project is really useful, and perhaps even crucial the bigger the project grows.
The options you suggest seem to be either (a) feed the bot now (halt development and reformat all code) or (b) under-pin/suppress and feed the bot later, probably dealing with people adding more non-conformant code to already-reformatted files.
You will probably have to do it all over again when these guys release even newer and shinier 0.17.0, with even more rules.
I can't understand how the option of not feeding the bot eludes people.
Stylistically ugly code that is globally consistent (ideally across the entire ecosystem) >> locally beautiful code that is inconsistent with the rest of the codebase / up to authors taste. No one likes black formatting, but it’s at least consistent. “Your car can be any color you like as long as it’s black”
With regards to your example, adding a comma to the final element should preserve multi-line. You just don’t know the formatting rules yet apparently
> The actual problems in the code I work with are not the spaces at the end of line or imports in non-alphabetic order, it's the 10-line list comprehensions that are so long that they're impossible for me to parse.
100%. Almost all of my time burned navigating code is not hung up on stylistic conventions but on nasty services with inconsistent abstractions and patterns.
BUT, conventions and consistency make code easier to read and write, period. If you’re debating over single or double quotes that’s almost a fireable offense IMO.
Additionally, when you have a culture that delegates to tools as much as possible, the focus sharpens in a healthy way.
I feel for you both working with straw men day to day. I’ve worked mainly in Python for two decades and have neither seen such a thing nor considered, even to be a bastard, doing such a thing.
The actual rules are less important, only the consistency of applying them is.
I find that people who argue against automatic linting and formatting tend to be the same that would argue incessantly about style. So much wasted energy, I want none of that.
Also, in your example above, if you put the comment on the line _above_ instead of inline the formatter will most likely do the right thing.
In my first ten years of writing Python, I never argued about code style with anyone. It's only after they brought the bots in that I learned that my style is somehow "bad."
Well, it’s nice you bring up strawmen after you called me a hobgoblin (a delicious word btw). I hope this _divine_ irony is not lost on someone like yourself.
> I learned that my style is somehow "bad."
That’s what you are missing: your style is not bad at all. It’s just your style. Use it for yourself!
Teams work on shared ground. Automatic formatting is just part of that shared ground.
It’s fine to be Howard Roark on a commission or pet project by yourself or a small team, but not when working on a subdivision with many hands touching it
You may do as you like in your projects. You might even find you can make a formatter enforce that rule which is apparently important to you.
But one thing I bring to every team is this: “I don’t want to look at a file and say that is Tom’s code or that is your code. What I want to see is [project name] code.”
You want to be a cog wheel, in a company of cog wheels. There's nothing wrong with that. But I want to be an artist, and preferably in a company of artists, having been in a company of cog wheels too many times. Not only it's more fun that way, but also tends to lead to better outcomes.
Wowsers. As what I think is a fellow traveler, I would encourage you to look back at all the places you created art and ask how many friends you made at each stop.
That's an uncharitable take and I urge you to reconsider. I also take great joy and pride in the art of writing beautiful code. I have no desire to be a cog in a machine. However, my ADHD brain is delighted to not have to make a million little distracting decisions in place of the code planning I should be doing. Formatters let me concentrate on writing the best data structures and APIs and algorithms instead of how many spaces to put before comment markers or while kind of quote mark is more aesthetically pleasing in this particular file.
I, too, want to be an artist. For me, the flow of data through my work is the artistry, not the typography of the code.
Yep. So far I have found every auto-formatter tool to be hopelessly naive in its workings. No, sorry, I do not want my logger call to stretch over 5 lines of screen real estate, just because the silly tool doesn't allow to distinguish between logger calls and other stuff, and a log message can be > 80 characters. They often make code less readable than before. I am still traumatized from adding trailing comma to every multi-line call, due to black being "opinionated" and always moving things back to one line, making them less readable, if they are not longer than some limit. It's silly.
Oh man, list comprehensions, that's me. I get inspired, caught up in the flow, write some eldritch horror of a LC. 30 seconds later "What in the name of saint Lovecraft does this even do?" (sighs) Rewrite as a loop. I mean, I guess they are fine in small doses, very small doses, But boy do they read poorly compared to the rest of Python.
I also need something to save me from my regexps, the verbose flag helps a lot. but what would be really nice is some sort of python subset that could compile to the state machine. I would also like composable expressions, that is, composable at the graph level not the string composition we are forced to use. And a pony, I would also like a pony.
As you can tell I am not a fan of APL, not because of what it can do but because of the super compressed syntax it adopts. see also: perl
The other commenters seem to be commenting on style consistency but ignoring your example. Your original format is far easier to read and understand plus the meaning of the comment is lost. IMO you're completely right about this example. Single line dict definitions shouldn't exist. Dicts are far easier to understand and manage when each key/value pair is on its own line, regardless of length.
Thank you, but I'm afraid I disagree. Single line dict definitions are fine when that's my intent, multilines are also fine when that is, and I trust myself to know when to use one or the other. My problem is with arbitrary rules like "only x-line dict definitions are allowed," for any value of x.
Yeah, you've ruffled the feathers of people who've been conditioned to strive for uniformity through conformity. Alas you're tilting at windmills in this age of automatic code generation where people look at the general shape of the code and check its intent by running it.
Nobody even addressed the other issue you expressed about uncomprehensible code being fine for linters as long as it conforms to the style guide or low-hanging fruit of "don't use this deprecated method". Who knows, maybe soon enough someone will make a dev tool which runs per file, acknowledges your clever little piece of code and then yells at you that nowadays programming is a team sport called software engineering.
Personally it saddens me that I must agree with the other commenters even if I see where you're coming from. The mass produced, assembly-line with snazzy pipelines took over from the workshop in a wood shed style approach and code linters among other dev tools slot in much better in the former type of workflows than the latter. On one hand I want to be trusted to format my own code according to my intentions but it seems the majority has decided that this is a waste of time, needless bikeshedding and so on. I've begrudgingly gave in for the sake of collaboration which is more important to me in the grand scheme of things.
I guess the upside remains we're still free to do as we please on our own projects. Now to find similar thinking people huddling around Sublime Text shunning linters is not gonna be a cakewalk these days.
I'm surprised a formatter does that. Prettier in the JS world likes to jam destructured arguments onto one line, but it will leave them alone if there's a comment.
Yes sure, but the point of something like ruff is that you don't even think about formatting anymore. It is just done for you and it is done consistently.
It's funny to see this comment about Python, which was designed to be a style North Korea. Maybe in a decade we'll see a Go linter and have a good laugh at that.
The "bad" code is actually bad imho. The comment explains intent inside the object rather than on the spec, which makes it easy to miss and harder to discover later.
This seems like a case where the tooling is compensating for unclear conventions. Collaboration tools are useful, but it may be more effective to strengthen the code specifications and documentation practices first, so intent lives in a predictable place instead of being scattered through implementation details.
I wish Go had something like Ruff! A lot of languages are getting amazing tool but Go has a very fragmented ecosystem with a lot of tools but none of them feel as high quality has the likes of Ruff, Oxc, Biome or even PHP's Mago
It is fairly new so not well known but it is what powers go fix and go vet under the hood. And I believe the Go team is currently working on making it possible for module authors to easily describe their own custom analysis passes that would run automatically when running go fix.
It is extremely easy to define your own analysis.Analyzer struct that describes your own static analysis pass. You get access to all sorts of useful information such as the AST, types, even SSA info and you can even compose the information between analyzers. Then you can easily compile it into a binary and run it by passing that binary to go fix with a command line flag. The go toolchain itself handles all the complex caching logic so that your analyzers run fast.
Since it is made by the go team itself and part of the toolchain it should slowly become the unified standard you are looking for. So hopefully golangci-lint and others should eventually all unify under this framework.
You can easily give it a go by telling some AI agent to write some Go Analysis analyzers and telling them to drive them with go fix. My Go projects tend to accumulate a bunch of these to enforce all sorts of rules deterministically and automatically instead of some imprecise markdown file.
That sounds amazing! I do hope golangci-lint and the likes pick it up, I'm a big fan of the strict by default approach that Ruff and other linters have taken.
Gofmt is great but it's still not very strict, the amount of times my colleagues have argued about formatting in Go is still too great, stuff like consts, types, funcs, methods order, struct initialziation newlines and other details that do matter, but they should be decided one and then applied everywhere like that.
On problem of such go linters is they can only lint what the compiler sees so if you have multiple `build` tags, you need to run multiple lint passes. No other language's linter operates like they, they all operate on file level.
Yeah, that's how much Astral changed python. UV and Ruff are amazing tools.
Go can get there, but like I said, I think everyone is building some partial tools that solve some problems, but there is not that one player building that one great tool.
I’m not entirely sure what you mean by a fragmented ecosystem? Go has first-party formatting and linting and the language is deliberately restricted to ensure it is written in a certain way, even when written by complete novices.
Compare that to Python or TypeScript which are Wild West languages without opinionated first-party tooling, and it’s clear why Ruff/Biome feel great.
Go has a good start for that, but It's not enough for me.
Gofmt is a great idea but it isn't strict enough for my taste. Go does not have any first party static analyzers, closest is Staticcheck which is even sponsored by Google themselves
I don't believe you can add custom analyzers to `go vet`, which arguably rules it out.
But it is extremely simple to make a custom vet-like tool, because it's a trivial binary wrapper around the analysis framework, which is first-party (though it's structurally pretty limited by design, and won't handle all linting needs): https://cs.opensource.google/go/go/+/refs/tags/go1.26.5:src/...
What? I feel go has one of the best language tooling out there without requiring a bully ide to do stuff. golangci-lint is quite comprehensive. What do you miss otherwise?
(Honestly go's distributing itself has covered a lot of it)
golangci-lint is good, but you have to glue together a bunch of linters, and it adds up. You can easily get runtimes of several minutes, there are caches etc but the main problem is that golangci-lint doesn't own the lints, so it will never be as fast as something like Ruff.
I've been using golangci-lint for years, it's okay but it's slow even with a cache, as soon as you have good amount of lints enabled your ci can suddendly take minutes.
That's not the fault of golangci-lint, it's just the concept of bringing together a bunch of different linters and keeping it fast is impossible. And the configuration is a bit wild too, because every linter obviously has their own decisions on how it wants to be configured.
Maybe it's just me, but getting overrun with "413 x potential warnings" hardly sounds useful :) Great for greenfield, less so for existing projects. Although nowadays, fire up your agent with "/goal work through and address all lint warnings by fixing the code according to XYZ" and leave it alone for a couple of hours and I guess it's no longer an issue...
Don't get me wrong, the new level of details coming from Ruff is much appreciated and a good thing :)
On the 0.16.0 release it was more like 10% autofixable on the codebases I maintain. One of them had 100+ unfixable errors across less than 10k lines of code and tests in total. Previous ruff version labeled it perfectly clean on format and check with default settings.
Good thing we have LLM:s that can do the tedious work of checking each case and applying local annotations or updating the config globally! If I was stuck doing this manually, I would have just gotten rid of ruff and went back to black+pylint.
I don't think they were trying to say "No, ruff doesn't work at all", more that "gets you 90% there" might have been an over-exaggeration, or very codebase-specific number and really, YMMV.
I really wish Ruff would introduce something similar to Nix’s stateVersion, which is used to determine the set of defaults that will be applied. Updating Ruff at a scale beyond a single repo is a bit of a crapshoot currently, with every version introducing a bunch of new default rules which you then have to deal with immediately (either by turning them off or fixing them).
I’m aware we could have an allowlist in place which specifies all rules that are turned on but I’d much rather have a simpler config file, and be able to bump the state version at a good time for everyone to spend a few hours fixing new violations.
I think the consensus is that this would be the wrong end to tackle the problem from. Your project should include ruff at the version it wants to use in its pyproject.toml. Bump the version when you're ready to spend the time on one project, but no need to coordinate across multiple projects. If you really want to do it, you can, but if there's one hold-out, you're not stuck behind forever because one project can't move yet.
This is great news! With the advent of agentic coding, strong linting is more important than ever. What I'd really like to see is forbidigo for more languages.
100% agree, I am upgrading my projects now. And yet, I have mixed feelings about this. I like to think that when I (was) writing code myself, I applied a great deal of intuition to decide when to skip or ignore some rule. On the hand, I also saw projects were developers where using pylint with most rules enabled and I have to say the code was not better - on the contrary it had plenty of hacks to make pylint happy at the cost of illegible code.
Not unlike that experience, I also saw coding agents spend lots of tokens trying to fix a benign issue, as well as doing the opposite - like disabling the tests !!! when they don't pass. :facepalm:
I grew to trust AI results in terms of overall correctness, but I still have hard time trusting their *judgement* on the *code quality*.
Default on lint is good for agent fleets. fwiw, I watched one agent "fix" an unfamiliar rule by deleting the test; perfect compliance, tiny misunderstanding.
You know how many bugs I've met working on large Python codebases over 20 years, caused by unsorted imports or `except Exception`? Yeah, you've guessed it: exactly 0 bugs.
It's really annoying how many people think that if you enforce stupidly strict rules about formatting, more strict that those of Fortran in 70s, you'll automatically get good code.
I've seen companies that enable 100% of ruff rules, use several other linters, and enforce other rules (like every variable name should be at least 20 characters long to fully describe its purpose). And they have awful buggy unmaintainable code. But it is nicely formatted (although reading code where half of the screen estate is consumed by the variable name is a bit difficult) and has no "dreadful" `except Exception`. Or every mutable class attribute variable is annotated with `typing.ClassVar` (RUF012 - the most idiotic rule I've ever seen). Nevermind that it doesn't really stop anyone from changing value of that attribute. You must annotate it with `ClassVar`!
I think it should depend on if it's `except Exception` (don't use) or `except Exception as e` (probably fine, as long as you log `e` or do something else with it).
I sorta kinda get why `ty` is pre v1.0.0 -- it's a typechecker that doesn't check a huge number of types. But what are we waiting for with `ruff`? Surely it's eaten whatever the old options were (black? maybe a few takes on py+lint?) by now many times over, and is even more dominant than `uv`.
I run this program hundreds of times a day so I'm generally excited for new features, but I guess the OAI acquisition has made me ornery when it comes to these folks. Apologies to kindly nerds who made this release happen, and congrats <3
Yes but the question is why Ruff has not released v1 yet. They’re clearly used in production and the entire purpose of v0.x is to signal a project is early in development and is expected to have coarse edges.
It's the same for JavaScript. Rust is the better language (especially for large projects like these), but sometimes you don't have a choice but to use Python or JS/TS.
Broadly you have a point. In this case I would expect a Rust-based Python linter to run significantly faster than one writen in Python itself (e.g., pylint).
ruff is not new. It's also truly really fast. On an as-yet un-ruff'ed 32k SLOC python package it returns in 158ms finding 2k errors. Having it fix errors is also very fast.
What's really tiring is reading such facile "critiques", especially when they are not even applicable.
I updated a ~3k line Python project with this the other day. I was using v0.15.x beforehand. It didn't take too long and the new rules do improve code quality. It caught quite a few things previous versions didn't.
Here's a few commits of changes:
(A whole bunch of manual changes based on its suggestions) https://github.com/nickjj/plutus/commit/9af66d31f98bef841588...
(Re-enable line length) https://github.com/nickjj/plutus/commit/21789f89bbcee37913c1...
(Force _ prefix for unused variables) https://github.com/nickjj/plutus/commit/a272c77b932e1c78558a...
(Auto-corrected by Ruff) https://github.com/nickjj/plutus/commit/6fe69cf88385ebdf9b8c...
Great to see ruff, ty and uv being actively developed, even after Astral was acquired by OpenAI.
Tbh I was super enthusiastic about Ty but it’s just so far behind basedpyright I had to switch off it. The lack of checks wasn’t the issue, it was the false positives which were the real dealbreaker. That and it doesn’t have support for baselining, which is such an insanely awesome feature for larger codebases. uv and ruff are incredible though. Maybe someday on Ty.
The amount of fascination that people have with these "grammar nazi" bots -- some of them implementing completely arbitrary "rules" and some of them disagreeing with others on what "good" Python code should look like -- doesn't stop to amaze me.
Here's "bad" code:
Here's what "good" code should look like: Which completely misses the writer's intent. But did you notice that there are two spaces before the pound now? Also, the quotes are now double because apparently it's somehow more kosher! So much improvement!The actual problems in the code I work with are not the spaces at the end of line or imports in non-alphabetic order, it's the 10-line list comprehensions that are so long that they're impossible for me to parse. None of these tools catch that. My place used pylint, flake8, black, ruff -- with hundreds of commits on every change. All that energy could be better spent elsewhere.
The point of the "grammar nazi" bots is to focus on the actual problems: if a bot is deciding about linting, you don't have to waste brain power to discuss it in PRs.
It is what it is, everyone gets the same, shut up and work on what matters.
I'm surprised you consider it a lot of energy spent, I tend not to spend any on this, it just runs automatically on my code and I drop out of pretty much every discussions about linting as it's good enough with automated tools.
I also used to work in places where there was no such tools, and there I had to actually spend time discussing and thinking about linting.
In my previous place, discussions on coding style were forbidden in PRs. It worked just fine.
Edit: one could also use single or double quotes in strings, and it didn't anger the grammar nazi bots as there were none.
Which gets rid of the discussion, but not the problem coding style rules are supposed to fix: Code looks the same, regardless of who wrote it. That's the whole point of code style guidelines like that as there's no functional reason for them.
That's why I like Go, every piece of code looks the same, there's one default enforced linter and this discussion (or discussion if discussion should be allowed or prohibited) doesn't even cross anyones mind.
Waiting patiently for the other person to produce a post hoc rationalization about why everybody’s code looking different is in fact a good thing
My argument is more to the tune of "everybody’s code looking slightly different is not a problem in practice as long as I can read and understand it."
However since you've asked so nicely here you go: everybody’s code looking different is because all humans are different. It's what makes us human. I am very serious about my craftsmanship, and I bring my "humanity" to it: sometimes I include a cultural reference (as in the example above), or an internal joke in the name of a (very long) variable, or vent my frustration in a comment.
My first ten years of writing Python were uneventful; nobody commented about my style and I never commented about others'. With the advent of grammar nazi bots, everyone is supposed to now please them by writing completely bland code, which in my opinion degrades me from a craftsman to a code-monkey. This is dehumanising, in a certain sense.
But your choice of funny variable names is not a craft, and you’re not a craftsman if you think it matters. What matters is extensibility, maintainability and value delivered.
I care how the food tastes, not that the chef has a really cool Japanese knife and is really fast at cutting onions.
I need a kitchen linter to highlight dishes red that my kids leave around rather than putting in the sink or dishwasher
I think you miss the point, but using your foodie reference: would you rather go to a couple of Michelin restaurants -- where each piece brings a reflection of the chef, the geographic area, and what quality ingredients were available on that day -- or would you rather only eat at McDonald's for an experience that is extremely consistent across days, seasons, and continents?
Now imagine a chef who has a nice little restaurant but is now being sent a couple of "quality assurance" guys from McDonald's who tell him that his choice of potato variety for chips does not exactly conform to the "standards" defined at the mothership.
A chef in a michelin restaurant, unless he/she is the sole employee (very rare) is likely in charge of a team of line cooks and while they may get some time and place to experiment you better believe it will not be when preparing the chefs carefully curated menu and recipes for paying customers who have been on a reservation waitlist for months. Bob decided to cook the broccoli with a blowtorch and substituted some ingredients in table 9s dinner. There is a fire in the kitchen and the NYT food critic is going into anaphylactic shock but Bob at least feels like an artist so we will let it slide.
The one day, your chef leaves, the food goes to shit and productivity/output/morale drops. When the new chef arrives, then everyone has to relearn everything.
Software shouldn't be bespoke and "artisan".
And here's where this abstraction breaks: when a programmer leaves, their code is not going anywhere.
The bigger problem is this: you want me to be a cog wheel, but I want to be an artist.
I too want to be an artist, but I work in an economy that requires me to be an artist mostly in my off hours.
And honestly, this is such a weird hill to die on. Lint rules when automated are basically pure win in most scenarios, and for your example, just exclude the rule for this block.
> The bigger problem is this: you want me to be a cog wheel, but I want to be an artist.
Well, that's the ball game then. This isn't the industry to be an artist in, especially post-AI.
I think the artisanal code industry will exist. It will not be large and it will not pay well.
Take it to your employer then, not to us.
We want maintainable code we can quickly onboard in, and be able to modify and extend. Your artistry is getting in the way of other people getting stuff done.
Then go be an artist and leave the rest of us to tend our well oiled machines.
The hidden implication in your extension of the foodie analogy is that consistent food must have worse quality.
That's not true, and if anything in software having consistent software delivery makes it more likely to achieve Michelin-graded level of quality, not less.
But the formatting is not all that "delivery" stands for.
I've worked on great codebases that would produce thousands of linter errors (lowball estimation), and on shitty codebases that were 100% compliant.
I'd go so far as to argue that person to person differences are helpful to a reader. When I am dealing with a long term code base it's easy to develop a feel for who is responsible for different constructs without needing to rely on the git history. This gives me immediate information on what to expect.
Can you not read code if it uses single vs double quotes?
I can - in fact, I have to, a lot more than I'd like - when someone makes changes with different code style settings in their IDE or tooling, and that affects unrelated parts of the code, and suddenly the PR contains tons of code that just looks subtly different but still does the same thing.
If sieving the spam from ham in code reviews is your thing, go have fun.
I personally prefer code that is automatically and unconditionally kept in the exact same shape, preserving only the intended changes to stop the team from wasting time on formatting or reviewing.
That seems like a problem caused by an autoformatted, not solved by one. Obviously having reviewable commits is a reasonable expectation but that doesn't seem like a justification at all.
Pretty much all tools developers use to write code format code to some extent while editing, but often with individual configuration knobs or default settings that are different between all ICs.
Having a single tool that runs before committing, ideally as a git hook, ensures a single, uniform style across all files touched.
You are not making the strongest point by using a relatively subjective rule like that. It includes indentation, how to structure functions and the parameters and many more that in sum make it very easy to jump between projects (Internal company projects, dependencies, other open source projects) without ever getting used to a new style. This makes reading and contributing very easy.
Compare that with other languages where you need to load a different set of prettier rules, code formatting tools and follow contribution guidelines on how things should look like depending on the opinion of the maintainers.
I've never found this to be the case having jumped companies a fair bit. I can switch programming languages easily enough, surely I can handle formatting.
That's exactly what a good cogwheel in the machine would say
The value isn't just in the lack of discussion, but in amplifying the signal to noise ratio of diffs. with ruff or gofmt or whatever, it’s pretty much guaranteed that a change of code is an intentional part of the proposed change. with multiple authors with variable code styles touching the same project, there is a much higher chance of a diff containing lines that preserve semantics but change the code.
>Which gets rid of the discussion, but not the problem coding style rules are supposed to fix: Code looks the same, regardless of who wrote it.
Which might be more overrated OCD than anything worth it. If you like that, you can add a formatter at the end of the chain or even just when reviewing.
There's a ton of variation in golang code, as gofmt is not opinionated enough. As evidenced by the existence of golines[0] or gofumpt[1].
0: https://github.com/golangci/golines/
1: https://github.com/mvdan/gofumpt
Is that really a problem that needs solving though? The easiest way to not spend time bikeshedding is to just not bikeshed. Don't make guidelines. Don't run tools to check them. Don't comment on them in PRs. None of it matters.
Historically, yes. At some point, one person who likes to work with narrow terminal windows gets fed up with long lines and starts reformatting stuff as they go. Another person with a widescreen editor hates looking at code clustered around the left edge of the screen gets fed up and starts reformatting stuff as they go. It's just a mess. You end up with PRs where it's hard to see the 3 things that changed because it contains 200 lines of "my IDE would prefer to format things this way instead".
I have never seen this not happen, barring using autoformatters. I think Go and Rust have had great success avoiding all these dumb arguments by having built-in opinionated formatters from nearly the beginning.
Aren't you supporting the GP's point though? If you auto-format all the things, they will likely not be right for people either, neither for the narrow terminal window, not for the widescreen full size window enjoyer. And there was energy spent on making it so, without it being a clear winner. Instead we could just be respectful and leave such things as they are, and not have to discuss this at all.
No. Auto-formatters have a way of getting people out of the mindset of arguing about it and just accepting that's the way it is. It's not that different from, say, using a compiled language instead of assembler. You and I might have differing ideas about how to write the best assembly code. If we standardize on C, it's likely neither of us will love the compiler's output, but short of outright bugs we'd move past that and go back to writing code.
> That's why I like Go, every piece of code looks the same, there's one default enforced linter and this discussion (or discussion if discussion should be allowed or prohibited) doesn't even cross anyones mind
I remember people saying this exact same thing about Python ~20 years ago.
Using a consistent string delimiter has value: if you search for ['foo'] you will find all instances of the string foo. With inconsistent delimiters, you better have a single canonical 'foo' in your project or you're going to run into problems.
When a string contains strange characters, how do you enforce the single consistent delimiter?
You probably haven't written diverse enough code.
See if you know where this leads before clicking on it: https://xkcd.com/208/
So your argument is that your freedom to use whatever string delimiter you want (remember that ''', """, ` and even weird unicode glyphs are valid in many languages) is worth forcing other engineers on the team to know all valid string delimiters and remember to use the right regex to account for all possible weirdo choices?
My argument is that searching for \bfoo\b will produce all the results you want.
Python was designed from the start so that 'foo' and "foo" are equal. It also worked like that for 30 years or so. This has not been an issue in these 30 years. But then someone came with an opinion that one of them is better than the other.
\bfoo\b will produce noisy results. People have been pushing linting rules that force a single consistent choice of string delimiters since at least 2010 when Python went from a cute academic language to a real industry workhorse.
> \bfoo\b will produce noisy results.
Add a look-behind for '[' and a look-ahead for ']' and you'll be fine.
I would hate to work on that codebase. We learn to parse code more quickly over years of looking at code written with the same conventions and style. There’s a reason why Google are so serious about following their style guides.
Even better: if someone feels like the syntax must be different, they can pick it up at a centralized repository with more people that specialize in syntax.
Afterwards it will affect production code everywhere on the planet. :)
This is the way, and why I wrote https://pypi.org/project/ssort/. Unfortunately you need to follow it up with shameless self-promotion or it doesn't work.
>The point of the "grammar nazi" bots is to focus on the actual problems: if a bot is deciding about linting, you don't have to waste brain power to discuss it in PRs.
And yet, as per the example, the bot force worse linting decisions, and even pushes towards more bugs (e.g. future readers missing the comment intention since it now applies to all 3).
the claim we are arguing here is not that “linters never make mistakes” it is that “linters are generally beneficial and avoid mostly useless debates we previously spent an inordinate amount of time on” Most linters have an escape hatch for exactly this which is usually a one line disable comment
There is no point discussing these minutia, ever. Just ban discussing them in PRs and then you don't need an opinionated bot rife with false positives to "fix" them.
And yet, a new argument surfaces about whether or not the linking should take place. Some people just like to argue :)
I've never had discussions about code formatting in PRs, this seems like a made up problem or something that predates my career.
It's not by accident that tabs vs spaces is one of the stereotypical great debates in programming.
I've never seen this debate take place outside of internet forums, almost always as a joke. It's been about 15 years since I even remember it coming up much as a joke, frankly.
Yes, because the battles have been fought and spaces won
Okay, so if the battles are won why do I have to care? What is the point of having so many strict, enforced style rules if your exemplar has already been long dealt with?
One war was won bro, there are many more.
Pretend they don’t exist as much as you’d like, tell everyone their lived experience is wrong, that the tools arose from delusion and not any real world need, and that they benefit no one.
I’ll keep using them though, and never suffer from someone writing typescript with 4 spaces, leading commas, and no semicolons again.
I'm not saying that lived experiences are wrong, I'm saying that I don't believe that these problems would exist under the current software engineering culture to the degree that people seem to insist upon.
> something that predates my career
That's because everybody uses linters nowadays, which was the original point.
I'm not convinced.
These used to happen all the time. In one (otherwise great) programming course in university you would even get points subtracted if you did not follow the lecturer's preferred style.
(I think that in their case it was partially because uniformity made correcting faster.)
At work? School isn't really representative.
Oh, absolutely. I started writing Python professionally in 2010. Whenever a new group of people started working on a Python project, one of the first things they had to decide was tabs versus spaces. Everyone would begin by saying that the question was unimportant and silly, but then immediately add a “but” followed by a detailed justification of their personal preference. That would lead to people spending two full days exchanging arguments and links to blog posts.
Worse, sometimes you would start working on a project without having that discussion first, only to discover a few days later that the code was failing because different people had different indentation settings in their IDEs, both regarding tabs versus spaces and the number of spaces. You would then have to pause the work, go back, have the discussion, and decide who was going to fix all the code committed so far.
This only stopped when linters became popular. They may be a little like vaccines: when they work, you do not see them working, so it is easy to forget what things were like before. But believe me, when they appeared, they were a breath of fresh air. We could finally focus on discussing the work rather than the conventions.
And tabs versus spaces is only one example. There used to be PR discussions about countless style issues throughout the entire lifetime of a project. That was not entirely unreasonable, because a consistent style genuinely makes a project easier to work on. But when every individual convention has to be negotiated by humans, it takes enormous amounts of energy and becomes tedious very quickly.
> only to discover a few days later that the code was failing because different people had different indentation settings in their IDEs, both regarding tabs versus spaces and the number of spaces.
yeah, why Guido got away with the "we don't need deliminators, white space is just fine" for so long has always baffled me.
Because somehow a special character that marks the start and end of a block is hard for people to understand??
I think that tabs vs spaces is a rather extreme example because it has unexpected parser semantics - that is, it's not a style choice. So I don't think it's actually relevant to a conversation about conventions/ style.
It definitely is relevant to a discussion about linters. And as I said on my comment - it's just an example, I mentioned other types of discussions as well.
Yes, also at workplaces I saw these discussions in the past. These discussions almost completely disappeared when formatters became popular.
Silicon Valley (the TV show) memed about it with its tabs vs spaces bit. It used to be a thing for sure. It has been a good 10 or 15 years since I had such a discussion. Automated linting and formatting tools largely killed it in my experience.
I think the culture has just shifted. I haven't even sniffed a whiff these conversations since roughly 15 years ago. I've never, ever seen them at work.
I also never have these conversations at work…because every repo uses a linter and the engineers are mature enough to recognize most of these debates as bikeshedding. Every once in a while someone will suggest turning on or enabling a new custom rule with some justification and there is a brief discussion and it is enabled or not. Outside of work in a consulting gig I didn’t have a linter in the codebase and the owner brought on a new engineer, he wrote if else statements all on one line no matter how long they were and removed spaces everywhere because he thought shorter code was better. He balked hard at any feedback about code style and I was told I couldn’t enforce code style or install a linter. I think you will surprised how much you care if someone with a truly weird style starts putting it in your codebase, I always told myself I didn’t before this. After more dumb waste of time clashes like that I quit the project. Now even personal projects get a linter :).
> this seems like a made up problem or something that predates my career
I can believe the latter, and I can assure you this was a Big Deal back in the day. There's a reason Go ships with gofmt, and gofmt was somewhat revolutionary for being a built-in "you gotta do it like this" back in the day.
What is "back in the day" here? I've been in this field for like 16 years.
Others may remember it differently, but I feel like things started to change somewhere between 2009 to 2012? People were still hand-indenting code back then, and your personal style felt like something you had a bit of ownership of or was a creative outlet for you. I think we are in a much better place now where the editor + a central config file owns the whole thing, and I'd be surprised to see a modern codebase where a formatter's defaults weren't being used.
Dunno, now the workflow is like agent makes code changes, ruff complains, agent fixes complaints at the cost of code bloat, agent makes a PR, another agent reviews the PR, agent makes changes, PR is approved and merged. Nobody reads PRs anymore with the agentic development velocity.
> Nobody reads PRs anymore with the agentic development velocity.
Tokens are burnt, that's the most important part. And with more of the generated shitty code there will be more tokens spent in the future.
FWIW, ruff sees your line comment and keeps each item on its line. It only adds a trailing comma and additional space.
It also does not collapse lines when there’s a trailing comma.
Your output seems to be from black. IMO it’s insane to collapse lines when there are line comments.
(I intentionally switched to double quotes since that really is a stylistic choice in Python, you can escape in both, and if you use double quotes inside of single quotes ruff leaves it as-is)(am not the GP)
I personally like double quotes, because in so many other languages they are for strings, while single quotes are often for other things. But somehow many people have a single quotes obsession in Python. I am guessing, that it is because of ease of typing them on a US keyboard layout.
> many people have a single quotes obsession
Not my problem. My problem is that I want to use them both, as I've been able to for many years.
I'll always use double to surround "can't" and single to surround '8.5" x 14"'. I'll use a context-appropriate pair when adding to existing code. But for all the other cases, I don't want to expend any energy on this. It's a complete non-issue.
> ease of typing them on a US keyboard layout
On both the US and European keyboards single-quotes are easier: no need to press Shift.
I've read through all the sub-comments you've left here.
In short, you seem to have a grievance against linters that goes way past reasonable. You assert this example you give is "perfect" below. You also complain linters waste time and commits despite able evidence in the field they do the opposite.
You seem willing to add drama, grievance, and personal bias to what could have been a rather straightforward technical discussion. The sum of which is: linters sometimes make changes to code that I don't like.
I'd encourage you to reconsider what's really important when it comes to team development and try listening more to those around you. You seem overly focused on asserting your opinion instead of having a discussion.
These types of exchanges are usually very unproductive and stressful in a team environment. They are evidence, IMO, of a dev having the wrong priorities. I'd encourage you to step back and really consider how you view craftsmanship in a team dynamic and whether or not you are fighting the right battles.
There are some really bad linters out there like Rubocop so if they have had experience with those I get it.
A person just as opinionated as the tool. People who have very strong opinions about formatting in any direction are suspect.
I do not have any opinion on formatting. My argument is that nobody should.
your argument is about formatting and clearly you feel strongly about it.
this is the difference between the atheist who strongly believes nobody should believe and the atheist who isn't interested and doesn't care
Yeah, I'm in the "not interested" and "don't care" camp, with the addition of "please don't come at me with your bots who would tell me what to believe and what not to believe".
If someone wants to use a linter, I have no problem with that. I don't, and I don't want others forcing one on me, especially so as most of its "results" are straight in "this doesn't matter either way" category (in my example: two spaces instead of one, replacing quotes) and the rest make my code verifiably worse (folding a list in one line).
I think, generally speaking, the decision to enforce a linter in CI/CD pipelines is well within a project lead’s or technical director’s purview, and there seems to be a pretty broad consensus that they add value, so they’ll almost always be in CI/CD pipelines. So just use the same linter locally and format on save so you catch any issues like your comment getting folded example.
unwillingness to go with the flow without big arguments is what I'm talking about
My issue with your comment is not that it seems to offer unsolicited psychological advice completely out of the blue; it's that it leaves me with an uncanny feeling that it was written by an LLM.
In a thread where I complain about the de-humanisation of my profession by the bots, the irony of having a bot's psychological advice (and so, de-humanising the discussion too) is not lost on me.
If you are really concerned about my being a bot, a quick look at my comment history, going back over a decade, should dispel that concern.
Edit: I once had someone apply for a job and their written communication was pretty caustic. After rejecting their application, I wrote and explained how unprofessional I found their interactions, encouraging them to step back and reconsider how their communication appears from the other side.
To my great surprise, they wrote back and said they had never had anyone give them such feedback and they agreed with my assessment of their interactions. They just had never seen it before. They thanked me for the feedback.
Such interactions are exceedingly rare. But I sometimes offer unsolicited advice on the off chance that said advice can make a difference.
You neither usually write in that style, nor go full shrink mode on people you talk to, so your original comment here is quite unusual in both the contents and the style.
Peace, man.
It’s because you forgot a comma after the last item. If you had kept that the items wouldn’t have been compacted (at least in black, I’ve stopped linking my code because it breaks intends of formatting more often than it helps)
Sorry, but I did not "forg[e]t a comma after the last item". I intentionally did not put a comma there. The list of the "important numbers" in my example is already complete and perfect, and is not supposed to ever change. I wanted to emphasise this fact by specifically omitting the comma there, in the hope that intelligent people in the future would get this message exactly as I intended for it to be.
I think all of us have worked on code that other people thought was perfect or will never change. Personally I love this rule because it saves me from looking at one extra commit (adding the comma when the next value gets added) when I'm trying dig through line history.
The trailing comma rule is to avoid errors when devs copy/paste a line or dump a new line at the end. This matters particularly for arrays in languages with implicit string concatenation, where the missing comma won't trigger a compile error.
yes, and isn't it funny that
foo=[baz, bar,]
returns a nice list of two items, but if you edit this down and miss the comma,
foo=bar,
is not a scalar.
returning a tuple.
So the trailing comma thing is a thing.
You will find that pretty much every rule we have in any possible context will have some exceptions that are justified under some conditions.
Taking that as a reason to not have those rules in the first place seems like a logical fallacy that intelligent people should not make.
Must be nice to write code with the hopes that only intelligent people will read it.
Those tools actually save team energy. Without them any programmer has different opinion on formating, code quality, what is readable etc. You can discuss it endlesly or you can just use ruff.
We can replace the endless and useless discussion on how to format code with an endless and useless discussion on what linting tool to use.
Another option would be to leave both topics alone and go on with our lives, improving the product, fixing bugs, implementing new features, and generally giving customers a better product and shareholders more value, while respectfully agreeing to disagree on the issues of style.
People tend to use tools set up in CICD and/or precommit. Without rules everyone uses their tool and is unwilling to compromise. Even if they are willing it is hard to setup tools to do exactly what you want. Default configuration is always the way.
So to the practical question: what we're gonna do today in CICD and/or precommit? "Default configuration is always the way" but which one: `ruff<0.16.0` or `ruff=0.16.0`?
I would stick to older ruff until team has resources to fix issues and move to newer version. The same think you do with other dependencies.
> We can replace the endless and useless discussion on how to format code with an endless and useless discussion on what linting tool to use.
That is why gofmt is so great. As the saying goes: gofmt’s style is nobody’s favorite, yet gofmt is everybody’s favorite.
Gods forbid I talk to people I work with, learn their preferences, and figure out the way we can accommodate each other. Sounds like such a waste of energy. It’s so much better if we all acquiesce to the preferences of some people neither of works with!
How does that work when hundreds of people are working on a project, with a few new people joining every week?
It boggles me that anyone would argue against standardization, or believe that “just get hundreds of people to agree today and tomorrow and every future day” is a better solution.
You can still talk to them about more important problems than placement of collons and closing bracket.
I have never discussed code formatting and drive-by changes more than in projects with auto formatters.
Why though? I've yet to come across an auto formatter that formats things the way I personally like. But that's fine. I do whatever the hell I want until I'm ready to open a PR, then bang it through the auto formatter. And you know what? I can also go the other direction and run a home-brew auto formatter to reshape things the way I personally want on my local machine.
I'm not sure why, but apparently my experience is unwelcome enough to be downvoted. Drive by changes are really annoying, IMO, and as the GP shows, the formatting tool outcome differs. So there are many "valid" versions of the same program logic. You have therefore not do what these formatters pretend to do, namely take this variable out of the equation. Rather, the wrong auto format can look much worse than the original.
I'm not sure why people felt formatters were necessary. but now they're part of the zeitgeist and there is no going back.
This seems like a strange hill to die on. Linters don’t consume energy, that’s the entire point. They get everyone on the same standard so that no energy is wasted by anyone on having to discuss, debate, and implement these standards. And yes, there are instances where someone’s non-standard coding style is not a problem. But there are instances where it absolutely can be, and these tools help there.
I’m also not sure I understand the “ My place used pylint, flake8, black, ruff -- with hundreds of commits on every change”. These tools don’t add extra commits. You run them prior to your PR, get them aligned with the linting, and then commit the change you were already going to make. Thats 0 extra commits.
For someone ultimately arguing that there is too much effort spent on people’s coding styles, you are spending a lot of effort arguing about people’s coding styles. These tools are some of the most set it and forget it things around.
> These tools don’t add extra commits.
When you replace tool x with tool y, you need to fix your code, because these tools do not agree on what your code should look like, and what was acceptable for tool x simply isn't for tool y.
One of these changes I mentioned brought a massive reformatting of all code base, as the new tool failed 'import' statements unless they were in alphabetical order.
If you change the formatter for your project then yes, you have a single commit where you reformat your project and then commit this.
A single commit after a tool change. I don't see the problem.
Sounds like the issue is not the tools themselves, but that the project management / stewardship allows someone to replace tools willy-nilly leaving others to pay the cost of migration?
At $WORK we have "code guardians" whose responsibility is do to this kind of migrations strategically in the background. The intent is that most other developers should not need to do any (or very little) extra work beyond the task they are actively working on.
The linked article says that ruff now has 413 default rules -- instead of 59 previously. Do I understand it right that all the projects that were using ruff were 100% conformant to 59 rules yesterday but likely not conformant to 413 rules today, which will cause breakage all across the board? Likely immediately followed by a swarm of pull requests all formatting their code in conformance to whatever these new (413-59) rules are?
There's no "tool replacement" going on here.
Well, that's the danger of using software without version pinning.
Also, usually tools like Ruff can be configured to enable/disable project-specific formatting rules (via a config file), allowing you to suppress the defaults you disagree with, or whose consequence you don't want to deal with _right now_. Some tools might even support suppressing specific rules per-file, so you can migrate codebase over time instead of in one go.
Having a config that explicitly defines the rules enforced in the project is really useful, and perhaps even crucial the bigger the project grows.
The options you suggest seem to be either (a) feed the bot now (halt development and reformat all code) or (b) under-pin/suppress and feed the bot later, probably dealing with people adding more non-conformant code to already-reformatted files.
You will probably have to do it all over again when these guys release even newer and shinier 0.17.0, with even more rules.
I can't understand how the option of not feeding the bot eludes people.
Stylistically ugly code that is globally consistent (ideally across the entire ecosystem) >> locally beautiful code that is inconsistent with the rest of the codebase / up to authors taste. No one likes black formatting, but it’s at least consistent. “Your car can be any color you like as long as it’s black”
With regards to your example, adding a comma to the final element should preserve multi-line. You just don’t know the formatting rules yet apparently
> The actual problems in the code I work with are not the spaces at the end of line or imports in non-alphabetic order, it's the 10-line list comprehensions that are so long that they're impossible for me to parse.
100%. Almost all of my time burned navigating code is not hung up on stylistic conventions but on nasty services with inconsistent abstractions and patterns.
BUT, conventions and consistency make code easier to read and write, period. If you’re debating over single or double quotes that’s almost a fireable offense IMO.
Additionally, when you have a culture that delegates to tools as much as possible, the focus sharpens in a healthy way.
> it's the 10-line list comprehensions
I feel for you both working with straw men day to day. I’ve worked mainly in Python for two decades and have neither seen such a thing nor considered, even to be a bastard, doing such a thing.
The actual rules are less important, only the consistency of applying them is.
I find that people who argue against automatic linting and formatting tend to be the same that would argue incessantly about style. So much wasted energy, I want none of that.
Also, in your example above, if you put the comment on the line _above_ instead of inline the formatter will most likely do the right thing.
"A foolish consistency is the hobgoblin of little minds, adored by little statesmen and philosophers and divines."
-- Ralph Waldo Emerson
TIL I am divine!
By the way, you have the exact energy of someone who would argue about code style: a prima donna who quotes Emerson when confronted with disagreement.
You can build yourself any strawman you want :)
In my first ten years of writing Python, I never argued about code style with anyone. It's only after they brought the bots in that I learned that my style is somehow "bad."
Well, it’s nice you bring up strawmen after you called me a hobgoblin (a delicious word btw). I hope this _divine_ irony is not lost on someone like yourself.
> I learned that my style is somehow "bad."
That’s what you are missing: your style is not bad at all. It’s just your style. Use it for yourself!
Teams work on shared ground. Automatic formatting is just part of that shared ground.
I’ve written it up-thread: you want me (and possibly, everybody) to be a cog wheel. But I want to be an artist.
It’s fine to be Howard Roark on a commission or pet project by yourself or a small team, but not when working on a subdivision with many hands touching it
That does work in private projects but not in team projects.
I don’t want you to be anything but a team player. Let’s work together and get it done.
Be an artist on your time, not mine.
You may do as you like in your projects. You might even find you can make a formatter enforce that rule which is apparently important to you.
But one thing I bring to every team is this: “I don’t want to look at a file and say that is Tom’s code or that is your code. What I want to see is [project name] code.”
You want to be a cog wheel, in a company of cog wheels. There's nothing wrong with that. But I want to be an artist, and preferably in a company of artists, having been in a company of cog wheels too many times. Not only it's more fun that way, but also tends to lead to better outcomes.
Wowsers. As what I think is a fellow traveler, I would encourage you to look back at all the places you created art and ask how many friends you made at each stop.
Please don't turn this site into Reddit.
That's an uncharitable take and I urge you to reconsider. I also take great joy and pride in the art of writing beautiful code. I have no desire to be a cog in a machine. However, my ADHD brain is delighted to not have to make a million little distracting decisions in place of the code planning I should be doing. Formatters let me concentrate on writing the best data structures and APIs and algorithms instead of how many spaces to put before comment markers or while kind of quote mark is more aesthetically pleasing in this particular file.
I, too, want to be an artist. For me, the flow of data through my work is the artistry, not the typography of the code.
The beauty of it is that all artists can be different. It's the cog wheels that need to be identical.
I genuinely think you're on the wrong track there. Someone can express their artistry different from your ways and still take pride in their artistry.
Ruff/black support and enforce this. Just do ‘'z': 2,’
If you add an extra dangling comma at the end of the last item, ruff/black will auto-format to the line-by-line style you like.
Yep. So far I have found every auto-formatter tool to be hopelessly naive in its workings. No, sorry, I do not want my logger call to stretch over 5 lines of screen real estate, just because the silly tool doesn't allow to distinguish between logger calls and other stuff, and a log message can be > 80 characters. They often make code less readable than before. I am still traumatized from adding trailing comma to every multi-line call, due to black being "opinionated" and always moving things back to one line, making them less readable, if they are not longer than some limit. It's silly.
Oh man, list comprehensions, that's me. I get inspired, caught up in the flow, write some eldritch horror of a LC. 30 seconds later "What in the name of saint Lovecraft does this even do?" (sighs) Rewrite as a loop. I mean, I guess they are fine in small doses, very small doses, But boy do they read poorly compared to the rest of Python.
I also need something to save me from my regexps, the verbose flag helps a lot. but what would be really nice is some sort of python subset that could compile to the state machine. I would also like composable expressions, that is, composable at the graph level not the string composition we are forced to use. And a pony, I would also like a pony.
As you can tell I am not a fan of APL, not because of what it can do but because of the super compressed syntax it adopts. see also: perl
Put a comma after "'z': 2" and it will work how you expect, fyi
The other commenters seem to be commenting on style consistency but ignoring your example. Your original format is far easier to read and understand plus the meaning of the comment is lost. IMO you're completely right about this example. Single line dict definitions shouldn't exist. Dicts are far easier to understand and manage when each key/value pair is on its own line, regardless of length.
IIRC, if you put a trailing comma on the last entry, the formatter will keep multiline dicts multiline.
Thank you, but I'm afraid I disagree. Single line dict definitions are fine when that's my intent, multilines are also fine when that is, and I trust myself to know when to use one or the other. My problem is with arbitrary rules like "only x-line dict definitions are allowed," for any value of x.
Yeah, you've ruffled the feathers of people who've been conditioned to strive for uniformity through conformity. Alas you're tilting at windmills in this age of automatic code generation where people look at the general shape of the code and check its intent by running it.
Nobody even addressed the other issue you expressed about uncomprehensible code being fine for linters as long as it conforms to the style guide or low-hanging fruit of "don't use this deprecated method". Who knows, maybe soon enough someone will make a dev tool which runs per file, acknowledges your clever little piece of code and then yells at you that nowadays programming is a team sport called software engineering.
Personally it saddens me that I must agree with the other commenters even if I see where you're coming from. The mass produced, assembly-line with snazzy pipelines took over from the workshop in a wood shed style approach and code linters among other dev tools slot in much better in the former type of workflows than the latter. On one hand I want to be trusted to format my own code according to my intentions but it seems the majority has decided that this is a waste of time, needless bikeshedding and so on. I've begrudgingly gave in for the sake of collaboration which is more important to me in the grand scheme of things.
I guess the upside remains we're still free to do as we please on our own projects. Now to find similar thinking people huddling around Sublime Text shunning linters is not gonna be a cakewalk these days.
I'm surprised a formatter does that. Prettier in the JS world likes to jam destructured arguments onto one line, but it will leave them alone if there's a comment.
Yes sure, but the point of something like ruff is that you don't even think about formatting anymore. It is just done for you and it is done consistently.
this is why i like golang these sorts of things just don't matter, go fmt move on with life.
I agree with you, but that is literally what ruff is meant to do, and what this person is arguing against.
Same for rustfmt.
I love gofmt, rustfmt, ruff.
It's funny to see this comment about Python, which was designed to be a style North Korea. Maybe in a decade we'll see a Go linter and have a good laugh at that.
The "bad" code is actually bad imho. The comment explains intent inside the object rather than on the spec, which makes it easy to miss and harder to discover later.
This seems like a case where the tooling is compensating for unclear conventions. Collaboration tools are useful, but it may be more effective to strengthen the code specifications and documentation practices first, so intent lives in a predictable place instead of being scattered through implementation details.
I wish Go had something like Ruff! A lot of languages are getting amazing tool but Go has a very fragmented ecosystem with a lot of tools but none of them feel as high quality has the likes of Ruff, Oxc, Biome or even PHP's Mago
It does and it is even better. It is called the Go Analysis Framework (https://pkg.go.dev/golang.org/x/tools/go/analysis).
It is fairly new so not well known but it is what powers go fix and go vet under the hood. And I believe the Go team is currently working on making it possible for module authors to easily describe their own custom analysis passes that would run automatically when running go fix.
It is extremely easy to define your own analysis.Analyzer struct that describes your own static analysis pass. You get access to all sorts of useful information such as the AST, types, even SSA info and you can even compose the information between analyzers. Then you can easily compile it into a binary and run it by passing that binary to go fix with a command line flag. The go toolchain itself handles all the complex caching logic so that your analyzers run fast.
Since it is made by the go team itself and part of the toolchain it should slowly become the unified standard you are looking for. So hopefully golangci-lint and others should eventually all unify under this framework.
You can easily give it a go by telling some AI agent to write some Go Analysis analyzers and telling them to drive them with go fix. My Go projects tend to accumulate a bunch of these to enforce all sorts of rules deterministically and automatically instead of some imprecise markdown file.
That sounds amazing! I do hope golangci-lint and the likes pick it up, I'm a big fan of the strict by default approach that Ruff and other linters have taken.
Gofmt is great but it's still not very strict, the amount of times my colleagues have argued about formatting in Go is still too great, stuff like consts, types, funcs, methods order, struct initialziation newlines and other details that do matter, but they should be decided one and then applied everywhere like that.
On problem of such go linters is they can only lint what the compiler sees so if you have multiple `build` tags, you need to run multiple lint passes. No other language's linter operates like they, they all operate on file level.
https://news.ycombinator.com/item?id=49057398
Let's spread the word
Oh wow, not so long ago the sentiment was exactly opposite. Python community was struggling with tools and everyone wished to have `gofmt` for Python.
Granted, this is a linter, not a formatter, but my larger point is I am glad that Python ecosystem evolved like it did recently.
Yeah, that's how much Astral changed python. UV and Ruff are amazing tools.
Go can get there, but like I said, I think everyone is building some partial tools that solve some problems, but there is not that one player building that one great tool.
Ruff is actually a formatter too
It's not really even that recent of a change. Black's been stable for the past 4+ years.
I’m not entirely sure what you mean by a fragmented ecosystem? Go has first-party formatting and linting and the language is deliberately restricted to ensure it is written in a certain way, even when written by complete novices.
Compare that to Python or TypeScript which are Wild West languages without opinionated first-party tooling, and it’s clear why Ruff/Biome feel great.
You just wouldn’t get that same high with Go.
Go has a good start for that, but It's not enough for me.
Gofmt is a great idea but it isn't strict enough for my taste. Go does not have any first party static analyzers, closest is Staticcheck which is even sponsored by Google themselves
go vet, plus all the ones in gopls?
I don't believe you can add custom analyzers to `go vet`, which arguably rules it out.
But it is extremely simple to make a custom vet-like tool, because it's a trivial binary wrapper around the analysis framework, which is first-party (though it's structurally pretty limited by design, and won't handle all linting needs): https://cs.opensource.google/go/go/+/refs/tags/go1.26.5:src/...
What? I feel go has one of the best language tooling out there without requiring a bully ide to do stuff. golangci-lint is quite comprehensive. What do you miss otherwise?
(Honestly go's distributing itself has covered a lot of it)
golangci-lint is good, but you have to glue together a bunch of linters, and it adds up. You can easily get runtimes of several minutes, there are caches etc but the main problem is that golangci-lint doesn't own the lints, so it will never be as fast as something like Ruff.
Golang-ci exists for a while and everyone is using it.
Not sure what you're talking about.
I've been using golangci-lint for years, it's okay but it's slow even with a cache, as soon as you have good amount of lints enabled your ci can suddendly take minutes.
That's not the fault of golangci-lint, it's just the concept of bringing together a bunch of different linters and keeping it fast is impossible. And the configuration is a bit wild too, because every linter obviously has their own decisions on how it wants to be configured.
Ruff can lint in seconds or even sub second.
It's like 100 times slower than ruff. They had to massive caching techniques to get it to a remotely acceptable speed.
Good news. enabling 413 rules by default means most projects get useful linting without touching the config at all!
Maybe it's just me, but getting overrun with "413 x potential warnings" hardly sounds useful :) Great for greenfield, less so for existing projects. Although nowadays, fire up your agent with "/goal work through and address all lint warnings by fixing the code according to XYZ" and leave it alone for a couple of hours and I guess it's no longer an issue...
Don't get me wrong, the new level of details coming from Ruff is much appreciated and a good thing :)
> uv run ruff check --fix
Generally gets you 90% there.
On the 0.16.0 release it was more like 10% autofixable on the codebases I maintain. One of them had 100+ unfixable errors across less than 10k lines of code and tests in total. Previous ruff version labeled it perfectly clean on format and check with default settings.
Good thing we have LLM:s that can do the tedious work of checking each case and applying local annotations or updating the config globally! If I was stuck doing this manually, I would have just gotten rid of ruff and went back to black+pylint.
In reality it got me 30% there.
That suggests it’s doing its job.
I don't think they were trying to say "No, ruff doesn't work at all", more that "gets you 90% there" might have been an over-exaggeration, or very codebase-specific number and really, YMMV.
I really wish Ruff would introduce something similar to Nix’s stateVersion, which is used to determine the set of defaults that will be applied. Updating Ruff at a scale beyond a single repo is a bit of a crapshoot currently, with every version introducing a bunch of new default rules which you then have to deal with immediately (either by turning them off or fixing them).
I’m aware we could have an allowlist in place which specifies all rules that are turned on but I’d much rather have a simpler config file, and be able to bump the state version at a good time for everyone to spend a few hours fixing new violations.
> with every version introducing a bunch of new default rules which you then have to deal with immediately
According to the original post
> Ruff's default rule set was last modified in v0.1.0
Which was over 2 years ago.
https://github.com/astral-sh/ruff/releases/tag/v0.1.0
I think the consensus is that this would be the wrong end to tackle the problem from. Your project should include ruff at the version it wants to use in its pyproject.toml. Bump the version when you're ready to spend the time on one project, but no need to coordinate across multiple projects. If you really want to do it, you can, but if there's one hold-out, you're not stuck behind forever because one project can't move yet.
If you listen closely you can hear the thousands of CI jobs that just pull latest UV failing all at once.
This is great news! With the advent of agentic coding, strong linting is more important than ever. What I'd really like to see is forbidigo for more languages.
100% agree, I am upgrading my projects now. And yet, I have mixed feelings about this. I like to think that when I (was) writing code myself, I applied a great deal of intuition to decide when to skip or ignore some rule. On the hand, I also saw projects were developers where using pylint with most rules enabled and I have to say the code was not better - on the contrary it had plenty of hacks to make pylint happy at the cost of illegible code.
Not unlike that experience, I also saw coding agents spend lots of tokens trying to fix a benign issue, as well as doing the opposite - like disabling the tests !!! when they don't pass. :facepalm:
I grew to trust AI results in terms of overall correctness, but I still have hard time trusting their *judgement* on the *code quality*.
413 rules and somehow every codebase I join still has the same three arguments about import sorting
Looks like it's recommended to use zero-config now, which is great! My new .ruff.toml is just:
TIL RustPython uses Ruff's AST which is fast.
LibCST can modify the AST/CST to do python codemod, now in Rust too;
rustpython-ruff_python_parser: https://crates.io/crates/rustpython-ruff_python_parser .. https://github.com/astral-sh/ruff
LibCST: https://github.com/Instagram/LibCST
Default on lint is good for agent fleets. fwiw, I watched one agent "fix" an unfamiliar rule by deleting the test; perfect compliance, tiny misunderstanding.
ran 0.16 on a file with no config, it flags unsorted imports and `except Exception` by default now
You know how many bugs I've met working on large Python codebases over 20 years, caused by unsorted imports or `except Exception`? Yeah, you've guessed it: exactly 0 bugs.
It's really annoying how many people think that if you enforce stupidly strict rules about formatting, more strict that those of Fortran in 70s, you'll automatically get good code.
I've seen companies that enable 100% of ruff rules, use several other linters, and enforce other rules (like every variable name should be at least 20 characters long to fully describe its purpose). And they have awful buggy unmaintainable code. But it is nicely formatted (although reading code where half of the screen estate is consumed by the variable name is a bit difficult) and has no "dreadful" `except Exception`. Or every mutable class attribute variable is annotated with `typing.ClassVar` (RUF012 - the most idiotic rule I've ever seen). Nevermind that it doesn't really stop anyone from changing value of that attribute. You must annotate it with `ClassVar`!
It's an idiotic cargo-cult.
I think it should depend on if it's `except Exception` (don't use) or `except Exception as e` (probably fine, as long as you log `e` or do something else with it).
I don't think people expect import sorting to fix bugs. It's just good (and free!) practice.
Anyway I have definitely had linters like Ruff catch bugs. Probably the most common is the mutable default argument gotcha.
I sorta kinda get why `ty` is pre v1.0.0 -- it's a typechecker that doesn't check a huge number of types. But what are we waiting for with `ruff`? Surely it's eaten whatever the old options were (black? maybe a few takes on py+lint?) by now many times over, and is even more dominant than `uv`.
I run this program hundreds of times a day so I'm generally excited for new features, but I guess the OAI acquisition has made me ornery when it comes to these folks. Apologies to kindly nerds who made this release happen, and congrats <3
Why must my poor semver be hurt so!
It is fully according to their versioning policy:
https://docs.astral.sh/ruff/versioning/
But also compliant with semver:
Major version zero (0.y.z) is for initial development. Anything MAY change at any time. The public API SHOULD NOT be considered stable.
https://semver.org/
(Maybe I misinterpreted your remark, but semver does not get hurt.)
It's conventional for semver to allow breaking changes for 0.x minor releases (but not for patch releases).
Yes but the question is why Ruff has not released v1 yet. They’re clearly used in production and the entire purpose of v0.x is to signal a project is early in development and is expected to have coarse edges.
The versioning doc linked elsewhere in the thread explains: There isn't yet a stable API and they want to wait on that before releasing v1.
Zerover for life
I find it somehow interesting that a tool for Python is written in Rust.
What languages are python interpreters written in?
Interesting but not surprising in an application where speed matters.
It's the same for JavaScript. Rust is the better language (especially for large projects like these), but sometimes you don't have a choice but to use Python or JS/TS.
Wait until you hear what CPython is written in.
this is writing 3000 lines in this era you must be a normal human being
Every new single piece of software calls itself “extremely fast”, “blazingly fast”, it gets tiring.
Broadly you have a point. In this case I would expect a Rust-based Python linter to run significantly faster than one writen in Python itself (e.g., pylint).
ruff is not new. It's also truly really fast. On an as-yet un-ruff'ed 32k SLOC python package it returns in 158ms finding 2k errors. Having it fix errors is also very fast.
What's really tiring is reading such facile "critiques", especially when they are not even applicable.
Compare it to some of the other options in that space and you'll see why that was a selling point.