Several weeks ago (in this post), I said that there would be a delay before I wrote more about the topic of persistence ignorance and the entity framework because I wanted to spend some time learning more about the viewpoint of those folks asking for persistence ignorance. As seems always to be the case, lots of things have intruded and the delay is growing longer than I'd like, but I have made some progress. One part of that has been to read the book Agile Principles, Patterns, and Practices in C# by Martin & Martin (and to purchase a stack of additional books on related subjects that I intend to read).
Even this initial foray has been very informative, and when I was finished I wrote up the following report to share with folks on my team. It occurred to me that others might find some value in it as well.
Note that this is something of a "no holds barred" review. Please don't read too much into any criticism. Overall I found the book definitely a worthwhile read, and I highly recommend it.
Agile Principles, Patterns, and Practices in C# by Martin & Martin
This book starts off with a section that is a general overview of the ideas and principles behind Agile methodologies which I found very helpful--in about 50 pages there's a good overview, and then there's this fairly extensive (another 50 pages or so) case study that is a story of how to developers followed an Agile process to build a program to track bowling scores. This case study doesn't just talk about how they worked together but it focuses a lot on the design and how it started and evolved and what they finally ended up with. The pattern of principles followed by case studies is used throughout the book, and it's an interesting one, but I have to admit that I didn't get as much from the case studies--I felt like I was able to glean the important points by reading the sections on principles and then at most skimming the case studies.
The second section of the book (approx. 75 pages) was also very informative. It talks about design principles and patterns that are generally good design for any methodology but are especially highly valued in agile. In many cases these are relatively common sense kinds of things we would just consider good design practices but they have been better named and codified here which makes it more likely that folks will apply them in all the places they would be useful. In a few cases there are principles that are new to me, and I'm still mulling over what I think about them.
Next there's a fairly large section (another 75 pages or so) on UML which I found not terribly helpful given my familiarity with UML from other sources. This is followed by a very interesting case study about designing a solution for the control system of a coffee maker. The interesting part about this study is that it starts out with an initial design many people might come up with for such a system and then demonstrates the ways in which that design does not follow the design principles. So they go on to describe a much better design that follows the principles but which would not be the first intuitive approach most folks would take unless they were concentrating on the principles. This example may be a bit longer than necessary, but it provides good motivation for the overall design principles.
The final sections (approx. 375 pages) cover a very large and involved case study around a payroll system and interleave case study discussion with general design info. I was not ready to invest what it would take to really get a lot of value out of the payroll case study, but skimming through the interleaved chapters on principles did seem useful.
While I have mixed reactions to the use of "agile methods" at Microsoft based on some very light exposure to folks who either didn't understand or articulate well the principles or who in general were perverting the process, as with many things the underlying ideas seem to be very sound and worth considering. There are a set of 4 values that inspire 12 principles (largely copied directly from the book).
· Individuals and interactions over processes and tools.
· Working software over comprehensive documentation.
· Customer collaboration over contract negotiation.
· Responding to change over following a plan.
· Highest priority is to satisfy the customer through early and continuous delivery of valuable software. Studies show that the less functional the initial delivery, the higher the quality in the final delivery and the more frequent the deliveries, the higher the final quality.
· Welcome changing requirements, even late in development. Harness change for the customer's competitive advantage. This means you must adopt a lot of other practices and work hard to make sure that everything is flexible so that change can happen inexpensively.
· Deliver working software frequently, from a couple of weeks to a couple of months (preferably weeks).
· Businesspeople and developers must work together daily throughout the project.
· Build projects around motivated individuals. Give them the environment and support they need, and trust them to get the job done.
· The most efficient and effective method of conveying information to and within a development team is face-to-face conversation.
· Working software is the primary measure of progress.
· Agile processes promote sustainable development. The sponsors, developers and users should be able to maintain a constant pace indefinitely.
· Continuous attention to technical excellence and good design enhances agility.
· Simplicity--the art of maximizing the amount of work not done--is essential.
· The best architectures, requirements, and designs emerge from self-organizing teams.
· At regular intervals, the team reflects on how to become more effective, then tunes and adjusts its behavior accordingly.
One kind of agile development is extreme programming which involves a series of practices including pair programming (which in this sense is not just working in pairs but also involves an open work environment, rotating partners and changing ownership over sections of the code), test-driven development, continuous integration, etc. This work style clearly seems aimed at a particular kind of end-user application (like many business applications which probably make up 95% of all software in the world) where a development team is trying to write business software for an audience of domain experts who will use it. Key parts of this story involve short cycles (iterations or sprints) and some specific practices around how to decide what goes into each iteration--these are planned each time using data about how things went the last time. I think this style of development could work very well for the kinds of applications and teams that they are targeting (which almost certainly includes various projects at Microsoft). For many MS projects delivering platforms with broader scale, longer timeframes, less direct customer connection, a variety of individuals of differing abilities and contribution types, etc., though, these practices as described are probably too narrow to follow to the letter of the law. In any case, though, there are certainly some ideas to consider.
Test-driven development is described in some detail. This is not new to me, but every time I read about it I get a stronger feel for how right it is and why. Some amount of reiteration is probably good to help all of us get off our rears and start doing it. Some other interesting points are the impact on planning ahead for testability will have on designs and the value of investigating and employing specific technologies which can help here like mock objects.
There's also a discussion about refactoring, but I found Fowler’s book on that topic which I read a while back more enlightening (if anyone’s interested, I can probably dig out and post a report on that book as well). I will, however, throw in one quote from this book which I particularly like:
Refactoring is like cleaning up the kitchen after dinner. The first time you skip cleaning up, you are done with dinner sooner. But the lack of clean dishes and clear working space makes dinner take longer to prepare the next day. This makes you want to skip cleaning again. Indeed, you can always finish dinner faster *today* if you skip cleaning. But the mess builds and builds. Eventually, you are spending an inordinate amount of time hunting for the right cooking utensils, chiseling the encrusted dried food off the dishes, scrubbing them down so they are suitable to cook with, and so on. Dinner takes forever. Skipping the cleanup does not relaly make dinner go more quickly.
The goal of refactoring… is to clean your code every day, every hour, and every minute. We don't want the mess to build. … I can't stress this enough. All the principles and patterns in this book come to naught if the code they are used within is a mess. Before investing in principles and patterns, invest in clean code.
This is the really interesting part of the book, and off-and-on, it's the primary theme for the remainder of the text. First, though, there's a discussion of "design smells" which is familiar from the refactoring book. Here's the list for this book:
· Rigidity. The tendency for software to be difficult to change, even in simple ways.
· Fragility. The tendency of a program to break in many places when a single change is made.
· Immobility. When a design contains parts that could be useful in other systems, but the effort and risk involved with separating those parts from the original system are too great.
· Viscosity. Generally, changes can be made in multiple ways--some which preserve the design, and some which are hacks. If it is easy to do the wrong thing but difficult to do the right thing, that's viscosity in design. Similarly, viscosity can show up in the development environment when tools or processes are inefficient--this can cause developers to batch changes to minimize the impact of long compile times (which goes in the face of refactoring and other good practices), etc.
· Needless Complexity. Often caused by attempts to anticipate future requirements.
· Needless Repetition. Cut & paste is disastrous in code.
· Opacity. The tendency of a module to be difficult to understand.
Changing requirements are often blamed for software rot, but agile teams thrive on change. They invest little up front so they are not vested in an aging initial design; they keep the design of the system as clean and simple as possible and back it up with lots of unit and acceptance tests. This keeps the design flexible and easy to change. The team then takes advantage of that flexibility in order to continuously improve the design.
This quote is probably the biggest insight of the book for me:
The attitude that agile developers have toward the design of the software is the same attitude that surgeons have toward sterile procedure. Sterile procedure is what makes surgery *possible*. Without it, the risk of infection would be far too high to tolerate. Agile developers feel the same way about their designs. The risk of letting even the tiniest bit of rot begin is too high to tolerate.
Later in the book there's a case study that shows how an initial design had to undergo radical change to support some new requirements. The conclusion had another telling quote:
…it is tempting to blame the problem on insufficient analysis. Poppycock! There is no such thing as *enough* analysis. No matter how much time you spend trying to figure out the perfect software structure, you will always find that the customer introduces a change that violates that structure. There is no escape from this. There are no perfect structures. There are only structures that try to balance the current costs and benefits. Over time, those structures must change as the requirements of the system change. The trick to managing that change is to keep the system as simple and as flexible as possible.
I'm still pondering how this plays out in the world of frameworks like what we on the ado.net/entity framework team are building, but it clearly makes sense in the world of applications where most of our customers live. Everything we can do to assist them in building simple, clean, flexible solutions is super valuable, and anytime our framework forces requirements upon them which violate the principles, we have a problem.
· The Single-Responsibility Principle (SRP) - A class should have only one reason to change. If you can think of more than one motive for changing a class, that class has more than one responsibility. Persistence is one of the key examples given as a common violation of SRP where classes end up containing both business rules and persistence control. Business rules tend to change frequently, persistence changes as well (maybe not frequently, but it does change) and does so on a completely asynchronous schedule.
· The Open/Closed Principle (OCP) - Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification. That is, the behavior can be extended as requirements change, but extending the behavior does not result in changes to the source, or binary, code of the module. Abstraction is the key, but it should be applied only to those parts of the system that exhibit frequent change. Resisting premature abstraction is as important as abstraction itself.
· The Liskov Substitution Principle (LSP) - Subtypes must be substitutable for their base types. A symptom of violated LSP is runtime type checking by consumers of a class hierarchy. This principle leads to a very important conclusion: A model, viewed in isolation, cannot be meaningfully validated. The validity of a model can be expressed only in terms of its clients. This principle is one of the prime enablers of the previous principle (OCP).
· The Dependency-Inversion Principle (DIP) - High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend upon details. Details should depend upon abstractions. To make this more concrete, instead of an object which implements business logic depending on lower-level components for persistence (as we might normally expect with high-level components at the top of a layered stack--procedural style design), we should work to isolate those objects with interface contracts so that changes to persistence code will not affect the important business logic asset.
· The Interface Segregation Principle (ISP) - Clients should not be forced to depend on methods they do not use. Class surface area should be broken into client-specific interfaces.
· Package/Component Design Principles: There are a series of related design principles around how to set component boundaries…
i. The Reuse/Release Equivalence Principle (REP) - The granule of reuse is the granule of release.
ii. The Common Reuse Principle (CRP) - The classes in a component are reused together. If you reuse one of the classes in a component, you reuse them all.
iii. The Common Closure Principle (CCP) - The classes in a component should be closed together against the same kinds of changes. A change that affects a component affects all the classes in that component and no other components.
iv. The Acyclic Dependencies Principle (ADP) - Allow no cycles in the component dependency graph.
v. The Stable-Abstractions Principle (SAP) - A component should be as abstract as it is stable. That is, a stable component should also be abstract so that its stability does not prevent it from being extended. On the other hand, an instable component should be concrete, since its instability allows the concrete code within it to be easily changed.
A Detour into UML
UML is a tool and nothing more. Use it to envision code and communicate. Do not use it for CASE, etc. There was a lot more in this section, but it seemed to me a pretty standard treatment of the topic.
Next in the book is a series of chapters which describe and give examples of various patterns which may be used in support of the design principles. These included Command and Active Object, Template Method, Strategy, Singleton and Monostate, Null Object, Factory, Composite, Observer, Abstract Server, Adapter, Bridge, Proxy and Gateway, Extension Object, Visitor, Decorator, Façade, Finite State Machines, Model/View/Presenter, etc.
By and large the discussions of these patterns are detailed with very good motivations for when to use them as well as thorough code samples of them in action (all written in C#). In some cases, there is specific discussion of database persistence strategies and even mention of ado.net. Often the pattern descriptions refer back to the design principles so they also help reinforce and explain them. In general, though, the pattern content is more a compendium of tricks to have up your sleeve rather than fundamental principles that apply most everywhere, so I'll refer you to the book (as well as to other pattern books) for more details.