The next symptom of bad design is called fragility. Fragility is the tendency of a system to break in unexpected and unexplainable ways. You make a small change to one part of the system, some isolated part of the system far away, and something else breaks, something else that shouldn't have any connection to what you changed, but it breaks in an unexpected and unpredictable way. Imagine that you take your car into a garage and the repairman there looks at the car and you say to the repairman, yeah, I've got a problem with my electric windows. My electric windows do not go up and down properly. And the repairman looks at it and says, yes, I can fix that. Come back tomorrow. So you come back tomorrow and the repairman is happy and smiling. And he shows you that the windows go up and down just fine. And he hands you the keys to the car. You get into the car, you attempt to start the car, and the car will not start. And now you have to ask yourself, are you ever going back to that repairman again? He fixed the broken windows, but now the car doesn't work at all. This is the symptom of fragility. Fragility is the tendency of a system to break in unexplained ways when you touch it in others. Fragility is the symptom that is most terrifying to managers and customers because it is the one symptom that they can see very clearly. They ask you to do one little thing. They ask you to do one little thing and you do it, but then something else completely different breaks. And they can see that there's something wrong underneath the hood. There's something wrong with this software. And if this happens again and again, then they realize that the software developers have lost control of this software and frankly don't know what the hell they're doing anymore. That's a terrifying state for the customers and the managers to be in because they can no longer trust software developers to do anything. And the end result of this is official rigidity. Official rigidity is where the managers and the customers finally tell the developers to stop doing anything. Don't touch the software because every time you touch the software, it breaks in some unexpected way. And that is the final admission that the software has become worthless. software has become worthless. Another symptom of poor design is immobility. Immobility is the tendency of a system to have desirable modules, modules that would be desirable in other contexts and in other systems. But those modules are so interconnected that they cannot be removed and placed in other systems. We used to call this reuse, or it's the opposite of reuse, actually. Immobility is the inability to reuse components of software. And once again, this is a result of entanglement. A desirable element can be made immobile by making it depend on other things within the system that nobody else wants. You tie it so closely into the system that you cannot extract it and use it anywhere else. There are many other symptoms of poor design, but for the sake of brevity, let's turn towards the remedies. What can we do about these problems of design? Well, the primary issue with all of those symptoms was entanglement. The software became entangled. And here you see a system to the left which has rotted. And it has rotted because of the entanglements, the dependencies within the code. On the right, you see a system where the dependencies have been managed and kept orderly. And our goal in the solid principles and in software design in general is to build systems that have this kind of discipline in their interdependencies. We want to minimize the entanglement and allow our systems to have a minimum amount of coupling. The principles that we use help us to build firewalls, firewalls that prevent the propagation of change, firewalls that block interdependencies and prevent the system from becoming intertangled. And the primary mechanism that we use behind that is dependency inversion, dependency management and dependency inversion. Dependency management and dependency inversion. What is dependency inversion? What you're looking at on the screen is the architecture of every software project before 1970, roughly. Every software system before 1970 looked like this. And many software systems after 1970 looked like this as well. There's some main program at the top and then the main program has a number of high-level modules that it invokes. And then each of those high-level modules has lower-level modules that they invoke. And each of those lower-level modules have yet lower-level modules that they invoke. Now, the arrows you are looking at come in two forms. The red arrows, the solid red arrows, are source code dependencies. A source code dependency is a pound include, or an import, or a using statement. It is a statement within the source code module that references the name of another module. In Java, that would be an import statement. In C or C++, that is a pound include statement. In C sharp, it is a using statement. In Ruby, it is a require statement. Every language has one of these statements that allows you to reference another module. of these statements that allows you to reference another module. The green dotted arrows are the flow of control, the runtime dependencies, the dependencies that exist during execution. Main calls HL1, and the flow of control moves from main into HL1. And then the flow of control leaves HL1 and goes into ML1 and ML2. And then the flow of control leaves ML1 and goes into LL1 and LL2. That is the runtime dependency. And if you look at this diagram, you will realize that the runtime dependencies and the source code dependencies are parallel. They are the same. This was true of every software system prior to the 1960s and the 1970s and most software systems since then. But in the intervening time, something was invented. And what was invented was object-oriented design. In object-oriented design, something else can happen. Here you see HL1. And notice the runtime dependency, that dotted arrow, is going towards ML1. So the high-level module, high-level 1, is calling the lower-level module ML1. But now look at the red arrows. The red arrows are fascinating. The red arrows are fascinating. HL1 depends not on ML1, but on some interface, that I thing. That's an interface, a polymorphic interface, like a Java interface or a C-sharp interface. Or in C++, it would be an abstract class. And within that interface, there is declared a method F. That is the method that HL1 wants to call. But ML1, which contains the implementation of F, depends on the interface through inheritance. isosceles triangle arrow, that open arrowhead there that points to the left. That is an inheritance relationship. ML1 implements the interface. And so the source code dependency opposes the flow of control. Object oriented programming gave us this power. It is the power to take source code dependencies and invert them against the flow of control. And this is an immense amount of power. To show you just how much power that is, I want to do a simple example for you.