280 likes | 428 Views
Refactoring. Originally prepared for COMP314, Bernhard Pfahringer see also: links on the COMP204 page Code Complete, Chapter 24. Refactoring definition.
E N D
Refactoring Originally prepared for COMP314, Bernhard Pfahringer see also: links on the COMP204 page Code Complete, Chapter 24
Refactoring definition • “change to the internal structure of software to make it easier to understand and cheaper to change, WITHOUT changing the observable behaviour” (Martin Fowler, 1999) • I.e. changes “inside the black box” only
Key points • Program changes are a fact of life • Change => degrade or improve • “code smells” indicate need for change • know different refactorings (esp. IDE support …) • use safe strategy • do it early on
Examples • What is wrong with this: public void sendEmail(String message, Employee recipient) { … … recipient.getEmailAddress(); … recipient.getName(); } recipient type too specific: • missed potential for reuse • worse: potential for misuse: … recipient.approveRaise(10000) …
Solution one: primitive types • Only provide what is really needed using primitives including strings (or similar “value” or immutable types): public void sendEmail(String message, String address, String name) { … … address …; … name …; } Cons: many parameters, must change all callers
Solution two: Interface • Define Interface which provides only what is needed (e.g. only read access, can even mimic C++ const declarations) public interface Addressee { String getEmailAddress(); String getName(); } public class Employee implements Addressee { … }
Solution two continued public void sendEmail(String message, Addressee recipient) { … … recipient.getEmailAddress(); … recipient.getName(); } • no need to change any callers • easy extension, e.g. add functionality to Addressee, like boolean prefersPlainText()
Decompose complex condition • Extract code into separate methods with meaningful names: if (date.before(SUMMER_START) ||date.after(SUMMER_END)) { charge = quantity * _winterRate + _winterServiceCharge; } else { charge = quantity * _summerRate; } if (notSummer(date)) { charge = winterCharge(quantity); } else { charge = summerCharge(quantity); }
Reverse conditional if (notSummer(date)) { charge = winterCharge(quantity); } else { charge = summerCharge(quantity); } if (isSummer(date)) { charge = summerCharge(quantity); } else { charge = winterCharge(quantity); } Unless there is no ELSE branch …
Define Null objects X x = computeX(…); if (x != null) { x.methodZ(); } If there is a: class NullX extends X { … methodZ() …} Then computeX(…) can return it if needed, => no NullPointerException, the following is safe X x = computeX(…); x.methodZ();
Replace Constructors with Factory methods public final class Boolean { private boolean value; private Boolean(boolean value) { this.value = value; } public static final Boolean TRUE = new Boolean(true); public static final Boolean FALSE = new Boolean(false); public static Boolean getValue(boolean value) { if (value) return Boolean.TRUE; return Boolean.FALSE; } }
Safe refactoring (similar for code performance tuning) • ensure rollback (e.g. via svn) • small steps, one at a time • always test (unit tests, and more) • bug fixing is NOT refactoring • refactor early
“Code smells” • code is duplicated • a method is too long • a loop is too long or too deeply nested • class has poor cohesion • class interface does not provide consistent level of abstraction • parameter list has too many parameters • changes within a class are compartmentalized • change requires parallel mods in mult.classes • inheritance hierarchies must be modified in parallel
more code smells • case statements modified in parallel • related data not organized into classes • method uses more features of another class than its own • primitive data type is “overloaded” • class does not too very much • chain of methods passes “tramp data” • middleman object doing nothing • class relies on internals of another • method has a poor name • public data members/fields
and even more code smells • subclass uses only small parts of its parent’s methods • comments explain too complex code • use of global variables • method must use complex setup code before and/or takedown code after calling another method • code not needed now, but maybe in the future
Types of refactorings • Data level refactorings • Statement-level refactoring • Method-level refactoring • Class implementation refactoring • Class interface refactoring • System-level refactorings
Data level refactorings • replace magic number with named constant • rename variable to clearer informative name • move expression inline • replace expression with method call • introduce intermediate variable • replace multi-use variable with single-use variables
More Data level refactorings • Use local variable instead of parameter (use final in parameter list) • convert primitive data to class • convert type codes to enumeration • or to class with sub-classes • change array to an object • encapsulate collection
Statement-level refactoring • decompose boolean expression • replace complex boolean exp. with well-named method • consolidate code fragments of different parts of conditional statement • Use break/continue/return in loops • Return/break early • use polymorphism instead of switch • use “null” objects
Method-level refactoring • extract a method • move method call inline • turn a long routine into a class • replace complex algorithm with a simple one • add a parameter • remove a parameter
more Method-level refactoring • separate query from modification • combine similar methods by parameterization • separate methods • pass one whole object instead of many specific fields (cf “Addressee”) • pass specific fields instead of object • encapsulate downcasting
Class implementation refactoring • change value object to reference object • change reference object to value object • replace method with data initialization • change member data or method placement • extract specialised code into subclass • combine similar code into superclass
Class interface refactoring • move method into another class • split class into two • remove a class • hide a delegate • remove a middleman • replace inheritance by composition • replace composition by inheritance • introduce “foreign” method • introduce extension class
more Class interface refactoring • encapsulate exposed data fields • remove unwanted set methods • hide methods that are intended for use outside a class • encapsulate unused methods • collapse sub with superclass if very similar
System-level refactorings • create reference source for data outside your control • change bi-directional class association to uni-directional • change uni-directional class association to bi-directional • Provide factory method instead of a constructor • replace error code with exceptions and v.v.