While writing the code or during design do you try to generalize the problem at the first instance itself or try to solve that very specific problem. I am asking this because, trying to generalize the problem tends to complicate the things (which may not be necessary) and on the otherhand it will be very difficult to extend the specific solution if there is a change in the requirement. I guess the solution is to find the middle path which is easier said than done. How do you tackle this type of problem ? If you start generalizing it at what point of time you know that this much of generalization is sufficient ?

13 Answers
13

Too often when you try to design for the future, your predictions about future needs turn out to be wrong. It's usually better to refactor when you actually know how the needs have changed than to overdesign your system on day one. At the same time, don't shoot yourself in the foot, either. There's certainly a middle ground, and knowing where that is is more art than science.

Are you familiar with Agile? One of the big principles of Agile is YAGNI. I find that's the best way to approach things.

"You aren't gonna need it" ...is a principle of extreme programming (XP) that states a programmer should not add functionality until deemed necessary. Ron Jeffries writes, "Always implement things when you actually need them, never when you just foresee that you need them."

...YAGNI is a principle behind the XP practice of "do the simplest thing that could possibly work" (DTSTTCPW). It is meant to be used in combination with several other practices, such as continuous refactoring, continuous automated unit testing and continuous integration. Used without continuous refactoring, it could lead to messy code and massive rework. Continuous refactoring in turn relies on automated unit tests as a safety net (to detect unforeseen bugs) and continuous integration to prevent wider integration problems...

YAGNI is not universally accepted as a valid principle, even in combination with the supporting practices. The need for combining it with the supporting practices, rather than using it standalone, is part of the original definition of XP...

"Simplicity--the art of maximizing the amount of work not done--is essential," would apply to YAGNI and some other agile practices.
–
tvanfossonMar 24 '09 at 18:27

While it doesn't specifically say "YAGNI" in the manifesto, I think that they are very much in line with each other.
–
Matt GrandeMar 24 '09 at 18:42

2

@Jens and @Matt, YAGNI, is in Agile by means of XP being bundled as an "agile" methodology. As mentioned in the Wikipedia article the YAGNI principle was developed by Ron Jeffries as a part of the XP core practises.
–
Rob WellsJun 26 '09 at 13:38

I spend some time upfront thinking about the general direction of the design - not too much, but enough to basically sketch out a high-level overview. I then follow a story-based agile methodology using TDD to develop solutions for individual stories. As I'm implementing via TDD I keep my high-level overview in mind and either (a) direct my particular implementations to follow the high-level overview or (b) refactor (and improve) my high-level understanding/direction based on what I learn during testing/implementation.

I think it's a mistake to do no planning upfront, but it's probably a bigger one to do too much. As much as possible I like to let me experience guide me in the big picture and then let the design grow organically along the lines I've laid out in my mind for how the application is going to develop. Using TDD I find that the design itself is forced into better design principles (decoupled, single responsibility, etc) and is more malleable with respect to changes than if I try to pre-conceive the whole and fit the development into it.

This is probably one of the most difficult parts of software development because you need to walk the line between "YAGNI" and "PYIAC" (Paint Yourself Into A Corner).

It's pretty easy to say "don't write a feature unless you need it". The hard part is designing your code so that you can easily add features later when you do need them.

The key is to be able to design an extensible architecture where you don't write any more code than what you currently need. The ability to do this well really comes from a whole lot of experience (and pain).

What you're trying to deal with has to do with reuse (i.e. the generalization of a problem that you're dealing with now so you can reuse the work (code) in the future). I've said it before and I'll link to it again.

I think I've heard other people say something to the effect of:

I solve the problem the first time.
When I repeat my solution the first
time, I note it. When I repeat it
again, I refactor.

A good design accomodates future changes and is definitely worth going for. Consider the UNIX operating system and its "everything is a file philosophy". That design decision was taken not to meet some immediate need but with a view to future requirements. One shudders to think what an operating system based on an "agile" design would look like.

For me, it depends on how much time you have before a deadline. If you're close to deadline, bring out your programmers duct tape and throw a one-time fix on it. However, if you do this, make sure to put a comment that it's just duct tape and needs to be fixed properly at a later point.

In general though, I would recommend applying a general fix to the problem, unless you can be sure that this problem is just a one time thing.

The philosophy of YAGNI, You Ain't Gonna Need It, can be summarized with (from the article):

According to those who advocate the YAGNI approach, the temptation to write code that is not necessary at the moment, but might be in the future, has the following disadvantages:

The time spent is taken from adding, testing or improving necessary functionality.

The new features must be debugged, documented, and supported.

Any new feature imposes constraints on what can be done in the future, so an unnecessary feature now may prevent implementing a necessary feature later.

Until the feature is actually needed, it is difficult to fully define what it should do and to test it. If the new feature is not properly defined and tested, it may not work right, even if it eventually is needed.

It leads to code bloat; the software becomes larger and more complicated.

Unless there are specifications and some kind of revision control, the feature may not be known to programmers who could make use of it.

Adding the new feature may suggest other new features. If these new features are implemented as well, this may result in a snowball effect towards creeping featurism.

Design for "now + 1". That means, solve the immediate problem at hand, and build in enough functionality so that next time they ask for a change, you've already got it half way (or more) done and have the choice of either a) solving it immediately and refactoring later, or b) solving "now + 1" again (with the "now" half done)

This does depend project, and, in short, experience will teach you what the "+1" is.

I think it depends on the size and impact of a project. For small projects that aren't mission-critical, I tend to go with an incremental approach, solving a problem at hand with design considerations for big challenges more so in the back of my mind.

For larger "mission critical" projects, I tend to spend more time in the design phase up-front, and deal with implementation issues in a more incremental approach.

If you know there are changes coming down the road that will benefit from the generalization, I say you should generalize it when it is first implemented. For instance, my company often rolls out new features in phases, so I try to design in the flexibility to support the more advanced aspects of the new feature in the first phase. It makes for much less code churn when the next phase is started, and shortens the dev time of the next phase too.

If you only think "someday we'll need it", you probably don't. Unless you have hard knowledge that a feature will need it, don't try to implement it. Even if you know a feature is coming that will need it, if it's more than one or two release cycles away, that's probably enough of a time span that you should ignore it and just implement the simple case, since requirements often change over that kind of time period.

@rmeador, but then isn't this designing for what you know is needed now and not just second-guessing what are the general cases that may turn up in future?
–
Rob WellsJun 26 '09 at 13:41

@Rob: I think there's a subtle difference. We have well-specced requirements for the next phase(s), but we know we don't need it now. It'll be released to customers without the more complex features. The extra stuff won't be coming for 6 months or more, but it is absolutely certain that it is coming eventually.
–
rmeadorJun 26 '09 at 15:28

I'm a big believer of designing for the problem at hand and not blowing out your design by trying to guess all the cases that you have to cater for because "someday we might need it".

Basically, given a list of specific requirements, design against that, however, this does not mean that you shouldn't:

make aspects of your design configurable rather than hard coding those aspects in your solution. Either via parameters passed at runtime or via an external config read at startup (or after HUP'ing).

lace your code with magic numbers,

avoid having a look to see if there is something already written that you can reuse, maybe after adapting the existing solution to provide an approach that is suitable for the existing situation as well as for the new requirement(s).

The main problem with designing for "possible futures" is that you are always just guessing. Possibly making educated guesses, but "when push comes to shove" it's still just a series of guesses.

By doing this you also have the very real possibility of squeezing your solution to fit the general case(s) rather than solve the specific problem at hand as defined by your known requirement(s).

What's that saying? "When all you have is a hammer, everything begins to look like a nail."

I wish I had a pound for everytime I've heard someone say, "But it's a solution that's more adaptable for those general cases we might see in the future."