Generic Limits

Limitations and problems of making something "generic".


Re: http://www.geocities.com/tablizer/reustalk.htm#reuse_bloat

Describes how in order to make something truly generic, one has to keep adding more and more features, yet the number of features that any given usage instance actually uses remains relatively stable. In other words, the ratio of used features to total features continues to drop over time, making the thing harder to understand and use.


I disagree that one has to keep adding new features to make a system truly generic.

In any feature set, you're going to find commonalities, abstractions you can pull out. If you just add support for that abstraction, you can cover a large feature set without making the system much more complex for the user. "Feature bloat" is a symptom of bad design and an unwillingness to refactor, not of an inherent complexity to the system.

Take the referenced e-mail example, here's how I would add the new functionality:

Start with basic sendMail(to, from, title, message) functionality. Then say your user wants to add bcc functionality. That's just another header - and moreover, if they want bcc, someone else will likely want cc and other headers too. ZeroOneInfinityRule applies here. So you keep the original sendMail function (for backwards compatibility), but make it call the new sendMail(headers, message) function:

 // Pseudocode; no particular language, though it resembles PHP without the $
 function sendMail(to, from, title, message) {
headers = {
"to" => to,
"from" => from,
"title" => title,
};
sendMail(headers, message);
 }

Why do we have different parameters for sendMail here? Are you assuming parameter overloading?

I am. If you don't have this in whatever language you're using, you have to fall back on old-fashioned (and rather ugly) workarounds, like naming them sendMail2, sendMail3, or sendMailWithExtraHeaders if you like DescriptiveVariableNames?. Note that the ObjectOrientedProgramming solution below wouldn't need this...the interface there remains a simple MailMessage.send(), regardless of how many headers and attachments you put on it.

Well of course, sending is the easy part. The hard part is all the setup options. Non-overloading == "old fashioned"? Not all OO fans agree that overloading is a good thing. Another topic I suppose.

I could also have just pulled MailMessage out into a class, given it setHeader(header, text) and send() methods, and put some error checking in for the header fields (the procedural solution could have this error checking too). But I'm trying to DoTheSimplestThingThatCouldPossiblyWork here, and ObjectOrientedProgramming isn't necessary yet.

Now, say that your users want to use attachments. You could just define a new function sendMail(headers, message, attachment) that encodes the attachment and attaches it to message. Or you could have sendMail(headers, message) call sendMail(headers, message, attachment) with a null attachment. I'm not too familiar with the e-mail RFCs; one of these is probably better than the other for technical reasons. But neither would violate OnceAndOnlyOnce, as long as you factor and ReFactor your code.

Then your clients want MIME attachments. Correct me if I'm wrong, but an attachment is just a special case of a MIME part, right? So you have sendMail(headers, message, attachment) call sendMail(headers, message, mimePart). Here's a good place for ObjectOrientedProgramming; you can have a MimePart class that provides a single encode() method, and then each time of MIME content is responsible for encoding itself. The sendMail() function just calls encode() to get the data from the MIME content.

Supporting multiple addresses, multiple attachments, or multiple MimeParts is easy if the language supports DynamicTyping. Just let them pass in either a scalar or array, then test within the function and expand out to whatever the e-mail system needs. If you have StaticTyping, you'd probably just overload sendMail() with one version that takes a collection, and one that takes a String/MimePart/whatever. It's kind of a pain, but you really only need to support the most general case (sendMail(map<string, string> headers, string message, vector<MimePart> mimeParts)), and then provide convenience functions for common subsets.

And the great thing with this approach is that you still have all the simpler interfaces that you started with! So, even after you've created this generalized mailing API, simple clients can still call it with:

 sendMail(to, from, title, message);

More advanced clients would call it like:

 headers = {
"to" => array("bob@here.com", "sue@here.com");
"from" => "notif@inc.com";
"title" => "re: meeting";
"bcc" => "jane@here.com";
 }
 sendMail(headers, message);

And then still more advanced message senders might use;

 agenda = new DocMimePart("agenda.doc");
 vcard = new GifMimePart("vcard.gif");
 headers = {
"to" => array("bob@here.com", "sue@here.com");
"from" => "notif@inc.com";
"title" => "re: meeting";
"bcc" => "jane@here.com";
 }
 sendMail(headers, message, array(agenda, vcard));

That doesn't look very complicated to me. It supports all the features you mentioned, but without completely bloating the interface.

I think a much bigger limitation of generic system is DependencyCreep?, where in order to do anything complicated, you have to install zillions of extra modules. Like how MicroSoft claimed that Win98 would stop working if they removed InternetExplorer (probably marketing FUD, but it illustrates my point). I think the way around that is not to stop building modular systems, though; it's to build dependency management programs that will recursively look at a module's dependencies and download whatever's necessary for a program to run.

--JonathanTang

I hardly see this as simplification. Somebody might want to use mostly the simple stuff but one particular aspect of the "complicated stuff". Thus, a hierarchy or nesting does not necessarily hide complexity because we have to open complexity boxes just to get a few extra features.

I don't see even the advanced features as being particularly complicated; even the "advanced" example here took 8 lines of code use, and most was just filling in data. I also don't see any hierarchies here, beyond the MIMEParts inheriting from a common interface. This was supposed to be an example of how to extend a library without overly complicating the interface. Maybe it didn't really show that (never said I was an excellent programmer :)), but it's a library that I wouldn't mind using in my code.

Of course, you made it and thus know it. But you do have an explosion of variations here if you follow through with every parameter combination and every Mime type. Compare that huge list to the simple original. More complex things will probably have an even bigger CombinatorialExplosion.


A quote from http://www.artima.com/intv/handcuffs.html

If you ask beginning programmers to write a calendar control, they often think to themselves, "Oh, I'm going to write the world's best calendar control! It's going to be polymorphic with respect to the kind of calendar. It will have displayers, and mungers, and this, that, and the other." They need to ship a calendar application in two months. They put all this infrastructure into place in the control, and then spend two days writing a crappy calendar application on top of it. They'll think, "In the next version of the application, I'm going to do so much more."

Once they start thinking about how they're actually going to implement all of these other concretizations of their abstract design, however, it turns out that their design is completely wrong. And now they've painted themself into a corner, and they have to throw the whole thing out. I have seen that over and over. I'm a strong believer in being minimalistic. Unless you actually are going to solve the general problem, don't try and put in place a framework for solving a specific one, because you don't know what that framework should look like.


See also: GenericBusinessFrameworkUnobtainable, HelpersInsteadOfWrappers, VariationsTendTowardCartesianProduct


CategoryReuse


EditText of this page (last edited June 18, 2007) or FindPage with title or text search