1 of 89

REFACTORING

Tests against spect

Technical

debt!!!

2 of 89

2 Weird Tricks of Agile:

  1. TESTS. Write tests that ensure that you know that your code is meeting spec (or the tests fail until you meet spec)
  2. Fluency with REFACTORING.

3 of 89

4 of 89

You start small

5 of 89

Then you add something

6 of 89

Then you add something else:

DELOCALIZED CHANGE!!!!!

7 of 89

And then just one more small thing

8 of 89

And then build more!

And then maybe you add more discounts (like a seniors discount)... and more kinds of statements (simple statement, long form statement… etc)

9 of 89

The code changes...

And code issues emerge (duplication, rigidity, lack of reusability, mess)

If it’s larger scale it’s called Technical Debt

If it’s a cosmetic issue you can point to, it’s called a Code Smell

But really technical debt and code smells refer to the same thing: code that needs be restructured, before changes can be safely made or new code can be added.

Technical debt/Code smells are expected in agile methodologies!

10 of 89

A basic principle emerges: (emergent abstraction)

Any time you are trying to make a change, and it is difficult because either...

  1. You have to make that change in more than one place
  2. Changes (you can tell) will result in merge conflicts
  3. The code is too obfuscated to be clearly understood

...you run the risk of introducing bugs!

To avoid this: REFACTOR BEFORE you make the change introduce abstraction to either

  1. Solve the duplication/clone/scattering that causes the multiple change locations
  2. Solve the tangling between the pieces of functionality by splitting them up
  3. Make the code clearer and more understandable

This describes most (all?) code smells

11 of 89

What is REFACTORING?

A predictably meaning preserving code transformation.

12 of 89

Typical refactoring workflows

In an industry setting (where you are pushing code to production for release):

  • Refactor the portions you’ll be changing at the start of the sprint (or as you realise you need those parts to be cleaner
  • Implement your changes (being as clean as possible, but without obsessing) until all your tests pass
  • Release the code (without refactoring right before release)

In a personal project, which you’ll be sharing with others on, say, Github:

  • Refactor the portions you’ll be changing at the start of the sprint (or as you realise you need those parts to be cleaner
  • Implement your changes (being as clean as possible, but without obsessing) until all your tests pass
  • Refactor the new parts so they are readable and well modularised
  • Release the code to the repository for others to download

13 of 89

How to refactor?

  1. Make sure all your tests pass
  2. Identify the code smell
  3. Determine how to refactor this code
  4. Apply the refactoring
  5. Run tests to make sure you didn’t break anything
  6. Repeat until the smell is gone

“apply a refactoring”

Change is desired

Don’t refactor...

  • When the tests are failing
  • When you should just rewrite the code
  • When you have impending deadlines

14 of 89

Common Code Smells

  • Magic Numbers
  • Duplicated Code
  • Long Method
  • Switch Statements/Type Conditionals
  • Large class (doing the work of two)

  • Divergent Change
  • Shotgun Surgery

  • Comments (this is an interesting one)

15 of 89

“apply a refactoring”

Means restructure your code through one of these refactoring types

16 of 89

Common Code Smells

  • Magic Numbers
  • Duplicated Code
  • Long Method
  • Switch Statements/Type Conditionals
  • Large class (doing the work of two)

  • Divergent Change
  • Shotgun Surgery

  • Comments (this is an interesting one)
  • Feature Envy

Note: Divergent change is only within one class

MAGIC NUMBERS

17 of 89

How do we deal with Magic Numbers?

double potentialEnergy(double mass, double height) {

return mass * 9.81 * height;

}

Any use of an actual number right in the code

Maybe make this a constant

  1. You have to make that change in more than one place
  2. Changes (you can tell) will result in merge conflicts
  3. The code is too obfuscated to be clearly understood

Push your meanings into the type system.

double potentialEnergy(double mass, double height) {

Float gravitationalConstants = 9.81;

return mass * gravitationalConstant * height;

}

18 of 89

Magic Numbers

What to do?

These are not constants

maybe make them variables??

Fee

Days limit

Extra fee multiplier

Extra fee adjustment

19 of 89

Transforming Magic Numbers

20 of 89

No magic numbers...

21 of 89

Common Code Smells

  • Magic Numbers
  • Duplicated Code
  • Long Method
  • Switch Statements/Type Conditionals
  • Large class (doing the work of two)

  • Divergent Change
  • Shotgun Surgery

  • Comments (this is an interesting one)
  • Feature Envy

COMMENTS

22 of 89

Comments are NECESSARY as specs.

So basically, “comments needed to explain code” do not refer to specifications (requires/modifies/effects) or “javadoc”. Those are integral to writing good code. Don’t stop specifying your methods!

23 of 89

Code smell: Needing comments to explain code

24 of 89

These comments are actually okay!

They are “TODO”s, not explanatory

25 of 89

Why are explanatory comments a problem?

People don’t respect explanatory comments!

You can change code, and not change the explanatory comments -- this leads to documentational drift

Explanatory comments don’t necessarily move with code (you can copy and paste code but leave an explanatory comment behind. Or implement something between the explanatory comment and the implementation, meaning the explanatory comment is now a little farther away!

26 of 89

Refactoring to fix explanatory comments

Extract method and give that method a useful name!

People respect method names -- people generally will not willingly change the contract of a method

Method “envelopes” move with code -- you invoke the code by calling the method, not by pasting the code. Also, you will always know which method you are in.

EXTRACT METHOD REFACTORING:

  1. Pull out the duplicate code.
  2. Parameterize with the element that is changing (or variable!!).

27 of 89

Let’s do an example in code...

28 of 89

What we did: Extracted methods that are named to reflect the comments

29 of 89

30 of 89

And keep going until you’re done

EXTRACT METHOD REFACTORING:

  • Pull out the code of interest.
  • Parameterize with the element that is changing (or variable!!).

METHOD AESTHETICS PRINCIPLE:

All lines in a method should be at the same level of abstraction.

31 of 89

Common Code Smells

  • Magic Numbers
  • Duplicated Code
  • Long Method
  • Complicated Conditionals
  • Switch Statements/Type Conditionals
  • Large class (doing the work of two)
  • Divergent Change
  • Shotgun Surgery

  • Comments (this is an interesting one)
  • Feature Envy

LONG METHOD

  1. You have to make that change in more than one place
  2. Changes (you can tell) will result in merge conflicts
  3. The code is too obfuscated to be clearly understood

32 of 89

Method design aesthetics

They should do just one thing … if you’re thinking at the right level of abstraction

33 of 89

Method design aesthetics

They should do just one thing … if you’re thinking at the right level of abstraction

34 of 89

Method design aesthetics

Don’t make them too long…

(this isn’t the whole method)

35 of 89

Method design aesthetics

Can we fix this???

36 of 89

Method design aesthetics

All the same level of abstraction

37 of 89

Try it yourself:

Method design aesthetics

It’s not crazily long, but it does have identifiable regions.

Grab the code here:

tps://drive.google.com/open?id=1TLUslGeyPbJmcQUXI-8DzrZKsNI4bUe1

Then submit your new init method here for activity credit:

38 of 89

When faced with a long method

sample rate

Frames per wave

Harmonics and sound

Loop count

EXTRACT METHOD REFACTORING:

  • Pull out the duplicate code.
  • Parameterize with the element that is changing (or variable!!).

39 of 89

Common Code Smells

  • Magic Numbers
  • Duplicated Code
  • Long Method
  • Switch Statements/Type Conditionals
  • Large class (doing the work of two)

  • Divergent Change
  • Shotgun Surgery

  • Comments (this is an interesting one)
  • Feature Envy

FEATURE ENVY

40 of 89

Code smell: Feature Envy

Means: The method is in the wrong class

When you find yourself wishing you had access to lots of fields from another class, probably the method you’re implementing should live in that class.

Law of Demeter: Too many dereferences mean the method is probably in the wrong spot

  1. You have to make that change in more than one place
  2. Changes (you can tell) will result in merge conflicts
  3. The code is too obfuscated to be clearly understood

41 of 89

Code smell: Feature Envy

Means: The method is in the wrong class

When you find yourself wishing you had access to lots of fields from another class, probably the method you’re implementing should live in that class.

Law of Demeter: Too many dereferences mean the method is probably in the wrong spot

  • You have to make that change in more than one place
  • Changes (you can tell) will result in merge conflicts
  • The code is too obfuscated to be clearly understood

Move all of this into fido.reactToBeingChased()

42 of 89

Fixing Feature Envy

Refactoring: Move the method (carefully)

  1. Run tests
  2. Refactor envious lines of code into a method, if they’re not already together
  3. COPY the method into the new location, comment out existing method
  4. Edit the method in new location to make it refer to “this” to use fields, and make the parameter the receiver, make public
  5. Reroute the call
  6. Rerun tests (should still pass)

43 of 89

Now you try

Open the video store project, and look at the refactor this code into Customer.getThisAmount method and then it’s still in the wrong spot….

  1. Where should it go?

  • Make the move

A call to getB().c() means you should likely move the behaviour in this method to B.

determineAmount()

44 of 89

Now you try

Open the video store project, and look at the refactor this code into Customer.getThisAmount method and then it’s still in the wrong spot….

  • Where should it go?

  • Make the move

A call to getB().c() means you should likely move the behaviour in this method to B.

determineAmount()

determineAmount()

determineAmount()

determineAmount()

45 of 89

Common Code Smells

  • Magic Numbers
  • Duplicated Code
  • Long Method
  • Switch Statements/Type Conditionals
  • Large class (doing the work of two)

  • Divergent Change
  • Shotgun Surgery

  • Comments (this is an interesting one)
  • Feature Envy

SWITCH ON TYPE

46 of 89

code smell: switch statement/typed conditional

a conditional that chooses different behaviour depending on the type of an object (or a weird string representation of that type)

Class Bird:

47 of 89

code smell: switch statement/typed conditional

a conditional that chooses different behaviour depending on the type of an object (or a weird string representation of that type)

getSpeed

getSpeed

getSpeed

Class Parrot:

48 of 89

code smell: switch statement/typed conditional

refactoring: replace conditional with polymorphism

b.getSpeed()

Bird class

Each case gets its own class

Each class gets its own version of the method

return getBaseSpeed();

Each method gets the contents of the if statement

Instantiate the subtype

Call the method on the instance

49 of 89

Try it… this is your starting point

50 of 89

  1. Make subclasses for each branch of the condition

Rerun tests

Tip: Copy over all the contents from the original class, then delete the parts you don’t want. This includes the if-statements you don’t need.

You will likely, at least temporarily, have to put in a call to the super constructor

51 of 89

2) Call the constructor for the new subclasses

Rerun tests

52 of 89

3) Copy the guts of the methods in the super class, paste them into the new subclass. Cut the bits I don’t need.

3b) and then comment them out in the super class, (this should actually be two steps)

rerun tests: The overriding methods should take over!

53 of 89

4) Make the original class abstract, and make the original methods abstract

Rerun tests.

54 of 89

5) Get rid of disused type variable (and associated constructors)

Note that the type variable is now unused!

Erase it and the associated constructors

55 of 89

Common Code Smells

  • Magic Numbers
  • Duplicated Code
  • Long Method
  • Complicated Conditionals
  • Switch Statements/Type Conditionals
  • Large class (doing the work of two)
  • Divergent Change
  • Shotgun Surgery

  • Comments (this is an interesting one)
  • Feature Envy

SAME METHOD IN SUBCLASSES

56 of 89

Smell: Same method in two classes

Refactoring: Pull up method

If the names are different but the contents the same:

  1. refactor->rename so that they’re the same (tests still pass)
  2. Copy the method and paste it into the superclass
  3. Comment out the method in the subclasses
  4. Delete method from subclasses

Salesperson

Salesperson

57 of 89

NamedEmployee

Salesperson

Engineer

Sanitation

getName

Employee

58 of 89

Same code in two classes (w/o shared supertype)

  1. Introduce supertype
  2. Pull up method

Class A

abc()

Class B

abc()

Class SuperAB

Does duplication between classes always mean you should pull a method, and maybe introduce a supertype.

If A and B already have a shared supertype then YES GO FOR IT.

MUST DO IT ONLY if supertype is a real LSP style supertype

Consider doing DELEGATION -- a uses relationship.

Class A

abc()

Class B

abc()

Inheritance

(what we just saw)

Composition or Delegation

59 of 89

Same code in two classes (w/o shared supertype)

  • Introduce supertype
  • Pull up method

Class A

Class B

Class SuperAB

abc

Does duplication between classes always mean you should pull a method, and maybe introduce a supertype.

If A and B already have a shared supertype then YES GO FOR IT.

MUST DO IT ONLY if supertype is a real LSP style supertype

Consider doing DELEGATION -- a uses relationship.

Class A

abc(){

o.abc()

}

Class B

abc(){

o.abc()

}

Class Other

abc()

Make a local variable of Other, and call other.abc()

Inheritance

(What we just saw)

Composition AKA Delegation

a.abc()

60 of 89

Restaurant

serveFood

Plane

plane.serveFood();

Jet

restaurant.serveFood();

FlyingThing

serveFood

61 of 89

Same code in two classes (w/o shared supertype)

Class A

abc(){

o.abc()

}

Class B

abc(){

o.abc()

}

Class Other

abc()

a.abc()

Class A

abc(){ o.abc() }

62 of 89

Common Code Smells

  • Magic Numbers
  • Duplicated Code
  • Long Method
  • Switch Statements/Type Conditionals
  • Large class (doing the work of two)

  • Divergent Change
  • Shotgun Surgery

  • Comments (this is an interesting one)
  • Feature Envy

Almost

ALMOST

DUPLICATE

CODE

63 of 89

ALMOST duplicate code

getBillableAmt(){

doSpecialStuff();

doRed

}

Abstract doSpecialStuff;

doSpecialStuff(){

doPurple

}

doSpecialStuff(){

doGreen

}

64 of 89

ALMOST duplicate code

rs.getBA()

if instanceof RS:

Do fuchsia

If instanceof LS:

Do green

base = unit * rate * getBaseMultiplier

tax = base * taxrate * getTaxMultiplier

65 of 89

Don’t do this!

Because it doesn’t ENFORCE that the same pattern is applied

doRed(){

}

getBillableAmt(){

doPurple

//super.doRed

doYellow

}

getBillableAmt(){

doGreen

super.doRed

}

66 of 89

Apply refactoring: Move to template method

  • Pull up the duplicate code (or duplicated logic) into the super method.
  • The super method calls an abstract helper method that inserts the contents
  • The subclasses provide the implementations for the contents method

67 of 89

Walker

Field namestring

walk() {printMessage(); ...}

abstract printMessage() {print “namestring is walking”};

Dog

constructor: nameString = ‘dog’

// walk(): print(‘I’m a dog walking’); super;

// printMessage: print(“I’m a dog walking”);

Cat

// walk: print(“I’m a cat walking”); super;

printMessage: print(“I’m a cat walking”);

68 of 89

Animal

walk(): does walking stuff;

LoudDog

walk: makes lots of noise; super.walk;

LoudCat

walk: slinks instead - doesn’t even use the supertype’s walk

69 of 89

Don’t keep the pattern in the subtypes

Class A

method():

print a

print bla

Class B

method():

print a

print eek

Class SuperAB

abstract method()

Class A

method():

print bla

printy()

Class B

method():

printy()

print eek

Class SuperAB

abstract method()

printy():

print a

The problem here, is that there is nothing FORCING the pattern to remain the same

A a = new A()

a.method()

client

70 of 89

Keep the template in the supertype

Class A

method():

print a

print bla

Class B

method():

print a

print eek

Class SuperAB

Class A

details():

print bla

Class B

details():

print eek

Class SuperAB

method()

print a

details()

ENFORCE that behavioural pattern remains the same, but details change

A a = new A()

a.method()

client

Template

Method

<<<<<<<<<<<<<

Enforces same pattern in all subtypes

71 of 89

Let’s do an example in code...

72 of 89

1) Identify what is unique, and what is the same

Unique to this class

Unique to this class

73 of 89

2) Pull up duplicate behaviour

74 of 89

3) Make an abstract method to placehold the unique elements,

Then override to provide specific behaviour

75 of 89

Common Code Smells

  • Magic Numbers
  • Duplicated Code
  • Long Method
  • Switch Statements/Type Conditionals
  • Large class (doing the work of two)

  • Divergent Change
  • Shotgun Surgery

  • Comments (this is an interesting one)
  • Feature Envy

INTER-CLASS SMELLS

76 of 89

Experiential symptoms of code smells … issues that you notice while trying to change the code

The issues that we have seen up to this point are all “static” -- they can be readily identified just by looking at the code.

Code smells are often not so neatly identifiable. In reality, they are often identified experientially -- as you try to change the code, or in the process of living with the code.

Two experiential symptoms that point to perhaps more elusive code smells are divergent changes and shotgun surgery.

Both of these often point to the need for a new class to abstract the tangled or scattered design elements.

  1. You have to make that change in more than one place
  2. Changes (you can tell) will result in development collisions
  3. The code is too obfuscated to be clearly understood

77 of 89

symptom: divergent changes

If you look at a class and say, "Well, I will have to change these three methods every time I get a new database; I have to change these four methods every time there is a new financial instrument," you likely have a situation in which two objects are better than one. That way each object is changed only as a result of one kind of change. Of course, you often discover this only after you've added a few databases or financial instruments.

78 of 89

symptom: divergent changes

will need to change whenever the investments implementation is changed

will need to change whenever the loans implementation is changed

will need to change every time the printing implementation is changed

79 of 89

Code Smell: One class is actually two

Refactoring: Extract class

Delegate as appropriate -- as fields or just dependencies.

Knows about relationship of any strength (field? Local? injected?)

80 of 89

Making the decision between DELEGATION and INHERITANCE

If the new classes feel like KINDS OF the original class ---> INHERITANCE

If the new classes feel like PARTS OF the original class ---> DELEGATION (using fields instead of subtyping)

We would not be able to say:

InvestmentAccount p = new PrinterThingy()

p.manageInvestments() //Can’t be substituted -- so can not be a subtype

81 of 89

symptom: divergent changes

code smell: one class is actually two

There is more than one field related to the telephone number, which gives it more weight. There might be additional methods supporting the phone number.

82 of 89

Code Smell: One class is actually two

Refactoring: Extract class

And delegate!!!

Person p = new Person()

p.getTelephoneNumber()

83 of 89

Code Smell: One class is actually two

Refactoring: Extract class

Seems to be about billing, not about being a customer!

84 of 89

Code Smell: One class is actually two

Refactoring: Extract class

1

The approach here is typically to make the class, then copy the methods over, then get them to compile, then alter the invocation so the new methods are being called, and run tests. Only then do you delete the old methods.

85 of 89

symptom: shotgun surgery

Catchy synonym for Delocalized Change

AKA Scattered Change

86 of 89

Code smell: Shotgun Surgery

Refactoring: move method; move field

A

subA

87 of 89

Summary of Entire Topic:

& Underpinning Principle

Refactoring (The Ruby Edition)

by Fields, Harvie and Fowler

Makes an important statement on OO design:

That classes not just be considered in terms of their apparent responsibility, but must also be centered around their empirically observed relevance!

Suggests we decompose your system to align with changes

88 of 89

Resources

  • “The” Book, by Martin Fowler
    • Refactoring: Improving the design of existing code

  • Smells to refactorings

  • List of refactorings

  • A refactoring “cheat sheet”

89 of 89

Examinable skills

You should be able to…

  • Explain what a code smell is
  • Explain the concept of refactoring
  • Explain when to refactor and when not to refactor
  • Identify code smells and be able to identify and carry out appropriate refactorings:
    • Feature envy/Law of Demeter violation → move method and where to put the new method.
    • Magic numbers → Constants or variables
    • Comments → extract methods and pipe through parameters
    • Same method in multiple subclasses → pull up method
    • Almost duplicate code → refactor to template
    • Switch on type → refactor to use polymorphism
    • Divergent changes → extract class
    • Shotgun surgery → introduce class(ses) and move methods
  • Explain that the underpinning principle of design is that changes should be as localised as possible
  • Achieve well named of methods, and identify method aesthetics, and when they are violated (we will deal with fixing violations in a later lecture)
  • Ability to fix feature envy by moving a method and where to put the new method.
  • Ability to Identify of violations of the law of demeter (that.thatsfield.nextfield.get())

Refactoring