The Mathematics of Engineering

Suppose you need to write a new class. After thinking a bit you realize there are two ways to implement the class. The first one is slow but thorough (henceforth- ST): it will take you ten days to complete, but it can easily evolve to support a new set of features. Let's assume that a typical extension of this implementation will require five days of work.

The second way is quick and dirty (QD): you will need only one day to complete the task, but the resulting code will not be very scalable (that is, it will be difficult to extend it to support additional features).

So which approach should you choose? To answer this question we estimate the total development time needed for supporting the extended set of features.

  • The ST approach requires 15 days (10 for the initial version, 5 for the extension).
  • The QD approach requires at most 16 days (1 for the initial version, then throwing away the code and re-programming for 15 straight days)
At first sight we note that the penalty of choosing the QD approach was just one day of work (the amount of time required for the QD implementation)

But, this is just one side of the story. Here is why you should prefer the QD approach despite this penalty:
  1. It is possible that (eventually) you will not need the extended features. Thus, you will waste nine days of work for something that will never be used.
  2. It likely that you can reuse some of the QD code instead of throwing it away. For the very least you can reuse your test cases (remember: we assumed a similar interface). This can dramatically reduce the penalty.
  3. Getting something to a working state is very important. Other members of your team, who need this class, may have to wait until you finish writing it. Thus, by releasing your class sooner (i.e., choosing the QD approach), you increase the overall throughput of your team. This gain is well worth the QD penalty.
Of course, if you are about to take such a decision you will have to make these time estimates yourself based on the task at hand.

Whatever your final decision is, remember this simple moral: The penalty that the QD approach carries is much smaller than what it initially seems.

5 comments :: The Mathematics of Engineering

  1. "that (eventually) you will need the extended features" -- you mean "will NOT need", right?

    Anyway, in my experience, interface design is the key point here. Most Q&D designs are Q&D because of their interface, not because of the implementation. If you've nailed down the interface correctly, the rest of the discussion -- quick & dirty vs. proper -- is only about performance and correctness, NOT about design. You want correctness anyway; performance is something you can re-consider later, if problems are discovered.

    Also remember that it's not a binary choice; there are many shades of gray between dirty code and beautiful code.

  2. Yes you are right. Just fixed it to "will not need".

    I totally agree with you that this is not a binary decision. I just presented an (intentionally exaggerated) example to illustrate the issue.

    As for interface design, again I agree with you that nailing the interface is the main issue. Therefore, I think that the conclusion should be: Choosing the smallest, simplest interface is the best decision. When the interface is minimal it can easily accommodate various implmentations (here: Q&D as well as S&T).

    On the other hand, a fat interface effectively locks-in the implementation, thus making it difficult to switch from one approach to the other. I guess its easy to see that I am a "thin interface" kind of programmer (which, probably, will be the subject of another post...)

  3. A minimalistic interface is a wise choice in early design cases, and in most cases it is all that's needed. But if a piece of software becomes a cornerstone of the application, I am of the opinion that it should be fattened -- I support the notion of "humane interface". So if you're writing a collection class, I think putAll(obj[]) belongs there, even though clients can simply call put(obj) in a loop.

    As an unrelated side note, I also think that layers of the interface that are implemented purely on top of lower-lever layers should be marked as such. Reasoning: if you wish to change the class mechanics in a subclass, you should know which methods must be overridden, and which methods can keep their inherited implementation. For example, if putAll(obj[]) is implemented internally using put(obj), it is enough to override one in order for the other to immediately take benefit of the change. But unless that's publicly known, a subclass will have no choice but to override putAll as well. --TC

  4. If we have a good architect my fellow programmers would rely on an interface that is already designed for their *unit* testing. :) However, there is nothing wrong with prototyping QD classes just to learn about the domain and the possible solutions. Just be sure to really throw away the code and not to use it afterwards in production.

  5. For confort services in an interface like "putAll(obj[])" and that you know for sure that all implementations will use a for loop or something like that to implement it, you can avoid them in the interface definition.

    Instead of that, you go with an utility class providing the algorithm. Then you have both : minimalistic interface and conveniant methods.

Post a Comment