Speaker 1: Welcome to the Deep Dive, the show where we sift
Speaker 1: through the noise, snack up the sources, and extract the
Speaker 1: most potent nuggets of wisdom just for you. You know,
Speaker 1: if you've ever tried to create something, especially software, you
Speaker 1: get that drive, right, that feeling of building something meaningful.
Speaker 1: But then sometimes that joy kind of fades, doesn't it,
Speaker 1: When the cost, the sheer effort of changing or maintaining
Speaker 1: it just seems to outweigh the value.
Speaker 2: Oh, absolutely is a path. Pretty much everyone in software walks.
Speaker 2: You're excited, you ship something, it works, Yeah, But then
Speaker 2: comes the long game. How does it live? How does
Speaker 2: it adapt? That's where the real challenge often kicks in,
Speaker 2: sometimes the.
Speaker 1: Pain too exactly, And that challenge that's precisely why today
Speaker 1: we're doing a deep dive into object oriented design ODE.
Speaker 1: We're leaning heavily on a fantastic book, Practical Object Oriented
Speaker 1: Design and Agile Primer Using Ruby by Sandy Mets. But
Speaker 1: let's be clear, this isn't really about learning to code,
Speaker 1: specifically Ruby or anything. Our mission here is more like
Speaker 1: a shortcut, a shortcut to understand how to arrange code
Speaker 1: well so your software stays joyful to work on productive
Speaker 1: and it can actually handle future changes gracefully. It's about
Speaker 1: getting really clued into those design principles that make working
Speaker 1: on software a pleasure not a chore.
Speaker 2: And it all starts really with one fundamental truth about
Speaker 2: software dot change, it just happens. Yeah, you can imagine
Speaker 2: this like fantasy world where software is written once requirements
Speaker 2: are perfect, never changed.
Speaker 1: Oh wouldn't that be nice? No bugs, no new features needed,
Speaker 1: just done forever exactly.
Speaker 2: And in that world, honestly, design wouldn't matter that much.
Speaker 2: Your code could be a mess, but who cares, It
Speaker 2: just runs. But reality, well, as Sandimes puts it so well,
Speaker 2: change is unavoidable. It is ubiquitous, omnipresent, and inevitable. It's
Speaker 2: just the water we swim in.
Speaker 1: Okay, So if change is truly that inevitable, what's like
Speaker 1: the most counterintuitive thing a developer should maybe start doing
Speaker 1: differently right now, something that isn't immediately obvious.
Speaker 2: That's a good one. I'd say the most counterintuitive shift
Speaker 2: is probably aiming for FlexIt ability or trying to get
Speaker 2: it perfect on day one. So many devs they want
Speaker 2: the ideal solution immediately, trying to guess every future need
Speaker 2: but if change is guaranteed, your perfect first guess is
Speaker 2: almost certainly wrong or at least incomplete. So instead you
Speaker 2: should focus on making the code easy to change, even
Speaker 2: if that means putting off some decisions like unmanaged dependencies.
Speaker 2: They are the real killers here, often silent ones. They're
Speaker 2: like these invisible strings tying bits of code together. If
Speaker 2: you don't manage them carefully, even a tiny tweak, well,
Speaker 2: the damage can just radiate outward and overlapping concentric circles.
Speaker 2: Suddenly nothing feels safe to touch. You start with something
Speaker 2: small and successful, and before you know it, it's a tarpit.
Speaker 2: Every step feels dangerous.
Speaker 1: Right the tar pit. And that's where the books says
Speaker 1: something really interesting. Design is thus an art, the art
Speaker 1: of arranging code. It's not just about making it work now.
Speaker 1: It's about setting it up so it's understandable and changeable.
Speaker 2: Later, precisely. And we have tools for this art right
Speaker 2: design principles, design patterns, These are our guides. Academics have
Speaker 2: even tried to measure this stuff. You quantify goodness. There
Speaker 2: are metrics from to Damburn camera. Back in the day,
Speaker 2: they looked at things like how many connections does the
Speaker 2: class have how complex are its methods? And they did
Speaker 2: find correlations between using these kinds of design techniques and
Speaker 2: ending up with higher quality code. But it's important to
Speaker 2: remember bad ode metrics are indisputably a sign of bad design.
Speaker 2: If your metrics look bad, your code's probably hard to change.
Speaker 1: But getting good scores doesn't automatically mean your design is great.
Speaker 2: Does it exactly right? Good scores don't prove the opposite.
Speaker 2: You could definitely over anticipate the future. You might build
Speaker 2: this elegant structure that anticipates all the wrong changes. Then
Speaker 2: when the real future shows up, it's actually expensive to adapt,
Speaker 2: which brings us to technical debt. It's like borrowing time
Speaker 2: from the future. You take a short cut now, but
Speaker 2: you know you'll have to pay it back later, probably
Speaker 2: with interest. Sometimes it's a bunch's choice, but you have
Speaker 2: to plan to repay.
Speaker 1: That's a really useful way to think about it. So, yeah, okay,
Speaker 1: design can fail. What does it look like when there's
Speaker 1: just no design you just code? It works for a bit.
Speaker 2: Yeah, that leads to code that's easy to write but
Speaker 2: gradually impossible to change. Your programmers start saying things like yeah,
Speaker 2: I can add that, but honestly, it's going to break everything.
Speaker 2: That's a bad sign.
Speaker 1: Okay, so that's one extreme. What about the other overdesign?
Speaker 2: Ah? Yes, the complicated, beautiful castles of code. They look amazing, architecturally.
Speaker 1: Sound, but totally inflexible.
Speaker 2: Exactly, they're hemmed in by stone walls. Now, the response
Speaker 2: to a new feature is no, I can't add that feature.
Speaker 2: It wasn't designed to do that.
Speaker 1: So no design is bad. Over design is bad. What
Speaker 1: about trying to do all the design first, that whole
Speaker 1: big upfront design thing, but you have to separate the
Speaker 1: designers from the coders.
Speaker 2: Yeah, that often creates this weird tension, you know, designers
Speaker 2: versus programmers and the design itself. It's almost always wrong
Speaker 2: in some way because as the books as, certainty is
Speaker 2: unattainable in advance of the application's existence. You just can't
Speaker 2: know everything upfront. And this is where agile comes in.
Speaker 2: People sometimes think agile means no design, but that's wrong.
Speaker 2: Agile does not prohibit design. It requires it. In fact,
Speaker 2: it requires really good design. Design that's simple, flexible, ready
Speaker 2: for change because change is the constant.
Speaker 1: Okay, so if good design is about arranging code to
Speaker 1: handle change, let's dig into object oriented programming itself. How
Speaker 1: does OP help with this flexibility compared to older ways.
Speaker 2: Well, think about older procedural languages. You generally had your
Speaker 2: data structures over here and your functions that operate on
Speaker 2: that data over there, separate things. OO languages like Ruby,
Speaker 2: which the book uses, they combine them together into a
Speaker 2: single thing, an object.
Speaker 1: Hah.
Speaker 2: So now you're not thinking about data and action separately.
Speaker 2: You're thinking about things objects that no stuff and do
Speaker 2: stuff all bundled together. It changes how you model the problem.
Speaker 2: And Ruby, for instance, is open ended, you can invent
Speaker 2: brand new types of your own. What happens is your
Speaker 2: application sort of evolves into its own unique programming language
Speaker 2: that is specifically tailored to your domain. You could express
Speaker 2: ideas about your problem really naturally.
Speaker 1: Building your own language. That's a cool perspective. Okay, let's
Speaker 1: get practical. What are the core OD principles that help
Speaker 1: us build these flexible systems? Starting with big one Single
Speaker 1: responsibility principle SRP.
Speaker 2: Right SRP. The core idea sounds simple. A class should
Speaker 2: do the smallest possible useful thing. It should have a
Speaker 2: single responsibility. If a class is juggling too many jobs,
Speaker 2: It gets hard to reuse, things get tangled up. Changes
Speaker 2: in one responsibility break others. Yeah, it's a mess.
Speaker 1: Single responsibility sounds easy, But how do you actually know
Speaker 1: if a class is doing too much? Is there a
Speaker 1: practical test?
Speaker 2: Yeah, there's a neat trick. The book suggests you interrogate it.
Speaker 2: You try phrasing its methods as questions directed to the class.
Speaker 2: So please, mister gear, what is your ratio? That sounds
Speaker 2: totally normal. Gear should know its ratio. But please, mister gear,
Speaker 2: what is your tire size? That just sounds wrong? Doesn't it?
Speaker 2: A gear doesn't have a tire. If the question feels
Speaker 2: awkward or nonsensical, the class is probably doing things it
Speaker 2: shouldn't be. It has too many responsibilities, and this ties
Speaker 2: into asking yourself, what is the future cost of doing
Speaker 2: nothing today? Sometimes the best move is to not make
Speaker 2: a big design decision yet, wait until you know more.
Speaker 2: Making a class too early.
Speaker 1: Is just guessing, and this idea single responsibility. It applies
Speaker 1: to methods too, not just classes.
Speaker 2: Oh, absolutely, methods should also do one thing well. Keeping
Speaker 2: methods small and focused has huge benefits. It makes the
Speaker 2: code clearer, often eliminates the need for comments Because the
Speaker 2: method name says it all. It encourages reuse. Plus, small
Speaker 2: focus methods are much easier to move to a different
Speaker 2: class if you realize later that the responsibility belongs elsewhere.
Speaker 2: Sometimes you might even start by putting a small related
Speaker 2: responsibility inside a temporary structure like a structin Ruby, within
Speaker 2: the main class. It's like a holding pattern before deciding
Speaker 2: if it needs its own full class.
Speaker 1: Okay, so keep things focused, single responsibility. But these focused
Speaker 1: things need to work together, which brings us to dependencies
Speaker 1: the glue. The book calls it a little dot of blue,
Speaker 1: but warns that too much can strangle your application. How
Speaker 1: do we manage that glue?
Speaker 2: Managing dependencies is maybe the central challenge, and a key
Speaker 2: technique is dependency injection. Sounds fancy, but the idea is simple.
Speaker 2: Instead of an object creating its own collaborators, like Gear
Speaker 2: making its own wheel, you pass the collaborator in from
Speaker 2: the outside, so Gear just gets given a real object
Speaker 2: when it's created. The result, Gear actually becomes smarter because
Speaker 2: it knows less. It doesn't need to know how to
Speaker 2: create a wheel, or even that it is a wheel.
Speaker 2: All Gear cares about is that the object it receives
Speaker 2: responds to the messages it needs, like diameter. That's it
Speaker 2: immensely powerful for flexibility.
Speaker 1: That makes a lot of sense Practically speaking, how often
Speaker 1: do you find yourself using dependency injection? What's the biggest
Speaker 1: win you see from it?
Speaker 2: It becomes second nature. Really, you're constantly thinking, does this
Speaker 2: object really need to know how to create that other object?
Speaker 2: Usually the answer is no, So in jen acting dependencies,
Speaker 2: especially through the constructor, becomes standard practice. The biggest win,
Speaker 2: hands down, it's testability and substitutability. You can easily swap
Speaker 2: at a fake test wheel when testing gear, or a
Speaker 2: completely different kind of wheel later without changing gear at all,
Speaker 2: as long as it provides that diameter method and related tip,
Speaker 2: the book mentions using keyword arguments for initialization instead of
Speaker 2: relying on the order of arguments. You name them year
Speaker 2: dot new chane ring five bay good fifty two, cog
Speaker 2: eleven wheel dot mywheel. This makes the code much clearer
Speaker 2: and less brittle. If you add or change arguments later,
Speaker 2: no more remembering was chain ring first or second? Stepping back,
Speaker 2: the big idea here is depend on things that change
Speaker 2: less often than you do. Concrete classes, specific implementations, they
Speaker 2: tend to change. Abstract interfaces these common stable qualities they
Speaker 2: change much less often. So you want your pendencies pointing
Speaker 2: towards abstractions, not concrete details.
Speaker 1: Okay, focus responsibilities, manage dependencies, But how do these objects
Speaker 1: actually communicate effectively? How do we design those conversations well?
Speaker 2: Fundamentally, an object oriented application is defined by messages being
Speaker 2: sent between objects. The book uses a great analogy a
Speaker 2: restaurant kitchen. The menu is the public interface. Customers order
Speaker 2: off the menu. They don't need to know the complex
Speaker 2: process happening inside the kitchen. That's private. Your classes should
Speaker 2: be the same. A clear public interface, the menu of
Speaker 2: methods others can call and private internal.
Speaker 1: Workings and sequence diagrams come in handy here. The book
Speaker 1: calls them a perfect low cost way to experiment. It
Speaker 1: shifts thinking from what does this class do to who
Speaker 1: responds to this message exactly?
Speaker 2: That shift is crucial because, as Met says, you don't
Speaker 2: send messages because you have objects. You have objects because
Speaker 2: you send messages. Thinking about the messages first helps you
Speaker 2: discover the objects you actually need and what their public
Speaker 2: interfaces should.
Speaker 1: Be, which leads to another key idea, asking for what
Speaker 1: instead of telling how Take the example.
Speaker 2: Of a trip class and a mechanic object. Instead of
Speaker 2: trip telling mechanic every single step, check breaks and flight
Speaker 2: tires Lubrik chain the trips. You just ask the mechanic
Speaker 2: to do the what prepare trip and trust the mechanic
Speaker 2: to know how to do that. This is blind trust
Speaker 2: in ode. Objects collaborate by relying on each other's public
Speaker 2: interfaces without needing intimate knowledge of the internal details. It
Speaker 2: keeps them loosely coupled.
Speaker 1: And what about controlling access? Ruby has public protected private?
Speaker 1: Are these strict walls?
Speaker 2: Not really? You know, the book calls them flexible barriers
Speaker 2: in Ruby, and like some other languages, these aren't absolute guarantees.
Speaker 2: Because of that, many experienced rubyists actually prefer using comments
Speaker 2: or naming conventions like starting private method names with an
Speaker 2: underscore to signal intent, rather than relying solely on the
Speaker 2: key words. It's more about convention and trusting other developers.
Speaker 1: Okay, so flexible barriers. This sounds related to minimizing dependencies. Again.
Speaker 1: Are there common ways people mess up object communication anti patterns?
Speaker 2: Oh? Definitely. A classic one is violating the law of demeter.
Speaker 2: The simple version is only talk to your immediate neighbors,
Speaker 2: or even simpler, use only one dot. You see code
Speaker 2: like customer dot, bicycle, dot wheel, dot rotate. That's often
Speaker 2: called a train wreck.
Speaker 1: A train wreck, okay, why is that so bad?
Speaker 2: Because it deeply couples the code calling rotate to the
Speaker 2: entire internal structure of customer, bicycle and wheel. If any
Speaker 2: part of that chain changes, maybe bicycle no longer directly
Speaker 2: holds a wheel, this code breaks even though just wanted
Speaker 2: to rotate something. These chains are not transparent, reasonable, usable,
Speaker 2: or exemplary. They hide dependencies and make the code fragile.
Speaker 2: When you find yourself writing code like that, it's a
Speaker 2: strong signal. Listening to demeter tells you where your public
Speaker 2: interfaces are lacking. You probably need a method on bicycle
Speaker 2: itself like rotate wheel to hide that internal chain.
Speaker 1: That makes sense. It forces better interface design. Okay, let's
Speaker 1: shift gears to structuring behavior. How can we use dynamic
Speaker 1: behavior like duct typing to keep costs down?
Speaker 2: Right? Duck typing. The core idea is it's not what
Speaker 2: an object is that matters, it's what it does. If
Speaker 2: an object walks like a duck and quacks like a duck,
Speaker 2: than for the purpose of the code interacting with it.
Speaker 2: You can treat it as a duck. You care about
Speaker 2: its behavior. It's public interface, not its specific class name.
Speaker 2: The book uses a funny metaphor. An object is like
Speaker 2: a partygoer at a masquerade ball that changes masks. Its
Speaker 2: role can change depending on the context. A big warning
Speaker 2: sign the book points out is seeing a case statement
Speaker 2: that switches based on class names like caseobject dot class,
Speaker 2: when wheel, when gear. That kind of toad should grab
Speaker 2: your attention as if it were playing trumpets. It usually
Speaker 2: means there's a hidden duck type, a shared role or
Speaker 2: interface waiting to be discovered. For example, maybe different objects
Speaker 2: all know how to prepare themselves. Instead of checking their class,
Speaker 2: you just call prepare on them. You've identified a preparer
Speaker 2: duck type. This makes your code way more flexible. You
Speaker 2: can add new kinds of preparers later without changing the
Speaker 2: code that uses them. It's easier to extend but casting
Speaker 2: a veil over the underlying class, which is often a
Speaker 2: good thing.
Speaker 1: Okay, so that's dynamic behavior with ducks. What about more structured,
Speaker 1: hierarchical behavior using classical inheritance.
Speaker 2: Inheritance is basically automatic message delegation. It's like a family tree.
Speaker 2: A subclass automatically inherits behavior from its superclass. Subclasses are
Speaker 2: the specializations of their superclasses. A mountain bike is a
Speaker 2: special kind of bicycle. Finding the right abstraction level and
Speaker 2: inheritance can be tricky. The book suggests a strategy push
Speaker 2: everything down and then pull some things up. Start by
Speaker 2: making things specific in subclasses. Then identify common code or
Speaker 2: behavior and carefully pull it up into a shared superclass.
Speaker 2: Throughout this, you should constantly ask what will happen when
Speaker 2: I'm wrong. This encourages making conservative choices, keeping the cost
Speaker 2: of future changes low.
Speaker 1: Inheritance can get tricky, especially with super when subclasses have
Speaker 1: to remember to call their parents' implementation. That seems error prone.
Speaker 2: It definitely is, and that's where the template method pattern shines.
Speaker 2: Often using hook methods. Think of the superclass providing a
Speaker 2: template an overall algorithm, but it defines placeholder methods. The
Speaker 2: hooks like post initialized for customs, setup or local spares
Speaker 2: for bike specific parts. Subclasses can then override these hooks
Speaker 2: to plug in their specific behavior without needing to explicitly
Speaker 2: call super in the main method. This structure removes the
Speaker 2: need for subclasses to send super in many cases and
Speaker 2: dramatically reduces coupling between the layers. It makes the hierarchy
Speaker 2: much cleaner. General advice create shallow hierarchies. Deep inheritance trees
Speaker 2: build up a lot of dependencies and become hard to manage.
Speaker 2: Keep it simple if you can.
Speaker 1: All right, So we have ducks for dynamic behavior, inheritance
Speaker 1: for hierarchical specialization. What about combining distinct things together composition.
Speaker 2: Composition is about building complex objects by combining smaller, independent parts.
Speaker 2: It's a has a relationship. A car has an engine,
Speaker 2: has a wheels. The whole becomes more than the sum
Speaker 2: of its parts. Like notes forming music, the parts work together.
Speaker 1: And the book's recumbent bike example really drives this home
Speaker 1: right Well, it's.
Speaker 2: A fantastic example. Using inheritance. Adding support for a new
Speaker 2: recumbent bike took like nineteen lines of code chain just
Speaker 2: scattered around, But by refactoring to use composition. Where a
Speaker 2: bicycle has a parts object created by a parts factory,
Speaker 2: adding the recumbent bike became just three lines of configuration.
Speaker 2: That's it. It shows the power of composition for managing
Speaker 2: variations and adding new features. The decision rule of the
Speaker 2: book offers is pretty stark. If you cannot explicitly defend
Speaker 2: inheritance as a better solution, use composition.
Speaker 1: Inheritance carries more risk, so composition is generally preferred unless
Speaker 1: there's as strong is a relationship. What are the main
Speaker 1: benefits and drawbacks exactly?
Speaker 2: Composition usually leads to systems that are more transparent, usable, reasonable,
Speaker 2: and have a high tolerance for change. The parts are independent,
Speaker 2: easier to test, easier to swamp out. The main cost
Speaker 2: is that sometimes the overall structure the whole might feel
Speaker 2: less obvious than a clear inheritance try and you often
Speaker 2: need to write explicit delegation code to pass messages from
Speaker 2: the whole object to its parts. But the guideline holds.
Speaker 2: Use inheritance for is a relationships, true specialistation, low risk,
Speaker 2: shallow hierarchies. Use composition for has relationships. When an object
Speaker 2: is built from distinct parts, composition is usually the safer,
Speaker 2: more flexible.
Speaker 1: Bet Okay, awesome. We've designed our flexible system using these principles.
Speaker 1: Now how do we make sure it actually works and
Speaker 1: stays working? Testing and the book argues the purpose isn't
Speaker 1: just finding bugs.
Speaker 2: Not at all. Just like design, the true purpose of
Speaker 2: testing is to reduce costs. Good tests make changes cheaper
Speaker 2: and safer. Bad tests or hard to write tests increase costs.
Speaker 2: And there's a direct link. Tests are the canary in
Speaker 2: the coal mine when the design is bad. Testing is hard.
Speaker 2: If your tests are painful to write, it's often assign
Speaker 2: your design needs improving.
Speaker 1: That's a powerful connection. If testing is about reducing costs,
Speaker 1: how does that change what we test? How do we
Speaker 1: write cost effective tests?
Speaker 2: It radically shifts the focus away from just code coverage
Speaker 2: towards testing behavior and interfaces. The guidelines are pretty clear.
Speaker 2: Test incoming messages that change state, So incoming messages should
Speaker 2: be tested for the state they return. If you call
Speaker 2: deposit one hundred, test that the balance is now one
Speaker 2: hundred higher. Test outgoing commands. Outgoing command messages should be
Speaker 2: tested to ensure they get sent. If your object is
Speaker 2: supposed to tell another object to send mail, tests that
Speaker 2: the send to mail message was sent, not the details
Speaker 2: of how. But importantly, don't test outgoing queries. Outgoing query
Speaker 2: messages should not be tested if your object just asks
Speaker 2: another object for data like get chooser name and doesn't
Speaker 2: change anything. You don't need to test that interaction directly
Speaker 2: in the calling objects test testing the query method itself
Speaker 2: is enough. This approach minimizes the coupling between your tests
Speaker 2: and the implementation details, making tests less brittle and cheaper
Speaker 2: to maintain when the code changes. Makes sense.
Speaker 1: What about test doubles, stubs and mocks. They can sometimes
Speaker 1: create this alternate universe where tests pass but the real
Speaker 1: app is broken. Right.
Speaker 2: Yeah, that's a real danger. Your doubles might behave slightly
Speaker 2: differently than the real objects, leading to false confidence. The
Speaker 2: way to combat this is by using tests to document
Speaker 2: roles or interfaces. You can actually write tests for your
Speaker 2: test doubles. For example, create a diameterizable interface test. Any
Speaker 2: object claiming to be usable as a diameterizable thing, like
Speaker 2: your real wheel and your diameter double used in tests,
Speaker 2: must pass this interface test. This ensures your test doubles
Speaker 2: honor the interface this test expects and keeps them synchronized
Speaker 2: with the real object's behavior.
Speaker 1: Okay, interesting. What about testing private methods? Big debate there sometimes?
Speaker 2: Huh. Yes, The book's advice is wonderfully pragmatic and a
Speaker 2: bit cheeky. Never write them, and if you do, never
Speaker 2: ever test them, unless, of course, it makes sense to
Speaker 2: do so. Basically, testing private methods directly is usually a
Speaker 2: bad idea. It couples your test tightly to implementation details
Speaker 2: that should be free to change. If the public interface works,
Speaker 2: the privates are probably okay. However, the unless is important
Speaker 2: if you're dealing with a really complex private method, maybe
Speaker 2: as part of a temporary refactoring. Writing a targeted test
Speaker 2: for it can sometimes reduce the barriers to refactoring by
Speaker 2: giving you confidence as you clean it up. But treth
Speaker 2: those tests as temporary got it.
Speaker 1: And finally, testing code that uses inheritance where subclasses rely
Speaker 1: on shared superclass behavior.
Speaker 2: This is where those interface tests and what the book
Speaker 2: calls subclass responsibility tests really shine. You can write tests
Speaker 2: that define the contract a subclass must fulfill to work
Speaker 2: correctly with the superclass, template method or hooks. These tests
Speaker 2: take all of the pain out of testing the common
Speaker 2: behavior of subclasses. A new developer can come on, create
Speaker 2: a new subclass, run the tests, and the tests will
Speaker 2: tell them exactly which methods they need to implement correctly.
Speaker 2: It makes extending hierarchies much safer.
Speaker 1: Wow. What an amazing journey through arranging code We've covered responsibilities, dependencies, interfaces, ducts, inheritance,
Speaker 1: composition testing. It's a lot, but it connects beautifully, it.
Speaker 2: Really does, And that final insight from the book is
Speaker 2: so profound. Object oriented design is fractal. The central problem
Speaker 2: is to define an extensible way for objects to communicate,
Speaker 2: and at every level of magnification, this par looks the
Speaker 2: same whether you zoom out to the whole system or
Speaker 2: zoom into a single method interacting with its variables. That
Speaker 2: core challenge of clear communication and managed dependencies is always there.
Speaker 1: Fractal design. That's a fantastic way to think about it.
Speaker 1: It scales, and the book leaves us with a final
Speaker 1: piece of wisdom, almost a challenge. The rules of design
Speaker 1: are meant to be broken. Learning to break them well
Speaker 1: is a designer's greatest strength.
Speaker 2: Absolutely, It's not about blindly following rules. It's about understanding
Speaker 2: the principles behind the rules so you know when and
Speaker 2: how to bend them effectively. So yeah, persist, practice, experiment, imagine,
Speaker 2: do your best work, and all else will follow.
Speaker 1: So listening to all this, what's the one thing that
Speaker 1: really stands out to you? Maybe it's designing for that
Speaker 1: inevitable change or managing that glue of dependencies, or thinking
Speaker 1: about ducks, whatever it is, let that idea challenge how
Speaker 1: you think about building software next time See where it
Speaker 1: takes you