it was like that when i got here: steps toward modernizing a legacy codebase

49
It Was Like That When I Got Here: Steps Toward Modernizing A Legacy Codebase Sunshine PHP 08 Feb 2013 paul-m-jones.com @pmjones joind.in/8001 Friday, February 8, 13

Upload: pmjones88

Post on 25-Dec-2014

2.519 views

Category:

Technology


2 download

DESCRIPTION

 

TRANSCRIPT

Page 1: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

It Was Like That When I Got Here:Steps Toward Modernizing A Legacy Codebase

Sunshine PHP08 Feb 2013

paul-m-jones.com@pmjones

joind.in/8001

Friday, February 8, 13

Page 2: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Read These

Friday, February 8, 13

Page 3: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

About Me• 8 years USAF Intelligence

• PHP since 1999

• Developer, Senior Developer,Team Lead, Architect, VP Engineering

• Aura project, benchmarking series, Zend_DB, Zend_View

• PHP-FIG voting member,ZCE educational advisor

Friday, February 8, 13

Page 4: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Overview

• The (codebase) hole you’re in

• How to start climbing out

• Life is easier (but not perfect)

Friday, February 8, 13

Page 5: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

It Was Like ThatWhen I Got Here

Friday, February 8, 13

Page 6: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Messy Codebase

• Spaghetti include logic

• Few or no classes

• Global variables

• No unit tests -- QA working overtime

Friday, February 8, 13

Page 7: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

No Time To Remedy

• Bugs to fix, right now

• Features to implement, right now

• Making your own life easier?Not a priority.

• Dig in and try to make do

• How did it get this bad? “It was like that when I got here.”

Friday, February 8, 13

Page 8: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

The Great Thing About PHP ...

• ... is that anyone can use it.

• Have an idea? Implement it!

• It works! Great success!

• ... it “works.”

Friday, February 8, 13

Page 9: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

The Awful Thing About PHP ...

• ... is that anyone can use it.

• The codebase is like a “dancing bear”

• Architecture? Maintenance? Testing?

• Move on to the next idea ...

• ... but you are stuck with it now.

Friday, February 8, 13

Page 10: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Project Evolution Tracks

Standalone App

Library Collection

Modular App

Framework CMS

One-Off Heap

?

Friday, February 8, 13

Page 11: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

One-Off Heap

• No discernible architecture

• Browse directly to scripts

• Add to it piece by piece

• Little to no separation of concerns

• Global variables

Friday, February 8, 13

Page 12: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Standalone Application

• One-off heap ++

• Page scripts and common includes

• Installed in web root

• Responsible for global execution environment

• Script variables still global

Friday, February 8, 13

Page 13: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Typical Page Script

see editor for example

Friday, February 8, 13

Page 14: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Why Is It Like This?

• Original developer probably didn’t know better

• Subsequent developers worked with what was there

• “We can fix it later ...”

• ... until later becomes now.

Friday, February 8, 13

Page 15: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Technical Debt

• A metaphor referring to the eventual consequences of poor or evolving software architecture and software development within a codebase.

• As a change is started on a codebase, there is often the need to make other coordinated changes at the same time in other parts of the codebase.

• http://en.wikipedia.org/wiki/Technical_debt

Friday, February 8, 13

Page 16: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Paying Off Technical Debt

Friday, February 8, 13

Page 17: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Paying Off Technical Debt

• A lot like paying off financial debt

• Got the stuff first, but have to pay for it eventually

• No easy way out. Suffer as things are, or suffer through change.

Friday, February 8, 13

Page 18: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Big-Bang Approach

• Rewrite from scratch! (mmm, sexy)

• “It’s the only way to be sure.”

• Expend effort while not earning revenue

• End up with different bad architecture

Friday, February 8, 13

Page 19: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Incremental Approach

• Small changes across entire codebase

• Build on previous small changes

• Keeps running the whole time

Friday, February 8, 13

Page 20: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Incremental Goals

• Keep the application running

• Consolidate classes for autoloading (PSR-0)

• Convert globals to injected dependencies

Friday, February 8, 13

Page 21: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Consolidate Classes For Autoloading

Friday, February 8, 13

Page 22: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

// without autoloading, must include file firstinclude_once "/path/to/classes/Example/Name.php";$obj = new Example_Name();

// with autoloading, gets included automatically$obj = new Example_Name();

What Is Autoloading?

Friday, February 8, 13

Page 23: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

PSR-0

• Class name maps directly to file name

• Namespace separators map to directory separators

• Class underscores map to directory separators

• Vendor\Package_Name\Example_Name=> Vendor/Package_Name/Example/Name.php

Friday, February 8, 13

Page 24: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

function autoload($class){ $class = ltrim($class, '\\'); $file = ''; $ns = ''; $pos = strripos($class, '\\') if ($pos) { $ns = substr($class, 0, $pos); $class = substr($class, $pos + 1); $file = str_replace('\\', DIRECTORY_SEPARATOR, $ns) . DIRECTORY_SEPARATOR; } $file .= str_replace('_', DIRECTORY_SEPARATOR, $class); $base = "/path/to/classes"; require "{$base}/{$file}.php";}

spl_autoload_register('autoload');

Friday, February 8, 13

Page 25: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Move Class Files

• If you have class files in several paths, move to same base path

• If you have more than one class per file, split into separate files

• If you define classes as part of a script, extract to own file

• Remove include/require as you go (grep)

• If needed, change names as you go (grep)

Friday, February 8, 13

Page 26: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Convert Function Files to Class Files

• Many projects have files of function definitions

• Wrap in a class as static or instance methods

• Move to classes directory

• Change calls to static or instance calls (grep)

• Remove include/require as you go (grep)

Friday, February 8, 13

Page 27: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Original Function

function fetch_results(){ global $db; $results = $db->fetch('whatever'); return $results;}

$results = fetch_results();

Friday, February 8, 13

Page 28: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Static Method

class Example{ public static function fetchResults() { global $db; $results = $db->fetch('whatever'); return $results; }}

$results = Example::fetchResults();

Friday, February 8, 13

Page 29: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Instance Method

class Example{ public function fetchResults() { global $db; $results = $db->fetch('whatever'); return $results; }}

$example = new Example;$results = $example->fetchResults();

Friday, February 8, 13

Page 30: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Convert Globalsto Injected Dependencies

Friday, February 8, 13

Page 31: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Instantiating Dependencies In Methods

class Example{ public function fetchResults() { $db = new Database('username', 'password'); return $db->fetch('whatever'); }}

Friday, February 8, 13

Page 32: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Drawbacks Of Method Instantiation

• New connection on each call

• Cannot reuse connection

• Parameter modification

Friday, February 8, 13

Page 33: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Global Dependencies

// setup file$db = new Database('username', 'password');

// example class fileclass Example{ public function fetchResults() { global $db; return $db->fetch('whatever'); }}

Friday, February 8, 13

Page 34: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Global Drawbacks

class Evil{ public function actionAtADistance() { global $db; unset($db); }}

Friday, February 8, 13

Page 35: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Dependency Injection

• Instead of reaching out from inside the class to bring in dependencies ...

• ... inject the dependency into the class from the outside.

Friday, February 8, 13

Page 36: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Starting Point: Global In Method

class Example{ public function fetchResults() { global $db; return $db->fetch('results'); }}

Friday, February 8, 13

Page 37: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Interim: Global In Constructor

class Example{ public function __construct() { global $db; $this->db = $db; } public function fetchResults() { return $this->db->fetch('results'); }}

Friday, February 8, 13

Page 38: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Final: Dependency Injection

class Example{ public function __construct($db) { $this->db = $db; } public function fetchResults() { return $this->db->fetch('results'); }}

Friday, February 8, 13

Page 39: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

• Must change all new instantiations to pass dependencies (grep)

• Class instantiation inside methods? Pass intermediary dependencies.

Change Instantiation Calls

Friday, February 8, 13

Page 40: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Intermediary Dependencyclass Example{ public function fetchResults() { global $db; return $db->fetch('whatever'); }}

class Service{ public function action() { $example = new Example; return $example->fetchResults(); }}

Friday, February 8, 13

Page 41: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

class Example{ public function __construct($db) { $this->db = $db; } public function fetchResults() { return $this->db->fetch('whatever'); }}

class Service{ public function __construct($db) { $this->db = $db; } public function action() { $example = new Example($this->db); return $example->fetchResults(); }}

Friday, February 8, 13

Page 42: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Eliminate Intermediary Dependency

class Service{ public function __construct($example) { $this->example = $example; } public function action() { return $this->example->fetchResults(); }}

Friday, February 8, 13

Page 43: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Progression of Instantiation

// all globals$service = new Service;

// intermediary: Example uses DI,// but Service creates Example internally$db = new Database('username', 'password');$service = new Service($db);

// all DI all the time$db = new Database('username', 'password');$example = new Example($db);$service = new Service($example);

Friday, February 8, 13

Page 44: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Life After Reorganizing

Friday, February 8, 13

Page 45: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Initial Goals Completed ...

• Consolidated into classes with PSR-0 and autoloading

• Removed globals in favor of dependency injection

• Kept it running the whole time

Friday, February 8, 13

Page 46: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

... But Much Remains

• Things are still not perfect

• Individual page scripts still in place

• No tests yet? Jeff Carouth, “Introducing Tests in Legacy PHP Applications”

• But do write tests, or else ...

Friday, February 8, 13

Page 47: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

WE ALL TEST DOWN (with apologies to Stephen King’s “It”)

Friday, February 8, 13

Page 48: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

It’s Better Than Before

• ... easier to test the classes you have

• Organizational structure for future work

• Paid off a lot of technical debt

Friday, February 8, 13

Page 49: It Was Like That When I Got Here: Steps Toward Modernizing a Legacy Codebase

Thanks!

paul-m-jones.com@pmjones

joind.in/8001

auraphp.github.com

Friday, February 8, 13