Causes of Decay: Architecture by Accident

September 4, 2008

AKA “Copy-paste Architecture”

I recently read a great answer to the question: “What is architecture?” Or rather, “What elements are architectural?” It’s an important question, and one seemingly impossible to answer: where do you draw the line between architecture and design? Architecture and just good practices? Architecture and “that’s just the way we did it”?

The answer is both obvious and disappointing: architecture is in the eye of the beholder. To an Enterprise Architect, the broad business concepts (domain entities) and system capabilities are architectural. To an Applications Architect, modules and layers, component interfaces and connection protocols are all architectural. To a Network Architect, network topologies and hardware are architectural. There is some overlap in what each cares about, but the point is that each type of architect, each software design and each developer has their own opinion about what is important to know.

But don’t despair! There is a way of defining exactly what is architectural to YOU! You see, what an architect does in the end is specify enough of the elements, patterns, technologies and structure of the system (or systems, or subsystem…) to guarantee that the implementation work that follows will satisfy the functional and non-functional requirements that it is supposed to. The rest is just design details that you can let a designer or developer decide for themselves – what’s important is that the performance, scalability, extensibility and all the other -ilities have been taken care of.

And that’s a healthy thing: to make all decisions up front for everyone else is not only boring, it would take forever, and take up volumes of hard-to-use and impossible-to-maintain documentation. What’s more, you run a big risk of overdesign.

Another definition, or characteristic, I have heard of architectural elements is that they are not generally one-off decisions regarding a specific implementation. What separates an architectural element from a design element is that the former represents a pattern that is meant to instruct and guide the design and implementation. Architecture in this sense is “reusable design”.

So what does all of this have to do with the decay of an application’s code base? Well, let’s say you’ve gone ahead and done your duty to specify those elements of your application that are architectural. Now it’s time for the team to write the software according to their whims, but within the constraints you have imposed. During this time, developers will naturally make decisions about the implementation. “Where should I put this class?” “Which package should I use?” “What should I call the class?” “Should I use a design pattern here? And which?”

Of course, none of these decisions are (you hope) important to the overall architecture of the system. But many of them are to solve common problems, and will likely need to be made again by other developers as they create and maintain the application. So, let’s say we have a simple “8-ball decision” (cf the commandment “Be decisive”) that a developer is making regarding the name of their Data Access Object for saving “Foo” to the database. It has an interface, but let’s say this particular implementation uses Hibernate to get the job done. Our hero could name the class:

  1. FooHibernateDAO
  2. FooDAOHibernate
  3. HibernateFooDAO
  4. HibernateFooDao
  5. FooHibernateDataAccessObject
  6. and so on…

So, our hero decides he likes option #4 because it sounds like an answer to the question “WHICH FooDao?”, and because all-caps acronyms run the risk of run-on acronymtences (Note: it’s always good to keep a justification for a Magic 8-ball Decision in your back pocket, because it ends arguments quicker than, “The 8-ball said its sources say yes.”). The next time our hero makes a DAO to save Bars, you can bet he’ll call it a “HibernateBarDao”.

In the following iteration (your process has iterations, right?), our heroine finds that she, too must write a DAO to save (you guessed it) Bazzes. Fortunately, she noticed that our hero has already done this before, so she copies over the basic code, kindly extracts any commonalities to a base class (you go, girl!), and calls her new implementation a “JdbcBazDao” (say it 10 times fast!). Although this was never specified in the original architecture, you now have the beginnings of a standard AND a framework:

  • All Data Access Objects should follow the naming convention: Implementation approach + Object type persisted + “Dao”
  • Common data management code is implemented in a BaseDao class, which all other DAOs should extend

Remember the previous definition that the architecture is “reusable design”? This is more or less what is happening here, even though it wasn’t intentionally specified by the architect. I call this “Architecture by Accident.” I also call this “Copy-paste Architecture” due to the way these implicit conventions often occur. But don’t get me wrong: there’s absolutely nothing wrong with the example I just gave you. It’s fantastic when standards and architecture and evolve properly on their own.

But let’s say a third developer, our sidekick, needs to write a DAO to save XPTOs. Because these “standards” were never made official, it’s quite possible the sidekick never got the memo. And after consulting his own Magic 8-ball (the official High School Musical edition! Wow!), decides to call his DAO “XPTOJDBCDAO” (DON’T try to say that one out loud). And one more for good measure: what better for saving XYZPDQs than an “XYZPDQJDBCDAO”?

Now enters into the story our villain. Actually, he’s not such a bad guy, but he does have his own incredibly important and convincing reasons for why his DAO must be called a YoMamaDataAccessObjectHibernate.

Our simple application now sports the classes:

  • HibernateFooDao
  • HibernateBarDao
  • JdbcBazDao
  • YoMamaDataAccessObjectHibernate

Confused yet? This is the problem with a “Copy-paste Architecture”. It isn’t so much who is making the decisions, or even why. The problem is that they aren’t being made consistently. As a result, your code base starts to rot. Clarity goes out the window. It’s hard to find classes you know are there because you can’t begin to guess what they’re called. You can’t do any generalized analysis, conversions or rule enforcement because there’s no one rule that can cover all the possibilities. And we’re just talking about the class names!

Of course, Architecture by Accident isn’t limited to just naming conventions. Design patterns, layering structures, library classes and infrastructure solutions are all at risk of having their wheels reinvented if there isn’t someone looking out to identify these patterns and maintain consistency. We’re all familiar with the results: increased complexity, reduced clarity and diminished productivity.

The only solution to this problem is collaboration. The whole team needs to recognize common patterns and solutions as they evolve, make them explicit, and let everyone know. But take my word for it: it won’t happen by accident.


Confessions of an overdesigner

September 2, 2008

I just read an interesting blog post from Guilherme Chapiewski on keeping software design simple (for those of you who don’t speak Portuguese, this might be a good time to try out that translation feature on Ubiquity. For those of you that are native Portuguese speakers, let this be my chance to set the record straight before bad practices become permanent: it’s pronounced “you-BIH-quih-tee”). This is obviously not the first time the need for design simplicity has been discussed in our field, and books are finally starting to come out regarding HOW to do this.

I personally am a confessed overdesigner, and I think it’s worthwhile discussing why this happens so often. No one goes out of their way to create more work for themselves or for others, at least explicitly (well, there are exceptions to this, as you will see). Below are the top reasons I’ve seen that cause a software developer or designer to go overboard on their design:

  1. Inexperience: developers who are just getting their feet wet with design patterns naturally want to sample the new patterns they have learned. There is a bit of healthy curiosity at work here, and a desire to get some real practice in implementing the pattern. But when I look at code like this, I can almost here it screaming, “Look, ma! New pattern!!” This is a tough problem to deal with, because developers really need to have practical experience to fully grok a pattern, but using it where it’s not needed isn’t necessarily what developers should learn, either. So, consider responding to this code with a firm, but loving, “That’s very nice, dear. Now go clean your room.”
  2. Too much experience: developers who have been around the block a few times, like myself, like to think we can “see” where the software is going. Using design patterns becomes automatic. They become so natural that the cost of implementing the pattern is almost the same as using a simpler implementation. But the cost to others may be much more significant in terms of understanding and maintaining the code, especially if there is no corresponding requirement for the flexibility (etc.) that the pattern affords. One team that I’ve been working with has adopted what they call the “red card” for me, whenever they see that I’m getting carried away with a design. Although it can be pretty embarassing, it’s a good opportunity to be humble, and to learn from your own mistakes. “That’s very nice, dear. Now go to your room.”
  3. To impress others: there can be a certain amount of prestige in saying that you used design patterns in your implementation. The same goes for coming up with “clever” (aka counterintuitive) solutions. This can be between developers, but it can crop up in other situations as well. I’ve personally never had to do this, but I have heard anecdotes of people being required to justify their designs to management by listing the patterns they are using. I have personally answered RFPs which ask for this. To me, this is ridiculous. Pretty much every large application I’ve worked on uses almost every design pattern in the GoF book in one way or another, and any design pattern without context is meaningless. Don’t let pride be a deciding factor in your design, nor should you use patterns gratuitously to fill up your forms.
  4. Because someone said to do it: when someone else does the design for you, or just provides some “helpful suggestions”, there is a risk that they will over-specify (cf. the next two entries). This is partly because the person doing the design doesn’t have to sully their hands with unpleasant details like implementing and maintaining their idea. A person who works only with abstractions will also naturally tend towards creating more abstractions, and always look to generalize concepts that currently apply to just a specific situation. It’s just what they DO. This is a fundamental problem with separating the act of design from the act of development. If your team finds this separation of duties useful, just make sure the designers (or architects, or whoever) are there to collaborate and see it through to the end.
  5. Fear of paying the cost later on: I often see myself leaning towards more complex designs to implement extra flexibility in the application when I am afraid that if we don’t do it now, there may not be enough time to do it later. This is silly, of course, since we will have to pay for it now, at a time when it’s not even needed. This fear is less ridiculous when the simple approach differs dramatically from the more complex implementation (e.g. when the two approaches require different architectures). A judgement call must be made in these cases, but very often the simple approach is enough if you can build in a single point of change where the more complex solution can be swapped in later. This is, after all, one of the main purposes of encapsulation and abstraction, and is the essence of the Open-Closed Principle.
  6. Fear that if you don’t do it, someone else will: this is one underlying cause of overdesign that I only recently discovered after many years of therapy and self-reflection. Somewhere in my subconscious was the little nagging question, “if not me, then WHO? If not now, then WHEN?” More than just an affirmation of my willingness to take on the challenge, there is also a hint of mistrust that others WON’T be so willing, or so capable. Better to overdesign now, than to underdesign later, right? This is a tough one to recognize, but in any case the “why” is less important than just recognizing that a solution can be simplified. If you find in yourself that this is one of the reasons why your are tending towards a big up-front design, remember the commandment: Empower developers. If you don’t trust your own team, you should figure out why and fix it, not work around it.
  7. Because it’s fun: OK, let’s just admit it. Programmers, architects, designers and the lot are all in it for the joy of puzzle-solving. A simple solution, like a video game that ends too soon, is just a let down. If you often find yourself caught up in the thrill of the sheer possibilities, go ahead and let your mind wander – that’s what brainstorming is all about. But make sure to timebox your thinking outside the box. When you’ve had your fun, root your feet solidly on the ground and pick a solution that actually fits your problem.

One quote in Guilherme’s post is certainly appropriate for an overdesigner like myself:

Beware though, keeping a design simple is hard work.

I am a natural when it comes to seeing abstractions, and generalizing problems. Whenever I’m approached with a new problem we haven’t solved before, I immediately try to name it. “Hmm… you say you need to temporarily store a file to keep it out of memory? Looks like we need a TemporaryMemoryManagementRepository component.” When I do this, I’m looking to shine some light on the problem itself that we’re trying to solve, rather than look immediately at the solution that was pulled out of a hat. This helps the “thinking outside the box” part of the exercise (“is this the ONLY way we can do this?”). I’m also looking for a way to encourage reuse, or at least to make sure that we only solve this problem once (check out “Causes of Decay: Copy-paste Architecture”, when I finally publish that article). But by doing so, I may have already created some confusion (“Hey, where’s that class that saves and reads temp files for us? It’s called WHAT?).

To me, overdesigning is the easy part. What really takes work is whittling that down to something not only reusable and extensible, but also easy to maintain. But the first step is admitting you have a problem…