Download presentation
Presentation is loading. Please wait.
Published byRuby Turner Modified over 9 years ago
1
Agile Software Development Lab Spring 2008 R O O T S 1 Sina Golesorkhi(golesork@cs.uni-bonn.de) Thomas Schmickler(schmickl@cs.uni-bonn.de ) XP: Extreme Programming Introduction into Refactoring, Refactoring to Patterns and Code Quality Refactoring
2
Agile Software Development Lab Spring 2008 R O O T S 2 Overview Definition An Example in Eclipse Short Description Precondition of Refactoring Checklist Advantages of Refactoring Indicators for Refactoring are called „Bad Smells“
3
Agile Software Development Lab Spring 2008 R O O T S 3 Defintion What is Refactoring ? Refactor (verb): to restructure software by applying a series of refactorings. Goals: Better readability, comprehensibility Better design Better maintainability and reusability Refactoring (noun): a change made to the internal structure of software to make it easier to understand and cheaper to modify without changing ist observable behavior.
4
Agile Software Development Lab Spring 2008 R O O T S 4 Example in Eclipse You can download the code from http://www.schmickler.de/code.zip
5
Agile Software Development Lab Spring 2008 R O O T S 5 Short Description Better readability, comprehensibility Sourcecode is for humans ! Bytecode for the machines.
6
Agile Software Development Lab Spring 2008 R O O T S 6 Short Description Better readability, comprehensibility Sourcecode is for humans ! Bytecode for the machines. Some developers are proud of their cryptic code.
7
Agile Software Development Lab Spring 2008 R O O T S 7 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.
8
Agile Software Development Lab Spring 2008 R O O T S 8 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.
9
Agile Software Development Lab Spring 2008 R O O T S 9 - 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.
10
Agile Software Development Lab Spring 2008 R O O T S 10 Short Description Better design Beginning with minimal or comprehensive Design
11
Agile Software Development Lab Spring 2008 R O O T S 11 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.
12
Agile Software Development Lab Spring 2008 R O O T S 12 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.
13
Agile Software Development Lab Spring 2008 R O O T S 13 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 ?
14
Agile Software Development Lab Spring 2008 R O O T S 14 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
15
Agile Software Development Lab Spring 2008 R O O T S 15 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.
16
Agile Software Development Lab Spring 2008 R O O T S 16 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. 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. 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. Important:
17
Agile Software Development Lab Spring 2008 R O O T S 17 Precondition Is there anything we have to do before and after a refactoring step ? Question:
18
Agile Software Development Lab Spring 2008 R O O T S 18 Precondition YES !
19
Agile Software Development Lab Spring 2008 R O O T S 19 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. 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.
20
Agile Software Development Lab Spring 2008 R O O T S 20 Precondition Should we make big or many refactorings in a single step ? Question:
21
Agile Software Development Lab Spring 2008 R O O T S 21 Precondition NO !
22
Agile Software Development Lab Spring 2008 R O O T S 22 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...
23
Agile Software Development Lab Spring 2008 R O O T S 23 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 ?
24
Agile Software Development Lab Spring 2008 R O O T S 24 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... Thats important because if the test fails you don't have to search a long time the bug. Why is this important ?
25
Agile Software Development Lab Spring 2008 R O O T S 25 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) Checklist
26
Agile Software Development Lab Spring 2008 R O O T S 26 Steps for improvement 4Eliminating temporary variables 4Splitting the invoice()-method 4Moving subroutines to appropriate classes l Replacing the pricecode dependentswitch-statements with message èMoving the method èApply the Strategy pattern Movie getPriceCode() setPriceCode() getTitle() 1 1 * * Customer invoice() Rental getDaysRented() getMovie() getCharge() getFrequentRenterPoints()
27
Agile Software Development Lab Spring 2008 R O O T S 27 Moving the charge()-method from Rental to Movie: Prior class Rental... 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; } class Rental... 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; } Movie getPriceCode() setPriceCode() getTitle() getcharge() 1 1 * * Customer invoice() Rental getDaysRented() getMovie() getCharge() getFrequentRenterPoints(
28
Agile Software Development Lab Spring 2008 R O O T S 28 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; } 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; } class Rental... public double getCharge() { return _movie.charge(_daysRented); } class Rental... public double getCharge() { return _movie.charge(_daysRented); } Movie getPriceCode() setPriceCode() getTitle() getCharge() 1 1 * * Customer invoice() Rental getDaysRented() getMovie() getCharge() getFrequentRenterPoints(
29
Agile Software Development Lab Spring 2008 R O O T S 29 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 Movie... public int getFrequentRenterPoints(int daysRented) { if ( (this.getPriceCode()==NEW_RELEASE) && daysRented>1) return 2; else return 1; } class Rental... public int getFrequentRenterPoints() { return _movie.getFrequentRenterPoints(_daysRented); } class Rental... public int getFrequentRenterPoints() { return _movie.getFrequentRenterPoints(_daysRented); } class Rental... public int getFrequentRenterPoints() { if ((getMovie().getPriceCode()==Movie.NEW_RELEASE) && getDaysRented()>1) return 2; else return 1; } class Rental... public int getFrequentRenterPoints() { if ((getMovie().getPriceCode()==Movie.NEW_RELEASE) && getDaysRented()>1) return 2; else return 1; } Movie getPriceCode() setPriceCode() getTitle() getCharge() getFrequentRenterPoints() 1 1 * * Customer invoice() Rental getDaysRented() getMovie() getCharge() getFrequentRenterPoints()
30
Agile Software Development Lab Spring 2008 R O O T S 30 Polymorphism l Polymorphism(from the Greek, meaning “many forms”) is a feature that allows one interface to be used for a general class of actions
31
Agile Software Development Lab Spring 2008 R O O T S 31 ChildrensPrice NewReleasePrice RegularPrice Polymorphism via Inheritance Movie priceCode:int getPriceCode() setPriceCode() getCharge(days:int) getFrequentRenterPoints(days:int) 1 1 Customer totalCharge() totalBonusPoints() invoice() invoiceAsHtml() * * Rental daysRented:int charge() bonusPoints () getCharge(days:int) getFrequentRenterPoints(days:int) getCharge(days:int) Not applicable here: A movie would always have a fixed pricecategory
32
Agile Software Development Lab Spring 2008 R O O T S 32 Polymorphism via State Pattern Movie – priceCode:int getPriceCode() setPriceCode() charge(days:int) bonusPoints(days:int) ChildrensPrice getPriceCode() getcharge(days:int) NewReleasePrice getPriceCode() getcharge(days:int) getFrequentRenterPoints(days:int) RegularPrice getPriceCode() getcharge(days:int) Price – priceCode:int getPriceCode() charge(days:int) getFr...Points(days:int) 1 * 1 1 Customer invoice() – totalCharge() – totalBonusPoints() invoiceAsHtml() * * Rental – daysRented:int getDaysRented():int getMovie():Movie charge() bonusPoints () charge(days:int) { return price.charge(days) }
33
Agile Software Development Lab Spring 2008 R O O T S 33 Polymorphismus via State Pattern Movie – priceCode:int getPriceCode() setPriceCode() getCharge(days:int) getFr..Points(days:int) NewReleasePrice getPriceCode() getCharge(days:int) getFr...Points(days:int) RegularPrice getPriceCode() getCharge(days:int) Price – priceCode:int getPriceCode() getCharge(days:int) getFr..(days:int) 1 * Steps 1. Create classes Price, …, RegularPrice 2.Implement getPriceCode()-methods therein 3.Replace the pricecode with a Price objekt (in Movie) usetPriceCode(int) ugetPriceCode uConstruktor 4. Move charge() und bonusPoints() from Movie to Price 5.Replace switch-statements with polymorphism uMove each occurance of the charge() method from Price to the charge()- method of a subclass uAnalogous for bonusPoints()
34
Agile Software Development Lab Spring 2008 R O O T S 34 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 int _priceCode; public Movie(String name, int priceCode) { _name = name; _priceCode = priceCode; } public int getPriceCode() { return _priceCode; } public void setPriceCode(int arg) { _priceCode = arg; } 3 abstract class Price { public abstract int getPriceCode(); } abstract class Price { public abstract int getPriceCode(); } class RegularPrice extends Price { public int getPriceCode() { return Movie.REGULAR; } class RegularPrice extends Price { public int getPriceCode() { return Movie.REGULAR; } class ChildrensPrice extends Price { public int getPriceCode() { return Movie.CHILDRENS; } class ChildrensPrice extends Price { public int getPriceCode() { return Movie.CHILDRENS; } class NewReleasePrice extends Price { public int getPriceCode() { return Movie.NEW_RELEASE; } class NewReleasePrice extends Price { public int getPriceCode() { return Movie.NEW_RELEASE; } 1+2 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); } public int getPriceCode() { return _price.getPriceCode(); }
35
Agile Software Development Lab Spring 2008 R O O T S 35 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; } 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; } Steps 4-5: Replace Switch with Polymorphism abstract class Price... abstract public double getCharge(int days); abstract class Price... abstract public double getCharge(int days); class RegularPrice extends Price {... public double getCharge(int daysRented){ double result =2; if (daysRented > 2) result += (daysRented -2)*1.5; return result; } 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 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; } class NewReleasePrice extends Price {... public double getCharge (int daysRented) { return daysRented * 3; } class Movie {... public double charge(int daysRented) { return _price.charge(daysRented); } class Movie {... public double charge(int daysRented) { return _price.charge(daysRented); } 4 5
36
Agile Software Development Lab Spring 2008 R O O T S 36 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) { if ((getPriceCode() == Movie.NEW_RELEASE) && daysRented > 1) return 2; else return 1; } class NewReleasePrice... int getFrequentRenteroints (int daysRented) { return (daysRented > 1) ? 2: 1; } class NewReleasePrice... int getFrequentRenteroints (int daysRented) { return (daysRented > 1) ? 2: 1; } class Rental... int getFrequentRentedPoints (int daysRented) { return _movie.bonusPoints(daysRented); } class Rental... int getFrequentRentedPoints (int daysRented) { return _movie.bonusPoints(daysRented); } class Movie... int getFrequentRenteroints(int daysRented) { return _price.getFrequentPoints(daysRented); } class Movie... int getFrequentRenteroints(int daysRented) { return _price.getFrequentPoints(daysRented); } class Price... int getFrequentRenteroints (int daysRented) { return 1; } class Price... int getFrequentRenteroints (int daysRented) { return 1; }
37
Agile Software Development Lab Spring 2008 R O O T S 37 Extreme Code Quality
38
Agile Software Development Lab Spring 2008 R O O T S 38 Benefits of refactoring 1. Improved Design 2. Better understanding of the code 3. Better bug detection 4. Faster development!
39
Agile Software Development Lab Spring 2008 R O O T S 39 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)
40
Agile Software Development Lab Spring 2008 R O O T S 40 Bad Smells l 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. l Different Kinds of Bad Smells uDuplicated code uLong Method uLarge Class uSwitch Statements uTemporary Field u…. l Bad Smells in Code by Kent Beck and Martin Fowler Bad Smells in Code by Kent Beck and Martin Fowler
41
Agile Software Development Lab Spring 2008 R O O T S 41 Refactoring-Catalog è Composition of Methodes uExtract Method uInline Method uReplace Temp with Query uInline Temp uSplit Temporary Variable uRemove Assignments to Parameters uReplace Method with Method Object u...
42
Agile Software Development Lab Spring 2008 R O O T S 42 Extract Method 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); } l How to indicate? uCode-Blocks that are logically related to each other l How to handle? uReplace with a well-named methode.
43
Agile Software Development Lab Spring 2008 R O O T S 43 Steps l Defining new and well-named methodes ualways „private“ l Copy the Code l Searching for the local variables in extracted code uVariables that will be used just in new methodes è local variables of new methodes uVariables 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“) uVariables that will be read in new methodes èParameters of new methodes
44
Agile Software Development Lab Spring 2008 R O O T S 44 Steps(2) l Compiling l in original methode uReplacing the extracted code by calling the new methode uDeleting of the delclaration of local variables which have use anymore l Compiling l Testing
45
Agile Software Development Lab Spring 2008 R O O T S 45 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
46
Agile Software Development Lab Spring 2008 R O O T S 46 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); } è Extraction of code for print banner uLocal variable which is not altered („outstanding“) private void printBanner() { System.out.println("**********************"); System.out.println("*** Customer owes ****"); System.out.println("**********************"); }
47
Agile Software Development Lab Spring 2008 R O O T S 47 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); } è Extraction of codes for calculation uLocal variable, which will be altered and finally used(„outstanding“) uLocal variable, which will be altered and will not be used anymore „e“ private void printDetails(double outstanding) { System.out.println(“name“+ _name); System.out.println(“amount“+ outstanding); }
48
Agile Software Development Lab Spring 2008 R O O T S 48 Example:Local variable wchich will be altered void printOwing(double amount) { Enumeration e = :orders.elements(); double outstanding = 0.0; printBanner(); outstanding = getOutstanding(); printDetails(outstanding); } è Extracting code for calculation uIf local variable is assigned before in original method private double getOutstanding() { Enumeration e = orders.elements(); double outstanding = 0.0; while (e.hasMoreElements()) { Order each = (Order) e.nextElement(); outstanding += each.getAmount(); } return outstanding; }
49
Agile Software Development Lab Spring 2008 R O O T S 49 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 utwice „inline temp“ for assigning to local variable „outstanding“ èIn this way we can eliminate „outstanding“ from „printOwing“-Methode
50
Agile Software Development Lab Spring 2008 R O O T S 50 Example: Elimination of „outstanding“ void printOwing(double amount) { double outstanding = amount *1.2; printBanner(); outstanding = getOutstanding(outstanding); printDetails(outstanding); }
51
Agile Software Development Lab Spring 2008 R O O T S 51 Example : End state of printOwing() void printOwing(double amount) { printBanner(); printDetails(getOutstanding(amount *1.2)); }
52
Agile Software Development Lab Spring 2008 R O O T S 52 Overview Refactoring Tools Commercial l Together since Version 5.5 uOnly a few refactorings uwww.togethersoft.com l jFactor uPlug-in for JBuilder and VisualAge for Java uExtensive functionality uwww.instantiations.com/jfactor/ l IDEA uwww.intellij.com/idea/ uIDE with builtin refactorings l JBuilder since version 6 uNot tested yet l Retool uNew uwww.chive.com Free l Smalltalk Refactoring Browser uFirst tool at all uVery powerfull u http://chip.cs.uiuc.edu/users/brant/Refactory/ l Xrefactory uPlug-in for Emacs uhttp://www.xref-tech.com/speller l JavaRefactor uPlug-in for jEdit uhttp://plugins.jedit.org/plugins/JavaRefactor l Eclipse IDE uPreview, Veto and Undo of changes uExtract Method, Rename Method,... uwww.eclipse.org
53
Agile Software Development Lab Spring 2008 R O O T S 53 Refrences l Slides of SWT Lecture (Günter Kniesel) l Slides of Exterme Programming for Nanjing Univesity(Günter Kniesel) Osborne - Java 2--Complete Reference (5th Ed 2002) l Exapmles of Kent Beck and Martin Fowler From Berkley University http://www.berkeley.edu/ http://www.berkeley.edu/ l http://sis36.berkeley.edu/projects/streek/agile/bad-smells-in-code.html http://sis36.berkeley.edu/projects/streek/agile/bad-smells-in-code.html
Similar presentations
© 2024 SlidePlayer.com. Inc.
All rights reserved.