Files
werkcrappit/docs/rfcs/0002-cohesion-and-coupling.md
miandmhoennig 60d7b42300
werkator build succeeded after 01:38
werkator/publish build succeeded after 00:09
RFC 0002: cohesion and coupling beside Mutation-CRAP (0.16.5) (#34)
## Related Links

- [RFC 0002](../rfcs/0002-cohesion-and-coupling.md)

## The Problem

werkcrappit shows how complex and how well tested a method is, but not how well a class holds together or how strongly classes depend on each other.
Adding both is larger than one pull request.

## The Solution

- RFC 0002 proposes LCOM4 for cohesion and Ca/Ce for coupling per top-level class, beside Mutation-CRAP, never mixed into it, with optional limits `maxLcom4` and `maxEfferentCoupling`.
  The decisions taken on 2026-10-04 are in its section 2, the three implementation steps in section 7, and the two questions settled after the first draft in section 8.
- Version 0.16.5; no change to the plugins.

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

---------

Co-authored-by: mhoennig <michael@hoennig.de>
Reviewed-on: #34
2026-10-04 10:27:38 +02:00

80 lines
5.2 KiB
Markdown

# RFC 0002: Cohesion and Coupling Beside Mutation-CRAP
- Status: proposed
- Date: 2026-10-04
- Author: Michael Hönnig
- Implementation: in three PRs (see section 7)
## 1. Goal
werkcrappit measures how complex a method is and how well its tests catch changes.
Two other main signs of design quality stay invisible: how well the parts of a class belong together, *cohesion*, and how strongly classes depend on each other, *coupling*.
The goal is "high cohesion, low coupling", measured per class, shown in the report and the code city, and with a gate of its own.
## 2. Decisions taken for this RFC
Each was a choice between options; the consequences are in the conversation of 2026-10-04.
- **Separate dimensions, no combined score.**
Mutation-CRAP stays as the specification defines it.
Cohesion and coupling are metrics of their own, each with its own limit.
A failing build always names the dimension; a combined number would hide whether to add tests, split a class or cut dependencies, and its weights would be guesses.
- **Unit: the top-level class**, as for the buildings and the edges of the code city (ADR 0007).
Nested classes and lambdas count for their top-level class.
- **Cohesion: LCOM4** (Hitz and Montazeri, 1995).
The instance methods of a class form a graph: two methods are connected when one calls the other or both use the same instance field.
LCOM4 is the number of connected parts; 1 is a cohesive class, more than 1 a class that could be split along its parts.
Constructors are left out, as they touch every field and would connect everything.
Methods the compiler or a processor like Lombok generates are left out, as JaCoCo filters them; a lambda body counts for its enclosing method.
Static methods and fields are left out.
A class without instance methods, after these rules, has no LCOM4 (`null`), never 0.
- **Coupling: Ca and Ce** between top-level classes of the analysed code, as the edges count them.
Ce (efferent) is the number of other classes a class depends on, Ca (afferent) the number of classes that depend on it.
Libraries and the JDK do not count.
- **Gate on cohesion and outgoing coupling only.**
`maxLcom4` and `maxEfferentCoupling` are new, optional limits.
Ca is shown, never judged: a domain type used everywhere is meant to have a high Ca.
Both limits are ratchets, as `maxMutationCrap` is in werkcrappit's own build.
## 3. Non-goals
- A change of the Mutation-CRAP formula or of its gate.
- Other cohesion measures (LCOM1 to LCOM3, TCC, LCC) and package metrics (instability, abstractness, distance from the main sequence); they may follow once the class values prove useful.
- Kotlin specifics: Kotlin's accessors are generated methods and fall out like Lombok's; anything beyond that waits for the specification's Kotlin step.
## 4. Where the values come from
The values come from the original class files, as the complexity does: no extra test run, no change to PIT.
The PIT listener already reads every analysed class file for the method inventory, and the CodeCharta export already reads their references for the edges.
Reading fields and calls per method is the same ASM pass with a little more bookkeeping.
Ca and Ce need every class of the analysed code, not only the mutated ones; the listener knows the code roots, as the export does.
## 5. The report
`mutation-crap.json` gets a section `classes`: per top-level class `lcom4`, `efferentCoupling`, `afferentCoupling`, and the method groups behind LCOM4, so a reader sees *what* would split.
As the report contract asks for any change of the shape (ADR 0004), `schemaVersion` goes from 1 to 2; the gate of the same release reads version 2.
`mutation-crap.md` gets a table of the classes with the highest LCOM4 and the highest Ce.
## 6. The gate and the code city
- `werkcrappit { maxLcom4 = 1; maxEfferentCoupling = 20 }`, both optional; unset, the dimension is reported, not judged.
`mutationCrapCheck` judges all configured dimensions and names each violation with its dimension.
- The city gets the metrics `lcom4`, `efferent_coupling` and `afferent_coupling`.
`lcom4` is `relative` (a district shows the median), the couplings `absolute`.
The report page links the city as before; a reader picks another colour metric in the viewer.
## 7. Steps
Each is one PR with green CI, a version of its own and Gherkin scenarios agreed one at a time, each with its reason (`… damit …`), in a new feature file `08-cohesion-coupling.feature`.
1. **Measure:** LCOM4, Ca and Ce in the report, schema version 2, a section in the Markdown report.
2. **Show:** the three metrics in the code city.
3. **Judge:** `maxLcom4` and `maxEfferentCoupling` in the gate and the Gradle DSL; werkcrappit's own build and the pilot hsadmin-ng get ratchets from their first reports.
## 8. Questions settled on 2026-10-04
- Getters and setters are methods like any other: a getter connects with every method using its field, as the published LCOM4 has it.
An entity with unrelated fields may thus show LCOM4 above 1; that is often true, and the limit is a judgement per project.
- A dependency on a nested class of another top-level class is a dependency on that top-level class, as for the edges; Ce counts top-level classes only.