Files
werkcrappit/docs/features/09-change-risk.feature
miandmhoennig 18248cb975
werkator build succeeded after 01:44
werkator/publish build succeeded after 00:11
Change risk per class from Mutation-CRAP, cohesion and coupling (0.22.0) (#41)
## Related Links

- [ADR 0008](../adr/0008-change-risk.md), the formula and its alternatives
- [RFC 0002](../rfcs/0002-cohesion-and-coupling.md), which brought LCOM4 and Ce

## The Problem

The report has three values per class or method, but none says where a change most likely breaks something unnoticed.
Mutation-CRAP per method misses a large class whose many simple methods are weakly tested.
In werkcrappit, `ClassReferences` has no method above 6.8, but 43 % of its mutants survive.

## Non-Goals

- A limit for the change risk: the gate keeps judging the single values (`@MC-CR-005`).
- Any change of Mutation-CRAP, LCOM4 or Ce.

## The Scenarios

The scenarios were agreed one by one on 2026-10-04, in the new feature file `09-change-risk.feature` and in `07-codecharta.feature`.
Name, formula and constants were decided before: "Change risk", Mutation-CRAP over the whole class as the base (option C of three), fixed `k_L = 2` and `k_C = 20`, a band from 30 to 100.

- `@MC-CR-001` change risk is Mutation-CRAP over the whole class: 20 methods of complexity 1 with half their mutants killed give 70, though no method exceeds 2.
- `@MC-CR-002` LCOM4 and Ce raise it, `× (1 + (LCOM4 − 1)/2) × (1 + Ce/20)`, as an exact fraction.
- `@MC-CR-003` only scored methods count, nested classes belong to their top-level class; no scored method, no change risk; no LCOM4 counts as 1.
- `@MC-CR-004` an `INCONCLUSIVE` method or one without complexity voids the change risk of its class, with the reason `method-not-scored:…`.
- `@MC-CR-005` the gate does not judge the change risk, and the methods' Mutation-CRAP stays as it was.
- `@MC-CR-006` `mutation-crap.md` lists the ten classes with the highest change risk, with what makes them risky.
- `@MC-CC-035` the city has the metric `change_risk`, a fourth scenario `change-risk.ccscenario` and a link coloured by it.

##### Verified by

- [ChangeRiskTest](../../werkcrappit-pit/src/test/java/de/javagil/werkcrappit/pit/core/ChangeRiskTest.java)
- [ChangeRisksTest](../../werkcrappit-pit/src/test/java/de/javagil/werkcrappit/pit/design/ChangeRisksTest.java)
- [JsonReportTest](../../werkcrappit-pit/src/test/java/de/javagil/werkcrappit/pit/report/JsonReportTest.java) `theClassEntryCarriesTheChangeRisk`
- [MarkdownReportTest](../../werkcrappit-pit/src/test/java/de/javagil/werkcrappit/pit/report/MarkdownReportTest.java) `theTenClassesWithTheHighestChangeRisk`
- [GateTest](../../werkcrappit-pit/src/test/java/de/javagil/werkcrappit/pit/gate/GateTest.java) `changeRiskIsNotJudged`
- [ReportInputTest](../../werkcrappit-pit/src/test/java/de/javagil/werkcrappit/pit/codecharta/ReportInputTest.java) `theChangeRiskOfTheReportIsReadBackExactly`
- [CodeCityTest](../../werkcrappit-pit/src/test/java/de/javagil/werkcrappit/pit/codecharta/CodeCityTest.java) `theCityShowsTheChangeRisk`, [CodeCityScenariosTest](../../werkcrappit-pit/src/test/java/de/javagil/werkcrappit/pit/codecharta/CodeCityScenariosTest.java) `changeRiskIsTheFourthScenario`, [CodeCityPageTest](../../werkcrappit-pit/src/test/java/de/javagil/werkcrappit/pit/codecharta/CodeCityPageTest.java) `thePageOpensTheCityByChangeRisk`
- [CodeChartaFunctionalTest](../../werkcrappit-gradle-plugin/src/functionalTest/java/de/javagil/werkcrappit/gradle/CodeChartaFunctionalTest.java) `aRealBuildWritesAValidCity`

## The Solution

- `ChangeRisk` in the metric core computes the value as a reduced fraction from the summed complexity and mutants, LCOM4 and Ce; its base is `MutationCrap.of`.
- `ChangeRisks` joins the methods to their top-level classes; `ClassDesign` carries the result as `ClassRisk`.
  `ReportData` does the join, so JSON, Markdown and console read the same values, and the listener's coupling stays at its ratchet of 21.
- `mutation-crap.json` has schema version 3; the gate and the export read version 3.
- `mutation-crap.md` has a section "Change Risk"; the city a metric `change_risk`, a scenario and a link, yellow from 30 and red from 100.
- Version 0.22.0.

## Additional Changes

- `FeatureFilesTest` now also checks the IDs `MC-CK-…` and `MC-CR-…` against the acceptance map; it knew only `MC-…` and `MC-CC-…`.
- The acceptance map is column-aligned, and its note on test locations moved below the table, where it no longer splits it.

## Follow-up PRs

- werkcrappit-pit uses the Gradle plugin 0.22.0: until then the nightly mutation build fails, as its gate reads schema version 2.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: mhoennig <michael@hoennig.de>
Reviewed-on: #41
2026-10-04 16:51:32 +02:00

75 lines
4.1 KiB
Gherkin
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# language: de
Funktionalität: Change-Risk je Klasse aus Mutation-CRAP, Kohäsion und Kopplung
Als Entwickler möchte ich in einem Wert sehen, wie riskant eine Änderung an einer Klasse ist,
ohne dass dieser Wert Mutation-CRAP oder das Gate verändert.
@MC-CR-001
Szenario: Change-Risk rechnet Mutation-CRAP über die ganze Klasse
… damit eine Klasse auffällt, deren Mutanten überleben, auch wenn keine einzelne Methode auffällt.
Angenommen eine Klasse Ablauf mit LCOM4 1 und ohne Abhängigkeiten zu anderen Klassen des Projekts
Und 20 bewerteten Methoden mit Komplexität 1, von denen jede 1 Mutante hat
Und 10 davon werden getötet, 10 überleben
Wenn der Report geschrieben wird
Dann hat jede Methode einzeln Mutation-CRAP höchstens 2
Und Ablauf hat im Abschnitt "classes" die Komplexität 20 und die Mutation-Score 10/20
Und Ablauf hat changeRisk 20² · (1 − 1/2)³ + 20 = 70
@MC-CR-002
Szenario: Kohäsion und Kopplung verstärken das Risiko der Klasse
… damit eine Klasse, die mehrere Dinge tut oder von vielen abhängt, bei gleicher Testqualität riskanter gilt.
Angenommen eine Klasse Abrechnung mit Komplexität 10 und Mutation-Score 1
Und Abrechnung hat lcom4 3 und efferentCoupling 10
Wenn der Report geschrieben wird
Dann hat Abrechnung changeRisk 10 · (1 + (3 − 1)/2) · (1 + 10/20) = 30
Und eine Klasse mit lcom4 1 und efferentCoupling 0 hat changeRisk gleich ihrer Basis
Und changeRisk wird als exakter Bruch berechnet und nur für die Anzeige gerundet
@MC-CR-003
Szenario: Nur bewertete Methoden zählen, verschachtelte Klassen gehören zur Top-Level-Klasse
… damit jedes Gebäude der Code-City genau einen Wert hat und Methoden ohne Mutanten das Risiko nicht verdünnen.
Angenommen eine Klasse Lager mit einer bewerteten Methode der Komplexität 4
Und einer verschachtelten Klasse Lager.Fach mit einer bewerteten Methode der Komplexität 2
Und einer Methode ohne Mutanten und einer durch Ausschlussregel ausgeschlossenen Methode
Wenn der Report geschrieben wird
Dann hat Lager die Komplexität 6, und die Mutanten beider bewerteter Methoden bilden die Mutation-Score
Und es gibt keinen Eintrag Lager$Fach
Und eine Klasse ohne bewertete Methode hat changeRisk null, nicht 0
Und eine Klasse mit lcom4 null zählt bei der Kohäsion wie lcom4 1
@MC-CR-004
Szenario: Eine unklar bewertete Methode sperrt das Change-Risk ihrer Klasse
… damit eine Klasse nicht harmloser erscheint, nur weil ein Teil ihrer Mutanten nicht auswertbar ist.
Angenommen eine Klasse Import mit zwei bewerteten Methoden
Und einer Methode im Zustand INCONCLUSIVE
Wenn der Report geschrieben wird
Dann hat Import changeRisk null
Und der Grund "method-not-scored" mit der betroffenen Methode steht beim Klasseneintrag
Und lcom4, efferentCoupling und afferentCoupling von Import bleiben unverändert
Und dasselbe gilt für eine Methode im Zustand COMPLEXITY_UNAVAILABLE
@MC-CR-005
Szenario: Change-Risk ändert weder Mutation-CRAP noch das Gate
… damit das Gate erklärbar bleibt: jede Verletzung nennt einen Einzelwert und seinen Grenzwert.
Angenommen ein Projekt mit maxMutationCrap 10, maxLcom4 3 und maxEfferentCoupling 20
Und eine Klasse mit changeRisk 500, deren Einzelwerte alle Grenzwerte einhalten
Wenn der Gate-Task läuft
Dann besteht der Build das Gate
Und die Mutation-CRAP-Werte der Methoden sind dieselben wie ohne Change-Risk
Und es gibt keinen Grenzwert für changeRisk
@MC-CR-006
Szenario: Der Markdown-Report nennt die Klassen mit dem höchsten Change-Risk
… damit man sieht, wo eine Änderung am ehesten etwas kaputt macht, und warum.
Angenommen ein Projekt mit 12 Klassen, die ein changeRisk haben, und einer mit changeRisk null
Wenn der Report geschrieben wird
Dann enthält der Markdown-Report einen Abschnitt "Change Risk"
Und dessen Tabelle zeigt die 10 Klassen mit dem höchsten changeRisk, absteigend
Und jede Zeile nennt changeRisk, Komplexität, Mutation-Score, lcom4 und efferentCoupling der Klasse
Und die Klasse mit changeRisk null fehlt in der Tabelle