Measuring Software Complexity: What Metrics to Use?
thevaluable.dev
thevaluable.dev
I do this all the time with other automated checkers (linters, etc.). I don't see why this should be different. If another human agrees it shouldnt be a problem.
Unless the tool just meassured for amount of change and flagged it for review, which might make sense, as you can also mess up by removing things, you think are unused.
In other words would it have been less painful if you didn't have to suffer the long iteration times required to get feedback from some remote CI job?
It’s just foolish.
The only reason a CI should ever fail is if it catches a defect from making it into production.
Mechanical checks for things like formatting rules, linting errors, and reasonable tools to verify code complexity, as long as they don't produce false positives, are all important to run as part of CI.
Quite literally, software engineers today spend most of their time fighting dependencies and poorly built delivery machines.
The main driving force for microservices is not technical but organizational. Therefore, there are plenty of non-technical issues, such as for example budget to grow and split a team, that make or break the adoption of this style of architecture.
What things make it more difficult to build those models? A partial list, mostly as others have mentioned:
- tool and library dependencies - nested conditions - loops and especially nested loops - asynchronous processing, callbacks, etc - non-descriptively named variables and functions - using non-standard code patterns for standard functionality - delocalized code, as in, you have to navigate somewhere else to see it (throws off your working memory)
By one study, developers using Eclipse for Java spent 27% of their time just doing code navigation.
The starting point for code complexity is about how our minds work.
As many people have said, "it's easier to write a program than read it."
Language features on the other hand can let you develop complex programs quickly and reliably, by catching errors before the code gets deployed and so on.
Just put the whole folder structure into one repo! This would reduce build complexity, and you can set up the configs for your tools and CI one time rather than 20. You can still have several services out of one repo, if you want to, but it's easier to reason about and easier to change those service delineations later, where in 20 repos you're having to "clone and cut code" to separate things. In one repo, you just move code around as you split or merge services.
I routinely create a "super repo" for myself at these companies using submodules, so that I can actually work with the code more easily, but that still requires me to check in maybe 5 or more PRs for one feature, so it's not ideal. This only solves the developer's problems with local tools and still requires more complex debugging since the services are not actually in one repo under one config for deployment.
But mostly metrics should not be telling you things you can't already know just looking at the code; if it looks complicated, it probably is. Metrics only become useful when you need to tell without looking. Sometimes that's useful.
Any kind of internal code quality affects the productivity of the debugging procedure, not the final number of bugs.
I don't agree with that premise. LOC are a metric for code size, not for complexity. I've found that in practice the number of statements is a more reliable indicator for code size than lines of code. (For typical imperative languages anyways.)
The problem with using lines of code as a metric for developer productivity is precisely that it leads to developers introducing unnecessary complexity into the system since they try to add as many lines of code as possible for implementing any feature.
There is no drawback in keeping the source lines of code to the minimum amount necessary to get the job done.
IMO, lines of code is even better at measuring complexity than compiled bytecode because it accounts for complexity from the developer's point of view (which is what the question is asking).
While some lines of code require more effort from a typical developer to understand than other lines, it doesn't matter so much once they're averaged out over thousands of lines and thousands of different developers (each with their own slightly different perception of complexity). It's reasonable to factor out individual perception of complexity.
Number of instructions is probably not a good metric. Without any loops/jumps you might have a lot of instructions, but a very low complexity.
An endless loop executes a lot of instructions, but does not have to be complex.
I recall implementing some linear algebra numerical code. The problem was a bit complex, resulting in a bit of code complexity.
However I realized I had some extra information I hadn't used, and I spent half a day going over the math again. After a couple of pages of derivations I could narrow down the result to a couple of dot products.
So, I ended up with a commit where I had 100 or so lines of comments including equations to justify my two lines of code.
The implementation became super-simple, but why it worked was suddenly not so simple. I had effectively moved complexity from code-space to problem-space.
By that definition a loop doing 1 billion times a simple calculation would count as very complex, even though it is very easy to understand.
LOC would be better, even though code can be dense and complivated, or very verbose and simple.
Complexity is a measure of human difficulty in comprehension, not mechanical difficulty in execution...
Somehow it is more important to measure "gratuitous" complexity, redundant complexity that is not justified by present or plausible future requirements...
The problem is that the code itself does not capture requirements, so code analysis can give you absolute indicators but never an "efficiency" measure (how efficient and justified the measured complexity)
So no engineering best practices. That explains very good the quality of SW.
Back in the day we called these things architecture, but I'm old and salty.
And it was called Uniformity back in the day, not "architecture".
A better name for "consistency" is "following project conventions" which IS well defined.
In most places project conventions are either not actually defined anywhere or if they are defined in writing, they're usually either very very old and outdated vs. the actual conventions that everyone is currently using or it's just one guy updating the text and hitting everyone else over the head with the document to push his opinion through.
I personally like to be 'locally consistent'. I don't care how old and crusty the code base is. If the file that I have to change or add to calls everything a "giraffe", I will call my stuff "giraffe" as well, even if it really is a "gorilla". If I start calling it a gorilla, nobody will understand that the gorilla is the same as the giraffe if they don't have the same background knowledge I have. Unless I do a refactoring and I am changing the giraffes to gorillas. Which might either be a first PR to "clean up" or a follow up PR.
Unfortunately I see so many people not doing that and it wreaks havoc with the code base. Especially if we're now outside of the place that defines the giraffes and gorillas. It's really hard for the caller to figure out that they're one and the same thing.
So either admit your project doesn't have conventions, in which case, don't nit people who don't follow whatever convention exists in your head but isn't documented, or document the project conventions. You cannot have your cake and eat it too.
That "one guy updating the text" is at least explicitly documenting expectations.
Per your "local consistency" comment, if "giraffe" is called "gorilla" in every other file (aka "global consistency"), it sounds like you're just setting up more work for some developer to take care of. Consider leaving a comment and start using the globally consistent name, rather than propagating more inconsistency (technical debt). Perhaps more aptly put, what you call "local consistency" sounds like "global inconsistency" to me.
At my current place we do a lot through linters and automatic code formatting for example and we collectively agree on when and how to change the configs for that. Eliminates a whole class of "arguments" (X spaces vs. tabs anyone?) and it's relatively easy to "convert" new hires to it as well. They can either adapt to it, make a really good argument for changing the configs via a widely circulated PR or they aren't a cultural fit to us.
My point is that this guy usually isn't. In the vast majority of places I've been or seen it's the other version of him. I.e. the equivalent of the guy at the regulars table that edits Wikipedia to prove his point in a discussion.
In other words, function point count is a measure of complexity.
Functionalities like undo/redo take a lot of planning, coordination and integration efforts than others like a simple export function, but good luck selling that to any marketing or product owner for xx manndays.
I still think that this subject is way too specific to be generalized like this, but general thumb of rules still apply like good estimation and technical planning.
Number of unnecessary abstractions.