refactoring responsibly

82
VANQUISHING THE FIRST DRAFT IN PRODUCTION CODE Refactoring Responsibly Drew Shefman @dshefman [email protected]

Upload: spiro

Post on 11-Feb-2016

67 views

Category:

Documents


0 download

DESCRIPTION

Refactoring Responsibly. Vanquishing the first draft In production code. Drew Shefman @dshefman [email protected]. Who am I? . Freelance Developer / Architect / Mentor 15+ years professional multimedia experience Adobe Certified Flex Expert / Instructor - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: Refactoring Responsibly

VANQUISHING T H E

FIRST DRAFTI N

PRODUCTION CODE

Refactoring Responsibly

Drew Shefman@dshefman

dshefman@squaredi . com

Page 2: Refactoring Responsibly

Who am I?

Freelance Developer / Architect / Mentor 15+ years professional multimedia experience

Adobe Certified Flex Expert / InstructorProfessor @ University of HoustonConsultant @ Twin Technologies

Drew Shefman@dshefman

dshefman@squaredi . com

Page 3: Refactoring Responsibly

Who are you?

Team member that

modifies or maintains

production level,

involved,

legacy code.

You touch the code

Significant financial cost if it breaks

Likely has appreciable code

smells

Code missing automated tests

Page 4: Refactoring Responsibly

This presentation is NOT about

TheoriesTest Driven Development (TDD) guilt tripsYesterday’s feature additionContrived, convoluted code

Page 5: Refactoring Responsibly

This presentation IS about

Practical answers for Why refactor? When to refactor? How to refactor?

Enabling real-world production code refactoring Extract & Override Characterization tests

Refactoring Observations

Page 6: Refactoring Responsibly

Do NOT Wait

Page 7: Refactoring Responsibly

WHAT IS IT?

WHY DO IT?

WHEN?

Refactoring?

Page 8: Refactoring Responsibly

What is refactoring?

Definition

“A process of changing a software system in such a way that it DOES NOT alter the external behavior of the code yet improves its internal structure”

-- Martin Fowler: Refactoring: Improving the Design of Existing Code

Page 9: Refactoring Responsibly

What is refactoring?

Refactored code

VisualDefinition

Page 10: Refactoring Responsibly

What refactoringis NOT

Adding functionalityFixing bugsDesign enhancementsDiscarding & rewritingSubstantial changes

Page 11: Refactoring Responsibly

Ideally Before making functional changes

Realistically When the • smell • complexity• trepidation • fragility

exceeds personal thresholds

Specifically When all tests EXIST & PASS!

When to refactor?

Page 12: Refactoring Responsibly

Increasing refactoring opportunities

When to refactor?

Page 13: Refactoring Responsibly

When NOT to refactor

For the sake of refactoringToo many classes affectedPotentially momentarily

unusableWhile adding a featureWhile fixing a bugWhen you don’t have tests

to support your change

Page 14: Refactoring Responsibly

When NOT to refactor

Supporting tests are missing

Page 15: Refactoring Responsibly

Why is refactoring needed?

If it works the first time, why change it?

The majority of legacy code

is a first draft!

Page 16: Refactoring Responsibly

Why not do it right the first time?

Anything important, deserves more than a first draft.

First Drafts*

40% not entirely hopeless, possibly reusable elements

5% GOLD

25% YAGNI

(You Ain’t Gonna Need It)

15%Random annoying inconsistencies

15%Code that

belongs elsewhere

* My personal non-empirical guess, based loosely on stats from copy

editors

Page 17: Refactoring Responsibly

Why refactor?

We want to add value, not spend time searching for how it works

Page 18: Refactoring Responsibly

Why stake holders dislike refactoring

Fear of breakingRefactoring =

uncontrolled && high riskNo ROI to “fix”

something that is “working”

Page 19: Refactoring Responsibly

How proper refactoring placates stake holders

Fear of Breaking

Characterization tests

Uncontrolled / high risk

Small precision efforts, backed by automated tests

Low ROI, it is already working

What is there was “working”, but it won’t with the new feature.

no refactor = no feature || no bug

Page 20: Refactoring Responsibly

How to refactor?

1. Define stopping point2. Identify class’s purpose3. Reorganize within class4. Characterization tests5. Extract & Override*6. All tests pass7. Modify the code8. Verify all tests pass

*optional

Page 21: Refactoring Responsibly

How to refactor?

Resources

Page 22: Refactoring Responsibly

How to refactor?

ToolsExtract MethodExtract InterfaceExtract VariableExtract Field…

Page 23: Refactoring Responsibly

How to refactor?

Tools:Extract Method

PLUGIN: SourceMate by ElementRiver

IDE: IntelliJ by JetBrains

Page 24: Refactoring Responsibly

Case Study:Order Presentation Model

• The Code• Define stopping point• Identify class purpose• Reorganize within class• Create characterization tests• Extract & Override• Verify all tests pass• Refactor

Page 25: Refactoring Responsibly

Similar methods

openCreateNewOrderPopUp()

openQuickOrderPopup() openImportOrderPopup() exportOrder() showCancelNotEditableOrd

er() showModifyNotEditableOrd

er()

Original Class: Excessive Duplication

Page 26: Refactoring Responsibly

Similar methods Duplication Code Pattern

if (submittedOrder) {

alertUser(uniqueMessage)

}else{

showPopup(uniqueView)}

Original Class: Excessive Duplication

openCreateNewOrderPopUp()

openQuickOrderPopup() openImportOrderPopup() exportOrder() showCancelNotEditableOrd

er() showModifyNotEditableOrd

er()

Page 27: Refactoring Responsibly

Original Code

public function openCreateNewOrderPopUp():void{

if (orderModel.inMSOState){

alertMSOToUser(RequestTypesBeforeSwitchingMSO.CREATE_NEW_ORDER)}else{

var createOrderPopup:CreateOrderView = new CreateOrderView() ;

new SM_PopupComponentEvent(createOrderPopup,true).dispatch()}

}

if (submittedOrder)alertUser(msg)

createPopup(instance)

Page 28: Refactoring Responsibly

Before starting to refactor…

Predetermine specifically when

you are going to stop.

There is ALWAYS more to clean up.

Page 29: Refactoring Responsibly

Determining when to stop

No changes to public interfacePopup & alert duplication eliminated100% test coverage on public interface

Page 30: Refactoring Responsibly

Ideal Purpose Actual Purpose

Manage creating, reading, updating, & deleting*

orders by dispatchingbusiness events

Order CRUD eventsCreate alerts &

popupsHandle responses

from alerts & popupsPricing processingDate processingHolding order state

Indentifying Purpose

*CRUD

Page 31: Refactoring Responsibly

Indentifying Purpose

Knowingthe purpose

helps determineswhat stays in

and what moves out.

Page 32: Refactoring Responsibly

Intra-Class Organization

Rearrange / cluster related variables and methods

Organize importsRemove disabled

code (commented)Remove UNUSED

private variables / methods

Page 33: Refactoring Responsibly

Use EXTREME CONFIDENCE while organizing

Page 34: Refactoring Responsibly

Characterization Tests

Characterization test:

A means to describe (characterize) the actual behavior of an existing piece of software, and therefore protect existing behavior of legacy code against unintended changes via automated testing.

--Wikipedia

Page 35: Refactoring Responsibly

Characterization Tests

One method at a timeExercise the ENTIRE method

Testing might not be easy.

Page 36: Refactoring Responsibly

Testing Challenges

public function openCreateNewOrderPopUp():void{

if (orderModel.inMSOState){

alertMSOToUser(RequestTypesBeforeSwitchingMSO.CREATE_NEW_ORDER)}else{

var createOrderPopup:CreateOrderView = new CreateOrderView() ;

new SM_PopupComponentEvent(createOrderPopup,true).dispatch()}

}

Dependency to OrderModel• Must exist• Must be in correct state

Page 37: Refactoring Responsibly

Testing Challenges

public function openCreateNewOrderPopUp():void{

if (orderModel.inMSOState){

alertMSOToUser(RequestTypesBeforeSwitchingMSO.CREATE_NEW_ORDER)}else{

var createOrderPopup:CreateOrderView = new CreateOrderView() ;

new SM_PopupComponentEvent(createOrderPopup,true).dispatch()}

}

Private method, calling Alert.show()• Creates a visual element• Difficult to verify

Page 38: Refactoring Responsibly

Testing Challenges

public function openCreateNewOrderPopUp():void{

if (orderModel.inMSOState){

alertMSOToUser(RequestTypesBeforeSwitchingMSO.CREATE_NEW_ORDER)}else{

var createOrderPopup:CreateOrderView = new CreateOrderView() ;

new SM_PopupComponentEvent(createOrderPopup,true).dispatch()}

}

Cairngorm 2, static event dispatcher• Event doesn’t broadcast from this

class• Annoying to capture

Page 39: Refactoring Responsibly

Testing Challenges

public function openCreateNewOrderPopUp():void{

if (orderModel.inMSOState){

alertMSOToUser(RequestTypesBeforeSwitchingMSO.CREATE_NEW_ORDER)}else{

var createOrderPopup:CreateOrderView = new CreateOrderView() ;

new SM_PopupComponentEvent(createOrderPopup,true).dispatch()}

}

Dependency to orderModel

Private method, calling Alert.show

Cairngorm 2, static event dispatcher

Page 40: Refactoring Responsibly

Ill-suited Suitable

OrderModel instance Many other dependencies Not part of the testing

“unit”

Mocking Introducing an unfamiliar

framework was not an appropriate option at the time

Extract & Override Improves readability Enables dependency

breaking within tests Immediately

understandable

Challenges: Dependency to OrderModel

Page 41: Refactoring Responsibly

Original Extracted Method

if (orderModel.inMSOState)…

if (modifyingSubmittedOrder())…

protected function modifyingSubmittedOrder():Boolean

{ return orderModel.inMSOState}

Extract Method

Potential for dependency elimination in subclass

Page 42: Refactoring Responsibly

Subclass: OrderPresentationModelTester

public class OrderPresentationModelTester extends OrderPresentationModel

{public var isModifiyingSubmittedOrder:Boolean = false;

override protected function modifyingSubmittedOrder():Boolean{

return isModifiyingSubmittedOrder;}

}

Page 43: Refactoring Responsibly

OrderPresentationModelTest

[Test]public function

openCreateNewOrderPopup_NotMSO_PopupEventWCreateOrderView():void

{var orderPresentationModel:OrderPresentationModel =

new OrderPresentationModelTester();

orderPresentationModel.openCreateNewOrderPopUp();fail(“The test compiles and fails… tests are working”);

}

Page 44: Refactoring Responsibly

Test Fails – This is good

Page 45: Refactoring Responsibly

What are we testing again?

public function openCreateNewOrderPopUp():void{

if (modifyingSubmittedOrder()){

alertMSOToUser(RequestTypesBeforeSwitchingMSO.CREATE_NEW_ORDER)}else{var createOrderPopup:CreateOrderView = new CreateOrderView() ;new SM_PopupComponentEvent(createOrderPopup,true).dispatch()}

} Test that event is dispatched with a 2 item payload

Page 46: Refactoring Responsibly

Reminder – not ideal environment

Unfortunately, we are not here.

We are here.

Page 47: Refactoring Responsibly

CairngormEventRecorder

public class CairngormEventRecorder{

public static function create():CairngormEventRecorder{

var rtn:CairngormEventRecorder = new CairngormEventRecorder();CairngormEventDispatcher.addEventListener(SM_PopupComponentEvent.EVENT, rtn.recordEvent,false,0,true);

return rtn;}

public static function destroy(inst:CairngormEventRecorder):void{

CairngormEventDispatcher.removeEventListener(SM_PopupComponentEvent.EVENT, inst.recordEvent);

}}

Capture Cairngorm

events minimizing

memory leaks

Page 48: Refactoring Responsibly

CairngormEventRecorder - Continued

public class CairngormEventRecorder{

public var storedEvents:Array = [];

public function recordEvent(e:Event):void{

storedEvents.push(e);}

public function getLastEvent():Event{

var len:int = storedEvents.length if (len >0){return Event(storedEvents[len]);}return null;

}}

Save / retrieve

dispatchedCairngorm

events

Page 49: Refactoring Responsibly

OrderPresentationModelTest

[Test] public function

openCreateNewOrderPopup_NotMSO_PopupEventWCreateOrderView():void{

var eventRecorder:CairngormEventRecorder = CairngormEventRecorder.create();

var orderPresentationModel:OrderPresentationModel = new OrderPresentationModelTester();orderPresentationModel.openCreateNewOrderPopUp();var lastEvent: SM_PopupComponentEvent = eventRecorder.getLastEvent() as SM_PopupComponentEvent ;

CairngormEventRecorder.destroy(eventRecorder);

assertNotNull(“Not Null”, lastEvent);assertTrue(“Popup Component”, lastEvent.component is CreateOrderView);assertTrue(“Modal”, lastEvent.modal);

}

Man

age

Eve

nt C

aptu

re

Page 50: Refactoring Responsibly

OrderPresentationModelTest

[Test] public function

openCreateNewOrderPopup_NotMSO_PopupEventWCreateOrderView():void{

var eventRecorder:CairngormEventRecorder = CairngormEventRecorder.create();

var orderPresentationModel:OrderPresentationModel = new OrderPresentationModelTester();orderPresentationModel.openCreateNewOrderPopUp();var lastEvent: SM_PopupComponentEvent = eventRecorder.getLastEvent() as SM_PopupComponentEvent ;

CairngormEventRecorder.destroy(eventRecorder);

assertNotNull(“Not Null”, lastEvent);assertTrue(“Popup Component”, lastEvent.component is CreateOrderView);assertTrue(“Modal”, lastEvent.modal);

}

SETU

P

Page 51: Refactoring Responsibly

OrderPresentationModelTest

[Test] public function

openCreateNewOrderPopup_NotMSO_PopupEventWCreateOrderView():void{

var eventRecorder:CairngormEventRecorder = CairngormEventRecorder.create();

var orderPresentationModel:OrderPresentationModel = new OrderPresentationModelTester();orderPresentationModel.openCreateNewOrderPopUp();var lastEvent: SM_PopupComponentEvent = eventRecorder.getLastEvent() as SM_PopupComponentEvent ;

CairngormEventRecorder.destroy(eventRecorder);

assertNotNull(“Not Null”, lastEvent);assertTrue(“Popup Component”, lastEvent.component is CreateOrderView);assertTrue(“Modal”, lastEvent.modal);

}

TEST

S

Page 52: Refactoring Responsibly

All Tests Pass

Page 53: Refactoring Responsibly

Original Extracted Method

private function alertMSOToUser (requestType:String):

void{

… Alert.show(requestType)}

private function alertMSOToUser (requestType:String): void

{…

createAlert(requestType)}

protected function createAlert(msg:String):void

{Alert.show(msg)

}

Extract & Override: alertMSOToUser

* Other 7 arguments not

shown for simplicity

Page 54: Refactoring Responsibly

Subclass: OrderPresentationModelTester

public class OrderPresentationModelTester extends OrderPresentationModel

{…

public var alertMsg:String = “”;

override protected function createAlert(msg:String):void{

alertMsg = msg;}

}

Page 55: Refactoring Responsibly

SETU

P

OrderPresentationModelTest - 2nd Test

[Test]public function

openCreateNewOrderPopup_IsMSO_AlertResubmitModifiedSubmitedOrder():void

{ …

var expectedString:String …orderPresentationModel.isModifyingSubmittedOrder = true;orderPresentationModel.openCreateNewOrderPopUp();var alertMsg:String = orderPresentationModel.alertMsg;

assertEquals(expectedString, alertMsg);

}Using Extract & Override, isolate Alert.show into

its own method

Trap and record arguments and assert against those

Page 56: Refactoring Responsibly

TES T

OrderPresentationModelTest - 2nd Test

[Test]public function

openCreateNewOrderPopup_IsMSO_AlertResubmitModifiedSubmitedOrder():void

{ …

var expectedString:String …orderPresentationModel.isModifyingSubmittedOrder = true;orderPresentationModel.openCreateNewOrderPopUp();var alertMsg:String = orderPresentationModel.alertMsg;

assertEquals(expectedString, alertMsg);

}

Page 57: Refactoring Responsibly

All Tests Pass

Page 58: Refactoring Responsibly

Review – Getting it under test

Extract methods (Use the tools)

Override methods to eliminate dependencies

Create helper objects if needed

Test everything that characterizes what is currently there.

Page 59: Refactoring Responsibly

Add more tests

Our major smell was duplicationGet the duplicated methods under

test

Page 60: Refactoring Responsibly

All Tests Pass

Page 61: Refactoring Responsibly

Can we make REAL changes yet?!

Page 62: Refactoring Responsibly

All Tests Pass – Refactoring Ready

Page 63: Refactoring Responsibly

Reminder –NO (FUNCTIONAL) CHANGE

Committed to stabilityEliminated duplicationImproved

maintainabilityReduced complexityIdentified

inconsistencies

Page 64: Refactoring Responsibly

Original Code

public function openCreateNewOrderPopUp():void{

if (orderModel.inMSOState){

alertMSOToUser(RequestTypesBeforeSwitchingMSO.CREATE_NEW_ORDER)}else{

var createOrderPopup:CreateOrderView = new CreateOrderView() ;

new SM_PopupComponentEvent(createOrderPopup,true).dispatch()}

}

Page 65: Refactoring Responsibly

Refactored Code

public function openCreateNewOrderPopUp():void{

if (modifyingSubmittedOrder())){

createAlert_ModifySubmittedOrder_Confirmation();}else{

createPopup_CreateNewOrder();

}}

Page 66: Refactoring Responsibly

if ( orderModel.inMSOState ) { alertMSOToUser(RequestTypesBeforeSwitchingMSO.CREATE_NEW_ORDER)} else {

var createOrderPopup:CreateOrderView = new CreateOrderView() ;

new SM_PopupComponentEvent(createOrderPopup,true).dispatch()}

if ( modifyingSubmittedOrder() ) { createAlert_ModifySubmittedOrder_Confirmation();} else {

createPopup_CreateNewOrder();}

Original & Refactored CodeO

RIG

INA

LR

EFA

CTO

RED

Page 67: Refactoring Responsibly

That’s it?! It looks nearly the same?

It *should*

It does the same thing

100% API test coverageDuplication eliminatedConsistent method naming

Page 68: Refactoring Responsibly

Refactored Consistent Protected Methods

createPopup_CreateNewOrder() createPopup_QuickOrderEntry() createPopup_ImportOrder() createPopup_ExportOrder() createPopup_CancelOrderWithNonEditableItems() createPopup_ModifyOrderWithNonEditableItems() createPopup(type)

  createAlert_OrderPastCutOff() createAlert_UnableToModifySubmittedOrder() createAlert_DeleteOrder_Confirmation() createAlert_CancelOrder_Confirmation() createAlert_ModifyOrder_Confirmation() createAlert_CancelSubmittedOrder_Confirmation() createAlert(msg)

Page 69: Refactoring Responsibly

Moving out responsibilities

public function createPopup(type:String):void{ var popup:UIComponent = popupFactory.create(type); new SM_PopupComponentEvent(popup,true).dispatch();}

public function createAlert(msg:String):void{ alertFactory.create(msg)}

Externalized object

creation

Page 70: Refactoring Responsibly

Other Refactoring Benefits

Alert inconsistencies identified & backlogged mx.Alert.show() spark.Alert.show() CustomAlert.show()

Page 71: Refactoring Responsibly

Metrics

Original RefactoredPublic methods 58 58Public variables 28 28Non-public methods 18 57PMD errors 23 4PMD warnings 37 13Import statements 157 99Tested methods 0 58

Page 72: Refactoring Responsibly

LESSONS LEARNEDAND

OTHER DISCOVERIES

Observations

Page 73: Refactoring Responsibly

Make a 2nd Draft

Anything that is important deserves / demands more than a first draft.

Page 74: Refactoring Responsibly

Be Responsible

Refactor responsiblyOnly modify once tests are in place

Page 75: Refactoring Responsibly

Work small

Work in tiny increments

Refactoring is precision surgery

Refactoring != rewriting from scratch

Page 76: Refactoring Responsibly

Extract & Override Dependencies

Isolate Creation code Evaluation code Control code UI Code

Page 77: Refactoring Responsibly

Q&A

D r e w S he f ma n@ d s h e f ma nd s h e f ma n @ s q ua r e d i . c o m

This presenta t ion* i s a t

h t tp : / /squared i . b logspot . com

* a n d s e v e r a l o t h e r r e f a c t o r i n g p o s t s

Page 78: Refactoring Responsibly
Page 79: Refactoring Responsibly
Page 80: Refactoring Responsibly

Practice

Use code katas, like the Gilded Rose, to practice refactoring in a safe environment.

https://github.com/dshefman/GildedRoseAS3

AS3 + other language versions:http://craftsmanship.sv.cmu.edu/

posts/gilded-rose-kata

Page 81: Refactoring Responsibly

Only add value

Refactor only if it adds value to the business

Page 82: Refactoring Responsibly

Respect

“Regardless of what we discover, we understand and truly believe that everyone did the best job they could, given what they knew at the time, their skills and abilities, the resources available, and the situation at hand.”

-- Retrospective Prime Directive

http://www.retrospectives.com/pages/retroPrimeDirective.html