2.46k likes | 2.48k Views
This lecture provides an introduction to refactoring, including why we refactor, how we refactor, and when we refactor. It covers the benefits of refactoring and provides examples of code that can be improved through refactoring.
E N D
This lecture is divided into and introduction to refactoring and then several lessons. The intent is not to teach you all the refactorings but to expose you to just a few…
Introduction Outline A. What is Refactoring? B. Why do we Refactor? C. How do we Refactor? D. When do we Refactor?
What is Refactoring? Refactoring is a technique which identifies bad code (code that smells) and allows promotes the re-structuring of that bad code into classes and methods that are more readable, maintainable, and generally sparse in code. Refactoring yields a “better” design of both your classes and methods.
B. Why do we Refactor? Refactoring allows building of complex systems by exploiting sophisticated object-oriented techniques that yield such systems catagorized as frameworks, beans, or other reusable software components.
Why do you refactor Enable sharing of logic. Explain intention and implementation seperately. Isolate change. Encode conditional logic.
How do we Refactor? • Refactoring is accomplished by • building, decomposing, and moving methods • building and decomposing classes • replacing code with patterns, and • replacing code with other techniques.
D. When do we Refactor? • Refactoring is done when • methods or classes are too LARGE, • code, control flow, or data structures are DUPLICATED, • attributes or methods are MISPLACED, • When parameters can make code reusable • When design is poor (the code smells).
When do you refactor Refactor when you add functions. Refactor as you do a code review. Refactor when you fix a bug.
Benefits of Refactoring Without refactoring, the design of the program decays. Refactoring helps you to develop code more quickly. Refactoring helps you to find bugs. Refactoring makes software easier to understand. Refactoring improves the design of software.
Movie Rental Customer pricecode: int children = 2 regular = 0 new_release=1 title: String daysRented:int name: String rentals: vector getPriceCode)_ setPriceCode() getTitle() getMovie() getDaysRented)_ Statement() addRental(rental) getName(): An example- Class Diagram 0..* 1..1 1..1 0..*
An Example – Sequence Diagram Customer Rental Movie statement forallrentals Rental: getMovie() getPriceCode() getDaysRented()
Movie pricecode: int children = 2 regular = 0 new_release=1 title: String getPriceCode)_ setPriceCode() getTitle() Code -- page 3 public class Movie { public static final int CHILDRENS = 2; // type movie for children public static final int REGULAR = 0; // type of regular movie public static final int NEW_RELEASE = 1; // new release movie private String _title; // title of the movie private int _priceCode; // price code for movie public Movie(String title, int priceCode) { _title = title; _priceCode = price Code; }// end constructor public int getPriceCode() { return priceCode; } public void setPriceCode (int agr) { _priceCode = arg; } public String getTitle () { return _Title; } } // end Movie Constructor
Rental daysRented:int getMovie() getDaysRented)_ Code (con’t)-- page 3 class Rental { private Movie _movie; // movie rented private int _daysRented; // number of days rental public Rental(Movie movie, int daysRented) { _movie = movie; _daysREnted = daysRented; } // end Rental public int getDaysRented() { return _daysRented; } public Movie getMovie() { return _movie; } }// end Rental Constructor
Customer name: String rentals: vector Statement() addRental(rental) getName(): Code (con’t)-- page 4 class Customer { private String _name; // name of customer private Vector _rentals = new Vector (); // vector of list of rentals by the customer public Customer (String name) { _name = name; } public void addRental (Rental arg) { _rentals.addElement(arg) } public String getName () { return _name} Constructor
Code (con’t)-- page 5 public String statement() { double totalAmount = 0; // total of the statement int frequentRenterPoints = 0; // number of frequent rental points of a rental Enumeration rentals = _rentals.elements(); // list of rentals String result = “Rental Record for “ + getName() +”/n” while (rentals.hasMoreElements() { double thisAmount =0; Rental each = (Rental) rentals.nextElement(); // continued on next page
Code (Con’t) page 5 // determine amounts for each line // regular 2.00 for two days 1.50 extra // new release 3.00 per day // children 1.50 for three days switch (each.getMovie().getPriceCode()) { case Movie.REGULAR: // statements for a regular movie thisAmount +=2; if (each.getDaysRented() >2) thisAmount +=(each.getDaysRented()-2)*1.5; Break; case Movie.NEW_RELEASE: // statements for a new release type movie thisAmount +=each.getDaysRented()*3; Break; case Movie_CHILDREN: // statements for a children movie thisAmount +=1.5; if (each.getDaysRented() >3) thisAmount +=(each.getDaysRented()-3)*1.5; Break; } // end switch
Code (Con’t) page 5 // add frequent renter points add 1 frequent renter point if NEW RELEASE rented > one day frequentRenterPoints ++; // add bonus for a two day new release rental if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1) frequentRenterPoints ++; // show figures for this rental result +=“/t” + each.getMovie().getTitle*(+”\t” + String.valueOf(thisAmount) + “\n”; totalAmount +=thisAmount; } // add footer lines result +=“Amount owed is “ + String.valueOf(totalAmount) + “\n”; result += “You earned “ + String.valueOf(frequentRenterPoints) + “ frequent renter points”; return result; } // end statement // end customer
This method, called statement, is TOO LARGE. • This statement method should not be inside customer.
Refactoring Opportunities a code cluster is setting one variable // determine amounts for each line switch (each.getMovie().getPriceCode()) { case Movie.REGULAR: thisAmount +=2; if (each.getDaysRented() >2) thisAmount +=(each.getDaysRented()-2)*1.5; Break; case Movie.NEW_RELEASE: thisAmount +=each.getDaysRented()*3; Break; case Movie_CHILDREN: thisAmount +=1.5; if (each.getDaysRented() >3) thisAmount +=(each.getDaysRented()-3)*1.5; Break; } // end switch EXTRACT it as a METHOD returning the variable
private double amountFor(Rental each) double thisAmount = 0.0; switch (each.getMovie().getPriceCode()) { case Movie.REGULAR: thisAmount +=2; if (each.getDaysRented() >2) thisAmount +=(each.getDaysRented()-2)*1.5; Break; case Movie.NEW_RELEASE: thisAmount +=each.getDaysRented()*3; Break; case Movie_CHILDREN: thisAmount +=1.5; if (each.getDaysRented() >3) thisAmount +=(each.getDaysRented()-3)*1.5; Break; } // end switch return thisAmount; } // end amountFor EXTRACT METHOD page 11
Original Code - Customer Class page 18 public String statement() { double totalAmount = 0; int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = “Rental Record for “ + getName() +”/n” while (rentals.hasMoreElements() { double thisAmount =0; Rental each = (Rental) rentals.nextElement(); thisAmount = amountFor(each); Calls the new method
Refactoring Opportunities a variable resides in the wrong class MOVE the METHOD to the class where it should reside public String statement() { double totalAmount = 0; int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = “Rental Record for “ + getName() +”/n” while (rentals.hasMoreElements() { double thisAmount =0; Rental each = (Rental) rentals.nextElement(); thisAmount = amountFor(each); Should NOT be in customer class Should be in the rental
private double getCharge(Rental each) double result = 0.0; switch (each.getMovie().getPriceCode()) { case Movie.REGULAR: result +=2; if (each.getDaysRented() >2) result +=(each.getDaysRented()-2)*1.5; Break; case Movie.NEW_RELEASE: result +=each.getDaysRented()*3; Break; case Movie_CHILDREN: result +=1.5; if (each.getDaysRented() >3) result +=(each.getDaysRented()-3)*1.5; Break; } // end switch return result; } // end getCharge MOVE METHOD and rename Rename the method and result
Original Code - Customer Class page 19 public String statement() { double totalAmount = 0; int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = “Rental Record for “ + getName() +”/n” while (rentals.hasMoreElements() { double thisAmount =0; Rental each = (Rental) rentals.nextElement(); thisAmount = each.getCharge(each); Calls the new method In the rental class
Rental: getMovie() amount: getCharge() An Example – Sequence Diagram Customer Rental Movie statement forallrentals getPriceCode() getDaysRented()
Refactoring Opportunities a code cluster is setting one variable EXTRACT it as a METHOD returning the variable } // add frequent renter points add 1 frequent renter point if NEW RELEASE rented > one day frequentRenterPoints ++; // add bonus for a two day new release rental if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1) frequentRenterPoints ++; // show figures for this rental result +=“/t” + each.getMovie().getTitle*(+”\t” + String.valueOf(thisAmount) + “\n”; totalAmount +=thisAmount; } // add footer lines result +=“Amount owed is “ + String.valueOf(totalAmount) + “\n”; result += “You earned “ + String.valueOf(frequentRenterPoints) + “ frequent renter points”; return result; } // end statement
EXTRACT METHOD // add frequent renter points add 1 frequent renter point if NEW RELEASE rented > one day int getFrequentRenterPoints() { if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1) return 2; else return 1;
Refactoring Opportunities a variable resides in the wrong class MOVE the METHOD to the class where it should reside public String statement() { double totalAmount = 0; int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = “Rental Record for “ + getName() +”/n” while (rentals.hasMoreElements() { double thisAmount =0; Rental each = (Rental) rentals.nextElement(); frequentRenterPoints += getFrequentRenterPoints(); Should NOT be in customer class Should be in the rental
MOVE METHOD class Rental….. ….. // add frequent renter points add 1 frequent renter point if NEW RELEASE rented > one day int getFrequentRenterPoints() { if ((each.getMovie().getPriceCode() == Movie.NEW_RELEASE) && each.getDaysRented() > 1) return 2; else return 1;
Original Code - Customer Class public String statement() { double totalAmount = 0; int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = “Rental Record for “ + getName() +”/n” while (rentals.hasMoreElements() { double thisAmount =0; Rental each = (Rental) rentals.nextElement(); thisAmount = each.getCharge(); frequentRenterPoints += each.getFrequentRenterPoints(); Calls the new methods In the rental class
Refactoring Opportunities a variable is used temporarily REPLACE TEMP with QUERY eliminating the temp public String statement() { double totalAmount = 0; int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = “Rental Record for “ + getName() +”/n” while (rentals.hasMoreElements() { double thisAmount =0; Rental each = (Rental) rentals.nextElement(); thisAmount = each.getCharge(); frequentRenterPoints += getFrequentRenterPoints(); // show figures for this rental result +=“/t” + each.getMovie().getTitle*(+”\t” + String.valueOf(thisAmount) + “\n”; totalAmount +=this.Amount; } // end while
REPLACE TEMP with QUERY page 21 public String statement() { double totalAmount = 0; int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = “Rental Record for “ + getName() +”/n” while (rentals.hasMoreElements() { double thisAmount =0; Rental each = (Rental) rentals.nextElement(); thisAmount = each.getCharge(); frequentRenterPoints += getFrequentRenterPoints(); // show figures for this rental result +=“/t” + each.getMovie().getTitle*(+”\t” + String.valueOf(thisAmount each.getCharge()) + “\n”; totalAmount += this.Amount each.getCharge() ; } // end while
Refactoring Opportunities a variable is used temporarily REPLACE TEMP with QUERY public String statement() { double totalAmount = 0; int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = “Rental Record for “ + getName() +”/n” while (rentals.hasMoreElements() { Rental each = (Rental) rentals.nextElement(); frequentRenterPoints += getFrequentRenterPoints(); // show figures for this rental result +=“/t” + each.getMovie().getTitle*(+”\t” + String.valueOf(each.getCharge()) + “\n”; totalAmount += each.getCharge(); } // end loop // add footer lines result +=“Amount owed is “ + String.valueOf(totalAmount) + “\n”; result += “You earned “ + String.valueOf(frequentRenterPoints) + “ frequent renter points”; return result; } // end statement eliminating the temp Problem – temp in loop
EXTRACT it as a METHOD private double getTotalCharge() { double result = 0; Enumeration rentals = _rentals.elements(); while (rentals.hasMoreElements() { Rental each = (Rental) rentals.nextElement(); result += each.getCharge(); } // end loop return result; } // end getTotalCharge
REPLACE TEMP with QUERY page 27 public String statement() { double totalAmount = 0; int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = “Rental Record for “ + getName() +”/n” while (rentals.hasMoreElements() { Rental each = (Rental) rentals.nextElement(); frequentRenterPoints += getFrequentRenterPoints(); // show figures for this rental result +=“/t” + each.getMovie().getTitle*(+”\t” + String.valueOf(each.getCharge()) + “\n”; totalAmount += each.getCharge(); } // add footer lines result +=“Amount owed is “ + String.valueOf( totalAmount getTotalCharge()) + “\n”; result += “You earned “ + String.valueOf(frequentRenterPoints) + “ frequent renter points”; return result; } // end statement Yes, we are looping twice
Refactoring Opportunities a variable is used temporarily REPLACE TEMP with QUERY public String statement() { int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = “Rental Record for “ + getName() +”/n” while (rentals.hasMoreElements() { Rental each = (Rental) rentals.nextElement(); frequentRenterPoints += getFrequentRenterPoints(); // show figures for this rental result +=“/t” + each.getMovie().getTitle*(+”\t” + String.valueOf(each.getCharge()) + “\n”; } // end loop // add footer lines result +=“Amount owed is “ + String.valueOf(getTotalCharge) + “\n”; result += “You earned “ + String.valueOf(frequentRenterPoints) + “ frequent renter points”; return result; } // end statement eliminating the temp Problem – temp in loop
EXTRACT it as a METHOD private double getTotalFrequentRentalPoints() { int result = 0; Enumeration rentals = _rentals.elements(); while (rentals.hasMoreElements() { Rental each = (Rental) rentals.nextElement(); result += each.getFrequentRentalPoints(); } // end loop return result; } // end getTotalFrequentRentalPoints
REPLACE TEMP with QUERY page 29 public String statement() { int frequentRenterPoints = 0; Enumeration rentals = _rentals.elements(); String result = “Rental Record for “ + getName() +”/n” while (rentals.hasMoreElements() { Rental each = (Rental) rentals.nextElement(); frequentRenterPoints += getFrequentRenterPoints(); // show figures for this rental result +=“/t” + each.getMovie().getTitle*(+”\t” + String.valueOf(each.getCharge()) + “\n”; } // add footer lines result +=“Amount owed is “ + String.valueOf( getTotalCharge()) + “\n”; result += “You earned “ + String.valueOf(frequentRenterPoints getTotalFrequentRentalPoints ) + “ frequent renter points”; return result; } // end statement Yes, we are looping thrice
An Example – Sequence Diagram Customer Rental Movie statement getTotalCharge amount: getCharge() getPriceCode() getFrequentRenterPoints() getPriceCode()
Refactoring Opportunities conditional code exist for sub-types Use POLYMORPHISM replacing conditional logic private double getCharge(Rental each) double result = 0.0; switch (getMovie().getPriceCode()) { case Movie.REGULAR: result +=2; if (getDaysRented() >2) result +=(getDaysRented()-2)*1.5; Break; case Movie.NEW_RELEASE: result +=getDaysRented()*3; Break; case Movie_CHILDREN: result +=1.5; if (getDaysRented() >3) result +=(getDaysRented()-3)*1.5; Break; } // end switch return result; } // end getCharge Cannot make subclasses of movie Movies can change classifications
Refactoring Opportunities object can change states in its lifetime Use the STATE Pattern for the variables which change values private double getCharge(Rental each) double result = 0.0; switch (getMovie().getPriceCode()) { case Movie.REGULAR: result +=2; if (getDaysRented() >2) result +=(getDaysRented()-2)*1.5; Break; case Movie.NEW_RELEASE: result +=getDaysRented()*3; Break; case Movie_CHILDREN: result +=1.5; if (getDaysRented() >3) result +=(getDaysRented()-3)*1.5; Break; } // end switch return result; } // end getCharge Cannot make subclasses of movie Movies can change classifications
Movie Rental Customer pricecode: int children = 2 regular = 0 new_release=1 title: String daysRented:int name: String rentals: vector getPriceCode)_ setPriceCode() getTitle() getMovie() getDaysRented)_ Statement() addRental(rental) getName(): Original- Class Diagram 0..* 1..1 1..1 0..*
Price Regular Price Childrens Price New Release Price Movie getCharge() getCharge() getCharge() getCharge() pricecode: int children = 2 regular = 0 new_release=1 title: String getPriceCode)_ setPriceCode() getTitle() getCharge() State Pattern- Class Diagram 1..1 1..1
Refactoring Opportunities state variables not encapsulated SELF ENCAPSULATE FIELDS add get and set methods private double getCharge(int daysRented) double result = 0.0; switch (getMovie().getPriceCode()) { case Movie.REGULAR: result +=2; if (getDaysRented() >2) result +=(getDaysRented()-2)*1.5; Break; case Movie.NEW_RELEASE: result +=getDaysRented()*3; Break; case Movie_CHILDREN: result +=1.5; if (getDaysRented() >3) result +=(getDaysRented()-3)*1.5; Break; } // end switch return result; } // end getCharge Cannot make subclasses of movie Movies can change classifications
Original Code -- page 3 public class Movie { public static final int CHILDRENS = 2; public static final int REGULAR = 0; public static final int NEW_RELEASE = 1; private String _title; private int _priceCode; public Movie(String title, int priceCode) { _title = title; _priceCode = price Code; }// end constructor public int getPriceCode() { return priceCode; } public void setPriceCode (int agr) { _priceCode = arg; } public String getTitle () { return _Title; } } // end Movie } Sub-types Self Encapsulate
Self Encapsulating Movie Code -- page 41 public class Movie { public static final int CHILDRENS = 2; public static final int REGULAR = 0; public static final int NEW_RELEASE = 1; private String _title; private int _priceCode; setPriceCode(priceCode); public Movie(String title, int priceCode) { _title = title; _priceCode = price Code; }// end constructor public int getPriceCode() { return priceCode; } public void setPriceCode (int agr) { _priceCode = arg; } public String getTitle () { return _Title; } } // end Movie
Movie Price Sub-Classes -- page 41 class Price { abstract int getPriceCode(); } // end Price class ChildrensPrice extends Price { int getPriceCode () { return Movie.CHILDRENS; } } // end ChildrensPrice class NewReleasePrice extends Price { int getPriceCode () { return Movie.NEW_RELEASE; } } // end NewReleasePrice class RegularPrice extends Price { int getPriceCode () { return Movie.REGULAR; } } // end RegularPrice
MOVE METHOD page 45 class Price private double getCharge(int daysRented) double result = 0.0; switch (getMovie().getPriceCode()) { case Movie.REGULAR: result +=2; if (getDaysRented() >2) result +=(getDaysRented()-2)*1.5; Break; case Movie.NEW_RELEASE: result +=getDaysRented()*3; Break; case Movie_CHILDREN: result +=1.5; if (getDaysRented() >3) result +=(getDaysRented()-3)*1.5; Break; } // end switch return result; } // end getCharge }// end price Once it is moved, we can replace conditional with polymorphism
Use POLYMORPHISM page 47 class RegularPrice double getCharge(int daysRented) double result = 2; if (getDaysRented() >2) result +=(getDaysRented()-2)*1.5; return result; } // end getCharge } // end regularPrice class ChildrensPrice double getCharge (int daysRented) { double result = 1.5; if (getDaysRented() >3) result +=(getDaysRented()-3)*1.5; return result } // end getCharge } // end ChildrensPrice class NewRelease double getCharge (int daysRented() { return daysRented * 3; } Take one leg of case statement at a time.
Use POLYMORPHISM page 47 class Price abstract double getCharge (int daysRented); Create an overiding method for the getCharge method.