530 likes | 563 Views
Learn the importance of refactoring in software development and how it enhances code quality, readability, and maintainability. Check out an example in Eclipse and understand the benefits and indicators for refactoring.
E N D
XP: Extreme Programming Introduction into Refactoring, Refactoring to Patterns and Code Quality Refactoring Sina Golesorkhi(golesork@cs.uni-bonn.de) Thomas Schmickler(schmickl@cs.uni-bonn.de)
Overview • Definition • An Example in Eclipse • Short Description • Precondition of Refactoring • Checklist • Advantages of Refactoring • Indicators for Refactoring are called „Bad Smells“
Defintion What is Refactoring ? Refactoring (noun): a change made to the internal structure of softwareto make it easier to understand and cheaper to modify without changing ist observable behavior. Refactor (verb): to restructure software by applying a series of refactorings. • Goals: • Better readability, comprehensibility • Better design • Better maintainability and reusability
Example in Eclipse You can download the code from http://www.schmickler.de/code.zip
Short Description • Better readability, comprehensibility Sourcecode is for humans ! Bytecode for the machines.
Short Description • Better readability, comprehensibility Sourcecode is for humans ! Bytecode for the machines. Some developers are proud of their cryptic code.
Short Description • Better readability, comprehensibility Sourcecode is for humans ! Bytecode for the machines. Some developers are proud of their cryptic code. Some developers think they write the code only for themself.
Short Description • Better readability, comprehensibility Sourcecode is for humans ! Bytecode for the machines. Some developers are proud of their cryptic code. Some developers think they write the code only for themself. To maintain,reuse or extend a programm the code must be understood before from the programmer.
Short Description • Better readability, comprehensibility Sourcecode is for humans ! Bytecode for the machines. Some developers are proud of their cryptic code. Some developers think they write the code only for themself. To maintain,reuse or extend a programm the code must be understood before from the programmer. We want to make good software not good documentationpapers. To understand what the code does it must be good comprehensible. Comprehesible code doesn't need to be documented comprehensive.
Short Description • Better design Beginning with minimal or comprehensive Design
Short Description • Better design Beginning with minimal or comprehensive Design Minimal (like in XP): Design grows with the Code. To get a good design we need to refactor.
Short Description • Better design Beginning with minimal or comprehensive Design Minimal (like in XP): Design grows with the Code. To get a good design we need to refactor. Given Design (Traditional SE): Good Design is given before coding begins. Nice. But with evolution the design changes. Maybe then it is not more good.
Short Description • Better design Beginning with minimal or comprehensive Design Minimal (like in XP): Design grows with the Code. To get a good design we need to refactor. Given Design (Traditional SE): Good Design is given before coding begins. Nice. But with evolution the design changes. Maybe then it is not more good. What to do to keep the good design ?
Short Description • Better design Beginning with minimal or comprehensive Design Minimal (like in XP): Design grows with the Code. To get a good design we need to refactor. Given Design (Traditional SE): Good Design is given before coding begins. Nice. But with evolution the design changes. Maybe then it is not more good. What to do to keep the good design ? Refactor after or before all changes. Yippiyeah
Short Description • Better maintainability and reusability Maintainability and Reusability: Changing any behaviour or adding new functionalities become much easier tasks if you understood the code fast and when the design is good.
Short Description • Better maintainability and reusability Maintainability and Reusability: Changing any behaviour or adding new functionalities become much easier tasks if you understood the code fast and when the design is good. Important: When a feature has to be added to a program, if the code is not structured in a convenient way to add the feature, first refactor the program to make it easy to add the feature, then add the feature.
Precondition Question: Is there anything we have to do before and after a refactoring step ?
Precondition YES !
Precondition Regressiontests: To be sure that you don't change the behavior of the code make tests after every refactoring. Before starting refactoring, it is important to have a solid test suite, with self-checking test cases. In fact, after refactoring the program, it is convenient to perform regression testing automatically, relying on its output to gain some confidence that bugs have not been introduced.
Precondition Question: Should we make big or many refactorings in a single step ?
Precondition NO !
Precondition We should make little iterations. 1. Test 2. little refactoring step 3. Test 4. Add a feature 5. Test 6. little refactoring step 7. Test and so on...
Precondition We should make little iterations. 1. Test 2. little refactoring step 3. Test 4. Add a feature 5. Test 6. little refactoring step 7. Test and so on... Why is this important ?
Precondition We should make little iterations. 1. Test 2. little refactoring step 3. Test 4. Add a feature 5. Test 6. little refactoring step 7. Test and so on... Why is this important ? Thats important because if the test fails you don't have to search a long time the bug.
Checklist Simple Design! In priority order, the code must: • Run all the tests • Contain no duplicate code • Express all the ideas the author wants to express • Minimize classes and methods • (Kent Beck) • Run all the tests • Follow the once and only once rule • Has high cohesion (clarity) • Has loose coupling • (Alan Shalloway)
1 * Customer Rental Movie 1 * invoice() getDaysRented() getMovie() getCharge() getFrequentRenterPoints() getPriceCode() setPriceCode() getTitle() Steps for improvement • Eliminating temporary variables • Splitting the invoice()-method • Moving subroutines to appropriate classes • Replacing the pricecode dependentswitch-statements with message • Moving the method • Apply the Strategy pattern
1 * Customer Rental Movie 1 * getDaysRented() getMovie() getCharge() getFrequentRenterPoints( getPriceCode() setPriceCode() getTitle() getcharge() invoice() Moving the charge()-method from Rental to Movie: Prior classRental ... public double getcharge() { double result = 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.CHILDRENS: result += 1.5; if (getDaysRented() > 3) result += (getDaysRented()-3)*1.5; break; } }
1 * Customer Movie Rental 1 getDaysRented() getMovie() getCharge() getFrequentRenterPoints( invoice() * getPriceCode() setPriceCode() getTitle() getCharge() Moving the charge()-method from Rental to Movie: Afterwards class Movie ... public double getCharge(int daysRented) { double result = 0; switch ( getPriceCode()) { case Movie.REGULAR: result += 2; if ( daysRented > 2) result += ( daysRented -2)*1.5; break; case Movie.NEW_RELEASE: result += daysRented *3; break; case Movie.CHILDRENS: result += 1.5; if ( daysRented > 3) result += ( daysRented -3)*1.5; break; } } classRental ... public double getCharge(){ return _movie.charge(_daysRented); }
1 * Customer Rental Movie 1 * invoice() getDaysRented() getMovie() getCharge() getFrequentRenterPoints() getPriceCode() setPriceCode() getTitle() getCharge() getFrequentRenterPoints() Moving bonusPoints()-method from Rental to Movie class Movie ... public int getFrequentRenterPoints(int daysRented) { if ( (this.getPriceCode()==NEW_RELEASE) && daysRented>1) return 2; else return 1; } class Rental ... public int getFrequentRenterPoints() { if ((getMovie().getPriceCode()==Movie.NEW_RELEASE) && getDaysRented()>1) return 2; else return 1; } class Rental ... public int getFrequentRenterPoints() { return _movie.getFrequentRenterPoints(_daysRented); }
Polymorphism • Polymorphism(from the Greek, meaning “many forms”) is a feature that allows one interface to be used for a general class of actions
Customer Rental daysRented:int totalCharge() totalBonusPoints() invoice() invoiceAsHtml() charge() bonusPoints () Polymorphism via Inheritance Not applicable here: A movie would always have a fixed pricecategory Movie 1 * priceCode:int 1 * getPriceCode() setPriceCode() getCharge(days:int) getFrequentRenterPoints(days:int) ChildrensPrice NewReleasePrice RegularPrice getCharge(days:int) getCharge(days:int) getFrequentRenterPoints(days:int) getCharge(days:int)
Rental Price * – daysRented:int – priceCode:int 1 getDaysRented():int getMovie():Movie charge() bonusPoints () getPriceCode() charge(days:int) getFr...Points(days:int) ChildrensPrice NewReleasePrice RegularPrice getPriceCode() getcharge(days:int) getPriceCode() getcharge(days:int) getFrequentRenterPoints(days:int) getPriceCode() getcharge(days:int) Polymorphism via State Pattern Customer Movie 1 * – priceCode:int 1 * invoice() – totalCharge() – totalBonusPoints() invoiceAsHtml() getPriceCode() setPriceCode() charge(days:int) bonusPoints(days:int) charge(days:int) { return price.charge(days) }
Polymorphismus via State Pattern Steps • Create classes Price, …, RegularPrice 2. Implement getPriceCode()-methods therein 3. Replace the pricecode with a Price objekt (in Movie) • setPriceCode(int) • getPriceCode • Construktor • Move charge() und bonusPoints() from Movie to Price 5. Replace switch-statements with polymorphism • Move each occurance of the charge() method from Price to the charge()-method of a subclass • Analogous for bonusPoints() Movie Price * – priceCode:int – priceCode:int 1 getPriceCode() setPriceCode() getCharge(days:int) getFr..Points(days:int) getPriceCode() getCharge(days:int) getFr..(days:int) NewReleasePrice RegularPrice getPriceCode() getCharge(days:int) getFr...Points(days:int) getPriceCode() getCharge(days:int)
Steps 1-3: Replacement of pricecode with Price object class Movie { ... private int _priceCode; public Movie(String name, int priceCode) { _name = name; _priceCode = priceCode; } public int getPriceCode() { return _priceCode; } public void setPriceCode(int arg) { _priceCode = arg; } } class Movie ... private Price _price; public void setPriceCode(int arg) { switch (arg) { case REGULAR: _price = new RegularPrice(); break; case CHILDRENS: _price = new ChildrensPrice(); break; case NEW_RELEASE: _price = new NewReleasePrice(); break; default: throw new IllegalArgumentException( “Incorrect price code“); } } } public Movie(String name, int priceCode) { _name = name; setPriceCode(priceCode); } 3 public int getPriceCode() { return _price.getPriceCode(); } 1+2 abstract class Price { public abstract int getPriceCode(); } class RegularPrice extends Price { public int getPriceCode() { return Movie.REGULAR; } } class ChildrensPrice extends Price { public int getPriceCode() { return Movie.CHILDRENS; } } class NewReleasePrice extends Price { public int getPriceCode() { return Movie.NEW_RELEASE; } }
Steps 4-5: Replace Switch with Polymorphism class Movie { ... public double charge(int daysRented) { return _price.charge(daysRented); } 4 class Price { ... public double getCharge(int daysRented) { double result = 0; switch (getPriceCode()) { case Movie.REGULAR: result += 2; if (daysRented() > 2) result += (daysRented()-2)*1.5; break; case Movie.CHILDRENS: result += 1.5; if (daysRented() > 3) result += ( daysRented()-3)*1.5; break; case Movie.NEW_RELEASE: result +=daysRented()*3; break; } } abstract class Price ... abstract public double getCharge(int days); 5 class RegularPrice extends Price { ... public double getCharge(int daysRented){ double result =2; if (daysRented > 2) result += (daysRented -2)*1.5; return result; } class ChildrensPrice extends Price { ... public double getCharge (int daysRented){ double result = 1.5; if (daysRented > 3) result += (daysRented -3) * 1.5; return result; } class NewReleasePrice extends Price { ... public double getCharge (int daysRented) { return daysRented * 3; }
The same for getFrequentRenterPoints class Rental... int getFrequentRentedPoints(int daysRented) { if ((getPriceCode() == Movie.NEW_RELEASE) && daysRented > 1) return 2; else return 1; } class Rental... int getFrequentRentedPoints (int daysRented) { return _movie.bonusPoints(daysRented); } class Movie... int getFrequentRenteroints(int daysRented) { return _price.getFrequentPoints(daysRented); } class Price... int getFrequentRenteroints (int daysRented) { return 1; } class NewReleasePrice... int getFrequentRenteroints (int daysRented) { return (daysRented > 1) ? 2: 1; }
Benefits of refactoring • Improved Design • Better understanding of the code • Better bug detection • Faster development!
Simplicity is Code Quality Everything we write must • Run all the tests • Express every idea that we need to express • Say everything once and only once • Have the minimum number of classes and methods consistent with the above (Ron Jeffries et al.: Extreme Programming Installed)
Bad Smells • In the community of computer programming, code smell is any symptom that indicates something may be wrong. It generally indicates that the code should be refactored or the overall design should be reexamined. The term appears to have been coined by Kent Beck on WardsWiki. • Different Kinds of Bad Smells • Duplicated code • Long Method • Large Class • Switch Statements • Temporary Field • …. • Bad Smells in Code by Kent Beck and Martin Fowler
Refactoring-Catalog • Composition of Methodes • Extract Method • Inline Method • Replace Temp with Query • Inline Temp • Split Temporary Variable • Remove Assignments to Parameters • Replace Method with Method Object • ...
Extract Method • How to indicate? • Code-Blocks that are logically related to each other • How to handle? • Replace with a well-named methode . void printOwing(double amount) { printBanner(); // print details System.out.println(“name“+_name); System.out.println(“amount“+ amount); } void printOwing (double amount) { printBanner(); printDetails(amount); } void printDetails (double amount) { System.out.println (“name“+_name); System.out.println (“amount“+ amount); }
Steps • Defining new and well-named methodes • always „private“ • Copy the Code • Searching for the local variables in extracted code • Variables that will be used just in new methodes • local variables of new methodes • Variables which are changed in new methodes and will be used in old methodes • If only one: give it back as the result of new method • more than one: Parts that can not be extracted! („Replace Temp with Query“ or try to „Split Temp Variable“) • Variables that will be read in new methodes • Parameters of new methodes
Steps(2) • Compiling • in original methode • Replacing the extracted code by calling the new methode • Deleting of the delclaration of local variables which have use anymore • Compiling • Testing
Example: no local variables void printOwing(double amount) { Enumeration e = :orders.elements(); double outstanding = 0.0; // print banner System.out.println("**********************"); System.out.println("*** Customer owes ****"); System.out.println("**********************"); // calculate outstanding while (e.hasMoreElements()) { Order each = (Order) e.nextElement(); outstanding += each.getAmount(); // print details System.out.println(“name“+ _name); System.out.println(“amount“+ outstanding); } • Extraction of code for print banner
Example: no local variables void printOwing(double amount) { Enumeration e = :orders.elements(); double outstanding = 0.0; printBanner(); // calculate outstanding while (e.hasMoreElements()) { Order each = (Order) e.nextElement(); outstanding += each.getAmount(); // print details System.out.println(“name“+ _name); System.out.println(“amount“+ outstanding); } private void printBanner() { System.out.println("**********************"); System.out.println("*** Customer owes ****"); System.out.println("**********************"); } • Extraction of code for print banner • Local variable which is not altered („outstanding“)
Local variable which is not altered void printOwing(double amount) { Enumeration e = :orders.elements(); double outstanding = 0.0; printBanner(); // calculate outstanding while (e.hasMoreElements()) { Order each = (Order) e.nextElement(); outstanding += each.getAmount(); printDetails(outstanding); } private void printDetails(double outstanding) { System.out.println(“name“+ _name); System.out.println(“amount“+ outstanding); } • Extraction of codes for calculation • Local variable , which will be altered and finally used(„outstanding“) • Local variable , which will be altered and will not be used anymore „e“
Example:Local variable wchich will be altered void printOwing(double amount) { Enumeration e = :orders.elements(); double outstanding = 0.0; printBanner(); outstanding = getOutstanding(); printDetails(outstanding); } private double getOutstanding() { Enumeration e = orders.elements(); double outstanding = 0.0; while (e.hasMoreElements()) { Order each = (Order) e.nextElement(); outstanding += each.getAmount(); } return outstanding; } • Extracting code for calculation • If local variable is assigned before in original method
Exapmle : local variable that was altered even before void printOwing(double amount) { double outstanding = amount *1.2; printBanner(); outstanding = getOutstanding(outstanding); printDetails(outstanding); } private double getOutstanding(double startValue) { Enumeration e = orders.elements(); double result = startValue; while (e.hasMoreElements()) { Order each = (Order) e.nextElement(); result += each.getAmount(); } return result; } • Now are more Rafactorings possible • twice „inline temp“ for assigning to local variable „outstanding“ • In this way we can eliminate „outstanding“ from „printOwing“-Methode
Example: Elimination of „outstanding“ void printOwing(double amount) { double outstanding = amount *1.2; printBanner(); outstanding = getOutstanding(outstanding); printDetails(outstanding); }