1

This may come off as a bit confusing, but it's a question that I keep finding myself asking as I pile on more and more responsibility to older systems and features that I designed previously. Trying to get to the gist of my question is a bit difficult, but I'll try to illustrate it with some examples.

A couple of years ago, I was tasked with creating a highly customizable menu editing system for our site. This menu editing system was designed with one very specific purpose, and that was to allow end users to configure custom navigation menus for specific user roles in the system. The system wasn't overly complex, or overly robust, but it was functional enough to handle every possible use case we could imagine for creating a new navigation menu. We built all of our custom logic and special handling in. We wrote hundreds of lines of code to suit our specific business rules for this piece of functionality, and we shored it up.

About a year after building the site menu system, I was tasked with developing a dynamic menu system for our mobile application that allowed us to create custom menus based on the company that the user worked for. This new system was very similar to the old system, and we decided that it was best to reuse the site menu tables and functionality. All we needed to do was expand them slightly, and they mostly suited our need. However, there were still some major differences, those were:

  • The mobile menus did not exclusively use Font Awesome as their icon like our website menu did. Instead, it used images that it fetched from a static location on our web-server. It also allowed custom images to be uploaded and stored in the database. So now we needed to check if this mobile menu item populated the url field or the file field.
  • The mobile menus were based off of the user's company rather than their assigned role in the system. This rendered our MenuRoles table useless for this function.
  • The mobile menu didn't navigate to different pages on our website. Instead, it corresponded to certain actions in the application, but it could also launch a webview for a custom URL. We mitigated this by making an actions table and including a URL field.

Now, to avoid cluttering the main menu table with excess columns, I built another table called MobileMenuItem that referenced our MenuItem table. This mobile menu item table housed all of our extra columns, and we simply put in code to ignore the irrelevant columns in the original MenuItem table. Not too big of a problem.

Next we had to update how the menu permissions were checked. Fortunately, over the last year, we also built an incredibly robust FeatureSet function that can hierarchically check through many many different scenarios to determine which features an individual user had access to. Unfortunately, it was originally designed to simply determine if a user had access to a specific feature. These features were simply a string in the database that we accessed through constants in the code, and the algorithms we put into place merely spit out a true/false as to whether a user had access to that feature.

Fortunately, a few months prior, we had customized the feature set system to be able to fetch strings from a resource table based on which features a user had access to and which individual features had been overridden. But again, this system wasn't quite the right fit for fetching the proper menu as we were fetching strings, not menu references. So again, we adapted the system to be able to handle menus as well. Again, disregarding previously implemented features and having to code around what we had built before.

In the end, this lead to a series of 3-4 unrelated systems that were just similar enough to reuse some portions of their functionality, but we broke the specificity of those systems in the process.

So I'm sitting here today, building a 3rd Menu Editor that customizes the home screen of our site based off of custom companies (much like the mobile application). However, it is just different enough that I have to modify the original MenuItem table to include many properties that were included on the MobileMenuItem table. So now I'm left with 3 unrelated menu editors with columns that are ambiguous between tables, logic that is specific to each individual system, and spaghetti code as far as the eye can see.

So my big question basically boils down to, what is the right approach? Should you completely decouple your features of your application, even though they are incredibly similar? Should you basically have potentially dozens and dozens of nearly identical tables and algorithms even though they they are only just slightly different.

Or, should you attempt to reuse as much of your other systems as possible?

4

What you describe is what I call architecture creep where a system designed at some point in time is adapted over and over, each time for different purposes' until you reach a point where the system becomes unmaintainable.

So my big question basically boils down to, what is the right approach?

People avoid big rewrites because of the cost and risks - which are inevitably high in both cases. However, eventually comes a time when the cost and risks of making changes to the legacy code, exceeds that of starting afresh and people face the inevitable re-write. I have seen this happen often in practice.

The right approach depends where you are: At design time or At the end of years of use and mods and extensions etc. If at design time, you need to be able to foresee what may lie ahead and then design a system that is capable adapting when needed. This is where a good understanding of the OOP Design Patterns would help to build a robust and extensible system. If its too late for all of that you need to assess Where you are on the architecture creep curve!

Should you completely decouple your features of your application, even though they are incredibly similar?

The DRY (Don't Repeat Yourself) principle is a good one, so that changes need to be made only in one place. You can carry this to extremes. In your case, if you re-achitecting your system you might consider very careful data analysis and create a set of tables and views that minimises or eliminates the same data in more than one place.

Should you basically have potentially dozens and dozens of nearly identical tables and algorithms even though they they are only just slightly different.

See comment on tables above. With respect to algorithms, if they are different they are different and you should avoid a lot of if-else/switch statements within functions to implement small variations. Instead use sub-classes and override methods accordingly. This is a must.

Very often, in a commercial situation, one of the programmers will see the need for a rewrite - but others will resist because of time pressure, cost and risk evaluations (that may be not well understood). In that case the programmer might consider making a skeletal version of a new system, and demonstrate its advantages. This is a great way to get bosses, clients and team members to buy in.

2

The defining moments are those when you go "This does what I need, could I use it for my new purpose?". What you should be asking yourself in good OOP tradition is "Is this what I need?"

Functionally you may have

  • A set of commands
  • A tree-like navigation structure
  • A menu item style definition class
  • A rights management system

You get the best reuse when keeping all of these separate but this is not how these things are typically developed and you now know how this works. You start out with modest demands and gradually stuff more functionality into the same thing because "it is only a minor change" and then you have what you need.

Whenever we are faced with a new feature request, we should not rush into the quickest way to cram it in and make it work but rather wonder whether we are still working on the same thing. Or that some new beast is emerging.

Lots of ifs in a class is typically a sign that you should have some class hierarchy with polymorphism. My first hunch for your menu would be something event based: when it is time to display an item, the menu raises an event and the MenuEventArgs object has properties that tell the menu whether to display the item and if so, how (like disabled or enabled, featuring a check mark, et cetera). These properties could be set by the implementer of the handler that calls on an AccessRightsManager and perhaps a UIStateManager. Another event could be used to render/paint the item. That way you could keep the menu structure separate from access management, state and presentation details.

The fact that "a lot of fields are the same" is never a good reason to put some inherently different entity in an existing table. If it is not the same thing, you can bet your life they will eventually diverge and it will be increasingly hard to hack new features in (and to refactor the lot into something it should have been from the start).

2

Take a new start ?

Starting from scratch is tempting. But is the approach as good and liberating as it seems, especially if you have a relatively successful working foundation ?

Joel's great article "Things you should never do" concludes that it's not.

You say you want a revolution
Well, you know
We all want to change the world

- The Beatles

Evolution instead of revolution ?

Great software is mostly made of design and code that evolved from release to release. Take Linux as example. It was not meant to become a planetary success. But today, millions of added and changed lines of code later, it still makes us happy.

With an evolutionary approach, no need to predict the future and have a perfect design. Deliver working software in small increments, with a design that's good enough to evolve.

Reading your story, I think that you were so far succesful with exactly this approach for your previous release. Congratulations ! Go on, your software is certainly great.

So my recommendation for your new version is: Adapt, expand, refactor, and over again. This includes generalizing some existing pieces to keep them fit for purpose. Past experience with new insights will help you to make existing design better, without the risk of getting lost in over-engineering a completely new design.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.