SDSU CS 683 Emerging Technologies: Embracing Change
Spring Semester, 2001
Refactoring Intro
Previous    Lecture Notes Index    Next    
© 2001, All Rights Reserved, SDSU & Roger Whitney
San Diego State University -- This page last updated 20-Mar-01

Contents of Doc 14, Refactoring Intro


References

Refactoring: Improving the Design of Existing Code, Fowler, 1999, pp. 110-116, 237-270

The Pragmatic Programmer, Hunt & Thomas, Addison Wesley Longman, 2000

Quality Software Management Vol. 4 Anticipating Change, Gerald Weinberg, Dorset House Publishing, 1997


Doc 14, Refactoring Intro Slide # 2

Refactoring Intro


We have code that looks like:

at: anInteger put: anObject
   (smallKey ~= largeKey)
      ifTrue:
         [(anInteger < smallKey)    
            ifTrue: [self atLeftTree: anInteger put: anObject]
            ifFalse: [(smallKey = anInteger)
               ifTrue: [smallValue := anObject]
               ifFalse: [(anInteger < largeKey)
                  ifTrue: [self atMiddleTree: anInteger put: anObject]
                  ifFalse: [(largeKey = anInteger)
                     ifTrue: [largeValue := anObject]
                     ifFalse: [(largeKey < anInteger)
                        ifTrue: [self atRightTree: anInteger put: anObject]]]]]]
      ifFalse:
[self addNewKey: anInteger with: anObject].

Now what?



Doc 14, Refactoring Intro Slide # 3

The Broken Window[1]


In inner cities some buildings are:



Clean inhabited buildings can quickly become abandoned derelicts

The trigger mechanism is:



If one broken window is left unrepaired for a length of time



Don't live with Broken Widows in your code


Doc 14, Refactoring Intro Slide # 4

The Perfect Lawn


A visitor to an Irish castle asked the groundskeeper the secret of the beautiful lawn at the castle

The answer was:



Spending a little time frequently



So frequently spend time cleaning your code


Doc 14, Refactoring Intro Slide # 5

Familiarity verse Comfort



Why don't more programmers/companies continually:




Familiarity is always more powerful than comfort.

-- Virginia Satir


Doc 14, Refactoring Intro Slide # 6

Refactoring


Refactoring is the modifying existing code without adding functionality


Changing existing code is dangerous



To avoid breaking code while refactoring:



Doc 14, Refactoring Intro Slide # 7

Sample Refactoring: Extract Method[2]


You have a code fragment that can be grouped together.

Turn the fragment into a method whose name explains the purpose of the method

Motivation


Short methods:



Doc 14, Refactoring Intro Slide # 8

Mechanics


Name the target method after the intention of the method
With short code only extract if the new method name is better than the code at revealing the code's intention






Doc 14, Refactoring Intro Slide # 9
Mechanics - Continued

If only one variable is modified, then try to return the modified value
If more than one variable is modified, then the extracted code must be modified before it can be extracted
Split Temporary Variables or Replace Temp with Query may help





Doc 14, Refactoring Intro Slide # 10

Example[3]

No Local Variables

Note I will use Fowler's convention of starting instance variables with "_" even though one can not do this is Squeak.

printOwing
   | outstanding |
   
   outstanding := 0.0.
   Transcript
      show: '********************';
      cr;   
      show: '***Customer Owes***';
      cr;   
      show: '********************';
      cr.
   
   outstanding := _orders inject: 0 into: [:sum :each | sum + each].
   
   Transcript
      show: 'Name: ';
      show:  _name;
      cr;   
      show: 'Amount: ';
      show: outstanding;
      cr.


Doc 14, Refactoring Intro Slide # 11
Extracting the banner code we get:

printOwing
   | outstanding |
   
   outstanding := 0.0.
   self printBanner.
   
   outstanding := _orders inject: 0 into: [:sum :each | sum + each].
   
   Transcript
      show: 'Name: ';
      show:  _name;
      cr;   
      show: 'Amount: ';
      show: outstanding;
      cr.

printBanner
   Transcript
      show: '********************';
      cr;   
      show: '***Customer Owes***';
      cr;   
      show: '********************';
      cr


Doc 14, Refactoring Intro Slide # 12
Examples: Using Local Variables

We can extract printDetails: to get

printOwing
   | outstanding |
   self printBanner.
   outstanding := _orders inject: 0 into: [:sum :each | sum + each].
   self printDetails: outstanding

printDetails: aNumber
   Transcript
      show: 'Name: ';
      show:  _name;
      cr;   
      show: 'Amount: ';
      show: aNumber;
      cr.

Then we can extract outstanding to get:

printOwing
   self 
      printBanner;
      printDetails: (self outstanding)

outstanding
   ^_orders inject: 0 into: [:sum :each | sum + each]

The text stops here, but the code could use more work

Doc 14, Refactoring Intro Slide # 13
Using Add Parameter (275)

printBanner
   Transcript
      show: '********************';
      cr;   
      show: '***Customer Owes***';
      cr;   
      show: '********************';
      cr

becomes:

printBannerOn: aStream
   aStream
      show: '********************';
      cr;   
      show: '***Customer Owes***';
      cr;   
      show: '********************';
      cr

Similarly we do printDetails and printOwing

printOwingOn: aStream
   self printBannerOn: aStream.
   self
      printDetails: (self outstanding)
      on: aStream

Perhaps this should be called Replace Constant with Parameter


Doc 14, Refactoring Intro Slide # 14

Simplifying Conditional Expressions

Decompose Conditional[4]


You have a complicated conditional (if-then-else) statement

Extract methods from the condition, then part and else parts

Example[5]

   (date before: SummerStart) | (date after: SummerEnd)
      ifTrue:[ charge := quantity * _winterRate + _winterServiceCharge]
      ifFalse:[ charge := quantity + _summerRate]

becomes

   (self notSummer: date)
      ifTrue: [ charge := self winterCharge: quantity]
      ifFalse: [ charge := self summerCharge: quantity]
or the more Smalltalk like:

   charge := (self notSummer: date)
            ifTrue: [self winterCharge: quantity]
            ifFalse: [self summerCharge: quantity]
Each method ( notSummer, winterCharge, summerCharge) should be extracted and tested one at a time

Doc 14, Refactoring Intro Slide # 15

Consolidate Conditional Expression[6]


You have a sequence of conditional tests with the same result

Combine them into a single conditional expression and extract it

Example

disabilityAmount
   _senority < 2 ifTrue: [^0].
   _monthDisabled  > 12   ifTrue: [^0].
   self isPartTime  ifTrue: [^0].
   "compute the disabilty amount here"
becomes

disabilityAmount
   (_senority < 2) | (_monthDisabled  > 12) | (self isPartTime)  ifTrue: [^0].
   "compute the disabilty amount here"
becomes:

disabilityAmount
   self isNotEliableForDisability ifTrue: [^0].
   "compute the disabilty amount here"


Doc 14, Refactoring Intro Slide # 16

Consolidate Duplicate Conditional Fragments[7]


The same fragment of code is in all branches of a conditional expression

Move it outside of the expression

Example

   self isSpecialDeal
      ifTrue:
         [total := price * 0.95.
         self send]
      ifFalse:
         [total := price * 0.98.
         self send]

Consolidating we get:

   self isSpecialDeal
      ifTrue:[total := price * 0.95]
      ifFalse:[total := price * 0.98].
   self send

A more Smalltalk like version:

   total := self isSpecialDeal
      ifTrue:[price * 0.95]
      ifFalse:[price * 0.98].
   self send


Doc 14, Refactoring Intro Slide # 17
Example continued

The text stops here, but the code could use more work

Use Introduce Explaining Variable (124) to improve readability

   discountRate := self isSpecialDeal
            ifTrue:[0.95]
            ifFalse:[0.98].
   total := price * discountRate.
   self send
Using Replace Temp with Query (120) we get:

   total := price * self discountRate.
   self send

Where we have

discountRate
   ^self isSpecialDeal
      ifTrue:[0.95]
      ifFalse:[0.98]
In Java or C++ we could use Replace Magic Number with Symbolic Constant (204) on the 0.95 and 0.98 to improve readability

In Smalltalk we can use Introduce Explaining Variable (124) or Constant Method (Beck)

Doc 14, Refactoring Intro Slide # 18

Remove Control Flag[8]


You have a variable that is acting as a control flag for a series of boolean expressions

Use a break or return instead


Doc 14, Refactoring Intro Slide # 19
Example[9]

checkSecurity: people
   | found |
   found := false.
   1 to: people size do:
      [:index |
      found ifFalse:
         [((people at: index) = 'Don') ifTrue:
            [self sendAlert().
            found := true].
         ((people at: index) = 'John') ifTrue:
            [self sendAlert().
            found := true]]]

Becomes:

checkSecurity: people
   people containsMiscreant ifTrue:[self sendAlert()]

containsMiscreant
   1 to: self size do:
      [:index |
      ((self at: index) = 'Don') ifTrue: [^true].
      ((self at: index) = 'John') ifTrue: [^true]].
   ^false

In Squeak the latter becomes:

containsMiscreant
   ^self anySatisfy: [:each | (each = 'Don') | (each = 'John')]


Doc 14, Refactoring Intro Slide # 20

Replace Nested Conditional with Guard Clauses[10]


A method has conditional behavior that does not make clear the normal path of execution

Use guard clauses for all the special cases

Example

payAmount
   | result |
   _isDead 
      ifTrue: [result := self deadAmount]
      ifFalse: 
         [_isSeparated 
            ifTrue:[result := self separatedAmount]
            ifFalse:
               [_isRetired 
                  ifTrue:[result := self retiredAmount]
                  ifFalse:[result := self normalPayAmount]]].
   ^ result

becomes

payAmount
   _isDead ifTrue: [^self deadAmount].
   _isSeparated ifTrue:[^self separatedAmount].
   _isRetired ifTrue:[^self retiredAmount].
   ^self normalPayAmount.


Doc 14, Refactoring Intro Slide # 21

Replace Conditional with Polymorphism[11]


You have a conditional that chooses different behavior depending on the type of an object

Move each leg of the conditional to an overriding method in a subclass. Make the original method abstract


Doc 14, Refactoring Intro Slide # 22
Example

Employee>>payAmount
   _type = Employee engineer ifTrue:[^ self _monthlySalary].
   _type = Employee manager ifTrue:[^ self _monthlySalary * 2].
   _type = Employee instructor ifTrue:[^ self _monthlySalary/2].
   self error: 'Invalid Employee'

becomes:


Employee>>payAmount
   ^_type payAmount: self

EmployeeType>>payAmount: anEmployee
   self subclassResponsibility

Engineer>>payAmount: anEmployee
   ^anEmployee monthlySalary

Manager>>payAmount: anEmployee
   ^anEmployee monthlySalary * 2

Instructor>>payAmount: anEmployee
   ^anEmployee monthlySalary/ 2


Doc 14, Refactoring Intro Slide # 23

Introduce Null Object[12]


You have repeated checks for a null value

Replace the null value with a null object

Example

customer isNil
   ifTrue: [plan := BillingPlan basic]
   ifFalse: [plan := customer plan]

becomes:


   NullCustomer>>plan
      ^BillingPlan basic


Now the code is:

plan := customer plan



Doc 14, Refactoring Intro Slide # 24

Introduce Assertion[13]


A section of code assumes something about the state of the program

Make the assumption explicit with an assertion

Example

getExpenseLimit
   "Should have either expense limit or a primary project"
   ^_expenseLimit isNil
      ifTrue:[_expenseLimit]
      ifFalse:[_primaryProject memberExpenseLimit]

Becomes:

getExpenseLimit
   self assert: [_expenseLimit isNotNil | primaryProject isNotNil].
   ^_expenseLimit isNil
      ifTrue:[_expenseLimit]
      ifFalse:[_primaryProject memberExpenseLimit]


Recall that $_ is used to indicate an instance variable (_primaryProject)

Squeak does have an assert: method in Object


Doc 14, Refactoring Intro Slide # 25
[1] Pragmatic Programmer, pp. 4-5
[2] Refactoring Text, pp. 110-116
[3] Example code is Squeak version of Fowler's Java example
[4] Refactoring Text, pp. 238-239
[5] Recall that "_" indicates an instance variable
[6] Refactoring Text, pp. 240-242
[7] Refactoring Text, pp. 243-244
[8] Refactoring Text, pp. 245-249
[9] John and Don happen to be the first names of the authors of the Refactoring Browser. Both are excellent Smalltalk programmers. I do not know why Fowler uses those names as examples of miscreants :)
[10] Refactoring Text, pp. 250-254
[11] Refactoring Text, pp. 255-259
[12] Refactoring Text, pp. 260-266
[13] Refactoring Text, pp. 267-270

Copyright ©, All rights reserved.
2001 SDSU & Roger Whitney, 5500 Campanile Drive, San Diego, CA 92182-7700 USA.
OpenContent license defines the copyright on this document.

Previous    visitors since 20-Mar-01    Next