The Computing Series

Exercises

Level 1 — Understand

1. What is a module, and what two things does its boundary define?

2. List the six types of connascence described in this chapter, ordered from weakest to strongest. Which type is hardest to detect and most expensive to violate?

3. What does a cycle in the import graph imply about the modules involved, and what is the consequence for testing and deployment?

Level 2 — Apply

  1. Draw the import graph for the following module structure and identify all cycles: auth imports users, users imports billing, billing imports auth, notifications imports users. Which pairs of modules are entangled? What would you need to extract to break the cycles?

  2. A function signature is processOrder(userId, accountId, orderId, productId, quantity). Identify which type of connascence is highest risk here and explain why. Propose a refactoring that reduces that risk.

  3. A module declares a global CONFIG dictionary that seven other modules read directly. What type of connascence does this create? What failure mode does it produce? Propose an alternative structure.

Level 3 — Design

  1. A codebase has grown to 150,000 lines with no enforced module structure. The import graph analysis shows 23 cycles involving 67 files. Design a strategy to eliminate cycles incrementally over six sprints without a flag day rewrite. Describe the tooling you would put in place, the order you would address cycles, and the boundaries you would enforce to prevent new cycles from forming. Name all relevant AT and FM codes.

A complete answer will: (1) specify the tooling: a CI-enforced import cycle detector (e.g., pydeps, madge, or a custom script) that fails the build if new cycles are introduced — this gate prevents regression while the existing cycles are addressed; a dependency visualisation tool to identify the highest-fan-out nodes that participate in the most cycles, (2) order cycle elimination by impact: start with cycles involving the most files or the most frequently changed modules (high churn cycles cause the most pain); within a cycle, extract the shared dependency into a new module that both sides can import, rather than reversing import direction, (3) name AT3 (Simplicity/Flexibility): extracting a shared module is simpler to reason about but introduces a new module that must be maintained; inlining dependencies eliminates the module boundary but increases file size — state which is preferred and under what conditions, and FM8 (silent semantic drift) for cycles that embed implicit contracts between files that are never documented, (4) describe the enforcement boundary: define allowed import layers (e.g., api → services → domain → infrastructure) and enforce directional imports only — any upward import (domain importing from api) triggers a CI failure — specify the tool configuration and the process for requesting exceptions.

  1. A team is designing a new service that will handle both user authentication and user profile management. Argue both sides: why these belong in one module (cohesion argument) and why they belong in separate modules (coupling argument). Give the conditions under which each argument wins, using connascence vocabulary.

A complete answer will: (1) make the cohesion argument using connascence vocabulary: authentication and profile management share connascence of identity (the user entity) and connascence of meaning (the concept of “logged-in user state”) — they change together when the user model changes, suggesting high cohesion that favours a single module, (2) make the coupling argument: authentication is consumed by every service in the platform (high fan-in), while profile management is consumed by a smaller set (settings, notifications) — placing them together creates connascence of execution (callers must import authentication logic to access profile operations) that spreads authentication’s blast radius to unrelated services, (3) state the conditions under which each argument wins: cohesion wins when the team is small, the user model is stable, and the overhead of a service boundary (network calls, separate deployment, schema migration coordination) is high; coupling wins when authentication scales independently of profile management, when separate SLAs are required (auth must be 99.99%, profile management can tolerate planned downtime), or when separate teams own each domain, and (4) name AT8 (Coupling/Cohesion) as the governing tradeoff and identify the connascence type that forces the decision: if profile management changes require authentication schema changes, the connascence of meaning is too tight for separation; if they can evolve independently, the coupling argument wins.

Read in the book →