Rewriting 15-Year-Old Code

Rewriting 15-Year-Old Code

Have you ever had to maintain a 15-year-old application? Dead code everywhere, database queries in between HTML tags, and some pages still in PHP 3. This strategy-level presentation will lead you through a progressive rewrite, from very old legacy to the latest shiny version of PHP. Learn how to automate legacy testing, how to seamlessly jump between the old and new parts, and how to overcome other challenges that arise from dealing with legacy.

B3b2139e4f2c0eca4efe2379fcebc1c5?s=128

Anna Filina

March 14, 2017
Tweet

Transcript

  1. @afilina Rewriting
 15-Year-Old Code Seattle PHP - March 14, 2017

  2. @afilina @seaphp

  3. Anna Filina • Project rescue expert • Dev, trainer, speaker

    • 1 company • 2 conferences
  4. You Inherited a
 15-Year-Old Codebase Code Doesn't Age Like Wine

  5. The Web Was 600px Wide

  6. Feeling Old Yet? • Y2K bug. • Internet Explorer 5.

    • ICQ was starting to be cool. • Rounded corners = status symbol.
  7. Code Smells

  8. Mixed Concerns <?php mysql_query('...'); $total = 0; foreach ($products as

    $p) { $total += $p['qty'] * $p['price']; } ?> <p>Total: <?= $total ?></p>
  9. Global Functions & Constants include("functions.php"); MyClass::myMethod(); // namespaced function? myMethod(DEV_MODE);

  10. False Security $clean = htmlentities($_GET['password']); // use filter extension or

    a framework // use prepared statements
  11. Inexplicable Conditions if ($order_id > 20117) { // use this

    sql } else { // use that sql } // weakness = time to refactor
  12. Long Methods public function importCsv($path, $googleApiKey, $databaseDsn, $databaseUser, $databasePassword) {

    // Convert CSV to array of conferences $lines = file($path); $csv = array_map('str_getcsv', $lines); $conferences = []; foreach ($csv as $line) { $conference = new Conference(); $conference->name = $line[0]; $conference->location = $line[1]; $conferences[] = $conference; // Get coordinates for location $location = urlencode($conference->location); $url = 'https://maps.googleapis.com/maps/api/geocode/json?address='.$location.'&key='.$googleApiKey; $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); $response = curl_exec($curl); curl_close($curl); $json = json_decode($response); if (count($json->results) == 0) { continue; } $latitude = $json->results[0]->geometry->location->lat; $longitude = $json->results[0]->geometry->location->lng; $coordinates = $latitude.','.$longitude; $conference->coordinates = $coordinates; // Save conference to database $pdo = new PDO($databaseDsn, $databaseUser, $databasePassword); $statement = $pdo->prepare('REPLACE INTO conference (name, location, coordinates) VALUES (?, ?, ?)'); $statement->execute([ $conference->name, $conference->location, $conference->coordinates, ]);
  13. Average Legacy Code File • 3000-6000 lines of code. •

    Half of it is commented "in case we need it later". • Method length of 800 lines. • Abuse of helper classes. • Sometimes no classes at all.
  14. Codebase • 5,000 classes. • 20,000 methods. • 1,500,000 lines

    of code.
  15. Before You Code

  16. Strategy • Make a strategy based on constraints. • Full

    rewrite vs progressive: ◦ By class. ◦ By feature/module. ◦ By HTTP call. • How to run code side-by-side: ◦ Session sharing. ◦ mod_rewrite.
  17. Data Can Be Lost, Stuff Can Break • Backup: test

    restore. • Nullify sensitive data. • Staging: simulate deployments/upgrades/batch processes. • Automate tests before code changes. • Make a risk assessment: ◦ Don't be too optimistic. ◦ Account for side-effects.
  18. Rewrite Example

  19. PHP 3 to PHP 5.6 • HTML + PHP +

    SQL in same file. • Includes all over the place. • IFs that concatenate SQL. • Previous rewrite attempt: ◦ Failed, made things worse. ◦ Folders of dead code. ◦ Classes with static functions (no instances).
  20. Solution • Rewrite complex forms in Symfony: ◦ mod_rewrite for

    concerned pages. • Rewrite biggest module as OOP: ◦ Design extraction. ◦ Flexible architecture. ◦ Automated tests.
  21. Design Extraction

  22. Avoid Code Bias • Old code → design docs. •

    Validate design docs: ◦ Clarify business rules. • Improve design: ◦ Reduce technical debt. ◦ More flexible. • Design docs → new code.
  23. Fixing Bugs

  24. Code Duplication • Sometimes, the bug is repeated 80+ times.

    • Remove duplications ASAP.
  25. Fix Long Methods • Extract broken part into its own

    method. • Write unit tests for it. • Fix it. • Call it from the mega-method.
  26. Spot Logical Groups Code block Comment Code block Comment Code

    block Comment Convert CSV to array of conferences. Get coordinates for conference location. Save conference to database.
  27. Extract Code block Comment Code block Method Code block Comment

    Call method
  28. Name Code block Method Code block Method Call method Call

    method Code block Method Call method getConferencesFromCsv getLocationCoordinates saveConference
  29. Rewrite Example #2

  30. ASP Classic to PHP 5.6 • 15+ spaghetti and hacks.

    • Language no longer supported. • Huge ERP with lots of code.
  31. Solution • Rewrite page by page to framework. • mod_rewrite

    for concerned pages. • DB session adapter in both apps. • Page in any language = HTTP request. ◦ Guzzle tests FTW!
  32. Guzzle Tests

  33. Stuck?

  34. Try Something New • Bounce ideas. ◦ New people to

    avoid tunnel vision. • Has this been done before? • Can I try another approach?
  35. Takeaways • Make a strategy. • You touch it, you

    refactor it. • Use known tools & methodologies. • Get inspiration from others. • Refactoring gets easier. • Every problem has a solution.
  36. @afilina afilina.com