Bob Martin Blames Tangled Dependencies for Cascading Software Rot

Bob Martin   SOLID Principles of Object Oriented and Agile Design — People career

People career

Software projects fail because compile-time dependencies mirror runtime control flow, requiring engineers to use polymorphism to invert dependencies and insulate core business policies from volatile implementation details.

AI-assisted summary of Bob Martin SOLID Principles of Object Oriented and Agile Design — original source. Check the original for full context.

Source published:

Key ideas

  • The global software developer population doubles roughly every five years, trapping the software engineering industry in an exponential curve where approximately half of all practitioners possess under five years of professional experience. (12:41)
  • Object-oriented programming did not invent encapsulation or real-world modeling; its core architectural innovation is safe dynamic polymorphism, which empowers engineers to invert source code dependencies against runtime execution flow. (49:15)
  • Software architecture primarily exists to manage coupling and prevent the three classic symptoms of architectural decay: rigidity (cascading edits), fragility (distant unrelated breaks), and immobility (inability to extract reusable logic). (26:53)
  • Software representations do not inherit the real-world relationships of the objects they model, making standard real-world taxonomy like 'a square is a rectangle' dangerous when applied naively to class hierarchies. (78:22)

Detailed notes

## Background This presentation was uploaded to YouTube by the channel People career on May 24, 2015, featuring a talk delivered by Robert C. Martin ('Uncle Bob') at a New Haven IO event. The video's show notes provide no description of the talk, which covers object-oriented design and agile principles.

## TL;DR Uncle Bob Martin examines the root causes of software project failure, pointing to exponential industry growth, perpetual developer inexperience, and the perils of rushing to meet deadlines. He breaks down the three primary symptoms of code rot—rigidity, fragility, and immobility—and demonstrates how improper coupling and procedural source dependencies drive these dysfunctions. Tracing the history of object-oriented programming, Martin debunks common myths around encapsulation and inheritance before articulating how polymorphism enables true dependency inversion. He then details the mechanics of the SOLID principles, focusing on the Single Responsibility Principle, Open-Closed Principle, and Liskov Substitution Principle through concrete architectural and geometric examples.

## Atmosphere & style The lecture combines the theatrical, confident energy of a seasoned technical orator with sharp pragmatic cynicism about modern software development. Martin engages the audience by drawing unexpected, visceral parallels—opening with physical chemistry and moving to heart surgery and car mechanics—before diving into low-level code mechanics. His delivery is animated and irreverent, punctuated with dry humor, biting critique of corporate marketing myths, and uncompromising reminders of professional engineering responsibility.

## Bulletin - [12:41](https://www.youtube.com/watch?v=TMuno5RZNeE&t=761s) The global software developer population doubles roughly every five years, trapping the software engineering industry in an exponential curve where approximately half of all practitioners possess under five years of professional experience. - [49:15](https://www.youtube.com/watch?v=TMuno5RZNeE&t=2955s) Object-oriented programming did not invent encapsulation or real-world modeling; its core architectural innovation is safe dynamic polymorphism, which empowers engineers to invert source code dependencies against runtime execution flow. - [26:53](https://www.youtube.com/watch?v=TMuno5RZNeE&t=1613s) Software architecture primarily exists to manage coupling and prevent the three classic symptoms of architectural decay: rigidity (cascading edits), fragility (distant unrelated breaks), and immobility (inability to extract reusable logic). - [01:18:22](https://www.youtube.com/watch?v=TMuno5RZNeE&t=4702s) Software representations do not inherit the real-world relationships of the objects they model, making standard real-world taxonomy like 'a square is a rectangle' dangerous when applied naively to class hierarchies.

## Key takeaways - [12:41](https://www.youtube.com/watch?v=TMuno5RZNeE&t=761s) The exponential doubling of developers every five years creates a structural condition of perpetual industry-wide inexperience. - [15:52](https://www.youtube.com/watch?v=TMuno5RZNeE&t=952s) Rushing code to beat deadlines is counterproductive; professional development speed requires meticulous care, disciplined cleanup, and deliberate pacing comparable to surgical procedures. - [19:06](https://www.youtube.com/watch?v=TMuno5RZNeE&t=1146s) Rigidity occurs when tightly coupled dependencies force a cascade of modifications across unrelated modules following a single requirement change. - [22:46](https://www.youtube.com/watch?v=TMuno5RZNeE&t=1366s) Fragility destroys customer trust by causing software to break unpredictably in modules far removed from the isolated area that was modified. - [24:45](https://www.youtube.com/watch?v=TMuno5RZNeE&t=1485s) Immobility arises when desirable business logic cannot be separated from unwanted framework and infrastructure baggage, forcing costly full rewrites. - [28:59](https://www.youtube.com/watch?v=TMuno5RZNeE&t=1739s) Procedural programming naturally couples compile-time source dependencies directly to runtime execution flow, polluting high-level business policies with low-level implementation details. - [38:52](https://www.youtube.com/watch?v=TMuno5RZNeE&t=2332s) C offered stronger encapsulation via forward-declared structs in header files than C++, which exposed private member variables in header files and required access specifiers as a compensatory fix. - [49:15](https://www.youtube.com/watch?v=TMuno5RZNeE&t=2955s) Polymorphism enables Dependency Inversion, allowing software architects to position interfaces so that source code dependencies point directly opposite to runtime control flow. - [52:27](https://www.youtube.com/watch?v=TMuno5RZNeE&t=3147s) The Single Responsibility Principle (SRP) mandates that a module should have only one reason to change, meaning it should answer to only one business stakeholder or organizational role. - [52:27](https://www.youtube.com/watch?v=TMuno5RZNeE&t=3147s) Mixing methods that serve different C-level stakeholders within a single class creates severe risks of cross-domain regression bugs. - [01:00:02](https://www.youtube.com/watch?v=TMuno5RZNeE&t=3602s) The Open-Closed Principle (OCP) states that modules should be open for extension without requiring modification or recompilation of existing caller code. - [01:02:42](https://www.youtube.com/watch?v=TMuno5RZNeE&t=3762s) Procedural designs relying on switch statements and type enums systematically violate OCP, introducing rigidity, fragility, and immobility whenever new types are added. - [01:08:33](https://www.youtube.com/watch?v=TMuno5RZNeE&t=4113s) Anticipatory modeling of real-world domains often fails because customer requirement changes routinely cut across the specific axes of change that architects predicted. - [01:13:50](https://www.youtube.com/watch?v=TMuno5RZNeE&t=4430s) The Liskov Substitution Principle (LSP) requires derived classes to be completely substitutable for their base abstractions without callers needing to know the difference. - [01:15:05](https://www.youtube.com/watch?v=TMuno5RZNeE&t=4505s) Subclassing Square from Rectangle violates LSP because modifying the height of a Square inherently alters its width, breaking fundamental caller assumptions about Rectangle behavior. - [01:17:16](https://www.youtube.com/watch?v=TMuno5RZNeE&t=4636s) Relying on type introspection operators like 'instanceof' or 'is' to inspect derived classes violates OCP and signals a breakdown in the base abstraction. - [01:18:22](https://www.youtube.com/watch?v=TMuno5RZNeE&t=4702s) The 'IS-A' relationship was imported from 1980s AI semantic networks into OO marketing, obscuring the reality that inheritance in programming is merely a tool for redeclaring variables and functions in sub-scopes.

## Detailed walkthrough ### [00:06](https://www.youtube.com/watch?v=TMuno5RZNeE&t=6s) Introduction by Kyle Jensen: Software and Entrepreneurship at Yale

Kyle Jensen, a faculty member at the Yale School of Management and director of its entrepreneurial programs, opens the evening by welcoming the audience and introducing Robert C. Martin ("Uncle Bob"), who is slated to speak on the SOLID principles of object-oriented and agile design. Jensen highlights that Martin's visit comes at a pivotal moment for Yale, as the university has been pouring substantial resources into entrepreneurship, blending practical venture creation with Yale’s academic foundations in theoretical computer science. Evidence of this growing ecosystem abounds across campus: two years earlier, the Yale Entrepreneurial Institute (YEI) launched its Tech Bootcamp; HackYale emerged; and just a year prior, YHack convened roughly 1,000 student developers from across the country for a 48-hour hackathon. Curricular changes also reflect this shift: Professor Daniel Abadi reoriented the core introductory programming course for non-majors toward mobile application development, while Jensen and Abadi are slated to co-teach Computer Science 113 (Entrepreneurship and Programming) in the upcoming spring semester alongside a software management course at the Yale School of Management.

Jensen emphasizes that software development at Yale no longer lives in an isolated silo, spanning the School of Management, Computer Science, the School of Engineering, and various student clubs. Crucially, the momentum extends well beyond the university's walls into the broader New Haven community. He thanks the evening's food sponsor, Continuity Control—a local startup and Ruby shop packed with passionate developers—and highlights their active role in New Haven IO, a community tech group Jensen helped organize alongside local developers Dan and Joel. Observing the crowd, Jensen expresses satisfaction that many attendees are not affiliated with Yale, pointing out that technology acts as a bridge across historical divides. As Kyle Jensen explains, “I'm very happy to say that uh the plurality of people here tonight are actually probably not not from Yale which I think is really important because traditionally Yale has this kind of town and gown problem right where there's not much uh there's not much interaction and I think that's a real problem.” With that, Jensen calls up Dan Bader, a software engineer at Continuity Control and key organizer of New Haven IO and the local Ruby meetup, to introduce the speaker.

- Yale initiatives mentioned: Tech Bootcamp (YEI), HackYale, YHack hackathon (1,000 developers), CS 113 (Entrepreneurship and Programming), and software management at SOM. - Community sponsors and groups: Continuity Control (Ruby-based regulatory tech startup) and New Haven IO.

---

[03:11](https://www.youtube.com/watch?v=TMuno5RZNeE&t=191s) Dan Bader's Introduction of "Uncle Bob" Martin

Dan Bader takes the stage and quickly deals with a stubborn microphone before delivering a brief recruiting pitch for Continuity Control. Noting that team members are wearing continuity lapel pins, he invites attendees to chat with them afterward, mentioning their emphasis on clean code and upcoming projects involving machine learning. Transitioning to software engineering as a craft, Bader contrasts the casual experience of writing code for fun with the harsh realities of professional development, where software failure carries real consequences.

Bader describes how developers often navigate complex, high-stakes environments without clear direction, framing Robert Martin as a much-needed steadying force for the entire discipline. As Dan Bader puts it, “Software is making software is a it's a weird business it's a messy business um those of you who are students may not have found this out yet because when it's just for fun it's a lot of fun but when it matters when when you have people paying you for the software that you're writing or people using your software or God forbid people whose lives depend on your software it's a weird business and sometimes you can feel kind of like you know we're a bunch of kids running around with our shoes untied and you're just hoping you don't fall down but luckily we have an uncle looking out for us giving us a kick in the butt every once in a while when we need it.”

Bader enumerates Martin’s major contributions to software craftsmanship over the decades: - Co-authoring and signing the Agile Manifesto, fundamentally reshaping how development teams collaborate with business stakeholders, plan releases, and handle project uncertainty. - Teaching developers how to structure code at every scale, from naming variables and methods up to overarching system architectures. - Guiding the industry through the adoption of new programming languages and explaining why specific paradigms matter. - Tonight's specific focus: the architectural sweet spot right in the middle—how to design, organize, and decouple object relationships using the SOLID principles.

---

[05:56](https://www.youtube.com/watch?v=TMuno5RZNeE&t=356s) Uncle Bob Takes the Stage: The Physics and Chemistry of Water

Robert C. Martin takes the stage, sorts out his microphone audio with the technicians, and opens with an immediate show of hands: nearly the entire room identifies as software programmers, with only one attendee calling out that they work on the online product side. Rather than launching directly into code or design patterns, Uncle Bob shifts the discussion completely by posing a simple, seemingly innocent foundational question: "What is water?"

Audience members toss out answers: a naturally occurring substance, something soft (though Martin quips that water itself is soft, and it is only dissolved minerals that make it "hard"), and its standard chemical formula, H2O. When asked what the molecule looks like, an audience member answers that it resembles Mickey Mouse. Martin draws the molecule on the board: a large central oxygen atom with two smaller hydrogen "ears" offset at an angle he recalls as approximately 103 degrees. He then pushes the audience past textbook jargon to examine the actual underlying physics: with a single positively charged proton in each hydrogen atom and eight protons in the oxygen nucleus, surrounded by a cloud of negatively charged electrons (one per hydrogen and eight for oxygen), like charges ought to repel. Martin challenges the room to explain why these three atoms bind together rather than push each other apart.

```mermaid flowchart TD accTitle: Water Molecule Atomic Layout accDescr: Structural diagram of an H2O molecule showing Mickey Mouse geometry and proton electron counts.

subgraph Molecule["Water Molecule (H2O)"] O["Oxygen Nucleus<br/>(8 Protons, 8+ Charge)"] H1["Hydrogen Ear 1<br/>(1 Proton, 1+ Charge)"] H2["Hydrogen Ear 2<br/>(1 Proton, 1+ Charge)"] O ---|"~103°–104.5° angle"| H1 O ---|"~103°–104.5° angle"| H2 end Cloud["10 Electron Probability Cloud<br/>(Concentrated between nuclei)"] -.-> Molecule ```

---

[08:58](https://www.youtube.com/watch?v=TMuno5RZNeE&t=538s) Covalent Bonds and the Dipole Moment

Addressing audience members who chime in that the atoms "share" electrons, Martin digs into the physical mechanisms behind covalent bonds. At low thermal velocities, the electron clouds cause the atoms to repel each other. They must collide with sufficient kinetic energy to overcome that repulsion. Once they are brought close enough, the electrons naturally gravitate toward the concentrated positive charges of the nuclei, concentrating negative charge directly between the protons. That pool of intervening negative charge acts as the electrostatic glue that snaps the nuclei together. While the Pauli exclusion principle dictates the electron capacity of oxygen's outer shell—which has room for eight electrons but only contains six, allowing the two hydrogen electrons to complete the octet—Martin stresses that the actual force holding the atoms together is electrostatics. As Robert Martin summarizes, “Covalent bond happens when you manage to get the bulk of the negative charge to hold the positive charges together of course the electrons flit around here and it's a big probabilistic thing they're really all over the place but they will spend more time there and they hold the whole thing together.”

Martin then bisects the Mickey Mouse drawing with a horizontal line across the face to reveal the molecule's structural asymmetry. The bulk of the electron density resides above the line around the electronegative oxygen atom, leaving the hydrogen protons exposed below. Because the electrical charge is not evenly distributed, water forms a permanent electrical dipole—negative on the oxygen side and positive on the hydrogen side. Martin jokes that his wife scolds him whenever he pronounces "across" as "acrosst," before driving home the physical consequence: because of this dipole, a water molecule will eagerly rotate in space to electrostatically latch onto any charged surface it encounters, which is the exact physical reason why water is sticky, why it adheres to human skin, and why it makes surfaces wet.

[11:25](https://www.youtube.com/watch?v=TMuno5RZNeE&t=685s) Water, Static Balloons, and the Shift to SOLID

Uncle Bob Martin wraps up an entertaining science aside about why water molecules stick to things electrostatically, how water dissolves substances, and why it cleans dirt. He asks the audience if they have ever performed the parlor trick where you adjust a faucet to run a thin, unbroken stream of water, rub a balloon on your hair, and bring it close: the stream bends immediately toward the balloon. "Dazzle your children," he quips, before conceding that this is not what the audience came to hear.

Abandoning physical chemistry for something more mundane, Martin turns to the official topic: the SOLID principles of agile and object-oriented design. He introduces the overarching question for software developers: what actually goes wrong with software?

[12:41](https://www.youtube.com/watch?v=TMuno5RZNeE&t=761s) The Half-Life of Experience and the Exponential Curve

Surveying the room, Martin asks how many people have been programming for more than a year, more than five years, more than ten, and more than fifteen. With each step, roughly half the hands drop. Martin explains that this pattern reflects the broader global population of software engineers. Depending on whether Visual Basic for Applications (VBA) developers are counted, the world currently holds around 100 million programmers. Forty years earlier, around 1970, there were perhaps 50,000; in 1960, there were barely a couple of hundred, most of whom were hardware engineers drafted into writing code because nobody understood what software really was.

This explosive growth is an exponential curve with a doubling rate of roughly five years. The inevitable mathematical outcome is an industry locked into permanent youth: at any given moment, approximately half of all working developers have less than five years of experience. Martin counters the frequent assumption that older engineers leave the trade to pursue hobbies or open chicken farms: they are still programming, but there simply were not many of them to begin with. Until this exponential surge tapers off—which may happen in a few years or a few decades—the entire discipline must contend with the consequences of perpetual inexperience.

- The global programmer population is estimated at roughly 100 million (including VBA programmers). - In 1960, there were roughly 200 programmers worldwide; in 1970, roughly 50,000. - The population doubles roughly every 5 years, guaranteeing that 50% of programmers always have under 5 years of professional experience.

> “How do we deal with the fact in our industry that we are stuck in an exponential curve that guarantees perpetual inexperience?” — Bob Martin

[15:52](https://www.youtube.com/watch?v=TMuno5RZNeE&t=952s) The Fallacy of Rushing: Surgery and Software Quality

Martin asks how many veteran developers have been slowed down by bad code, and then asks how many junior developers have been slowed down by bad code they wrote just the day before. Everyone agrees bad code creates friction, yet teams persistently write it because they feel an urgent need to deliver quickly. Martin rejects this logic as a contradiction in terms: rushing through tasks and releasing messy code inevitably backfires. Speed comes from deliberate care, careful inspection, and ongoing cleanup.

To illustrate, Martin asks the audience to imagine an out-of-body experience where they float above an operating table, looking down at a surgeon performing open-heart surgery on their cracked-open chest. The surgeon operates under a literal, life-or-death deadline. Yet no patient wants an erratic, panicked surgeon rushing to beat the clock. Patients want a doctor who stays calm, follows disciplined procedures by the book, quietly requests instruments, and works with measured precision. Martin points out that developers under deadline pressure should hold themselves to that same professional standard rather than frantically throwing code together.

> “My message to you today is you don't go fast by writing crap.” — Bob Martin

> “A programmer who goes fast, programmer who is rushing like crazy is guaranteed to go slow.” — Bob Martin

[19:06](https://www.youtube.com/watch?v=TMuno5RZNeE&t=1146s) Rigidity: The Cascading Cost of Changes

Transitioning to concrete design flaws, Martin asks what bad code looks like in practice. The first obvious tell is confusion: reading bad code leaves a programmer baffled about what it does. By contrast, truly good code is straightforward, unsurprising, and almost boring because its intent is instantly transparent. Beyond confusion, the primary damage comes when code is modified.

The first major symptom Martin defines is **rigidity**: when altering one part of a codebase forces massive, cascading modifications across unrelated modules just to restore overall consistency. He paints a familiar workplace vignette: a manager asks a developer to fix a bug. The developer privately recognizes the bug—having introduced it only the day before—and gives the standard safe minimum estimate of three weeks, jokingly noting that anyone quoting less than three weeks risks a beating from cubicle neighbors. When the engineer finally opens the target function, they realize another module depends on it. Fixing that second module forces tweaks in a third and fourth, until the developer spends weeks chasing dependencies across the entire architecture. When the manager asks why a minor fix took so long, the developer offers the universal excuse: "Well, it was a lot more complicated than I thought." Rigidity is the direct byproduct of tight coupling and tangled dependencies.

```mermaid flowchart TD accTitle: The Cascade of Rigidity accDescr: A single bug fix triggering endless cascading modifications across coupled modules.

subgraph Origin ["Initial Bug Fix"] A["Target Bug Located<br/>(Original Estimate: 3 Weeks)"] end

subgraph Cascade ["Cascading Dependencies"] B["Caller Module A<br/>Requires Signature Update"] C["Consumer Module B<br/>Requires Logic Change"] D["Downstream Modules C & D<br/>Require Complete Refactoring"] end

subgraph Outcome ["Resulting State"] E["Weeks Slip By<br/>'It was more complicated than I thought'"] end

A --> B B --> C C --> D D --> E ```

> “That is rigid code: code that has dependencies that snake out in so many directions that you cannot make an isolated change without changing everything else around it.” — Bob Martin

[22:46](https://www.youtube.com/watch?v=TMuno5RZNeE&t=1366s) Fragility: Unpredictable Breakage and Broken Trust

Martin moves to the second symptom: **fragility**. While rigidity forces the programmer to track down and manually edit dozens of files, fragility occurs when an isolated change silently breaks far-flung, seemingly unrelated areas of the application. For instance, modifying the payroll routine for hourly workers might cause the union boss's reporting tool to abruptly crash. The developer has to hunt through the code only to discover a shared hidden flag that someone set in one place and read in another.

To convey how this looks to external stakeholders, Martin uses an automotive analogy: you take your car to a mechanic to repair a broken power window. The mechanic repairs it and proudly displays the functioning window, but when you turn the key, the engine fails to start. No customer would trust that mechanic again. When managers and clients watch developers fix a defect in one feature only to see unrelated features fall apart, it destroys their confidence in the development team faster than anything else.

> “Fragility is the tendency of the code to break in many places even when you only change it in one place.” — Bob Martin

> “Nothing strikes more fear into the heart of a customer or a manager than that one, because all of a sudden the managers and [customers lose trust].” — Bob Martin

[24:45](https://www.youtube.com/watch?v=TMuno5RZNeE&t=1485s) The Third Symptom of Bad Code: Immobility and "Joe's Code"

Customers without technical knowledge eventually sense when developers lose control of a project. They begin to suspect that looking under the hood would reveal low-quality work, and they grow terrified of permitting changes. Robert C. Martin asks the audience whether anyone has worked at a company where management ultimately declared a fragile module off-limits. “That is the ultimate failure of a software developer when the business who is not technically competent says you're not touching that module.”

He then introduces the third classic symptom of bad code: immobility. This occurs when your boss instructs you to reuse code written by a colleague named Joe in another team, since Joe wrote something similar the previous year. Knowing Joe and the messy code he writes, you resist, but the boss insists. Upon inspecting the module, you find that while it does satisfy your core requirement, it also pulls in unwanted dependencies—coupling itself to an unfamiliar framework and an obscure database:

- The useful logic cannot be isolated from the framework and database plumbing. - The effort and risk of importing all those unwanted baggage dependencies far outweigh the cost of starting from scratch. - The developer gives up and decides to rewrite the module independently. - Uncle Bob sums up the core dilemma bluntly: “The desirable parts of the code are so horribly coupled to the undesirable parts of the code that you cannot use the desirable parts of the code somewhere else.”

[26:53](https://www.youtube.com/watch?v=TMuno5RZNeE&t=1613s) Managing Dependencies: The Core Problem of Coupling

When Uncle Bob asks the audience for the common thread linking rigidity, fragility, and immobility, someone suggests "spaghetti." He clarifies that while spaghetti is a colloquial term for scrambled code, the precise technical diagnosis is unwanted coupling. Code is rigid because modules depend on one another in undesirable ways; it is fragile because code depends directly on internal data structures; and it lacks reusability because desirable features are chained to undesirable dependencies.

Robert C. Martin asserts: “It can be said and I think accurately that the bulk of software design is managing dependencies figuring out where to put code and cutting the dependencies so that the dependencies don't run in strange and bizarre directions.” He pauses to clean the whiteboard, joking about wanting to confront the people who design modern whiteboards since traditional chalk never behaved this poorly.

[28:59](https://www.youtube.com/watch?v=TMuno5RZNeE&t=1739s) The Universal Calling Tree and Source Code Dependencies

To illustrate how dependencies operate, Uncle Bob sketches a classic procedural program dating back to C circa 1971. In any traditional application, a `main` function calls several high-level policy modules, which in turn call middle-level modules, which eventually invoke low-level utility functions. This creates a standard procedure calling tree where runtime control flows downward from `main` to the deepest leaves.

```mermaid flowchart TD accTitle: Procedural Calling Tree and Coupled Dependencies accDescr: Diagram illustrating flow of control and source code dependencies pointing together down toward low-level details.

subgraph HighLevel["High-Level Policy"] Main["main()"] HL["High-Level Modules"] end

subgraph MidLevel["Mid-Level Policy"] ML["Middle-Level Modules"] end

subgraph LowLevel["Low-Level Details"] LL["Low-Level Modules"] end

Main -->|"calls / depends on"| HL HL -->|"calls / depends on"| ML ML -->|"calls / depends on"| LL ```

To explain the mechanics, he simplifies the structure down to two modules, `M` and `N`: - Module `M` invokes a function `f()` residing inside module `N`. - The flow of control runs from `M` to `N`. - For the compiler to understand this interaction, `M` must explicitly reference `N` in its source text. - In C, this is an `#include "N.h"`; in Java, it is `import N;`; in.NET, it is `using N;`.

Because module `M` mentions module `N` by name, there is a compile-time source code dependency pointing directly from `M` to `N`. In traditional procedural architectures, the red arrow representing source code dependency points in the exact same direction as the green arrow representing the flow of control.

[32:18](https://www.youtube.com/watch?v=TMuno5RZNeE&t=1938s) Why Coupling to Details Destroys Architecture

Following the downward arrows of the procedural calling tree exposes a severe architectural flaw: high-level business policy directly depends on low-level implementation details. When an engineer tries to read high-level domain logic, they find it polluted with low-level concepts such as string buffers and database adapters.

Furthermore, if a developer modifies a single variable or detail in a deeply nested module at the bottom of the tree, every module sitting above it in the dependency chain must be recompiled and redeployed. Robert C. Martin attacks this arrangement directly: “A change to a detail affects high level policy and that's wrong.” He argues that high-level business rules ought to be completely immune to changes in concrete details:

- **Rigidity:** High-level policies are coupled directly downward into volatile implementation details. - **Fragility:** Minor changes in a low-level leaf node propagate upward, breaking unrelated high-level behaviors. - **Immobility:** High-level business rules cannot be extracted and reused in other contexts because they drag along a chain of concrete low-level baggage.

[34:22](https://www.youtube.com/watch?v=TMuno5RZNeE&t=2062s) A History of Object-Oriented Languages and the Objective-C Accident

Uncle Bob queries the audience about what languages they use day-to-day, hearing callouts for Java, C#, Ruby, Python, and Objective-C. When someone mentions using Objective-C for iPhone applications, he detours into a historical account of how that language survived through a chain of accidents:

- Brad Cox invented Objective-C around 1980 by building a preprocessor that layered Smalltalk features onto C, creating his company, Stepstone. - In 1985, Bjarne Stroustrup published *The C++ Programming Language*; because C++ mirrored the familiar syntax of Kernighan and Ritchie's C, the industry abandoned Objective-C in favor of C++, pushing Stepstone toward obsolescence. - Steve Jobs recruited Pepsi-Cola COO John Sculley to Apple, only for corporate politics to result in Jobs being ousted. - Jobs founded NeXT with his personal wealth to manufacture high-end black hardware and hired available Objective-C developers to build the NeXTSTEP operating system. - Although NeXT struggled commercially, Apple eventually acquired NeXT and brought Jobs back; Jobs brought his NeXTSTEP software and team with him. - Those NeXTSTEP foundations evolved into the software platform for OS X, the iPod, and eventually the iPhone.

He jokes that Objective-C is “the worst possible language for an iPhone” and that its current dominance is merely the result of this unusual chain of historical events.

[37:47](https://www.youtube.com/watch?v=TMuno5RZNeE&t=2267s) The Three Magic Words of OO

Turning back to the central question, Uncle Bob asks why object-oriented languages managed to displace Fortran, PL/I, COBOL, and procedural C across the entire software industry. When an audience member suggests that OO helps organize code via encapsulation, Martin notes the standard textbook definition of object-oriented programming.

OO has traditionally been defined by three "magic words": 1. Encapsulation 2. Inheritance 3. Polymorphism

He challenges the conventional textbook claim that encapsulation is exclusive to modern OO languages by asking the experienced C veterans in the room whether encapsulation already existed back when they were programming purely in C.

[38:52](https://www.youtube.com/watch?v=TMuno5RZNeE&t=2332s) The Myth of OO Encapsulation and Inheritance

Bob Martin begins by debunking the idea that object-oriented programming invented or improved encapsulation. In C, developers enjoyed complete encapsulation simply by forward-declaring function signatures and data structure names inside header files (`.h`) while placing the actual struct definitions and function bodies exclusively in source files (`.c`). Clients `#include`d the header and could see none of the implementation details or member variables—making it impossible to directly access or tamper with internal state.

```mermaid flowchart TD accTitle: C Encapsulation vs C++ Encapsulation accDescr: Comparison showing how C encapsulated data structures completely in implementation files whereas C++ exposed member variables in headers.

subgraph C_Approach["C Perfect Encapsulation"] direction TB CH["Header (.h)<br/>struct Stack;<br/>void push(struct Stack*, int);"] CC["Source (.c)<br/>struct Stack { int items[100]; int top; };"] ClientC["Client Code<br/>Cannot see or touch struct internals"] CH --> ClientC CC -.-> CH end

subgraph CPP_Approach["C++ Compromised Encapsulation"] direction TB CPPH["Header (.h)<br/>class Stack {<br/>private: int items[100]; int top;<br/>public: void push(int);<br/>};"] ClientCPP["Client Code<br/>Compiler enforces access,<br/>but variables are fully exposed in text"] CPPH --> ClientCPP end ```

By contrast, Martin explains, C++ completely disrupted this clean separation by requiring private member variables to reside directly inside class definitions in header files. Because every client could now read the internal variables of a type, language designers had to invent access modifiers like `public`, `private`, and `protected` to patch the leak. Martin calls these keywords "terrible hacks" and band-aids over a system that was previously airtight. When evaluating inheritance in C, he notes that developers regularly emulated single inheritance using `union`s and matching prefix structures, though it required manual pointer casting. While C++ made inheritance syntactically more convenient, it introduced the infamous "deadly diamond of death." Instead of resolving this ambiguity as earlier languages had done, Java bypassed the issue entirely by banning multiple inheritance of implementation and inventing the `interface` construct—an approach later mimicked by C# and other languages.

- In plain C, callers saw only forward-declared struct tags and function prototypes; internal data members were strictly inaccessible. - C++ placed member variables in publicly viewable header files, forcing compilers to enforce synthetic visibility boundaries. - As Martin bluntly asserts: “Every object language out there weakens the encapsulation that we used to have so when people tell you that OO is an encapsulated language they are wrong.” - The diamond problem occurs when two derived base classes inherit from a single common ancestor class, creating ambiguous duplicate paths down to a shared descendant. - Java’s `interface` is fundamentally an abstract class consisting purely of abstract methods; its only unique syntactic property is that classes are permitted to multiply inherit interfaces rather than concrete implementations. - Martin grants OO only "half credit" for inheritance, viewing it merely as a modest syntactic convenience over existing C techniques.

---

[43:28](https://www.youtube.com/watch?v=TMuno5RZNeE&t=2608s) Inheritance, Typing, and Function Pointers in C

Turning to modern dynamic languages, Martin asks Ruby developers in the audience how often they use inheritance and whether it is required for polymorphism. Dynamically typed languages such as Ruby and Python achieve full polymorphic dispatch via duck typing without requiring an explicit inheritance hierarchy or shared base class. In static languages like Java, C#, and C++, developers are forced to declare formal inheritance trees simply to satisfy the type checker. Therefore, having to rely on inheritance to achieve polymorphism is actually a structural limitation of statically typed OO languages.

Martin demonstrates that C was already capable of pure polymorphism by writing a minimal implementation of the classic Unix `copy` program:

```c int c; while ((c = getchar())!= EOF) { putchar(c); } ```

Although this snippet appears trivial, Martin highlights that `getchar()` and `putchar()` are fundamentally polymorphic functions. By default, standard input maps to the keyboard and standard output maps to the display terminal, but Unix redirection allows them to bind to disk files, network sockets, serial ports, or printers without altering a single line of application source code. The operating system achieved this through a strict contract: every device driver had to implement five fundamental functions sharing identical signatures—`open`, `close`, `read`, `write`, and `seek`. The kernel stored these function addresses in an internal dispatch table, dispatching calls through pointers at runtime without needing to know the underlying hardware identity.

- In dynamic languages, inheritance is utilized strictly to share variables and behavior, because polymorphic dispatch comes inherently for free. - The early Unix `copy` loop stored stream characters in integer variables (`int c`) rather than standard `char` types to allow checking against the integer sentinel `EOF`. - Device drivers in Unix exposed five core standardized routines: `open`, `close`, `read`, `write`, and `seek`. - The operating system's internal table of function pointers operates as an exact structural equivalent to the virtual method table (vtable) implemented by C++ compilers. - Object-oriented languages eliminate raw function pointers—or wrap them behind delegates—because built-in language polymorphism fulfills the same purpose safely. - In pure C, managing manual function pointer tables was exceedingly rare and perilous: “The reason we didn't do it in C very much was because it was dangerous as hell.”

---

[49:15](https://www.youtube.com/watch?v=TMuno5RZNeE&t=2955s) Polymorphism as Dependency Inversion

Martin argues that the true breakthrough of object-oriented programming is neither encapsulation nor real-world modeling, but rather the safe, ubiquitous availability of polymorphism. In procedural programming without function pointers, the source code compile-time dependency always mirrors the runtime flow of control: if module `M` calls function `F()` in module `N`, module `M` must directly mention, import, and depend upon module `N`.

```mermaid flowchart LR accTitle: Flow of Control vs Compile-Time Dependency Inversion accDescr: Diagram illustrating how introducing an interface reverses the compile-time dependency arrow against the runtime flow of control.

subgraph Procedural["Procedural Architecture"] direction LR M1["Module M"] -->|"Flow of Control & Compile Dependency"| N1["Module N<br/>f()"] end

subgraph OO_Inversion["Polymorphic Inversion"] direction LR M2["Module M"] -->|"Flow of Control & Calls"| I["Interface<br/>f()"] N2["Module N<br/>Implements f()"] -.->|"Compile Dependency (Inherits)"| I M2 -.->|"Compile Dependency"| I end ```

By placing an abstract interface between the caller and the implementer, an OO architecture completely breaks this lockstep coupling. Module `M` now calls the interface, while module `N` derives from and implements that interface. As a direct consequence, the source code compile-time dependency points backwards—directly against the runtime flow of control. This inversion gives software architects absolute authority over the direction of dependencies throughout a system, allowing them to isolate business policies from volatile implementation details.

- Direct procedural calls bind compile-time dependencies to runtime control flow, leading directly to software fragility, rigidity, and non-reusability. - Polymorphism empowers the developer to selectively point source-code dependency arrows in any chosen direction, independent of execution flow. - Martin dismisses claims that OO was designed to replicate human cognition or model the physical world: “These things were made up by marketing people in order to sell the idea to executives who didn't know what the what the programs were.” - In its purest technical essence, object-oriented design is the practice of managing software dependencies through the strategic inversion of interfaces.

[51:36](https://www.youtube.com/watch?v=TMuno5RZNeE&t=3096s) The Origin of the SOLID Acronym

Robert C. Martin begins connecting the earlier discussion on managing key architectural dependencies—specifically avoiding software rot like rigidity, fragility, and non-reusability—to the power of object-oriented polymorphism. Taking advantage of this property requires a disciplined set of design principles. Martin turns to his presentation slides, joking that although he had not been displaying them until now, everything he had been discussing was already prepared on them.

He introduces the collection known as the SOLID principles. As Martin recounts with some humor, the naming was not initially an intentional master plan. He had assembled the principles first, and only years later did someone point out to him that the initial letters formed the word "SOLID." Seeing the value in such a memorable moniker, he adopted it immediately: “Someone pointed it out to me several years ago and said, 'You know this spells SOLID,' and so I quickly changed it because it sounded like a great word to use for a bunch of principles.”

[52:27](https://www.youtube.com/watch?v=TMuno5RZNeE&t=3147s) The Single Responsibility Principle and the Three C-Level Executives

Martin introduces the first principle, represented by the letter **S**: the Single Responsibility Principle (SRP). He cautions that developers routinely misinterpret the word "responsibility" as referring to a module's computational function or what the code physically does. In software architecture, responsibility is defined strictly in terms of change. As Martin defines it: “A class should have only one reason to change.”

```mermaid flowchart TD accTitle: Single Responsibility Principle Violation accDescr: An Employee class coupled to three distinct C-level executives and their subordinates. subgraph Executives["Organizational Stakeholders"] CFO["CFO / Accountants"] COO["COO / Auditors"] CTO["CTO / DBAs"] end subgraph ProblematicClass["Employee Class (3 Reasons to Change)"] CP["calcPay()"] RH["reportHours()"] WE["writeEmployee()"] end CFO -.->|"business rules & payroll"| CP COO -.->|"audit reporting format"| RH CTO -.->|"database schema changes"| WE ```

To demonstrate the real-world danger of violating this principle, Martin presents a hypothetical `Employee` class containing three distinct methods: `calcPay()`, `reportHours()`, and `writeEmployee()`. While these all appear to belong naturally to an employee entity, each serves an entirely different corporate master: - **`calcPay()`**: Calculates employee pay. If this logic breaks and costs the company a million dollars, the Chief Financial Officer (CFO) will discover it in the financial ledgers, trace it back, and fire the developers. - **`reportHours()`**: Generates audit reports detailing hours worked and project expenses. If a million-dollar bug manifests here, the Chief Operating Officer (COO)—who oversees operational auditors—will be the executive who finds out and fires the developers. - **`writeEmployee()`**: Handles database persistence. If this method corrupts a million dollars' worth of enterprise data assets, the Chief Technology Officer (CTO) will be the one knocking on the engineering team's door to fire them.

Beneath each of these C-level executives sit distinct organizational actors: financial accountants beneath the CFO, regulatory auditors beneath the COO, and database administrators (DBAs) beneath the CTO. When any of these actors request a change—whether it is an updated pay calculation, a restructured audit layout, or a modified database schema—their requests converge on the exact same class file. Coupling these disparate concerns creates a massive hazard: modifying the reporting logic for an auditor can easily introduce a collateral bug that silently breaks payroll calculations.

[56:58](https://www.youtube.com/watch?v=TMuno5RZNeE&t=3418s) Decoupling Responsibilities and Avoiding Collateral Damage

To prevent developers from being fired by upset C-level executives, Martin emphasizes that these three methods must be pulled out of a single class and separated into distinct components. Developers often resist this, protesting that object-oriented programming is supposed to bundle data with the operations that manipulate that data. While that is true, Martin argues that software must also be structured to withstand organizational pressure and localized changes.

He outlines several architectural patterns to decouple the responsibilities: - An `Employee` core class handling `calcPay()`, with standalone auxiliary classes like a `ReportWriter` and an `EmployeeRepository` that interact with it. However, Martin notes that direct dependencies between them still carry recompilation and redeployment baggage if core signatures change. - Defining an abstract `Employee` interface containing specific method subsets, implemented by specialized derived classes. - Creating three separate classes—one for each responsibility—fronted by a unified Facade object that delegates incoming calls to the respective underlying workers.

Martin illustrates the risk with an everyday scenario: operational auditors decide they no longer want fractional hours printed with a decimal point (for example, wanting hours and tenths split into separate numbers without the dot). The underlying calculation remains untouched; the auditor simply dislikes the punctuation. A developer opens the file to tweak the formatting string, inadvertently damages the adjacent `calcPay()` method, and triggers a payroll catastrophe. “I don't want to expose the `calcPay()` method to the vagaries of the auditors who want to change the format of a report,” Martin asserts. Formatting concerns belong in one place, core business calculations in another, and persistence mechanisms in a third.

[01:00:02](https://www.youtube.com/watch?v=TMuno5RZNeE&t=3602s) The Open-Closed Principle and Polymorphic Extension

Martin moves to the second principle, the letter **O**: the Open-Closed Principle (OCP). Coined by Bertrand Meyer in his 1988 book *Object-Oriented Software Construction*, the rule establishes a fundamental benchmark for maintainable code: “A class or a module should be open for extension but closed for modification.” As an aside, Martin strongly encourages developers to read the first edition of Meyer’s book, playfully warning that the second edition expanded to over a thousand pages and is impossible to finish.

The principle often sounds paradoxical or oxymoronic to developers at first encounter: how can one change a module's runtime behavior without altering its source code? Martin explains that software engineers utilize this exact mechanism constantly through polymorphism: - In the previously discussed command-line copy program, the core copying loop is written against abstract input and output interfaces. - If a team later needs to support reading input from an optical character reader (OCR), they can author a brand-new OCR device driver that adheres to the established interface. - The existing copy program requires no source modifications, no edits, and no recompilation. It seamlessly invokes the new driver at runtime despite the driver being written long after the original program was compiled and shipped.

When an architecture adheres to the Open-Closed Principle, adding a new feature involves writing new code while leaving existing, proven modules entirely untouched.

[01:02:42](https://www.youtube.com/watch?v=TMuno5RZNeE&t=3762s) Violating the Open-Closed Principle in C

Martin shifts to concrete code to demonstrate what happens when the Open-Closed Principle is violated. Choosing C (or a C-like syntax) to show that architectural principles operate independently of language-level object syntactic sugar, he begins writing a failing, non-conformant implementation.

He sketches out a header file, `shape.h`, that uses an enumeration to discriminate between different geometric types:

```c enum ShapeType { Circle, Square };

struct Shape { enum ShapeType itsType; };

struct Circle { enum ShapeType itsType; double itsRadius; /*... */ }; ```

Martin explains the manual inheritance trick being used: because the `Circle` data structure begins with the exact same memory layout as the `Shape` structure (the `itsType` field), a pointer to a `Circle` can be cast to a pointer to a `Shape`. While this mimics object inheritance, tracking types through an explicit `enum` inevitably forces procedural control logic like `switch` or `if/else` ladders across the codebase, ensuring that any introduction of a new shape requires opening and modifying existing, compiled source files.

[01:03:33](https://www.youtube.com/watch?v=TMuno5RZNeE&t=3813s) Procedural Shape Drawing and the Symptoms of Rot

Bob Martin illustrates procedural code that violates the Open-Closed Principle using an example written in C-style structures and functions. In this design, a header file defines an enumeration of shape types alongside shapes like `Circle` (with a center point, a radius, and a `drawCircle` function) and `Square` (stored in `square.h`, with side length, a top-left point, and a `drawSquare` function). The central dispatching routine, `drawAllShapes`, takes an array of shape pointers, iterates over them, checks each shape's type tag, and branches via a `switch` statement to call either `drawSquare` or `drawCircle`.

```mermaid flowchart TD accTitle: Procedural Shape Drawing Coupling accDescr: Diagram showing how drawAllShapes and shape definitions depend directly on shape.h enum and each other.

subgraph Header["Header Dependencies"] SH["shape.h<br/>(enum ShapeType)"] end

subgraph Shapes["Concrete Implementations"] SQ["square.h / square.c<br/>(Square struct, drawSquare)"] CI["circle.h / circle.c<br/>(Circle struct, drawCircle)"] end

subgraph Dispatcher["High-Level Policy"] DAS["drawAllShapes()<br/>(switch on ShapeType)"] end

SH --> SQ SH --> CI DAS --> SH DAS --> SQ DAS --> CI ```

When asked what goes wrong if an `Oval` must be added, Martin walks through the cascade of failures:

1. **Rigidity:** The very first line of code that must change is the `ShapeType` enum located inside `shape.h`. Because every single module `#include`s `shape.h`, the entire project recompiles. Martin highlights the absurdity: “Square and Circle don't give a damn about oval, but they've got to recompile just because I added an oval.” That unnecessary recompilation is the textbook symptom of rigidity: “You have to compile things that should not have had to be recompiled because of a change.” 2. **Fragility:** Next, the programmer must track down the `switch` statements to insert the new case. Martin warns that these statements rarely exist in isolation: “Switch statements are like the Sith: always more than one there are.” Switch blocks duplicate and spread throughout the system for every behavior—`drawAllShapes`, `eraseAllShapes`, `dragAllShapes`, `scaleAllShapes`, `rotateAllShapes`—or appear disguised as chained `if-else` blocks. Missing one causes obscure bugs, especially because programmers apply custom optimizations. For instance, rotating a circle requires no operations, so a developer might omit `case CIRCLE:` altogether from `rotateAllShapes`. Trying to locate and decode all these scattered, hand-optimized conditionals inevitably breaks the software. 3. **Immobility:** Martin introduces a startup scenario where cash is running out and management hires an expensive consultant. The consultant suggests giving `drawAllShapes` away as a free download (packaged in a JAR or DLL) to drive traffic, while charging $5 each for separate JAR or DLL downloads of `Circle`, `Square`, `Oval`, and `Triangle`. The developers are forced to confess that their code cannot do this. Because `drawAllShapes` contains source and binary dependencies on every concrete shape, they cannot be deployed independently. The architecture cannot support the business model, leading to failure: “The reusable part was not properly decoupled from the goop.”

[01:08:33](https://www.youtube.com/watch?v=TMuno5RZNeE&t=4113s) The Object-Oriented Solution and "The Lie"

Martin transitions to the object-oriented refactoring, sarcastically setting the mood with “soft music in the background, the sound of wind blowing through the trees, fuzzy bunnies jumping over hillsides.” In this clean C++ version, an abstract base class `Shape` declares a pure virtual `draw()` method. `Square` and `Circle` derive from `Shape` and provide their own implementations. The `drawAllShapes` function simply iterates through an array of `Shape*` and invokes `shape->draw()`.

```mermaid flowchart TD accTitle: Polymorphic Shape Hierarchy accDescr: Class hierarchy where high-level policy depends only on an abstract Shape interface.

subgraph Policy["Abstract Layer"] DAS["drawAllShapes()"] S["class Shape<br/>+ draw()"] end

subgraph Implementations["Concrete Derivatives"] SQ["class Square<br/>+ draw()"] CI["class Circle<br/>+ draw()"] OV["class Oval<br/>+ draw()"] end

DAS --> S SQ --"derives from"--> S CI --"derives from"--> S OV --"derives from"--> S ```

When an `Oval` is introduced: - **Nothing recompiles:** Existing shapes and `drawAllShapes` remain untouched. - **Fragility is eliminated:** The compiler forces the implementer of `Oval` to provide implementations for all required pure virtual methods, avoiding missed switch branches or missing behavior like empty rotations. - **Independent deployability is unlocked:** `drawAllShapes` depends strictly on the `Shape` interface, unaware that `Square` or `Circle` exist. The high-level policy can be packaged into its own binary library and distributed freely, while individual shape plugins are sold separately.

However, Martin abruptly calls this idyllic picture "the lie." He points out that this object-oriented abstraction is strictly designed to guard against one specific axis of change: the addition of new shapes. In reality, customers rarely ask for what developers anticipate: “Our customers have no interest in new shapes. What they want is for all the squares to be drawn first and all the circles to be drawn second.”

If the customer demands that circles render on top of squares, the simple loop iterating through polymorphic pointers fails to accommodate the requirement. Developers cannot foresee every bizarre customer requirement: “Customers are very good at somehow knowing what your design is and then choosing the new feature that will completely thwart your design.”

Martin argues that trying to preemptively design abstractions against every hypothetical future requirement by blindly modeling the "real world" is a trap. Instead, the correct engineering practice is agile pragmatism: - Implement the simplest possible working design. - Deliver it directly to the customer as quickly as possible. - Beg the customer to use it and break it: “Please change our code. Give us new features.” - Observe where the actual axis of change falls, and only then construct abstractions to protect against those specific changes.

[01:13:50](https://www.youtube.com/watch?v=TMuno5RZNeE&t=4430s) The Liskov Substitution Principle (LSP)

Martin turns to the next SOLID principle: the Liskov Substitution Principle (LSP). Formulated in 1988 by Barbara Liskov as a formal mathematical property of subtyping, Martin paraphrases her rule for everyday software development: “Derived classes must be usable through the base class interface without the need for the user to know the difference.” In essence, any client module consuming a base class interface should be able to accept any derivative of that base class transparently, without behavioral surprises or side effects.

To demonstrate how easily this principle is broken by naive object-oriented modeling, Martin introduces the classic "Rectangle and Square" problem. He describes an existing, well-tested `Rectangle` class with `height` and `width` member fields stored as real numbers, manipulated via standard setter methods like `setWidth(double w)` and `setHeight(double h)`. Problems arise as soon as a new requirement demands adding a `Square` to the model.

[01:15:05](https://www.youtube.com/watch?v=TMuno5RZNeE&t=4505s) The Classic Square-Rectangle Paradox and Heap Corruption

Uncle Bob Martin introduces a new requirement: the system must now model a square. Geometrically and conceptually, a square is undeniably a rectangle, making inheritance seem like the obvious choice. However, the architectural mismatch appears immediately when examining internal state. A `Rectangle` has two fields—height and width—whereas a `Square` conceptually only requires one. By inheriting from `Rectangle`, a `Square` inherits both fields, needlessly wasting memory. While one might rationalize that memory is cheap, the behavioral problems run far deeper. To preserve the square constraint, a programmer overrides `setWidth` to set both height and width, and similarly overrides `setHeight` to do the same.

```mermaid flowchart TD accTitle: The Square-Rectangle Liskov Violation accDescr: Shows the caller passing a Square as a Rectangle, breaking caller assumptions and corrupting state. subgraph ClientCode["Client Function"] C["Caller holding Rectangle reference"] C -->|"1. calls setHeight(h)"| R C -->|"2. expects width unchanged"| A{"Assumption check"} A -->|"Width changed unexpectedly!"| Err["Fails later / Corrupts Heap"] end subgraph Hierarchy["Inheritance Hierarchy"] R["Rectangle\n- height\n- width\n+ setHeight(h)\n+ setWidth(w)"] S["Square\n+ setHeight(h) { h=w=h }\n+ setWidth(w) { h=w=w }"] S --|>|"inherits from"| R end ```

The true catastrophe happens from the caller's perspective. A user calling `setHeight` on an object believed to be a `Rectangle` legitimately expects the width to stay unchanged. When a `Square` is passed instead, calling `setHeight` silently alters the width. Martin vividly describes how this subtle side effect can quietly dismantle an application: “if I pass him a square, he will call set height and the width will change and that will corrupt his internal finite state machine and he will then corrupt the heap and you will discover this a billion instructions later when you get a null pointer exception.”

Tracing this issue is a nightmare for the developer whose code failed. Weeks might be spent using a logic analyzer to trace backwards through a billion instructions, only to realize the root cause was an unexpected `Square` object passed into a routine expecting standard `Rectangle` behavior.

[01:17:16](https://www.youtube.com/watch?v=TMuno5RZNeE&t=4636s) Defensive Type-Checking and the Demise of OCP

Once a developer uncovers this bug, their initial instinct is often to write defensive checks inside the calling module: checking `if (rectangle is Square)`. Martin emphasizes that doing this instantly hangs an unwanted dependency on `Square`. A module that previously only needed to know about rectangles becomes coupled to a specific subtype, directly violating the Open-Closed Principle (OCP) and introducing rigidity and fragility.

Developers frequently rely on constructs like `instanceof` in Java, or `is` and `as` in.NET, to perform these runtime type inspections. Martin cautions that while using these type checks is not inherently evil in every single scenario, it almost always signals a deeper architectural flaw: “It is not true that every time that you do that it's a problem, but most of the time when you do that it is a problem.”

- `instanceof` (Java) and `is` / `as` (.NET) are commonly used to interrogate an object's dynamic type. - Type-checking subclasses breaks the Open-Closed Principle by forcing parent abstractions to depend on their derivatives. - The need for an `if` type-check usually reveals that a base class contract has been violated downstream.

[01:18:22](https://www.youtube.com/watch?v=TMuno5RZNeE&t=4702s) Representatives vs. Reality: Why "IS-A" Is a Lie

Martin confronts the core intuition that tricks developers: isn't a square undeniably a rectangle? He refutes this by separating reality from software representation. A piece of code named `Square` is not an actual square, and a piece of code named `Rectangle` is not a rectangle—they are merely software artifacts that represent real-world concepts. Martin articulates the crucial distinction: “The things that represent do not share the relationships of the things they represent.” To illustrate, he uses a divorce analogy: if a husband and wife are getting divorced, their respective divorce attorneys do not divorce each other.

Martin traces the phrase "IS-A" back to the 1980s artificial intelligence community, which built knowledge networks featuring relationships like "has-a," "tastes-like-a," "smells-like-a," and "is-a." When AI funding dried up because the field failed to deliver practical utility, these researchers transitioned into object-oriented programming, bringing their terminology with them. Martin argues that describing inheritance as "IS-A" is misleading; in reality, inheritance is merely the redeclaration of functions and variables in a sub-scope.

- `Square` and `Rectangle` classes are representations, and representations do not inherit the real-world relationships of the underlying items. - The "IS-A" taxonomy originates from 1980s AI knowledge networks rather than sound software engineering theory. - Inheritance in programming languages is fundamentally a scoping mechanism for functions and data, not a mirror of ontological reality.

[01:20:30](https://www.youtube.com/watch?v=TMuno5RZNeE&t=4830s) Liskov Substitution Principle and the Floating-Point Trap

When addressing how to properly structure the classes, Martin observes that `Square` and `Rectangle` have fundamentally different behaviors and state requirements. While both might derive independently from a broader abstract base like `Shape`, `Rectangle` cannot serve as the parent of `Square`. Martin states the overarching rule: “This is a violation of the Liskov Substitution Principle, because square is not substitutable for rectangle.” Whenever the substitution principle is broken, developers are inevitably forced to add dynamic type checks to stop the system from crashing.

```mermaid flowchart TD accTitle: Proper Separation via Common Shape Base accDescr: Illustrates decoupling Rectangle and Square by having both inherit from a generic Shape abstraction. Base["Shape\n+ getArea()\n+ draw()"] R["Rectangle\n- height\n- width\n+ setHeight(h)\n+ setWidth(w)"] S["Square\n- side\n+ setSide(s)"] R --|>|"inherits from"| Base S --|>|"inherits from"| Base ```

As the lecture wraps up around 8:00 PM, an audience member suggests eliminating the `Square` class entirely and adding an `isRegular()` method or flag to `Rectangle`. Martin entertains the idea: if a rectangle is regular, what happens when `setHeight` is called? Does it alter the width, or does it toggle the flag to false? If the method computes regularity dynamically by comparing height and width, Martin highlights another critical trap: real numbers. He warns sternly against comparing floating-point numbers for exact equality: “You do not compare real numbers equal in a computer; floating point numbers can be greater than or less, but they are never equal.” Comparing real numbers for exact equality—especially when representing currency—is a catastrophic mistake that leads to severe bugs. With that final caution, Martin thanks the room and closes the session.

- Violating LSP forces the introduction of type checks that pollute client code. - If modeled at all, `Square` and `Rectangle` should be separate peers under an abstract base class like `Shape`. - Never use direct equality (`==`) to compare real or floating-point numbers; software should test within an acceptable delta or tolerance instead.

## Notable quotes > [15:52](https://www.youtube.com/watch?v=TMuno5RZNeE&t=952s) My message to you today is you don't go fast by writing crap. — Bob Martin

> [52:27](https://www.youtube.com/watch?v=TMuno5RZNeE&t=3147s) A class should have only one reason to change. — Robert C. Martin

> [01:18:22](https://www.youtube.com/watch?v=TMuno5RZNeE&t=4702s) The things that represent do not share the relationships of the things they represent. — Robert C. Martin

> [22:46](https://www.youtube.com/watch?v=TMuno5RZNeE&t=1366s) Fragility is the tendency of the code to break in many places even when you only change it in one place. — Bob Martin

> [38:52](https://www.youtube.com/watch?v=TMuno5RZNeE&t=2332s) Every object language out there weakens the encapsulation that we used to have so when people tell you that OO is an encapsulated language they are wrong. — Bob Martin

> [24:45](https://www.youtube.com/watch?v=TMuno5RZNeE&t=1485s) The desirable parts of the code are so horribly coupled to the undesirable parts of the code that you cannot use the desirable parts of the code somewhere else. — Robert C. Martin

## Mentioned Kyle Jensen, Robert C. Martin, Dan Bader, Daniel Abadi, Brad Cox, Bjarne Stroustrup, Steve Jobs, John Sculley, Bertrand Meyer, Barbara Liskov, Yale School of Management, Yale Entrepreneurial Institute, HackYale, YHack, Continuity Control, New Haven IO, Agile Manifesto, Stepstone, NeXT, NeXTSTEP, Apple, The C++ Programming Language, Object-Oriented Software Construction, C, C++, Java, C#, Ruby, Python, Objective-C, Smalltalk, Visual Basic for Applications, Fortran, COBOL, PL/I, Unix, OS X, iPhone, iPod, .NET

[Source](https://www.youtube.com/watch?v=TMuno5RZNeE)

中文简报

鲍勃·马丁认为依赖关系混乱导致软件持续恶化 软件项目之所以失败,是因为编译时依赖关系映射了运行时控制流,这要求工程师运用多态性来反转依赖关系,从而使核心业务策略免受易变实现细节的影响。 鲍勃·马丁大叔探讨了软件项目失败的根本原因,指出行业呈指数级增长、开发者普遍经验不足,以及为了赶工期而盲目求快所带来的危害。他剖析了代码恶化的三大主要症状——僵化性、脆弱性和不可流动性,并阐释了不当的耦合以及过程式的源码依赖是如何引发这些问题的。通过追溯面向对象编程的发展历程,马丁打破了围绕封装和继承的常见误解,随后阐明了多态性如何实现真正的依赖反转。接着,他结合具体的架构与几何学案例,深入剖析了SOLID原则的机制,重点讲解了单一职责原则、开闭原则和里氏替换原则。 全球软件开发人员数量大约每五年翻一番,这使软件工程行业陷入一条指数增长曲线,导致大约一半的从业者工作经验不足五年。 (12:41) 面向对象编程并非首创了封装或现实世界建模;其核心架构创新在于安全的动态多态性,这使工程师能够打破依赖运行时执行流的限制,实现源码依赖的反转。 (49:15) 软件架构的主要目的是管理耦合,并防止架构衰退的三大典型症状:僵化性(修改引发连锁反应)、脆弱性(无关的远端代码损坏)和不可流动性(无法提取可复用逻辑)。 (26:53) 软件中的表示并不继承其所建模对象的现实世界关系,因此将“正方形是矩形”等标准现实世界分类法生搬硬套到类继承体系中是危险的。 (78:22)

Reading summary

Robert C. Martin argues that professional software engineering depends on actively managing source code dependencies through polymorphism rather than rushing under deadline pressure. He contends that the SOLID principles provide the necessary discipline to decouple high-level business policies from low-level implementation details and organizational volatility. Perpetual Inexperience and the Symptoms of Rot (12:41) Martin observes that the global programmer population has expanded exponentially from roughly 200 in 1960 to an estimated 100 million, doubling roughly every five years. Consequently, around half of all working developers possess fewer than five years of experience, locking the industry into a state of perpetual inexperience. Under schedule pressure, inexperienced teams often rush and generate low-quality code, creating friction that slows future progress. This dynamic manifests in three technical symptoms of bad code: rigidity, where altering a single module forces cascading recompilations and edits across unrelated files; fragility, where an isolated change silently breaks distant application functions and destroys stakeholder trust; and immobility, where desirable business logic cannot be extracted or reused because it is hopelessly tangled with frameworks, databases, or unwanted dependencies. Martin emphasizes that all three symptoms stem from uncontrolled coupling where high-level policies depend directly downward on concrete implementation details. Polymorphism as Inversion of Source Dependencies (49:15) Challenging textbook definitions, Martin asserts that object-oriented programming did not invent encapsulation or inheritance. In procedural C, encapsulation was complete because callers only saw forward-declared structs in header files while implementations remained hidden in source files; C++ actually compromised this boundary by requiring private member variables in public headers. Similarly, single inheritance was routinely achieved in C through struct prefixes and pointer casting, whereas modern dynamic languages achieve dispatch without formal inheritance trees. According to Martin, the true breakthrough of object-oriented languages is safe, universal polymorphism. In traditional procedural architectures, source-code dependencies inevitably point in the same direction as the runtime flow of control. Polymorphism enables dependency inversion: by placing abstract interfaces between callers and callees, source-code compile-time dependencies point against the flow of execution. This gives architects absolute control over dependency arrows, isolating high-level business rules from volatile external details. Single Responsibility and Organizational Sources of Change (52:27) The Single Responsibility Principle (SRP) asserts that a module should have only one reason to change, which Martin defines strictly in terms of business actors rather than computational tasks. When distinct stakeholders influence different behaviors within a single class, those stakeholders' unrelated demands become artificially coupled. To illustrate, Martin points to an Employee class that combines pay calculation, audit hours reporting, and database persistence. These functions serve three distinct executives: the Chief Financial Officer (accounting rules), the Chief Operating Officer (audit formats), and the Chief Technology Officer (database schemas). If an auditor requests a minor cosmetic change to drop decimal points from report formatting, a developer editing the shared class risks inadvertently introducing a bug into the adjacent payroll calculation routine. Martin stresses that separating these responsibilities into distinct classes or behind facades prevents changes requested by one department from inflicting collateral damage on another. Open-Closed Extensibility and the Agile Reality (60:02) Bertrand Meyer's Open-Closed Principle (OCP) dictates that classes should be open for extension but closed for modification. Procedural designs relying on type flags and switch statements violate this principle: adding a new type requires modifying central dispatchers and recompiling every dependent file across the codebase. With polymorphic base classes, new functionality can be added by implementing interfaces in new source files without altering or recompiling existing, tested modules. However, Martin characterizes purely speculative object-oriented abstractions as a trap, noting that developers rarely anticipate how customer requirements will evolve. For example, while an interface might easily accommodate new shapes, a client might suddenly demand that all circles render strictly after squares. Rather than attempting to predict every theoretical requirement upfront by modeling the real world, Martin advocates an agile approach: implement the simplest working solution, deliver it rapidly, observe the actual axes of change demanded by users, and construct targeted abstractions only where volatility demonstrably occurs. Subtyping Pitfalls and the Liskov Principle (73:50) The Liskov Substitution Principle (LSP) requires derived subtypes to be fully substitutable for their base types without callers needing to know the difference. Martin demonstrates the fragility of naive modeling using the classic geometry paradox where a Square inherits from a Rectangle. While geometrically valid, a software Square that overrides height and width setters to remain square violates the caller's reasonable assumption that modifying height leaves width unchanged, risking silent state corruption. When substitutions fail, developers often resort to dynamic type checks such as instanceof or is/as, which immediately violates the Open-Closed Principle by coupling parent abstractions to specific derived types. Martin attributes this error to the historical 'IS-A' framing adopted from 1980s artificial intelligence networks. In software, representations do not share the physical relationships of the entities they represent; inheritance is merely a scoping mechanism for data and functions, not a tool for ontological fidelity. Martin concludes that object-oriented design is fundamentally an exercise in managing dependencies rather than mimicking physical reality. Because developers cannot predict future business demands in advance, sustainable architecture relies on rapid delivery, empirical observation of change, and the disciplined application of polymorphic boundaries to keep systems extensible, testable, and robust against organizational volatility.

精炼阅读摘要

Robert C. Martin(“Uncle Bob”)在演讲中指出,软件架构的核心挑战在于依赖关系的管理,开发团队若因追求交付速度而编写混乱代码,将不可避免地导致系统出现僵化、脆弱与无法复用等退化现象;通过应用以多态和抽象接口为基石的 SOLID 原则,工程师能够反转编译时依赖,构建出易于维护和敏捷响应变更的系统。 行业经验断层与劣质代码代价 (12:41) Martin 估算全球程序员人口已达约 1 亿人(含 VBA 开发者),较 1960 年仅数百人及 1970 年约 5 万人呈指数级膨胀,大约每 5 年翻一番。这一数学规律导致整个行业面临永久性经验不足——任何时刻都有约半数程序员的从业时间不足 5 年。在经验断层与工期压力下,团队常误以为牺牲代码质量能换取更快的交付速度。Martin 明确反驳这一观念,将软件开发比作开胸心脏手术:真正的速度来自冷静、规范的流程与持续的清理,匆忙拼凑代码只会让后续维护寸步难行。 系统腐化的三大技术表征 (19:06) 劣质代码的核心破坏力体现在系统变更阶段,主要包含僵化(Rigidity)、脆弱(Fragility)与无法复用(Immobility)三种表征。僵化指代码间错综复杂的强耦合,使得修改一处缺陷会引发连锁反应,迫使开发人员在无关模块中持续修改并重新编译,原本微小的调整最终演变为数周的依赖追踪。脆弱则是局部修改在无提示的情况下破坏看似完全无关的模块,例如调整计薪逻辑导致工会报表系统崩溃,这种不可预测的破坏会迅速摧毁管理层和客户对技术团队的信任。 无法复用发生于系统有价值的核心逻辑与底层框架、数据库等不必要的实现细节深度耦合。当其他团队尝试引入模块时,发现剥离附属依赖的风险与成本远高于从头重写,最终导致业务逻辑难以在不同上下文中迁移。Martin 总结认为,这三种系统腐化现象在技术本质上完全源于未受约束的直接耦合与失控的调用依赖。 多态机制与依赖反转的本质 (38:52) 针对教科书将面向对象(OO)归纳为封装、继承与多态的观点,Martin 提出批判:C 语言早已通过头文件声明与源文件私有结构体实现了严密封装,而 C++ 将私有成员暴露在头文件中反而是对封装的削弱;面向对象语言真正的革命性突破在于提供了一种安全可控的多态分发机制。在传统过程式调用树中,运行时控制流与编译时源码依赖完全同向,导致高层业务策略直接依赖于底层的具体实现。 多态打破了这一硬性绑定。通过在调用方与实现方之间插入抽象接口,高层模块面向接口编程,底层具体模块实现该接口。此时,编译时的源码依赖箭头发生反转,逆向指向抽象层,与运行时的执行流截然相反。架构师得以借助依赖反转(Dependency Inversion)将易变的外部细节(如 UI、数据库、外部库)与核心商业逻辑彻底解耦,使高层业务规则免受低层变动的影响。 单一职责与开闭原则的实践 (52:27) SOLID 原则为依赖管理提供了落地准则。单一职责原则(SRP)强调“一个类应该仅有一个引起变更的原因”。Martin 以包含 calcPay()、reportHours() 和 writeEmployee() 的 Employee 类为例说明:这三个方法分别服务于财务总监(CFO)、运营总监(COO)和技术总监(CTO)对应的业务诉求。若合为一个类,仅为了调整审计报表格式而修改源文件,就可能误伤相邻的薪酬计算逻辑,因此必须将多方利益诉求在代码层面拆分为独立组件。 开闭原则(OCP)要求模块对扩展开放、对修改关闭。以图形绘制系统为例,传统过程式设计依赖 switch 语句判断具体形状,增加 Oval 形状会导致通用头文件重新编译,并且必须在全系统查找并修改所有散落的 switch 分支。基于抽象 Base 接口的多态设计则允许直接新增独立编译的子类插件,高层调度逻辑无需做任何改动。不过 Martin 强调,开发者无法预知所有需求,盲目预设未来场景属于过度设计,敏捷团队应先交付最小可用方案,观察客户实际的变更维度后再针对性地建立抽象屏障。 里氏替换原则与现实建模的陷阱 (73:50) 里氏替换原则(LSP)规定子类必须能够在不改变程序正确性的前提下替换其基类。Martin 以经典的“正方形与长方形”悖论阐明建模陷阱:几何常识中正方形属于长方形,但若让 Square 继承 Rectangle 并重写 setter 使得宽高联动,当调用方持有 Rectangle 引用并调用 setHeight() 时,其对“宽度保持不变”的契约假设便被隐式破坏,极易造成远端状态损坏与内存错误。 发现此类问题后,开发者往往采用 instanceof 或 is 等运行时类型检查进行防御性编码,但这不仅破坏了开闭原则,还重新引入了对具体子类的耦合。Martin 指出,代码中的对象只是现实实体的代表,代表之间的关系并不继承被代表者之间的本体关系(正如离婚律师并不互相离婚)。继承在本质上仅是特定作用域内的函数与变量复用机制,绝非现实世界“IS-A”层级的自然映射;若无法满足完全替换,Square 与 Rectangle 应作为同级平级模块独立实现,而非强行建立继承树。 Martin 论证指出,软件架构的重心在于控制依赖方向而非机械地模仿现实世界。然而,面对真实业务变更的不确定性与行业普遍的经验不足,架构师究竟应在项目初期保持多大程度的抽象,抑或完全依赖后期敏捷重构,仍取决于团队对变更轴线的观察敏锐度与工程纪律。

Detailed notes — zh

## Background 该视频由 YouTube 频道 People career 于 2015 年 5 月 24 日发布,记录了由耶鲁大学管理学院与本地技术社区联合举办的线下技术讲座。视频未提供具体的文字简介,其核心内容为“鲍勃大叔”罗伯特·C·马丁(Robert C. Martin)受邀发表的关于面向对象与敏捷设计中的 SOLID 原则主题演讲。 ## TL;DR 在演讲中,Bob Martin 从程序员人口每五年翻倍引发的“永久缺乏经验”行业危机切入,剖析了糟糕代码导致的系统僵化、脆弱和不可复用等恶疾。他打破了面向对象带来完美封装与贴合现实世界建模的神话,指出其终极价值在于通过多态实现依赖反转,从而自由控制源代码依赖。随后,他结合生动案例与黑板推演,深入剖析了单一职责原则(SRP)、开闭原则(OCP)与里氏替换原则(LSP)的本质,指出软件设计的核心在于根据真实变更隔离关注点与维护调用契约,而非生搬硬套现实概念。 ## Atmosphere & style 整场讲座充满了 Bob Martin 标志性的辛辣讽刺与机智幽默,他以水分子物理特性、心脏外科手术甚至离婚律师等极具张力的奇妙比喻牢牢抓住全场注意力。现场互动频繁、笑声不断,叙事节奏在拆穿教科书式教条的当头棒喝与白板现场画图推演的代码细节之间流畅切换。观众仿佛置身于一间兼具戏剧舞台效果的大学阶梯教室,在极度轻松风趣的听讲体验中,接受直击灵魂的软件工程纪律与职业操守洗礼。 ## Bulletin - [12:41](https://www.youtube.com/watch?v=TMuno5RZNeE&t=761s) 全球程序员人口每5年翻一番,导致行业中始终有约50%的从业者工作经验不足5年,造成系统性的“永久缺乏经验”困境。 - [18:59](https://www.youtube.com/watch?v=TMuno5RZNeE&t=1139s) 劣质代码导致的系统三大核心病症为僵化性(Rigidity)、脆弱性(Fragility)与不可复用性(Immobility),其根源在于控制流与源码编译期依赖的绝对绑定。 - [44:27](https://www.youtube.com/watch?v=TMuno5RZNeE&t=2667s) 面向对象(OO)的核心价值并非真实世界建模或所谓的三大特性,而是利用多态安全地实现“依赖反转”,使源码依赖方向能够逆控制流而行。 - [51:36](https://www.youtube.com/watch?v=TMuno5RZNeE&t=3096s) 单一职责原则(SRP)中的“职责”实质是“变更的来源”,一个类应仅对单一业务角色(如特定的高管关切)负责,避免不同部门的需求在同一类中产生破坏性连锁反应。 - [01:18:22](https://www.youtube.com/watch?v=TMuno5RZNeE&t=4702s) 代表现实事物的代码并不具有事物本身的真实关系,正方形继承自长方形是对里氏替换原则(LSP)的经典违背,会彻底破坏调用者的有限状态机契约并诱发类型检查坏味道。 ## Key takeaways - [15:49](https://www.youtube.com/watch?v=TMuno5RZNeE&t=949s) 程序员无法靠胡乱拼凑烂代码来真正赶进度,唯有保持冷静规范、通过深思熟虑与持续重构才能实现真正的高效。 - [18:59](https://www.youtube.com/watch?v=TMuno5RZNeE&t=1139s) 僵化性(Rigidity)表现为系统组件间的高度耦合,使得局部修改引发全系统海量无关模块的连锁改动与重新编译。 - [22:46](https://www.youtube.com/watch?v=TMuno5RZNeE&t=1366s) 脆弱性(Fragility)表现为在某处改动微小逻辑时,会在看似毫无关联的远程模块中意外崩溃,严重破坏客户与管理层信任。 - [24:45](https://www.youtube.com/watch?v=TMuno5RZNeE&t=1485s) 当非技术的业务人员由于模块过于脆弱而明令禁止开发者改动核心代码时,标志着软件工程在架构控制上的彻底失败。 - [28:59](https://www.youtube.com/watch?v=TMuno5RZNeE&t=1739s) 传统过程式架构中,控制流与源码物理依赖方向重合自顶向下,导致高层核心业务策略频繁被底层易变的实现细节污染。 - [38:52](https://www.youtube.com/watch?v=TMuno5RZNeE&t=2332s) C 语言通过头文件前向声明实现了绝对的数据结构隐藏,而 C++ 等面向对象语言强制在头文件暴露私有字段,本质上是削弱了完美封装。 - [40:50](https://www.youtube.com/watch?v=TMuno5RZNeE&t=2450s) Java 使用 interface 并非先进发明,而是为了减轻编译器开发难度以避开多重继承“菱形问题”的妥协方案。 - [44:27](https://www.youtube.com/watch?v=TMuno5RZNeE&t=2667s) 面向对象的多态将底层基于函数指针的调度转化为安全、内建的机制,使开发者能随意翻转编译依赖箭头以实现依赖反转。 - [51:36](https://www.youtube.com/watch?v=TMuno5RZNeE&t=3096s) 单一职责原则(SRP)的核心定义是“一个类应只有一个引起它变更的理由”,绝不能将服务于不同部门利益相关者的方法混杂在同一个类中。 - [01:00:02](https://www.youtube.com/watch?v=TMuno5RZNeE&t=3602s) 开闭原则(OCP)要求模块对扩展开放、对修改关闭,增加新特性应仅通过增量编写新代码与驱动来实现,而非修改既有模块。 - [01:03:33](https://www.youtube.com/watch?v=TMuno5RZNeE&t=3813s) 在面向过程代码中滥用 switch/case 处理多类型,会造成“西斯定律”般的重复蔓延,导致新增类型时系统出现大面积重新编译和逻辑遗漏风险。 - [01:08:33](https://www.youtube.com/watch?v=TMuno5RZNeE&t=4113s) 过度依赖现实世界分类学的设计往往是脆弱的童话,客户总能提出击穿抽象的新需求,因此应尽早交付原型以探索真实的变化轴。 - [01:13:50](https://www.youtube.com/watch?v=TMuno5RZNeE&t=4430s) 里氏替换原则(LSP)要求派生类必须完全可作为基类替代使用,使用者无需知晓具体差异,任何改变状态独立性的子类重写都会破坏调用方契约。 - [01:18:22](https://www.youtube.com/watch?v=TMuno5RZNeE&t=4702s) “正方形是一个长方形”(Is-A)源于人工智能领域的陈旧概念,面向对象的继承本质仅是在子作用域中重新声明函数与变量,并非现实逻辑映射。 - [01:16:12](https://www.youtube.com/watch?v=TMuno5RZNeE&t=4572s) 在代码中依靠 instanceof 或类型检查来规避子类副作用是系统设计退化的坏味道,直接践踏了开闭原则。 - [01:20:30](https://www.youtube.com/watch?v=TMuno5RZNeE&t=4830s) 在面向对象中尝试用动态状态字段配合浮点数绝对等值比较(==)来判定正方形状态,极易踩入底层精度缺失的陷阱。 ## Detailed walkthrough ### [00:06](https://www.youtube.com/watch?v=TMuno5RZNeE&t=6s) 耶鲁大学的创业氛围与活动开场介绍 耶鲁大学管理学院(Yale School of Management)教员兼创业项目主任 Kyle Jensen 首先登台致辞。他向在场观众宣布,今晚的特邀嘉宾是 Robert C. Martin(业内尊称为“鲍勃大叔” Uncle Bob),他将深入分享“面向对象与敏捷设计的 SOLID 原则”(SOLID Principles of Object-Oriented and Agile Design)。Kyle 指出,这次演讲对耶鲁而言恰逢其时。当前大学正在大力投资创业领域,并将这种创业精神与耶鲁在计算机科学以及理论层面的传统优势深度融合,催生了一系列蓬勃发展的校园实践:两年前创办了 Tech Bootcamp;一年前创立的 HackYale 掀起热潮;Y/Hack 黑客马拉松更是吸引了来自全美各地的上千名开发者展开为期两天的代码角逐。 教学体系也随之发生了重大调整。本学期 Daniel Bodily 重新设计了耶鲁面向非计算机专业的核心先修课,将其改为移动应用开发;明年春季,Kyle 还将与 Daniel 联合讲授编号为 CS 113 的“创业与编程”(Entrepreneurship and Programming)课程;此外,管理学院也开设了“软件开发管理”课程。这种对软件工程的极高热情不仅打通了商学院、计算机系和工学院的学科壁垒,也联动了耶鲁创业研究所(Yale Entrepreneurial Institute)与各类学生社团。更重要的是,这种活力早已溢出校园边界。Kyle 特别感谢了为今晚活动提供餐饮赞助的本地 Ruby 创业公司 Continuity Control,以及长期活跃的技术社团 New Haven IO。他由衷感慨,以往耶鲁一直存在大学与当地社区割裂的“城学隔阂”(town-and-gown problem),而今晚到场的绝大多数人并非耶鲁校友,这恰恰证明了软件工程能够成为打破隔阂、凝聚社区共同兴趣的绝佳纽带。随后,Kyle 邀请 Continuity Control 的工程师 Dan Bergh 登台主持。 ### [03:11](https://www.youtube.com/watch?v=TMuno5RZNeE&t=191s) Dan Bergh 的引言:软件工程的本质与 Uncle Bob 的行业地位 Dan Bergh 登台后先处理了麦克风音量问题,随后简要介绍了 Continuity Control 正在招聘工程师的现状,提到团队不仅推崇极致整洁的代码(clean code),接下来还将开展有趣的机器学习(machine learning)项目。谈及软件开发的真实业态,Dan 感慨写代码是一项极其诡异且混乱的事业(a weird and messy business)。当写代码只是出于个人兴趣时确实妙趣横生,但一旦进入现实商业世界,有人为你写的代码付费、有真实用户依赖它,甚至用户的人身安全都系于代码之上时,开发者往往会感到自己像是一群连鞋带都没系好就到处乱跑的孩子,只能提心吊胆地祈祷自己别摔跤。 Dan 强调,万幸的是软件行业有一位时刻守护着大家、在必要时狠狠“踢大家屁股”促人前行的“鲍勃大叔”。Uncle Bob 参与起草并签署了《敏捷宣言》(Agile Manifesto),从根本上重塑了整个软件产业与业务人员的协作方式,打破了在写代码前试图制定详尽预测计划的幻想;在代码层面,他教会开发者从变量命名、函数方法设计,一路思考到大型应用架构的解耦与通信。今晚的讲座聚焦于架构中枢——如何设计对象以及对象之间的相互协作关系。Uncle Bob 多年来著书立说、四处巡回布道,以一己之力极大地提升了整个行业的工程水平。Dan 在全场的掌声中正式请出 Uncle Bob。 > “When people paying you for the software that you're writing or people using your software or God forbid people whose lives depend on your software, it's a weird business and sometimes you can feel kind of like you know we're a bunch of kids running around with our shoes untied and you're just hoping you don't fall down.” —— Dan Bergh ### [05:56](https://www.youtube.com/watch?v=TMuno5RZNeE&t=356s) 从水分子结构说起:化学与物理底层的作用力机制 Uncle Bob 登台并调试好麦克风后,首先做了一个现场调查,询问台下有多少人是程序员。在看到全场绝大多数人都举手后,他调侃了一位自称做“线上产品”的观众,打趣称那可不算硬核程序员。紧接着,他突然抛出一个看似与软件架构风马牛不相及的问题:“什么是水?”台下观众给出了“天然物质”、“有软硬之分”等常识性回答,Uncle Bob 则纠正道水本身是极软的,是其中溶解的杂质让它变“硬”,并引导大家回忆化学式 $H_2O$。 当问及单个水分子长什么样时,台下有人喊出“米老鼠”(Mickey Mouse)。Uncle Bob 顺势在白板上画出了水分子模型:正中间是一个巨大的氧原子,两只“耳朵”则是两个氢原子,二者之间的夹角约为 103 度(由于量子力学效应决定)。随后他抛出了深层疑问:为什么这三个原子会牢牢结合在一起?在台下试图用“共价键”(covalent bond)这种术语敷衍过去之前,Uncle Bob 带领大家深入剖析原子的基本结构: - 两个氢原子各拥有一个带正电荷的质子; - 氧原子原子核内包含 8 个带正电荷的质子; - 外围环绕着负电荷电子云:每个氢贡献 1 个电子,氧贡献 8 个电子。 按照同性相斥的常理,这三个被负电荷云包裹的原子本该互相排斥,为何最终会凝聚在一起? ```mermaid flowchart TD accTitle: 水分子微观电荷分布与共价键形成机制 accDescr: 展示质子间的正电荷、电子云集中分布区域以及偶极子形成的不平衡电荷分布状态。 subgraph Atoms["原子构成"] H1["氢原子 1<br>(1 个质子, 1 个电子)"] O["氧原子<br>(8 个质子, 8 个电子)"] H2["氢原子 2<br>(1 个质子, 1 个电子)"] end subgraph Interaction["动能碰撞与电荷转移"] Collision["足够热运动碰撞"] --> Concentration["电子云聚集于质子之间"] end subgraph Structure["宏观电荷特征与偶极"] Dipole["形成强偶极子 (Dipole)<br>上半部带负电 / 下半部带正电"] end H1 --> Collision O --> Collision H2 --> Collision Concentration --> Dipole ``` Uncle Bob 解释道,在低温状态下原子运动缓慢,彼此确实会相互排斥无法接近;只有当温度升高、热运动足够剧烈导致它们正面碰撞时,奇妙的物理现象才会发生。电子渴望尽可能靠近所有带正电荷的质子,因此大量负电荷最终聚集在氢质子与氧质子之间的缝隙中。正是这股夹在质子中间的强负电荷将质子牢牢拉在一起。虽然根据泡利不相容原理(Pauli Exclusion Principle),氧原子的最外电子层最多可容纳 8 个电子,原本只有 6 个外层电子的氧与两个氢原子的电子配对填满了外壳,但那只是允许它们嵌合的量子机制,“共价键”的根本物理实质是高密度负电荷将带正电的原子核束缚在了一起。 随后,Uncle Bob 从米老鼠头部正中横向切了一刀,引导大家观察电荷的宏观分布。显而易见,大部分负电荷集中在线的上方,而下方质子区域偏正。水分子在整体上呈现出明显的不均匀电荷分布,从而形成了一个极强的偶极子(dipole)。正因如此,水分子具有强烈的旋转倾向,会迅速调整姿态去吸附任何带有电荷的物体表面——这也正是水能附着在人类皮肤上、让皮肤变湿的根本原因。 > “Covalent bond happens when you manage to get the bulk of the negative charge to hold the positive charges together.” —— Robert C. Martin (Uncle Bob) ### [11:25](https://www.youtube.com/watch?v=TMuno5RZNeE&t=685s) 水分子的静电特性与转向 SOLID 原则 Bob Martin 以一段有趣的物理小把戏作为开场闲聊:水分子由于极性带微弱电荷,因而能够依靠静电吸附在带电物体上,这也是水能溶解物质、能洗去污垢的根本原因。他向台下提问,是否有人给孩子们表演过那个经典小把戏:把水龙头拧开,调出一股极细但不断裂、不滴水的水流,然后拿气球在头发上摩擦起电,慢慢靠近水流,原本笔直的水流便会“咻”地一下被弯曲吸引过去。他笑着调侃大家可以用这个去震撼自家的小孩,随后收回话题,表示这显然不是当天的主题,大家必须告别这个迷人的物理话题,回到相对平淡枯燥的正题——敏捷与面向对象设计中的 SOLID 原则,以及随之而来的各种修饰定语。 ### [12:41](https://www.youtube.com/watch?v=TMuno5RZNeE&t=761s) 程序员人口几何级暴增与“永久缺乏经验”困境 Bob Martin 话锋一转,开始剖析软件行业深层的人口结构问题。他让现场观众举手示意从业时间:从业超过 1 年、5 年、10 年、15 年……每一次年限增加 5 年,举手的人数就精准地折半。他指出全球程序员人口极其庞大,若把 Visual Basic for Applications(VBA)开发者也算进去,可能逼近 1 亿人。回顾历史,他在 40 多年前入行,1970 年左右全球程序员顶多 5 万人;再往前推到 1960 年,全球甚至只有几百人,且根本算不上专职程序员,而是被迫写代码的硬件工程师,因为当时根本没人搞懂软件是什么。 从 1950 年代的寥寥数人、60 年代的数百人、70 年代的数千人,到如今的近 1 亿人,这展现了惊人的指数级增长,其翻倍周期恰好是 5 年。这一数学规律带来了一个令行业震惊且无法回避的残酷现实:市场上永远有一半的程序员从业经验不足 5 年。人们常常疑惑为什么年长的资深程序员那么少,以为老程序员都改行去养鸡了;Bob Martin 反驳道,老程序员并没有放弃编码,他们依然在写代码,只是因为当年入行的人数基数本就极少。这种长达数十年的指数曲线,直接导致软件工程界陷入了“永久缺乏经验”(perpetual inexperience)的系统性危机,直到这种增长斜率在未来几年或几十年放缓之前,一半从业者经验欠缺的现状都将一直持续。 ```mermaid flowchart TD accTitle: 程序员人口增长与经验结构模型 accDescr: 展示程序员人口每五年翻倍的指数级增长导致行业永久处于半数人缺乏经验的状态 subgraph Timeline["历史人口翻倍趋势"] A["1960年:数百人 (硬件人员兼职)"] --> B["1970年:约5万人"] B --> C["2010年代:近1亿人"] end subgraph Consequence["行业结构后果"] D["翻倍周期 = 5年"] --> E["行业永远有50%的人从业不足5年"] E --> F["系统性的永久缺乏经验 (Perpetual Inexperience)"] end Timeline --> D ``` - 1960 年:全球仅有几百名从事编程工作的人,多为硬件工程师转岗。 - 1970 年:全球程序员规模大约在 5 万人左右。 - 现今规模:全球程序员总数已逼近 1 亿人(取决于是否计入 VBA 等开发人群)。 - 人口翻倍周期:大约每 5 年翻一番,直接导致全行业约 50% 的程序员工作年限在 5 年以内。 ### [15:49](https://www.youtube.com/watch?v=TMuno5RZNeE&t=949s) 为什么写烂代码:欲速则不达与心脏外科医生的隐喻 Bob Martin 询问台下无论是老手还是新手,是否都被极度糟糕的代码拖慢过脚步,甚至被自己昨天刚写的劣质代码卡住。每个人都知道糟糕的代码会严重降低生产力,每次触碰相关模块都会被狠狠拖慢,但开发者却不断炮制烂代码,其最常见的借口便是“我们必须赶进度(had to go fast)”。对此,Bob Martin 辛辣地讽刺了其中的逻辑荒谬:“我的核心观点是,你不可能靠写垃圾代码来提速,胡乱狂奔、不顾一切把代码拼凑上线绝不可能让你真正快起来。” 他引用老一辈常挂在嘴边的话强调:要想走得快,就必须把事情做好;必须沉下心来深思熟虑、从容推进,而不是手忙脚乱地盲目冲刺。一个疯狂赶工的程序员注定会越来越慢,而一个坐下来仔细思考、敲几行代码就回头阅读并打磨重构的开发者,才是真正高速前行的人。为了让大家深刻体会这一点,他描述了一个灵魂出窍的场景:想象你正浮在手术台上方,看着一名正在为你实施开胸心脏手术的外科医生。这名医生面临着字面意义上的“生死截止日期(deadline)”,但你绝不希望他慌张毛躁、胡乱开刀;你期盼他哪怕在巨大时间压力下,也能极其冷静、沉着、一丝不苟地遵循专业规范,按部就班地索要器械、处理切口,展现出完全掌控局面的专业素养。正如 Bob Martin 所指出的: > “You don't want the surgeon acting like the typical software developer.”(你绝不希望那名外科医生表现得像个典型的软件开发人员。) ### [18:59](https://www.youtube.com/watch?v=TMuno5RZNeE&t=1139s) 劣质代码的表象与第一大死穴:僵化性(Rigidity) 那么劣质软件除了拖慢开发速度之外,究竟有哪些具体病症?第一个最直观的表现就是令人困惑。当你阅读一段代码时,如果只能皱着眉头发出困惑的吸气和“这到底在干嘛”的呻吟,那就是劣质代码。优秀的代码应该是不言自明的、甚至让人读起来觉得枯燥乏味,一眼望去就能理所当然地回应“嗯,对,毫无悬念”。而劣质代码往往一改就崩,其展现出的第一个核心架构病症就是**僵化性(Rigidity)**——即系统内部耦合极重,只要稍微改动一处,就必须跟着改动海量无关的代码以维持一致。 Bob Martin 栩栩如生地下了一剂现场猛药,生动还原了开发者遭遇僵化系统时的日常情景: 1. **预估时间时的职场默契**:老板跑来让你修一个 Bug,你扫了一眼报告,心想自己昨天才写下这段逻辑,对其位置了如指掌。但你绝不能实话实说,而是张口报价“三周”,因为团队有一条心照不宣的潜规则:绝对不能给出低于三周的排期,否则其他同事会拿着大棒冲进你的工位群殴你;老板听到三周这个保底预估便心满意足地离开了。 2. **连锁改动的噩梦循环**:你胸有成竹地打开该模块,刚准备修改,突然发现另一个模块调用了这个函数,于是打开第二处模块微调;紧接着发现改动又波及了另外几个模块,你开始在代码库中深陷“追着自己尾巴跑”的死循环。 3. **两手一摊的经典借口**:两周、三周、五周过去了,老板气急败坏地跑来质问为什么还没搞定,你只能坚称“明天一定能好”。等到终于改完时,整个系统的每个模块几乎都被翻了个底朝天。面对老板的追问,开发者便吐出了全行业通用的传世金句: > “Well, it was a lot more complicated than I thought.”(唉,这玩意儿比我原先想的要复杂得多。) 这种由于不良依赖纵横交错、各组件高度耦合导致无法进行隔离修改的代码,就是典型的僵化代码。 ### [22:46](https://www.youtube.com/watch?v=TMuno5RZNeE&t=1366s) 第二大死穴:脆弱性(Fragility)与修车隐喻 紧随僵化性之后的第二个主要病症是**脆弱性(Fragility)**。脆弱性是指软件在某处进行微小改动时,会在许多毫无关联的地方意外崩溃的倾向。开发者在 A 处做了一点微不足道的调整,结果千里之外完全不相干的 B 处突然报错。例如仅仅改动了计算计时工人工资(hourly employees)的逻辑,系统在为工会主席打印工资报表时就会莫名其妙地直接崩溃,排查到最后才发现原来是某个函数在底层悄悄设置了一个全局标志位(flag),而报表打印恰好依赖了那个标记。 为了阐明客户和管理层面对脆弱代码时的极度恐慌,Bob Martin 举了一个生活中的修车隐喻:如果你的汽车电动车窗坏了,你开去修理厂,修车工信誓旦旦表示能修;第二天你去取车,修车工得意洋洋地展示车窗已经升降自如,结果你坐进驾驶座一拧钥匙,发动机竟然打不着火了。你绝对不可能再光顾这家修理厂,只会在心里怒骂这个修车工是个彻头彻尾的白痴。然而,软件开发人员在经理和客户面前却频繁上演这种荒诞剧——修好这里却搞砸那里,这成了最令管理者和客户闻风丧胆的梦魇。 ### [24:45](https://www.youtube.com/watch?v=TMuno5RZNeE&t=1485s) 劣质代码的症状:不可动模块与无法复用的“Joe 的代码” ```mermaid flowchart TD accTitle: 软件不良依赖与劣质代码症状的传导链 accDescr: 展示自上而下的脆弱性传导以及模块难以复用导致代码腐化的过程 subgraph S1["业务层危机"] A["高层/客户发现软件频繁出错"] --> B["业务方下达禁令:任何人都不能碰该模块"] end subgraph S2["复用陷阱:Joe 的模块"] C["老板要求复用 Joe 写的类似模块"] --> D["Joe 的核心功能满足需求"] D --> E["严重耦合怪异框架与特定数据库"] E --> F["引入副作用成本过高,只能重写"] end subgraph S3["底层根本原因"] B & F --> G["耦合 (Coupling) 与不良依赖 (Dependencies)"] end ``` 当软件频繁出错时,即使完全不懂技术的客户和管理层也能敏锐地察觉到团队已经“失控”了。他们开始怀疑代码底层全是一团糟——事实证明他们的直觉完全正确。由于害怕引入新的问题,业务部门最终会做出一个荒谬却无奈的决定:“好的,任何人都不要碰那个模块;那个模块太脆弱了,每次动它都会搞垮别的东西。”Bob Martin 强调,当毫无技术背景的业务人员下令禁止开发人员修改某个核心模块时,“That is the ultimate failure of a software developer when the business who is not technically competent says you're not touching that module.” 劣质代码的第三个典型症状体现在无法复用已有的实现上。老板可能会建议你去借鉴另一个小组 Joe 去年写过的类似模块。虽然你深知 Joe 的代码质量令人不敢恭维,但碍于指令还是不得不去检视。你会发现 Joe 的模块确实实现了你需要的功能,但它同时深度绑定了某种古怪的框架或特定的数据库。想要提取核心有用的逻辑,就必须全盘接受随之而来的严重副作用;反复权衡之后,你只能无奈地选择自己重写一套。这种“优秀核心与糟糕细节死死缠绕”的状态,正是不可复用的本质根源。 - 软件僵化(Rigidity)源于模块之间存在不良依赖。 - 软件脆弱(Fragility)源于代码对数据结构产生了不合理的强依赖。 - 不可复用(Immobility)表现为想抽离有效逻辑时,被其附带的大量非必要依赖所拖累。 - 软件设计的本质,很大程度上就是在管理依赖关系——厘清代码的归属并斩断不合理的依赖方向。 --- ### [28:59](https://www.youtube.com/watch?v=TMuno5RZNeE&t=1739s) 传统调用树下的源文件依赖陷阱 ```mermaid flowchart TD accTitle: 传统过程式调用树与源文件依赖方向 accDescr: 高层模块直接单向依赖中低层细节实现,导致依赖与控制流方向一致 Top["Main 程序"] --> High["高层业务策略 (High-level Modules)"] High --> Mid["中层模块 (Mid-level Modules)"] Mid --> Low["低层实现细节 (Low-level Modules)"] Low --> Detail["极端底层变量 / 字符串缓冲等"] linkStyle 0,1,2,3 stroke:#2ecc71,stroke-width:2px; ``` 在以 1971 年前后的 C 语言为代表的传统过程式编程中,系统的架构表现为典型的调用树结构:从 `main` 函数开始向下分层发散,高层模块调用中层模块,中层模块进而调用底层模块。这种层级结构导致控制流(Flow of Control)自上而下流动。当模块 M 中的逻辑需要调用模块 N 的函数 `f` 时,在代码层面必须显式写入包含依赖,例如 C 语言中的 `#include "n.h"`、Java 中的 `import n`,或.NET 中的 `using n`。 这种机制带来了一个致命后果:源代码的物理依赖方向与控制流方向完全重合,都是自顶向下。这意味着高层模块直接依赖于低层模块,高层业务策略(High-level Policy)时刻暴露在底层实现细节的污染之下。原本在阅读核心业务逻辑时,代码中却充斥着字符串缓冲(String Buffer)等细枝末节的杂质;更为严重的是,一旦位于树底部的底层模块稍有变动,所有沿着箭头反向依赖它的中高层模块全部都需要重新编译和重新部署。 > “A change to a detail affects high level policy and that's wrong.” —— Bob Martin - 控制流方向与源码依赖方向在传统结构中绝对绑定且同向。 - 软件僵化的根源:高层策略对底层细节产生单向物理耦合。 - 软件脆弱的根源:底层细微改动顺着依赖链向上蔓延,引发连锁崩溃。 - 高层逻辑无法单独复用,正是因为它被死死捆绑在特定的底层实现上。 --- ### [34:22](https://www.youtube.com/watch?v=TMuno5RZNeE&t=2062s) 面向对象浪潮与 Objective-C 的历史巧合 随着 Java、C#、Ruby(以及 Rails 框架)、Python 等语言成为主流,面向对象编程(OOP)几乎成为了现代主流编程环境的标配。回顾历史,各语言的发展轨迹充满了历史的偶然性。以苹果生态的核心语言 Objective-C 为例,它诞生于 1980 年左右,由 Brad Cox 发明。当时 Cox 作为一名 Smalltalk 程序员,因被迫在 C 语言下开发而感到极其痛苦,于是为 C 编写了一个具有 Smalltalk 特性的预处理器,并创办了 Stepstone 公司进行推广。 在 1980 到 1985 年间,Objective-C 曾是 C 程序员涉足面向对象领域的唯一主要途径。然而 1985 年 Bjarne Stroustrup 出版了经典的《The C++ Programming Language》,其语法极其贴近原生的 Kernighan & Ritchie C,吸引了大量 C 程序员倒戈转向 C++,导致 Stepstone 最终倒闭。Objective-C 之所以没有就此湮灭,纯粹是因为苹果公司历史上的一系列人事震荡: - 苹果引入前百事可乐 COO 约翰·斯考利(John Scully),但管理层政治斗争导致史蒂夫·乔布斯(Steve Jobs)被逐出苹果。 - 乔布斯拿着大量资金创立了 NeXT 公司,开发名为 NextStep 的操作系统,并顺势招募了因 Objective-C 市场凋零而失业的开发者。 - 苹果后期陷入经营困境,被迫请回乔布斯,乔布斯带着 NeXT 团队及 Objective-C 技术重返苹果。 - 这一团队随后开发了 iPod 及 iPhone 系列,使这门语法奇特且充满年代感的语言意外成为支撑移动生态的核心力量。 --- ### [37:45](https://www.youtube.com/watch?v=TMuno5RZNeE&t=2265s) 破除迷信:面向对象的“三大特性”真是核心吗? 在探讨所有语言为何纷纷转向面向对象时,台下听众给出了教科书式的答案:“封装(Encapsulation)、继承(Inheritance)与多态(Polymorphism)”。然而 Bob Martin 随即对此提出了质疑。如果深入对比历史,传统 C 语言在面向对象概念普及之前,早就完全具备了强大的数据封装能力。面向对象范式之所以能够彻底重塑软件工程,并非仅仅依赖于所谓的“面向对象三大支柱”,其真正重大的价值在于为打破源文件依赖与控制流的绝对捆绑提供了关键手段。 ### [38:52](https://www.youtube.com/watch?v=TMuno5RZNeE&t=2332s) 封装的迷局:C 语言的完美封装与面向对象的倒退 Bob Martin 尖锐地反驳了“面向对象带来了封装”这一传统观点。他指出,真正的完美封装其实早在 C 语言中就已经实现了:程序员只需在头文件(`.h`)中对函数与数据结构进行前向声明(forward declaration),而无需包含具体实现。用户通过 `#include` 引入头文件后,除了函数签名和结构体名称外,根本无法窥探任何内部实现细节,更不可能直接访问结构体内部的成员变量。这种机制构成了无懈可击的物理隔离。 然而,C++ 的诞生却彻底打破了这一完美状态。为了满足编译期的内存布局计算等需求,C++ 不得不把成员变量一股脑写进头文件里,导致私有变量对所有调用方完全可见。为了弥补这个漏洞,语言设计者发明了 `public`、`private` 和 `protected` 这类访问修饰符。Bob Martin 毫不客气地称这些修饰符为“糟糕的打补丁手段”(Band-Aid),认为它们只是在试图用编译器的访问权限检查去掩盖封装已被破坏的事实。 针对市面上广泛流传的 OO 神话,Bob Martin 做出了严厉的定性批评: > “When people tell you that OO is an encapsulated language, they are wrong.” - C 语言依靠头文件前向声明实现了对数据结构成员的完全隐藏,提供了绝对完美的封装。 - C++ 强制在头文件中暴露成员变量,破坏了原本的物理隔离,不得不发明 `public`、`private`、`protected` 等关键字进行人工限制。 - 每一个现代面向对象语言实际上都削弱了过去在 C 中所具备的封装完整性。 --- ### [40:50](https://www.youtube.com/watch?v=TMuno5RZNeE&t=2450s) 继承的演变与 Java 接口的“偷懒黑客手段” 谈到继承,Bob Martin 表示在 C 语言中也可以通过技巧模拟出继承:利用 `union` 联合体,或者设计两个拥有完全相同头部字段、仅在末尾字段有所差异的结构体,然后通过指针类型转换(pointer casting)在函数间传递,达到类似多态对象的效果。C++ 让继承变得更加直观方便,但同时也引入了极其棘手的“致命多重继承菱形问题”(Deadly Diamond of Death)。 很多语言早已解决了菱形继承的歧义问题,但当 Java 诞生时,它却选择完全剔除多重类继承。Bob Martin 认为,Java 团队并非技术上无法解决,而是单纯为了简化编译器实现而偷懒。为了填补功能空白,他们发明了所谓的 `interface`(接口)。Bob Martin 调侃大家现在将接口奉若神明,但实际上接口无非就是一个全由抽象方法构成的抽象类;Java 接口唯一的“特殊之处”,就在于语言强行限制了类只能继承一个父类,却允许实现多个接口。 在与台下 Ruby 开发者的互动中,Bob Martin 进一步厘清了动态类型与静态类型语言中继承与多态的关系。在 Ruby、Python 等动态类型语言中,依靠鸭子类型(Duck Typing),多态完全不需要借助继承来实现,继承主要用于代码与状态的复用;而在 Java、.NET 和 C++ 等静态类型语言中,开发者必须被迫使用继承来达成多态,这恰恰是静态类型语言的一大软肋。 > “An interface is nothing but an abstract class with a bunch of abstract methods in it, right? It's not some special thing.” - C 语言通过对齐数据结构头部字段和指针强制类型转换,已能手工实现继承与多态模拟。 - 致命多重继承菱形问题(Diamond Problem)在类继承图谱中产生语义歧义,许多语言此前已有解法。 - Java 之所以采用 `interface` 替代多重类继承,在 Bob Martin 看来是一种为了减轻编译器编写难度的偷懒做法(hacky workaround)。 - 动态类型语言无需继承即可拥有免费的多态;静态类型语言则必须通过继承(类或接口)来获得多态。 --- ### [44:27](https://www.youtube.com/watch?v=TMuno5RZNeE&t=2667s) 多态的真谛:从 Unix IO 表到依赖反转的力量 为了阐释多态的本质,Bob Martin 在白板上写下了经典的 Unix 极简文件拷贝程序(利用 `getchar()` 和 `putchar()` 的 `while` 循环)。该程序看似只是从标准输入复制到标准输出,但标准输入可以绑定键盘、文件或管道,标准输出也可以是终端或网络流。`getchar` 和 `putchar` 本质上就是对名为 `File` 的基类所发起的多态方法调用。 在底层,Unix 的这一多态机制是通过 IO 设备驱动表实现的:每个驱动都必须统一实现五个具有相同签名的基本函数——`open`、`close`、`read`、`write`、`seek`。操作系统利用函数指针将它们填入表格中,调用时直接根据槽位派发。Bob Martin 指出,这张驱动函数指针表在本质上就是 C++ 编译器所生成的虚函数表(vtable)。在 C 语言时代,手动维护函数指针表极其危险易错,而面向对象语言最大的贡献,就是把这种基于函数指针的多态调度变成了语言级别的廉价、安全且内建的特性。 ```mermaid flowchart LR accTitle: 多态反转编译期依赖示例 accDescr: 展示模块 M 调用接口 I,而模块 N 实现该接口,使依赖箭头反转 subgraph CallerSide ["调用方边界"] M["Module M"] I["<<interface>>\nFunction F()"] end subgraph CalleeSide ["实现方边界"] N["Module N\nImplements F()"] end M -->|"运行时调用\nControl Flow"| I M -->|"源码依赖\nCompile Dependency"| I N -.->|"实现/指向接口\nInverted Dependency"| I ``` 随后,Bob Martin 揭示了面向对象的终极力量所在:依赖反转(Dependency Inversion)。在传统结构化编程中,控制流如果从模块 M 走向模块 N,编译期的源代码依赖方向也必然是从 M 指向 N;但一旦引入了多态与接口,模块 M 仅依赖抽象接口,而模块 N 则实现该接口。此时,运行时控制流依然是从 M 到 N,但编译期依赖却完全反转了过来。这种随时调转依赖箭头的绝对掌控力,才是解决代码脆弱(fragile)、僵化(rigid)和不可复用(non-reusable)的终极武器。 > “Polymorphism gives you the ability to create one module calling another and yet have the compile time dependency point against the flow of control instead of with the flow of control.” - Unix 设备的五个核心操作函数为:`open`、`close`、`read`、`write`、`seek`;驱动表是 C++ vtable(虚方法表)的原型。 - C 语言极少全盘使用函数指针是因为缺乏编译器安全保障,指针悬挂和错位极其危险;面向对象将多态调用变得安全开箱即用。 - 拥有多态就无需手动操作函数指针,Java、C#(除 Delegate 包装外)以及 Ruby 等语言因此排除了裸函数指针概念。 - Bob Martin 斥责所谓“面向对象是给真实世界建模”、“面向对象更贴近人类思维方式”等说法完全是销售人员忽悠不懂代码的高管的营销话术(marketing nonsense)。 - 面向对象的本质核心在于:**通过多态实现依赖反转,让软件架构师能够自由掌控和隔离系统模块间的源代码依赖关系**。 ### [51:36](https://www.youtube.com/watch?v=TMuno5RZNeE&t=3096s) SOLID 原则的提出与单一职责原则(SRP)的本质 Bob Martin(Uncle Bob)指出,在系统架构中控制关键依赖关系,能够有效消除软件开发中常见的“僵化性”(rigidity)、“脆弱性”(fragility)和“不可复用性”(non-reusability)。利用面向对象的多态特性,衍生出了一套核心设计原则,统称为 SOLID 原则。Uncle Bob 风趣地透露,这个缩写并不是他最初刻意设计的,而是几年前有人提醒他这些原则的首字母拼在一起恰好是“SOLID”(坚固),他觉得用这个词来统称这些原则非常棒,便欣然采纳并迅速推广。 ```mermaid flowchart TD accTitle: 单一职责原则中利益相关者与方法的映射关系 accDescr: 展示 Employee 类中的三个方法分别对应不同高管层与变更原因,进而解释单一职责原则。 subgraph Executives["利益相关者 (高管/角色)"] CFO["CFO (财务总监)"] COO["COO (运营总监)"] CTO["CTO (技术总监)"] end subgraph EmployeeClass["Employee 胖类 (违背 SRP)"] M1["calcPay()"] M2["reportHours()"] M3["writeEmployee()"] end CFO -->|"关注薪资核算与账目"| M1 COO -->|"关注工时报表与审计"| M2 CTO -->|"关注数据库模式与数据完整性"| M3 ``` SOLID 中的第一个原则是单一职责原则(Single Responsibility Principle,简称 SRP)。Uncle Bob 强调,许多人对“职责”(responsibility)一词存在严重误解,以为它指的是功能或“这个类具体做了什么”。然而在 SRP 中,“职责”的真正含义是“变更的来源”——即一个类应该只有一个发生变更的理由。正如 Bob Martin 所明确指出的:“A class should have only one reason to change. It should have a single responsibility to change.” ### [53:06](https://www.youtube.com/watch?v=TMuno5RZNeE&t=3186s) 经典反例:混杂三层高管关切的 Employee 类 为了深入剖析单一职责原则,Uncle Bob 在屏幕上展示了一个典型的反模式范例:一个名为 `Employee` 的类,其中包含三个方法:计算薪资的 `calcPay()`、生成工时报表的 `reportHours()` 以及将员工持久化保存到数据库的 `writeEmployee()`。他向听众提问:这个类到底有几个发生变更的理由?会受到多少不同来源的变更请求影响? 为了让问题更加生动且残酷,Uncle Bob 设想了极端事故场景:如果 `calcPay()` 计算错误,导致公司损失了一百万美元,哪位 C 级别(C-level)高管会最先发现?答案是首席财务官(CFO),因为他一直在紧盯账目和资产负债表,紧接着 CFO 就会冲进开发办公室把程序员解雇。如果是 `reportHours()` 出现 bug 导致百万美元损失,首席运营官(COO)会最先知晓,因为 COO 管辖着负责核查工时与支出报表的审计人员。而如果是 `writeEmployee()` 代码崩溃毁坏了价值百万的数据资产,第一个察觉的则是首席技术官(CTO)。这意味着,这个单一的类同时背负着对三位不同高管、三个独立组织部门的职责;来自 DBA 的数据库模式调整(归属于 CTO)、来自审计员的报表格式变更(归属于 COO)以及财务人员提出的算薪规则改动(归属于 CFO),都会强制修改这同一个类。 - **三位高管与方法映射**:`calcPay()` 对应 CFO(业务与账目),`reportHours()` 对应 COO(审计与营运),`writeEmployee()` 对应 CTO(技术与数据基础设施)。 - **连锁破坏风险**:当开发人员为了满足 COO 部门的需求而修改 `reportHours()` 时,很可能会意外破坏 CFO 依赖的 `calcPay()` 逻辑。 ### [56:58](https://www.youtube.com/watch?v=TMuno5RZNeE&t=3418s) 拆分职责的多种实现方式与关注点分离 面向对象的初学者常常会反驳:“面向对象不就是把对象的数据和操作它的方法封装在一起吗?”Uncle Bob 幽默地回应道,这固然没错,但更重要的是保护你自己不被那些愤怒的高管炒鱿鱼。为了避免修改报表格式意外搞砸算薪系统,必须将这三个方法从单一类中剥离出去,分散到各自独立的结构中。 解决这种职责耦合有许多种架构手段。一种常见做法是将 `calcPay()` 留在核心业务模型中,另外创建专门的报表生成类(Report Writer)和持久化仓库类(Employee Repository),让它们与业务对象交互;但 Uncle Bob 指出,如果直接依赖具体类,一旦接口变动依然会导致重新编译和重新部署。更优雅的方式包括定义接口、采用抽象基类,或者将三个职责分别放入三个独立类中,外层通过外观模式(Facade)对外提供统一委托调用。无论采用哪种具体技术手段,核心原则都是绝不动摇的。Uncle Bob 强调:“Formatting should go in one place, calculation should go in another, database in another, and if you've got some other concern, put it in another. Don't mix concerns in your classes.” - **报表格式变更现实场景**:审计人员经常提出看似无关紧要的格式修改(例如将带小数点的工时表示法改成不带小数点的整数+十分位数字结构);如果在同一个类中修改,一个去小数点的改动就可能破坏关键的薪资计算逻辑。 - **关注点分离原则**:将数据格式化、商业规则计算、持久化操作及其他关切点彻底隔离在不同模块中。 ### [01:00:02](https://www.youtube.com/watch?v=TMuno5RZNeE&t=3602s) 开闭原则(OCP)与通过多态实现的无损扩展 随后,Uncle Bob 转入 SOLID 中的第二个原则——开闭原则(Open-Closed Principle,简称 OCP)。他介绍说,该原则最初由 Bertrand Meyer 在 1988 年出版的经典著作《面向对象软件构造》(Object-Oriented Software Construction)中提出。Uncle Bob 还顺带打趣道,建议大家去找该书的第一版来读,因为第二版厚达上千页,“根本没人读得完”。 开闭原则的定义极为精炼,却常被初学者视作悖论:“A class or a module should be open for extension but closed for modification.” 很多人会困惑,如果不去修改一个模块的代码,到底怎么改变它的行为?Uncle Bob 举了之前讲过的 `copy` 程序的例子:当需要支持从光学字符识别器(OCR)读取字符时,开发人员只需新写一个 OCR 的 I/O 驱动并传入即可。在整个过程中,原始的 `copy` 程序无需更改任何一行代码,也完全无需重新编译;通过内部的多态调用,系统在没有对旧模块进行任何修改的前提下实现了行为的扩充。 ```mermaid flowchart LR accTitle: 开闭原则下的多态扩展机制 accDescr: 展示 copy 核心程序保持不变,通过扩展具体驱动实现功能扩展。 subgraph CoreModule["已编译核心 (对修改关闭)"] CopyProg["copy 程序逻辑"] ReaderInterface["<<interface>>\nReader"] end subgraph Extensions["新增扩展代码 (对扩展开放)"] DiskDriver["Disk 驱动"] OCRDriver["OCR 驱动 (新特性)"] end CopyProg --> ReaderInterface DiskDriver -.->|实现| ReaderInterface OCRDriver -.->|实现| ReaderInterface ``` 为了让大家看清违背 OCP 的危害,Uncle Bob 开始在幻灯片上展示一个用类似 C 语言编写的反面代码案例。在这个设计失败的版本中,头文件 `shape.h` 内定义了一个枚举类型 `enum`,包含 `Circle`(圆形)和 `Square`(矩形)两个枚举值,并定义了包含该枚举字段的 `Shape` 结构体,以及通过伪继承(Fake Inheritance)将开头字段与 `Shape` 对齐的 `Circle` 结构体,为后续展示传统 `switch/case` 带来的维护梦魇埋下了伏笔。 - **OCP 核心主旨**:新增应用功能时,应当仅仅通过编写新代码来实现,而无需改动任何既有的旧代码。 - **C 语言反例要素**:在数据结构中显式包含类型枚举标签(type tag),通过硬编码的类型判断处理不同图形,完全破坏了模块对修改的关闭性。 ### [01:03:33](https://www.youtube.com/watch?v=TMuno5RZNeE&t=3813s) 过程式代码违反开闭原则(OCP)及其带来的病态症状 Bob Martin(Uncle Bob)继续用代码示例阐释违反开闭原则(Open-Closed Principle, OCP)的严重后果。在前面的代码结构中,`shape.h` 头文件定义了包含形状类型的枚举(`enum`),`circle.c` 中定义了带有半径、中心点以及 `drawCircle()` 函数的 `Circle` 结构体;`square.c` 中定义了带有边长、左上角坐标以及 `drawSquare()` 函数的 `Square` 结构体。随后他展示了核心函数 `drawAllShapes`:它接收一个形状指针数组,循环遍历每个形状,用 `switch` 语句检查其类型;如果是正方形就调用 `drawSquare()`,如果是圆形就调用 `drawCircle()`。 ```mermaid flowchart TD accTitle: 过程式形状绘制的紧耦合结构 accDescr: 展示了 drawAllShapes 如何通过 switch 语句直接依赖具体的形状和公共头文件,导致编译依赖蔓延。 subgraph Core["核心派发逻辑"] DAS["drawAllShapes(Shape*[])"] SW{"switch (shape->type)"} DAS --> SW end subgraph Header["共享定义"] SH["shape.h\n(enum ShapeType)"] end subgraph Concrete["具体形状实现"] CIR["circle.c / drawCircle()"] SQU["square.c / drawSquare()"] end SW -->|"case CIRCLE"| CIR SW -->|"case SQUARE"| SQU CIR -.->|"#include"| SH SQU -.->|"#include"| SH DAS -.->|"#include"| SH ``` Bob Martin 提问:如果此时需要增加一个新形状——椭圆(Oval),代码会发生什么坏事?台下听众指出需要增加一个 `case` 分支,但 Bob 强调这甚至不是第一步。“第一行需要修改的代码在 `shape.h` 的枚举里,”他解释道。一旦在头文件中修改了枚举,灾难便随之发生:所有包含(`#include`)了 `shape.h` 的文件都必须重新编译——`Circle` 要重编,`Square` 也要重编。Bob Martin 极其尖锐地批评道:“Circle recompiles because I added an oval. Square recompiles because I added an oval. That’s fundamentally sick, right?”(圆因为我加了椭圆而重新编译,正方形也因为我加了椭圆而重新编译。这从根本上就是病态的,对吧?)圆和正方形根本不在乎椭圆的存在,却被迫重新编译,这就是架构“僵化性”(Rigidity)的典型症状:由于一次局部变更,导致大量本不该重新编译的代码被迫重新编译。 - **僵化性(Rigidity)**:增加一个新类型会导致基础头文件发生变更,触发全局范围的连锁重编译。 - **脆弱性(Fragility)**:不仅仅是这一个 `switch` 语句需要修改。Bob Martin 打了个生动的比方:“Switch statements are like the Sith — always more than one there are.”(`switch` 语句就像《星球大战》里的西斯,绝不可能只有一个,永远成双成对出现)。系统中会不断复制、蔓延类似的结构:`eraseAllShapes`、`dragAllShapes`、`scaleAllShapes`、`rotateAllShapes` 等。有的程序员甚至因为偏好写成复杂的 `if-else`。更危险的是程序员会做“逻辑优化”——例如旋转圆形(Rotate Circle)根本什么都不需要做,程序员就会把圆形的 `case` 分支直接删掉。当添加椭圆时,你必须人肉翻遍整个系统找出所有的分支,一旦遗漏或逻辑推断错误,系统就会崩溃。 - **牢固性 / 不可移动性(Immobility)**:Bob 设想了一个极其逼真的初创公司商业情境:老板走过来说公司资金快烧光了,原有的销售模式行不通,高薪聘请的咨询顾问给出一个新商业模式——将通用的核心组件 `drawAllShapes` 打包成免费的 jar 包或 DLL 放在官网上吸引流量,然后将具体的 `Circle`、`Square`、`Oval` 等各打包成独立的动态链接库,以每个 5 美元的价格单独售卖。然而工程师只能绝望地告诉老板:“我们做不到。”因为 `drawAllShapes` 的 `switch` 语句硬编码依赖了所有具体形状,根本无法独立拆分部署。架构无法支持商业诉求,最终公司倒闭,大家各奔前程。这就是组件未与具体实现解耦所导致的典型牢固性。 ### [01:08:33](https://www.youtube.com/watch?v=TMuno5RZNeE&t=4113s) 面向对象解法与所谓的“童话谎言” 为了解决过程式代码的上述三大恶疾,Bob Martin 给出了经典的面向对象(OO)解决方案。在引入这个方案前,他戏谑地让大家想象背景中响起了轻柔舒缓的音乐、微风拂过树梢、小兔子在山坡上蹦跳的童话场景——因为这就是 OO 宣称能解决一切的“谎言”。在 C++ 示例中,定义了一个拥有虚函数 `draw()` 的基类 `Shape`;`Square` 和 `Circle` 分别派生自 `Shape` 并覆写自己的 `draw()` 函数。此时,`drawAllShapes` 的实现变得极其优雅纯粹:它只需要遍历形状数组,直接多态调用每个对象的 `draw()` 即可。 ```mermaid flowchart LR accTitle: 面向对象的动态多态绘制结构 accDescr: drawAllShapes 仅依赖于抽象基类 Shape,具体子类实现 draw 方法,实现依赖反转。 subgraph HighLevel["高层策略 (独立打包)"] DAS["drawAllShapes()"] ABS["class Shape\n+draw() = 0"] DAS --> ABS end subgraph LowLevel["低层细节 (可按插件分发)"] SQU["class Square\n+draw()"] CIR["class Circle\n+draw()"] ABS <|-- SQU ABS <|-- CIR end ``` 此时再分析系统面对变更的表现: 1. **彻底消除僵化性**:新增一个 `Oval` 派生类,现有的 `Shape`、`Square`、`Circle` 以及 `drawAllShapes` 统统不需要重新编译。 2. **彻底消除脆弱性**:面向对象的强制契约要求派生类必须实现所有纯虚函数。开发者不会遗漏方法,即使旋转圆形什么都不做,也必须提供一个空的实现体 `{}`,再也不用满世界去搜寻散落的 `switch` 分支。 3. **支持独立部署**:高层策略(`drawAllShapes` 和 `Shape`)对具体的正方形、圆形一无所知。控制流在运行时依然能够调用到具体的 `draw` 函数,但在编译期,源码依赖完全被反转。高层可以打包成免费库,各种形状可以打包成独立的 DLL 作为插件单独分发。 然而,Bob 话锋一转,指出这套看似完美的 OO 设计实际上是一个“谎言”: > “In order for OO design to protect you from the customer, you must know what the customer is going to do, and customers always do the other thing.” —— Bob Martin 这套面向对象抽象唯一能防范的变更,是“增加新形状”。但现实中的客户往往对增加新形状毫无兴趣;客户提出的新需求往往是:“我们希望先绘制所有的正方形,然后再绘制所有的圆形,让圆形完全覆盖在正方形之上(调整渲染层级)。”如果提前知道这个需求,系统或许可以设计出防范渲染顺序变更的抽象,但开发人员在设计之初只是一厢情愿地认为“从 `Shape` 继承正方形和圆形是完美的现实世界建模”。 Bob Martin 强调,开发者绝不能盲目以为现实世界的分类学能够天然对抗变更。客户有一种近乎神奇的能力,总能精准提出彻底击穿你现有架构设计的新需求。务实的做法不是过度预先设计(Big Design Up Front),而是实现极其简单的代码,以最快速度交付到客户手中,并主动请求:“求求你们提出修改、给我们新需求吧!”通过客户的真实反馈,摸清应用系统中真正的“变化轴”(Axis of Change),再有针对性地构建防御性抽象。 ### [01:13:50](https://www.youtube.com/watch?v=TMuno5RZNeE&t=4430s) 里氏替换原则(LSP)与经典的矩形-正方形困境 在阐明 OCP 的真实边界后,Bob Martin 转向 SOLID 原则中的下一个核心原则——由 Barbara Liskov 于 1988 年提出的里氏替换原则(Liskov Substitution Principle, LSP)。Bob 赞叹 1988 年是软件工程原则的大年。虽然 Barbara Liskov 最初是用严密的数学公式来定义的,但 Bob 将其通俗地意译为工程语言:派生类必须能够通过基类接口使用,而使用者无需知晓其中的差异。 > “Derived classes must be usable through the base class interface without the need for the user to know the difference.” —— Bob Martin 为了展示看似符合常理的继承是如何轻易违背 LSP 的,Bob Martin 引出了面向对象领域最著名的噩梦案例——“矩形与正方形问题”(The dreaded rectangle-square problem)。假设系统中很久以前就存在一个基础的 `Rectangle`(矩形)类,它拥有两个实数类型的字段 `height`(高)和 `width`(宽),以及对应的 `setHeight(double)` 和 `setWidth(double)` 函数。此时,业务提出了一个看似再自然不过的新需求:系统中需要引入正方形。在几何学常识中,“正方形是一种矩形”(A Square IS-A Rectangle),一场关于继承滥用与子类型契约破坏的经典陷阱由此展开。 ### [01:15:05](https://www.youtube.com/watch?v=TMuno5RZNeE&t=4505s) 正方形与长方形的继承困境 当系统出现需要支持“正方形”(Square)的新需求时,直觉和几何学常识都会告诉我们:正方形显然是一种长方形(Rectangle),因此让 `Square` 继承自 `Rectangle` 似乎理所当然。然而,在对象模型中这一假设立刻引发了矛盾。长方形需要两个字段来维护状态——高(height)和宽(width);而正方形在几何上只需要一个字段(边长)。一旦继承,正方形必然继承两个字段,产生冗余。 很多开发者的第一反应是:“内存很便宜,浪费就浪费吧。”为了保证正方形的几何特性,开发者会选择重写(override)方法:重写 `setWidth`,使其同时修改宽和高;再重写 `setHeight`,同样同时修改宽和高。表面上看,这似乎完美抹平了差异,但灾难随之潜伏在调用方代码中。 ```mermaid flowchart TD accTitle: 正方形继承长方形导致状态破坏流程 accDescr: 展示调用者调用长方形方法传入正方形时引发内存崩溃与违背OCP的连锁反应 subgraph Problem["继承与重写引发的连锁反应"] A["客户端持有 Rectangle 引用"] -->|"实际传入 Square"| B["调用 setHeight(h)"] B --> C["Square 重写逻辑触发:宽与高同时改变"] C --> D["破坏调用方的内部有限状态机"] D --> E["十亿条指令后触发 NullPointerException / Heap 损坏"] end subgraph FixFailure["错误排查与设计退化"] E --> F["排查数周,加入防御代码:if (r instanceof Square)"] F --> G["引入对具体类 Square 的直接依赖"] G --> H["破坏开闭原则 (OCP),导致系统僵化与脆弱"] end ``` - 几何概念上的长方形具有两个独立自由度(高与宽),而正方形仅有一个自由度。 - 试图通过在子类重写赋值方法来维护约束,破坏了基类使用者对状态独立性的根本预期。 --- ### [01:16:12](https://www.youtube.com/watch?v=TMuno5RZNeE&t=4572s) 破坏使用者契约与违背开闭原则 假设某个模块正在操作一个 `Rectangle` 对象并调用了 `setHeight`。作为长方形的调用者,它完全有权预期调用此方法后对象的宽度保持不变。然而,如果运行时传入的是一个 `Square` 实例,调用 `setHeight` 就会连带改变宽度。这会彻底破坏调用模块内部维护的有限状态机(finite state machine),进而搞垮堆内存(heap)。更要命的是,这种逻辑崩溃往往不会立即显现,而是在执行了数亿甚至十亿条指令后,才以一个莫名其妙的空指针异常(`NullPointerException`)爆发出来。 写出崩溃代码的程序员不得不搬出逻辑分析工具,在数以亿计的指令调用栈中痛苦排查数周,最终发现根源居然是“有人传进了一个正方形”。为了自保,开发者被迫在长方形的处理模块中加入防御代码:检查传进来的对象是否是正方形(在 Java 中使用 `instanceof`,在.NET 中使用 `is` 或 `as`)。这种做法瞬间让本来只该了解长方形的模块产生了对正方形具体类型的依赖,直接践踏了开闭原则(Open-Closed Principle, OCP),令系统变得僵化(rigid)且脆弱(fragile)。 Bob Martin 指出,并非每次做类型检查都是错误,但绝大多数时候它都预示着坏味道:“It is not true that every time that you do that it's a problem but most of the time when you do that it is a problem most of the time and this particular interesting relationship causes that to happen.” - 在类型系统中强制伪造几何直觉,会导致隐藏的副作用与极其昂贵的排错成本。 - 通过 `instanceof`、`is` 或类型强转来规避子类副作用,是典型的设计退化标志。 --- ### [01:18:22](https://www.youtube.com/watch?v=TMuno5RZNeE&t=4702s) 代表事物的代码并不具有事物本身的真实关系 针对“正方形难道不是长方形吗”这一执念,Bob Martin 给出了深刻的哲学剖析:真实世界中的正方形确实是长方形,但代码里的 `Square` 类并不是一个正方形,它只是一段代码;`Rectangle` 类也不是长方形,它同样只是一段代码。它们只是各自真实事物的“代表”(representatives),而代表事物的代码绝不会自动继承被代表事物之间的关系。 为了生动阐述这一点,他打了一个极具讽刺意味的比方:如果一对夫妻正在打离婚官司,各自请了一位律师代表自己,这两位律师并不会因为代理了离婚案件而自己也去办离婚手续。Bob Martin 强调:“The things that represent do not share the relationships of the things they represent.” 程序员绝不能仅仅因为类名借用了某个现实概念,就草率地使用继承。 随后,他调侃了面向对象中常说的“Is-A”(是一个)关系的荒谬历史起源。上世纪 80 年代,人工智能学者们研究“知识网络”(knowledge nets)等数据结构,发明了“Has-A”(有一个)、“Tastes-Like-A”(尝起来像)、“Smells-Like-A”(闻起来像)以及“Is-A”等关系。随着那轮人工智能因无法落地、经费耗尽导致研究人员纷纷失业,这批学者转行成为了面向对象程序员,把这套缩写术语带进了面向对象领域。Bob Martin 直言不讳地揭穿道:“The inheritance relationship is not the is-a relationship. The inheritance relationship really is just the redeclaration of functions and variables in a subscope.” - 现实世界的“概念归属”不等于面向对象体系中的“子类型多态”。 - 继承的本质仅仅是在一个子作用域中对函数和变量进行重新声明,而非现实世界的同义映射。 --- ### [01:20:30](https://www.youtube.com/watch?v=TMuno5RZNeE&t=4830s) 里氏替换原则(LSP)与浮点数比较陷阱 正方形与长方形之间究竟应该是什么关系?它们甚至不应该有直接的父子继承关系。长方形与正方形不仅变量数目不同,行为约束也截然不同;如果非要关联,它们至多共同继承一个更高阶的抽象基类(例如 `Shape`)。把长方形作为正方形的父类,是对里氏替换原则(Liskov Substitution Principle, LSP)的典型违背。Bob Martin 总结道:“This is a violation of the Liskov Substitution Principle because square is not substitutable for rectangle.” 只要违反了替换规则,开发者迟早会被逼着去写 `if` 语句检查对象类型以防系统崩溃。 现场有观众提议:能否不单独建立 `Square` 类,而是在 `Rectangle` 中提供一个 `isRegular()` 方法来判断它是否是“正规格”(即正方形)?Bob Martin 顺着这个思路展开推演:如果在一个正规格长方形上调用 `setHeight`,系统是否该偷偷修改宽?还是把 `isRegular` 标记置为 false?如果改为在运行时通过判断宽与高是否相等来动态计算 `isRegular()`,又会踩进另一个致命陷阱——长方形的高和宽通常是实数(浮点数)。 Bob Martin 幽默地警告道:在计算机中永远不能直接拿 `==` 去比较两个浮点数是否相等,“People go to jail for doing that by the way, especially if the real number represents money.” 浮点数可能大于或小于,但在底层表示上几乎无法判定绝对相等;用等号比较得出的相等结论往往只是假象,唯一可行的只能是带容差(epsilon)的近似比较。在解答完该问题后,时间来到约晚上 8 点,Bob Martin 结束了全场演讲,感谢大家的聆听并留下来解答现场观众的个别提问。 - 里氏替换原则的核心是“行为可替换性”,而非名称或几何属性的逻辑相容。 - 采用动态状态判断替代子类设计时,必须格外警惕浮点数精度对逻辑等值判断造成的破坏。 ## Notable quotes > [51:36](https://www.youtube.com/watch?v=TMuno5RZNeE&t=3096s) A class should have only one reason to change. It should have a single responsibility to change. — Bob Martin > [01:00:02](https://www.youtube.com/watch?v=TMuno5RZNeE&t=3602s) A class or a module should be open for extension but closed for modification. — Bob Martin > [01:13:50](https://www.youtube.com/watch?v=TMuno5RZNeE&t=4430s) Derived classes must be usable through the base class interface without the need for the user to know the difference. — Bob Martin > [24:45](https://www.youtube.com/watch?v=TMuno5RZNeE&t=1485s) That is the ultimate failure of a software developer when the business who is not technically competent says you're not touching that module. — Bob Martin > [01:18:22](https://www.youtube.com/watch?v=TMuno5RZNeE&t=4702s) The things that represent do not share the relationships of the things they represent. — Bob Martin > [44:27](https://www.youtube.com/watch?v=TMuno5RZNeE&t=2667s) Polymorphism gives you the ability to create one module calling another and yet have the compile time dependency point against the flow of control instead of with the flow of control. — Bob Martin > [01:18:22](https://www.youtube.com/watch?v=TMuno5RZNeE&t=4702s) The inheritance relationship is not the is-a relationship. The inheritance relationship really is just the redeclaration of functions and variables in a subscope. — Bob Martin > [28:59](https://www.youtube.com/watch?v=TMuno5RZNeE&t=1739s) A change to a detail affects high level policy and that's wrong. — Bob Martin ## Mentioned Yale School of Management, Kyle Jensen, Robert C. Martin, SOLID, Daniel Bodily, Tech Bootcamp, HackYale, Y/Hack, Continuity Control, New Haven IO, Dan Bergh, Agile Manifesto, Pauli Exclusion Principle, Visual Basic for Applications (VBA), Brad Cox, Stepstone, Smalltalk, Bjarne Stroustrup, The C++ Programming Language, John Scully, Steve Jobs, NeXT, NextStep, Objective-C, Bertrand Meyer, Object-Oriented Software Construction, Barbara Liskov [Source](https://www.youtube.com/watch?v=TMuno5RZNeE)

Source quotes

[51:36] Bob Martin: A class should have only one reason to change. It should have a single responsibility to change. [60:02] Bob Martin: A class or a module should be open for extension but closed for modification. [73:50] Bob Martin: Derived classes must be usable through the base class interface without the need for the user to know the difference. [24:45] Bob Martin: That is the ultimate failure of a software developer when the business who is not technically competent says you're not touching that module. [78:22] Bob Martin: The things that represent do not share the relationships of the things they represent. [44:27] Bob Martin: Polymorphism gives you the ability to create one module calling another and yet have the compile time dependency point against the flow of control instead of with the flow of control. [78:22] Bob Martin: The inheritance relationship is not the is-a relationship. The inheritance relationship really is just the redeclaration of functions and variables in a subscope. [28:59] Bob Martin: A change to a detail affects high level policy and that's wrong.

Source quotes

[15:52] Bob Martin: My message to you today is you don't go fast by writing crap. [52:27] Robert C. Martin: A class should have only one reason to change. [78:22] Robert C. Martin: The things that represent do not share the relationships of the things they represent. [22:46] Bob Martin: Fragility is the tendency of the code to break in many places even when you only change it in one place. [38:52] Bob Martin: Every object language out there weakens the encapsulation that we used to have so when people tell you that OO is an encapsulated language they are wrong. [24:45] Robert C. Martin: The desirable parts of the code are so horribly coupled to the undesirable parts of the code that you cannot use the desirable parts of the code somewhere else.

Original transcript

[0:06] good evening my name is Kyle Jensen I'm [0:09] on the faculty here at the El School of [0:11] Management and I'm also the director of [0:12] entrepreneurial programs here uh our uh [0:16] guest tonight is Robert Martin uh who's [0:18] going to talk about solid principles of [0:20] object-oriented and agile design and we [0:22] are very lucky to have him here uh it [0:24] comes at a he's here at a great time for [0:26] Yale um as you may know the university [0:29] is investing a great deal and [0:30] Entrepreneurship and that is creating I [0:33] think um or mixing with the University's [0:36] traditional uh expertise in let's say [0:39] computer science and the theory side and [0:40] producing all sorts of great things on [0:42] campus so for example uh two years ago [0:44] Yi started the tech boot camp and hackel [0:47] started uh a year ago y hack brought [0:49] together a thousand developers from [0:51] around the country for a two-day [0:52] hackathon uh this [0:54] semester uh Daniel body changed the [0:56] introduction to programming course here [0:58] which is the kind of core program course [1:00] for non- Majors to be um mobile [1:03] application development and Daniel and I [1:05] are teaching together next year uh in [1:08] the spring computer science 113 which is [1:10] entrepreneurship and programming and we [1:11] have a course on management of software [1:13] development here at the El School of [1:15] Management so this is to say that uh [1:17] there's a great interest at Yale uh in [1:20] software development and um and it [1:23] transcends a couple different [1:25] disciplines so it's here at the school [1:26] of management and the department of [1:27] computer science it's and School of [1:29] Engineering and all sorts of places and [1:31] across different clubs so organizations [1:33] like the Yale entrepreneurial Institute [1:35] hack Yale entrepreneurship Club here it [1:38] yhack all these kinds of organizations [1:41] uh and it's not just Yale um so there's [1:43] a couple logos up there uh that are uh I [1:46] think that are not at Yale but are [1:50] indicative of the activity in this space [1:52] uh in the local community so uh we have [1:55] to thank for the food tonight continuity [1:57] control which is local startup and Ruby [1:58] shop filled with uh wonderful hackers [2:00] and developers uh who are very active in [2:03] New Haven IO which is a local developer [2:05] group that I've been honored to be a [2:06] part of and I worked with Dan and Joel [2:07] and many other people who are here uh to [2:10] organize a variety of events for New [2:11] Haven IO and I'm very happy to say that [2:14] uh the plurality of people here tonight [2:16] are actually probably not not from Yale [2:18] which I think is really important [2:19] because traditionally Yale has this kind [2:21] of town and gown problem right where [2:23] there's not much uh there's not much [2:25] interaction and I think that's a real [2:27] problem I think this is an area in which [2:28] we have everybody has a mutual interest [2:29] and excitement and so it's wonderful to [2:31] have events like this and we're very [2:33] happy to have uh Bob here to speak to us [2:35] so I'm going to invite Dan to come up [2:38] and uh introduce himself and well I [2:41] guess you don't need to introduce [2:43] yourself you should introduce Bob but [2:44] Dan I will introduce you so Dan is Dan [2:47] is a developer at continuity control uh [2:50] he has been an organizer of uh the local [2:53] Ruby group for many years an organizer [2:56] of New Haven IO I think he's on the [2:58] board of New Haven IO he is a [3:00] essentially you know very active local [3:02] development very supportive of the local [3:04] community uh and I think that's [3:05] wonderful and that's how uh our [3:07] friendship has grown in that context so [3:09] Dan thank [3:11] you so said I'm Dan ber and I'm [3:15] developer and [3:18] one [3:19] um we are hiring and you enjoy the [3:23] snacks come talk to us afterno M's not [3:25] on oh m not on [3:28] sorry hello any better all right I [3:32] should have just been projecting sorry [3:34] all right um Dan berer continuity New [3:37] Haven [3:38] IO um summary version all right so um [3:43] like I did say we are hiring um and we [3:45] all have little continuity lapel pins so [3:47] uh we do some pretty interesting things [3:50] um we aim to have really clean code uh [3:53] we have some really interesting projects [3:54] coming up with machine learning [3:55] interesting stuff like that so we're [3:58] hiring come talk to us [4:00] um software is making software is a it's [4:04] a weird business it's a messy business [4:07] um those of you who are students may not [4:09] have found this out yet because when [4:11] it's just for fun it's a lot of fun but [4:14] when it matters when when you have [4:16] people paying you for the software that [4:18] you're writing or people using your [4:19] software or God forbid people whose [4:21] lives depend on your software it's a [4:24] weird business and sometimes you can [4:27] feel kind of like you know we're a bunch [4:29] of kids running around with our shoes [4:30] untied and you're just hoping you don't [4:32] fall down but luckily we have an uncle [4:36] looking out for us giving us a kick in [4:38] the butt every once in a while when we [4:39] need it um Uncle Bob has been uh doing [4:45] quite a number of things over his career [4:46] he he helped write and signed the agile [4:49] Manifesto which really helped the [4:51] industry rethink the way it looks at [4:54] working with business people and the way [4:57] it plans software how much we can know [4:59] about a of software before we've built [5:00] it and how wise it is to bet a lot on [5:03] that um he's helped us look at the way [5:06] we organize our code the way like all [5:09] the way from down to like how do you [5:10] name that variable and that method all [5:12] the way up to how do you organize pieces [5:14] of applications and making them talk to [5:16] each other uh and we're going to talk [5:18] tonight or he's going to talk tonight [5:19] about a spot right in the middle about [5:21] how you organize your objects and and [5:23] have them relate to each other um and [5:26] he's helped us understand new [5:27] programming languages and why they [5:29] matter and why they help uh and he's [5:31] been writing books about all these [5:32] things along the way and speaking at [5:34] events like this all around um so we owe [5:38] a lot to him he's he's raised the [5:40] overall level of our field quite a lot [5:43] and he's kind of journaled it all along [5:44] the way so hope I haven't built him up [5:46] too much but with that Uncle [5:56] Bob hey y y all right um let's see do I [6:02] need to turn the switch on or or do the [6:03] audio people have this going here switch [6:05] on yeah all right so [6:09] oh oh now I have to talk [6:21] quietly there how's that oh good okay um [6:27] how many of you are [6:28] programmers look at that yeah [6:31] programmers is say you're [6:33] not you're what online online product [6:37] that's not a [6:40] programmer not a hard okay all right all [6:42] right most of you are you're not um [6:45] that's [6:49] okay what's [6:52] water what is [6:56] water a naturally occurring substance [6:59] Good Very general definition um soft [7:03] soft or [7:04] hard um but water itself is soft it's [7:08] the stuff in it that makes it hard [7:09] what's the chemical [7:10] formula H2O which [7:13] means two hydrog two hydrogen one oxygen [7:18] and what does the molecule look like [7:20] Mickey Mouse Mickey Mouse very good so [7:23] um a water molecule has a big oxygen [7:28] atom and two hydrogen atoms just like [7:32] Mickey Mouse uh the angle here if I [7:35] remember correctly is 103 degrees for uh [7:39] quantum mechanical reasons having to do [7:40] with God knows [7:42] what why do these three atoms stick [7:46] together now before you give me the co [7:49] you know the normal answer oh it's Cove [7:50] valent Bond think [7:53] about what what those atoms are that's a [7:57] a [7:58] proton for the hydrogen right with a [8:01] positive charge and another proton for [8:04] the other hydrogen with a positive [8:05] charge and uh how many protons in the [8:08] oxygen [8:09] atom [8:11] eight six eight eight it's eight eight [8:15] protons in the uh uh center of the [8:19] oxygen nucleus and then around those [8:23] protons is this cloud of electrons one [8:26] electron for the hydrogen one electron [8:28] for the other hydrogen and eight for the [8:33] oxygen and that cloud of electrons has a [8:35] negative charge so these two these three [8:38] atoms should repel each other because [8:41] they're surrounded by negative charge [8:43] why do they stick together what's [8:45] holding them they [8:47] share they share it's classic you know [8:50] they share what what do they [8:54] share so let's look carefully now first [8:58] of all if the the temperature is low [9:00] enough if these atoms aren't moving fast [9:02] enough they do repel each other they're [9:03] not going to get near each other you [9:05] have to get them moving fast you have to [9:07] get them to bump into each other and if [9:09] you get them to bump into each other [9:11] something really interesting happens [9:13] which is this first of all you got a [9:15] positive charge here and you got a [9:17] positive charge here and a positive [9:19] charge here where do the electrons want [9:21] to [9:22] go they want to be as close to all those [9:25] positive charges as they can get which [9:28] happens to be right here [9:30] so you get a preponderance of negative [9:33] charge [9:35] between the protons and the negative [9:38] charge holds the protons together you [9:40] just have to get them close enough and [9:42] then they just kind of go snip and they [9:43] snap together now you mentioned that [9:45] they share well they do share why uh [9:48] because there's some bizarre quantum [9:50] mechanical effect that has to do with [9:51] the poly Exclusion Principle which says [9:53] that um the outer shell of the oxygen [9:56] atom can have as many as eight electrons [9:59] in it it's only got six uh and the two [10:02] electrons from the hydrogens will just [10:04] snap into place there that's not the [10:07] reason they stick together they stick [10:08] together because of this charge they [10:11] will fit because of the uh quantum [10:15] mechanical effect that is a Cove valent [10:18] Bond Cove valent Bond happens when you [10:20] manage to get the bulk of the negative [10:24] charge to hold the positive charges [10:26] together of course the electrons flit [10:28] around here and it's a big probabilistic [10:31] thing they're really all over the place [10:33] but they will spend more time there and [10:36] they hold the whole thing [10:38] together um now look at [10:41] this I bict the molecule cutting Mickey [10:45] Mouse's head in half and where's most of [10:48] the negative [10:49] charge it's above the line the molecule [10:54] does not have its charge evenly [10:56] distributed it is more negative above [10:58] the line [10:59] it is more positive below the line the [11:02] molecule has a dipole a charge across it [11:06] across it my wife gets mad at me when I [11:08] put the T on the end of a cross and uh [11:11] what that means is is that the molecule [11:14] wants to rotate and stick to anything [11:18] that has a charge no matter what the [11:19] charge is why does water stick to your [11:22] hand why does water get wet because all [11:23] the molecules rotate and find some [11:25] little charge on your hand and they [11:27] stick to it electrostatically [11:29] why does water uh dissolve things [11:32] because anything that has a little [11:34] charge the water molecule will stick [11:37] to why does water is why is water good [11:40] for washing because it can stick to [11:43] dirt who's done the cute little trick [11:46] where you take a [11:51] faucet and you take a really thin stream [11:54] of water you have to get this as thin as [11:56] you can get it but without drips it's [11:59] got got to be a nice thin stream of [12:01] water and then you get a balloon and you [12:03] rub the balloon on your head and you [12:05] take the balloon and you put it there [12:07] and the water just goes [12:10] wh attracts the balloon attracts the [12:12] water who's done that one Dazzle your [12:15] children kids watch [12:19] this of course it's not what we're [12:21] supposed to be talking about is it so [12:24] we'll have to abandon this interesting [12:25] topic and talk about something more [12:28] mandane uh the solid principles [12:32] of agile and O design and I can throw a [12:35] whole bunch of other adjectives in there [12:36] if you'd [12:41] like what goes wrong with [12:47] software how many of you have been a [12:49] programmer for more than a [12:52] year 5 [12:55] years notice how that's half 10 years [13:00] notice how we've cut it in half again 15 [13:05] years H half [13:08] again uh why that interesting [13:14] having what is the [13:16] um population of programmers how many [13:19] programmers are in the [13:23] world it's a lot and it depends on if [13:27] you count the VBA programmers but it's a [13:30] laugh it's probably close to 100 million [13:34] depending on how you count I started [13:37] programming over 40 years ago how many [13:41] programmers were there in [13:43] 19 what was that 70 [13:46] say well no more than [13:49] five was probably more like [13:52] 50,000 but it wasn't 100 [13:55] million and if you go back 10 years [13:58] before that [14:01] 1960 how many programmers were in [14:07] 1960 couple of hundred in the world [14:10] couple of hundred right hardly any uh [14:14] and they weren't even programmers they [14:16] were Hardware developers that got roped [14:17] into writing code cuz nobody knew what [14:20] the hell this stuff [14:21] was you think what about this uh this [14:24] progression of time right we started [14:27] with a few programmers the ' 50s managed [14:30] to get a few hundred in the 60s a few [14:32] thousand in the 70s now we're close to [14:34] 100 million that is a geometric [14:38] exponential you you tell me the word uh [14:41] increase it has a doubling [14:44] rate and the doubling rate happens to be [14:46] 5 years every five years the population [14:49] of programmers [14:50] doubles which has an interesting [14:53] implication half the programmers have [14:55] less than 5 years experience and this [14:57] has always been true [15:00] old guys like me people wonder you know [15:03] how come there aren't more older [15:04] programmers they must all quit they must [15:06] all go chicken farming or something we [15:08] don't quit there just weren't a lot of [15:09] us right we're still all here we're [15:12] still writing code it's just that there [15:14] were hardly any of us to begin [15:17] with how do we deal with the fact in our [15:22] industry that we are stuck in an [15:25] exponential curve that guarantees [15:27] perpetual [15:30] inexperience half one half of the [15:33] programmers will have less than 5 years [15:35] experience and that's going to remain [15:37] true until that exponential curve tapers [15:39] off which probably will happen sometime [15:41] in the next couple of years maybe a [15:44] couple of decades but pretty [15:46] soon how do we deal with that what is [15:49] the problem with [15:52] that o the problem with that well let me [15:56] ask this question how many of you of the [15:57] old guys [16:00] have been slowed down by really bad [16:05] code and the young guys don't have their [16:07] hands in the air right how many of the [16:09] young guys been slowed down by a really [16:11] bad code that you wrote [16:13] yesterday [16:15] yeah we know the bad code slows us down [16:19] why do we write [16:20] it why do we write the code that slows [16:24] us [16:25] down how much does it slow us down it [16:27] slows us down a lot [16:29] and Every Time We Touch the modules we [16:31] get slowed down again Every Time We [16:33] Touch the bad code it slows us down why [16:36] do we write this stuff that slows us [16:39] down and the answer to that is wow we [16:40] had to go [16:42] fast I will leave you to deal with the [16:45] logical inconsistency there my message [16:48] to you today is you don't go fast by [16:51] writing [16:52] crap you don't go fast by rushing you [16:55] don't go fast by tearing through the [16:57] code and just making it work in [16:58] releasing it as fast as you can you want [17:00] to go fast you do a good job your your [17:04] grandmother told you this a long time [17:05] ago right you want to go fast you go [17:08] well you take your time you study the [17:12] problem you move deliberately instead of [17:15] instead of [17:17] rapidly a programmer who goes fast [17:20] programmer who is rushing like crazy is [17:24] guaranteed to go [17:25] slow a programmer who sits carefully and [17:28] thinks about it and you types A few [17:29] characters and looks at the code and [17:31] cleans it a little bit and does this and [17:33] that and he'll go [17:34] fast she he doesn't [17:37] matter imagine [17:41] this you are having an out ofbody [17:45] experience floating above an operating [17:48] table where a surgeon is performing open [17:51] heart surgery upon you you're looking [17:54] down at that surgeon as he is opening [17:56] your chest and trying to rep the damage [17:59] to your [18:00] heart how would you like this surgeon to [18:07] behave carefully deliberate now he's got [18:10] a deadline [18:12] literally [18:16] right but you hope even because of this [18:21] deadline even in the face of this [18:22] deadline you hope this surgeon is [18:26] behaving carefully calmly deliberately [18:28] following his or her [18:31] disciplines going close to the book not [18:33] exactly by the book but close to the [18:35] book calmly asking for an instrument [18:38] calmly doing a thing acting like the [18:42] surgeon knows what they're [18:45] doing you don't want the surgeon acting [18:48] like the typical software [18:57] developer think about that for a while [18:59] what goes wrong with software well [19:01] that's what goes wrong with [19:03] it but we can be more [19:06] specific what are the symptoms of bad [19:10] software how do you know you have bad [19:13] code what does it do to you other than [19:17] the obvious thing of slowing you down [19:19] how does it slow you [19:25] down it's confusing it's confusing okay [19:28] fine you look at it and you [19:32] go [19:34] huh now that's bad [19:39] code did I not put that up [19:45] there you look at the code and you [19:49] go that's bad [19:51] code because good code should explain [19:55] what it's [19:56] doing but good code you should look at [19:58] it you should go oh yeah mhm yeah yeah [20:01] oh yeah it should be boring you should [20:04] read it and go it's perfectly [20:07] obvious that's good code bad code [20:13] you man what the hell is this [20:19] doing what happens when you modify bad [20:23] code you break something that's how you [20:26] know it's bad code if you modify [20:29] something and that breaks something else [20:31] the code is bad um so one of the [20:34] symptoms is called rigidity rigidity is [20:37] when you touch the [20:39] code and um you must now [20:44] modify massive amounts of other code to [20:48] come back into consistency with this [20:51] modification you made your boss comes to [20:52] you one day and says can you fix this [20:54] bug and you look at the bug report and [20:56] you say h i know I know exactly where [20:59] this bug is you don't tell your boss [21:00] that you just think to yourself I know [21:02] exactly where this bug is I actually put [21:04] it in the code [21:07] yesterday and so you tell your boss [21:10] three [21:12] weeks because that's the minimum [21:14] estimate you're not allowed to give an [21:15] estimate less than six 3 weeks if you [21:17] give an estimate less than three weeks [21:18] all the other developers come to your [21:19] cubicle and with [21:22] clubs boss goes away happy he got the [21:24] minimum estimate he'll be back in 3 [21:26] weeks now you go to the module cuz you [21:29] know where this bug is you go to the [21:30] module it's up on your screen you look [21:32] at it for a minute say yep there's the [21:33] bug I know how to fix [21:35] it oh wait a minute if I do that there's [21:40] this other module over here that calls [21:42] that function I better go check it you [21:43] bring that module up and oh [21:46] man I'm going to have to fiddle with [21:48] that a little bit it changes a little [21:50] bit oh wait if I make that change [21:54] there's these modules over here and now [21:57] you begin to chase the tail through the [22:00] codee and the weeks go [22:03] by two weeks three weeks four five your [22:06] boss comes back in says I thought you [22:08] were going to be done with this in 3 [22:09] weeks it'll be done tomorrow I [22:12] swear by the time you're done you have [22:14] touched every module in the [22:16] system your boss says what the heck took [22:18] so long and you utter The Immortal words [22:21] of every software developer well it was [22:22] a lot more complicated than I [22:25] thought that is rigid code code that has [22:29] dependencies that snake out in so many [22:32] directions that you cannot make an [22:34] isolated change without changing [22:36] everything else around it bad [22:40] dependencies systems that are [22:43] coupled another uh symptom of bad code [22:46] similar to that one is called fragility [22:49] fragility is the tendency of the code to [22:52] break in many places even when you only [22:55] change it in one place you make one very [22:57] simple change and a whole bunch of other [22:59] things break but they break in parts of [23:02] the code that have no relationship to [23:05] what you [23:06] changed you made a little change here [23:09] and something over there doesn't work [23:13] anymore you make a change to the way [23:17] um the salary for hourly employees is [23:20] calculated just a little change made a [23:24] little change and now it won't print the [23:27] report for the Union [23:29] boss it crashes when it prints that [23:32] report why'd that happen this is fragile [23:36] code fragile code breaks in bizarre and [23:39] strange ways ways that you cannot [23:41] predict you make a change over here [23:43] something over that breaks you you [23:45] didn't predict it you don't know why it [23:46] happened you have to chase the bug [23:48] around finally realize oh yeah that [23:50] function over there set a flag and that [23:52] guy over there used that flag and uh [24:00] your [24:01] car has an electric window that doesn't [24:05] work you take it to a mechanic mechanic [24:08] looks at it for a while says yeah I can [24:10] fix that you come back the next day [24:12] mechanic proudly shows you the electric [24:14] window [24:15] working you thank him you get in the car [24:18] you turn it on the car won't [24:21] start you're not going back to that [24:23] mechanic that mechanic's an [24:26] idiot and that's what happens when [24:29] managers and customers see software [24:31] developers change one thing over here [24:35] and something over there breaks nothing [24:37] strikes more fear Into the Heart of a [24:40] customer or a manager than that one [24:43] because all of a sudden the managers and [24:45] the customers who don't have any idea [24:47] about the [24:48] technology they believe these guys have [24:50] lost [24:51] control they begin to think that if they [24:54] looked under the [24:56] hood they'd find crap they're [25:01] right and they begin to suspect that [25:04] maybe the quality of this product is not [25:06] all that good at all they become afraid [25:09] of letting you make changes has anybody [25:12] worked at a company where the management [25:14] finally said all right nobody changes [25:16] that [25:17] module right that module is too fragile [25:19] every time you touch it something else [25:21] wrong nobody touches that module that is [25:22] the ultimate failure of a software [25:24] developer when the business who is not [25:27] technically competent since you're not [25:29] touching that [25:34] module third symptom of bad [25:38] code your boss comes to you one day and [25:41] says you know that module you got to [25:43] write um you know Joe over in uh that [25:48] other group over there he wrote one just [25:49] like it last year you should go talk to [25:53] Joe and see if you can use some of his [25:56] code [25:58] no you know Joe and you know the kind of [26:00] code that Joe writes and you don't want [26:02] to go over there and talk to Joe but [26:05] your boss told you to do it so you go [26:06] over there and you look and sure enough [26:09] Joe's module does just what you need it [26:11] to [26:13] do but it does more than that it couples [26:17] to some bizarre framework it uses some [26:20] weird database and the more you look at [26:23] it the more you realize that yes you [26:25] could use Joe's code but the problem s [26:28] you would be bringing in are so severe [26:31] that you finally say nah it' just be [26:33] easier for me to write it [26:36] myself the desirable parts of the code [26:39] are so horribly coupled to the [26:42] undesirable parts of the code that you [26:44] cannot use the desirable parts of the [26:46] code somewhere [26:48] else Joe's code was [26:53] crap how do we deal with this what is [26:57] the common threat spr of all of those [27:03] flaws spaghetti well okay spagetti is [27:06] the word that we use to say well this [27:09] code's all scrambled up but there's a [27:11] more technical term coupling [27:13] coupling the reason the code is rigid is [27:17] because the modules depend on each other [27:19] in undesirable ways the reason that the [27:22] the code is fragile is because the code [27:25] depends on data structures in [27:28] undesirable ways the reason that I [27:30] cannot take the desirable parts of Joe's [27:33] code and use them in my system is [27:35] because Joe's code depends on other code [27:38] in undesirable ways and the Common [27:40] Thread there is coupling [27:47] dependency it can be said and I think [27:50] accurately that [27:53] the bulk of software design is managing [27:57] dependencies [27:58] figuring out where to put code and [28:01] cutting the dependencies so that the [28:03] dependencies don't run in strange and [28:05] bizarre [28:06] directions how do you do [28:14] that [28:18] so there's two people in the world I [28:20] want to talk [28:21] to the people who designed [28:25] whiteboards you know chalk does not do [28:27] this [28:37] right we have space over here [28:49] yeah chalk also does not do that [28:59] let's say that I've got a main program [29:01] and let's say that we are writing code [29:03] in C Circa [29:06] 1971 um okay I got a main program my [29:09] main program [29:11] calls uh some highlevel functions highle [29:15] modules and those high level modules [29:17] call uh middle level [29:20] modules more than one there's a bunch of [29:23] these middle level [29:25] modules oh sorry it's the high level [29:27] ones that call the mid-level ones mhm [29:30] and the midlevel ones call lower level [29:35] modules and you can see that there is [29:38] this lovely tree of function [29:41] calls this is the procedure function [29:44] call tree that every application has no [29:47] matter what the application is every [29:48] application has one of these we start at [29:50] Main and we explode [29:53] downwards towards the lowest level [29:55] functions in the system um the flow of [30:01] control [30:03] goes that [30:05] way main calls high level high level [30:08] calls mid level mid-level calls low [30:10] level the flow of control goes [30:15] downwards which of these [30:18] modules knows about the [30:23] other which of these modules knows about [30:26] the others in fact let's make it simpler [30:29] I have a module M I have another module [30:33] n module n has a function named f m [30:37] calls F within [30:39] n which of these modules knows about the [30:42] other flow of control goes that way M [30:46] knows about n and how do we know that M [30:49] knows about [30:52] n changes okay if n changes M has to H [30:58] that must mean that the compiler [31:01] knows how does the compiler know that M [31:05] depends on [31:07] N there is a statement in the source [31:10] code of M that says we're doing C now [31:14] pound include [31:16] N.H If This Were Java it would be import [31:19] n if it were net it would be using n but [31:23] whatever language it is the the name n [31:28] appears in M there is a source code [31:33] dependency from M to n which I will draw [31:36] with a red [31:40] line notice that the source code [31:42] dependency the red [31:44] line and the flow of control the green [31:48] line point in the same [31:51] direction that's a universal in order [31:53] for one module to call [31:55] another you have to have one module know [31:59] about the [32:00] other so how do the source code [32:03] dependencies run here in this procedure [32:06] calling [32:06] tree well they must go like [32:10] this the highle modules know about the [32:12] lowle [32:16] modules think about that for a minute [32:18] wait a [32:19] minute the highlevel modules know about [32:23] the low-level [32:26] modules what [32:28] rule does that [32:41] violate inversion of [32:45] something do you want your highlevel [32:48] policy polluted with low-level [32:52] detail this is what makes code hard to [32:54] read you're reading code and you're [32:56] trying to get the idea of what's [32:57] Happening at the high level and all of a [32:59] sudden you're dealing with string [33:00] buffers right and you know we're down at [33:02] some really lowlevel concept we're [33:04] trying to piece together what's really [33:06] going on at the high level but we can't [33:09] because our highlevel modules depend on [33:12] low-level modules which depend on even [33:14] lower level modules um imagine some [33:17] module down [33:19] here which has an extremely lowlevel [33:22] thing in it God knows what some lowlevel [33:26] variable that that we need to make a [33:28] change [33:30] to I make a change to that module who [33:34] recompiles you have to follow the arrows [33:37] back every module that depends on this [33:39] low-level module is going to have to [33:41] recompile and be redeployed a change to [33:44] a detail affects high level policy and [33:48] that's [33:49] wrong if you think about that for any [33:51] length of time you think that's insane I [33:53] don't want details changing high level [33:56] policy I want my high level policy to be [33:59] immune from details why does code get [34:02] rigid because we've got all this [34:05] coupling down towards detail why does [34:07] code get fragile cuz I can make a little [34:09] change down here and break a whole bunch [34:11] of stuff up there why is it that I can't [34:13] reuse a bit of high level policy because [34:15] it's tightly coupled to all this [34:17] lowlevel [34:22] crap what's o o [34:28] objects what are objects what is [34:31] objectoriented design what is [34:35] oo why is [34:38] oo part of every language that you work [34:41] in nowadays who's working in in uh [34:45] Java some people good how about how [34:48] about [34:49] c h some more people uh Ruby oh the Ruby [34:55] guys rails oh rails yeah we're going to [34:59] talk about [35:00] rails [35:02] um what else what other languages do we [35:05] have python that's [35:08] Ruby what [35:10] else huh oh Objective C Conquering the [35:13] world with iPhone apps how old ises [35:16] Objective [35:17] C when was it invented [35:21] 1980 who invented [35:24] it Brad Cox why did he Ved he was a [35:29] small talk programmer somebody made him [35:31] program in C he hated it he wrote a [35:33] little pre-processor in front of C gave [35:36] it some small talk attributes he called [35:37] it Objective C he started his own [35:39] company called it stepstone he thought [35:41] he was going to take over the world uh [35:43] he did for several years this was the [35:45] only option that c programmers had to do [35:47] any kind of oo work and we all worked in [35:49] Objective C frantically from 1980 to [35:53] 1985 and then yarna stwp came along and [35:56] published this lovely book called called [35:57] the C++ programming language and it [36:00] looked so much like Kernan and Richie [36:02] that all the C programmers dropped [36:04] whatever they were doing and immediately [36:05] adopted C++ and poor old stepstone went [36:07] out of business and that language would [36:10] be dead today were it not for an [36:12] accident of history that accident of [36:15] History was um Steve Jobs who decided he [36:19] would um hire a real businessman for [36:24] apple and who did he go get he got the [36:27] COO of Pepsi Cola John Scully uh as [36:33] if the COO of Pepsi Cola knew anything [36:36] about a high-tech computer company what [36:39] the COO of Pepsi Cola did know was [36:41] enough politics to get Steve Jobs fired [36:45] Steve Jobs went away uh with a lot of [36:47] money he had tons of money so he didn't [36:48] really care he started a new company new [36:51] company was called Next this company [36:53] sold Hardware black computers very [36:55] pretty I have one in my basement [36:58] um he hired a bunch of programmers who [37:01] were out looking for jobs because their [37:03] language had disappeared from out from [37:04] underneath them they happened to be [37:05] objective c programmers and they wrote [37:08] the operating system for the next [37:09] machine the operating system was called [37:10] Next [37:11] Step the company was a terrible failure [37:14] the machine never did anything the [37:16] software never went anywhere it didn't [37:18] matter because Apple went back to Steve [37:20] on their hands and knees and said please [37:21] come back Steve this guy's killing us [37:23] and Steve said fine but I'm bringing my [37:25] team with me and Steve went back to [37:27] Apple take taking all these Objective C [37:28] programmers with them and they started [37:29] working on the [37:31] iPod that's why you're programming in [37:33] Objective C the worst possible language [37:35] for an iPhone by the way you should not [37:38] be working in that horrible language but [37:40] okay that's the accident of [37:45] History why is it that all these [37:47] languages are oo languages it didn't [37:50] used to be that way right we used to all [37:52] work in Fortran or pl1 or cobal or C and [37:55] nobody knew about oo [37:57] how come all our languages are now o [37:59] languages what's so good about oo that [38:02] they're all o languages [38:04] now C help us organize code [38:07] how [38:12] encapsulation [38:14] encapsulation [38:16] so three magic [38:19] words [38:23] encapsulation inheritance [38:33] polymorphism those are the three magic [38:35] words of oo every oo language has to be [38:38] uh encapsulated inherited in [38:44] polymorphed did we have encapsulation [38:46] when we were programming in c do we have [38:48] any old C programmers in the room okay [38:50] good so did we have [38:52] encapsulation we have perfect [38:55] encapsulation in C [38:58] all you had to do is forward declare [39:01] your functions and your data structures [39:04] you didn't have to implement them you [39:05] would forward declare them in a header [39:07] file and then you would Implement them [39:10] in a C file your users would pound [39:13] include your header file they could see [39:15] nothing of your implementation perfect [39:17] encapsulation there was no way any of [39:19] your users could see any of your data [39:22] values all they could see was your [39:24] function signatures they could see the [39:26] names of your data structures but none [39:28] of the members inside your data [39:30] structures absolutely perfect [39:34] encapsulation uh objects completely [39:36] screwed that up C++ came along and put [39:40] all the variables in the header file [39:42] what did that do well me all the [39:44] variables were suddenly visible to [39:46] everybody and in order to get that under [39:48] control we had to invent these horrible [39:50] hacky words public private protected [39:53] terrible hacks ridiculous some kind of [39:55] Band-Aid over the fact that we used to [39:57] have perfect encapsulation but now we've [40:00] got this messy approach of trying to [40:03] point at certain variables and say well [40:05] that one's public but that one's private [40:06] but that one's protected oh and we'll [40:08] invent other ones too that have no [40:12] name Oho did not give us encapsulation [40:15] oo weakened encapsulation every object [40:18] language out there weakens the [40:20] encapsulation that we used to have so [40:22] when people tell you that oo is an [40:23] encapsulated language they are [40:26] wrong it destroyed good encapsulation [40:29] and replaced it with this horrible hacky [40:32] thing of of making the compiler check oh [40:34] you're not allowed to touch that [40:36] variable so I'm not even going to [40:39] respond to the encapsulation [40:42] answer that is just not [40:45] there we had encapsulation now we [40:50] don't could you do inheritance and [40:54] see yeah but it was hard [40:58] well we had [40:59] unions and um it wasn't too hard to take [41:02] two data structures and give them common [41:04] elements uh and change them only at the [41:06] end so the last few elements were [41:08] different and then you could cast [41:10] pointers from one to the other and pass [41:12] them around inside functions just like [41:13] polymorphic objects U we used to do this [41:15] all the time it's a very common approach [41:17] and [41:18] see C++ made it a little more convenient [41:21] but not a [41:22] lot um multiple inheritance was much [41:25] more convenient than C++ that's why they [41:27] took it away in [41:30] Java why does Java not have multiple [41:32] inheritance wait wait wait why does c [41:35] not have multiple inheritance because [41:37] Java doesn't have [41:39] it why does Java not have multiple [41:44] inheritance the diamond problem and the [41:48] diamond problem has been solved the the [41:49] Diamond problem is simple you got a base [41:51] CL a derivative two base classes both [41:54] those base classes have a common Base [41:56] Class this is the deadly diamond of [41:57] death um and there's a huge ambiguity [42:00] around that we won't go into the details [42:03] uh but why didn't they resolve it [42:05] because it had been resolved many times [42:07] before by many other [42:08] languages why didn't Java resolve [42:12] this to simplify the compiler this this [42:15] is my best guess they were too lazy to [42:16] deal with it so they invented some [42:18] horrible hack called [42:19] interface and they stuck that in the [42:21] language instead and we all bow and [42:25] Revere the interface o interface are [42:27] good interfaces are wonderful an [42:29] interface is nothing but an abstract [42:30] class with a bunch of abstract methods [42:31] in it right it's not some special thing [42:34] in fact the only special thing in Java [42:37] about an interface is what you can't do [42:38] to it you can't inherit multiple [42:41] interfaces you can't inherit multiple [42:44] classes into another class why because [42:46] the only thing you're allowed to [42:47] multiply inherit is [42:49] interfaces a hack a lazy hack they [42:53] shouldn't have done that they should [42:54] have solved the problem instead of just [42:58] throwing it out there and then what [42:59] happened it went into a bunch of other [43:01] languages you can tell that I'm enjoying [43:04] myself here aren't [43:06] you okay so um we did have inheritance [43:10] of a kind in SE we were able to simulate [43:13] it um but it wasn't very convenient so [43:17] what I'll do here is I will give c um [43:20] half credit I will give oo half credit [43:23] for giving us a slightly more convenient [43:26] inherent inance Ruby programmers how [43:28] much do you use inheritance all the time [43:31] yeah for what mix [43:34] mixing active [43:37] record do you use it for [43:42] polymorphism duck typ do you need to use [43:45] inheritance for duct [43:46] types no [43:48] no no in Ruby in Python dynamically [43:53] typed languages you can have [43:54] polymorphism without any inheritance in [43:56] fact all all polymorphism in these [43:59] languages comes without inheritance [44:00] inheritance is not necessary what you [44:03] use inheritance for in a dynamically [44:05] typed language is to inherit behavior [44:07] and [44:08] variables but not interface you get all [44:11] the polymorphism you want for free in [44:14] Java and.net and C++ we have to have [44:17] inheritance to do polymorphism one of [44:19] the weaknesses of these languages is [44:21] that you have to use inheritance to get [44:23] polymorphism um okay which lead us with [44:27] the last of those three [44:30] polymorphism did we have polymorphism in [44:35] C whoa sort of sort [44:39] of in [44:42] C we could do [44:44] this is this going to [44:47] work [44:50] copy copy application uh [44:53] Inc uh we used integers for characters [44:56] in early C uh [44:59] while uh C equals get [45:03] Char uh not equal to eof I think there's [45:07] a parentheses there good put [45:11] Char [45:13] C the Unix copy program vastly [45:18] simplified what does that program [45:22] do o you did a nasty [45:29] it does [45:31] but not [45:33] exactly it copies from standard input to [45:38] standard [45:40] output getchar reads a character from [45:44] standard input putch writes a character [45:47] to standard output what is standard [45:50] input it defaults to the keyboard but it [45:53] does not have to be the keyboard it [45:55] could be anything put writes to standard [45:58] output which is usually the [46:00] terminal I use the word terminal shows [46:02] how old I am but it doesn't have to be [46:05] the terminal could be anything those are [46:07] polymorphic calls getar and putar are [46:10] polymorphic method calls on a class [46:13] named file if you wish they're just like [46:16] any other virtual function in C++ just [46:19] like any other uh polymorphic function [46:21] in Java I have um a type hierarchy there [46:25] every iio every IO driver is just [46:28] another type I had perfect encapsulation [46:32] in C I had reasonable inheritance in C I [46:35] could get polymorphism in C O how did [46:38] that polymorphism [46:41] work how was it that the flow of control [46:44] left getar and got into the keyboard [46:49] driver how'd that [46:54] happen was implemented in different and [46:57] just the name was the same no it's much [47:00] more clever than that much more [47:01] Insidious every IO driver had to have [47:05] five magical functions every IO driver [47:08] had these five functions they all had [47:09] the exact same signature uh read write [47:12] open close seek if you wrote an IO [47:15] driver you had to implement those five [47:17] functions and the five functions had to [47:18] have the same signature you open close [47:20] read right seek the operating system [47:23] would take pointers to those functions [47:24] and put them into a table and when you [47:27] called getar it would go to the table [47:29] for standard in and say where's the read [47:31] function oh I'll call that read function [47:32] not knowing what the read function was [47:35] just knowing that it had the right [47:36] signature C++ programmers what was that [47:40] table it's a v table exactly the way C++ [47:44] implements virtual [47:46] functions Java programmers do you have [47:49] pointers to [47:51] functions no why [47:54] not because you have polymorphism if you [47:57] have polymorphism you don't need [47:58] pointers to [47:59] functions C programmers do you have [48:02] pointers to functions you got those ugly [48:04] delegate [48:05] things but they're not quite pointers to [48:07] functions no no you don't have pointers [48:09] to functions C++ programmers do but they [48:11] don't use them if they're sayane uh Ruby [48:14] programmers do you have pointers to [48:18] functions really not really oo languages [48:22] do not need pointers to functions [48:24] because oo languages are polymorphic and [48:26] if you have polymorphic dispatch you do [48:28] not need pointers to [48:33] functions we did not do polymorphism in [48:37] C very much and the reason we didn't do [48:39] it in C very much was because it was [48:41] dangerous as hell if you created a table [48:44] of function pointers you had to load [48:46] that table and you had to make sure that [48:48] everybody called the functions through [48:50] that table and if anybody violated any [48:52] of those rules you had a terrible [48:55] problem on your hand so most of the time [48:58] we did not have polymorphism in C or in [49:02] any language prior to [49:04] C++ when C++ came along all of a sudden [49:08] we got cheap easy safe [49:12] polymorphism and polymorphism is [49:15] interesting because polymorphism allows [49:17] you to do something with [49:23] this that we couldn't do before let me [49:26] draw it again [49:30] here's our module M here's our module n [49:35] it has a function named F the flow of [49:38] control will go from n to n calling F [49:43] but if I have polymorphism if I have an [49:47] oo language I can do this I can put an [49:51] interface here yes I know I'm using the [49:54] interface [49:57] my m method my M module can mention the [50:00] name of that interface and use it to [50:03] call F I will Define f in here the N [50:06] module will derive from that [50:09] interface and notice what has happened [50:11] to the compile time [50:14] dependency it points against the flow of [50:18] control this is what polymorphism gives [50:20] you polymorphism gives you the ability [50:23] to create one module calling another [50:27] and yet have the compile time dependency [50:29] Point against the flow of control [50:31] instead of with the flow of control and [50:34] if you have that power then you can take [50:37] any of these red arrows here and turn [50:40] any one of them [50:41] around you suddenly have absolute [50:43] control over your dependency structure [50:46] and if you have absolute control over [50:48] your dependency structure you can avoid [50:50] writing fragile rigid and non-reusable [50:55] modules you you can get around what goes [50:58] wrong with software by carefully [51:01] deciding which direction the arrows [51:04] between modules should [51:07] Point that's what oo [51:10] is you may have heard that o is modeling [51:13] the real [51:14] world nonsense you may have heard that o [51:18] is closer to the way we think these [51:19] these things were made up by marketing [51:21] people in order to sell the idea to [51:25] Executives who didn't know what the what [51:27] the programs were oo is about managing [51:32] dependencies by selectively re inverting [51:36] certain key dependencies in your [51:38] architecture so that you can prevent [51:41] rigidity fragility non- [51:44] reusability there are several principles [51:47] that we can talk about that take [51:49] advantage of this aspect of polymorphism [51:52] of oo we're going to introduce a few of [51:56] them here as long as we have time notice [51:58] that I've not been using my slides but [52:00] everything I was talking about was [52:02] actually there oh [52:06] okay the principles we're going to talk [52:08] about are called the solid principles [52:09] they're called the solid principles [52:11] because the words happen to spell out [52:13] the word solid which is not something I [52:15] knew to begin with but someone pointed [52:17] it out to me several years ago and said [52:18] you know this spells solid and so I [52:20] quickly changed [52:21] it because it sounded like a great word [52:24] to use for a bunch of of principles the [52:27] first of these principles is called the [52:29] single responsibility [52:34] principle the single responsibility [52:36] principle says [52:40] this a [52:42] class should have only one reason to [52:46] change it should have a single [52:50] responsibility to change don't be [52:53] confused about the word responsibility [52:55] the word responsibility does not mean [52:57] function does not mean what it does the [53:01] word responsibility means [53:03] change it has one reason to change look [53:06] at that um look at that class up there [53:09] that employee class it has a number of [53:11] methods on it Cal pay report hours WR [53:14] employee Cal pay calculates the pay of [53:17] an employee it's a payroll application [53:20] report hours generate some horrible [53:22] report that the Auditors will read from [53:24] time to time about the employees and [53:26] write employee is a database function [53:28] that saves the employee to a [53:30] database how many reasons does this [53:33] class have to [53:35] change how many changes could be [53:40] requested how many sources of change are [53:45] there now let's think about [53:49] that if the calc pay method were to [53:52] break and cost the company a million [53:55] dollars [53:57] which c-level executive would find out [53:59] about it [54:02] first would it be the CFO the COO or the [54:09] CTO it would be the CFO because he'd be [54:11] the one looking at the problem in the [54:14] books in the accounts and then he would [54:16] go to the programmers and fire [54:22] them if there were a bug in the report [54:25] hours [54:26] function which and it cost the company a [54:29] million dollars which sea level [54:32] executive would find out about it [54:34] first the CFO the COO or the [54:40] CTO the co because he controls the [54:44] Auditors who check the reports about [54:48] what got worked on and what got spent [54:51] and he would go to the developers and [54:53] fire [54:54] them and if the database crashed [54:57] destroying a million dollars worth of [54:59] data assets because some programmers [55:02] screwed up that right employee [55:05] method which sea level executive would [55:08] discover at [55:09] first Le the CTO so I have three [55:13] different sea-level Executives [55:15] interested in these three [55:17] functions if they don't work right each [55:20] of them will be upset for different [55:22] reasons and if they want changes if they [55:25] particular organ organizations want [55:27] change those changes will come from [55:30] beneath those particular sea [55:32] levels a a change to the database schema [55:35] is going to come underneath the CTO some [55:37] DBA is going to want to change the [55:39] schema forcing me to change this class [55:42] if uh the report needs to change it'll [55:44] be some auditor who wants to change he [55:46] reports to the COO and if the business [55:49] rules for calculating pay change it'll [55:51] be some accountant uh who works for the [55:54] CFO [55:56] and so this class has three different [55:58] responsibilities it has responsibilities [56:00] to three different actors three [56:03] different people or three different [56:05] organizations all at the same [56:07] time is it possible for me to um add a [56:12] new feature to the report hours function [56:15] and accidentally break the Cal pay [56:18] function it is well I'm not going to do [56:21] that I'm not going to put those three [56:23] methods in my class because those three [56:25] methods have different reasons to change [56:28] they change at different times for [56:30] different reasons they change uh based [56:33] on the interest of different people in [56:35] the organization so I'm going to [56:37] separate those three methods and keep [56:39] them out of the single class and you say [56:42] well wait a minute I thought this was [56:44] objects I thought objects were supposed [56:46] to have the methods that make them work [56:48] and yeah sure that's true but you'd also [56:50] better protect yourself from those sea [56:53] level guys who are going to come and [56:54] fire you [56:58] so what do we do how do we get those [57:00] three methods into three different [57:02] classes because I don't want them there [57:04] there's a whole bunch of ways to do that [57:05] you could do it this way if you cared [57:07] to you could create an employee which [57:10] has the Cal pay method you could create [57:12] other classes like the report writer and [57:14] the employee repository that that speak [57:17] to the employee method and manage to do [57:20] their jobs that's a possibility although [57:22] I don't much like [57:24] that because [57:27] um these guys now depend on that and if [57:31] there were a change to calc pay these [57:33] guys would have to be uh changed they [57:35] would have to be recompiled and [57:36] redeployed I don't much care for that so [57:39] maybe what we'll do is this maybe what [57:41] we'll do is we'll create an interface [57:43] named employee that has the um at least [57:46] one of these methods and then we will [57:49] derive from that interface another one [57:51] of these classes that implements calc [57:54] pay maybe we could do that [57:57] or GE maybe we could um maybe we could [58:01] have a single class that implements all [58:04] three methods but has three different [58:06] base classes one with each of the [58:08] methods that would be interesting [58:10] although I don't know if it solves the [58:11] problem or maybe what we could [58:15] do is create three classes one with each [58:18] method and put a facad object out here [58:21] that delegates to them there's a million [58:23] ways to to address this problem million [58:26] ways to move around it the important [58:28] part is don't put functions that change [58:32] for different reasons in the same [58:35] class what reason is [58:39] the report hours function going to [58:42] change for what reason will the report [58:44] hours function [58:51] change they decide they want to report [58:54] fractional hours in a different yeah [58:56] want to report fractional hours in a [58:57] different way they're not changing the [58:59] substance of the report at all they're [59:00] just changing the format uh they used to [59:02] report fractional hours with a decimal [59:04] point now they're going to uh report [59:06] fractional hours with two numbers uh one [59:09] for the number of hours one for the [59:10] number of tenths without the dot anymore [59:13] and they don't like the dot right you [59:16] think these guys don't do things like [59:18] that yeah we don't like that [59:20] dot okay so you work in there and you [59:23] change the dot you pull the dot out [59:24] right and you break the calay [59:28] method you don't [59:31] want methods in a class in the same [59:35] class if they change for wildly [59:37] different reasons I don't want to expose [59:40] the calay method to the vagaries of the [59:43] Auditors who want to change the format [59:45] of a [59:46] report formatting should go in one place [59:49] calculation should go in another [59:50] database in another and if you've got [59:52] some other concern put it in another [59:54] don't mix concern in your [59:57] classes that's [60:02] one ah the open close [60:05] principle the O in solid the open closed [60:08] principle says this was invented by the [60:10] way by bertran Meer in 1988 in a lovely [60:13] book called object-oriented software [60:15] construction if you haven't read that [60:16] book go read the find the first edition [60:18] read the first edition the second [60:20] edition is a th pages long no one can [60:22] possibly read [60:23] it the open Clos PR principle says [60:27] this a class or a module should be open [60:32] for extension but closed for [60:38] modification what does that mean it [60:40] means you should be able to change what [60:42] the module does without changing the [60:45] module you should be able to change the [60:47] behavior of the module without changing [60:50] the module it should be open for [60:53] extension so Its Behavior can be [60:55] extended [60:56] but closed for modification now this [60:59] sounds oxymoronic it sounds impossible [61:01] how the heck can you change the behavior [61:02] of a module if you can't change the [61:04] module but we do it all the [61:06] time this module here this copy [61:10] module I can change its Behavior by [61:13] writing a new IO driver I can write a [61:16] new IO driver that reads characters from [61:19] the optical character [61:21] reader and my copy program can call that [61:25] iio driver [61:26] do I have to recompile my copy program [61:29] no I don't have to change it I don't [61:32] have to recompile it I don't have to do [61:33] anything to it and yet it can call the [61:36] optical character reader the optical [61:38] character reader driver is written long [61:40] after the copy program was compiled and [61:43] put on my computer but my program can [61:45] still call it I can extend the copy [61:49] program because I have polymorphic calls [61:52] within it and I can Implement those [61:54] polymorphic calls [61:56] anytime I want to in any interesting way [61:58] I want to I can [62:01] extend the copy [62:07] program if you can conform to this [62:13] principle then when you add a new [62:15] feature to an [62:16] application you should be able to do [62:18] that by writing new code and not [62:20] changing any old [62:23] code but you're not going to modify it [62:25] you're going to extend it so all your [62:27] modules are closed for modification but [62:29] they can be extended so you can extend [62:31] your application with a new feature [62:33] without changing any of the existing [62:36] modules if you have conformed to that [62:40] principle is this possible yeah it's [62:42] possible I can do it here I'll show you [62:43] it's [62:47] easy we'll do this in C or some language [62:50] that sort of looks like [62:53] C um I've got a um a simple application [62:56] by the way this is the failing version [62:58] this is the version that does not [63:00] conform to the open close principle [63:02] written in C um okay I've got some [63:06] shape. H class it has an enum in it that [63:10] enum uh has two enumerators Circle and [63:13] square there is a data structure within [63:15] it called shape which has one data [63:18] element which is the [63:19] enumeration I have another data [63:21] structure called Circle it begins the [63:24] same way that shape does which means I [63:26] can cast a circle to a shape and still [63:29] use it this is kind of fake fake [63:31] inheritance here um I've got a Double [63:33] Radius and a center point and a function [63:36] that can draw a circle I've got a data [63:38] structure named Square in in another [63:40] file called square. it also begins with [63:43] the enum data structure has a double [63:46] side and a top left point and a function [63:48] that draws the square and now I have [63:51] this function here draw all shapes draw [63:55] all shapes takes an array of shape [63:58] pointers it Loops through the array of [64:01] shape pointers it asks each shape what [64:04] its type is if it's a square it calls [64:06] draw a square if it's a circle it calls [64:08] draw a [64:11] circle what bad thing happens because I [64:15] wrote the code this way this violates [64:18] the open close principle oval you need [64:21] to add anothere so if I add an oval I [64:25] must must add another switch another [64:29] case to the switch statement uh but [64:31] that's not the first thing I do what's [64:33] the first line of code that [64:35] changes en [64:38] enim and that's in shape. h who pound [64:41] include shape. H everybody everybody who [64:45] recompiles everybody everybody Circle [64:47] recompiles because I had it an oval [64:49] Square recompiles because I added an [64:51] oval that's fundamentally [64:53] sick right square and Circle don't give [64:56] a damn about oval but they've got to [64:57] recompile just because I add it an oval [65:00] something's really wrong with that [65:01] that's rigid that is the symptom of [65:04] rigidity you have to compile too much [65:06] because of a change you have to compile [65:08] things that should not have had to be [65:10] recompiled because of a [65:12] change but it's worse than that because [65:15] I do have to modify the switch statement [65:17] I've got to go to the switch statement [65:19] and I've got to add the oval case and [65:21] you think well that's not so bad it's [65:22] just a switch statement ah but there's [65:24] more than one switch statement switch [65:27] statements are like the Sith always more [65:30] than one there [65:33] are switch statements replicate and [65:36] duplicate and spread through the system [65:38] there are switch statements of this form [65:40] for draw all shapes erase all shapes [65:42] drag all shapes scale all shapes rotate [65:44] all shapes anything you can do with [65:46] shapes there's a switch statement for it [65:48] except that they're not all switch [65:50] statements because some programmers [65:51] don't like switch statements and they [65:53] use IFL statements instead [65:55] but there's still switch [65:57] statements and now what I have to do is [66:00] I have to find them all and I have to [66:02] modify them all I have to put oval in [66:04] all of them and that may seem simple [66:06] except for the fact that programmers are [66:08] of strange beasts they like to do [66:10] logical optimizations if they can get [66:13] rid of a case or do some special ANS and [66:15] ores or something they will do that for [66:18] example what is the case of a circle in [66:22] rotate all [66:24] shapes nothing you don't do anything to [66:26] a circle when you rotate it it doesn't [66:28] have a [66:34] case so this is fragile it's going to [66:38] break because I'm not going to find all [66:40] of those switch statements and I'm not [66:41] going to logically decode them properly [66:43] and I will wind up creating some problem [66:46] because I missed something this is rigid [66:49] because it recompiles more than it ought [66:51] to it is fragile because I can't [66:54] possibly find all the statements and [66:56] modify them correctly but that is not [66:58] the worst of the problems is the worst [67:00] of the problem is [67:02] this our boss comes to us and says you [67:06] know we've had a lot of trouble uh with [67:08] this startup company and uh we're about [67:11] to run out of funding um the sales model [67:13] we had just isn't working uh but we got [67:15] a new idea hired a consultant paid him a [67:17] lot of money and he told us what we had [67:19] to do what we have to do is this we have [67:22] to give away draw all shapes for free [67:26] we're going to take that we're going to [67:27] put it in a jar file or a dll we're [67:30] going to put it up on our website give [67:32] it away for free and then we're going to [67:34] take all the circles and squares and [67:36] ovals and triangles and we're going to [67:37] put them in separate jar files or dlls [67:40] and we'll sell them all for $5 each on [67:43] our [67:45] website and we have to tell our boss [67:49] that we can't do [67:51] that because the switch statement has a [67:55] dependency on square and circle and oval [67:58] and triangle we cannot separate them we [68:02] must deploy Circle and square and [68:04] triangle and oval withdraw all shapes [68:07] there's no way to independently deploy [68:09] them and we tell our boss our [68:12] architecture does not [68:14] support what you want to do and we go [68:18] out of business and I'll look for other [68:20] jobs that is immobility the reusable [68:24] part was not properly decoupled from the [68:30] goop how can we fix [68:33] that uh are you ready for the [68:36] lie here comes the [68:40] LIE this is the oo version and I want [68:43] you to you know think of soft music in [68:48] the background the sound of wind blowing [68:51] through the trees fuzzy bunnies jumping [68:54] over hillsides [68:59] class [69:00] shape has a draw function this is C++ [69:05] but don't let that worry you um class [69:08] Square derives from shape it also has a [69:11] draw function class Circle derives from [69:14] shape it also has a draw function and [69:16] look at draw all shapes the Elegance the [69:18] glory the beauty of it all draw shapes [69:22] Loops through the shapes and just tells [69:24] each one [69:25] to draw itself oh [69:32] God what happens when we add the [69:36] oval what here must [69:41] recompile [69:42] nothing nothing I can add the oval [69:46] nothing here recompiles open close [69:50] principle is it fragile it's not rigid [69:53] now can't be rigid cuz nothing has to [69:54] recompile is it fragile can't be fragile [69:58] because everything I can do to a shape I [70:01] must Implement in that shape there's no [70:04] way for me to forget to implement a [70:06] method It's Not Fragile I don't have to [70:08] go hunting for switch statements there's [70:10] no funny logical optimizations the [70:13] rotate function will be implemented to [70:15] do at least something in circle even if [70:18] it's just open close [70:21] brace It's Not Fragile but the best part [70:24] is that when our boss comes to us and [70:26] says yeah we want to put those shapes in [70:28] one dll and we want to put the draw [70:30] shapes in another dll we can do that [70:33] because draw shapes doesn't have any [70:35] idea that squares and circles [70:38] exist the high level policy here does [70:42] not know about the low-level [70:45] details the flow of control still works [70:47] the same way this Loop calls these draw [70:51] functions but at compile time the high [70:54] level policy does not know about the [70:56] lowlevel details and none of the design [70:58] flaws appear none of the symptoms of bad [71:01] code are [71:05] evident I said this was the lie why is [71:08] this the [71:10] LIE well it turns [71:13] out that [71:16] customers have a different idea in [71:19] mind this protects us from something [71:23] what does it protect us from [71:26] what changes does it protect us [71:32] from new [71:34] shapes if we make new shapes nothing [71:36] here changes our customers have no [71:39] interest in new [71:40] shapes what they want is for all the [71:43] squares to be drawn first and all the [71:45] circles to be drawn second that's the [71:47] change they want to make they want all [71:50] the circles to be on top of the squares [71:53] so they want the ordering to change [71:55] now had we known that ahead of time [71:59] maybe we could have invented an [72:00] abstraction that protected Us Against [72:02] The Ordering of the shapes but we didn't [72:05] know that ahead of time we thought this [72:06] was a perfectly good real world model [72:09] squares derived from shape Circle [72:11] derived from shape How Better Could you [72:13] model the real world than that it's just [72:15] that's not what the customers care [72:18] about customers are very good at somehow [72:23] knowing what your design is [72:27] and then choosing the new feature that [72:29] will completely thwart Your [72:31] Design um this is a fact of life it's [72:34] one of the great flaws of oo design [72:36] because in order for oo design to [72:38] protect you from the customer you must [72:39] know what the customer is going to do [72:41] and customers always do the other thing [72:44] so you can't just blly model the real [72:48] world and hope for the best because the [72:49] customers will certainly find some [72:51] interesting way to screw you um what we [72:53] do instead is take a very pragmatic view [72:56] we say all right uh what we're going to [73:00] do is implement the simplest thing we [73:01] possibly [73:02] can and then we're going to get it out [73:07] in front of the customer as soon as we [73:08] possibly can and we're going to ask the [73:10] customer to change it please change our [73:13] code have you ever done this please [73:15] change our code Give us new features and [73:18] the customers will suggest new features [73:20] and this gives you a clue about where [73:23] the axis of change really is is in your [73:25] application and then you can implement [73:28] the abstractions that protect you from [73:30] those changes do not think that you know [73:33] because you don't go out to the [73:35] customers as early as you can find out [73:38] what they're going to do to you and then [73:41] build the abstractions into your [73:42] application that will protect you and [73:45] they won't be what you think they [73:50] are I think we have time for one [73:53] more this this is called the lisof [73:56] substitution principle it was invented [73:58] by Barbara lisof in 1988 1988 was a good [74:01] year for [74:03] principles Barbara lisof said this well [74:07] actually I this is a paraphrase because [74:09] what she wrote was a mathematical [74:10] formula but what I will do is paraphrase [74:13] that formula derived classes must be [74:16] usable through the Base Class interface [74:19] without the need for the user to know [74:21] the difference that makes perfect sense [74:24] right I've got some uh user and the user [74:29] uses a base class but I've got a [74:31] derivative I should be able to [74:33] substitute that derivative in and the [74:35] client shouldn't know anything about it [74:38] simple polymorphism [74:40] right [74:45] so the dreaded rectangle square [74:49] problem I have a [74:51] rectangle this class we wrote a long [74:54] time ago [74:56] it's got a field named height and a [74:57] field named width they're both real [74:59] numbers uh there's a set height and a [75:02] set width function and a new requirement [75:05] has come along that new requirement is [75:08] for a square now clearly a square is a [75:12] rectangle so Square inherits from [75:15] rectangle makes perfect [75:18] sense uh wait a minute how many fields [75:22] does a rectangle have [75:26] two rectangle two height and width how [75:30] many fields does a square [75:32] need one how many will it [75:35] inherit something's [75:38] wrong Square cannot inher it from [75:41] rectangle and only have one [75:43] field [75:45] now that means we're going to waste [75:48] memory memory is [75:51] cheap let's just waste the memory and do [75:54] this let's [75:55] override set width in square so that it [75:58] sets both the height and the width and [76:00] will override set height so it sets both [76:03] the height and the width that solves the [76:12] problem imagine some [76:15] user of [76:19] rectangle that user calls set [76:23] height does the user that calls set [76:26] height on rectangle have the right to [76:30] expect that the width won't [76:32] change yes but if I pass him a [76:36] square he will call set height and the [76:39] width will change and that will corrupt [76:41] his internal finite State machine and he [76:43] will then corrupt the Heap and you will [76:45] discover this a billion instructions [76:47] later when you get a null pointer [76:51] exception what [76:53] happened oh by the way way you're the [76:55] guy writing the code that just blew [76:58] up you're the guy writing the code [77:01] that's calling set width on the [77:02] rectangle and you get your logic [77:04] analyzer out and you walk through the [77:06] back Trace through a billion [77:07] instructions and you find after weeks of [77:09] analysis that the reason you blew up is [77:12] because somebody passed you a [77:14] square so what code are you going to [77:16] write in your module that will protect [77:18] you from the possibility that the [77:21] rectangle you're holding might actually [77:22] be a square [77:29] if the rectangle is a [77:33] square and by doing so you will hang a [77:35] dependency on the Square something that [77:37] you never wanted to know about but your [77:39] module which was supposed to know about [77:41] rectangles will suddenly have a [77:43] dependency upon the square violating the [77:45] open closed principle and making you [77:47] rigid and making you [77:50] fragile does anybody have any code like [77:52] that in their system where you ask what [77:54] type an object [77:57] is what do we use in Java we use [77:59] instance of uh in net we use is or as [78:04] there's a bunch of different ways to do [78:06] that it is not true that every time that [78:10] you do that it's a problem but most of [78:12] the time when you do that it is a [78:14] problem most of the time and this [78:18] particular interesting relationship [78:20] causes that to happen so what went wrong [78:22] isn't a square a rectangle [78:27] right a square is a rectangle so that [78:29] should be the right [78:31] relationship no because that's not a [78:34] square that's a piece of code that is [78:36] not a rectangle it is a piece of code it [78:39] is true that a square is a rectangle but [78:41] the class square is not a square the [78:44] class rectangle is not a rectangle a the [78:47] class Square represents a square the [78:50] class rectangle represents a rectangle [78:53] but the represent atives of things do [78:56] not share the relationships of the [78:58] things they represent if anybody here [79:01] has been [79:03] divorced you may have had two lawyers [79:05] representing you and your spouse those [79:07] two lawyers were probably not themselves [79:08] getting [79:10] divorced the things that represent do [79:13] not share the relationships of the [79:16] things they [79:19] represent you cannot simply say well [79:22] that class has the name of something and [79:25] therefore I'm going to use an [79:26] inheritance relationship you have to [79:28] understand what these relationships [79:31] are we um we call that [79:36] relationship [79:38] Isa that's the wrong name for that it's [79:41] not Isa it it got inherited from the [79:44] artificial intelligence people in the [79:46] 80s they they were doing interesting [79:48] data structures called knowledge Nets [79:50] and they had interesting relationships [79:52] has a tastes like a smells like a is a [79:55] and they all got fired because their [79:56] funding dried up because artificial [79:58] intelligence didn't actually do anything [80:00] useful and so then they all became o [80:03] programmers and They Carried their [80:05] interesting abbreviations with them and [80:07] that's how we got the has a and uses a [80:09] and is a relationships and they named [80:12] these relationships that way but they're [80:14] lies they're not correct The Inheritance [80:16] relationship is not the ISA relationship [80:19] The Inheritance relationship really is [80:22] just the redeclaration of functions and [80:25] variables in a Subs scope oh and I know [80:27] that's complicated to remind remember [80:30] how can we fix this problem what is the [80:33] relationship between square and c and [80:39] rectangle behaves like they do they but [80:42] yeah you'd want like a behaves like a [80:44] thing what am I going to do am I going [80:46] to have any relationship between these [80:48] two classes at [80:51] all they might both derive from a a [80:54] common Base Class [80:56] maybe maybe the Base Class shape but [81:00] these two classes really have nothing in [81:01] common at all they don't have the same [81:03] number of variables they don't have the [81:05] same behavior uh they are not related at [81:09] least not as siblings and certainly [81:11] rectangle is not the parent of square [81:13] this is a violation of the lisof [81:16] substitution principle because square is [81:18] not substitutable for [81:20] rectangle even though it seems to fit [81:23] the definition of Isa it does not fit [81:25] the substitution Rule and whenever you [81:28] violate the substitution rule you will [81:29] eventually add an if statement that [81:31] checks the type of the object to prevent [81:34] you from crashing your [81:36] system which makes this about [81:42] 8:00 oh you want to [81:46] continue you want a method regular is uh [81:51] regular is regular ooh you want that up [81:54] in [81:56] rectangle and what you're saying is is [81:58] it a regular [82:00] rectangle so I don't even want the oh [82:03] wait I don't want the square class I [82:06] just want a regular now what would [82:08] happen if I were then to set the height [82:11] of a regular [82:15] rectangle would it set the [82:18] width would I modify the width would I [82:20] dare or would I just set the is regular [82:22] flag false [82:28] if they happen to be the same do I [82:30] automatically set the uh these regular [82:32] flag no you don't have [82:34] to I calculated so is regular will [82:37] return true if the height and width are [82:39] the same sure [82:44] okay they're real they're reals what can [82:48] you not do with a real [82:51] number you cannot compare them equal [82:56] cannot compare real numbers equal people [82:58] go to jail for doing that by the [83:00] way especially if the real number [83:02] represents money and you do not compare [83:04] real numbers equal in a computer [83:06] floating Point numbers can be greater [83:08] than or less but they are never equal [83:09] and if you use the equal operator and it [83:11] says they're equal it's a [83:13] lie right cannot compare them equal you [83:15] can compare them [83:18] close you can't compare equal all right [83:21] I think we're done for the day thank you [83:23] all for your attention if there's any [83:25] questions I'm going to hang around for a [83:26] little while um but the rest of you can [83:29] go