650 likes | 911 Views
How and When to do Refactoring. CSE301 University of Sunderland Harry R. Erwin, PhD. Resources. Eclipse automates a lot of refactoring (8)). Fowler, 2000, Refactoring: Improving the Design of Existing Code, Addison-Wesley. Note: this is available electronically at the library.
E N D
How and When to do Refactoring CSE301 University of Sunderland Harry R. Erwin, PhD
Resources • Eclipse automates a lot of refactoring (8)). • Fowler, 2000, Refactoring: Improving the Design of Existing Code, Addison-Wesley. Note: this is available electronically at the library. • http://www.amazon.com/exec/obidos/tg/detail/-/0321109295/103-4060125-0311065 • http://www.amazon.com/exec/obidos/ASIN/0130648841/103-4060125-0311065 • http://www.refactoring.com/catalog/ • http://www.win.ua.ac.be/~lore/refactoringProject/index.php
Final Year Project Comments • A few comments on OO programming. Important refactorings will be in dark blue. • Don't translate a C program into Java. Think objects. • Avoid overusing static fields and methods. That suggests you're writing a C program in Java. Don't go there. • Use 'move method' to push responsibilities into subclasses. Students often define methods in the calling classes rather than in the called classes, and then need to correct the design. • Look for polymorphism. It can be used rather elegantly. • Are you using JUnit and TDD? Use the existing suite and read the documentation about asserts!
Rename Move Change Method Signature Convert Anonymous Class to Nested Class Change Nested Class to Top-Level Class Push Down Pull Up Extract Interface Use Supertype Where Possible Inline Extract Method Extract Local Variable Extract Constant Convert Local Variable to Field Encapsulate Field Refactoring that Eclipse Supports Already!
A Catalog of Refactorings • Composing Methods • Moving Features Between Objects • Organizing Data • Simplifying Conditionals • Making Method Calls Simpler • Generalization • Big Refactorings
The Basic Rule of Refactoring • “Refactor the low hanging fruit”http://c2.com/cgi/wiki?RefactorLowHangingFruit • Low Hanging Fruit (def): “The thing that gets you most value for the least investment.” • In other words, don’t spend much time on it. There are ways to improve any design incrementally. We will explore a few of them.
The Goal of Refactoring • To improve code without changing what it does. • This in some ways is similar to how an optimizing compiler restructures code. • These how-to’s describe things you can do. • Think about why they work!
Composing Methods • Extract Method • Inline Method • Replace Method with Method Object • Substitute Algorithm
Extract Method • Extract Method is one of the most important refactorings. If you see a patch of coherent code that does something like compose a pane in a GUI encapsulate it as a method. This is my favorite refactoring; usually I apply it when a method gets too long. • Take a clump of related code and make it into a small method. eclipse handles this well. • It does have problems with local variables….
Example for Extract Method final JCheckBox oos = new JCheckBox( "Out of Supply ",unit.outOfSupply); ActionListener oosListener = new ActionListener(){ public void actionPerformed(ActionEvent e){ theUnit.outOfSupply = oos.isSelected(); theBattle.recompute(); } }; oos.addActionListener(oosListener); res.add(oos);
That Code Becomes final JCheckBox oos = addOOS(unit, theUnit); res.add(oos); AND private JCheckBox addOOS(Ground unit, final Ground theUnit) { final JCheckBox oos = new JCheckBox( "Out of Supply ",unit.outOfSupply); ActionListener oosListener = new ActionListener(){ public void actionPerformed(ActionEvent e){ theUnit.outOfSupply = oos.isSelected(); theBattle.recompute(); } }; oos.addActionListener(oosListener); return oos; }
Inline Method Reverses Extract Method • You use Inline Method when the code the results from Extract Method is uglier than the original. • You also use it when the method’s body is just as clear as its name. • Or if you have a group of badly factored methods—inline them and then re-extract methods. • Remember to get rid of the method—needless indirection is irritating and makes you look dumb.
Replace Method with Method Object • Replace Method with Method Object moves local variables into class fields of a ‘functor’, so Extract Methodcan be used more easily. • The curse of extracting methods is local variables. So introduce method objects if you have a big method with lots of local variables. Applying this refactoring converts all those local variables to fields of the method object. • Then you can extract methods freely.
Method Object Implementation (after Beck via Fowler) • Create a new class named after the method. • Define a final field for the object that owned the original method and a modifiable field for every local variable. • Define a constructor with those fields as arguments. • Give the class a method named ‘compute()’. • Replace the old method with one that creates the object and calls compute(). int gamma(int val1, int val2, int val3) { return new Gamma(this,val1,val2,val3).compute(); } • Now you can freely decompose compute() without passing any parameters. This is a good thing.
Substitute Algorithm • This one sounds a bit dumb. Have you thought of a smarter or clearer way of doing something? Go ahead and replace the method with the new algorithm. • If you’re using test-driven development, you can quickly check whether the new version works. If it doesn’t, backtrack, and nobody will ever know…
Moving Features Between Objects • A fundamental design decision is where to put responsibilities. You’ll never get it right the first time. Refactoring allows you to change your mind. • When you want to move functions between objects, these refactorings handle it. Move Method, Move Field, and Extract/Inline Class are the heavyweights here.
Move Field • “Moving state and behavior between classes is the very essence of refactoring.” (Fowler) • You do this to shuffle responsibilities around. Consider a move if a field is used more by other classes than by its home class. • If you do an Extract Class, the fields get moved first and then the methods follow them. • You may need to encapsulate a field first. Then move the field and point any accessors at the new location, perhaps using new accessors there.
Move Method • “The bread and butter of refactoring.” (Fowler) • A method is used by another class more than by its home class. I know of cats like that. • Create a similar method in the other class. Either convert the old method to a simple delegation or remove it entirely. • Think about doing this after you’ve moved some fields between classes. Warning: watch for polymorphism on the original class. That can make the move impossible.
Extract Class • Classes grow, and you may have one class doing work that should be done by two. You find you’re violating the Single Responsibility Principle (SRP) with lots of data and methods. Time to get the scalpel out. • Create a new class and move the relevant fields and methods there. Delegate! • If you don’t know why the SRP is important, wait till next semester.
Inline Class • You find a class isn’t doing very much. This often occurs after a refactoring that has removed fields and methods. • Move its features into another class and then delete it. • Use Encapsulate Field, Move Field, and Move Method to do this.
Hide Delegate • A client calls a method defined on a field of a server object, possibly violating encapsulation. theServer.getManager().getDepartment(); • To do this, the client needs to know that theServer has a Manager field. Replace with a getDepartment() method for theServer. Then unless getManager() is needed, remove it. theServer.getDepartment();
Remove Middle Man • “Here, you take the phone.” After some development, you notice that a class is doing a lot of simple delegation. • Get the client to call the delegate directly. • You may need to create an accessor for the delegate, but thereafter a method only needs to call on the delegate.
Organizing Data • These refactorings generally clean up problems with how classes define and access fields. • The most interesting refactoring here is probably Duplicate Observed Data. That’s how you fix a class that mixes business logic with GUI or SQL code. We’ll spend some time on it since Fowler isn’t very clear. • By the way, value objects are objects that are equal if their fields are equal—you override equals() and hashCode() for them.
Self Encapsulate Field • You are accessing a field of a class directly in a method, but use of the field is becoming awkward. • Create getting and setting methods (‘accessors’) for the field and make the field private. Use these methods within the class. • Why not always do this? Because getting and setting methods obscure what the code is doing. • Doing this is particularly important if a subclass may later need to override direct access with a computed value. • A closely related refactoring: Encapsulate Field.
Replace Data Value with Object • You find that you have a data item (a primitive or value type) that needs additional data or behavior. • For example, the data might be a String that has a special format. • Turn the data item into an object that holds the original data as a final field. Provide a constructor and a getting method. If you need a setting method, have it create a new instance of the class. • You may need to Change Value to Reference, if multiple instances need to share the data object.
Change Value to Reference • Reference objects stand for one object in the real world. • Value objects are defined entirely through their data values. You need to know when two are equal, so you have to override equals() and hashCode(). • A class has many instances, many of which have the same value. You want to replace them with a single instance. • Replace the value constructor with a factory method. • Decide how to provide access to the objects. • Decide whether to precreate or create as required. • Alter the factory method to return the reference object.
Change Reference to Value • You have a reference type object that is small, immutable, and awkward to manage. • Turn it into a value object. • Check that it is really immutable (final) or can become immutable. • Create an equals() and a hashCode() method. The easiest way to write hashCode() is to do a bitwise xor (^) on all the hashcodes of the fields referred to in the equals() method. Primitive types need to be ‘boxed’ for this. E.g., to box i, Integer iBoxed = new Integer(i); • The consider removing the factory method and replacing it with a public constructor. • Finally make the contents final.
Replace Array with Object • You have inherited a design that uses an array where different elements mean different things. This is bad programming practice. • Replace with an object that has a field for each element. • You do this by creating getters and setters for each entry in the array, creating the fields, and then removing the array and repointing the getters and setters at the new fields.
Duplicate Observed Data • This is how you pull a domain model (the business logic) out of a poorly-designed system with no model. If there are multiple windows, see Separate Domain from Presentation. • Copy the business data in the system to a domain object. Set up observers to synchronize the domain model with whatever is dependent on it. • This is a tricky dribble, so pay close attention.
DOD Implementation (Part 1) • Start with the dependent objects (e.g., parts of the GUI, other interfaces or a database). • Create a domain class as an Observable. Create references to the dependent objects in the domain class. • Make the dependent objects Observers of the domain class, so that if it changes, they change. Write an update() method for each. Each constructor has to connect to the domain class, add itself as an Observer of that class, and finally call its update() method on the domain class instance. • Use Self-Encapsulate Dataon any domain data in the dependent objects. Test—behavior should not change.
DOD Implementation (Part 2) • Now for GUIs, add a method to the event handler that uses the new setting methods to update the component with its current value using direct access to the field. This ensures user input goes through the setting method. This will be important later. Test. • Define corresponding data fields and getting/setting methods in the domain class. Setting methods need to trigger model updates and the notify mechanism. Test. • Redirect the accessors for the dependent classes to read and write the domain class fields. Modify the observer’s update method to copy from the domain fields to the dependent class using direct access. Test one last time.
Change Unidirectional to Bidirectional Association • You need a backpointer? First, lie down to see if the need passes. If it doesn’t, do the following: • Add a field for the backpointer. • Decide which class controls the link. • Create a helper method on the other side to send back the backpointer. • If the existing link modifier is on the controlling side, just modify it to update the backpointers. • If not, have it call a routine on the other side to update the backpointers.
Change Bidirectional to Unidirectional Association • Reduce bidirectional to unidirectional whenever possible. • If nobody needs one direction, simply remove it. • For clients who do need the other direction: • Try Encapsulate Field on the backpointer • Then try Substitute Algorithmon the getter and test • If clients don’t need the getter, change each to get the object in the field some other way. • When no reader is left, dump the updater and remove the backpointer.
Encapsulate Field • A class field is publically accessible. Hide it. • Some people consider this good programming practice. Other people find it makes code inefficient or hard to read. YMMV. • Provide accessors—getField(), setField(arg) • Then find all users and change them to use the accessors. • Finally, make the field private. This will tell you if you have really found all the users. • Closely related to Self Encapsulate Field.
Encapsulate Collection • If a class contains a collection of instances, getters and setters don’t work really well. • First, initialize an empty collection when you create it. • Encapsulate the collection field to create a getter and setter. • Next provide public methods to add and remove elements. Modify users to use them instead. • Modify your getter to return a read-only iterator (see the Collections class for how). • Think about moving code that uses the getter into the class that owns the collection.
Replace Type Code • With Class • When a class has a numeric type code that doesn’t affect its behavior—replace with a new class for each code. • With Subclasses • If the type code does affect behavior, use subclasses • With State/Strategy • Or replace the type code with a state object if subclassing doesn’t work
Replace Subclass with Fields • You have subclasses that differ only in methods that return constant (final) data. • Define final fields to contain the data, eliminate the subclasses, and return the field values instead.
Simplifying Conditionals • These simplify conditional logic (if/then/else). Decompose Conditionalis the most important. The elements of an if/then/else are replaced with method calls. These refactorings can also clean up if/then/else messes where flags are used to control sequencing or returns. • Switch statements should be replaced with polymorphism. • A null object is a real ‘do-nothing’ object that substitutes for a null value in a reference.
Decompose Conditional • Suppose you have a complicated if-then-else (or ?:) statement. • Use Extract Method on the condition, the then part, and the else part. • If there is a nested conditional, try Replace Nested Conditional with Guard Clauses first.
Consolidate Conditional Expression • If you have a long list of ‘if(condi) return 0;’ statements in a method, combine them into a single conditional and extract it. • This does the same thing, is clearer, and sets you up to use Extract Method.
Consolidate Duplicate Conditional Fragments • The same fragment of code is in all branches of a conditional expression. • Then move it outside of the expression. • This makes things clearer. Consider using Extract Method, too.
Replace Nested Conditional with Guard Clauses • If a method contains a complicated if-then-else expression that doesn’t make clear what the normal path is, use ‘guard clauses’ for all the special cases. • Handle each special case with an immediate return. Then execute the normal path. • Be clear!
Replace Conditional with Polymorphism • Indicator—you have logic that chooses different behavior based on the type of an object or the value of a flag. • Consider splitting the behavior among subclasses. Make the original method abstract. • You may have to replace type codes with subclasses or state/strategy first.
Introduce Null Object • You’re getting very tired of checking for null. This is often the case for framework code. • Then replace the null value with a do-nothing ‘null object’. (This is what an Adaptor class is in swing, by the way.) • This has the advantage of inheriting from Object (or even from the base class of your hierarchy). Unlike null, a ‘null object’ can have do-nothing methods called on it. A lot of if-then-else cases then disappear.
Making Method Calls Simpler • These refactorings make interfaces more straightforward. • Rename Methodis convenient to document what a method does. • Most of the remaining refactorings are used to get rid of parameters, but be cautious in concurrent programming. • Factory methods hide the concrete implementation of an interface.
Rename Method • You have a method with a name like ‘foo’. Change the name to mean something. • This is important when you have lots of small methods as a form of documentation. • eclipse handles this very well.
Separate Query from Modifier • You have a method that returns a value but also changes the state of an object. • This violates the SRP (discussed later). • Break the method into two: one returns the value, and the other changes the state.
Preserve Whole Object/Replace Parameter with Method • You get values from an object and use them as parameters to a method call. • Why not simply pass the object to the method and let it get its own values? (You can even create a parameter object to do this.) • If you call a method to get a value to pass as a parameter to a second method, why not let the second method get the parameter?
Replace Constructor with Factory Method • You find you need to do more than simple construction when you create an object. • Make the constructor private and replace it with a factory method. • Factory methods are often static methods that do a smart construction. See the Singleton pattern for an example.