The Computing Series

Exercises

Level 1 — Understand

1. What is the difference between coupling and cohesion? What is the target state for each at component boundaries?

2. Name the five coupling types described in this chapter, from weakest to strongest. Give a one-sentence description of each.

3. What are the two code smells that signal coupling problems in practice? Describe what each looks like.

Level 2 — Apply

  1. Classify the coupling type in each of the following scenarios: (a) sendEmail(recipient, subject, body) — only these fields are passed; (b) generateReport(user: User) where the function only uses user.email and user.name; (c) a class reads from a global FEATURE_FLAGS dictionary; (d) process(data, skipValidation=True). For each, identify the coupling type and describe the minimum refactoring to weaken it.

  2. A UserService class contains: createUser, sendWelcomeEmail, updatePassword, writeUserCreationToAuditLog, calculateUserRiskScore. Which methods belong in UserService? Which belong elsewhere? Organise them by cohesion and describe what new components you would create.

  3. Identify the code smell in this design: OrderService.calculateTotal(order) reads order.customer.taxRegion, order.customer.discountTier, and order.customer.memberSince. Name the smell and describe the fix.

Level 3 — Design

  1. A team is building a notification system. Notifications are triggered by events from five other services: orders, payments, shipments, returns, and accounts. Design the coupling structure between the notification service and the five source services. Compare two approaches: (a) notification service imports and calls each service directly, (b) notification service subscribes to events published by each service. Name the coupling type in each approach, the tradeoffs using AT codes, and the failure modes using FM codes.

A complete answer will: (1) name the coupling type for each approach: (a) direct calls create connascence of execution and connascence of identity — the notification service must know the API shape of all five source services and call them in a specific order; (b) event subscription creates connascence of meaning only (shared event schema) — the notification service depends on the event structure, not on the source services’ internal APIs, (2) name AT8 (Coupling/Cohesion) as the governing tradeoff: direct calls are tighter coupling (easier to trace, lower latency) but higher change impact (if any source service changes its API, notification service must change too); event subscription is looser coupling (each service evolves independently) but harder to trace and adds event broker complexity, (3) name FM2 (cascading failure) for approach (a): if any of the five source services is down, the notification service cannot issue notifications for that event type — a failure in the payments service silences payment notifications; for approach (b), name FM4 (stale data): if the event broker is slow, notifications are delayed but the notification service remains operational, and (4) recommend approach (b) for this specific design and justify: five source services with independent release cycles create too many synchronous coupling points for approach (a) to maintain safely — the number of breaking changes scales with the number of source services, making event subscription the correct choice at this fan-in count.

  1. A company has a Customer object used by billing, support, marketing, and logistics. Each team needs slightly different fields and methods. Propose a design that gives each team what it needs without creating a single 500-line Customer class that changes for every team’s reasons. Name the principle you are applying, the tradeoffs, and where coupling is unavoidable.

A complete answer will: (1) apply the Interface Segregation Principle (or DDD bounded context decomposition): define separate BillingCustomer, SupportCustomer, MarketingCustomer, and LogisticsCustomer types, each containing only the fields and methods relevant to that team’s domain — each type is owned by the team that uses it and changes only when that team’s requirements change, (2) name AT8 (Coupling/Cohesion): segregated types reduce the blast radius of changes (billing changes do not affect support) but increase the total amount of code and require a mapping layer when data must cross boundaries — state the conditions under which the added mapping complexity is worth the reduced coupling, (3) identify where coupling is unavoidable: all four types share the same customer identity (customer ID, name, contact information) — this shared identity creates connascence of meaning that cannot be eliminated without duplicating the identity concept; propose a CustomerIdentity type owned by a shared domain layer that all four bounded contexts import, and (4) name FM8 (silent semantic drift) as the failure mode when the four types diverge: if billing’s Customer.address and logistics’ Customer.address are separately maintained and drift apart (different validation rules, different field names), data inconsistencies accumulate silently — state the synchronisation mechanism (e.g., shared identity events or a read-model projection) that keeps identity data consistent across bounded contexts.

Read in the book →