The Agile Architect
In Agile, Simple Is Not...
In agile, we have the saying "Do the simplest thing possible." Our Agile Architect explains why simple is not so easy.
- By Mark J. Balbes, Ph.D.
Making something that is simple is extremely hard. To complicate matters, different people have their own personal definitions of simple. But before we talk about what simple is, let's talk about what simple is not.
Simple Is Not Doing the First Thing You Think of
Test Driven Development (TDD) is one of the crucial ingredients to a successful agile project. (It's good to do in non-agile projects, too, but that's for another column.)
TDD has a simple cadence. Write an automated unit test. Do the simplest thing possible to make the test pass while keeping all other tests passing. Refactor the code to a better design.
The idea is that you write the test to define the desired behavior. Then you write code to do whatever mechanical things you need to do to make the test pass. At this point, you aren't worried too much about good design, separation of responsibilities, the Open-Closed Principle or any other good Object Oriented design principles or patterns. You are doing what needs to be done to get the test to pass so that you can learn what you need to do to make the system behave correctly. The final step is to refactor what you've done into a good design. Refactoring maintains working software throughout the process of introducing a good design.
Unfortunately, in the agile world, the phrase "Do the simplest thing possible" has taken on a life of its own and is used to justify poor designs and implementations. When asked to justify their poor design, developers will push back with "Well, this is the simplest thing possible." While it may be true that it is the most straightforward or obvious thing possible, it almost certainly is not the simplest. Agile says that you aren't done at that point. You still have to refactor to a good, simple design.
It is unfortunate that the phrase "Do the simplest thing possible" was not coined as "Do the most straightforward thing possible. Then refactor to a simple design."
Simple Is Not Simplistic
Several years ago, I was having a conversation with another developer, let's call her Susan, about how to build a particular user interface. We needed an easy way for the user to interact with a series of semi-autonomous screens. We decided on a paradigm that would have a left pane for hierarchical navigation and a right pane for interactive content, similar to Windows Explorer but the right side would be interactive screens rather than files. One of the issues we had to deal with was that the user could change data on the right panel that would change the name, and therefore the location, of the document in the left panel's navigation hierarchy. Our job was to figure out how this would all work in the software and then build it.
I remember our design conversation vividly because of the ludicrous nature of it.
Susan started the conversation. She went to the white board and drew a UML Class box. She then started adding methods to it like rename(), save(), and updateNavigation(). It was clear that her one Class box was intended to represent this entire portion of the application.
I, of course, sprung into action. I started drawing my own class diagram. I created two parallel MVP patterns, one for the left pane and one for the right. They shared a common model, abstracted by interfaces, that could inform each side about changes to the other. A change to a document on the right side would update the model, which would fire an event to each Presenter in order to update the corresponding view. In order to facilitate navigation, I drew a higher-level Controller above both MVP triads. When the user selected a different node in the navigation window, the Controller would be informed and it would install a new view in the right side, wiring it up appropriately. All of this took me only a few minutes to draw. It was a straightforward application of some basic design patterns. Yet by the time I was done, I'd filled up the whiteboard with more than a dozen classes and interfaces, all connected with different lines to indicate inheritance and composition relationships.
And, of course, Susan looked directly at me and asked, "How is that simpler?"
My answer to her was that, while her diagram looked simple, it didn't answer any of the questions we had about how to build the capabilities. She had basically drawn a black box around the entire problem, masking any complexity.
My diagram, on the other hand, was a peek inside that black box. From the diagram, you could explain the responsibilities of each class, understand their relationships to each other and walk through how they collaborate in order to provide the required functionality.
Both of us thought our own solution was simpler and stared at each other with stunned incredulity.
Simple Is Not Expedient
We've been working on an Android application that connects with different kinds of radiation and chemical sensors. We had a requirement to create a demonstration mode that would display simulated sensors. This allows our customer to demonstrate the functionality of the application to potential users without needing actual sensors and sources.
We had a team discussion about how we should design it. We all agreed that the simulator would work independent of the actual application. In other words, the simulator should feed data to the app as if it was a real sensor and the app should behave as it would for any sensor that connects to it. In addition, I insisted that the demo mode should be built into the actual app in order to make it easy to use.
The capability was built on a branch in our Git repository. At the same time, we continued to add new capabilities to the application on other branches.
When the demo mode was completed on its branch, we merged in the changes from other branches and it promptly broke. It turned out that the developers had not simply fed data into the existing application. Since the demo mode was embedded in the app, they instantiated their own sensor model and fed data directly to it, bypassing the apps own object construction mechanism and essentially creating a parallel app running in the same process. When developers on other stories changed the object construction mechanism, their code no longer worked.
When asked why they did this, they said that it was the fastest way to get the story done. It wasn't. We ended up having to rebuild the demo mode per our original discussion to get it working again.
Simple Is Not Ignoring Challenges
Many years ago, I was sitting with a bunch of other developers talking about how to build a very sophisticated drug design tool that would allow chemists to search for different molecules based on specific properties. The design was to include a centralized back-end EJB server and a Java Swing thick client.
During our rather intense discussion about how the client was going to interact with the server and how we would determine what capabilities it would need using a top-down approach, one of the back-end developers said, "Why don't we just build the server and then see what it can do." (No, I am not making this up.)
Flash forward to the present day where I'm working with an agile team trying to cope with similar issues. They have a heterogeneous environment with different technologies in the different tiers of their system. Rather than focusing on building vertical, end-to-end stories, they have broken into sub-teams, each focused on its own tier with its own stories and initiatives. This makes it easier to ignore cross-tier dependencies and challenges. Or so they think.
While agile tells you not to try to solve tomorrow's problems today, it certainly does not tell you to ignore today's problems and deal with the fallout tomorrow.
I've spent a lot of time giving examples of unsimple (non-simple?) things. That begs the question, what is "simple?" It turns out that making something simple can be extremely difficult. We'll talk about that next time.