Design evolution

Today is the last post from a couple of design thoughts I've been writing down, and it addressed what happens with components over time.

The natural tendency of systems is to gain more capabilities over time. The path of least resistance is often to extend the existing components by making them do more work. So your client-tier form which used to just send data to a middle-tier server when a button was pressed will now do a bunch of validation work as well.

Now, this isn't a bad thing per se - after all, your system just got better. But if left unchecked, your form eventually becomes a monster that does validation, pre-populating values, logging, printing a report on what's being submitted, and actually sending the data to the server for processing. Any or all of these triggered by the event handler of the 'Submit' button.

In a way, the method for 'Submit' has 'moved up' as an abstraction. It doesn't just submit data to a server anymore - instead, it coordinates a much more involved form submission process that involves all the steps mentioned.

The problem with this is that the code tends to become harder to maintain. Submission may now depend on validation having succeeded, logging may not happen if validation cancels the operation, and there's no way to get a printout without submitting the form!

A better approach when evolving like this is to recognize that submission is now an important first-class operation, and that the other sub-operations should work standalone and be coordinated by the larger one. Now the submission operation code grow in interesting ways, like handling the user interface notifications, canceling partial progress, etc., while the other components can focus on a smaller operation at a time and potentially be reused elsewhere.

Mandatory bullet points, then, with some extra thoughts.

  • Beware ever-growing components. The path of least resistance is to extend bit by bit, but this leads to big messes of code. Recognize when it's time to refactor into separate pieces.
  • Prefer to grow by specializing rather than absorbing capabilities. When components grow because they're doing more-of-the-same or better-of-the-same, that tends to be a good sign. Celebrate your collection class when it gains a bunch of code to be more performant. When components grow because they do more things, see the previous point, and be on the lookout for breaking out into areas that you can imagine specializing independently in the future.
  • Acknowledge the value of your evolved components. Sometimes there is a tendency to think that a component is worthless because the design became entangled after extending capabilities and adding special cases. Resist the temptation! There is usually important business value in those capabilities and in handling those special cases; the trick is to support them in a coherent way, not to throw them away when starting over.

Enjoy!