That's not a real operator. You've put a space in "i--" and removed the space in "-- >". The statement is "while i-- is greater than zero". Inventing an unnecessary "goes to" operator just confuses beginners and adds something else to think about while debugging.
And yes I have seen beginners try to use <-- and --<. Just stop it.
IDK, but if the reason is "to break stuff into multiple functions", then I'm not necessarily writing yet another single-use function just to avoid writing a comment, especially in time critical applications. Did that with a text parser that could get text formatting from a specifically written XML file, but mainly due to it being way less time critical, and had a lot of reused code via templates.
Like with everything, context matters. Sometimes it can indicate poorly structured control flow, other times inefficient loop nesting. But many times it is just somebody’s preference for guard clauses. As long as the intent is clear, there are no efficiency problems, and it is possible to reach the fewest branches necessary, I see no issues.
It's important to remember that Linus is primarily writing about C code formatting. C doesn't have things that tend to create more deeply nested structures, such as a formal class syntax, or nested functions.
Going too deep is still bad--as zea notes, it's an indication of control structures run amok--but the exact number is dependent on the language and the context.
Indentation implies there's some control structure causing it. Too many control structures nested gets hard to mentally keep track of. 3 is arbitrary, but in general more indentation => harder to understand, which is bad.
Honestly I don't mind the indentation since C isn't going to give us many ways to address this with as little code.
That said, with compilers that are good at inlining trivial functions, I really do appreciate the "it does what it says on the tin" approach to using functions on things like this. Even if they're only used once. Comments would help too.
The logic in these if statements is inscrutable on a cold read like this. To me, that's a maintenance risk; imagine seeing a snippet this size on a PR. Having functions that name what the hell is going on could only help.
One nit: whatever IDE is displaying single-character surrogates for == and != needs to stop. In a world where one could literally type those Unicode symbols in, and break a build, I think everyone is better off seeing the actual syntax.
I think it's a lineature. FiraCide does that for example, and I like it very much. My compiler and lsp will tell me if there is a bad char there. Besides, the linea tires take the same space as two regular characters, so you can tell the difference.
It's not the 90s anymore. My editor can look nice.
In a world where your IDE and maybe also compiler should warn you about using unicode literals in source code, that's not much of a concern.
VSCode (and I'm sure other modern IDEs, but haven't tested) will call out if you're using a Unicode char that could be confused with a source code symbol (e.g. i and ℹ️, which renders in some fonts as a styled lowercase i without color). I'm sure it does the same on the long equals sign.
Any compiler will complain (usually these days with a decent error message) if someone somehow accidentally inserts an invalid Unicode character instead of typing ==.
If your build fails because you can't track down the literal ≠ in the code I would recommend just looking at the compiler error. I understand the concerns about == vs = more but the vast majority of LSPs (and some compilers) will catch that too.
I have also yet to see any IDE enable ligatures by default, at least VS Code and the JetBrains suite both require opting into them even though their default fonts support them.
I don't know enough Rust to understand by what you mean by the last one. My understanding was that mod name was just declaring the module that this file depends on. Could you explain what I should do instead? Since your other statements I totally agree with, I should probably agree with the last one.
mod name declares that the module should be compiled and reachable as a submodule of the current module. This assumes that you have a file or directory of the name in the right place. This is what you should do.
You can also declare a module like this: mod name {...} where you just put the content in the block. The two are functionally equivalent, from the compilers perspective.
Why have an async block spanning the whole function when you can mark the function as async? That's 1 less level of indentation. Also, this quite is unusable for rust. A single match statement inside a function inside an impl is already 4 levels of indentation.
A single match statement inside a function inside an impl is already 4 levels of indentation.
How about this?
The preferred way to ease multiple indentation levels in a switch statement is to align the switch and its subordinate case labels in the same column instead of double-indenting the case labels. E.g.:
switch (suffix) {
case 'G':
case 'g':
mem <<= 30;
break;
case 'M':
case 'm':
mem <<= 20;
break;
case 'K':
case 'k':
mem <<= 10;
/* fall through */
default:
break;
}
I had some luck applying this to match statements. My example:
let x = 5;
match x {
5 => foo(),
3 => bar(),
1 => match baz(x) {
Ok(_) => foo2(),
Err(e) => match maybe(e) {
Ok(_) => bar2(),
_ => panic!(),
}
}
_ => panic!(),
}
Is this acceptable, at least compared to the original switch statement idea?
This posts entire comment chain is an interesting example of people that have extensive knowledge in completely different areas of programming to me. And have some concepts I had never heard/thought of.
And I have a fucking conniption because just move that shit into a variable before the return. I get it when sometimes you just need to resolve something inline, but a huge amount of the time that ternary can be extracted to a variable before the ternary, or just rewrite the function to take multiple types and resolve it in the function.
In my codebase? I'd pull a "let's linger after standup about your PR" and have the coder sweat through a 10 minute soapbox about nothing before laying down the law.
Yeah, the annoying thing is the people who I generally have found to be the worst about stuff like this are old school Senior C developers, who still program like it's 1987 and we have to fit everything into 4K of RAM.
Fortunately there's nothing like that in my code base, I just run into stuff like that periodically when I'm digging around in other team's server code looking for something.
Then perhaps the code you are trying to extract doesn't make a clear and cohesive procedure. Maybe include more or less of the code, or rework the code into logical pieces or steps. Write the algorithm in human language first, then implement the steps using functions.
Pick something and change it when inspiration strikes. Sometimes you need a big picture view of something to get the right abstractions or even just name things.
You get one level at the get go because everything is in a function. So just two levels of indentation? A pretty basic if.. for..if nesting has to be refactored? Into what? Goto? Should I sprinkle return statements all over the place?
Y’all gotta understand that Linus is often kind of an ass.
You can't read a block of code and as quickly and understand its control flow without reading every line, especially in regards to resource cleanup.
For example say you have:
...
if this:
something
or other
and many more...
...
else:
yet another thing
and some more
...
do some cleanup
return
...
Say you aren't exactly interested in what happens inside each branch. If you can assume that there's one return at the end of the block, you can see the if and else, you can reason about what values would trigger each branch, you can also see that no matter which branch is executed, the cleanup step will be executed before returning. Straightforward. I don't have to read all the lines of the branches to ensure the cleanup will be executed. If I can't assume a single return, I have to read all those lines too to ensure none of them jumps out of the function skipping the cleanup. Not having to think about such cases reduces the amount of reading needed and it makes reasoning about the block simpler. The bigger the blocks, the more the branches, the stronger the effect. You have one less foot-shotgun to think about. The easier you make it for your brain, the fewer mistakes it's gonna make. For all those days when you haven't slept enough.
E: Oh also refactoring blocks of code out into functions is trivial when you don't have multiple returns. Extracting a block with a return in it breaks the parent control flow and requires changes in the implementation.
E2: Shorter blocks do not obviate this argument. They just make things less bad. But they make almost everything less bad. Shorter blocks and single returns make things even better.
I mean, I certainly wouldn't give someone else shit for using ligatures, but personally, I don't like them, because:
they break with monospacedness. Everything is in a nice grid and you've randomly got these character combinations that needlessly stick out.
they sometimes happen in places where they really shouldn't.
they hide what the actual characters are. Especially, if go to edit that code, my brain will really struggle for a split-second when there's a '≠', then I delete one character and rather than the whole thing disappearing, I'm left with a '!'.