Chapter 03 Flashcards — Modularity
flashcards fsa modularity cohesion coupling connascence metrics
What is modularity, and why is it architecturally significant?
?
Modularity is the degree to which a system’s components can be separated, combined, and understood independently — defined as a logical grouping of related code (classes, functions, packages, or services). It is architecturally significant because it directly determines: (1) changeability — whether modifications have bounded or cascading effects; (2) testability — whether components can be tested in isolation; (3) deployability — what can be deployed independently; (4) team autonomy — whether teams can own parts of the system cleanly; and (5) evolvability — whether the architecture can be restructured without heroic effort.
What is cohesion, and what is the goal in terms of cohesion type?
?
Cohesion measures how related the elements within a module are. The goal is functional cohesion — every element in the module contributes to a single, well-defined purpose that can be described in one sentence without the word “and.” The spectrum from worst to best is: coincidental → logical → temporal → procedural → communicational → sequential → functional. Low cohesion indicates that a module has multiple unrelated responsibilities and should be split.
What is LCOM and what does it measure?
?
LCOM (Lack of Cohesion in Methods) measures the degree to which a class’s methods use the class’s instance fields. A class where every method uses every field has LCOM near 0 (high cohesion — one responsibility). A class where methods each use completely different subsets of fields has LCOM near 1 (low cohesion — multiple responsibilities masquerading as one class). LCOM is the primary automatable cohesion metric — it can be computed by static analysis tools and used as an architectural fitness function.
What is afferent coupling (Ca) and what does high Ca imply?
?
Afferent coupling (Ca), also called fan-in, measures how many other modules depend on a given module. High Ca means many modules depend on this one — it is a central, stable component. High Ca implies high responsibility: any breaking change to this module has wide blast radius. High-Ca modules must be maximally stable and should be abstract (interface-based) so dependents are not broken by implementation changes.
What is efferent coupling (Ce) and what does high Ce imply?
?
Efferent coupling (Ce), also called fan-out, measures how many other modules a given module depends on. High Ce means this module is fragile — it can be broken by changes in any of the many modules it depends on. High-Ce modules should be monitored for excessive fragility. The instability formula Ce / (Ca + Ce) quantifies how much of a module’s coupling is outgoing versus incoming.
What is the instability metric (I), how is it calculated, and what do extreme values mean?
?
Instability (I) = Ce / (Ca + Ce), ranging from 0.0 to 1.0. I = 0.0 (maximally stable): no outgoing dependencies — nothing the module depends on can break it; it is used by others but depends on nothing. I = 1.0 (maximally unstable): no incoming dependencies but depends on many others — any dependency change breaks it, yet nothing would break if it disappeared. Architectural health requires matching instability to abstractness along the main sequence.
What is abstractness (A) and how is it calculated?
?
Abstractness (A) = (number of abstract classes + interfaces) / total number of classes, ranging from 0.0 to 1.0. A = 0.0: entirely concrete — all implementation, no abstractions. A = 1.0: entirely abstract — all interfaces/abstract classes, no implementation. High abstractness in a stable module (low I) is healthy: dependents use the interface, so the implementation can change without breaking them. High abstractness in an unstable module (high I) is wasteful — nobody uses it.
What is the Main Sequence and what are the Zone of Pain and Zone of Uselessness?
?
The Main Sequence is the ideal diagonal on the Abstractness/Instability plot where A + I ≈ 1.0 — stable modules are abstract, unstable modules are concrete. Zone of Pain (low I, low A): stable but concrete — many things depend on it, yet it is difficult to change without breaking them (e.g., a widely-used utility with no interfaces). Zone of Uselessness (high I, high A): abstract but unstable — well-designed interfaces nobody actually uses. Distance from Main Sequence D = |A + I − 1| / √2 is the modularity fitness function; values near 0.0 are healthy.
What is the Stable Dependencies Principle?
?
The Stable Dependencies Principle states that modules should depend in the direction of stability — a module should depend on modules that are more stable than itself. If a stable module (I ≈ 0) depends on an unstable module (I ≈ 1), the stable module has inadvertently become fragile: changes in the unstable dependency cascade into what was supposed to be a foundation. This principle guides the direction of dependency arrows in package/component design.
What is connascence, and what two dimensions describe it?
?
Connascence describes the type of knowledge two components must share in order to change together — it provides finer-grained vocabulary than traditional coupling by asking not just “are these coupled?” but “in what way?” The two dimensions are: (1) Strength — how difficult the connascence is to detect and refactor (weak = compiler-caught; strong = runtime behavioral assumption); (2) Locality — how close together the connected elements are (same method = low risk; different services = high risk). The goal is to minimize connascence, minimize its strength, and keep strong connascence local.
What is Connascence of Name (CoN) and why is it acceptable?
?
Connascence of Name (CoN) exists when elements share knowledge of a name — a method name, variable name, or field name. If the name changes, all references must change. CoN is the weakest and most acceptable form of connascence because: compilers detect it immediately; IDEs can refactor it globally in seconds; it is the unavoidable minimum coupling in any system that calls anything. All method calls exhibit CoN — it is not something to eliminate but the baseline to aim for when reducing stronger connascence.
What is Connascence of Meaning (CoM) and how should it be refactored?
?
Connascence of Meaning (CoM) (also Connascence of Convention) exists when elements share knowledge of what a value means — e.g., using integer codes where both sides must know that 1 = “active”, 2 = “inactive”. CoM is dangerous because it is invisible to the type system but causes subtle bugs when either side misinterprets the value. Refactoring: replace raw values with named constants or enums — this converts CoM to CoN (callers now reference a named symbol, not an unlabeled magic value) and makes the meaning explicit and compiler-checked.
What is Connascence of Position (CoP) and why is it risky?
?
Connascence of Position (CoP) exists when elements share knowledge of the order of values — positional parameters in a method signature, or fields in a CSV/fixed-format record where order matters. CoP is risky because: swapping two parameters of the same type compiles cleanly but produces silent incorrect behavior; it is especially dangerous in dynamically-typed languages with no compiler check at all. Refactoring: replace positional parameters with named structs, value objects, or builder patterns — converting CoP to CoN.
What is Connascence of Algorithm (CoA) and when is it an architectural red flag?
?
Connascence of Algorithm (CoA) exists when two elements must independently implement the same algorithm to communicate correctly — e.g., a client and server that each implement the same custom serialization, hashing, or checksum algorithm without sharing code. CoA is an architectural red flag when it crosses component or service boundaries: if either side’s algorithm drifts, communication breaks silently. Refactoring: extract the shared algorithm into a shared library that both sides depend on — converting CoA to CoN (both reference the same named implementation).
What is Connascence of Execution (CoE) and what type of bug does it cause?
?
Connascence of Execution (CoE) exists when elements must execute in a particular order, but the code does not enforce that order — e.g., initialize() must be called before process(), but nothing prevents calling process() first. CoE is a dynamic connascence (only manifests at runtime) and causes bugs that are hard to reproduce because they depend on call order, which may vary by context. It is particularly severe in concurrent or distributed systems where execution order is non-deterministic. Fix: enforce order through the type system (constructor injection, builder pattern, state machine).
What is Connascence of Identity (CoI) and why is it the most dangerous form?
?
Connascence of Identity (CoI) exists when two components must reference the exact same object instance — they both hold a pointer to the same mutable object and rely on the other not modifying it unexpectedly. CoI is the strongest and most dangerous form because: it creates invisible shared mutable state; it makes reasoning about component behavior dependent on external mutations; across service boundaries, it is nearly impossible to guarantee safely. It is the primary connascence behind race conditions, concurrent modification bugs, and distributed data consistency failures. Fix: immutable value objects, explicit ownership, or passing copies rather than references.
What is the general refactoring strategy for managing connascence?
?
Four principles: (1) Minimize overall connascence — fewer shared knowledge dependencies = more independent change; (2) Minimize connascence across boundaries — CoV, CoE, CoI across separately-deployed components are severe red flags; (3) Convert strong connascence to weak — CoM → CoN (enums for magic numbers), CoP → CoN (structs for positional params), CoA → CoN (shared library for algorithms); (4) Locality matters — strong connascence within a single function is often unavoidable and harmless; the same strength across service boundaries is an architectural crisis.
What is Big Ball of Mud and what forces drive a system toward it?
?
Big Ball of Mud (Foote & Yoder) is the architectural antipattern resulting from consistently ignored modularity: no identifiable module boundaries, undifferentiated interconnection between all components, no separation of concerns, changes requiring edits across dozens of files, and no team that can claim ownership. It is never designed — it accretes. Driving forces include: schedule pressure (modularity requires upfront discipline), developer turnover (new developers create shortcuts), lack of fitness functions (drift goes undetected), “just this once” boundary exceptions that are never cleaned up, and absence of shared architectural vocabulary.
How do modularity metrics become architectural fitness functions?
?
Modularity metrics (LCOM, instability, distance from main sequence, connascence indicators) should be computed automatically in CI pipelines and treated as fitness functions with defined thresholds. Examples: flag classes with LCOM > 0.8 as warnings; fail the build if any component has distance D > 0.5; flag methods with > 5 positional parameters (CoP indicator); detect magic numbers not assigned to named constants (CoM indicator). This converts modularity from a subjective code-review observation into a continuously-measured, enforceable architectural constraint — preventing Big Ball of Mud through automated detection rather than periodic audit.
What is the distributed monolith antipattern and which connascence types characterize it?
?
A distributed monolith is a system partitioned into microservices that cannot actually operate independently — they must deploy together, share a database schema, and break when any peer changes. It achieves the operational complexity of microservices with none of the modularity benefits. The characteristic connascence types are: Connascence of Values (CoV) across service boundaries (shared database invariants), Connascence of Timing (CoT) (synchronous call chains where all services must be up), and Connascence of Algorithm (CoA) (independently re-implemented shared logic). The root cause is typically insufficient domain decomposition — the services are not actually modular, just separately deployed.
What is the difference between static and dynamic connascence?
?
Static connascence can be detected by analyzing source code — it is visible to compilers, IDEs, and static analysis tools. Types: CoN, CoT, CoM, CoP, CoA. These are the safer forms because tooling can detect, and often automatically fix, violations. Dynamic connascence only manifests at runtime and cannot be detected by source analysis alone. Types: CoE (execution order), CoT (timing), CoV (value consistency), CoI (shared identity). Dynamic connascence requires testing (unit, integration, concurrency, end-to-end) to detect — and is often only discovered in production incidents. Minimizing dynamic connascence across component boundaries is one of the highest-value modularity activities.
Priority: HIGH — modularity metrics and connascence vocabulary are the analytical foundation for evaluating every architectural style in the book