how to work with legacy code

93
HOW TO WORK WITH LEGACY CODE

Upload: michal-kruczek

Post on 08-Jan-2017

312 views

Category:

Technology


2 download

TRANSCRIPT

HOW TO WORK WITHLEGACY CODE

MICHAŁ SZCZURPHP Developer since 2010

bass guitarist and trumpeter

michalszczur.plCreated by / Michał Szczur @partikus

AGENDAScope explanationDive into legacy messHow to start and not dieStep by step to heaven

THANKS TO MAREK MATULKAWHO INSPIRED ME!

WHAT IS LEGACY CODE?

CODE WRITTEN MANY YEARS AGOBY MYSELF OR OTHER NINJAS

LOW QUALITY CODE

(PRE)HISTORICAL CODE USES NONEXISTINGFRAMEWORKS/LIBRARIES

NO ENVIRONMENT SEPARATION

NO TESTS !!!

WHEN CODE CAN BE CALLED

LEGACY ???

WHEN IT IS

UNMANAGED

WHEN IT IS

TOO BUGGY

WHEN IT IS

TOO HARD TO UNDERSTAND

WHEN IT IS

NONUPGRADABLE

WHEN IT IS

UNDEBUGGABLE

WHEN IT IS

UNREADABLE

WHEN IT IS

TOO COUPLED TO FRAMEWORK

WHEN IT IS

TOO HARD TO ADD NEW FEATURE

WHAT IS NEXT?

FIGHT OR DIE

NO !!!WE'RE NINJA DEVS

WE LOVE CHALLENGES

LET'S DO IT!

TAKE NEW FRAMEWORK AND START FROM THE BEGINNING

:(MAYBE SOME DAY IN THE FUTURE

WHAT IS NEXT?

LET'S REFACTOR

BUT WHY ???

EXTENDED LEGACYCODE

WEB PHP APP

PREASUMPTIONSYOU DON'T KNOW BUSINESS LOGIC

MOSTLY PROCEDURAL CODE

NO SEPARATION, JUST A FEW LARGE FILES

YOU NEED TO CHANGE STH

ANALYZE

COVERIT MEANS WRITE TESTS

WHAT SHOULD WETEST?

WHY SHOULD WE WRITE TESTS?

LOW LEVEL DOCUMENTATIONFEATURES DESCRIBED BY SCENARIOS (E.G.

GHERKIN)CLEAN CODE

EASY TO CHANGE (LESS PAIN)EASY CONTINUOUS DEPLOYMENT

START REFACTORING

# Project structure /404.php /database.php /functions.php /index.php /page.php

PROCEDURAL CODEWRITTEN IN PHP

INLINE PHP FUNCTIONS MIXED WITH HTML,CSS,JS

#/functions.php function show_all() { $db = connect_to_db(); $sql = 'SELECT * FROM receivers'; $result = mysql_query($sql) or die(mysql_error()); while ($row = mysql_fetch_array($result)) { //echo 'ID: ' . $row['id'] . ', mail: ' . $row['mail']; echo ''.$row['mail'].''; echo '<form id="'.$row['id'].'" name="n_ID" method="POST" action=<input type="hidden" name="id" value="'.$row['id'].'" /> <input type="submit" name="delete" value="Delete" /> </form>'; } if (isset($_POST['delete'])) { $n_ID = $_POST['id']; $sql = "DELETE FROM receivers WHERE id = $n_ID"; mysql_query($sql) or die(mysql_error());

WRITE FUNCTIONALTEST

// show_all_receivers.jscasper.test.begin('List all receivers', 5, function suite(test) { casper.start("http://myapp.dev/mailsender.php", function() { test.assertHttpStatus(200); test.assertTitle("Homepage | Mail Sender", "Homepage title is expected" }); casper.thenClick('a#show-all', function() { test.assertHttpStatus(200); test.assertTitle( "Receivers list | Mail Sender", "Page title is correct" ); }); casper.run(function() { test.done(); }); });

PROCEDURAL CODE??

YOU NEED STH MORE

IOCINVERSION OF CONTROL

DEPENDENCYINJECTION

IOC IMPLEMENTATION

DON'T REINVENT THEWHEEL

PACKAGE MANAGERCOMPOSER

RUBYGEMS

MAVEN

WHY SYMFONYCOMPONENTS?

WELL TESTED

DECOUPLED

REUSABLE

WELL DOCUMENTEDAND KNOWN BY COMMUNITY

NEXT STEP IS...

MOVE APP TO /WEB/# new project structure / /web/404.php /web/database.php /web/functions.php /web/index.php /web/page.php

INSTALL COMPOSERphp -r "readfile('https://getcomposer.org/installer');" > composer-setup.phpphp -r "if (hash('SHA384', file_get_contents('composer-setup.php')) === php composer-setup.php php -r "unlink('composer-setup.php');"

INIT COMPOSER CONFIG IN THE ROOT DIRphp composer.phar init

PROJECT STRUCTUREls -l / /composer.json /web/

COMPOSER.JSON

{ "name": "michalszczur/legacy-demo", "authors": [ { "name": "Michal Szczur", "email": "[email protected]" } ], "require": {}, "autoload": { "psr-4": { "": "src/" } }, "description": "Legacy app demo", "type": "project", "license": "proprietary"

ADD DEPENDENCIESphp composer.phar require symfony/dependency-injection php composer.phar require symfony/config php composer.phar require symfony/yaml

<?php # /web/container.php use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;

require __DIR__.'/../vendor/autoload.php';

$container = new ContainerBuilder(); $loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../app/config'$loader->load('services.yml');

$container->compile();

WHICH SERVICES DO WE REALLYNEED?

function show_all() { # Database connection needed $db = connect_to_db(); $sql = 'SELECT * FROM mail_sender'; $result = mysql_query($sql) or die(mysql_error()); //...

CREATE SEVICES.YML DEFINITION

SERVICES.YMLparameters: db_host: localhost db_port: 3306 db_name: legacyapp db_user: myuser db_pass: mypass

services: db: class: \PDO arguments: ['mysql:port=%db_port%;host=%db_host%;dbname=%db_name%' receiver_repository: class: PDOReceiverRepository arguments: ['@db']

USE CONTAINER AND REPOSITORY

#index.php ... require 'container.php'; ... function show_all() { # Database connection needed $receiverRepository = $container->get('receiver_repository'); $result = $receiverRepository->findAll(); ...

#index.php ... $result = $receiverRepository->findAll(); foreach ($result as $row) { echo ''.$row['mail'].''; echo '<form id="'.$row['id'].'" name="n_ID" method="POST" action="mail_sender.php?page=show_all"><input type="hidden" name="id" value="'.$row['id'].'" /> <input type="submit" name="delete" value="Usuń" /> </form>'; } ...

PHP ~ HTMLSTILL MIXED TOGETHER

TWIGTEMPLATE ENGINE FOR PHP

INSTALL TWIG USING COMPOSERphp composer.phar require twig/twig

SERVICES.YMLparameters: ... twig_paths: - app/Resources/views services: ... twig.loader: class: Twig_Loader_Filesystem arguments: ['%twig_paths%'] twig: class: Twig_Environment arguments: ['@twig.loader']

#index.php ... $twig = $container->get('@twig'); $result = $receiverRepository->findAll(); echo $twig->render( 'receiver_list.html.twig', ['receivers' => $result] ); ...

{% for receiver in receivers %} <form id="{{ receiver.id }}" name="n_ID" method="POST" action="mail_sender.php?page=show_all"<input type="hidden" name="id" value="{{ receiver.id }}" /> <input type="submit" name="delete" value="Delete" /> </form> {% endfor %}

BEFOREfunction show_all() { $db = connect_to_db(); $sql = 'SELECT * FROM mail_sender'; $result = mysql_query($sql) or die(mysql_error()); while ($row = mysql_fetch_array($result)) { //echo 'ID: ' . $row['id'] . ', mail: ' . $row['mail']; echo ''.$row['mail'].''; echo '<form id="'.$row['id'].'" name="n_ID" method="POST" action="mail_sender.php?page=show_all"><input type="hidden" name="id" value="'.$row['id'].'" /> <input type="submit" name="delete" value="Usuń" /> </form>'; } ... mysql_free_result($result); mysql_close($db); }

AFTERfunction show_all() { $receiverRepository = $container->get('receiver_repository'); $twig = $container->get('@twig'); $result = $receiverRepository->findAll(); echo $twig->render( 'receiver_list.html.twig', ['receivers' => $result] ... );

LET'S SUM UP

COMMUNICATION

ANALYZE

TESTS

SMALL STEPS

NOT EVERYTHING SIMUNTANOUSLY

PACKAGE MANAGER

DEPENDENCY INJECTION

THIRD PARTY LIBRARIES

DESIGN PATTERNS

SOLID

DECOUPLE

TESTS

QUESTIONS?SLIDES: SLIDESHARE.NET/MICHASZCZUR

FEEDBACK: JOIND.IN/EVENT/CODETECON-20161

TWITTER: @PARTIKUS

HOMEPAGE: MICHALSZCZUR.PL

THANKS!

LINKSMarek Matulka - Modernising the LegacyCasperJSSymfony ComponentsWorking Effectively with Legacy Code by Michael FeathersPage Object Pattern (Martin Fowler)Page Object Pattern (Selenium Docs)