Ironic way to end an article that repeatedly belittles the target audience.
https://www.youtube.com/watch?v=bmSAYlu0NcY
which is for the book:
https://www.goodreads.com/book/show/39996759-a-philosophy-of...
One consideration is that about 40 loc will comfortably fit in one code split, and it's nice if one func will fit in 1-2 splits. Past that length, no matter how simple it is, the reader will have to start jumping around for declarations. That might be ok at first, but at some point the ratio of offscreen to onscreen code gets too big.
Personally, my setup has shown the same number of vim rows/cols on my 4K monitor vs my 1080p one (or whatever you call the 16:10 equivalents).
My 28" 4K monitor is exactly like four 14" FHD screens put together. It offers me 480 columns (a bit fewer, because window borders and scroll bars of multiple windows).
So indeed, better screens allow me to see much more text than poorer screens of old. There is a limit, but definitely it was not reached 30-40 years ago yet.
What is the LoC for that function? The first implementation or the final rewrite? They express the same thing.
No, LoC is not a dumb metric. In the same way that cognitive complexity, code coverage, and similar metrics aren't. CC is definitely not "superior" to LoC, it's just different.
These are all signals that point to a piece of code that might need further attention. It is then up to the developer to decide whether to do something about it, or not.
You know what's dumb? Being dogmatic, one way or the other. There are very few hard rules in software development—they're mostly tradeoffs. So whenever I hear someone claiming that X is always bad or always good, I try to poke holes in their argument, or just steer clear from their opinion.
With low thresholds ("clean code" like low) both LoC and "cognitive complexity" (as in SQ) are bad measures that lit up in red large percentage of otherwise correct code in complex projects. The way people usually "solve" them is through naive copy pasting which doesn't reduce any cognitive load - it just scatters complexity around making it harder to reason about and modify in the future.
I have been programming for 30 years and, while I don't consider myself a great programmer, I don't like large functions. In my experience they usually fail at clearly expressing intent and therefore make the code a lot harder to reason about. There are, of course, exceptions to this.
> Linearity is the idea of having instructions be read one after the other, from top to bottom, just like how code is executed. Having to jump back and forth from function call to function call reduces linearity.
1000 times yes.
smaller functions are also usually easier to test :shrug:
Some good advices in here on what balance to strike with some clear examples. Always a good reminder :). thanks for the writeup!
#define G(l) l->_G
What leverage have you achieved there buddy
I don’t think functions inherently need to be small though. 5 20-line functions are just as buggy as that 1 100-line function. Bugs scale with LoC, not how well you’ve broken functions apart.
The lesson is really just to avoid overcomplicating things. Use less LoC if you can. You should also avoid overly-shortening or cleverness but it tends to be the lesser evil.
I have a personal preference towards the “more functions” approach too but I want to separate out my emotional feeling about what’s better vs what’s actually supported with data.
al_borland•3mo ago
Knowing At a high level what needed to happen to accomplish what the code did, I know for a fact it could have and should have been broken down more. However, because of the complexity of what he wrote, no one had the patience to go through it. When he left, the code got thrown away.
While 10 LOC vs 50 LOC doesn’t matter, when commas enter the number, it’s a safe bet that things have gone off the rails.
manwe150•3mo ago
ruszki•3mo ago
- Testing
- Reusability
- Configurability
- Garbage collector/memory management
- etc
I never understood these kind of restrictions in code analysis tools. These kind of restrictions don’t help overall complexity at all, and sometimes they even make things more complex at the end.
mrheosuper•3mo ago
Why even merging his code at first place. He was intern so i assume whatever he was working on was not mission critical.
al_borland•3mo ago
jcranmer•3mo ago
There are times when even a 1,000 LOC function is the better solution. The best example I can think of involve a switch statement with a very high branch factor (e.g., 100), where the individual cases are too simple to break out into separate functions. Something like the core loop of an interpreter will easily become a several-thousand line function.
imoverclocked•3mo ago
In my experience, there are very few places where something can't be broken up to reduce cognitive overhead or maintainability. When a problem is solved in a way as to make it difficult to do then maybe it's better to approach the problem differently.
zdragnar•3mo ago
The inverse is not always true, but often is.
imoverclocked•3mo ago
Often true; That's why cognitive complexity wins over cyclomatic complexity.
eg: Embedded logic doesn't add linearly.
fn() { // 1
} // cognitive complexity of 7 vs cyclomatic complexity of 4It's been a while since I've implemented anything around this and was remembering cognitive complexity while writing cyclomatic complexity in the previous response. They both have their place but limiting cognitive complexity is vastly more helpful, IMHO. eg: The above code might be better written as guard-clauses to reduce cognitive complexity to 4.
gorgoiler•3mo ago
morshu9001•3mo ago
kqr•3mo ago
nake89•3mo ago
hu3•3mo ago
This is something AI is good at. It could have shown the intern that it is indeed possible to break such function without wasting an hour of a seniors time.
bsder•3mo ago
I would argue that the word you are looking for is "containment".
Is there any real difference between calling a function and creating a "const varname = { -> insert lots of computation <-; break retval;}" inline?
If you never do that computation a second time anywhere else, I would argue that a new function is worse because you can't just scan in it quickly top to bottom. It also ossifies the interface if you have the function as you have to rearrange the function signature to pass in/out new things. Premature optimization ... mumble ... mumble.
Even Carmack mentioned this almost 20 years ago: http://number-none.com/blow/john_carmack_on_inlined_code.htm...
Given that we've shrugged off "object-itis" which really demands small functions and given modern IDEs and languages, that kind of inline-style seems a lot more useful than it used to be.
RHSeeger•3mo ago
I feel exactly the opposite. By moving "self contained" code out of a method, and replacing it with a call, you make it easier to see what the calling location is doing. Ie, you can have a method that says very clearly and concisely what it does... vs making the reader scan through dozens or hundreds of lines of code to understand it all.
Chapter 12 of Kent Beck's book Tidy First? is about exactly this.
bsder•3mo ago
As for your "function documentation", the primary difference between that assignment and a function is solely ossified arguments. And ossified function arguments is something that I would put under "premature abstraction".
At some point you can pull that out into a separate function. But you should have something that causes you to do that rather than it being a default.
RHSeeger•3mo ago
I didn't. Rather, I stated my opinion on the subject and then noted that there is a book that discusses the same (so clearly I'm not the only one that thinks this).
> As for your "function documentation", the primary difference between that assignment and a function is solely ossified arguments.
No, it's not. It's that the code being moved to a function allows the original location to be cleaner and more obvious.
saghm•3mo ago
shakadak•3mo ago
I do the computation in my head each time I read it. If it is a function I can cache its behavior more easily compared to a well defined block of code. Even if it's never used anywhere else, it's read multiple time, by multiple people. Obviously, it has to make sense from a domain logic perspective, and not be done for the sake of reducing lines of code, but I have yet to see a function/module/file that is justified in being gigantic when approaching it with domain logic reasoning instead of code execution reasoning.
Sohcahtoa82•3mo ago
At my job, we're not a SaaS. Our software is distributed as cloud images that customers run in their own environments. I need to run regular vulnerability scans on built images.
So, I need to do several things:
- Stand up EC2 instances with our image
- Run our software setup
- Start the scans
- Wait for the scans to complete
- Destroy all the instances
- Download the scan reports
- Upload the reports to S3
- Create Jira tickets based on findings
Now, I COULD easily write it all as one big function. But I tend to prefer to have one "main" function that calls each step as its own function. When looking at the main function, I can much more easily tell what the program is going to do and in what order.
In other words, I much prefer style A from Carmack's post.
xorcist•3mo ago
It could have been me behind one of those thousand line functions. Some tings lends itself naturally to this style, such as interpreter-like loops and imperative business logic. There's always that someone that argues that splitting up the code with function calls would somehow make it more "maintainable".
I'm reluctant to do it, because the code is identical, only non-linear which means harder to understand. Combine this with global symbols and it's a recipe for accidental complexity. This is so obvious that I for a long time thought people with the opposite opinion were just cargo-culting some OOP "best practice" mess which has doomed so many projects, but maybe they're just wired differently.
endymion-light•3mo ago
Although I personally despise the massive call graphs, my rule of thumb tends to be if I spend over 10 minutes trying to hold all the logic in my head, something is wrong.
Especially with imperative business logic - surely it makes so much more sense to break it down into functions. If you're on call at 2am trying to solve a function with imeprative business logic that someone else has written and has over 1000 lines, you're going to hate yourself, and your coworker.
westcoast49•3mo ago
Moreover, I've come to realize that my colleague is not adverse to using abstractions if they are well established, and if they already exist. But he is (much) less inclined to invent or write new abstractions than I am. As you have concluded, I have also concluded that this is actually a matter of cognitive style, more than it's a symptom of "slop" or "cargo-culting of best practices".
delichon•3mo ago
stirfish•3mo ago
Inevitably there will come a new special case where, for example, where Gold Members will get to use double coupon codes on Thursday. You'll start by looking in `checkout()` where you'll see either 1000 lines of other special cases, or maybe 30 lines with function calls like `foo = applyCouponCode(shoppingCart, couponCode)`.
For me, it's easier to see where to start with the latter.
Kinrany•3mo ago
stirfish•3mo ago
Well, twice, including the test. If a block is complex enough to warrant a high level explanation, you might as well capture that intent in a test too.
Edit: in my example, `applyCouponCode` takes in a `shoppingCart` and a `couponCode`, so you know that's all you need to apply a coupon code. You'd change it to something like `applyCouponCode(shoppingCart, couponCode, user.memberStatus)` so you can tell at a glance that `memberStatus` has something to do with how coupons work. You wouldn't want to pass in the whole `user`, because giving the function more than it needs makes it hard to infer what context the function needs and, therefore, what it does.
Kinrany•3mo ago
1718627440•3mo ago
stuaxo•3mo ago
westcoast49•3mo ago
And today an LLM could probably have refactored it fairly well, automatically.
waynesonfire•3mo ago
He'd of course develop functions and macros to avoid repeating themselves.
It just worked for his style and problem solving approach. He knew when to stretch a function. So, I'm convinced long functions can be done elegantly, it's probably uncommon.
Ferret7446•3mo ago
Setting aside the anecdote, this statement is key. If you exclude deliberate efforts to game the LOC metric, LOC is closely correlated to complexity, however else you measure it (branch count, instruction count, variable count, rate of bugs, security vulnerabilities, etc). Unlike any other metrics however, LOC is dead simple to measure. Thus, it's an extremely useful metric, so long as you don't set up an incentive for devs to game it.