Design Patterns and Cyclomatric Complexity
What
is Cyclomatic Complexity?
Cyclomatic complexity is a software metric used to measure the complexity of a program. These metric, measures independent paths through program source code. Independent path is defined as a path that has atleast one edge which has not been traversed before in any other paths. Cyclomatic complexity can be calculated with respect to functions, modules, methods or classes within a program.
In the graph, Nodes represent processing tasks while edges represent control flow between the nodes.
Below are Flow diagrams for statements like if-else,
While, until and normal sequence of flow.
Mathematical representation: Mathematically, it is set of independent paths through the graph diagram. The complexity of the program can be defined as –
V(G) = E – N + 2
Where,
E – Number of edges
N – Number of Nodes
V (G) = P + 1
Where P = Number of predicate nodes (node that contains condition)
Simple Example:
Below is a simple program as an example:
IF A = 354
THEN IF B > C
THEN
A = B
ELSEA=
C
ENDIF
ENDIF
Print
A
Second Example
01.i = 0;
02.
03.n=4; //N-Number of
nodes present in the graph
04.
05.while (i<n-1)
do
06.
07.j = i + 1;
08.
09.while (j<n)
do
10.
11.if A[i]<A[j]
then
12.
13.swap(A[i], A[j]);
14.
15.end do;
16.
17.i=i+1;
18.
19.end do;
Flow graph for this program will be
Computing mathematically,
- V(G) = 9 – 7 + 2 = 4
- V(G) = 3 + 1 = 4 (Condition nodes are 1,2 and 3 nodes)
- Basis Set – A set of possible execution path of a program
- 1, 7
- 1, 2, 6, 1, 7
- 1, 2, 3, 4, 5, 2, 6, 1, 7
- 1, 2, 3, 5, 2, 6, 1, 7
Complexity
Number V(G)
|
Meaning
|
1-10
|
Structured and well written code
High Testability
Cost and Effort is less
|
10-20
|
Complex Code
Medium Testability
Cost and effort is Medium
|
20-40
|
Very complex Code
Low Testability
Cost and Effort are high
|
>40
|
Not at all testable
Very high Cost and Effort
|
Tools for Cyclomatic Complexity calculation:
Examples of tools are
- OCLint – Static code analyzer for C and Related Languages
- devMetrics – Analyzing metrics for C# projects
- Reflector Add In – Code metrics for .NET assemblies
- GMetrics – Find metrics in Java related applications
- NDepends – Metrics in C# and Java applications
Design patterns and their impact on cyclomatic complexity
A design pattern describes a solution to a recurring
software engineering problem at the design level. Design patterns aim to
promote reusability of successful designs and architectures.
Table 1 lists the design patterns
with the two classifications mentioned above.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Although design patterns focus on
reusability and extensibility, most design patterns, particularly behavioural patterns, can assist in the
reduction of cyclomatic complexity. This is accomplished by organising
participants such that each solves a part of the problem resulting in simpler
control flows within each participant hence lowering their cyclomatic
complexity.
Chain of Responsibility:
For Example, A calculator could be implemented in single
method (say, Calculate(double FirstValue, double SecondValue, string Operator)
like this:
Code A:
switch(operator){
case '+':
result = operand1 + operand2;
break;
case '-':
result = operand1 - operand2;
break;
case '*':
result = operand1 * operand2;
break;
case '/':
if (Math.abs(operand2) >
0.0){
result = operand1 / operand2;
} else {
throw new ArithmeticException(
"Numerator is
zero.");
}
break;
default:
throw new
IllegalArgumentException(
operator + "
unknown.");
}
return result;
This code could be refectored into a chain of
Responsibitlity, as show below:
Chain of Responsibility consists of a chain of loosely
coupled objects with responsibilities between objects kept specific and
minimal. These include knowing the task they can perform and the next object in
the chain to which a message can be propagated if they cannot process the
message. Client objects need not know which object in the chain has to be sent
a message to perform a particular task. Instead, clients only know of the
object at the head of the chain and they send the message to it. That object
processes the message if it can otherwise it passes it on to the next object in
the chain.
Code B:
Processor Class:
public abstract class Processor {
protected Processor successor;
public Processor(){
successor = null;
}
public void setSuccessor(Processor successor){
this.successor = successor;
}
protected abstract boolean canProcess(char operator);
protected abstract double process(double operand1, double operand2);
public double calculate(double operand1,double operand2, char operator)
{
double result;
if (canProcess(operator))
result = process(operand1, operand2);
else
{
if (null != successor)
result = successor.calculate(operand1, operand2, operator);
else
throw new Exception("No successor set");
}
return result;
}
}
Inherited Adder , Subtractor, Multiplier and Divider classes
.
public class Adder : Processor
{
protected boolean canProcess(char operator)
{
return operator == '+';
}
protected double process(double operand1,double operand2)
{
return operand1 + operand2;
}
}
public class Subtractor : Processor
{
protected boolean canProcess(char operator)
{
return operator == '-';
}
protected double process(double operand1,double operand2)
{
return operand1 - operand2;
}
}
public class Multiplier : Processor
{
protected boolean canProcess(char operator)
{
return operator == '*';
}
protected double process(double operand1,double operand2)
{
return operand1 * operand2;
}
}
public class Divider : Processor
{
protected boolean canProcess(char operator)
{
return operator == '/';
}
protected double process(double operand1, double operand2)
{
double result;
if (Math.abs(operand2) > 0.0)
result = operand1/operand2;
else
throw new ArithmeticException("Divide by zero.");
return result;
}
}
Here is Calculator Class.
public class Calculator
{
private Processor processor;
public Calculator()
{
processor = null;
}
public void setProcessor(Processor processor)
{
this.processor = processor;
}
public double calculate(double operand1,double operand2,char operator) {
return processor.calculate(operand1,operand2, operator);
}
}
In addition to Chain of Responsibility, Processor and its subclasses
also implement the Template Method pattern. calculate method is a concrete method in the Processor base class.
However, both canProcess
and process
methods are abstract methods, specialised implementations of which exist in the
subclasses of Processor.
The code “Code B” results in more classes than the code in “Code
A” but these classes are more cohesive and have lower individual cyclomatic
complexities. The cyclomatic complexities of all methods of Adder, Subtractor and Multiplier classes are 1
whereas that of the process
method of the Divider
class is 2. The cyclomatic complexities of all instance methods of the Calculator class are also 1.
The main method
of the Calculator
class and the calculate
method of the Processor
class have the highest cyclomatic complexities of 3.
Strategy Desing Pattern:
The Strategy pattern allows the definition of a family of
algorithms such that they are interchangeable within an application. Thus it
lets algorithms vary independently from the clients that use it. Strategy is
typically used to configure a class with one of many behaviours, when different
variants of an algorithm are needed or when a class defines may behaviours and
these appear as multiple conditional statements in its operations
Folllowing is the class diagram of the calculator application
implemented using a variation of the Strategy pattern. The Processor interface
represents the abstract strategy in the application:
public interface Processor
{
public double process(double operand1,double operand2);
}
public class Adder : Processor
{
public double process(double operand1,double operand2)
{
return operand1 + operand2;
}
}
public class Subtractor: Processor
{
public double process(double operand1,double operand2)
{
return operand1 - operand2;
}
}
public class Multiplier : Processor
{
public double process(double operand1,double operand2)
{
return operand1 * operand2;
}
}
public class Divider : Processor
{
public double process(double operand1,double operand2)
{
double result;
if (Math.abs(operand2) > 0.0)
result = operand1/operand2;
else
throw new ArithmeticException("Divide by zero");
return result;
}
}
Adder, Subtractor, Multiplier and Divider classes (above) contain only one method, process. For Adder, Subtractor and Multiplier, this method has a cyclomatic complexity of 1 whereas for the Divider, it has a cyclomatic complexity of 2.
Calculate the Cyclomatric Complexity V(G) for the following:
Comments
Post a Comment