Hi all, are there any guidelines on cyclomatic complexity, level depth per function (e.g. nested if/else statements and loops) and the number of parameters passed to a function that a program should have? Especially in regard to testing? If I use tools such as Miri, Kani, Proptest and Criterion, for example, should I reduce my program's complexity so that the tools can work properly? And if so, how can I tell if I have reduced it enough?
Cyclomatic complexity isn't a goal. Look to make code work, easy and fast.
To answer should you reduce it. Maybe yes. It gives you experience.
For Miri, Kani, proptest and criterion, "level depth per function" itself has absolutely zero effect on their performance as they don't operate on an AST, but on MIR or machine code which doesn't have any concept of control flow nesting. For criterion only how fast your code is after optimization matters. For the rest, reducing cyclomatic complexity may help, but you shouldn't try to make changes that reduce cyclomatic complexity if those changes were to hurt readability. Readability is an inherently subjective thing differing between people for which objective measures like cyclomatic complexity and "level depth per function" can only be a lossy proxy. They can still be useful for as long as you remember that minimizing them is not the goal. I can write absolutely unreadable code that has a cyclomatic complexity and "level depth per function" of 1 if I want.
Thanks!
Thanks!
I was looking for some specific limitations of the Rust testing tools, found only one for the quickcheck tool regarding the number of parameters: "Only functions with 8 or fewer parameters can be quickchecked" GitHub - BurntSushi/quickcheck: Automated property based testing for Rust (with shrinking).
I thought that maybe someone had dealt with such limitations before and could tell me about their experience.
This only applies to the test functions themselves. And it's very easy to work around by using tuples.
Managing cyclomatic complexity is an approximation. And it is a means not an end.
I want to expand on this, and claim it is not even a very good means to that end.
Anecdotally: I never found linters that warn about complexity very useful. Usually they are just annoying.
- They warn for functions that are long but fairly simple due to having a regular structure (lots of conditions and lots of returns, but structured as a large dispatch table for example).
- They don't warn for code I do want to factor out into separate functions.
In other words, they have both false positives and false negatives from what I consider "good code taste". In general I refactor the code before the tool triggers anyway, so all I see are the false positives.
Of course "good taste" is a very vague concept, something that you develop over time as you code. It is also to some extent subjective, but I do find that there is a large amount of commonality in the taste between many people who I do consider good coders.
So maybe cyclomatic complexity is good when starting out (though I'm doubtful) but it is straight up useless once you become skilled. It also doesn't capture many other aspects of over-complexity, such as overly clever type system trickery, reliance on obscure/subtle details of the language, etc.
In C++ I found the most hard to follow code generally used overly clever template tricks. Rust is similar: overly complex abstractions and overly complex where-bounds as a result of trying to make things overly generic. In neither case is cyclomatic complexity a useful measurement.
My top tips would be:
- Don't introduce an abstraction layer you don't need. Don't do it just in case, do it only when you know you need it.
- Try to keep modules as independent of each other as possible and as focused on a single thing as possible. In particular, don't have dependency cycles.
- Go for the simple solutions first. Often they are good enough, and often they are more performant as there is less stuff to optimise away.
- It is easier to add new code than to remove existing code. This ties into the previous point.
- Don't try to be overly clever.
There's a PR to deprecate this lint, and it had already been downgrade to a nursery lint.
Based on other languages (my experience in the past few years has mainly been in .NET), if you write readable and maintainable code you will generally end up with low cyclomatic complexity. In Visual Studio I have a couple of dynamic extensions that show me this.
"Low" means < 10.
E.g., see right column.
Thanks for the tips!