## Related Links - [RFC 0002](../rfcs/0002-cohesion-and-coupling.md), step 3; [PR#36](2026-10-04-PR%2336-cohesion-coupling-measured.md), [PR#37](2026-10-04-PR%2337-cohesion-coupling-city.md) ## The Problem Since PR#36 cohesion and coupling are measured and since PR#37 shown, but nothing kept a class from falling further apart or depending on ever more classes. The first measurement of hsadmin-ng also showed that LCOM4 as published counts every method without state as a group of its own: of 66 classes with LCOM4 of 2 or more, most owed it to a `toString` through a static helper, a bean factory or the methods of an anonymous class. ## The Scenarios - `@MC-CK-010` a group of methods that uses no instance field at all does not count; a class with only such methods has no LCOM4. - `@MC-CK-011` the gate judges LCOM4 and outgoing coupling against `maxLcom4` and `maxEfferentCoupling`; a violation names class and dimension; a class exactly at a limit passes. - `@MC-CK-012` a dimension without a limit is reported, not judged; without any limit the gate is inactive, as before. ##### Verified by - [DesignAnalysisTest](../../werkcrappit-pit/src/test/java/de/javagil/werkcrappit/pit/design/DesignAnalysisTest.java) `methodsWithoutStateDoNotCount` - [GateTest](../../werkcrappit-pit/src/test/java/de/javagil/werkcrappit/pit/gate/GateTest.java) `cohesionAndCouplingHaveLimitsOfTheirOwn`, `aDimensionWithoutALimitIsOnlyReported`, `classLimitsNeedTheSectionClasses` - [GateFunctionalTest](../../werkcrappit-gradle-plugin/src/functionalTest/java/de/javagil/werkcrappit/gradle/GateFunctionalTest.java) `cohesionAndCouplingLimitsReachTheGate` ## The Solution - `Cohesion` keeps only the groups that use an instance field. - `GateConfig` has the optional limits `maxLcom4` and `maxEfferentCoupling`; the gate judges the methods only with `maxMutationCrap` and the classes only with one of the new limits. The failure line counts "limit(s) exceeded", as it now covers classes too. - The Gradle DSL has `maxLcom4` and `maxEfferentCoupling`; a limit below its minimum fails the check with a message. - werkcrappit-pit applies the Gradle plugin 0.19.0, so its own city shows the metrics of step 2. - Version 0.20.0. ## Follow-up PRs - Ratchets for werkcrappit itself, once 0.20.0 is released: `maxLcom4 = 3`, `maxEfferentCoupling = 21`. - hsadmin-ng, in its pilot branch: `maxLcom4 = 4`, `maxEfferentCoupling = 26`. ## Attachments ### LCOM4 and Ce, measured from the class files on 2026-10-04 | | LCOM4 max, as published → without stateless groups | Classes with LCOM4 of 2 or more | Ce max | |---|---|---|---| | hsadmin-ng (267 classes, generated code left out) | 12 → 4 | 66 → 19 | 26 (`HsAccountController`) | | werkcrappit-pit (58 classes) | 3 → 3 | 9 → 4 | 21 (`MutationCrapListener`) | The top of hsadmin-ng now shows real candidates: `HsOfficeContact` (4) with its independent e-mail, phone and postal address, `HsOfficeRelationSearchCriteria` (4) with four independent search fields. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: mhoennig <michael@hoennig.de> Reviewed-on: #38
138 lines
7.3 KiB
Gherkin
138 lines
7.3 KiB
Gherkin
# language: de
|
|
Funktionalität: Kohäsion und Kopplung je Klasse neben Mutation-CRAP
|
|
Als Entwickler möchte ich sehen, wie gut eine Klasse zusammenhält und wie stark sie von anderen abhängt,
|
|
ohne dass diese Werte die Mutation-CRAP-Metrik verändern.
|
|
|
|
@MC-CK-001
|
|
Szenario: Eine Klasse aus zwei unabhängigen Teilen hat LCOM4 2
|
|
… damit man sieht, welche Klasse sich entlang ihrer Teile aufteilen ließe.
|
|
|
|
Angenommen eine Klasse Konto mit den Feldern saldo und inhaber
|
|
Und den Methoden einzahlen und abheben, die nur saldo nutzen
|
|
Und der Methode umbenennen, die nur inhaber nutzt
|
|
Wenn der Report geschrieben wird
|
|
Dann hat Konto im Abschnitt "classes" lcom4 2
|
|
Und die Methodengruppen [abheben, einzahlen] und [umbenennen]
|
|
|
|
@MC-CK-002
|
|
Szenario: Konstruktoren, generierte und statische Methoden verbinden nichts
|
|
… damit LCOM4 nicht schon deshalb 1 ist, weil der Konstruktor jedes Feld setzt.
|
|
|
|
Angenommen die Klasse Konto aus @MC-CK-001
|
|
Und ein Konstruktor, der saldo und inhaber setzt
|
|
Und eine von Lombok generierte Methode toString, die beide Felder liest
|
|
Und eine statische Methode, die beide Felder einer Instanz liest
|
|
Wenn der Report geschrieben wird
|
|
Dann hat Konto weiterhin lcom4 2
|
|
|
|
@MC-CK-003
|
|
Szenario: Aufrufe, Getter und Lambdas verbinden Methoden
|
|
… damit LCOM4 die Zusammenarbeit der Methoden zählt, nicht nur gemeinsame Felder.
|
|
|
|
Angenommen eine Klasse Bestellung mit den Feldern positionen und status
|
|
Und einer Methode summe, die positionen nur über einen Getter getPositionen liest
|
|
Und einer Methode abschliessen, die status setzt und summe aufruft
|
|
Und einer Methode drucken, deren Lambda status liest
|
|
Wenn der Report geschrieben wird
|
|
Dann hat Bestellung lcom4 1
|
|
|
|
@MC-CK-004
|
|
Szenario: Verschachtelte Klassen zählen für ihre Top-Level-Klasse, eine Klasse ohne Instanzmethoden hat kein LCOM4
|
|
… damit jedes Gebäude der Code-City genau einen Wert hat und eine reine Konstantenklasse nicht als zusammenhanglos gilt.
|
|
|
|
Angenommen eine Klasse Lager mit einer verschachtelten Klasse Lager.Fach
|
|
Und Lager.Fach hat zwei Methoden, die nichts gemeinsam nutzen
|
|
Und eine Klasse Konstanten mit nur statischen Feldern und Methoden
|
|
Wenn der Report geschrieben wird
|
|
Dann gibt es im Abschnitt "classes" einen Eintrag Lager, keinen für Lager$Fach
|
|
Und Lager.Fach geht mit seinen Methoden in das LCOM4 von Lager ein
|
|
Und Konstanten hat lcom4 null, nicht 0
|
|
|
|
@MC-CK-005
|
|
Szenario: Ce und Ca zählen die anderen Top-Level-Klassen des Projekts
|
|
… damit man sieht, welche Klasse von vielen anderen abhängt und von welcher viele abhängen.
|
|
|
|
Angenommen BestellService nutzt Bestellung, Bestellung.Position, Kunde und java.util.List
|
|
Und Rechnung nutzt Bestellung
|
|
Wenn der Report geschrieben wird
|
|
Dann hat BestellService efferentCoupling 2: Bestellung und Kunde
|
|
Und Bestellung hat afferentCoupling 2: BestellService und Rechnung
|
|
Und java.util.List zählt nicht, Bibliotheken und das JDK gehören nicht zum Projekt
|
|
|
|
@MC-CK-006
|
|
Szenario: Der Report führt Kohäsion und Kopplung neben Mutation-CRAP, ohne diese zu verändern
|
|
… damit Gate, Code-City und Leser alle drei Dimensionen aus einer Quelle lesen.
|
|
|
|
Angenommen ein PIT-Lauf über ein Projekt, dessen Code-Roots auch Klassen ohne Mutanten enthalten
|
|
Wenn der Report geschrieben wird
|
|
Dann hat mutation-crap.json die schemaVersion 2 und ist gültig gegen das mitgelieferte Schema
|
|
Und der Abschnitt "classes" führt jede Top-Level-Klasse der Code-Roots mit lcom4, Methodengruppen, efferentCoupling und afferentCoupling, nach Namen sortiert
|
|
Und die Werte der Methoden, ihre Mutation-CRAP und ihre Zustände sind dieselben wie mit schemaVersion 1
|
|
Und mutation-crap.md zeigt die zehn Klassen mit dem höchsten LCOM4 und die zehn mit dem höchsten efferentCoupling
|
|
Und mutationCrapCheck liest den Report der Version 2
|
|
|
|
@MC-CK-007
|
|
Szenario: Die Zugriffsmethoden eines Records zählen wie generierte Methoden nicht
|
|
… damit ein Record nicht schon wegen seiner Komponenten als zusammenhanglos gilt, Lombok-Klassen gleich.
|
|
|
|
Angenommen ein Record Buchung mit den Komponenten betrag, waehrung und text
|
|
Und den eigenen Methoden istSoll, die betrag nutzt, und inEuro, die betrag und waehrung nutzt
|
|
Und ein Record Adresse mit drei Komponenten und ohne eigene Methoden
|
|
Wenn der Report geschrieben wird
|
|
Dann hat Buchung lcom4 1 mit der Methodengruppe [inEuro, istSoll]
|
|
Und Adresse hat lcom4 null
|
|
|
|
@MC-CK-008
|
|
Szenario: Die Code-City zeigt Kohäsion und Kopplung als eigene Metriken
|
|
… damit man im Viewer die Farbe von mutation_crap auf lcom4 oder efferent_coupling umstellen kann.
|
|
|
|
Angenommen ein Report mit dem Abschnitt "classes"
|
|
Wenn der CodeCharta-Export ausgeführt wird
|
|
Dann hat jedes Gebäude die Metriken lcom4, efferent_coupling und afferent_coupling
|
|
Und lcom4 ist "relative": ein Package zeigt den Median seiner Klassen
|
|
Und efferent_coupling und afferent_coupling sind "absolute": ein Package zeigt ihre Summe
|
|
Und ein Gebäude ohne LCOM4 hat keinen Wert lcom4, nie 0, und erscheint in der Farbe lcom4 grau
|
|
|
|
@MC-CK-009
|
|
Szenario: Die Report-Seite öffnet die Stadt auch mit Kohäsion oder Kopplung als Farbe
|
|
… damit man die drei Dimensionen mit einem Klick nacheinander ansehen kann.
|
|
|
|
Angenommen die Report-Seite wird über http(s) ausgeliefert
|
|
Wenn sie die Links zum Viewer bildet
|
|
Dann gibt es neben "Mutation-CRAP" die Links "Kohäsion" und "Kopplung"
|
|
Und "Kohäsion" färbt nach lcom4: grün bei 1, gelb bei 2, rot ab 3
|
|
Und "Kopplung" färbt nach efferent_coupling, mit den Farbgrenzen des Viewers
|
|
Und die Tabelle der Seite erklärt lcom4, efferent_coupling und afferent_coupling in einem Satz je Metrik
|
|
|
|
@MC-CK-010
|
|
Szenario: Methoden ohne jeden Zustand zählen nicht für LCOM4
|
|
… damit toString über einen statischen Helfer, Bean-Fabriken und Konstanten-Getter keine Aufteilung vortäuschen.
|
|
|
|
Angenommen die Klasse Konto aus @MC-CK-001
|
|
Und eine Methode toString, die nur einen statischen Helfer aufruft
|
|
Und eine Methode waehrung, die eine Konstante zurückgibt
|
|
Wenn der Report geschrieben wird
|
|
Dann hat Konto weiterhin lcom4 2 mit den Gruppen [abheben, einzahlen] und [umbenennen]
|
|
Und eine Klasse, deren Methoden alle keinen Zustand nutzen, hat lcom4 null
|
|
|
|
@MC-CK-011
|
|
Szenario: Das Gate prüft LCOM4 und die ausgehende Kopplung gegen eigene Grenzwerte
|
|
… damit eine Klasse nicht unbemerkt weiter zerfällt oder an immer mehr Klassen hängt.
|
|
|
|
Angenommen werkcrappit.maxLcom4 ist 3 und werkcrappit.maxEfferentCoupling ist 20
|
|
Und eine Klasse Lager hat lcom4 4, eine Klasse Service hat efferentCoupling 21
|
|
Wenn mutationCrapCheck läuft
|
|
Dann schlägt der Build fehl
|
|
Und die Meldung nennt "Lager has lcom4 4 > 3" und "Service has efferentCoupling 21 > 20"
|
|
Und eine Klasse genau auf dem Grenzwert besteht, wie bei maxMutationCrap
|
|
|
|
@MC-CK-012
|
|
Szenario: Ohne Grenzwert wird eine Dimension nur berichtet
|
|
… damit ein Projekt Kohäsion und Kopplung erst ansehen kann, bevor es sie begrenzt.
|
|
|
|
Angenommen werkcrappit.maxMutationCrap ist gesetzt, maxLcom4 und maxEfferentCoupling sind es nicht
|
|
Und eine Klasse hat lcom4 12 und efferentCoupling 40
|
|
Wenn mutationCrapCheck läuft
|
|
Dann urteilt das Gate nur über Mutation-CRAP
|
|
Und ohne irgendeinen Grenzwert ist das Gate inaktiv, wie bisher
|