|
Comments
Did you read today's front page stories & breaking news?
SYS-CON.TV
|
Java Basics The Perils of Copy-Paste Coding
Three approaches that help make it work
By: Paul Mukherjee
Apr. 5, 2004 12:00 AM
Copy-paste coding is a kind of misguided code reuse. You have a problem to solve and you see a similar problem and its solution in your existing body of code. So you copy and paste the solution, and make the necessary modifications so that the solution matches your current problem. Here's an example to make this more concrete: you've written a system that allows departments in your organization to analyze their productivity; each department has its own ideas about what it wants, so each has its own domain logic. The sales department wants to be able to export data from the system into a planning tool. After a few months in production, the personnel department spies this particular feature and says they would like it as well. No problem, says your boss; we already have that functionality in the system so we can make it available to you in the next maintenance release. Your boss then arrives at your desk one morning asking you to make this modification, assuming it won't take long because you already have the code. The problem is that functionality, which is intended to be specific to one business domain, is not usually written generically. The ideal solution would be to rewrite the code to export to a planning tool as a generic component that can be configured for multiple business domains. However, doing it properly means you can't make the deadline that your boss has promised to the customer. Your only option is to take a copy of the existing code, say as a new class, and then modify that copy for the personnel department's specific requirements. This results in two different classes that share a similar functional structure, but whose details vary according to the business domain. What's the Problem with Copy-Paste Coding? There is also an issue of principle: tailoring the export code for each business domain leads to tight coupling between the export code and the business domain. Changes to the business domain could have unexpected repercussions for the exporting feature. What Can You Do About It? All three solutions rely on analyzing corresponding methods. I often find that the easiest way to do this is to print out the relevant methods and then look at them side by side. This way it's easier to see which lines are common to both methods and which lines are domain specific; I usually draw boxes around the domain-specific lines. Solution 1: Helper Classes Looking at these two classes, the similarity between the two export methods is quite striking. The structure for both is identical: initialize the tool, populate the tool with data, and terminate the tool. For PersonnelInfo Exporter the three tasks are, respectively, on lines 18–22, 23–64, and 65–67; SalesInfoExporter has these tasks on lines 16–20, 21–51, and 52–53. This structure suggests that the first and third tasks have little to do with the actual domain-specific data being exported, so we should be able to extract methods for initializing and terminating the tool. A refactoring tool should make this straightforward. In this case, the application of the refactorings Extract Local Variable, Extract Method, and Inline Temp leads to two methods:
Since neither of these methods is specific to a domain or uses instance variables, they can be moved as static methods to a helper class, in this case ExportHelper, as shown in Listing 3. The new SalesInfoExporter is also shown in this listing; similar changes would be made to PersonnelInfoExporter. These methods are static as it makes their state-independence explicit. Solution 2: Inheritance 1. Initialize the tool with the title and number of columns (lines 24–26) 4. Terminate the tool with the footer text (lines 58–59) Those parts of the algorithm that are domain specific are underlined and will be refactored as methods using the Extract Method refactoring. For example, I'll create a method getTitle(): protected String getTitle() { The corresponding part of the export method is: PlanningTool planningTool = When doing this type of refactoring, I try to use method names that indicate the role performed by these lines in the original methods. Using this approach, it's easy to extract the methods getTitle(), getNumberOfColumns(), and getFooterText(). However, this still leaves the body of the loop unresolved; what can we do about getting the data item for the current column? One approach would be to create a method called getDataItem (Object obj, int column), which takes the object currently iterated over and generates the corresponding data item for the column. This would work, but in my experience working with Object instances and then upcasting to the class we are interested in indicates that the design lacks something. In this case it's most natural to create an interface representing an object that can be exported: public interface IExportableData { I can then create a PlanningTool.Data-Item object from an IExportableData object. There is now a design issue: Does the export of data fall under the responsibility of SalesInfo and PersonnelInfo, respectively? This depends on the specific applications; if it does fall under their responsibility, it's appropriate that the corresponding classes implement IExportableData directly. Otherwise it's more appropriate to create implementation classes of IExportableData (e.g., ExportableSalesInfo and Exportable PersonnelInfo), which, respectively, delegate to SalesInfo and Personnel Info. In this example I've chosen the former solution; for example, the new SalesInfo class can be seen in Listing 4. With this interface in place, the last piece of domain-specific functionality is the retrieval of the list of data for the given domain. We create a hook for this that the implementations use to call the corresponding controller. For example, for SalesInfo: protected List getDataForPeriod( After this refactoring, exportSales Info() contains no domain-specific code. The approach now is as follows: 1. Create an abstract superclass (Abstract Exporter); domain-specific subclasses (SalesInfoExporter and PersonnelInfo- Exporter) should extend it. That's it! AbstractExporter is shown in Listing 5 with the refactored SalesInfoExporter class. Note that sometimes the renaming part in stage 3 might not be possible (for example, if a particular interface is to be implemented or preserved). In this case the method should just call the superclass method, as shown below: public void exportSalesInfo( This refactored design is a major improvement over the original one: there is now very little repetition, the separation of responsibilities is much clearer, and the code performing the export and the data to be exported are now loosely coupled. One practical issue that often arises is that it isn't always possible to choose the superclass of a class; for example, if the class has to extend a specific superclass to fit into a particular framework. In this case we choose solution 3. Solution 3: Delegation First, an interface defining the hooks described in the previous section (lines 36–41 in Listing 5) should be created: public interface IExporter { Next, the classes SalesInfoExporter and PersonnelInfoExporter should implement this interface; the implementations from solution 2 (e.g., lines 46–59 in Listing 4) should be made public. The third step is that the abstract superclass from solution 2 should be concrete and have an IExporter instance variable. The hooks that were previously in this class should be removed, and calls to them replaced by calls to the corresponding methods on the IExporter instance variable. This is shown in Listing 6. Finally, the export methods in SalesInfoExporter and PersonnelInfo- Exporter should create an instance of AbstractExporter and call its exportInfo() method. For example: public void exportPersonnelInfo( SalesInfoExporter and PersonnelInfoExporter could be further decoupled from AbstractExporter using an Inversion of Control pattern (see the list of references for more details). The idea behind solution 3 is exactly the same as solution 2. However, their structures differ according to whether inheritance or delegation is more suitable. Refactoring Template Method Pattern Closing Remarks References Reader Feedback: Page 1 of 1
Your Feedback
Latest Cloud Developer Stories
Subscribe to the World's Most Powerful Newsletters
Subscribe to Our Rss Feeds & Get Your SYS-CON News Live!
|
SYS-CON Featured Whitepapers
Most Read This Week
Breaking Cloud Computing News
|
||||||||||||||||||||||||||||||||||||||||||||||||||||