Blog

Softwaremetriken: Zyklomatische Komplexität

Eines der Ziele der professionellen Softwareentwicklung ist es, qualitativ hochwertigen Code zu erstellen. Aus Sicht des Entwicklers bedeutet das unter anderem, dass Software durch entsprechend gute Strukturierung lesbarer, testbarer und wartbarer wird. Auch aus Sicht des Kunden bringt dies Vorteile. Wartungsaufwände werden verringert und Aufwandsschätzungen für Erweiterungen und Refactorings werden erleichtert.

Qualität darf in diesem Zusammenhang kein leerer Begriff bleiben, unter dem sich jeder Beteiligte etwas anderes vorstellt. Deswegen versucht man, Qualität und die Einhaltung von Qualitätsvorgaben mithilfe von Softwaremetriken zu quantifizieren und messbar zu machen.

Nach Definition des IEEE ist:

Eine Softwaremetrik […] eine Funktion, die eine Software-Einheit in einen Zahlenwert abbildet. Dieser berechnete Wert ist interpretierbar als der Erfüllungsgrad einer Qualitätseigenschaft der Software-Einheit.

Dieser Artikel will nun die Metrik Zyklomatische Komplexität (engl. Cyclomatic Complexity, nachfolgend CC genannt) näher vorstellen. Sie wurde 1976 von Thomas J. McCabe eingeführt, weswegen sie oft auch McCabe-Metrik genannt wird. Sie gibt an, auf wievielen unterschiedlichen Pfaden ein Software-Modul (z.B. eine Klasse oder eine Methode in Java) durchlaufen werden kann. Formal gesehen, wird bei der Berechnung des CC-Wertes einfach nur die Anzahl der binären Verzweigungen des Kontrollflussgraphen ermittelt.

Viele mögliche Pfade bedeuten ein hohes Maß an Komplexität. Hohe Komplexität führt normalerweise dazu, dass Quellcode schwerer zu verstehen und zu testen ist. In der Folge droht die Gefahr, dass sich schon zur Entwicklungszeit schwerwiegende Fehler in den Code einschleichen.

Es lohnt sich also, den CC-Wert kontinuierlich im Auge zu behalten. Zieht man daraus die richtigen Schlüsse, so kann man später eine Menge Aufwand für Tests und Fehlerbeseitigung einsparen. McCabe gab damals einen Wert von 10 an, der innerhalb eines Programms nicht überschritten werden sollte. Code-Artefakte, die diesen Wert übersteigen, sollten einem Refactoring unterzogen werden.

Beispiel: Iteration über Strings

Am Beispiel zweier simpler Java-Methoden soll der Sachverhalt nun ein wenig veranschaulicht werden. Für die Ermittlung des CC-Wertes auf Methodenebene addiert man folgendermaßen auf:

  • Jede Methode hat einen Grundwert von 1.
  • +1 für jedes if, while, do, for, ?:, catch, case.
  • +1 für jedes Vorkommen der Operatoren && oder ||

Angewendet auf Listing 1 ergibt sich ein CC-Wert von 5. Bei Listing 2 konnte dieser Wert durch einige Vereinfachungen auf einen Durchschnittswert von 2,5 verringert werden.

	/**
	 * Print out a list of messages.
	 * @param tokens
	 */
	// complexity++
	public final void iterateComplex(final List<String> tokens) {
		if (tokens == null) {							// complexity++
			return;
		}
		for (final String eachToken : tokens) {			// complexity++
			if (eachToken != null && 		// complexity++ complexity++
					! "".equals(eachToken.trim())) {
				System.out.println(eachToken);
			}
		}
		return;
	}
	

Listing 1: Methode iterateComplex() mit einem CC-Wert von 5

	/**
	 * Null-check for List of messages.
	 * @param messages
	 */
	// complexity++
	public final void callIterateSimple(final List<String> messages){
		if (messages != null){							// complexity++
			iterateSimple(messages);
		}
	}

	/**
	 * Print out a list of messages.
	 * @param messages
	 */
	// complexity++
	public final void iterateSimple(final List<String> messages) {
		for (final String eachMessage : messages) {		// complexity++
			if (! StringUtils.isBlank(eachMessage)) {	// complexity++
				System.out.println(eachMessage);
			}
		}
		return;
	}

Listing 2: 2 neue Methoden mit einem durchschnittlichen CC-Wert von 2,5

In Listing 2 wurde der CC-Wert also halbiert. Erreicht wurde dies durch die Auslagerung von Funktionalitäten. Eine Methode wurde in zwei weniger komplexe Methoden zerlegt. Für die Überprüfung eines Strings zur Vermeidung einer NullpointerException wird in Listing 2 eine Utility-Klasse aus der Apache-Commons-Sammlung verwendet.

Anwendung in der Praxis

Das obige Beispiel wurde bewusst sehr einfach gewählt. In der Praxis hat man es für gewöhnlich mit sehr viel komplexerem Quellcode zu tun. In nicht-trivialen Projekten lässt sich ein gewisses Maß an Komplexität normalerweise nicht vermeiden. Außerdem ist der Umfang der Software meist so hoch, dass man sie ohne spezielle Werkzeuge nicht analysieren kann.

Mit dem Einsatz von Werkzeugen wie etwa Sonar ist es jedoch ein Leichtes, große Mengen Quellcode kontinuierlich zu analysieren, um sich danach die Code-Artefakte mit der höchsten Komplexität einmal genauer anzusehen. Dort finden sich meist gute Kandidaten für ein Refactoring. Nach dem Motto Teile und Herrsche kann komplexer Quellcode dann in mehrere, einfacher zu handhabende Segmente aufgeteilt werden. So helfen Softwaremetriken, die Qualität von Software gezielt zu verbessern und die Komplexität im Quellcode beherrschbarer zu machen.

Für den theoretischen Background siehe den zugehörigen Artikel in der Wikipedia oder das Buch Abenteuer Softwarequalität: Grundlagen und Verfahren für Qualitätssicherung und Qualitätsmanagement von Kurt Schneider aus dem dpunkt.verlag.

Über den Autor

Ein Kommentar

  1. In Listing 2 ist die CC für beide Methoden zusammen ebenfalls 5. Der Testumfang sinkt durch die Aufteilung erst dann, wenn eine weitere Funktionalität hinzugefügt werden soll, die auf bestehenden Code zurückgreifen kann.

Antwort hinterlassen