<
The Halstead metrics
The Halstead metrics have been with us for a long time. They were designed by Maurice Halstead at a time (1977) when programs were procedural and , in general, monolithic. In other words your average computer program was just one big file of code – probably written in COBOL – whose only sub-division was a procedure or method. This has implications for the use of these metrics with Java code – and we’ll see why later on.
Halsteads metrics are based on the numbers of operators and operands. This leads us to a set of questions –
- Which Java constructs will we define as operators?
- Which Java constructs will we define as operands?
- How are the Halstead metrics calculated from these basic counts?
- Which parts of the code will we examine to find these operators and operands?
- How will we assign the operators and operands to higher level Java constructs – i.e methods, classes, packages?
First we need to decide what we mean by operators and operands. Operators and operands are defined by their relationship to each other – in general an operator carries out an action and an operand participates in such an action. A simple example of an operator is something that carries out an operation using zero or more operands. An operand is something that may participate in an interaction with zero or more operators. So let’s look at an example -
int X = 1 + 2;
Most people here would be happy to describe = and + as operators. Similarly X, 1 and 2 can probably be safely viewed as operands. This leaves us with ‘int’ and ‘ ;’. We could view ‘;’ as an operator as it performs a function on the line of code – it illustrates that the line is ended. Or we could ignore it, justifying our choice by saying that it is simply a punctuation mark. ‘int’ is a qualifier of the variable X – is it then a part of the definition of the identifier (making it an operand) or does it change the nature of the identifier and therefore acting more like an operator. Even with a small simple example like this you can see how many possible interpretations there could be.
We’ve only partly answered our questions at this point but we need to take a break to look at what Halstead was trying to measure. The names of his metrics give us clues as to what he was aiming for. The three most primitive Halstead metrics are Length, Vocabulary and Difficulty. So we see an immediate parallel between Halsteads view of code and a readers view of a piece of text (e.g. a book). When we pick up a book we can glance through it and we will get an immediate sense of how much effort we are going to have to put into understanding this book. A glance at the last page tells us how many pages we have to read, looking at a couple of pages will give us a sense of how difficult the book is to read – the number of technical terms, sentence and paragraph length. With a book we might also assess the ease of understanding by looking at the number of illustrative diagrams – but that’s not an issue with code.
When we count the numbers of operators and operands we actually count them in two different ways – firstly we just count the number but we also keep a count of the number of unique operands. For example in the following line -
int x = x + 1;
x occurs twice so if we take int, x and 1 as operands and =, + and ; as operators we have 4 operands (3 unique) and 3 operators (3 unique). Taking OP as the number of operators, OD as the number of operands, UOP as the number of unique operators and UOD as the number of unique operands we define the three primitive Halstead metrics as -
- The Halstead length (LTH) is OP+OD
- The Halstead Vocabulary (VOC) UOP+UOD
- The Halstead Difficulty (DIF) is (UOP/2) * (OD/UOD)
So taking our analogy with text the Halstead Length is the number of words and the Halstead Vocabulary is the number of different words. These make a lot of sense to us in these terms – we have a Length of 7 and a vocabulary of 6.
The difficulty is (3/2) * (4/3) or 1.99. When you look at the difficulty measure you can see that the number of unique operators has a big impact on the difficulty value. In practice we will usually have a small number of operators as these will be supplied by the language. One interesting aspect of the Halstead Difficulty calculation is that having a large number of repetitions of the same operand increases the difficulty more than having a large number of different operands infrequently repeated. For example this line –
int x=y+1;
has OP=3, UOP=3, OD=4, UOD=4 and therefore has difficulty (3/2) * (4/4) = 1.5.
This is an interesting contrast to measures of reading comprehension of ordinary written text such as books and articles. In this case text which uses a small number of words frequently is viewed as easier to read than text which has a large number of words used infrequently.
The other three Halstead metrics are derived from these three ‘primitive’ metrics.
- Halstead Volume (VOL) = LTH * log2(VOC)
- Halstead Effort (EFF) = DIF * VOL
- Halstead Bugs (BUG) = VOL/3000 or EFF^(2/3)/3000
The Halstead Volume is based on the Length and the Vocabulary. You can view this as the ‘bulk’ of the code – how much information does the reader of the code have to absorb to understand its meaning. The biggest influence on the Volume metric is the Halstead length which causes a linear increase in the Volume i.e doubling the Length will double the Volume. In the case of the Vocabulary the increase is logarithmic. For example with a Length of 10 and a Vocabulary of 16 the Volume is 40. If we double the Length the Volume doubles to 80. If we keep the Length at 10 and double the Vocabulary to 32 we get a volume of 50.
The Halstead Bugs Metric estimates how many bugs you are likely to find in the system. This is a simple division of the Volume metric by 3000 – Halsteads original calculation was EFF^(2/3)/3000 but in recent years VOL/3000 has been viewed as more appropriate as a measure, particularly with regard to object-oriented languages.
The last one we cover here is Halstead Effort. This is viewed as the amount of mental effort required to recreate the software. It is simply the Halstead Volume times the Halstead Difficulty – which seems quite a reasonable way to calculate the Effort. Remember though that this is simply the effort to write the code – it doesn’t include the effort required to interpret the specification for example, or the effort required in testing. It is probably useful in measuring how much effort would be required to rewrite the software in another computer language. I am not convinced that the Halstead Effort is a reasonable measure of the complexity of code. It certainly measures something different to cyclomatic complexity and Halsteadd volume - you can see that if you graph them against each other.
There is one more Halstead measure that is frequently mentioned and that is the Halstead Time which is the Halstead Effort divided by 18. This gives the time in seconds that it should take a programmer to implement the code. This is one of the more controversial metrics and is quite often omitted when listing Halsteads metrics. 18 is sometimes called the Stroud number as it is based on work by the psychologist John Stroud who reckoned that human beings could detect between 5 and 20 ‘moments’ or discrete events per second. Halsteads reasoning is lost in the mists of time but he probably thought that a person dealing in the same kind of information as part of their day-to-day job would probably end up being trained to a point quite high up the scale. Or else it was just typical progammer arrogance – if humanity was on a scale from 5 to 20 then programmers must be up at the top and average programmers would be just under it. You will also see this figure stated as being in hours rather than seconds – a factor of 3600 difference (to be fair most metrics programs will divide the figure by 3600 if they quote it in hours)! All this makes me a teeny bit suspicious of the possible use of this measure. Taking our line of code –
int x = y+1;
We got a difficulty of 1.5 and a Volume of 19.6 (7*log2(7)) which gives an Effort of 29.4 (1.5 *19.6).
Dividing by 18 we get a time of just under 2 seconds to implement this line of code. I can type it that fast alright.
The next question is which parts of the code should we use to calculate the numbers of operators and operands. Here is a short, but complete, package description -
package com.virtualmachinery.test.mypackage;
public class MyClass {
String name ;
/**
* The Constructor
*/
public MyClass(String theName) {
name = theName;
}
public void writeMansName() {
String salutation = “Mr.”;
System.out.println(salutation+” “+name);
}
public void writeWomansName() {
String salutation = “Ms.”;
System.out.println(salutation+” “+name);
}
}
How do we decide which parts of this code to analyse for Halstead metrics? Lets start at the top – Is the package declaration part of the code or is it really part of the structure of the file? I think you can argue that it is part of the code – if its not there then something different happens to the code – the class is assigned to a default package. On the other hand it doesn’t really get executed after the code has been compiled.
What about the class and method declarations? – aren’t these just compiled away into the byte codes, every class and every method must have a declaration. Like the package declaration they could be viewed as part of the structure of the code rather than being of the code itself. However it is also reasonable to argue that in object-oriented languages design and code are more intertwined than in the monolithic approach that prevailed when Halstead designed the original metrics. The method declarations probably have a better case to be included than the class declarations as they include information about the visibility of the method, the type of its return value and the type and names of its arguments. All of this is useful information that needs to be understood in order to use the method properly, and therefore contributes to the overall complexity of the method.
Within the class declaration we have instance variable declarations – these are probably uncontentious in that they quite definitely contribute to the code of the class. Likewise the code within the methods will always be viewed as something that we must count.
Comments are never included in the Halstead metrics.
Now we come to look at how we apportion the metrics across the code structure. Let’s just refresh our memory about the four figures that are used as the basis for all of the Halstead metrics –
The number of operators (OP)
The number of operands (OD)
The number of unique operators (UOP)
The number of unique operands (UOD)
If we look at our class declaration we can see that the variable salutation is declared twice in two different methods and in both cases serves the same purpose. If we were to examine our code at a class level we could say that there was only one operand and that it appears four times. If we take it at the method level we can view it as two separate operands that are counted twice in each method. It’s quite easy to make the case that salutation is different in each method since it is declared separately in each method. It is more difficult in the case of the instance variable ‘name’ which is mentioned once in the constructor and in each of the two methods. Here the variable is only declared once – in the class body – and yet it could be counted as non-unique in each of the constructor the methods and the class body. How should we deal with this case?
All of the Halstead metrics can be calculated at the method level. At higher level care needs to be taken to ensure that they are calculated properly. This is because it does not make sense to accumulate the numbers of unique operators and operands. However it does make sense to some accumulate values that are based on these as long as the calculations from which these are derived are only performed at method level.
At class level –
- Halstead Length (C_LTH) is the total of all the lengths in the methods
- Halstead Volume (C_VOL) is the total of all the volumes in the methods
- Halstead Bugs (C_BUG) = C_VOL/3000
- Halstead Effort (C_EFF) = is the total of all the effort in the methods
- Halstead Vocabulary is not calculated
- Halstead Difficulty is not calculated
At package level –
- Halstead Length (P_LTH) is the total of all the lengths in the classes
- Halstead Volume (P_VOL) is the total of all the volumes in the classes
- Halstead Bugs (P_BUG) = P_VOL/3000
- Halstead Effort (P_EFF) = is the total of all the effort in the classes
- Halstead Vocabulary is not calculated
- Halstead Difficulty is not calculated
There are other questions that need to be dealt with –
- What about static code?
- What about the class declarations?
- What about the package imports, the package declaration?
Should the code in each of these areas be added on to the totals from the underlying structures (classes, methods)?
These are all questions that need to be answered when building a metrics tool designed to measure Halsteads metrics. It is quite reasonable to question the use of Halsteads metrics at anything other than the method level and to view them as measures of the structural complexity of the individual methods. Perhaps the only proper use of the higher level measures of these metrics is to act as a pointer to those packages and/or classes that may contain methods with values for these metrics that are outside the acceptable range. It is probably telling that in Henderson-Sellers standard text on OO metrics (Object Oriented Metrics – Measures of Complexity, Prentice-Hall 1996) he only briefly mentions the Halstead metrics as measures of code complexity. (Henderson-Sellers also includes a section in one of the early chapters entitled ‘Into the Quagmire’!) .
Halstead Effort (and less commonly Halstead Volume) is used in the calculation of the Maintainability index - a compound metric which also uses cyclomatic complexity, lines of code and comment lines. There is a discussion on the Maintainability index in sidebar 4.
If you want to find out more about Java metrics you can read our full tutorial here or you can read the individual sections on System and Package Level Metrics, Class Level Metrics and Method Level Metrics.