Loc Is a Dumb Metric for Functions
Posted3 months agoActive2 months ago
theaxolot.wordpress.comTechstory
calmmixed
Debate
80/100
Software DevelopmentCode QualityMetrics
Key topics
Software Development
Code Quality
Metrics
The article argues that Lines of Code (LoC) is a poor metric for evaluating function quality, sparking a discussion on the usefulness of LoC and other metrics in assessing code complexity and maintainability.
Snapshot generated from the HN discussion
Discussion Activity
Very active discussionFirst comment
21m
Peak period
52
Day 1
Avg / period
15.5
Comment distribution62 data points
Loading chart...
Based on 62 loaded comments
Key moments
- 01Story posted
Oct 19, 2025 at 7:39 PM EDT
3 months ago
Step 01 - 02First comment
Oct 19, 2025 at 8:00 PM EDT
21m after posting
Step 02 - 03Peak activity
52 comments in Day 1
Hottest window of the conversation
Step 03 - 04Latest activity
Nov 2, 2025 at 8:55 PM EST
2 months ago
Step 04
Generating AI Summary...
Analyzing up to 500 comments to identify key contributors and discussion patterns
ID: 45638986Type: storyLast synced: 11/20/2025, 2:49:46 PM
Want the full context?
Jump to the original sources
Read the primary article or dive into the live Hacker News thread when you're ready.
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.
- 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.
Why even merging his code at first place. He was intern so i assume whatever he was working on was not mission critical.
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.
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.
The inverse is not always true, but often is.
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.
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.
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.
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.
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.
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.
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.
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.
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".
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.
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.
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.
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.
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.
And today an LLM could probably have refactored it fairly well, automatically.
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.
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.
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.
What is the LoC for that function? The first implementation or the final rewrite? They express the same thing.
smaller functions are also usually easier to test :shrug:
> 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.
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.
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.