Imagine a legacy application that processes orders. The application was grown from a small LOB utility. If you asked the original developer he would be surprised that it is still in use. The application is made up of nearly 50,000 lines of code with little organization, and no standard has been defined for interactions with other systems. It was a straight forward application at inception after all. But this means there is no defined place to start a task. A programmer is reduced to keyword searches in hopes of finding a fringe piece of functionality. That, in some rare case, contains descriptive naming or on the off chance it has comments that match the vocabulary that one might ascribe to the item that needs fixed.
Imagine if you had to find and fix a tax calculation. What if you were told that someone thought that there may be three or more places where this is done and as you talk to people it quickly becomes apparent that no one really knows where it is happening. There is some code that sends an email confirmation to the customer that uses a calculation. Also there is some code that puts the order in the database as well as other code that actually charges the customer. Each one may do its own calculation and they all need to match, or the customer might lose confidence and shop somewhere else.
Addressing the situation requires starting at the most fundamental levels and enhancing up the stack from code to the system level.
In an architectural void duplicate functionality is free to run rampant. Analysis can shed light on which system is best suited to ensure an operation is carried out in a safe manner. However, being unclear about where to expect certain types of operations in the first place stifles analysis. This hampers problem resolution and increases technical debt. Analysis should be repeated periodically to ensure clarity, performance and maintainability.
On the other hand, when the code has a specific purpose, that is to deal with the entirety of a function or a business subject, its functionality can located in a single module or subset of modules. By assigning responsibility to a module it would make it clear what code belonged and which did not, significantly reducing the amount of code within that module. Further it would certainly make it easier to find code to enhance. Even in a system loosely organized this way would make it much easier to support.
Rarely is a system grown in a void without communicating with other systems and exchanging data and functionality. When this is the case it is prudent to analyze each system to assign appropriate responsibility and reduce redundant functionality as much as it makes sense. When landing new functionality it would also be important to first scrutinize which, if any, existing systems should own that responsibility.
Analysing responsibility at the module and system level not only reduces time investment, it magnifies transparency to the business when enhancement activity must take place.
Doing this takes planning. The tradeoff being that an organized system has more flexibility. Modularising By responsibility could provide a way of assembling chains of application functionality that is not brittle. This architectural style could also be made to scale horizontally as well if each of the modules were able to be distributed and operations were made asynchronously.
Ultimately placing responsibility in clear meaningful buckets and segregating the code will help keep this programmer sane in the most complicated systems. Is this how you do it now? How do you ensure there is no duplication in your system?