Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Bringing your application into 2014

Bringing your application into 2014

This should probably be titled "A practical guide to surviving legacy code"

Slides with speaker notes can be found at http://cl.ly/470i2k2O2T1U

Michael Heap

October 05, 2014
Tweet

More Decks by Michael Heap

Other Decks in Technology

Transcript

  1. Bringing your application into 2014

    View Slide

  2. Refactoring your application in 2014

    View Slide

  3. Refactoring your application

    View Slide

  4. A practical guide to surviving legacy code

    View Slide

  5. A practical guide to surviving any code

    View Slide

  6. I’m Michael!
    • @mheap on Twitter
    • Fifth time at PHPNW!
    • Developer at…

    View Slide

  7. View Slide

  8. Let me paint you a picture

    View Slide

  9. Job interview

    View Slide

  10. Job offer

    View Slide

  11. 10am Start

    View Slide

  12. Awesome workstation

    View Slide

  13. $dbhost = 'localhost:3036';!
    $dbuser = 'root';!
    $dbpass = 'rootpassword';!
    $conn = mysql_connect($dbhost, $dbuser, $dbpass);!
    if(! $conn )!
    {!
    die('Could not connect: ' . mysql_error());!
    }!
    $sql = 'SELECT id, name FROM employee';!
    !
    mysql_select_db('test_db');!
    $retval = mysql_query( $sql, $conn );!
    if(! $retval )!
    {!
    die('Could not get data: ' . mysql_error());!
    }!
    while($row = mysql_fetch_array($retval, MYSQL_ASSOC))!
    {!
    echo "EMP ID :{$row['id']}
    ".!
    "EMP NAME : {$row['name']}
    ".!
    "--------------------------------
    ";!
    } !
    echo "Fetched data successfully\n";!
    mysql_close($conn);!

    View Slide

  14. The harsh truth

    View Slide

  15. All code is legacy code

    View Slide

  16. Not about intentions

    View Slide

  17. Fixing it

    View Slide

  18. Disclaimer

    View Slide

  19. Why put the effort in?

    View Slide

  20. Reduce complexity

    View Slide

  21. Increase maintainability

    View Slide

  22. Improve performance

    View Slide

  23. Better stability

    View Slide

  24. Prerequisites

    View Slide

  25. Refactor != Rewrite

    View Slide

  26. No new functionality

    View Slide

  27. Modern PHP

    View Slide

  28. Version control

    View Slide

  29. Choose a standard

    View Slide

  30. Leave the code a little better than you found it

    View Slide

  31. PSR

    View Slide

  32. PSR0/4

    View Slide

  33. PSR2

    View Slide

  34. Request lifecycle

    View Slide

  35. Dependency

    View Slide

  36. Action

    View Slide

  37. Response

    View Slide

  38. What about our code?

    View Slide

  39. !
    $dbhost = 'localhost:3036';!
    $dbuser = 'root';!
    $dbpass = 'rootpassword';!
    $conn = mysql_connect($dbhost, $dbuser, $dbpass);!
    if(! $conn )!
    {!
    die('Could not connect: ' . mysql_error());!
    }!
    $sql = 'SELECT id name FROM employee';!
    !
    mysql_select_db('test_db');!
    $retval = mysql_query( $sql, $conn );!
    if(! $retval )!
    {!
    die('Could not get data: ' . mysql_error());!
    }!
    while($row = mysql_fetch_array($retval, MYSQL_ASSOC))!
    {!
    echo "EMP ID :{$row['id']}
    ".!
    "EMP NAME : {$row['name']}
    ".!
    "--------------------------------
    ";!
    } !
    echo "Fetched data successfully\n";!
    mysql_close($conn);!
    ?>!

    View Slide

  40. Write a test

    View Slide

  41. Run the test

    View Slide

  42. !
    $dbhost = 'localhost:3036';!
    $dbuser = 'root';!
    $dbpass = 'rootpassword';!
    $conn = mysql_connect($dbhost, $dbuser, $dbpass);!
    if(! $conn )!
    {!
    die('Could not connect: ' . mysql_error());!
    }!
    $sql = 'SELECT id, name FROM employee';!
    !
    mysql_select_db('test_db');!
    $retval = mysql_query( $sql, $conn );!
    if(! $retval )!
    {!
    die('Could not get data: ' . mysql_error());!
    }!
    while($row = mysql_fetch_array($retval, MYSQL_ASSOC))!
    {!
    echo "EMP ID :{$row['id']}
    ".!
    "EMP NAME : {$row['name']}
    ".!
    "--------------------------------
    ";!
    } !
    echo "Fetched data successfully\n";!
    mysql_close($conn);!
    ?>!

    View Slide

  43. !
    $dbhost = 'localhost:3036';!
    $dbuser = 'root';!
    $dbpass = 'rootpassword';!
    $conn = mysql_connect($dbhost, $dbuser, $dbpass);!
    if(! $conn )!
    {!
    die('Could not connect: ' . mysql_error());!
    }!
    $sql = 'SELECT id, name FROM employee';!
    !
    mysql_select_db('test_db');!
    $retval = mysql_query( $sql, $conn );!
    if(! $retval )!
    {!
    die('Could not get data: ' . mysql_error());!
    }!
    while($row = mysql_fetch_array($retval, MYSQL_ASSOC))!
    {!
    echo "EMP ID :{$row['id']}
    ".!
    "EMP NAME : {$row['name']}
    ".!
    "--------------------------------
    ";!
    } !
    echo "Fetched data successfully\n";!
    mysql_close($conn);!
    ?>!

    View Slide

  44. …!
    !
    mysql_select_db('test_db');!
    $retval = mysql_query( $sql, $conn );!
    if(! $retval )!
    {!
    die('Could not get data: ' . mysql_error());!
    }!
    while($row = mysql_fetch_array($retval, MYSQL_ASSOC))!
    {!
    echo "EMP ID :{$row['id']}
    ".!
    "EMP NAME : {$row['name']}
    ".!
    "--------------------------------
    ";!
    } !
    !
    …!

    View Slide

  45. …!
    !
    mysql_select_db('test_db');!
    $retval = mysql_query( $sql, $conn );!
    if(! $retval )!
    {!
    die('Could not get data: ' . mysql_error());!
    }!
    !
    $data = array();!
    !
    while($row = mysql_fetch_array($retval, MYSQL_ASSOC))!
    {!
    ! $data[] = $row;!
    }!
    !
    foreach ($data as $row)!
    echo "EMP ID :{$row['id']}
    ".!
    "EMP NAME : {$row['name']}
    ".!
    "--------------------------------
    ";!
    } !
    !

    View Slide

  46. …!
    !
    $data = array(!
    ! array("id" => 1, "name" => "Michael ")!
    );!
    !
    foreach ($data as $row)!
    echo "EMP ID :{$row['id']}
    ".!
    "EMP NAME : {$row['name']}
    ".!
    "--------------------------------
    ";!
    } !
    !
    …!

    View Slide

  47. !
    $dbhost = 'localhost:3036';!
    $dbuser = 'root';!
    $dbpass = 'rootpassword';!
    $conn = mysql_connect($dbhost, $dbuser, $dbpass);!
    if(! $conn )!
    {!
    die('Could not connect: ' . mysql_error());!
    }!
    $sql = 'SELECT id, name FROM employee';!
    !
    mysql_select_db('test_db');!
    $retval = mysql_query( $sql, $conn );!
    if(! $retval )!
    {!
    die('Could not get data: ' . mysql_error());!
    }!
    !
    while($row = mysql_fetch_array($retval, MYSQL_ASSOC))!
    {!
    ! $data[] = $row;!
    }!
    !
    include 'employee_view.php';!
    !
    mysql_close($conn);!
    ?>!

    View Slide

  48. !
    $data = array(!
    ! array("id" => 1, "name" => "Michael ")!
    );!
    !
    include 'employee_view.php';!
    !
    ?>!

    View Slide

  49. !
    $dbhost = 'localhost:3036';!
    $dbuser = 'root';!
    $dbpass = 'rootpassword';!
    $conn = mysql_connect($dbhost, $dbuser, $dbpass);!
    if(! $conn )!
    {!
    die('Could not connect: ' . mysql_error());!
    }!
    $sql = 'SELECT id, name FROM employee';!
    !
    mysql_select_db('test_db');!
    $retval = mysql_query( $sql, $conn );!
    if(! $retval )!
    {!
    die('Could not get data: ' . mysql_error());!
    }!
    !
    while($row = mysql_fetch_array($retval, MYSQL_ASSOC))!
    {!
    ! $data[] = $row;!
    }!
    !
    include 'employee_view.php';!
    !
    mysql_close($conn);!
    ?>!

    View Slide

  50. !
    $dbhost = 'localhost:3036';!
    $dbuser = 'root';!
    $dbpass = 'rootpassword';!
    $conn = mysql_connect($dbhost, $dbuser, $dbpass);!
    if(! $conn )!
    {!
    die('Could not connect: ' . mysql_error());!
    }!
    $sql = 'SELECT id, name FROM employee';!
    !
    mysql_select_db('test_db');!
    $retval = mysql_query( $sql, $conn );!
    if(! $retval )!
    {!
    die('Could not get data: ' . mysql_error());!
    }!
    !
    while($row = mysql_fetch_array($retval, MYSQL_ASSOC))!
    {!
    ! $data[] = $row;!
    }!
    !
    include 'employee_view.php';!
    !
    mysql_close($conn);!
    ?>!

    View Slide

  51. !
    include ‘db_setup.php';!
    !
    $sql = 'SELECT id, name FROM employee';!
    !
    $retval = mysql_query( $sql, $conn );!
    if(! $retval )!
    {!
    die('Could not get data: ' . mysql_error());!
    }!
    !
    while($row = mysql_fetch_array($retval, MYSQL_ASSOC))!
    {!
    ! $data[] = $row;!
    }!
    !
    include 'employee_view.php';!
    !
    include ‘db_teardown.php';!
    ?>!

    View Slide

  52. !
    include ‘db_setup.php';!
    !
    $sql = 'SELECT id, name FROM employee_archive’;!
    !
    $retval = mysql_query( $sql, $conn );!
    if(! $retval )!
    {!
    die('Could not get data: ' . mysql_error());!
    }!
    !
    while($row = mysql_fetch_array($retval, MYSQL_ASSOC))!
    {!
    ! $data[] = $row;!
    }!
    !
    include 'employee_view.php';!
    !
    include ‘db_teardown.php';!
    ?>!

    View Slide

  53. !
    class Db {!
    ! public function __construct($user, $password, $database) {!
    ! ! // Connection logic goes here!
    ! }!
    !
    ! public function query($sql) {!
    ! ! $retval = mysql_query( $sql, $this->conn );!
    ! ! if(! $retval )!
    ! ! {!
    ! ! die('Could not get data: ' . mysql_error());!
    ! ! }!
    !
    ! ! while($row = mysql_fetch_array($retval, MYSQL_ASSOC))!
    ! ! {!
    ! ! ! $data[] = $row;!
    ! ! }!
    !
    ! ! return $data;!
    !
    ! }!
    }!

    View Slide

  54. !
    include 'db_setup.php';!
    !
    $data = $db->query('SELECT id, name FROM employee');!
    include 'employee_view.php';!
    !
    include 'db_teardown.php';!
    ?>!

    View Slide

  55. !
    class EmployeeRepo {!
    ! public function getAllEmployees() {!
    ! ! global $db;!
    ! ! return $db->query('SELECT id, name FROM employee');!
    ! }!
    }!

    View Slide

  56. !
    class EmployeeRepo {!
    ! public function __construct() {!
    ! ! global $db;!
    ! ! $this->db = $db;!
    ! }!
    !
    ! public function getAllEmployees() {!
    ! ! return $this->db->query('SELECT id, name FROM employee');!
    ! }!
    }!

    View Slide

  57. !
    include 'db_setup.php';!
    include 'employee_repo.php';!
    !
    $repo = new EmployeeRepo;!
    $data = $repo->getAllEmployees();!
    include 'employee_view.php';!
    !
    include 'db_teardown.php';!
    ?>!

    View Slide

  58. class Db

    View Slide

  59. /src/Db.php

    View Slide

  60. class Foo_Bar_Baz

    View Slide

  61. /src/Foo/Bar/Baz.php

    View Slide

  62. class \mheap\Foo

    View Slide

  63. src mheap/Foo.php

    View Slide

  64. !
    include ‘vendor/autoloader.php’;!
    include ‘db_setup.php’;!
    !
    $repo = new EmployeeRepo;!
    $data = $repo->getAllEmployees();!
    include 'employee_view.php';!
    ?>!

    View Slide

  65. !
    class EmployeeRepo {!
    ! public function __construct() {!
    ! ! global $db;!
    ! ! $this->db = $db;!
    ! }!
    !
    ! public function getAllEmployees() {!
    ! ! return $this->db->query('SELECT id, name FROM employee');!
    ! }!
    }!

    View Slide

  66. !
    class EmployeeRepo {!
    ! public function __construct($db) {!
    ! ! $this->db = $db;!
    ! }!
    !
    ! public function getAllEmployees() {!
    ! ! return $this->db->query(‘SELECT id, name FROM employee');!
    ! }!
    }!

    View Slide

  67. !
    include ‘vendor/autoload.php';!
    include ‘db_setup.php’;!
    !
    $repo = new EmployeeRepo($db);!
    $data = $repo->getAllEmployees();!
    include 'employee_view.php';!
    ?>!

    View Slide

  68. !
    include 'vendor/autoload.php';!
    include 'db_setup.php';!
    !
    $repo = new EmployeeRepo($db);!
    $data = $repo->getAllEmployees();!
    !
    $response = new Response();!
    ob_start();!
    include 'employee_view.php';!
    $reponse->content = ob_get_contents();!
    ob_end_clean();!
    !
    $response->send();!
    ?>!

    View Slide

  69. !
    $this->assertEquals($response->content, "EMP ID: ...etc");!
    !
    ?>!

    View Slide

  70. And more!

    View Slide

  71. Do it alongside your app

    View Slide

  72. View Slide

  73. View Slide

  74. Review

    View Slide

  75. Dependency

    View Slide

  76. Action

    View Slide

  77. Response

    View Slide

  78. Small, reusable classes

    View Slide

  79. Don’t change behaviour

    View Slide

  80. Useful tools

    View Slide

  81. PHPCS

    View Slide

  82. PHPMD

    View Slide

  83. PHPCPD

    View Slide

  84. “Don’t Repeat Yourself” was never about code. It’s about
    knowledge. It’s about cohesion. If two pieces of code represent
    the exact same knowledge, they will always change together.
    Having to change them both is risky: you might forget one of
    them. On the other hand, if two identical pieces of code
    represent different knowledge, they will change independently.
    De-duplicating them introduces risk, because changing the
    knowledge for one object, might accidentally change it for the
    other object.
    - http://verraes.net/2014/08/dry-is-about-knowledge/

    View Slide

  85. Qafoo Refactoring Tools

    View Slide

  86. Scrutinizer CI

    View Slide

  87. The parts that didn’t fit

    View Slide

  88. Testing legacy code

    View Slide

  89. Working with external dependencies

    View Slide

  90. Most of the PHP-FIG work

    View Slide

  91. Logging strategies

    View Slide

  92. Automation

    View Slide

  93. Effective version control

    View Slide

  94. Find me later

    View Slide

  95. Parting thoughts

    View Slide

  96. Perfect is the enemy of better

    View Slide

  97. Optimise for reading code

    View Slide

  98. $ordersWithProducts rather than $data.

    View Slide

  99. markReadAndNotifyUser()
    rather than markRead(true)

    View Slide

  100. Constants are good

    View Slide

  101. Code doesn't lie

    View Slide

  102. Any questions?

    View Slide

  103. Thank you
    http://joind.in/11802

    View Slide