Wednesday, 23 September 2026

Cohesion: does a class do one thing

Part of a series on how software architecture shapes AI driven code degradation. This post explains a group of measurements on their own. Each metric gets its definition, its figure, and its numbers.

Lack of cohesion, average class (LCOM)

ck_lcom_mean  ·  ↓ lower is better  ·  Chidamber & Kemerer 1994, via the CK tool

Lack of cohesion, average class (LCOM)

Line is the mean of ten runs. Band is one standard deviation. Click for full size.

Counts the method pairs in a class that share no field, against the pairs that do. A class whose methods all touch the same state is one idea. A class whose methods touch disjoint state is several classes wearing one name. This is the numeric version of what the plain-English cohesion prompt asked for in words.

How it is calculated
LCOM(C) = max(0, |P| − |Q|)
ck_lcom_mean = mean over classes of LCOM(C)

Where. P is the set of method pairs in C whose accessed-field sets are disjoint. Q is the set of pairs that share at least one field. Low is cohesive. The measure is unbounded above and grows roughly with the square of the method count, so a large class is penalised twice: once for incoherence and once for being large.

In this harness. Computed by the CK tool, which parses source with Eclipse JDT. Only declared source classes are seen: library types are not, and neither is anything the parser fails on, so the class count in the validity group is worth checking beside any CK figure. The harness reads CK's per-class LCOM column and takes the mean, and separately the maximum.

How to read it. The natural place to check whether asking the agent for cohesion actually produced it.

Careful. Unbounded and method-count sensitive. Read it with the normalised LCOM* below and with tight class cohesion, which has the opposite sign. Agreement across all three is what makes a cohesion claim safe.

Lack of cohesion, worst class

ck_lcom_max  ·  ↓ lower is better  ·  C&K 1994, via CK

Lack of cohesion, worst class

Line is the mean of ten runs. Band is one standard deviation. Click for full size.

The least cohesive class in the codebase. Unlike the mean it cannot be diluted by adding cohesive classes, so it answers whether there is a junk-drawer class in here, rather than whether classes are cohesive on average.

How it is calculated
ck_lcom_max = max over classes of LCOM(C)

Where. Same LCOM as above. Because it is unbounded and grows with method count, the worst class is often simply the largest one, so read it beside the class-weight concentration index.

In this harness. Computed by the CK tool, which parses source with Eclipse JDT. Only declared source classes are seen: library types are not, and neither is anything the parser fails on, so the class count in the validity group is worth checking beside any CK figure.

How to read it. A step change here means a junk drawer appeared.

Lack of cohesion, normalised (LCOM*)

ck_lcom_star_mean  ·  ↓ lower is better  ·  Henderson-Sellers 1996, via CK

Lack of cohesion, normalised (LCOM*)

Line is the mean of ten runs. Band is one standard deviation. Click for full size.

A redesign of LCOM that is bounded roughly to the range 0 to 1 and does not simply grow with the number of methods. Because it is normalised, a difference here is a difference in shape rather than in class size, which is exactly the correction the original LCOM needs.

How it is calculated
LCOM* (C) = ( (1/a) Σj=1..a μ(aj) − m ) / ( 1 − m )

Where. m is the number of methods in C and a the number of fields. μ(aj) is how many of those methods access field j. So the first term is the average number of methods per field. If every method touches every field the numerator is m − m = 0 and the result is 0, meaning perfectly cohesive. If each field is touched by exactly one method the result approaches 1. Low is cohesive. Undefined for a class with fewer than two methods or no fields.

In this harness. Computed by the CK tool, which parses source with Eclipse JDT. Only declared source classes are seen: library types are not, and neither is anything the parser fails on, so the class count in the validity group is worth checking beside any CK figure.

How to read it. The version to prefer when comparing arms whose classes differ in size.

Tight class cohesion (TCC)

ck_tcc_mean  ·  ↑ higher is better  ·  Bieman & Kang 1995, via CK

Tight class cohesion (TCC)

Line is the mean of ten runs. Band is one standard deviation. Click for full size.

The fraction of method pairs in a class that are directly connected through shared field access. It is reported precisely because its direction is inverted relative to LCOM: if a condition improves LCOM and worsens TCC, the improvement is an artefact of one definition rather than a real gain in cohesion.

How it is calculated
TCC(C) = NDC / NP,   NP = m(m − 1) / 2

Where. m is the number of visible methods. NP is therefore every possible pair of them. NDC is the number of pairs that are directly connected, meaning they access at least one instance variable in common. High is cohesive, which is the opposite sign to LCOM. Undefined, and blank, for a class with fewer than two methods.

In this harness. Computed by the CK tool, which parses source with Eclipse JDT. Only declared source classes are seen: library types are not, and neither is anything the parser fails on, so the class count in the validity group is worth checking beside any CK figure.

How to read it. Agreement with LCOM in the opposite direction is what makes a cohesion claim safe. Disagreement means one definition is doing the work.

Loose class cohesion (LCC)

ck_lcc_mean  ·  ↑ higher is better  ·  Bieman & Kang 1995, via CK

Loose class cohesion (LCC)

Line is the mean of ten runs. Band is one standard deviation. Click for full size.

The same as tight cohesion, but it also counts methods connected indirectly, through a chain of other methods. It is always at least as high as the tight version, and the gap between them is informative on its own.

How it is calculated
LCC(C) = (NDC + NIC) / NP

Where. NIC is the number of pairs connected only indirectly: not sharing a field themselves, but linked through a chain of methods that do. NDC and NP are as in TCC. High is cohesive.

In this harness. Computed by the CK tool, which parses source with Eclipse JDT. Only declared source classes are seen: library types are not, and neither is anything the parser fails on, so the class count in the validity group is worth checking beside any CK figure.

How to read it. A large gap between LCC and TCC means the class holds together only through intermediaries, which is weaker cohesion than the LCC figure alone suggests.

Lack of cohesion of the handler class

ck_handler_lcom  ·  ↓ lower is better  ·  C&K 1994, via CK, pinned to the handler class

Lack of cohesion of the handler class

Line is the mean of ten runs. Band is one standard deviation. Click for full size.

Cohesion of the one class both architectures agree is the entry point. Codebase averages can be moved by adding files. This cannot.

How it is calculated
ck_handler_lcom = LCOM(H)

Where. H is the handler class, matched by name in CK's per-class output. Same LCOM definition as the codebase mean. Low is cohesive.

In this harness. Computed by the CK tool, which parses source with Eclipse JDT. Only declared source classes are seen: library types are not, and neither is anything the parser fails on, so the class count in the validity group is worth checking beside any CK figure.

How to read it. The like-for-like cohesion comparison. A controller accumulating rules that touch disjoint state climbs here.

Tight cohesion of the handler class

ck_handler_tcc  ·  ↑ higher is better  ·  Bieman & Kang 1995, via CK, pinned to the handler class

Tight cohesion of the handler class

Line is the mean of ten runs. Band is one standard deviation. Click for full size.

The inverted-sign cohesion check, on the handler class.

How it is calculated
ck_handler_tcc = TCC(H) = NDC(H) / NP(H)

Where. Same TCC definition. High is cohesive. Undefined, and therefore blank, for a handler class with fewer than two methods, because there are no pairs to connect.

In this harness. Computed by the CK tool, which parses source with Eclipse JDT. Only declared source classes are seen: library types are not, and neither is anything the parser fails on, so the class count in the validity group is worth checking beside any CK figure.

How to read it. Sparse by construction in whichever arm keeps its handler minimal. The blanks are a result, not missing data: a handler with one method has no cohesion to measure.

Depth of inheritance tree

ck_dit_mean  ·  ↓ lower is better  ·  Chidamber & Kemerer 1994, via CK

Depth of inheritance tree

Line is the mean of ten runs. Band is one standard deviation. Click for full size.

How deep the class hierarchy goes, on average. It is reported to show that neither architecture is achieving its structure through inheritance, which matters because none of the other metrics on this page would attribute complexity hidden in a hierarchy correctly.

How it is calculated
DIT(C) = number of edges from C up to the root
ck_dit_mean = mean over classes of DIT(C)

Where. A class extending nothing has DIT = 1 in CK's convention, counting Object as the root. Interfaces implemented do not add depth.

In this harness. Computed by the CK tool, which parses source with Eclipse JDT. Only declared source classes are seen: library types are not, and neither is anything the parser fails on, so the class count in the validity group is worth checking beside any CK figure.

How to read it. Flat and low in both arms is the expected and desired result. A rise would mean complexity moved into a hierarchy.

No comments:

Post a Comment