Skip to main content
Object Oriented Design, Analysis and Programming

Measuring Object-Oriented Design

Published: 2026-08-19
Level: postgraduate
Audience: Postgraduate students in Object Oriented Design, Analysis and Programming

Prerequisite Knowledge

This lecture builds on the following concepts from earlier lectures. If any feel unfamiliar, review the linked notes before proceeding.

Previously Covered in This Subject

  • Representing Design - UML Interaction, Class and State Diagrams — covered in Lecture 9: Object Oriented Design with UML Interaction Models
  • GRASP: General Responsibility Assignment Software Patterns — covered in Lecture 10: Designing Object Systems with GRASP and Interaction Diagrams
  • Low Coupling and High Cohesion - The Yin and Yang of Software Design — covered in Lecture 11: GRASP Patterns and Design Principles — Polymorphism, Indirection, Fabrication and Protected Variations
  • Coupling in Depth - Types, Measures, and Desirable Levels — covered in Lecture 12: Object Oriented Design Principles and UML Modeling
  • Foundations of Design Patterns — covered in Lecture 13: Design Patterns — Gang of Four Solutions

# Measuring Object-Oriented Design

15.1 Why Measuring a Design Matters Before Implementation

15.1.1 The Goal of Design Measurement

Hook — why pay for numbers before a single line of code runs? Imagine an architect discovering that a bridge design cannot carry its load only after concrete has been poured. Fixing paper is cheap; fixing concrete is ruinous. Object-oriented design measurement is that paper check — it asks, on the diagram itself, whether the arrangement of classes will be understandable, reusable, and cheap to change.

A design is the set of decisions that fix how objects, their attributes, their methods, and their relationships appear in class diagrams and interaction diagrams. A measurement is a number or a derived indicator that describes a property of that design so teams can compare alternatives before any code is written. The lecture frames measurement as an economic discipline: if a design is wrong and the team discovers the flaw only after implementation, the team must discard code, redesign, and re-implement. That rework consumes calendar time, developer effort, and budget that an early check could have saved.

Intuition — the blueprint analogy. Think of a class diagram as an architect's blueprint and an interaction diagram as the traffic plan for how rooms are used. You can count, on paper, how many rooms exist, how many doors connect them, and whether one hallway serves every room. You do not need to build the house to see that a single hallway serving every room will become a bottleneck. Where the analogy breaks: Unlike a building, software couplings are invisible at runtime until they cause change-propagation or test difficulty. Numbers make those invisible connections visible.

From vocabulary to judgment. The previous several sessions built the design vocabulary.

  • GRASP patterns (General Responsibility Assignment Software Patterns — heard as "grass patterns" in the recording) capture who should do what — rules for assigning responsibilities to classes so that information expert, creator, controller, and low coupling guide placement of duties.
  • Design patterns (Gamma et al. GoF) capture recycled solutions — named, time-tested arrangements such as Observer, Strategy, or Factory that solve a recurring problem with known trade-offs.
  • Principles, rules, and guidelines refine those solutions — Single Responsibility, Open-Closed, information hiding.

Even with that vocabulary, a team still needs a way to answer whether a particular arrangement is good or bad for this system. Rules tell a designer what to prefer in general ("prefer low coupling"). A measure helps a team judge a specific diagram and spot the spots that may cause trouble later. The stated aim is to avoid bad designs and to steer toward designs that are easier to understand, to reuse, and to maintain. Measuring the design means counting and weighting objects, attributes, operations, and relationships so the team can estimate complexity, reuse potential, and risk before committing to code. An early inspection catches a God class or a tangled collaboration network when erasing a box on a diagram still costs almost nothing.

Early inspection does not predict every run-time property. It predicts structural risk. A structural measure that correlates with comprehension time or fault density is worth collecting because it points to where a redesign will pay back most. That is why measurement was placed immediately after GRASP and design patterns in the course flow — vocabulary first, judgment second.

15.1.2 What a Class Diagram Offers to Measure

A class diagram, together with its companion interaction diagrams, makes four kinds of information countable without running the system:

  1. Elements. How many classes, how many attributes per class, how many methods per class. This is pure size — the raw material cost of the design.
  2. Relationships. Inheritance links that form a hierarchy, associations that show collaboration lifetime, and dependencies that show one class using the services of another. This is shape — how the pieces connect.
  3. Responsibility distribution. Whether a class groups related duties or whether unrelated duties have been forced into the same box. This is coherence — whether the decomposition matches the domain abstraction.
  4. Interaction dynamics. Interaction diagrams (sequence and communication diagrams) add the count and direction of messages that objects exchange. Those messages reveal coupling in motion and the work a class must do when it receives a request — its response set.

Together, class diagrams and interaction diagrams supply the raw facts that later sections turn into named metrics. The team does not need to run the system to collect them. The diagram alone is enough for a direct measure, though interpreting what the number means still requires context and judgment against a reference range.

Scope — what diagrams can and cannot tell you. A diagram can tell you fan-out (how many collaborators a class touches), depth of inheritance, and whether duties are scattered. It cannot directly tell you latency, throughput, or memory consumption under load. Those remain performance KPIs that become observable only after implementation and execution. Confusing a structural proxy with a run-time observation is a recurring beginner pitfall — see the next subsection.

A useful habit introduced here is to read a diagram twice: once for what is inside each box (count attributes and methods), and once for how boxes reference each other (count inheritance edges, associations, and dependencies). The first pass feeds size and cohesion; the second feeds coupling and reuse. CRC cards (Class-Responsibility-Collaborator), used during responsibility assignment, already pre-count the second pass — each Collaborator line is an early, provisional coupling edge.

Visual intuition: picture a class diagram where each class is a node sized by its method count and each dependency is a directed arrow. A healthy design looks like a set of medium-sized islands with a few deliberate bridges between them. An unhealthy design shows one giant island (the God class) with dozens of arrows entering and leaving it, or a starburst where every class points to a single utility class. That visual pattern is what CBO, RFC, and related metrics later quantify.

15.1.3 Student Questions and Answers on Where to Look First

The lecture opened the floor for candidate properties to measure. Several distinct suggestions were offered, and the response sorted them into what can be judged on paper versus what requires execution.

Q: What properties should we try to measure on a class diagram to judge design quality — reuse, coupling, cohesion, representation gap, abstraction, or even KPIs such as latencies?

A: Several useful directions were offered and then clarified:

  • Amount of reuse — how much of the design can be reused. Inheritance gives one path to assess that (depth and breadth of hierarchy, inherited versus newly defined members). Reuse via inheritance is measurable directly on the diagram.
  • *Modularity as coupling and cohesion. Coupling (how strongly classes depend on each other) should be low but not zero — collaboration is necessary. Cohesion* (how closely the duties inside a single class hang together) should be high — members of one class should support a single, focused abstraction.
  • Representation gap and abstraction. How directly the model mirrors reality, and how well the model hides detail while preserving essential structure. These matter for understanding, but they are harder to count without a qualitative review.
  • KPIs for performance such as latencies. This was the pivotal correction. The response drew a firm line between what can be judged on paper and what can be judged only after a system runs. Performance aspects such as latency become visible only after implementation and execution. At design time a team can estimate structural properties — complexity, size, coupling — that correlate with fault risk and change effort, but it cannot directly observe run-time speed. A design check therefore focuses on structural properties that are known to correlate with understanding, fault risk, and future change effort, while performance evaluation stays as a later empirical check with profiling and testing.

The trigger here was the plausible idea that if we can count anything on a diagram, we can count latency there too. The correction is that latency is a dynamic, environment-dependent outcome — it depends on hardware, load, caching, and data size — and is invisible on a static diagram. Structural proxies can hint at performance risk (e.g., a chatty collaboration may become slow), but they are not latency itself.

Pitfalls — what beginners get wrong when choosing what to measure.

  • Treating latency or throughput as a design-time KPI. Fix: reserve performance measurement for an implemented, running system; at design time, measure structural complexity and dependency structure instead and then validate against later performance tests.
  • Counting only class count and declaring a design good because it has "many classes" (hence "more object-oriented"). Fix: read cohesion alongside count — many small but incoherent classes can be worse than fewer focused ones.
  • Confusing high fan-in with high fan-out. Many classes depending on one stable abstraction (high fan-in) can be healthy. One class depending on many unstable collaborators (high fan-out / high CBO) is fragile. The coupling number needs direction.

Real-world: In industry reviews, coupling and cohesion are the first checks teams run on a diagram. Tools that read UML or code (CCCC, JMetric, SonarQube, Structure101) can flag a high fan-out immediately — for example a class that depends on 12 others where the team norm is 3–5. That flag often points to a class that will be hard to test in isolation and that will ripple on change. Experienced reviewers then ask "can we introduce an interface, move a responsibility to its owner, or split the class?"

Recap — from "does it look good?" to "what number tells us it is good?" A design fixes objects, attributes, methods, and relationships on paper. Measuring that paper — elements, relationships, responsibility distribution, and message flows — gives early, cheap indicators of complexity, reuse, and risk. GRASP and design patterns give the vocabulary; metrics give the judgment on a specific diagram.

Bridge: Those raw counts are only useful if the numbers are trustworthy. The next section asks why raw lines of code fails that trust test and what better size and complexity signals replace it.

Exam note: Design measurement and the relationship of coupling and cohesion to quality are recurring discussion points that can appear as conceptual questions on diagrams. EC3 weightage is 40 percent (weightage 40%), with full syllabus examinable and main focus on the later part including design patterns and object-oriented design. Diagram reading and creation remain central to assessment, so practice translating a class diagram into coupling and cohesion judgments.

Connections: This section sets up 15.2 (why LOC misleads), 15.3 (what makes any metric trustworthy), and 15.5–15.9 (the twelve CK/MOOD metrics that formalize coupling and cohesion).

15.2 Traditional Size and Complexity Measures

15.2.1 Lines of Code and Why It Misleads

Hook — can you price a novel by counting its words? A 200-page novel and a 200-page phone directory have the same page count but wildly different value and effort. Lines of code (LOC) counted pages for software before the field learned to count ideas instead.

A line of codeLOC, heard as "lines of pool" in the recording — counts physical or logical lines that a programmer writes. Historically it was the earliest size measure teams used to estimate cost, effort, and even price. The intuition is simple: more lines mean more work, so a larger LOC should mean higher cost. The lecture challenged that intuition directly and left it as a misconception to correct.

What LOC counts — and what it misses. LOC counts keystrokes, not decisions. It is indifferent to whether a line contains a duplicated copy-paste, a trivial getter, or a deeply nested branch that doubles test effort. Three verbatim observations were stressed:

  • A recursive routine may express the same logic in far fewer lines than an iterative version yet be easier to understand and to test. Fewer lines can mean clearer logic, but only because duplication and accidental complexity were removed — not because brevity is always correctness.
  • Splitting a complex duty across several small, focused classes increases the total line count while reducing the mental load on any one class. The system has more lines but lower per-class complexity and higher cohesion, which is the intended trade.
  • Repeated code inflates LOC without adding value. Copy-paste reuse is a reuse failure; the inflated count rewards the very habit that creates maintenance risk. Reusing a pattern may keep lines modest while improving quality.

So LOC alone cannot tell a team whether code is good, and it cannot support pricing unless it is combined with other factors such as feature count and structural complexity. Pricing based on LOC alone would reward verbose or duplicated code and punish compact, well-factored code.

Q: Is lines of code a sound basis for judging software quality or for setting price? Is fewer lines always better than many lines?

A: No on both counts. Fewer lines can improve readability, and recursive code often reads more directly than its iterative counterpart when the problem is naturally recursive. Yet more classes and therefore more lines can be the right response to genuine domain complexity — breaking a heavy class into smaller, single-responsibility parts is sound design even though the total LOC grows. Reusing a tested pattern may keep lines modest while improving quality. For pricing, LOC alone is perverse: it pays for verbosity and punishes factoring. Cost and price therefore need a complexity-aware model that considers feature count alongside complexity rather than a raw line count.

Trigger was the common student intuition "shorter is always better" and "LOC is an objective price." Resolution was that context decides — brevity helps when it removes duplication, but distribution across more classes is correct when it lowers per-class complexity.

Pitfalls — LOC traps.

  • Verbose equals expensive fallacy. Adding blank lines, comments-as-code, or duplicated blocks to inflate LOC for a contract. Fix: measure delivered functionality, not keystrokes.
  • Brevity worship. Golfing code into a single dense line to lower LOC while destroying readability and testability. Fix: measure per-method cyclomatic complexity alongside LOC.
  • Cross-language blindness. 100 LOC in Python and 100 LOC in assembly represent vastly different effort. Fix: normalize by feature-based size or use language-aware baselines.

Visual intuition: imagine a scatter plot with LOC on the horizontal axis and defect density on the vertical axis. Dots wander widely — high-LOC modules include both clean, well-factored subsystems and tangled legacy monoliths. The cloud has no tight diagonal. Now plot cyclomatic complexity or coupling on the horizontal axis instead; the cloud tightens and a clearer upward trend appears. That visual gap is why LOC was demoted from primary measure to supporting size hint.

Complexity is the degree to which a piece of logic is hard to follow, test, and change. One named measure is cyclomatic complexity (heard as "cyclamatic" or "McCabe complexity"), which counts independent paths through control flow. A straight-line method with no branches scores low; a method with many if, loop, and case branches scores high because each branch creates a new path that needs a separate test and a separate mental walk-through.

How complexity is estimated at design time versus in detailed design. At early object-oriented design little pseudo-code exists, so teams estimate complexity from structure visible on class and interaction diagrams:

  • the number of methods a class carries,
  • the number of calls it makes to other classes (fan-out), and
  • the fan-in and fan-out visible in CRC cards and interaction diagrams — how many collaborators are listed for a class and how many incoming messages it handles.

When detailed design exposes pseudo-code or actual code for a method, cyclomatic complexity can be computed precisely as

where is edges, is nodes in the control-flow graph, and is connected components (usually 1), equivalently number of decision points + 1. During early OO design the count of methods and collaborations already gives a useful proxy because each additional method and each additional collaboration edge adds a distinct behavior to understand and test. Lower complexity is preferred because higher complexity tends to demand more time, more tests, and more care during modification.

Scope — when cyclomatic complexity applies. Cyclomatic complexity measures control-flow complexity, not structural complexity. A class can have low per-method cyclomatic scores yet be structurally complex because it is highly coupled to many collaborators (high CBO / high RFC). Both dimensions must be checked. Early design leans on the structural proxy; detailed design adds the control-flow number.

Real-world: a reviewer who sees a method with cyclomatic complexity above 15 will ask for extraction even if the class has only three methods. Conversely, a class with ten trivial getters will have higher LOC but low complexity and is not the same risk.

15.2.3 Function Points, Effort Estimation and Pricing

Function points (FP) measure size in terms of external functionality — inputs, outputs, inquiries, files, and external interfaces — weighted by their complexity, rather than by how the functionality is coded. An output that requires deriving and formatting counts more than a simple inquiry. Because the count is anchored in what the user sees, it is language-independent and can be estimated from requirements and early design, well before code exists.

From size to effort — how teams plan staffing, schedule, and price. Together with complexity signals, function points feed effort models (the classic COCOMO family and its commercial successors) that customers and teams use to plan staffing, schedule, and price.

  • The discussion noted that complexity of modules, including per-class method counts and dependency structure, predicts how much time and effort a team will need for a given function-point count. Two products with the same function-point count can have very different cost if one is tangled and the other is well-partitioned.
  • A product with many reusable fragments that have already been tested can be delivered faster — reuse amortizes design, code, and test effort across projects.
  • Simply adding more people does not solve schedule pressure in the way it can on a construction site. This was the explicit "adding people like construction does not speed software" correction. One person cannot be freely swapped for another, and coordination cost grows quickly — the observation associated with Brooks's Law. Effort estimation therefore uses feature counts and complexity measures, not headcount alone.

In short: , not . That function is calibrated on local historical data so that a team quoting a new project can say "a 100-FP subsystem at our current complexity level has taken 8 person-weeks on average" rather than "a 5,000-LOC subsystem takes…"

Visual intuition: picture a dashboard where function points are the horizontal "amount of work the customer will see," complexity (WMC/CBO) is the vertical "how tangled the work is," and reuse is the depth "how much is already built." Projects that sit high on complexity or low on reuse cast a larger staffing shadow even when their function-point footprint is modest.

Pitfalls — effort traps.

  • Pricing by LOC. Quoted above — it rewards duplication. Fix: price by function points adjusted for complexity.
  • Linear headcount scaling. Believing doubling the team halves the schedule. Fix: model coordination overhead and use incremental delivery instead of staffing spikes.
  • Ignoring domain complexity. Treating a real-time control function point like a simple CRUD inquiry. Fix: apply the complexity weighting tables for function points rigorously.

Real-world: Commercial estimation approaches still combine function points with complexity tables to quote cost-to-fix and to negotiate contracts. Teams that track those numbers over releases — "our last four releases averaged 12 function points per person-month at CBO ≈ 4" — build a baseline that makes later forecasts defensible. When a new diagram shows a jump in coupling, the estimator knows to raise the cost buffer even though LOC is still zero.

Recap — from counting lines to counting value and difficulty. LOC counts lines but not decisions; recursive brevity and well-factored distribution across classes both break the "fewer lines = better" rule. Cyclomatic complexity counts paths and is proxied at design time by method counts and fan-in/fan-out. Function points count externally visible functionality weighted by complexity and, together with complexity and reuse, predict staffing and price far more reliably than LOC. All three teach the same lesson: raw size needs a quality lens.

Bridge: If LOC is too crude to trust on its own, what makes any replacement number trustworthy? The next section lays the foundations — quantification, validation, indicators, and trade-offs — that turn a raw count into an engineering metric.

Connections: 15.2's LOC critique motivates 15.3 (validation and indicators) and anticipates WMC in 15.6 — where method count and cyclomatic weight finally combine into a single class-level complexity number.

15.3 Measurement Foundations — What Makes a Number Trustworthy

15.3.1 Engineering Means Quantification and Validation

Hook — when did programming become engineering? When a team stopped saying "we are almost done" and started saying "12 faults per thousand lines in module X, and the mean time to change is 2.3 days." A number that can be checked is the line between opinion and engineering.

Software became an engineering discipline when teams moved from vague claims such as very big or almost done to statements that can be checked with numbers. An engineering statement says two months and three days remain, or the class at the bottom of the hierarchy inherits twelve methods, or this class depends on eight others where the team norm is three. Each claim names a unit, a scope, and a way to verify it.

Quantification is only half of the work. A measure also needs validation against reality. Teams collect data across many projects — so-called empirical studies — and look for correlation between the computed value and observed outcomes such as faults, change effort, or comprehension time. A metric earns trust when it repeatedly moves with the outcome it claims to indicate, not when it sounds plausible in one anecdote.

The blood-test analogy — reference ranges, not single-point diagnosis. A single blood value is not a diagnosis by itself; clinicians interpret it against a reference range built from many patients of similar age and health. In the same way, a metric value is read against a range built from many prior designs in a similar domain.

  • A WMC of 45 for a controller class may be unremarkable in a legacy GUI framework but alarming in a new microservice where peers average 12.
  • A DIT of 6 may be routine in a mature Java collections hierarchy but a review flag in a freshly designed payroll domain where stability of upper levels has not been proven.

Direct correlation between one metric and one outcome may be weak for any single number, but repeated studies give a sense of where concern should start. That is why lecture references to "empirical studies" and "correlation with reality" matter — without a historical distribution, a number is just a count; with it, the same number becomes an indicator that guides review priority.

Real-world: organizations that publish a metric without first publishing the reference range are often surprised when teams game the number. A cap such as "WMC must be < 20" without domain context either blocks legitimate complexity or lets risky designs slip. Mature teams instead publish a percentile band — "our last 20 services sit between WMC 8–18; ask for a review above 25" — and update that band each release.

15.3.2 Indicators, Direct and Indirect Measurement

Most software properties are intangible. A team cannot see quality or usability the way it sees a beam length or a count of bricks. So measurement proceeds through indicators — metrics or small combinations of metrics that point toward a property without fully defining it.

Indicators need companions. An example traced in the lecture is number of errors found during testing.

  • One hundred errors on a large product with 50 person-months of testing may signal a thorough testing cycle rather than poor quality — the team looked harder and found more.
  • Five errors on a similarly large product with the same test effort may signal weak coverage rather than excellence — the team looked less and found less.
  • Five errors on a tiny utility tested lightly may be alarming, while 100 errors on a million-line platform in its first system test may be expected.

The count gains meaning only alongside project size, test effort, and history. Similarly, number of errors per person per hour helps discuss productivity only when paired with context — domain difficulty, tooling, and reuse all shift the denominator. One metric alone rarely explains a product or a person; a balanced set does.

The taxonomy used:

  • Direct counting — attributes that can be read with a single rule: number of classes, number of children, number of methods. Give the diagram, the count follows deterministically.
  • Indirect measurement — attributes that require several observable proxies combined through a model: usability, reliability, maintainability, overall quality. These are inferred via a function of direct counts and process data, never by one number.

A parallel used to make the point memorable was the CGPA and percentage analogy (also heard as "Cage Pa" in discussion). Asking for a student's percentage or CGPA gives a quick number, and a score toward 90 percent signals strong performance, yet a single number cannot fully capture the person. Different courses stress different skills, effort, and context — a 8.5 CGPA built on theory-heavy courses means something different from a 8.5 built on project-heavy courses. Two students with the same CGPA may differ in important ways — depth versus breadth, consistency versus spikes, domain focus. In the same way, a single design metric cannot fully capture a design. The team must interpret a metric alongside project history, domain, and a set of complementary measures, and must build correlations through repeated observation across releases.

Visual intuition: picture two dashboards. The left one shows a single number — "Errors: 100" — blinking red or green by an arbitrary threshold. The right one shows a small context strip: errors, size in function points, test hours, release phase, and a trailing history of the same three numbers over five releases. The left dashboard invites a snap judgment; the right dashboard invites a conversation about thoroughness. Engineering teams are trained to demand the right dashboard.

15.3.3 Desirable Directions and Necessary Trade-offs

Teams often ask the simple question: should this metric go up or down? The discussion gave a compact set of preferences that recur throughout CK and MOOD:

Preferred directions (ceteris paribus).

  • Lower coupling is desirable — fewer dependencies make a class easier to understand, reuse, and test in isolation.
  • Higher cohesion is desirable — members of a class should collaborate toward a single, focused purpose.
  • Lower complexity is desirable — fewer paths and fewer collaborations mean less test and change effort.
  • Higher encapsulation (hiding) is desirable — less exposure of internal state and helper methods narrows the surface where ripple effects can propagate.

Those wishes collide. Making coupling zero is not possible if objects must collaborate to do useful work — a system with zero coupling is a set of isolated programs that cannot deliver a feature. Raising the number of classes can improve cohesion by splitting a God class — a class that has taken on unrelated responsibilities and become large, weakly cohesive, and highly coupled — but it also adds relationships that raise coupling and understanding load elsewhere. There is no free improvement on one axis.

The lecture used a concrete size comparison to block a simplistic rule:

  • A design with ten classes (10 classes) for a large domain may hide a God class with low cohesion — a few boxes doing too much, each method touching disjoint state.
  • A design with one hundred classes (100 classes) for the same domain may be well partitioned — each class focused and cohesive — or it may be over-fragmented, with a scattering of tiny, barely used abstractions that raise coupling without adding clarity.

The number alone does not settle the question. Every measurement therefore invites a trade-off analysis rather than a blind target. Similarly, finding 100 errors versus 5 errors during testing cannot be read without context of size and test effort, a point stressed when errors were discussed as indicators. A God class split is not justified because "more classes equals more object-oriented." It is justified because splitting raises cohesion, lowers per-class complexity, and — crucially — keeps coupling from rising disproportionately. If splitting scatters one coherent abstraction into five coupled fragments with no new clarity, cohesion and coupling both worsen.

Q: Is more classes always better, or fewer classes always better, when the objective is object orientation?

A: Neither extreme decides by itself. If the class count is low and a few classes carry unrelated duties, cohesion is low and the classes should be split — the God class is the signal. If the count is high, the team should check whether each addition serves a real abstraction that raises cohesion or whether it fragments the model without benefit, scattering a single concept into many coupled pieces. The right count is the count that gives focused, cohesive classes with limited and purposeful coupling for the system at hand. Historical ranges for a given product family — not an absolute constant — help decide whether a class count is typical or an outlier.

Pitfalls — indicator misuse.

  • Single-metric worship. Declaring a design good because one number moved in the preferred direction. Fix: always read a small set — e.g., coupling together with cohesion and a size/complexity measure — and check the trade-off.
  • Threshold fundamentalism. Treating an informal ceiling (e.g., DIT 3–5, CBO < 6) as a pass/fail gate independent of domain. Fix: treat thresholds as review triggers tied to a reference range, not as specifications.
  • 100-errors versus 5-errors without denominator. Reading error counts as quality without project size and test-effort context. Fix: normalize by size and report effort alongside outcome.
  • Zero-coupling fantasy. Attempting to drive coupling to zero and eliminating needed collaboration. Fix: accept that lower coupling is a direction, not a destination — some coupling is the price of useful work.

Scope — when these directions fail. All four preferences assume that functionality already exists. If required features are missing, no amount of low coupling makes the system good. Functionality is the floor; maintainability and portability are the walls and roof built on top (see the McCall triangle in 15.4). A highly encapsulated, low-coupled design that does not do what the user asked for is not a good design.

Real-world: reviewers often flag God classes first — a class where WMC, CBO, and LCOM are all outliers — then check whether a proposed split genuinely moves those numbers together. A healthy split lowers WMC per new class, lowers LCOM (methods now share state), and keeps CBO from exploding by placing collaborators where data already lives (information expert / GRASP). Historical ranges for a given product family — built from many past designs like the blood-test reference — let the team say "this WMC is in the top 10% for our domain" rather than "this WMC is above 20, therefore bad."

Recap — a number becomes a measure only with context, companions, and trade-offs. Engineering demands quantification plus validation against reality. Most software properties are intangible and require direct counts combined into indirect indicators. A CGPA or a 90-percent score or a 10-versus-100 class count teaches the same lesson: a single number never tells the whole story; complementary evidence, project history, and domain reference ranges do. The four compass directions — lower coupling, higher cohesion, lower complexity, higher encapsulation — guide judgment, but every improvement on one axis must be checked for cost on the others.

Bridge: Those compass directions need a quality map to sit on. The next section introduces the McCall quality triangle and its thirteen elements, and locates measurement in the product versus process distinction that decides what is measured when.

Connections: 15.3's indicator idea explains why 15.2's LOC fails (one number, no context) and why 15.5's CK + MOOD families are presented as a twelve-view indicator set rather than twelve competing scores.

15.4 Quality Models and Where Measurement Sits in the Lifecycle

15.4.1 The Quality Triangle and Its Thirteen Elements

Hook — what does "good software" even mean? A system can be fast but unusable, feature-complete but impossible to change, reliable but impossible to install elsewhere. "Good" is not one property; it is a negotiated bundle of properties. A quality model names the bundle so a team can decide what to measure.

One early framework mentioned was the McCall quality triangle, described as appearing in the late 1970s and later elaborated into a quality model with thirteen elements. The triangle organizes thinking around three broad concerns that still frame project conversations:

  1. How the product behaves in operation — does it do what it should, correctly and efficiently? (Functionality, correctness, reliability, efficiency, integrity, usability.)
  2. How easy it is to adapt after delivery — what does a requirement shift cost? (Maintainability, testability, flexibility.)
  3. How easily it moves to new contexts — can it be ported, reused, or made to interoperate? (Portability, reusability, interoperability.)

Underneath that triangle, the elaborated model lists properties such as functionality, reliability, efficiency, usability, maintainability, testability, portability, and reusability, among others, toward the thirteen named in the lecture. Functionality comes first — if required features are missing, no one cares that the system is portable or elegant. Usability, reliability, and performance matter once features exist, and support concerns such as maintainability govern how expensive future change will be.

Correctness as the underpinning. Correctness — conformance to specification — underpins all of them and links directly to verification ("did we build the product right?") and validation ("did we build the right product?"). A highly maintainable system that is incorrect is still a failure; a correct system that is unmaintainable is a slowly accumulating failure. That priority ordering matters when a team must choose which metrics to watch most closely. The McCall structure does not prescribe exact formulas; it prescribes a checklist — for this release, which of the thirteen elements is the risk we are buying information about with our metrics?

Visual intuition: imagine the triangle with "operation" at the top, "revision" at the bottom-left, and "transition" at the bottom-right. Each property is a label attached to a corner. A consumer mobile app pulls the center of gravity toward operation + usability; an embedded controller pulls it toward operation + reliability + efficiency; a platform SDK pulls it toward transition + reusability + portability. The same metric — say coupling — matters in all three, but the tolerance for a high coupling reading shifts with where the center of gravity sits.

15.4.2 Product Measurement and Process Measurement

Two lenses on the same project.

  • Product measurement looks at the artifact itself — a class diagram, a package, an interaction diagram, or the code that implements them. CK metrics (WMC, DIT, NOC, CBO, RFC, LCOM) and MOOD ratios (MIF, AIF, COF, POF, MHF, AHF) are product measures. Does the diagram show a God class? Is the hierarchy deep or wide? Is encapsulation strong? No development process needs to be observed to answer those questions — the artifact already contains the evidence.
  • Process measurement looks at how the team works — inspection coverage (what fraction of design artifacts were reviewed), defect discovery rate, rework time, mean time to change, and whether predicted risk matched observed faults. A product measure may show that a design is highly coupled (high CBO, high COF). A process measure may show whether reviews caught that coupling early or whether it escaped to system test where fixing it was ten times more expensive.

Both lenses are needed. A superb product metric suite without process feedback is a speedometer that no one watches; a rigorous process without product metrics is a careful driving style with no road signs. The discussion stressed that no one model — McCall or any successor — fits every product or every team. The choice of what to measure depends on the kind of system, the domain, and the risks that matter most for that release. A trading engine weights reliability and efficiency; a content platform weights usability and portability; each choice changes which metrics are watched most closely and which thresholds trigger review.

A practical encouragement followed: create new formulas when a standard one does not match the question at hand, but any new formula needs the same discipline as the existing twelve — define the count precisely, collect data consistently across projects, analyze whether the new number correlates with the outcome it claims to predict, and feed the lesson back into practice. A metric without that validation loop is just a formula; with it, it becomes an engineering indicator.

Scope — product does not explain process, and process does not excuse product. High CBO on a class diagram is a product fact; whether it slipped through review is a process fact. Conflating them — "we have a good process, so the design must be good" — is a common management pitfall. Measure both and check their link: does the process that claims to catch high coupling actually catch it before code?

15.4.3 Maintainability and Support Measures

Support-oriented properties received special attention because they govern the cost a client pays after delivery — often the largest share of total cost.

What support measurement asks. Questions include mean time to change (how long from requirement change to deployed fix), cost to correct a defect (effort per fault at each lifecycle phase), and effort to port a component to a new platform or customer. Those numbers justify decisions to a client in business terms. Two systems may both satisfy the same functional specification, yet one may demand far more effort when a requirement shifts — because its God classes and highly coupled cores force changes to ripple. Measuring that difference lets a team argue for refactoring, for a different partition, or for investment in tests with numbers rather than taste.

The discussion also noted that testing itself provides support evidence. Counts such as errors found internally before release, defects escaped to customers after release, and the rate at which each appears give a picture of quality that design measures alone cannot supply. A dropping escape rate across releases, while product metrics remain steady, signals a strengthening process; a rising escape rate while product metrics worsen signals a design drift that testing is no longer containing.

Visual intuition: picture two release trains for the same product. Train A has low coupling and high cohesion; a feature change touches two classes and one test suite. Train B implements the same feature but the change touches eight classes and five test suites, several of which fail unexpectedly. Both trains arrive — functionality is satisfied — but Train B paid more fuel and broke more windows along the way. Support measures weigh that fuel and glass; product measures explain why the route was longer.

Real-world: quality models are still used at project kickoff to choose which properties to emphasize. The chosen emphasis then selects the metric set: a team building an embedded controller may monitor reliability and efficiency proxies (low RFC, bounded WMC), while a team building a content platform may monitor usability and portability proxies (stable interfaces, high hiding factors). In hiring interviews and internal design reviews that choice is often the first question asked — "what quality properties are you optimizing for, and which metrics will tell you if you are drifting?"

Recap — a model names what is worth measuring, and product plus process names when and where you measure it. The McCall triangle bundles thirteen product properties under operation, revision, and transition; correctness underpins them all. Product measurement reads the artifact (class and interaction diagrams); process measurement reads how the team inspects and reworks that artifact. Which properties dominate depends on the product and the release, and any custom metric needs the same empirical validation as the CK/MOOD families.

Bridge: With the map (quality model) and the lenses (product versus process) in place, the lecture could finally lay out the twelve named object-oriented metrics that teams actually compute. The next section introduces those twelve as two families of six — CK at the class level and MOOD at the system level.

Connections: 15.4's support measures (mean time to change, cost to fix) give business meaning to 15.6–15.9's structural numbers — a high CBO is not abstract badness; it is predicted change cost.

15.5 The Twelve Object-Oriented Design Metrics — CK and MOOD at a Glance

15.5.1 Two Families, Six Metrics Each

Hook — twelve numbers to profile a design the way twelve blood tests profile a body. No one test tells the whole story; together they show where to look closer.

Object-oriented design has two well-known metric families that together supply the twelve named measures taught in this lecture.

The two families at a glance.

  • Chidamber and Kemerer (CK) suite — introduced in 1994 and still the most studied family. Six metrics, each anchored to a single class and its immediate neighbors: WMC (Weighted Methods per Class), DIT (Depth of Inheritance Tree), NOC (Number of Children), CBO (Coupling Between Objects), RFC (Response for a Class), and LCOM (Lack of Cohesion in Methods).
  • MOOD suite (Metrics for Object-Oriented Design — heard as "mood" and as "nude" in the recording) — six metrics that measure similar concerns at system level through ratios in : MIF (Method Inheritance Factor), AIF (Attribute Inheritance Factor), COF (Coupling Factor), POF (Polymorphism Factor), MHF (Method Hiding Factor), and AHF (Attribute Hiding Factor).

The families do not compete directly. CK measures tend to focus on a single class and its immediate neighbors, giving a direct count from a diagram (e.g., count children of class ). MOOD measures tend to summarize an entire system or package as a fraction (e.g., what share of all methods in the system are inherited rather than newly defined). Teams often run both. Together they cover the design properties taught earlier — inheritance, encapsulation, coupling, cohesion, and polymorphism — from a local lens (which class is risky?) and a global lens (is the system drifting?).

Scope — twelve is a catalog, not a constitution. The lecture stressed that additional measures exist beyond these twelve, including counts of messages sent, Halstead volume, McCabe cyclomatic numbers at code level, and package-level aggregates. The twelve are the shared language that lets teams compare findings; they are not an exhaustive list, and a team may define a thirteenth when its question is not answered by the catalog.

15.5.2 How the Twelve Map to Design Properties

Mapping the twelve to the properties taught earlier helps keep the list organized and prevents treating the metrics as twelve unrelated numbers.

Property-to-metric map.

Design property CK expression (class level) MOOD expression (system level)
Inheritance / reuse DIT — how deep a class sits; NOC — how wide its direct reuse is MIF — share of methods that are inherited; AIF — share of attributes that are inherited
Encapsulation / information hiding Indirectly via LCOM (cohesion) and via the design itself; direct hiding appears in MOOD MHF — share of methods that are hidden; AHF — share of attributes that are hidden
Coupling CBO — number of other classes a class depends on; RFC — size of the response set a message can trigger COF — fraction of all possible ordered class pairs that are actually coupled (excluding inheritance)
Cohesion LCOM — how many method pairs share no state (lack) (no direct MOOD cohesion ratio; read via LCOM and via hiding)
Polymorphism (implicit in DIT/NOC/RFC interaction) POF — share of possible polymorphic override opportunities that are exercised
Complexity / size WMC — sum of per-method complexities; also size as count of methods/attributes; fan-in / fan-out on CRC cards Structural complexity seen in aggregates and in the distributions of the above ratios

Polymorphism appears as a direct POF ratio in MOOD, while complexity appears as WMC in CK. Structural complexity, size in terms of count of classes or attributes, and fan-in versus fan-out also frame discussion at package and component levels, beyond a single class. The spoken phrase "volume of reason" in the recording is the spoken form of volume-of-design or size-related measure in this context — a reminder that size itself is a design property to watch alongside the twelve.

Visual intuition: imagine a radar chart with five axes — Inheritance, Encapsulation, Coupling, Cohesion, Polymorphism — and a sixth axis for Size/Complexity at the center. Plot CK-to-MOOD for one system: one shape per release. A system where inheritance is overused blooms on the Inheritance axis (high DIT, high MIF/AIF) while cohesion collapses; a system where encapsulation is eroding flattens on the Hiding axis (falling MHF/AHF) while coupling blooms. That shape tells at a glance which property the design is trading away.

15.5.3 Direct Counts and System-Level Ratios

A useful distinction raised was between a count that can be read directly from a diagram and a ratio that needs a system-wide denominator. Understanding this split tells you how to collect each number and how to interpret it.

Direct counts (CK style). Give the hierarchy or the class box, the count follows with a single rule and no global denominator.

  • — count the immediate children of one parent in the inheritance tree.
  • — count edges upward from one class.
  • — count distinct collaborators of one class.
  • and from method-variable-use overlap — both counted from one class's members.

A direct count flags a single risky class. It answers "which class should we review or split first?"

System-level ratios (MOOD style). Compare an inherited or hidden or coupled population to all elements across the system, so the denominator needs every class.

  • — inherited methods not overridden over all methods available in the system.
  • follow the same pattern: a summed numerator over classes divided by the maximum or total that could exist, so the result lies in .

A ratio flags a system-wide drift. It answers "is the whole system becoming more coupled, more exposed, or more polymorphically flexible than it was last release?" For example, a steady fall in / means a steadily growing share of methods and attributes are becoming public — an encapsulation drift that no single-class count would reveal on its own.

Both styles are valuable, and value grows when they are read together. A high on one class plus a rising at system level tells a consistent story about coupling that either number alone would leave ambiguous. Like the blood-test analogy from 15.3, no single number settles whether a design is good. The measures are indicators that gain meaning when interpreted together and against a reference set — distributions from comparable projects or past releases of the same product.

Q: Will production of a single metric be enough to judge a design or a product?

A: No. A single metric or a single count rarely gives a reliable signal. Properties such as usability, quality, and reliability cannot be read from one value. An indicator set — for example coupling together with cohesion and a size/complexity measure — gives a more stable picture. Interpretation also needs feedback over time. Teams collect values release after release, check whether predicted risk matched observed faults and change effort, and adjust which metrics they watch. The trigger was the hope that one number could serve as a verdict; the correction is that judgment needs a correlated, cross-validated set — that is why the course presents twelve, not one.

Pitfalls — reading twelve numbers as twelve verdicts.

  • Treating a ratio near 1 or 0 as inherently good or bad. A MIF near 1 can mean a stable framework with faithful reuse or a lazy leaf layer that adds nothing — intent decides. Fix: read ratios against design intent, not against an absolute ideal.
  • Comparing a class-level count directly with a system-level ratio. Fix: compare class counts with class distributions and ratios with ratio histories; do not rank a class by its contribution to COF alone.

Recap — two lenses, twelve views, one indicator habit. The CK suite (1994) profiles the class and its neighbors with direct counts — WMC, DIT, NOC, CBO, RFC, LCOM. The MOOD suite profiles the whole system with bounded ratios — MIF, AIF, COF, POF, MHF, AHF. Their joint map covers inheritance, encapsulation, coupling, cohesion, polymorphism, and size/complexity. Counts flag local risk; ratios flag global drift. Either lens alone is partial; together they are the indicator set the foundations chapter demanded.

Bridge: The map is now in the room. The next two sections open the CK half of the catalog in detail — WMC, DIT, and NOC in 15.6, then CBO, RFC, and LCOM in 15.7 — each with a formula, a worked sketch, and the trade-off the number reveals.

Exam note: Expect to distinguish CK and MOOD by what each measures and at what level (class/neighbor versus system/package ratio), and to compute a small example at the class level (e.g., DIT or NOC on a tree) versus interpreting a system-level ratio (e.g., what a rise in COF or fall in AHF means).

Connections: The property map here is the index into 15.6–15.9, where each row becomes a formula and a worked example worth practicing by hand before trusting a tool.

15.6 CK Suite — Weighted Methods per Class, Depth of Inheritance Tree, Number of Children

15.6.1 WMC — Weighted Methods per Class

Hook — how heavy is this class to carry? A class with two trivial getters and a class with two 80-line branching engines both say "two methods." Weight tells the difference.

Weighted Methods per Class (WMC) estimates the complexity of a class by summing the complexities of its methods. The verbal description given in the lecture was direct: each method has its own complexity weight, and the class weight is the sum of those weights. That verbal line becomes the governing equation.

Formalize — from words to weight. Let a class have methods . Let be the complexity assigned to method . Then

where is the weight for method , often taken as for a uniform count or as cyclomatic complexity for a weighted count, and is the number of methods declared in . The phrase "give F8 according to that" in the recording is the spoken form of "give a weight according to complexity."

Two common weightings and when to use each:

  • Uniform weighting for all : then , a simple method count. Cheap to compute on a class diagram before pseudo-code exists. Useful for early size and staffing estimates.
  • Cyclomatic weighting for each method's control-flow graph: then a branching method counts more than a straight-line accessor. Useful when method detail is available and testing effort is the question.

Every symbol named: is the class under measure; is the -th method declared in ; is the complexity of ; is the method count of ; is the resulting class weight (dimensionless, but comparable within a reference set). For tensor or shape clarity: is a scalar per class, aggregatable by summation to a package total .

Why WMC matters is threefold:

  • Effort to build, understand, and test grows with WMC — more methods or more complex methods each add a distinct path or collaboration to verify.
  • Specificity and reuse. A class with a large tends to be more specific to one context, which limits its reuse across the system. It tries to do too much in one place.
  • Signal for extraction. A heavy method that mixes unrelated logic inside a heavy class is a sign that responsibility should be extracted to a collaborator.

Two worked intuitions were given in the lecture: first, the number of methods and their individual complexities predicts time and staffing needed for that class; second, a class with a large and high has a larger impact on its children because children inherit that weight and must be understood together with it — complexity propagates downward.

Worked example — WMC as sum of method complexities.

Take class OrderProcessor with methods declared: validate(), price(), dispatch().

Case A — uniform weighting. Assign for each method.

Case B — cyclomatic weighting. Suppose code review gives cyclomatic numbers: (three branches + entry), (pricing rules), (straight-line with one guard). Then

Reading the gap: Case A and Case B have the same method count but Case B is more than four times heavier for testing — price() alone needs seven path tests. A second class OrderLine with and has . Both OrderProcessor (uniform) and OrderLine read as 3, but the weighted view reveals where test budget should go. Teams often publish both numbers: method count for size, weighted WMC for test risk.

Sense-check: a class with WMC = 0 has no methods and is either an empty stub or a pure data holder whose behavior lives elsewhere — review for feature envy. A class with WMC far above team norms (e.g., 45 where peers are 10–20) invites extraction by responsibility.

Scope — assumptions that make WMC meaningful. WMC assumes that the complexity weight is comparable across methods. Mixing unstated conventions (some methods weighted by LOC, others by cyclomatic) breaks that assumption and makes the sum misleading. It also assumes methods are at a comparable abstraction level — counting both a one-line getter and a 40-line orchestration as 1 each under uniform weighting understates risk. When those assumptions fail, publish the weighted variant alongside the count.

Pitfall — reinventing a God class by aggregation. Summing child WMCs into a package total is useful for staffing, but a package total can hide one extreme class. Always report maxima and distributions alongside sums.

Visual intuition: picture a bar chart per class with one segment per method, height equal to . A healthy service shows many short, even bars; a God class shows a few towering bars next to many tiny ones. The towering segment is the extraction candidate — it names the responsibility that wants its own class.

Real-world: In code review, reviewers flag a WMC outlier within a service boundary and ask whether the class can be broken by responsibility (information expert / GRASP) or whether the heaviest method can be simplified by extracting a strategy or helper class.

15.6.2 DIT — Depth of Inheritance Tree

Intuition — how far from the family founder do you live? A class deep in the inheritance tree inherits the vocabulary of every ancestor — powerful to reuse, costly to understand.

Depth of Inheritance Tree (DIT) measures how far a class sits from the root of its hierarchy. The verbal description given was: the maximum length from the class node to the root of the class hierarchy tree, counting inheritance edges. If multiple inheritance is present (several parents), the longest path to any root is taken — the measure reports the worst-case understanding load.

Formalize — counting edges upward. For a class ,

where ranges over roots of the hierarchy and length counts edges (inheritance links), not nodes, so a direct child of the root has . A class at level 4 under the root — root → A → B → C → target — has .

Every symbol named: is the class under measure; is any inheritance root reachable from (a class with no super-class); length is the count of inheritance edges on the path; is a non-negative integer, usually for a root that inherits from nothing (some authors define root as 0 rather than 1 — agree on convention before comparing). The longest path matters because multiple inheritance gives several ways to reach a root, and the deepest chain dictates the heaviest inherited vocabulary.

Why DIT matters involves a classic trade-off that the lecture stressed:

  • Deeper means richer reuse. A class deeper in the tree inherits more methods and state without redefinition — "greater the number of methods it is likely to inherit, making it more complex to understand" was the spoken caution alongside the benefit. The subclass can lean on the ancestor's tested contracts.
  • Deeper means heavier understanding and change cost. A lower-level subclass must be understood together with all ancestors that supply behavior. A change high in the tree ripples downward to every descendant. Some variants of the measure count only methods that are not overridden, treating an overridden method as specialized rather than inherited weight — the lecture noted this as a defensible variation.

The teaching emphasized that deeper trees raise design complexity, so depth should be kept modest. Values around 3 to 5 were described as a practical informal ceiling in many projects, though the correct limit depends on domain and on how stable the upper levels are. A mature, frozen library root justifies deeper extension; a volatile domain model does not. Deeper trees can offer more reuse of inherited methods, yet they also make change more delicate and testing broader because a single message may dispatch through overridden steps.

Worked example — DIT with subclasses 4 and 5 at depth 2.

Picture a hierarchy:

        Root (DIT=0)
        /    \
   Class1  Class2
     |       / \
     |   Class3 Class4
     |      |
   (no)   Class5

The lecture's concrete case: subclasses 4 and 5 sit two edges below the root, so their . Classes 1 and 2 sitting one edge below the root have . More fully, trace any path:

  • Class2: Root → Class2 = 1 edge ⇒ DIT = 1.
  • Class4 (a child of Class2): Root → Class2 → Class4 = 2 edges ⇒ DIT = 2.
  • Class5 (a child of Class3, which is a child of Class2): Root → Class2 → Class3 → Class5 = 3 edges ⇒ DIT = 3 in this elaboration — the longest path governs if multiple paths exist.

Sense-check: adding a level always adds exactly 1 to DIT; re-parenting a class to a deeper ancestor increases its DIT by the length difference; multiple inheritance never reduces DIT because the max over paths is reported.

Numerical cross-check: if every edge inherits on average methods, a class at DIT = 4 implicitly carries about inherited methods that must be held in mind together with its own. That linear accumulation is why depth 6+ is often a review trigger.

Scope — when depth is deceptive. DIT counts edges, not behavior. A hierarchy of pure interfaces with no inherited state or method bodies contributes far less per level than a hierarchy of concrete classes. Some variants therefore count only non-overridden inherited methods rather than raw depth, which better tracks the "methods inherited" intuition the lecture named. Agree locally on which variant the tool computes before comparing teams.

Pitfall — deep for reuse's sake. Deepening a tree to share one helper method trades a small duplication saving for a large comprehension cost across all descendants. Fix: prefer composition or a utility collaborator when reuse is narrow.

15.6.3 NOC — Number of Children

Intuition — how many direct reports does this class manage? Breadth measures immediate reuse impact the way depth measures inherited load.

Number of Children (NOC) counts immediate subclasses of a class, not all descendants. The verbal description given was: at any moment, look for immediate subordinates — the direct children in the hierarchy, one level down only.

Formalize — one level, not the whole subtree. For a class ,

so only subclasses connected by a single inheritance edge count. An example given in the lecture was class with rather than 4, precisely because only direct children qualify — a grandchild is a child of the child, not of .

Every symbol named: is the parent under measure; ranges over classes that directly extend or implement ; is set cardinality; is a non-negative integer. Contrast with "number of descendants" (the whole subtree) — that broader count answers a different question and is not NOC.

Why NOC matters mirrors the depth discussion but along breadth:

  • Higher NOC means stronger reuse of the parent abstraction — many classes build on it — but it also means the abstraction must serve many distinct needs, which can dilute its focus. The spoken line "as NOC increases reuse increases but the abstraction may be diluted" captures this tension.
  • A class that is split into teaching staff, non-teaching staff, and contract staff illustrates a coherent case where a parent Employee abstraction is shared by several children, each adding a distinct variation while the parent statement "an Employee has an ID, a name, and a work assignment" stays true without exception.
  • If unrelated concepts are forced under the same parent merely to raise reuse, the parent loses clarity — it must promise services that make sense for some children but not others (varying return types, conditional behavior flags). That is the dilution of abstraction.

Testing and change impact is also direct. A class with a large NOC influences many children, so faults in the parent propagate widely and testing effort should concentrate there (more child test suites re-exercise the contract). Changing such a class is harder because each child may rely on details of the contract; the open-closed principle (extend without modifying) is put under strain. The spoken guidance "depth is generally better than breadth" was the spoken form of the design heuristic that a deep chain that refines a focused abstraction is often healthier than a wide fan of loosely related children under one parent. For that reason designs where lower-level classes carry a large NOC invite extra scrutiny, and designs with multiple inheritance need additional review for competing contracts.

Worked example — NOC with Employee teaching/non-teaching/contract plus the C2 count.

Hierarchy sketch of the Employee branch:

Employee (abstract)  NOC = 3
├── TeachingStaff
├── NonTeachingStaff
└── ContractStaff

So . Each child adds one specialized attribute or policy (e.g., coursesTaught, labAssignment, contractEndDate) and overrides computePay() differently. The parent contract stays coherent because the rule "polymorphic pay must be computable from Employee data plus subtype policy" holds for all three.

Now consider the lecture's C2 diagram where rather than 4. Suppose C2 has children D1, D2, D3, and D3 has a child G1. Counting:

  • Direct: D1, D2, D3 ⇒ 3 — correct NOC.
  • Indirect: G1 is a descendant but is one edge away from D3, not from C2 ⇒ excluded.
  • If a novice counted all descendants, they would report 4 — the error the lecture used to correct the definition to "immediate."

Sense-check: splitting TeachingStaff further into Professor and Lecturer raises but leaves unchanged — measure at the right parent. A sudden jump in NOC at a mid-level class (e.g., from 2 to 9 in one sprint) is a review signal for dilution even if total class count barely moved.

Q: When NOC grows, reuse goes up. Why is that described as dilution of abstraction?

A: Reuse here means more children rely on the parent. Dilution means the parent must be general enough to serve all those children. If the children are coherent variations such as different kinds of employees — teaching, non-teaching, contract — the abstraction stays focused because the same invariant can be stated for every child. If the children represent unrelated ideas grouped only for convenience, the parent is stretched thin. It must promise services that make sense for some children but not for others, often via conditional logic or optional attributes. That loss of focus is the dilution. A good test is whether a statement about the parent remains true for every child without exception. If exceptions accumulate ("all Employees have a department, except contract staff who…"), the grouping is too wide, and the metric signals that the parent may need to be refactored into more focused parents (e.g., SalariedEmployee versus Contractor).

Trigger was the surprise that a reuse-friendly number could be bad. Resolution is that reuse magnitude and abstraction quality are orthogonal — NOC measures the first; the truth-test for all children measures the second.

Q: At the class level, how is size measured before code exists?

A: From the diagram, not from LOC. Count methods per class, attributes per class, and collaborations visible in CRC cards and associations. Lines of code are not yet available, so structural counts and dependency counts serve as the size and complexity proxies. That is why WMC (as method count), NOC, and CBO appear together in the same section — they are all diagram-readable before any implementation. Once pseudo-code exists, weight methods by cyclomatic complexity for the weighted WMC variant.

Pitfalls — NOC traps.

  • Counting descendants as children. Fix: count exactly one inheritance edge down — draw the tree and circle only the direct neighbors.
  • Wide fan as reuse theater. Creating many children under a catch-all Entity or Manager to claim reuse while the parent becomes a bag of optional fields. Fix: apply the "true for every child" test; if it fails, split the parent.
  • Large NOC low in the tree. High NOC on a leaf-level refinement (e.g., SpecializedLecturer with 8 children) suggests the leaf is itself an under-modeled domain concept — consider whether an intermediate abstraction is missing.

Real-world: reviewers who see a sudden NOC spike ask the author to walk through the parent invariant for each child. If the walk needs a per-child exception clause, the reviewer requests a refactor into a narrower hierarchy or a composition-based design before approval.

Recap — WMC is local weight, DIT is vertical reuse depth, NOC is horizontal reuse breadth. captures per-class complexity (count or cyclomatic-weighted); counts the longest path to a root and favors modest depth (informal ceiling 3–5, intent-dependent); counts only immediate children and warns when breadth dilutes the parent contract. Uniform versus weighted WMC separates size from test risk; depth versus breadth separates deep refinement from wide fan-out.

Bridge: Inheritance tells one side of the CK story. Coupling tells the other. The next section completes the CK suite with Coupling Between Objects (fan-out), Response for a Class (call-chain reach), and Lack of Cohesion in Methods (state-sharing shortfall).

Connections: WMC's method-count intuition links back to 15.2's complexity proxies; DIT/NOC's depth-breadth trade-off previews the MIF/AIF system ratios in 15.8 that ask the same questions at whole-system scale.

15.7 CK Suite — Coupling Between Objects, Response for a Class, Lack of Cohesion in Methods

15.7.1 CBO — Coupling Between Objects

Hook — how many other classes does this one class need to call to do its job? A class that phones ten neighbors for every task is harder to move than one that phones two.

Coupling Between Objects (CBO) measures how strongly a class depends on other classes outside inheritance. The verbal description given was: the number of other classes referenced in class through method calls or attribute accesses, counting non-inheritance collaborations. It was also described as fan-out of a class.

Formalize — distinct collaborators, inheritance excluded. For a class ,

where uses means a method of calls a method of or accesses data of (attribute read/write or parameter-type dependence that induces a compile-time link). Inheritance links are not counted — a child inheriting from a parent is not coupling in the CBO sense; it is reuse via specialization. The "distinct" matters: if calls ten times, counts once; if uses and , its CBO is 2.

Every symbol named: is the class under measure; ranges over all other classes in the system; "uses" is the collaboration predicate defined above; is cardinality; is a non-negative integer. CRC cards already hint at this count because each Collaborator line records a dependency that, when realized as an association or dependency arrow on the class diagram, contributes at least one to CBO.

Direction matters. CBO as usually taught counts fan-out — outgoing dependence. The companion notion fan-in — how many other classes depend on — is a separate measure. High fan-out makes this class fragile (many places can break it); high fan-in makes clients fragile but can be healthy when is a stable abstraction (e.g., an interface or library).

Why CBO matters:

  • Higher CBO means more dependencies, which lowers the chance that the class can be reused in isolation — it drags its collaborators with it.
  • The lecture stressed that low coupling is desirable but zero coupling is not a sensible goal; some collaboration is necessary to do useful work. A class with CBO = 0 does nothing useful in an OO system.
  • Empirically, teams have found that low coupling correlates with fewer defects and cheaper change — fewer dependency edges mean fewer ripple paths when a collaborator changes.
  • When coupling is high, understanding a class in isolation becomes hard, and a change in a collaborator can ripple back unexpectedly.

High fan-out therefore draws attention during review, while high fan-in across the whole system is a separate strategic pattern — many clients depending on a stable abstraction can be healthy, whereas one class depending on many unstable collaborators is fragile. That is the fan-in versus fan-out distinction the lecture used with the small picture of each counting distinct others they depend on.

Worked example — CBO fan-out with classes C1, C2, C3 and CRC cards.

Suppose the class diagram shows:

  • C1 uses C2 (calls C2.validate()) and uses C3 (reads C3.status)
  • C2 uses C3 only
  • C3 uses no other class (leaf)

Counts:

CRC-card check: for C1, its Collaborators list reads "C2, C3" — two lines ⇒ CBO at least 2, matching the diagram. If C1 also inherits from Base, that parent does not increment CBO; only associated/used classes do.

System sense-check: total distinct directed edges here is 3. A class with CBO = 8 in a system where the median is 2 would be a review outlier — ask whether an intermediary, a facade, or moving a responsibility to its owner (information expert) would cut the fan-out.

Refactoring move: if C1 uses both C2 and C3 only to obtain a price that C2 already knows how to compute from C3, move the price logic into C2 and reduce from 2 to 1 while keeping behavior identical.

Scope — what CBO does not count. CBO counts non-inheritance uses. It does not count inheritance, and it does not weight frequency — ten calls to the same collaborator are still one distinct edge. For frequency-weighted coupling, pair CBO with RFC or with message counts from interaction diagrams.

Pitfall — CBO = 0 heroics. Attempting to drive CBO to zero by inlining collaborators produces a God class with high WMC and low cohesion. Fix: trade coupling against cohesion explicitly — accept a small, deliberate coupling that keeps each class focused.

Visual intuition: picture a dependency graph with classes as dots and "uses" edges as arrows. High-CBO classes sit at the center of a starburst with many outgoing arrows; high fan-in classes sit at the center of many incoming arrows. A healthy module looks like a few stars (stable abstractions with high fan-in) surrounded by smaller nodes with 1–3 outgoing edges each — not a hairball where every node points to every other.

Real-world strategy: during design reviews many teams set a soft trigger such as for individual review. The number is not a gate; it is a question — "is this fan-out purposeful, or can we introduce an interface to hide the collaborators behind a single contract?"

15.7.2 RFC — Response for a Class

Intuition — when this class receives one message, how many methods might run? One request can set off a chain. The longer the chain, the more tests you need and the harder it is to hold the behavior in your head.

Response for a Class (RFC) measures the size of the response set for a class — how many methods can execute when an object of the class receives a message. The verbal description given was: the set of methods that are potentially executed in response to a message received by an object, read from interaction diagrams as the calls the object may trigger, including its own methods and the methods it invokes on others.

Formalize — the response set as a union. Let be the set of methods declared in . Let be the set of methods called by (directly invoked on any collaborator or on self). Then the response set is

and

so is the cardinality of that union — count each distinct method once even if several paths reach it.

Every symbol named: is the class under measure; is the set of methods of ; is the -th method; is the set of methods directly calls (in any class); is the response set — all methods that could run starting from any method of ; is its size. Because interaction diagrams already enumerate for a given class what messages it receives and what it sends, RFC can be read directly from those diagrams plus the class's own method list.

Why RFC matters is similar to coupling but with a call-graph flavor. Each additional method in the response set adds a distinct path that tests must consider — a message placeOrder() that can trigger validate(), price(), discount(), and notify() is four distinct behaviors to verify. Larger RFC means higher complexity for testing and for reasoning, since a single message can set off a chain whose branches interact. More testing effort follows higher values — RFC is routinely paired with cyclomatic complexity to size a test plan. Like CBO, RFC is interpreted as a complexity and dependency indicator, with lower preferred, ceteris paribus.

A useful mental contrast: CBO counts how many classes you depend on (breadth across types); RFC counts how many methods could run (reach along the call graph). A class can have modest CBO but large RFC if it calls many helpers on one collaborator, or modest RFC but large CBO if it touches many collaborators once each. Both are worth watching.

Worked example — RFC as a union.

Consider class OrderService with .

  • placeOrder calls Inventory.reserve(), Pricing.compute(), and Notification.send()
  • cancel calls Inventory.release()

So

If Pricing.compute() internally calls Tax.lookup() but RFC as usually taught stops at direct calls from (one hop), the indirect callee is not included. Some authors count transitive closure; agree locally which convention a tool uses before comparing numbers. Either way, a class whose RFC is double the team median should prompt a test-design question: does every behavior in that response set have a meaningful test, or can some helpers be encapsulated behind a smaller contract?

Sense-check: a class with one method that calls no helper has ; a class where one incoming message fans out to 10 helpers has and deserves either decomposition or contract tests for the helpers.

Scope — direct versus transitive. The classic CK definition counts only methods directly called by (plus itself). Some tools and textbooks extend to transitive calls. Either variant is defensible but they produce different numbers — never compare an RFC from one convention with a threshold from the other without adjusting.

Pitfall — shrinking RFC by hiding real dependencies. Inlining helpers into one large method lowers RFC while raising WMC and destroying cohesion. Fix: read RFC together with WMC and LCOM; a "better" RFC that worsens the other two is not an improvement.

Visual intuition: picture a sequence diagram where the vertical axis is time and horizontal lanes are objects. Drop a message onto OrderService.placeOrder() at the top. The diagram fans outward into horizontal arrows to Inventory, Pricing, Notification. RFC is roughly the number of distinct arrow labels reachable from that entry point plus the entry itself. A diagram that fans to 2–3 collaborators reads as RFC ≈ 4; one that fans to eight reads as a review flag.

15.7.3 LCOM — Lack of Cohesion in Methods

Hook — do the methods of this class work on the same data? A class where every method touches the same core state feels like one idea. A class where half the methods use one set of fields and the other half use a disjoint set feels like two ideas sharing a name.

Cohesion describes how closely the duties inside a class belong together. Lack of Cohesion in Methods (LCOM) measures the absence of that closeness. The verbal description given was: look at which instance variables each method uses; two methods are similar if they share variables.

Formalize — disjoint versus shared variable-use pairs. Let be the set of instance variables used by method . Consider all unordered pairs of distinct methods with . Define

where counts pairs with empty intersection (no shared variable) and counts pairs that share at least one variable. Then the original form used in the session — the Chidamber–Kemerer 1994 definition — was

also phrased as count of empty intersections minus count of non-empty intersections, with a floor at zero. A value of zero is the best case in this formulation — it signals that cohesive pairs are at least as numerous as disjoint pairs. It does not, however, distinguish degrees of high cohesion (a fully cohesive 4-method class and a moderately cohesive one both read as 0), which is why later variants were invented such as LCOM3, LCOM4, and LCOM5 that the session noted as "more additions people have come up with." The spoken phrase "welcome of zero" in the recording is the spoken form of "value of zero."

Every symbol named: is the -th method; is the variable-use set for ; collects pairs with no overlap; collects pairs with overlap; is the floored difference. LCOM is a non-negative integer (in this variant); later variants normalize to or .

Interpretation needs care. High LCOM means many method pairs operate on disjoint state. That suggests the class groups unrelated duties and may need to be split into more focused classes, each owning one variable cluster. Low LCOM — near zero — means methods tend to share state, which aligns with higher cohesion. Only when exceeds does the positive difference signal a cohesion shortfall. Encapsulation concerns such as whether a member is private are not part of LCOM; accessibility and cohesion were treated as separate measures, a point that drew the student question transcribed below. In the special case where a method touches no instance variable at all (a pure utility or factory method), its makes every pairing with it empty-intersecting — a known bias of the original formula that later variants correct.

A small teaching picture accompanied the formula: label instance variables used by method and intersect with variables used by method . If and are variables used by different method groups, pairs drawn from different groups fall into while pairs drawn from the same group fall into . The instructor invited the group to read more about LCOM during a short break and to try examples before asking follow-up questions — the classic way to internalize the pair-count idea before moving to variants.

Worked example — LCOM pair count: empty versus non-empty intersection with zero floor.

Take class BankAccount with three methods and instance variables :

  • :
  • :
  • :

List all unordered pairs:

  • ⇒ belongs to Q.
  • ⇒ belongs to P.
  • ⇒ belongs to P.

So and

Reading: a positive LCOM signals a cohesion shortfall — indeed updateOwner forms a state-disjoint cluster from the balance cluster. Splitting into AccountBalance (balance, rate) and AccountHolder (owner) would give for each new class.

Second case: add a fourth method with . Now pairs:

  • , , — three sharing balance.
  • , , — three disjoint with owner.
  • . Zero here masks that one method cluster is still disjoint — that masking is why LCOM3/LCOM5 were later introduced to separate "zero but split" from "truly cohesive."

Sense-check: a class where every method touches the same single variable has ⇒ LCOM = 0 (ideal). A class where no two methods share any variable has (maximal lack).

Q: Does accessibility of methods or attributes — such as private versus public — change LCOM?

A: No. LCOM looks only at which instance variables methods touch, not at visibility. Whether a method is private, protected, or public influences hiding factors, which are separate encapsulation metrics (MHF/AHF in 15.9). Whether an attribute is public or private does not change which set a method belongs to — a private field used by two methods still creates a shared-variable pair in . Mixing the two would conflate cohesion with information hiding, and the metric families keep them apart.

Trigger was the natural hypothesis that private members should affect cohesion. Resolution is the terminological contrast that cohesion is about state-sharing topology while accessibility is about exposure control — different axes measured by different suites.

Scope — which LCOM variant is in your tool. The original LCOM (floor at zero) is coarse; LCOM3 counts connected components of the method-variable graph, LCOM5 normalizes by method-attribute overlap, and Henderson-Sellers variants scale differently. Tools report different numbers for the same class — lock the variant before setting any threshold.

Pitfalls — LCOM misreads.

  • Utility-method bias. A static-like helper with empty creates empty intersections even in a cohesive class. Fix: exclude pure utilities from LCOM or read LCOM5 which adjusts for this.
  • Getters inflate Q in a simple way. A class of only getters/setters each touching one variable can appear disjoint. Fix: read LCOM alongside WMC/CBO — a data-holder class is a different smell from a mixed-responsibility class.
  • Treating LCOM as a gate. Because the original LCOM floors at zero, many cohesive and moderately cohesive classes share the same score. Fix: use LCOM distributions and the graph-based variants rather than a single cutoff.

Visual intuition: picture a bipartite graph with methods on the left and instance variables on the right; draw an edge whenever a method uses a variable. LCOM asks how many method-method pairs are disconnected through shared variables. In a cohesive class the graph is one connected blob (many paths between methods via shared variables); in a non-cohesive class the graph splits into two or more islands — each island is a candidate for its own class.

Real-world: refactoring tools often compute LCOM alongside CBO and WMC. A class that is an outlier on all three — heavy (high WMC), highly coupled (high CBO/RFC), and lacking cohesion (high LCOM) — is a strong candidate for extraction into collaborating, focused classes. Reviewers then check that the proposed split actually reduces the island count rather than merely scattering members.

Recap — coupling breadth, response reach, and cohesion texture. counts distinct non-inheritance collaborators (fan-out); counts how many methods a single incoming message could trigger (test reach); counts how many method pairs share no state versus share state, with zero floored as "best" in the original and finer variants beyond it. Lower CBO/RFC is preferred; near-zero (or low graph-disconnect) LCOM is preferred. Accessibility is orthogonal — it changes hiding, not cohesion.

Bridge: CK has now been covered end-to-end at the class level — WMC/DIT/NOC for size and inheritance, CBO/RFC/LCOM for coupling and cohesion. The two MOOD sections that follow lift the same questions to system-level ratios: how much is inherited, how coupled the system is as a whole, and how polymorphic and encapsulated it is.

Connections: CBO's CRC-card count prefigures COF's system-wide is_client ratio; RFC's call-graph reach prefigures POF's override-discipline ratio; LCOM's accessibility caveat is the handoff to MHF/AHF.

15.8 MOOD Suite — Method and Attribute Inheritance Factors and Coupling Factor

15.8.1 MIF — Method Inheritance Factor

Hook — of all the methods your system can run, how many came from inheritance rather than being newly written or overridden? A system that mostly reuses inherited behavior feels different — for better and worse — from one that rewrites almost everything locally.

Method Inheritance Factor (MIF) is a system-level ratio that reports how much of the available method population is inherited rather than newly defined or overridden. The verbal description given was: numerator counts all inherited methods that are not overridden, summed across every class; denominator counts all methods that can be invoked in each class — declared plus inherited plus overridden contributions — summed across the system. Because numerator and denominator are summed over the same system, the ratio lies in and is comparable across systems of different size.

Formalize — inherited-not-overridden over all available methods. For each class let be the count of inherited methods not overridden in , let be the count of methods declared in (the class's own definitions, including new and overriding), and let be the count of methods that can be invoked in — the available vocabulary viewed from that class — where

with as new methods (introduced for the first time in ), as overriding methods (redefining an ancestor signature in ), and as above. Then

where (total classes) is the number of classes in the system.

Every symbol named: is the -th class; is inherited-not-overridden methods in ; are new methods; are overrides; is the total invokable methods available from 's perspective via its inheritance chain; is total classes; is the system inheritance share for behavior.

Reading the ends: MIF near 1 means little specialization — the system largely reuses inherited behavior unchanged, a leaf-heavy reuse culture. MIF near 0 means large change — many methods are newly introduced or overridden in leaves, a specialization-heavy culture. No single best value exists; the intended degree of specialization drives the target. A stable framework often tolerates higher MIF in its extension layers; a fast-evolving domain often shows lower MIF as leaves add new operations.

Worked example — MIF system ratio of inherited methods to total available.

Take a tiny system with classes in a chain (A is root, B child of A, C child of B):

  • : defines new methods, inherits none ⇒ .
  • : inherits 4 from A, overrides 1, adds 2 new ⇒ (4−1), , , so .
  • : inherits 6 from B (the available set), overrides 1, adds 1 new ⇒ , , , so .

Sums:

Sense-check: roughly half the invocable methods are inherited unchanged — moderate reuse. If C instead overrode all 6 inherited methods (), then and — low inheritance, high local specialization, which may be correct if C represents a genuinely different policy.

Relationship to CK: MIF is the system-wide companion to DIT. DIT asks "how deep is this class's inheritance?"; MIF asks "across the whole system, how much of what you can call was inherited?"

Scope — what counts as "inherited." MIF counts only not-overridden inherited methods in the numerator. An override is local specialization, not reuse of ancestor behavior, so it moves from to . Inherited attributes versus inherited methods are separate — a class can inherit state heavily while overriding behavior heavily, so MIF and AIF can diverge sharply. Read both.

Pitfall — high MIF as lazy reuse. A very high MIF where leaf classes add almost nothing may signal an over-generalized parent whose contract is too vague to guide behavior. Fix: check AIF and leaf WMC alongside MIF — thin leaves with huge inherited surface suggest an abstraction that needs narrowing.

15.8.2 AIF — Attribute Inheritance Factor

Intuition — the same question, but for state: how much of the data you can access was inherited?

Attribute Inheritance Factor (AIF) mirrors MIF for state rather than behavior. The verbal description given was: same structure as MIF but counting attributes (fields). Let be inherited attributes not redefined in , and let be all attributes that can be accessed from via its inheritance chain. Then

Formalize — inherited-not-redefined attributes over all accessible attributes.

so with the same reading as MIF — near 1 signals heavy reliance on inherited state, near 0 signals heavy introduction of new state in leaf classes. Teams read MIF and AIF together to see whether reuse concentrates in behavior, in state, or in both — and whether that concentration matches intent.

Every symbol named: is inherited-not-redefined attributes in ; is all attributes accessible from ; much like for methods, one can think of with new attributes and redefined ones, though attribute redefinition is rarer than method overriding in most languages. is total classes.

The lecture stressed that MIF and AIF can tell different stories about the same hierarchy. An Employee hierarchy may have moderately high (ID, name, join-date inherited widely) but low (each subtype overrides computePay() differently). That divergence is informative, not contradictory — it says "shared state, specialized behavior," which often matches a healthy domain model.

Worked example — AIF alongside MIF.

Extend the tiny chain, now counting attributes:

  • : defines 3 attributes ⇒ .
  • : inherits 3, adds 1 ⇒ .
  • : inherits 4, adds 2 ⇒ .

Compare with the earlier . Both near one-half, but if C's new methods were many while its new attributes were few, MIF would drop while AIF stayed high — the expected "shared state, varied behavior" shape. A tool that reports MIF = 0.7 but AIF = 0.2 would instead suggest behavior is inherited but each leaf carries distinct state — unusual and worth a design review for whether data belongs higher in the tree.

Pitfall — attribute hiding confounds attribute inheritance. A private attribute in an ancestor is inherited in the language sense but not always accessible from the descendant for direct use. MOOD's counts what is accessible via the hierarchy, not merely declared. Fix: confirm whether the tool counts private inherited attributes before comparing AIF across codebases with different visibility conventions. Cross-read with AHF (Attribute Hiding Factor) from 15.9 to understand how much of what is counted as inherited is actually hidden.

15.8.3 COF — Coupling Factor

Hook — how coupled is the entire system, not just one star class? CBO tells you which class is the starburst; COF tells you whether the whole sky is a hairball.

Coupling Factor (COF) is a system-level coupling ratio that excludes inheritance coupling and counts only client-server collaborations — the uses edges that CBO counts locally, but now normalized so a 20-class system and a 200-class system can be compared.

The verbal description given was: numerator counts all non-inheritance couplings where a client class uses a server class; denominator counts the maximum possible couplings in the system, including both inheritance and non-inheritance possibilities, often written as — all ordered pairs of distinct classes — so the ratio stays between 0 and 1. Inheritance coupling (a derived class inheriting methods/attributes from a base) is counted in the denominator as a possible relation but not in the numerator, which isolates the non-inheritance dependencies that reviewers most want to limit.

Formalize — is_client over all ordered distinct pairs.

Define

Then

with as total classes.

Every symbol named: is the client class (potential user), the server class (potential used), is the 1/0 predicate that is 1 only for a genuine non-inheritance use edge, the double sum counts every actual client-server edge, and is the number of ordered distinct pairs that could be coupled. The spoken phrase "is client as in some connection" in the recording is the spoken form of the is_client predicate. Order matters: using and using are two distinct coupling facts for COF, mirroring the directed nature of dependencies on class diagrams.

Reading the ends: COF near 0 means low coupling — few of the possible ordered pairs are actually coupled, a loosely connected system. COF near 1 means almost every ordered pair is coupled — a highly entangled system where change anywhere can ripple everywhere. Interpretation seeks the lower side, though some coupling is inevitable — the zero-coupling trade-off from 15.3 applies at system scale too. A small, tightly scoped microservice may correctly tolerate moderately higher COF than a system-wide platform where every service must evolve independently.

Worked example — COF as normalized CBO.

Take a system with classes . Suppose actual non-inheritance uses are:

  • ,
  • No other uses; inheritance edges ( say) are excluded from the numerator.

Count: is 1 for three ordered pairs, 0 for the rest.

Sense-check: 25% of all possible directed uses are present. If two more edges were added ( and ), COF would rise to — noticeably more coupled. In a system, the same five edges would give — essentially sparse. That normalization is the point: five edges mean something completely different at different scales, and COF makes the comparison fair.

Relationship to CBO: summing over all counts the same numerator but without normalization. COF = when inheritance edges are excluded from CBO. A single-class CBO outlier flags a local hotspot; COF flags whether the whole system is drifting toward entanglement across releases.

Q: How is coupling at the system level (COF) different from CBO at the class level?

A: CBO counts for one class how many others it depends on — a fan-out number with no denominator, ideal for flagging a single risky class. COF normalizes that same idea across the whole system as a ratio — actual client-server edges divided by all ordered distinct pairs — so a 20-class system and a 200-class system can be compared and tracked over time. Both point the same way — lower is preferred — but COF makes the comparison fair by dividing by the maximum number of ordered pairs that could be coupled. The numerator counts only non-inheritance couplings; inheritance possibilities remain in the denominator so that COF stays bounded in and isolates the coupling reviewers most want to limit. A rising CBO on one class and a rising COF system-wide tell a consistent story; either alone is partial.

Scope — ordered pairs and exclusion of inheritance. Two subtleties to keep straight for the exam and the tool:

  • Ordered pairs in the denominator: is distinct from . This matches the directed nature of "uses." Some textbooks note this explicitly with the double sum over .
  • *Inheritance excluded from the numerator but included as a possible relation in the denominator: a derived class inheriting from its base is not counted as coupling for COF, but that ordered pair is one of the possibilities. That keeps COF focused on client-server* coupling, which is the dependency designers have more freedom to redesign.

Pitfall — COF near zero on a tiny system is not automatically good. With , a COF of 0.25 is 3 edges; with , COF 0.25 is ~7 edges. The same ratio at different permits very different absolute edge budgets. Fix: track absolute edge counts alongside COF, especially when is changing quickly.

Visual intuition: picture an adjacency matrix of size with diagonal blank (a class does not couple to itself). Mark a cell when uses outside inheritance. COF is the density of that matrix — fraction of cells filled. A sparse, well-modularized system shows a few clustered marks near subsystem blocks; a hairball shows marks scattered everywhere. Tracking that density across sprints gives the "coupling trend" plot that managers use to justify refactoring budgets.

Recap — MIF and AIF measure how much of what you can use was inherited; COF measures how much of what you could couple is actually coupled. for methods, for attributes, both in with no universal ideal — intent and framework stability decide. in with inheritance-excluded numerator and ordered-pair denominator; lower is preferred, with scale-aware reading.

Bridge: Inheritance share (MIF/AIF) and coupling density (COF) leave two questions: how polymorphic the hierarchy actually is, and how well implementation is hidden. The next section completes MOOD with Polymorphism Factor and the two Hiding Factors that quantify encapsulation.

Connections: MIF/AIF are the system-ratio siblings of CK's DIT/NOC; COF is the normalized sibling of CBO, designed to answer the same coupling question when system size changes.

15.9 MOOD Suite — Polymorphism Factor and Hiding Factors for Encapsulation

15.9.1 POF — Polymorphism Factor

*Hook — of all the places you could have used polymorphism, how many actually do? A hierarchy can allow overriding everywhere and exercise* it nowhere — or exercise it everywhere and become hard to reason about. POF measures the gap between opportunity and use.

Polymorphism Factor (POF) measures how extensively inherited methods are overridden to provide polymorphic behavior. The verbal description given was: numerator counts all overriding methods across the system; denominator counts the maximum number of distinct polymorphic situations that could exist, computed as new methods multiplied by descendants.

Formalize — overrides over opportunities. For each class let be the count of overriding methods — methods in that redefine an inherited signature — let be the count of new methods added in (first introduced there, not inherited), and let be the number of descendant classes of (all direct and indirect children, not just immediate NOC).

Then

where is the total number of classes and the product for a given base class counts how many distinct opportunities exist to override those new methods in its descendants. Each pair is one polymorphic slot that could be filled by an override.

Every symbol named: counts overrides in ; counts methods new to ; counts all descendants of ; is total classes; (define when no polymorphic opportunities exist, e.g., a flat system with no inheritance). A low POF means little overriding; a high POF means much of the possible polymorphic flexibility is exercised. Whether a high or low value is desired depends on design intent — frameworks that live on extension often want meaningful polymorphic extension; a stable payroll domain may need less and should not be forced toward high POF.

Worked example — POF as overrides over new-times-descendants.

Tiny hierarchy: (root) with children and ; has child . So .

Descendant counts: (B,C,D), (D), , .

Suppose method declarations:

  • : (new methods process, audit)
  • : overrides process, adds no new ⇒
  • : overrides process, adds new extra
  • : overrides audit (inherited via B→A) ⇒ , adds none ⇒

Numerator: .

Denominator: , plus , plus , plus ⇒ total 6.

Reading: half the override opportunities are taken — moderate polymorphic use. If instead only one descendant overrode one method, — a largely non-polymorphic hierarchy where inheritance is structural more than behavioral. If the denominator were 0 (no inheritance chain with new methods), POF is undefined and treated as 0 — there is no polymorphic question to answer.

Sense-check: adding a descendant without adding new methods in its ancestors leaves the denominator unchanged — the count of opportunities has not grown. Adding a new method in a class with many descendants grows the denominator proportionally, which is the key scaling insight POF captures.

Scope — what POF does not measure. POF counts overriding occurrence, not polymorphic call frequency at runtime and not dynamic dispatch cost. A system with POF = 0.4 may still dispatch polymorphically rarely if clients call concrete types directly. For behavioral frequency, pair POF with dynamic analysis; for design flexibility, POF alone suffices.

Pitfall — high POF as virtue theater. Pushing POF toward 1 by overriding for its own sake creates fragile, deep overrides that break Liskov substitution. Fix: ask whether each override genuinely refines the parent contract (subtype substitutability holds) rather than whether it exists.

15.9.2 MHF — Method Hiding Factor

Intuition — how much of the behavior you declared is hidden behind the wall versus exposed on the street?

Method Hiding Factor (MHF) quantifies encapsulation of behavior — the share of methods that are not visible outside their class.

The verbal description given was: count hidden methods — methods that are not visible to other classes, typically private methods and, in some treatments, protected methods — and divide by all methods.

Formalize — hidden methods over all declared methods. Let be the count of hidden methods in (often taken as private methods and sometimes including protected methods, especially when the focus is hiding from outside the inheritance hierarchy) and let be the count of methods declared in (the methods the class itself introduces plus overrides it declares; inherited methods not re-declared do not appear in ).

Then

so is a fraction.

Every symbol named: is hidden-method count in ; is total declared-method count in ; is total classes; answers "what share of declared behavior is hidden?"

Reading the ends: If every method is public, and no behavioral encapsulation is present — every helper is exposed and every change is a potential breaking change. If no method is public (all helpers are hidden and only inherited public interfaces are exposed), and behavior is fully hidden behind the class boundary, which is the high-encapsulation side viewed as desirable when interface exposure should be limited. Real systems sit well between. The session noted variation in practice: some authors count only private as hidden, others count private plus protected as hidden, especially when focus is on hiding from outside the inheritance hierarchy — agree locally before comparing. The spoken phrase "MF is 100%" in the recording corresponds to the high-encapsulation extreme in this notation.

Worked example — MHF as hidden over declared.

Suppose a system with two classes:

  • : 5 declared methods, 3 hidden (private) ⇒ .
  • : 4 declared methods, 1 hidden ⇒ .

Reading: 44% of declared behavior is hidden. If made two more helpers private, and — higher encapsulation, narrower public surface. A design rule such as "domain entities should keep at least 50% of methods hidden" would flag the 0.44 case for review without needing to read every class.

Notation cross-check: If protected is counted as hidden, move each protected method from visible to and recompute. A class with 2 private + 2 protected + 2 public reads as under private-only but under private+protected — the convention flips the verdict, so the definition must be stated.

15.9.3 AHF — Attribute Hiding Factor and Visibility versus Invisibility

Intuition — the same wall, but for data. How much of the state you declared is inside the wall?

Attribute Hiding Factor (AHF) does the same for state. The verbal description given was: count hidden attributes — attributes not visible outside the class, typically private — and divide by all attributes. Both and were presented as measures of information hiding, whose goal is to show only the interface ("what") and to keep implementation operations ("how") private.

Formalize — hidden attributes over all declared attributes. Let be hidden attributes in and let be attributes declared in . Then

so with the same reading — higher means more state is encapsulated, lower means more state is exposed as public fields that any client can read or mutate directly.

Every symbol named: is hidden-attribute count; is declared-attribute count; is total classes; is the state-encapsulation share.

The session linked hiding to the notion of visibility. An attribute is visible if another class can access it directly — normally when it is public, or protected from outside the hierarchy but visible within the hierarchy — and hidden otherwise (private, or protected grouped with private under the private+protected convention). For the method-visibility details the session introduced invisibility counts and as sums of visibility predicates, phrased as one minus visibility for the private side. The stated intuition was

where visibility for a method (and analogously for an attribute) was described as the share of other classes that can call/access it, summed across all methods/attributes and classes. In words, if a method can be called from many other classes it is highly visible; if only its own class can call it, it is not visible elsewhere and contributes to hiding. Formally this appears in the MOOD literature as as visibility of method and , with hiding factor built from terms — the 1-minus-visibility intuition the lecture named. The lecture treated a concrete count of three variables with two encapsulated versus one not encapsulated — heard as "two is protected so product protected also is considered as encapsulated" — to illustrate that protected members are sometimes grouped with private members as encapsulated, depending on the author convention for hiddenness. Both conventions are defensible but, as with MHF, must be named before numbers are compared.

Worked example — AHF encapsulation and the three-attribute illustration.

Take the lecture's illustration: a class with three attributes — two encapsulated (say one private salary and one protected deptId counted as encapsulated under the private+protected convention) and one not encapsulated (public name).

Under private+protected as hidden: .

Under private-only as hidden: if only salary is private, then . The convention doubles the reported encapsulation — the pedagogical bite of the example is exactly that the choice of what counts as hidden changes the metric, so the choice must be documented.

Now add a second class with 4 attributes all private: . System totals:

Private+protected convention: — strong state encapsulation system-wide. Private-only: — still strong but noticeably lower.

Sense-check: a domain model where every entity exposes public fields will give near 0 — a bright red encapsulation flag. A model where every attribute is private with accessor discipline will give near 1 — the intended wall. Movement from 0.7 to 0.5 across two sprints is a drift worth investigating even when each individual class looks plausible.

Coverage of the invisibility intuition: a method helper() visible in only its own class ( share) contributes almost 1 to invisibility (), hence almost fully to hiding; a public api() visible in all other classes () contributes almost 0 to hiding. Summing across members yields the hidden mass that ratio normalizes.

Q: Does changing accessibility of a method (private versus public) change the polymorphism outcome measured by POF?

A: No. Access modifiers and polymorphic overriding answer different questions and are measured by different families.

  • An overridden method counts for POF if and only if it redefines an ancestor signature via an inheritance relation — the override predicate is redefines(C_child, M_parent) and is indifferent to private/protected/public.
  • Whether the method is visible outside its class is counted by MHF/AHF (hiding factors), not by POF. An overridden method can still be private in a language sense or exposed through an interface, and a private method can override in languages that allow it, but the inheritance relation is what POF examines, not the modifier.

Mixing the two would blur encapsulation (who can see it) with polymorphic extension (whether it refines ancestor behavior). The two families are kept separate precisely so that a team can have high encapsulation (high MHF/AHF) and meaningful polymorphism (moderate POF) at the same time — the desired shape for a framework core. The trigger here was the inference "hiding is low, so polymorphism must also be affected" — the correction is that they are orthogonal MOOD axes.

Terminology contrast preserved: hiding = visibility control; polymorphism = signature redefinition through inheritance. Different predicate, different denominator, different design question.

Pitfalls — hiding and inheritance conventions interact.

  • Private inheritance masquerading as hiding. A protected field counted as "visible within hierarchy" under one convention and "hidden outside hierarchy" under another. Fix: publish whether protected counts as before reporting MHF/AHF.
  • Confusing declaration with inheritance. count only what is declared in the class; inherited members that are not redeclared do not enter but they do enter in MIF/AIF. So a high AHF does not imply high AIF — they ask "how much of what you declared is hidden?" versus "how much of what you can access was inherited?"
  • Treating 1.0 as the goal. Fully hidden () means no public surface at all — the system cannot be called. High hiding is a direction conditioned on providing a sufficient public interface; the optimum is inside , not at the boundary.

Exam cue: when asked to justify a POF versus MHF distinction, name the two predicates separately — overrides(C_child, M_parent) for POF and isVisible(Caller, Member) summed into then for hiding — and state that they share no term.

Visual intuition: picture a city block. counts how many buildings replaced an ancestor's façade with their own (override) versus how many façades they could have replaced. count how many windows face the public street (visible) versus a private courtyard (hidden). A well-designed framework core looks like a block with consistent courtyards (high hiding) and a few deliberate façade replacements where variation is intended (moderate, purposeful POF) — not a glass block (low hiding) nor a block that rebuilt every façade (POF near 1).

Real-world: teams set informal gates for hiding factors during code review — for example requiring and in domain packages — and they justify the gate by tracking defect and change-cost data for modules that fell below the threshold in prior releases. A gate is not a law; it is a conversation starter backed by local history, exactly as 15.3 demanded.

Recap — extensibility versus exposure. in measures how much polymorphism is exercised among the spots it could be; and in measure how much of what you declared is hidden — the information-hiding wall. rise with stronger encapsulation; rises with more overriding, judged against intent. Protected-as-hidden is a convention that must be named, and the invisibility intuition is the bridge from per-member visibility to the system ratio. Overriding and visibility are orthogonal — one predicate per question.

Bridge: The twelve are now complete — six CK class-level counts and six MOOD system ratios. What remains is practice: where to apply them (class versus package versus system), which tools compute them without hand counting, and how to read the numbers as a disciplined indicator set rather than a set of verdicts. The final section makes that operational.

Connections: POF links back to DIT/NOC (opportunities grow with depth and descendants); MHF/AHF link to LCOM's accessibility caveat (cohesion does not change with private/public, hiding does).

15.10 Tools, Levels of Measurement and Interpreting Numbers in Practice

15.10.1 Where Measurement Applies — Class, Package, Component and System

Hook — zoom matters. A class that looks worrisome up close may be exactly what its package needs. A system that looks calm class-by-class may still be drifting toward entanglement package-by-package. Measure at the zoom where you decide.

Measurement points form a ladder. Each rung asks the same coupling, cohesion, and encapsulation questions but with a different aggregation and a different decision attached.

The measurement ladder.

  • Class level — the smallest decision unit. Evaluate individual classes and the connections between them: inheritance depth and breadth , per-class complexity , coupling , and cohesion , plus fan-in and fan-out read from CRC cards and interaction diagrams. Question answered: which class should we review or split first? Output is a distribution and a few outliers, not one number.
  • Package level — the collaboration cluster. Aggregate class values (mean WMC, max CBO, median LCOM, distribution of DIT) and ask whether a package is internally coherent (members share vocabulary and purpose) and loosely coupled to others (few cross-package uses). Question answered: is this package a deployable, testable module or an accidental bag of unrelated classes? Package coupling and cohesion often use the same underlying counts but counted on package edges rather than class edges.
  • Component and whole-system level — the architecture scale. The same ideas scale up: total class counts, system-wide ratios such as from 15.8–15.9, and overall structural complexity as the shape of the dependency graph. Additional measures named in the lecture exist beyond the twelve, including counts of messages sent and other complexity-related metrics at component scale. Question answered: is the system as a whole becoming more coupled, more exposed, or more polymorphically tangled than it was last release?

The key habit introduced is to choose the level that matches the decision at hand and to read a value against siblings at the same level, not against a universal constant. A WMC outlier is judged against other classes in the same service; a COF drift is judged against COF history for the same system; a package cohesion flag is judged against sibling packages, not against a textbook-wide ideal.

Visual intuition: picture a map with four transparent overlays — class dots, package clusters, component neighborhoods, and the system city. Turning on only the class overlay hides a package that is itself tightly coupled to its neighbor; turning on only the system overlay hides a single class that funnels half the traffic. Effective reviewers flick between overlays, because each suppresses a different kind of risk.

Scope — level confounds. Aggregating blindly confounds scale. A package mean CBO can hide one extreme class whose outlier drags the whole package into rework; a system COF can stay flat while class count doubles, masking that absolute edge count is rising fast. Fix: publish distribution (median, max, 90th percentile) alongside mean, and track absolute edge counts alongside ratios when is changing quickly.

15.10.2 Tools That Compute the Suites

Several freely available tools were named for hands-on use so that teams spend effort on interpreting numbers rather than hand-counting them on large diagrams. The recommendation was explicit: pick a design or a code sample, run a tool, and learn how the numbers move when the structure changes. Doing a few manual examples on small hierarchies first builds the intuition that makes automatic reports legible — a theme that will reappear in the exam note.

Tools named in the lecture.

  • CCCC (C and C++ Code Counter) — computes the CK suite and related distributions from design or code; one of the most cited free analyzers for OO metrics, searchable as "cccc metrics."
  • JMetric — another freely available calculator that reports maxima and distributions for the CK/MOOD families on Java codebases, useful for spotting outliers quickly.
  • Gen++ — named for C++ codebases; parses class relationships and emits CK-style counts for that language ecosystem.
  • McCabe (heard as "McAvee" in the recording) and Halstead (heard as "Hall Street") — referred to as part of the broader pool of complexity measures that surround the twelve: McCabe's cyclomatic complexity for control-flow path count and Halstead's software-science family for operator/operand volume and difficulty.

The point of the list was practical, not promotional: rather than counting by hand on a large diagram — where inheritance chains, indirect calls, and hidden-versus-visible distinctions are easy to miscount — a team can generate the numbers automatically and focus effort on interpreting what they mean and deciding what to refactor. Tool output typically shows a table per class (WMC, DIT, NOC, CBO, RFC, LCOM, fan-in/out) and a summary sheet for the system ratios, so the class-level outliers and the system-level drifts can be read together in one run.

Visual intuition: picture a CI pipeline stage that, on every push, emits a small metric strip chart per module — WMC median and max, CBO max, LCOM distribution, COF and AHF system points — plotted against a shaded reference band from prior releases. The point is not to fail a build on a threshold but to make a drift visible early, when a diagram edit still costs almost nothing.

Pitfall — tool-convention mismatch. Each tool bakes a convention: whether root is 0 or 1, whether private+protected counts as hidden, whether RFC is transitive, which LCOM variant is computed. Comparing a CCCC number with a JMetric threshold without naming the convention is meaningless. Fix: record the tool and its convention alongside every reported value.

Pitfall — hand-counting at scale. Counting a 200-class diagram by eye produces errors that swamp the signal. Fix: do hand counts only on 3–5 class teaching examples for intuition; trust the tool for the system and spend your review time on the outliers it flags.

15.10.3 How to Read Values and What to Do Next

Numbers alone do not make a judgment. Interpretation needs reference data and a link to observed reality — the validation lesson from 15.3 applied operationally.

How to read a value without over-reading it.

  • Build a local reference range. Teams build that reference by running metrics across past projects and by correlating metric levels with outcomes such as fault density, review effort, and effort to implement a change. The range is not a textbook constant; it is a distribution — "in our last four payroll releases WMC per class clustered 8–18 with 90th percentile 26" — that captures domain and team habits better than any universal cap.
  • Look for ranges, not points. Within that reference, treat an informal trigger as a band, not a pass/fail line. Just as a single lab value is read against a range built from many healthy individuals, a metric value is read against a range built from many comparable designs. If a class sits far outside the typical range for WMC or CBO, or if a system sits unusually high on COF or unusually low on MHF/AHF, the team looks closer rather than declaring failure — a high COF may be correct for a tightly orchestrated coordination service.
  • Validate the indicator. A prediction earns trust only when predicted risk matched observed faults. Track whether the classes flagged by high WMC/CBO/RFC/LCOM were indeed the ones that later required rework; adjust the set of metrics watched based on that feedback. A metric that never predicts anything locally is a count, not an indicator, and should be dropped from the review checklist.
  • Close the loop. Late in the session the group was advised to read about the patterns already taught (GRASP, GoF), to practice applying them in a small self-chosen design, and to use UML to make the intended structure explicit — class diagrams, interaction diagrams, and package diagrams all count. That explicit representation is what makes measurement possible in the first place, and the feedback loop — design → represent → measure → reflect → redesign — was framed as the starting habit for object-oriented analysis and design that teams carry into later professional work.

Real-world: In hiring interviews and in internal design reviews, candidates are often asked to walk through a diagram, name the metrics that would be high or low for each class (high WMC here, low LCOM there, rising COF system-wide), and sketch a refactoring that would move the numbers in the desired direction while keeping behavior unchanged — for example splitting a God class into a coordinator plus two collaborators and showing before-and-after WMC and LCOM plus reduced RFC for each new class. Showing the before-and-after values is more convincing than describing a redesign in words alone, because it makes the trade explicit: cohesion rose while coupling stayed bounded, so the change paid its own way.

Pitfalls — reading alone.

  • Point worship. "This class failed WMC > 20, therefore reject." Fix: ask whether peers in the same domain actually fail more at that level — maybe 20 is permissive noise.
  • Ratio without context. "COF rose from 0.08 to 0.12 — crisis." Fix: check whether fell (making the same edge count denser) and whether the new edges are purposeful. Trend plus absolute count plus relation type beats a single snapshot.
  • Metric sprinting. Adding metrics to a dashboard without adding the review conversation. Fix: every metric on the dashboard must have a named owner and a named response — "who looks at this when it moves, and what decision does it inform?"

Recap — measure at the right altitude, compute with a tool, and read against a lived reference. Class-level numbers flag hotspots; package and system ratios flag drift; tools (CCCC, JMetric, Gen++ plus McCabe and Halstead families) make both visible without hand-counting at scale. But a number becomes a decision only against a local reference band and a validated link to rework and fault history — the same empirical habit introduced in 15.3. The prescribed study habit closes the loop: choose a design, draw it in UML, run a tool, compare to history, and redesign on paper while it is still cheap.

Bridge: The twelve individual measures now sit inside the larger argument of the lecture — from why measurement matters, through why single-metric and single-line counts fail, through what makes any metric trustworthy, through the product/process and quality-model map, through the twelve themselves, to the practice that keeps those numbers honest. That arc is summarized in the two appendices that follow.

Exam note: Be ready to compute or interpret a small hierarchy by hand: identify DIT and NOC from a tree, apply the LCOM pair-count idea versus with the zero floor to a table of method-variable usage, and explain why a measure such as COF uses ordered pairs in the denominator — because uses edges are directed and a class does not couple to itself while every other ordered pair could be a client-server link.

Connections: 15.10 reuses every idea that preceded it — 15.1's early inspection, 15.2's rejection of LOC-alone, 15.3's reference ranges and indicators, 15.4's quality triangle as the why-behind-the-what, and the CK/MOOD suites themselves as the counted evidence to bring to a review.

Exam Guidance Summary

The final assessment carries a weight of 40 percent under the EC3 scheme with the full syllabus examinable and no separate restricted topic list given. Concentration was described as heaviest on the later part of the course, with design patterns and object-oriented design as the main focus areas — which includes GRASP responsibility assignment, GoF pattern application, and the CK/MOOD measurement suites taught in this lecture.

Diagram-related work remains central — questions often involve reading or creating diagrams (class diagrams, interaction diagrams, and package diagrams) and judging whether a given arrangement is sound. For this lecture, measurement topics belong to the design-evaluation portion and can appear in three forms:

  • Conceptual distinctions — distinguish CK (class-and-neighbor direct counts: WMC, DIT, NOC, CBO, RFC, LCOM) from MOOD (system and package ratios in : MIF, AIF, COF, POF, MHF, AHF), and map each to inheritance, coupling, cohesion, encapsulation, polymorphism, and complexity. Answering "which family and what level?" correctly is the first examiner check.
  • Short computations at class or system level — on a small hierarchy, identify DIT (longest path to root, edges counted, max over parents) and NOC (immediate children only, not descendants), apply the LCOM pair-count idea (empty intersection) versus (non-empty) with the zero floor, and compute or interpret a system ratio such as COF (ordered pairs in denominator) or MIF/AIF (inherited over available). Show the counted pairs or the numerator and denominator explicitly; the method matters as much as the final number.
  • Interpretation and trade-off questions — explain whether a higher or lower value is preferred and what breaks if the preference is pushed too far: lower coupling, higher cohesion, lower complexity, and higher encapsulation are the compass directions, but zero coupling is impossible (collaboration is necessary) and raising class count to improve cohesion can raise coupling elsewhere — every measure invites a trade-off analysis rather than a blind target.

Study guidance given in the lecture was concrete: work through the patterns taught, practice applying them on a self-chosen design, use UML notation to make the structure explicit (so that measurement has an explicit artifact to count on), try manual examples for metrics such as WMC, DIT, NOC, and LCOM on 3–5 class hierarchies (the C2 NOC count, the DIT depth-2 sketch, and the BankAccount LCOM example in these notes are all examiner-style warm-ups), and run a freely available tool such as CCCC or JMetric on a sample design to see how the numbers respond to restructuring — for example splitting a God class and observing before-and-after WMC, CBO/RFC, and LCOM. The habit of designing, representing, measuring, and reflecting on paper was framed as the skill the exam is probing as much as any single formula.

Key Industry Applications

  • Design reviews flag coupling and cohesion first. A class with high fan-out (high CBO) or a broad response set (high RFC) is flagged for dependency reduction, often by introducing an interface that hides collaborators behind a single contract or by moving a responsibility to the collaborator that owns the data (GRASP information expert). Reviewers check that a proposed reduction actually moves CBO and RFC together without inflating WMC elsewhere.
  • CRC cards continue to support early coupling estimates. Collaborations listed while assigning responsibilities translate directly into a provisional CBO before any code exists — each distinct collaborator line is one "uses" edge. Teams that fill CRC cards faithfully can estimate coupling and response reach at the table, before a UML tool ever parses the diagram.
  • Cost and schedule estimation combines function points with complexity proxies. Organizations that need to quote maintenance and enhancement effort combine function points (externally visible functionality weighted by complexity) with structural proxies such as method counts (WMC), dependency structure (CBO/COF), and hiding factors (MHF/AHF). Tracking those values across releases builds a local baseline — "our last four releases averaged 12 function points per person-month at CBO ≈ 4 and AHF ≈ 0.75" — that makes later forecasts more defensible than any lines-of-code estimate.
  • Reuse strategy builds on inheritance metrics. Framework teams watch MIF and AIF to avoid over-inheritance where leaf classes add almost nothing and inheritance merely carries dead weight. Product teams watch NOC to ensure a parent abstraction such as an Employee hierarchy that branches into teaching staff, non-teaching staff, and contract staff remains focused rather than diluted by unrelated children — the "true for every child without exception" test from 15.6. Depth (DIT) is favored over breadth when the choice is forced: a focused deep chain is usually healthier than a wide fan under a catch-all parent.
  • Encapsulation gates use MHF and AHF. Many codebases require a high share of private attributes and private helper methods in domain packages — for example or under a stated private-plus-protected or private-only convention — and treat a dip below the gate as an automatic review signal. The gate is justified by tracking defect and change-cost data for modules that fell below it in prior releases, not by appeal to a textbook constant.
  • Tooling is routine in continuous integration. CCCC, JMetric, and Gen++ for C++ (plus the broader McCabe and Halstead families for control-flow complexity) are run in CI to publish CK and related trends per commit or per night. Reports that plot WMC, CBO, RFC, and LCOM distributions across sprints — alongside system ratios COF, POF, MHF, AHF — give early warning before a module becomes hard to change. The pipeline is typically configured to shade a reference band from local history rather than failing a build on a single threshold, so the team sees drift early.
  • Refactoring decisions are argued with before-and-after numbers. Splitting a God class into smaller collaborators is justified by showing a before-and-after spread: WMC per new class drops, LCOM falls (methods now share state within focused classes), CBO per new class stays bounded, and RFC for each new class narrows to a smaller response set — rather than by appeal to taste alone. That evidence-based argument is persuasive to both engineers and managers because it links the structural metric to the support measures (mean time to change, cost to fix) that the client pays after delivery.
  • Interview and promotion panels test metric reasoning. Candidates are often asked to walk through a class diagram, name which metrics would be high or low and why, identify a God class candidate from its WMC/CBO/LCOM combination, and sketch a refactoring that moves each metric in the preferred direction while preserving behavior. Showing that the refactoring keeps coupling from exploding while raising cohesion is usually judged as stronger evidence than naming the twelve from memory.
  • Early measurement shapes reuse investment. Organizations deciding where to harvest a reusable asset start with packages whose MIF/AIF show genuine reuse (inherited behavior and state carry weight) and whose CBO/COF show low external dependence — good encapsulation plus low coupling predicts that the asset can be extracted without dragging half the system with it.

OODAP Lecture 15 notes · Measuring Object-Oriented Design

Object Oriented Design, Analysis and Programming Architecture· postgraduate· 2026-08-19

Sections Breakdown

115.1 Why Measuring a Design Matters Before Implementation

Economic case for measuring class and interaction diagrams before code; what diagrams make countable and why performance KPIs are not design-time measures.

215.2 Traditional Size and Complexity Measures

Why LOC misleads as quality/pricing, cyclomatic complexity and design-time proxies, and function-point based effort models versus adding people.

315.3 Measurement Foundations — What Makes a Number Trustworthy

Quantification plus empirical validation, indicators with direct/indirect measurement via CGPA and blood-test analogies, and trade-off compass (coupling, cohesion, complexity, encapsulation).

415.4 Quality Models and Where Measurement Sits in the Lifecycle

McCall triangle and 13 elements organizing product quality, product versus process measurement, and maintainability/support measures.

515.5 The Twelve Object-Oriented Design Metrics — CK and MOOD at a Glance

CK six class-level direct counts versus MOOD six system ratios; how twelve map to inheritance, coupling, cohesion, encapsulation, polymorphism and size.

615.6 CK Suite — Weighted Methods per Class, Depth of Inheritance Tree, Number of Children

WMC as sum of method complexities, DIT as longest path to root (practical ceiling 3-5), NOC as immediate children and dilution of abstraction via Employee example.

715.7 CK Suite — Coupling Between Objects, Response for a Class, Lack of Cohesion in Methods

CBO as distinct non-inheritance collaborators, RFC as response-set cardinality, LCOM as P versus Q pair count with zero floor and variant caveats.

815.8 MOOD Suite — Method and Attribute Inheritance Factors and Coupling Factor

MIF/AIF as inherited over available ratios in [0,1] and COF as client-server edges over ordered pairs TC^2-TC, normalized sibling of CBO.

915.9 MOOD Suite — Polymorphism Factor and Hiding Factors for Encapsulation

POF as overrides over new-times-descendants opportunities; MHF/AHF as hidden over declared with private versus private+protected convention and 1-visibility invisibility.

1015.10 Tools, Levels of Measurement and Interpreting Numbers in Practice

Ladder class-package-component-system, tools CCCC/JMetric/Gen++ plus McCabe/Halstead, and reading values against local reference bands with before-after refactoring evidence.

11Exam Guidance Summary

EC3 40% full syllabus, heaviest on later design patterns and OOD; exam tests CK/MOOD distinctions, small computations and trade-off interpretation.

12Key Industry Applications

Industry uses: coupling/cohesion gates, CRC early CBO, FP+complexity costing, reuse via MIF/AIF/NOC, hiding gates, CI tooling, God-class split evidence.

Postgraduate students in Object Oriented Design, Analysis and Programming

Exam Revision Notes

Below is the distilled, exam-ready core. Every entry comes from the full explanation above. Use this section for rapid review; return to the main notes when a point needs more context.

Why Measuring a Design Matters Before Implementation

Must-know: A design fixes objects/attributes/methods/relationships on diagrams; measurement gives early structural indicators before code, while latency is run-time only.

Top pitfall: Treating latency/throughput as a design-time KPI — it is observable only after implementation and execution.

Self-check: Which two diagram types supply the raw facts for CK/MOOD and which property requires execution to observe?

Connects to: 15.2, 15.3

Traditional Size and Complexity Measures

Must-know: LOC alone misleads; cyclomatic V(G)=E-N+2P counts paths, proxied at design by method counts and fan-in/out; function points measure external functionality for effort.

Top pitfall: Pricing by LOC or worshipping brevity — both reward duplication or destroy readability.

Self-check: Why can more classes and more LOC be the correct complexity reduction?

Connects to: 15.3, 15.6

Measurement Foundations — What Makes a Number Trustworthy

Must-know: Trustworthy measurement needs quantification plus validation against history; most properties need indirect indicators; lower coupling/higher cohesion/lower complexity/higher hiding are directions with trade-offs.

Top pitfall: Reading a single metric as a verdict or treating 10 vs 100 classes as inherently good/bad without cohesion context.

Self-check: Why is WMC=45 alarming in a microservice but normal in a legacy GUI framework?

Connects to: 15.4, 15.5

Quality Models and Where Measurement Sits in the Lifecycle

Must-know: McCall triangle groups 13 properties under operation/revision/transition; product measurement reads artifacts, process reads how the team inspects and reworks them.

Top pitfall: Measuring only product or only process and conflating the two.

Self-check: Name one product measure and one process measure for a highly coupled design and why both are needed.

Connects to: 15.5, 15.10

The Twelve Object-Oriented Design Metrics — CK and MOOD at a Glance

Must-know: CK (1994) profiles class+neighbors with direct counts; MOOD profiles whole system with [0,1] ratios; together they cover five properties at two scales.

Top pitfall: Comparing a class count directly with a system ratio or treating a ratio near 1 as inherently bad.

Self-check: Which suite tells you which single class to review first, and which tells you the system is drifting?

Connects to: 15.6, 15.7, 15.8, 15.9

CK Suite — Weighted Methods per Class, Depth of Inheritance Tree, Number of Children

Must-know: WMC=sum ci; DIT=max path to root; NOC=immediate children; deeper inherits more but costs understanding; wide fans dilute parent abstraction.

Top pitfall: Counting descendants as NOC children or deepening hierarchy to share one helper.

Self-check: In the lecture tree subclasses 4 and 5 have DIT=2 — what is DIT of their parent and why is C2 NOC 3 not 4?

Connects to: 15.7, 15.8

CK Suite — Coupling Between Objects, Response for a Class, Lack of Cohesion in Methods

Must-know: CBO=distinct uses outside inheritance; RFC=|M U U Ri|; LCOM=max(0,|P|-|Q|) where P empty intersections, Q shared-variable pairs.

Top pitfall: Thinking private/public changes LCOM or chasing RFC to zero by inlining.

Self-check: BankAccount with three methods gave |P|=2,|Q|=1 — what is LCOM and what split does it suggest?

Connects to: 15.6, 15.8, 15.9

MOOD Suite — Method and Attribute Inheritance Factors and Coupling Factor

Must-know: MIF=sum Mi/sum Ma, AIF=sum Ai/sum Aa, COF=sum sum is_client/(TC^2-TC) ordered non-inheritance coupling in [0,1]; lower COF preferred.

Top pitfall: Forgetting COF denominator counts ordered pairs and that inheritance is excluded from numerator but included as possible relation.

Self-check: With TC=4 and three client edges COF=3/12=0.25 — what does the same three edges give at TC=50?

Connects to: 15.7, 15.9

MOOD Suite — Polymorphism Factor and Hiding Factors for Encapsulation

Must-know: POF=sum Mo/sum Mn*DC in [0,1]; MHF=sum Mh/sum Md, AHF=sum Ah/sum Ad; high hiding is direction not 1.0 goal; overriding and visibility are orthogonal.

Top pitfall: Thinking accessibility changes POF or treating MHF=1 as ideal with no public surface.

Self-check: Why does changing private to public change MHF/AHF but not POF?

Connects to: 15.7, 15.10

Tools, Levels of Measurement and Interpreting Numbers in Practice

Must-know: Measure at the decision zoom; use tools for scale; interpret against local bands and validated fault history via design->represent->measure->reflect loop.

Top pitfall: Point worship on one threshold or comparing numbers across tools without naming convention.

Self-check: Why must a COF trend be read with TC and absolute edge count alongside the ratio?

Connects to: 15.3, 15.6

Exam Guidance Summary

Must-know: Exam is full syllabus, weight on patterns and OOD; expect diagram reading, CK vs MOOD levels, and hand computations.

Top pitfall: Memorizing thresholds without knowing convention or level.

Self-check: What three question forms can measurement appear in on the exam?

Connects to: 15.5

Key Industry Applications

Must-know: Industry argues refactoring with before-after WMC/CBO/RFC/LCOM and tracks MIF/AIF/NOC and MHF/AHF gates via CCCC/JMetric in CI.

Top pitfall: Arguing redesign by taste instead of numbers plus historical correlation.

Self-check: Which three CK metrics together most strongly signal a God-class extraction candidate?

Connects to: 15.6, 15.7

Was this lecture useful?

Loading comments…
🤖

BitsNotes AI Assistant

Subject Notes Assistant

Configure AI Chat

Choose how to access the chatbot
Have your own API key?

Switch to "Bring Your Own Key" tab above for unlimited access with any OpenAI-compatible provider.

🔑 Enter API key above to fetch live models from provider, or enter model name manually.
OpenAI-Compatible API Support

Choose any provider preset (Gemini, DeepSeek, Kimi, GLM, MiniMax, Qwen, OpenAI, Groq, Ollama, etc.) or enter a custom endpoint URL.

Security & Privacy First

Your API key is sent directly from your browser to your specified provider. BitsNotes servers never store or see your key.