Monday, June 30, 2008

Flexibility Is Bad Design

Ground Rules
Since this is the Internet, the land of mistranslation, I'm going to get a few things out of the way at the beginning.

Design has many meanings and contexts. For this post, I'm going to use the following definition of design:



design
noun
The purposeful or inventive arrangement of parts or details: the aerodynamic design
of an automobile; furniture of simple but elegant design.




That is, I'm going to talk about the particular design that represents a piece of application/service software.

Also, this post refers only to application/service software. Libraries have entirely different goals.

Get On With It Already
Alright, now that we're on the same page, I'm going to tell you that flexibility in application code is bad design.

The job of a programmer is to make a realization of some type of specification. There are good and bad specifications to be sure. However, the end result of all application programming should be a piece of software that does its intended job. This sounds completely logical, and many developers will pay lip service to it. But time and time again I will have a conversation with a developer who will start going on and on about flexibility for situations that might possibly happen in the future.

STOP IT!

What these developers mean by "flexibility" is that the code allows you to do something that currently has no plan to be used. Where is the flexibility in code that has every path and possible use covered by the application? That's just code that solves the problem. For these developers, true flexibility comes from solving problems that do not need to be solved.

An Analogy
In my opinion, good design is whatever results in a shipping product, using the least amount of code, in the least amount of time. This definition won't allow you to say what the best design is, but it does give you a rubric to pit two actual designs against each other. Less code is always better than more code because it will always be more readable, easier to digest, and easier to maintain. Note for the dense, I'm not talking about deliberately obtuse or obfuscated code. Least amount of time is obvious. If you aren't interested in shipping, then fiddle with your code all you want, be my guest. Most of us want this software out the door yesterday.

This definition of good design also lends itself well to a grocery shopping analogy. Everyone knows you're supposed to buy based on the lowest per unit price of an item right? If you can buy 30 apples for 10 cents a piece, and 10 apples for 20 cents apiece, then the 30 apples is the way to go right? Obviously this isn't always correct. If you are only going to eat 1 apple a day, and the apples go bad after 10 days, you're now overpaid $1 if you bought the 30 pack.

The same is true of "flexibility" in code. Since this "flexibility" in code is related to the future, that means you're only going to receive value out of it if it eventually gets used. Until then, it is dead weight. It is more code that must be read, understood, and maintained. It is a minus on the overall design because it is work spent for no gain. You could mark it as a plus to possibly be received at a later date, but then you would be building the Enron of code.

The Most Flexible Code Ever
So maybe I haven't convinced you to stop trying to write flexible code that will also make toast in the morning. I have a little secret to let you in on.

The most flexible code already exists!

Seriously, go look it up. The amazing thing about it, is we all use it everyday. That's right, it's called a Turing complete programming language. If your goal is to create the ultimate in flexible code, then I think Alan Turing, and everyone who has written a programming language has got you beat.

These guys have written code that allows you to make operating systems, databases, windowing toolkits, financial software, even the software that you are working on right now. I guess it's easy to forget that programming languages are software too.

If you think you can make a better programming language, then I would love to try it out. But please, all you architecture astronauts, let's stop worrying about the undefined future and start shipping great software.

15 comments:

Jason DeFontes said...

I agree that flexibility for its own sake is highly suspect. On the other hand, one does have the benefit of past experience with their particular problem domain and can probably make a better than random guess about the probability of certain types of requirements changes in the future. Given that, you can choose to build in some flexibility as a hedge against future changes, especially if the upfront cost difference is minimal. If you guess correctly then you might win a future feature request for free.

Phil Larson said...

I probably should have clarified, but I am not advocating going through your code line by line and trying to make it as inflexible as possible. In the process of solving a problem, if you happen to have a general design appear, then I wouldn't tell you to rip it out because it's evil.

The average ability to accurately predict future code issues is fairly low. Many programmers think they can solve anything in a weekend if they just put their mind to it. The same thing applies to The Future. The fact is, the human problems that software is created to solve rarely map directly onto the platonic software design ideals that people worship.

If you write less code, it will always be easier to maintain. It will be emotionally easier to rewrite parts of it, and it will be easier to understand.

How many times have you taken over someone else's code, and you end up just rewriting things instead of adapting their design?

Tom Ritchford said...

As the XP people point out, when you design for generality, half the time when the actual request for new functionality comes down the line, you generalized in entirely the wrong way and all that code you wrote is useless.

Phil Larson said...

I agree, I also think that developers tend to generalize for whatever they easily can, not what they should. And that almost always leads to problems down the road.

Wolter said...

Like absolutely everything else in the programming multiverse, flexibility is perfectly valid when implemented intelligently.

Example:

I work for a large financial institution. I was initially temp hired for a 3 month project, writing an app to view pending funds transfers, change their status, and issue jobs to generate reports of those transfers in certain cases.

As you experienced programmers have probably guessed, some new requirements came along. It's now been 3 years, I'm heading a team of developers, and there's another 4 man years work to be done (and the mountain keeps growing).

I knew from the start that there was no way they'd simply stop after the initial app, and so I built a full 3-tiered framework with some flexibility in mind, based on what I guessed they would be asking for in the future. And it paid off in spades. In fact, it worked a little too well.

I initially designed it to handle multiple sub-applications (around 10 or so), but I never dreamed it would turn into 8 MB of code, in a giant web app that runs 27 different, loosely related sub-applications, some internal, some later made external, requiring godly amounts of memory for some functions. Needless to say, this is something that I couldn't have foreseen with any decent amount of precision, so my initial design was correct. I am, however, pushing for separation of the sub-applications into their own app, with common business and data logic either in libraries or in web services.

I was also careful to make the currency units flexible. Initially, it was only USD and GBP (and they assured me that it would remain so), but sure enough, moods changed, and they were suddenly hot to trot about euros, wan, and yen. Thanks to the initial flexibility, it took all of 3 hours to modify this beast of an app to run with all of those currencies flawlessly. In contrast, most of the other apps built by the company required MONTHS of work to refactor. In total, I'd guess it cost them more than 2 man years to convert all of their applications, with mine contributing 2 man weeks by the time all the regression testing was done.

The point of all this is, bad designs come from bad designers, and not from a particular design philosophy. A good craftsman knows his tools.

shevy said...

I agree that a big focus on flexibility as such can make things worse, but on the other hand being too rigid is something I dont really like either.

Right now I use the shortest route for a possible solution but try to stay in a position where I can do changes quickly (thus having a somewhat flexible solution)

I think the language that is used is very important too.

Mike said...

Spot on.

Make your choices, learn to live with them, get on with it.

Joe Magly said...

I agree and disagree at the same time. Part of being an architect, particularly when dealing with large cross functional applications, is identifying where you just want to get the job done and where you need flexibility. Too much in either direction can be over limiting and cause more work down the road.

Ideally when designing an application a good architect will have a solid understanding of both technical and business drivers as well as product direction and needs. The reasons for putting flexibility in some points in an application can vary from extensibility or standards support to security or scalability factors.

In my experience most large applications tend to follow the 80/20 rule where 80% of the code written is purpose built and streamlined for the job at hand while the 20% is tuned to be flexible to support expressed business needs, goals or desires (not just some random "what if" scenario). In an enterprise where you have a good commodity infrastructure to build from that ratio may approach 90/10 or even 95/5.

I find the key is to know what the business is looking for and I will spec areas of flexibility where I know it will be necessary in the next 12-18 month cycle. Anything beyond that can be addressed in a later release if the need solidifies into something more than a "neat idea".

Tia Lobo said...

While I agree with the basic premise, I disagree with the statement Less code is always better than more code because it will always be more readable, easier to digest, and easier to maintain.

Ever try to reverse-engineer a Perl wizards regex? A single line of code may implement an entire OS and five applications (exaggeration). The same goes for finite state machines. Elegant as all get out with thin, stream-lined code. But, unless it's very, very well documented it can take weeks to reverse engineer.

I don't like to use code length as any kind of metric. Maybe we need to develop standards for readability.

James Justin Harrell said...

In my opinion, good design is whatever results in a shipping product, using the least amount of code, in the least amount of time.

Does that code not need to be maintained? Or perhaps the code you produce just isn't maintainable?

M@ said...

I am afraid I don't totally agree with you, but I do see your point. However, the statement that intentionally making flexible code is inherently bad is not right.

I think it comes down to what your job is. If you are an application programmer and your job is to get features x, y, and z out the door error free, then you are absolutely correct. Just do it the most direct way possible.

But this view doesn't consider the fact that very few applications stop at release 1. If you are an application architect (or just a thoughtful developer who realizes that you will be the one maintaining the code), experience has taught you that there will be a realease 1.1, 1.2....3.5, etc. So why does this matter?

Simple example: Your application is required to display an image in the UI. This image isn't going to change, so according to your premise, I should just hard code the path (example: /images/image1.jpg). All fine and good, and I agree with you so far. But now, a few weeks down the road, I need to display a second image (image2.jpg) in the UI. Since I hard coded it, and it made sense the first time, I hard code it again. To do this, I have to also duplicate the code that loads the image...maybe four lines.

Now, a year later, I am displaying 15 images, and each one has its own hard coded block of code, so it now consists of 60 lines of code. This is not turning out to be less code.

On the other hand, if I had realized that the problem was eventually going to exist when the second image requirement came in, I could have created a function "ShowImage()" and passed in the image name for each of the 15 images, thereby adding flexibility.

This is a small, pointless example but I hope it illustrates my point. Most developers who lean towards flexible development do it because the best way to do it one time is usually not the best way to do it 1000 times. There is a tipping point where not making it flexible turns out to be more code, more work, and harder to maintain.

For me, that tipping point is the second time I have to write the exact same code with a different value. My rule is "If you do it twice, make it a function". The only other choice is "copy & paste code reuse", which most will agree is a bad idea.

I believe that this rule has saved much refactoring and code bloat over the years.

M@

abby said...

how very true! Some of the "coolest" designs I've worked on ("cool" for their infinite flexibility - ooooh look, and if you just tweak this one variable here ALL these things just fall into place) have turned into the biggest nightmares to maintain and evolve. I think YAGNI has to win out in the ultimate battle.
--
The Hacker Chick Blog

KenDowns said...

Great post. At my shop I forbid designing against hypothetical possibilities. You never get it right because there is no demand for it (hence no feedback), and it gets in the way when the real demand shows up and is different.

Real flexibility is found in a strong architecture. Features should be handled case-by-case

João Ribeiro da Silva said...

I agree with both arguments.

What I think you are doing here is generalizing the situation.

More or less flexible code may be necessary in a bigger or smaller degree.

If you know that your application may need some features soon you can prepare it for those features today, so tomorrow it will be easyer to implement those new feature, this is called thinking ahead.

In other applications it makes no sense at all and therefore you are right.

As each person has different needs so software and many other things.

Srivats said...

The design of Apples is right - However, it depends on who uses the application and the purpose of using it - If the 30 apples for 10 cents are bought by reseller, it would make sense. Design flexibility for its own sake - to take the words of Jason - is not advisable: however, while this is targetted at different user types, it stands to gain