skip to content

How is cohesion measured in practice — what does the LCOM (Lack of Cohesion of Methods) family of metrics compute, and how does it map onto the classic cohesion spectrum from coincidental to functional?

level: middleimportance: should knowfreq 20%

answer

  1. Cohesion = things inside belong together (co-change)
  2. LCOM from the method × field matrix
  3. LCOM4 = connected components; 1 = good, N = split into N
  4. Spectrum: coincidental → logical → temporal → procedural → communicational → sequential → functional
  5. Lies on DTOs and stateless services; use VCS co-change too

basics

~20 s

LCOM looks at which methods of a class touch which fields. If methods share fields, the class is cohesive; if they form separate groups touching disjoint fields, the class is really several classes glued together. High LCOM = low cohesion, a hint to split.

solid answer

~60 s

Cohesion means how strongly the elements inside one module belong together. The LCOM family operationalizes it for a class using the method-field usage matrix. **LCOM1/2** (Chidamber–Kemerer) count method pairs sharing no field minus pairs that do share one; **LCOM4** builds a graph where methods are nodes, edges join methods sharing a field or calling each other, and reports the number of **connected components** — LCOM4 = 1 means cohesive, LCOM4 = 3 means the class contains three independent clusters and should probably be three classes; **LCOM-HS** (Henderson-Sellers) normalizes to roughly [0, 2] where 0 is perfect cohesion. Qualitatively these map onto Stevens/Myers/Constantine's spectrum, worst to best: coincidental (a `Utils` bag), logical (a switch over 'kinds'), temporal (an `init()` that does unrelated startup work), procedural, communicational (operations on the same data), sequential (output of one feeds the next), and **functional** (everything contributes to one well-defined task) — the target. Caveats: LCOM is blind to data-free classes, misfires on records/DTOs, on classes using dependencies rather than fields, and rewards field-touching over meaning.

code

text · 11 lines
text
Method x field matrix (LCOM4 = connected components)

            balance currency ownerEmail mailer
deposit        X       X
withdraw       X       X
sendStatement                   X         X
updateEmail                     X

edges: deposit-withdraw (balance, currency)
       sendStatement-updateEmail (ownerEmail)
components = 2  ->  LCOM4 = 2  ->  extract a second class

go deeper

for a junior

Define cohesion, name the worst (coincidental) and best (functional) points on the spectrum, and say that LCOM looks at methods sharing fields.

for a middle

Explain LCOM4 as connected components with a worked matrix and the extract-class refactoring it implies.

for a senior

Cover the metric's blind spots (stateless classes, DTOs, constructors) and complement it with version-control co-change analysis and change-reason (SRP) reasoning.

for a principal

Move the discussion to module and service cohesion — bounded contexts, team ownership, and co-change analytics — and treat class-level LCOM as a low-signal input into a review backlog, never a gate.

### What cohesion means **Cohesion** = the degree to which the elements inside a module belong together. Coupling is a property *between* modules; cohesion is a property *within* one. The design goal is the pair: low coupling, high cohesion. Connascence phrases the same goal as "maximize connascence within a boundary, minimize it across boundaries". The modern restatement is the Single Responsibility Principle in its later form: *gather together the things that change for the same reason; separate things that change for different reasons.* Cohesion is really about **co-change**, not about topic similarity. ### The classic cohesion spectrum (Stevens, Myers, Constantine, 1974) Worst to best: 1. **Coincidental** — elements grouped for no reason at all. `Utils`, `Helpers`, `Common`, `Misc`. Nothing in it relates to anything else in it. This is the canonical worst case and it also tends to have huge afferent coupling, making it a change amplifier. 2. **Logical** — grouped because they are the same *kind* of thing, selected by a flag or switch: an `IOHandler` handling disk, network, and keyboard input in one switch. Callers pass a control flag — control coupling — and every branch drags in unrelated dependencies. 3. **Temporal** — grouped because they happen at the same *time*: `initialize()` that opens a DB pool, warms a cache, and registers metrics. They change for entirely different reasons. 4. **Procedural** — grouped because they execute in a certain order as part of a procedure, but operate on unrelated data. 5. **Communicational** — grouped because they operate on the same data (all methods that read/write the customer record). 6. **Sequential** — grouped because the output of one is the input of the next (parse → validate → transform). 7. **Functional** — every element contributes to a single, well-defined task, and nothing else does. The target. A useful extra axis is **domain cohesion**: even a technically functional class can be misplaced if it changes on a different business rhythm than its neighbours. ### LCOM: turning that into a number All LCOM variants start from the same matrix: rows = methods of a class, columns = instance fields, cell = "this method reads or writes this field". - **LCOM1 (CK, 1991)** — count pairs of methods sharing **no** field. Bigger = worse. Problem: it grows with the square of method count, so large classes score badly regardless of design. - **LCOM2** — `P − Q` where P = pairs sharing no field, Q = pairs sharing at least one, floored at 0. Same scaling issue and a large dead zone at 0. - **LCOM4 (Hitz & Montazeri)** — the practically useful one. Build an undirected graph: nodes = methods; edge between two methods if they access a common field **or** one calls the other. LCOM4 = the number of **connected components**. - 1 → cohesive class. - 0 → no methods. - N > 1 → the class is N independent clusters; the mechanical refactoring is "extract class" per component. - **LCOM-HS (Henderson-Sellers)** — `(mean number of methods per field − m) / (1 − m)` where m = method count. Normalized to about [0, 2]; 0 = every method touches every field (perfect), 1 = no sharing, > 1 = pathological. Many tools (SonarQube historically, NDepend, PMD, ck for Java, various linters) report one of these; **always check which variant**, because they are not comparable. ### Where LCOM lies to you (the part that separates a middling from a strong answer) - **Data-free classes.** A stateless service class or a class that only orchestrates injected collaborators has no fields to share, so LCOM screams "incohesive" even when the class is perfectly focused. LCOM4's "or one calls the other" edge softens this, the pair-based variants do not. - **DTOs / records / entities.** A value object with 12 fields and 12 accessors that each touch one field scores terribly and is perfectly fine. - **Constructors and `toString`/`equals`.** They touch all fields and artificially connect everything; good tools exclude them, bad ones don't. - **Inheritance and static methods** are handled inconsistently across tools. - **Semantics are invisible.** Two methods can share a field yet belong to different responsibilities; LCOM cannot tell. - **Class-scoped only.** It says nothing about package/module/service cohesion, which is where architectural damage actually happens. Because of this, LCOM is best used as a **candidate finder**, not a verdict: sort classes by LCOM4 descending, look at the top 20 by hand. ### Better signals for module-level cohesion - **Co-change / logical coupling from version control.** Mine commit history: files that change together but live in different modules indicate misplaced cohesion; a module whose files never change together indicates a coincidental grouping. This is the empirical measure the static metrics approximate. - **Change-reason analysis.** Which stakeholders/business rules cause this module to change? Two answers = two modules (SRP). - **Bounded contexts / ubiquitous language** for service-level cohesion. - **Fan-in/fan-out imbalance:** a module with very high Ca and very high Ce is nearly always a coincidental grab-bag. ### Working example ``` class Account fields: balance, currency, ownerEmail, mailer methods: deposit() -> balance, currency withdraw() -> balance, currency sendStatement() -> ownerEmail, mailer updateEmail() -> ownerEmail ``` Graph components: {deposit, withdraw} and {sendStatement, updateEmail} → **LCOM4 = 2**. The class mixes money mechanics with notification/contact concerns. Extract `AccountNotifications` (or move notification out entirely) → both classes become LCOM4 = 1.

  • Why can a perfectly designed stateless service class score badly on LCOM?
    Pair-based LCOM variants only see shared instance fields. A class that holds no state and only orchestrates injected collaborators has almost no field sharing, so it looks incohesive. LCOM4 partially compensates by adding edges for method-to-method calls, but the metric still needs human interpretation.
  • What is a stronger, empirical alternative to LCOM for judging module cohesion?
    Logical coupling mined from version control: which files actually change in the same commits. Files that co-change across module boundaries reveal misplaced responsibility, and a module whose files never co-change is a coincidental grouping. It measures the thing LCOM only proxies.
  • How does cohesion relate to the Single Responsibility Principle?
    SRP in its mature phrasing — gather what changes for the same reason, separate what changes for different reasons — is a statement about cohesion at the actor/change-reason level. Functional cohesion is the SRP-satisfying end of the classic spectrum.

A toolbox is functionally cohesive: every item is for one job, and you carry the whole box to that job. A kitchen junk drawer is coincidentally cohesive: batteries, takeout menus, a screwdriver, dead pens. Nobody ever needs 'the drawer' — they need one thing in it, and everyone rummages through everyone else's stuff to get it. LCOM is roughly the count of distinct piles you would form if you emptied the drawer onto the table.

saying these in an interview costs you the question

  • Treating a high LCOM value as proof of a design defect without inspecting the class
  • Comparing LCOM numbers produced by different variants or different tools
  • Calling a DTO or record incohesive because each accessor touches one field
  • Defining cohesion as 'related topic' rather than 'changes for the same reason'
  • Believing a 'Utils' or 'Common' package is harmless organization rather than the textbook coincidental-cohesion smell

context