## 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
75 lines
4.1 KiB
Gherkin
75 lines
4.1 KiB
Gherkin
# 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
|