Some of the most common and easy-to-calculate/easy-to-measure code metrics. Understanding these can help you make your code better: avoiding code rot and writing maintainable code all starts here. Content is created for C# .net, however, the underlying principles apply to other languages/frameworks as well.
2. Attila Bertók
C# technical lead
Contact:
bertok.atti@gmail.com
https://www.linkedin.com/in/bertokattila/
3. After a certain size, code becomes worse
Sheer size
Complexity
Lack of ownership
everyone’s code is no one’s code
broken windows theory
Duplication
Conditional logic and logical exceptions
Legacy code (the no dev’s land)
code that other developers wrote
code that you wrote, but more than two weeks ago
4. Decreased performance
Developers need more time to understand the code
Developers need more time to implement changes to the code
Developers’ confidence is decreased
Increased bug count
New bugs get introduced
Old bugs surface and re-surface
7. Static (as opposed to dynamic): Done without actually
executing the program
Quantifiable measurements
General code health
Early warning system
11. “Any fool can write code that a computer can understand.
Good programmers write code that humans can understand.”
[Martin Fowler]
12. Spacing, bracing, indenting, etc.
Aim: give the code uniform look, so intent is obvious at the
first glance
Common example: braces for single line statements
13. Enforce braces:
if (areBracesEnforced)
{
return bracedCode;
}
Omit braces:
if (areBracesOmitted)
return bracelessCode;
14. The developers get used to the style and do their “internal
pattern-matching” based on their expectations
Inconsistent style leads to errors
15. Code should always convey intent
and do so as clearly as possible
Patterns in code should always be obvious
and easy to recognize
on any level
be it codestyle
or architectural design
16. private static Bitmap ConvolutionFilter(
Bitmap sourceBitmap,
double[,] filterMatrix) {
var lockedRectangle =
new Rectangle(
0,
0,
sourceBitmap.Width,
sourceBitmap.Height);
BitmapData sourceData =
sourceBitmap.LockBits(lockedRectangle);
// […]
if (sourceBitmap.IsGreyScale) {
for (int k = 0; k < buffer.Length; k += 4) {
rgb = buffer[k] * 0.11f;
buffer[k] = (byte)rgb;
}
}
}
private static Bitmap ConvolutionFilter(
Bitmap sourceBitmap,
double[,] filterMatrix)
{
var lockedRectangle =
new Rectangle(
0,
0,
sourceBitmap.Width,
sourceBitmap.Height);
BitmapData sourceData =
sourceBitmap.LockBits(lockedRectangle);
// […]
if (sourceBitmap.IsGreyScale)
{
for (int k = 0; k < buffer.Length; k += 4)
{
rgb = buffer[k] * 0.11f;
buffer[k] = (byte)rgb;
}
}
}
22. The title says it all.
Recommendation:
The best method is a method that has no arguments (niladic)
One argument (monadic) or two (dyadic) is acceptable
Three arguments (triadic) should be avoided whenever possible
More than that (polyadic) require a rather good justification
23. Move things to instance variables!
Separate functions (initialization, ctor, etc.)
Rearrange classes (are many of your methods accepting the
same class as argument? It is possible that those methods
belong to that class!)
Avoid out parameters
Avoid flag arguments
24. The title says it all – again.
Recommendation: keep it low. Anything above 8 is a serious
overkill, anything above 5 is potentially too many.
25. Guess what.
No overload is 1.
Recommendation: keep it low. Many overloads can be
difficult to keep in mind. However, there are no hard rules for
this.
26. CC = 1 + …
the number of any of the following expressions found in the
method body:
if/case/default/?:/??/.?
while/for/foreach/continue
goto
&&/||
catch
27. Recommendations: The smaller the better. Static analysis
tools recommend a max of 15 per method.
I’ve heard of companies where anything over 8 automatically breaks
the build.
28. In case of .net it is generally lines of IL code (Logical LOC)
Code style (and language) does not influence it
Interfaces, abstract methods, enumerations have a LOC of 0.
Namespaces, namespace references, and declarations have a
LOC of 0.
LOC of classes is the sum of the LOC of their methods.
Recommendation: keep as low as possible
29. Simple as it sounds, the number of the lines containing
comment
Inline comments and full-line comments are worth the same
Recommendation: keep it as low as possible
30. (100 * LOComments) / (LOComments + LOC)
The amount of required comment strongly depends on the
quality of code
Code should be self-explanatory and should not require comments
Recommendation: keep as low as possible
31. Percentage of code covered by (unit/acceptance) tests.
Recommendation: The closer to 100% the better.
If using test first, anything below 100% is pretty bad.
33. Classes should have a small number of instance variables. Each
of the methods of a class should manipulate one or more of
those variables.
In general the more variables a method manipulates the more
cohesive that method is to its class.
A class in which each variable is used by each method is
maximally cohesive.
34. The strategy of keeping functions small and keeping parameter
lists short can sometimes lead to a proliferation of instance
variables that are used by a subset of methods.
When this happens, it almost always means that there is at
least one other class trying to get out of the larger class. You
should try to separate the variables and methods into two or
more classes such that the new classes are more cohesive.
35. M is the number of methods in class (both static and instance
methods are counted, it includes also constructors, properties
getters/setters, events add/remove methods).
F is the number of instance fields in the class.
MF is the number of methods of the class accessing a particular
instance field.
Sum(MF) is the sum of MF over all instance fields of the class.
LCOM = 1 – (sum(MF)/M*F)
LCOM HS = (M – sum(MF)/F)(M-1)
36. DI: Incoming dependencies: the number of classes that depend on
this class
DO: Outgoing dependencies: the number of classes that this class
depends on.
Instability: DO / (DI + DO) (0..1)
I = 0: no other components depend on this component.
I = 1: this component depends on no other components.
37. NC: the number of classes in the assembly
NA: the number of abstract classes in the assembly
A: Abstractness:
A = NA / NC
38. Zone of Uselessness:
Abstract components that
are never inherited
Zone of Pain: very stable,
thus difficult to change,
meanwhile very concrete,
thus difficult to extend
39. D = |A + I - 1 |
Recommendation: Classes should be as close to the main
sequence as possible.
42. The title (almost) says it all.
For derived classes, it also includes the interfaces
implemented by the base class.
Recommendation: keep low. ;)
43. The title (almost) says it all (again).
(Including System.Object, so always starts from 1.)
Recommendation: keep low. ;)
46. Average number of internal relationships per type.
Let R be the number of type relationships that are internal to
this assembly (i.e. that do not connect to types outside the
assembly).
Let N be the number of types within the assembly.
H = (R + 1)/ N.
Recommendation: Keep it high.
48. SOLID principles
Consistent style
Succinct but readable code
Good naming
49. The Boy Scout Rule and Opportunistic refactoring
50. Code metrics in Visual Studio:
https://msdn.microsoft.com/en-us/library/bb385914.aspx
A Short Primer on Code Quality Metrics:
https://ardalis.com/static-code-analysis-and-quality-metrics
NDepend Code Metrics:
https://www.ndepend.com/docs/code-metrics