This document discusses strategies for migrating legacy PHP code to a more modern test-driven development approach. It begins with an introduction to legacy code and the agenda. It then discusses typical problems with legacy PHP projects, such as spaghetti code and lack of testing. The document provides strategies for introducing test-driven development practices like writing tests first, refactoring, and using fixtures. It emphasizes incremental changes over a full rewrite to safely evolve the code over time.
Theory and practice – migrating your legacy code into our modern test driven development world.
1. Theory and practice – migrating your
legacy code into our modern test
driven development world.
Hartmann, Jankowfsky, Rinne
2. Legacy Code?
Wikipedia says „Legacy code is source code that
relates to a no-longer supported or
manufactured operating system or other
computer technology. The term can also mean
code inserted into modern software for the
purpose of maintaining an older or previously
supported feature“
Jankowfsky, Rinne – Mayflower/swoodoo
3. Agenda
- Where are we now, and why?
- Ammunition!
- Refactor!
Bad News? -> Workshop
Jankowfsky, Rinne – Mayflower/swoodoo
4. Who we are
Lars Jankowfsky:
- CTO and (Co)Founder swoodoo.com
- (Co)Founder of OXID eSales. Refactored OXID eShop during 1.5
years with 10 developers.
Thorsten Rinne:
- Senior Developer & Team Lead at Mayflower GmbH
- Founder and main developer of phpMyFAQ
Johann Peter Hartmann:
- CTO and Founder of Mayflower GmbH
- CEO and Founder of SektionEins GmbH
Jankowfsky, Rinne – Mayflower/swoodoo
5. Who are you?
- What‘s your profession?
- Sofware company or agency?
- What‘s your team size?
- Using MVC?
- Who does other languages, too?
- Using agile methods?
- Using continuous integration?
- Using unit tests?
Jankowfsky, Rinne – Mayflower/swoodoo
6. What about your projects?
- What‘s your average project lifetime?
- Is there PHP code more than 5 years old?
- How many lines of code?
- How many change requests per year?
- Has there been a specification?
- Were all features in the first released version
implemented like they‘re specified in the
specification?
Jankowfsky, Rinne – Mayflower/swoodoo
7. Typical problems?
- Typical legacy applications
- Started some years ago with PHP 4
- written in Spaghetti code
- half procedual, half object-orientated
- „PHP 4“ OOP
- using old, unmaintained libraries like PEAR::DB
Jankowfsky, Rinne – Mayflower/swoodoo
8. PHP, made in 2000
- no coding standards
- no PHPDoc
- no Design Patterns
- few separation of concerns
- has been changed a lot
- no refactoring, because „it worked“
- updated to run with php 4 in 2003
- updated to run with php 5 in 2006
Jankowfsky, Rinne – Mayflower/swoodoo
9. Big ball of mud
http://en.wikipedia.org/wiki/Big_ball_of_mud
A Big Ball of Mud is a haphazardly structured,
sprawling, sloppy, duct-tape-and-baling-wire,
spaghetti-code jungle.
These systems show unmistakable signs of
unregulated growth, and repeated, expedient
repair....
Jankowfsky, Rinne – Mayflower/swoodoo
10. Enough Ammunition?
- change requests get more and more expensive
- bug rate is increasing
- clearly a dead-end street!
- team motivation decreases
- hard to bring in new members into the team
- deprecated functions cause problems in future
PHP releases
Jankowfsky, Rinne – Mayflower/swoodoo
11. Enough Ammunition?
Ever heard things like this?
„Only X can fix that.“
„It will take ages to fix it.“
„Changing this button will take two weeks.“
„I don‘t want to work for this project.“
„I don‘t want to touch this code.“
„I don‘t know how this bug could reappear.“
Jankowfsky, Rinne – Mayflower/swoodoo
12. Enough Ammunition?
60-80 % of all development effort is maintenance
http://elearning.tvm.tcs.co.in/SMaintenance/SMaintenance/6.htm
http://www.bitpipe.com/detail/RES/1138902960_291.html
Jankowfsky, Rinne – Mayflower/swoodoo
13. What you should never do!
Please don‘t try a complete rewrite!
- Too expensive
- Takes too long
- the old codebase is used, tested & bugfixed
- Developers love to rewrite:
new code is more fun, code is easier to write
than to read
Jankowfsky, Rinne – Mayflower/swoodoo
14. Remember?
Netscape 6? Rewrite....
dBase for Windows? Rewrite....
Quattro Pro? Rewrite....
Access refatored...
Excel
Jankowfsky, Rinne – Mayflower/swoodoo
15. joel in 2000
„When you throw away code and start from scratch, you are
throwing away all that knowledge. All those collected bug
fixes. Years of programming work.“
http://www.joelonsoftware.com/articles/fog0000000069.html
Jankowfsky, Rinne – Mayflower/swoodoo
16. Test Driven Adoption
1. Unit tests for existing code with PHPUnit
2. experience of confidence in own code
3. Insight: Tests are easier if written before
software
4. Insight: Tests help documenting the code
5. Insight: Tests define the real API
Jankowfsky, Rinne – Mayflower/swoodoo
17. PHP and Unit Testing
- Layout & UI code is hard to unit-test,
acceptance-test instead
- test maintenance costs:
- unit test work fine with stable APIs
- high change rate in PHP results in API changes
- tests need to be changed, too
- slows down development, increases initial
development costs
- ... but your software survives more than 4 years
Jankowfsky, Rinne – Mayflower/swoodoo
18. Refactoring?
- Modifying code without changing it‘s behaviour
- „cleaning up“
“Refactoring is the 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)
Jankowfsky, Rinne – Mayflower/swoodoo
19. Team?
- experience in Test driven Development?
- „know how vs. understanding“
- In PHP? It‘s different to the Java World!
- Developers are conservative. They do not like
any changes. How much use still vi or emacs?
- Courage? - You need to make sure
that everybody
understands TDD before
you start.
Jankowfsky, Rinne – Mayflower/swoodoo
20. Let‘s start.
- Identify the nastiest, ugliest and...
- probably most important piece of code and
let‘s start with this one.
- if you take the easy files you won‘t solve the
critical issues and...
- move the risk to the end.
Jankowfsky, Rinne – Mayflower/swoodoo
22. Modifying without... ?????
- if you refactor you need tests to proove that
you did not break any functionality
- Have tests first. Then change code.
- legacy code ? There are no tests!!
Jankowfsky, Rinne – Mayflower/swoodoo
23. And now ?
- Write tests first.
- You will need to refactor your application while
writing tests.
- Write selenium tests for
your application. - no :(
Jankowfsky, Rinne – Mayflower/swoodoo
24. While refactoring ...
- adjust coding style
- add missing documentation
- remove redundant code / copy & paste-code
- remove unused(!) code
- maintain a list of future todos with priorities
Jankowfsky, Rinne – Mayflower/swoodoo
25. Spaghetti Code?
- Very old code, maybe developed in
the last PHP 3 century
- a lot of redundant copy-paste code
- missing separation of concerns
- No or just minor separation of code
and layout
- No use of libraries like PEAR, Zend
Framework or eZ components
- No or outdated documentation
- No tests at all
Jankowfsky, Rinne – Mayflower/swoodoo
26. Spaghetti Code?
function getThema($id, $lang)
{
global $db, $PMF_LANG;
$result = $db->query(sprintf(quot;SELECT thema FROM %sfaqdata WHERE
id = %d AND lang = '%s'quot;, SQLPREFIX, $id, $lang));
if ($db->num_rows($result) > 0) {
while ($row = $db->fetch_object($result)) {
$output = htmlentities($row->thema);
}
} else {
$output = $PMF_LANG[quot;no_catsquot;];
}
return $output;
}
phpMyFAQ 1.3.x 2002/2003
Jankowfsky, Rinne – Mayflower/swoodoo
27. Spaghetti Code - strategy
- Identify recurring code parts and implement
classes
- Use of standard libraries like Zend Framework
or eZ components
- Add inline documentation
- Fix your coding styles!
- Add unittests for the new, refactored backend
- Add Selenium tests for the frontend
Jankowfsky, Rinne – Mayflower/swoodoo
28. „Half procedual –halb
object-orientated“
- Code with different quality
- Just a few documentation
- Maybe some tests ... maybe ...
- „the typical current PHP 4 project“
- Found everywhere! Really everywhere!
Jankowfsky, Rinne – Mayflower/swoodoo
29. „Half procedual –half object-
orientated“ - strategy
- Add inline documentation for all classes and
methods
- Improve the re-using of duplicate code
- Add unittests and Selenium tests
- Improve every code part with PHP 5 functions,
for example using file_put_contents()
instead of fopen(), fwrite(), and
fclose().
Jankowfsky, Rinne – Mayflower/swoodoo
30. PHP 4 OOP
- Application was developed using „object-
orientated“ PHP 4
- Using of
- PHP 4 references
- Re-declaration of $this
Jankowfsky, Rinne – Mayflower/swoodoo
31. PHP 4 OOP - strategy
- Maybe you‘re lucky and there are no problems.
Maybe.
- If you see problems, they are fatal errors like
- Objects are referenced by value
- $foo =& new Foo();
- Solution:
- Implement unittests
- UsestandardAPIs
- Fix the PHP 5 problems
Jankowfsky, Rinne – Mayflower/swoodoo
32. Global Problems
- OOP, Public, Private ?
- Globals ?
- Super Globals...
- Session
- Cookies
Jankowfsky, Rinne – Mayflower/swoodoo
33. Proxy for testing protected methods
public function getProxy($superClassName)
{
$proxyClassName = quot;{$superClassName}Proxyquot;;
if (!class_exists($proxyClassName)) {
$class = <<<CLASS
class $proxyClassName extends $superClassName
{
public function __call($function, $args)
{
$function = str_replace('protected_', '_', $function);
return call_user_func_array(array(&$this, $function), $args);
}
}
CLASS;
eval($class);
}
return new $proxyClassName();
}
Jankowfsky, Rinne – Mayflower/swoodoo
34. Global ?
class someOtherClass {
var $setting;
function calculateSomething($a, $b) {
return $a+$b;
}
}
class myOldNastyClass {
function needToTestThisFunction() {
$class = new someOtherClass();
$z = $_GET['input'];
// ....
return $class->calculateSomething( $class->setting, $z);
}
}
Jankowfsky, Rinne – Mayflower/swoodoo
35. class someOtherClass {
private $setting;
public function calculateSomething($a, $b) {
return $a+$b;
}
public function setSetting($set) {
$this->setting = $set;
}
public function getSetting() {
return $this->setting;
}
}
class myInput {
public function getParameter($name) {
return $_GET[$name];
}
}
class myOldNastyClass {
private $input; // set e.g. in constructor
public function needToTestThisFunction(someOtherClass &$class, $z) {
$z = $input->getParameter('input');
// ....
return $class->calculateSomething( $class->getSetting(), $z);
}
}
Jankowfsky, Rinne – Mayflower/swoodoo
37. Dependencies...
class displayUserDetails()
{
/**
* Processes input and sends user first name, last name to display;
*/
function show() {
global $dbLink;
global $templateEngine;
$itemId = (int) $_REQUEST['user_id'];
$firstName = $dbLink->getOne(quot;select first_name from users where id = $itemIdquot;);
$lastName = $dbLink->getOne(quot;select last_name from users where id = $itemIdquot;);
$templateEngine->addTemplateVar('firstName', $firstName);
$templateEngine->addTemplateVar('lastName', $lastName);
$templateEngine->display();
}
}
Jankowfsky, Rinne – Mayflower/swoodoo
38. /**
* A view class responsible for displaying user details.
*/
class userView()
{
/**
* Loads user object and sends first name, last name to display
*/
public function show()
{
$userId = $this->_inputProcessor->getParameter(quot;user_idquot;);
$this->templateEngine->addTemplateVar('user', $this->model->loadUser(userId));
$this->templateEngine->display();
}
}
/**
* And the corresponding model
*/
class userModel()
{
public function loadUser($userId)
{
$user = new User( $userId );
return array( 'firstName' => $user->getFirstName(),
'lastName' => $user->getLastName());
}
}
Jankowfsky, Rinne – Mayflower/swoodoo
39. Fixtures
- Make sure that tests do not alter fixture.
- Fixture is FIXture
- if you feel that creating fixtures is too much
work - refactor more!
- Do never let tests leave altered data!
Jankowfsky, Rinne – Mayflower/swoodoo
40. Fixtures the ruby way...
- Ruby uses „YAML Ain’t Markup Language“
- http://www.yaml.org/
- PHP YAML support done via Syck
- Syck = YAML + fast.
- http://whytheluckystiff.net/syck/
- http://www.frontalaufprall.com/2008/05/05/
Jankowfsky, Rinne – Mayflower/swoodoo
41. yaml - loading
public static function create($fileName)
{
$fileName = 'Fixtures'.DIRECTORY_SEPARATOR.$fileName;
ob_start();
include $fileName;
$fileContents = ob_get_contents();
ob_clean();
$yamlData = syck_load($fileContents);
return $yamlData;
}
Jankowfsky, Rinne – Mayflower/swoodoo
42. yaml - storing
public static function load($fixtures, $tableName)
{
if (is_array($fixtures) && count($fixtures)) {
foreach ($fixtures as $fixture) {
if (is_array($fixture) && is_array(current($fixture))) {
Fixtures::load($fixture, $tableName);
}
$fields = array_keys($fixture);
$statement = quot;INSERT INTO $tableName (quot; . implode(', ', $fields) . quot;) VALUES (:quot; . implode(quot;, :quot;, $fields) . quot;)quot;;
$stmt = self::$_db->prepare($statement);
if (count($fixture)) {
foreach ($fixture as $key => $value ) {
$stmt->bindValue(':'.$key, $value);
}
}
$stmt->execute();
self::$_usedTables[$tableName] = $tableName; }
}
}
Jankowfsky, Rinne – Mayflower/swoodoo
43. yaml - cleanup
if (!empty(self::$_usedTables)) {
foreach (array_reverse(self::$_usedTables) as $tableName) {
self::$_db->execute(quot;TRUNCATE TABLE $tableNamequot;);
}
}
Jankowfsky, Rinne – Mayflower/swoodoo
44. Fixtures the other side...
- manual fixtures are too much work?
- use a test database
- think about automatic creation of yaml files
Jankowfsky, Rinne – Mayflower/swoodoo
45. Mocking Stubs?
„...may simulate the behavior of existing code (such as a
procedure on a remote machine) or be a temporary
substitute for yet-to-be-developed code...“
Why do we need this ?
stub: einfache klasse, die
so tut, als wäre sie wie
das original
mock: das gleiche, aber
mit introspektion und
von aussen
Jankowfsky, Rinne – Mayflower/swoodoo
konfigurierbar
46. Stubs
- Unit testing is about testing a unit of work, not
a complete workflow
- isolates your code from external dependencies
- can be done with PHPunit, but doesn‘t need to
Jankowfsky, Rinne – Mayflower/swoodoo
47. Stubs
/**
* The PHPUnit way
*/
/**
* A simple stub providing a simple result directly instead of using the database
*/
class UserModelStub extends UserModel {
public getUserCount() {
return 10;
}
}
UserModelStub extends PHPUnit_Framework_Testcase {
public function testGetUserCount() {
$stub = $this->getMock(‘UserModel‘);
$stub->expects($this->any())->method(‘getUserCount‘)->will($this->returnValue(10));
}
}
Jankowfsky, Rinne – Mayflower/swoodoo
48. Mock Objects
- Helpful tool to fake complex objects
- Useful to mock service apis, external
software, ...
/**
* The PHPUnit way
*/
class UserModelTest extends PHPUnit_Framework_Testcase {
public function testGetUserCountIsCalled() {
$usermock = $this->getMock(‘UserModel‘);
$usermock->expects($this->once())->method(‘getUserCount‘)->with($this->equalTo(ADMIN));
$admin = new AdminModel($usermock);
$admin->getNumber();
}
}
Jankowfsky, Rinne – Mayflower/swoodoo
49. About Mocking
- a better separation of concerns helps
- writing less mock objects
- writing easier mock objects
- if there is a lot of mock objects, rethink your
architecture -> refactor more!
Jankowfsky, Rinne – Mayflower/swoodoo
50. Golden rules
- know your budget: what are your maintenance
costs? What are the things you can‘t do now?
- there is no silver bullet. Introducing TDD takes
A LOT of time
- TDD wins on the long run, not on the short
- Confident developers are efficient developers
- There is no way around proper coding style and
documentation
- You have to rewrite code, some even twice.
Jankowfsky, Rinne – Mayflower/swoodoo
51. Tools?
- CruiseControl for continous integration
- PHPUnit
- SeleniumRC and SeleniumIDE
- PHP Code Sniffer
- PHP CodeBrowser
Jankowfsky, Rinne – Mayflower/swoodoo