Getting yourself out of trouble with PHP anti-patterns

Getting yourself out of trouble with PHP anti-patterns

Do you have code that you’re afraid to touch because nobody really knows what it does any more? Do you have systems which would be easier to re-write than to maintain? Are developers avoiding working on various tickets because it’ll involve working with a particularly painful part of the code?

Maybe a few developers have tried refactoring over the years but given up (or made things worse!), and certain ‘bad smells’ in the code are stunting progress.

While ‘Design Patterns’ tell us how to design code upfront to solve a common problem, ‘Anti-patterns’ tell us how to rescue a struggling PHP application before it leads to a company’s downfall.

Together we’ll explore some of the common pitfalls, find out how to recognise issues before they become serious, and we’ll see how to move a failing project back into the limelight.

By the end of this session everybody will be eager to tackle those parts of your codebase that the whole team has been steering clear of for months!

Da2d2829b89cde136392973a35b68959?s=128

nealio82

June 10, 2019
Tweet

Transcript

  1. GETTING OUT OF TROUBLE WITH ANTI-PATTERNS

  2. None
  3. None
  4. None
  5. WE ARE HIRING!

  6. GETTING OUT OF TROUBLE WITH ANTI-PATTERNS

  7. None
  8. Design patterns are common ways of solving familiar problems in

    software engineering
  9. https://laracasts.com/series/design-patterns-in-php

  10. None
  11. Anti-patterns are common ways of solving familiar problems in software

    engineering…
  12. …which have an unintended negative result

  13. We Keep Making The Same Mistakes

  14. Spotting anti-patterns helps you identify where things have gone wrong,

    and puts you back on the right path
  15. None
  16. None
  17. None
  18. None
  19. SOFTWARE DEVELOPMENT ANTI-PATTERNS

  20. SOFTWARE ARCHITECTURE ANTI-PATTERNS

  21. PROJECT MANAGEMENT ANTI-PATTERNS

  22. I thought you’d already fixed that bug?

  23. CUT & PASTE PROGRAMMING

  24. The same bug recurs throughout the system despite many local

    fixes Symptoms
  25. Lines of code increase more than productivity Symptoms

  26. Excessive software maintenance costs Symptoms

  27. Organisational emphasis is on short-term payoff rather than long-term investment

    Typical Causes
  28. Development speed is considered a top marker of productivity Typical

    Causes
  29. Lack of abstraction / poor understanding of inheritance, composition, or

    other development strategies Typical Causes
  30. People who, unfamiliar with a new technology, copy & paste

    examples from stack overflow and just modify to their needs Typical Causes
  31. Code Mining

  32. $ composer require --dev sebastian/phpcpd

  33. $ php vendor/bin/phpcpd . --exclude-regexps=vendor

  34. Found 35 clones with 1873 duplicated lines in 44 files:

    .../campaign_groups/lib/ManageCampaignGroups.php:258-462 (204 lines) .../campaign/lib/ManageCampaigns.php:335-539
  35. Found 35 clones with 1873 duplicated lines in 44 files:

    .../campaign_groups/lib/ManageCampaignGroups.php:258-462 (204 lines) .../campaign/lib/ManageCampaigns.php:335-539
  36. Found 35 clones with 1873 duplicated lines in 44 files:

    .../campaign_groups/lib/ManageCampaignGroups.php:258-462 (204 lines) .../campaign/lib/ManageCampaigns.php:335-539
  37. Found 35 clones with 1873 duplicated lines in 44 files:

    .../campaign_groups/lib/ManageCampaignGroups.php:258-462 (204 lines) .../campaign/lib/ManageCampaigns.php:335-539
  38. Found 35 clones with 1873 duplicated lines in 44 files:

    .../campaign_groups/lib/ManageCampaignGroups.php:258-462 (204 lines) .../campaign/lib/ManageCampaigns.php:335-539
  39. Found 35 clones with 1873 duplicated lines in 44 files:

    .../campaign_groups/lib/ManageCampaignGroups.php:258-462 (204 lines) .../campaign/lib/ManageCampaigns.php:335-539
  40. Refactor

  41. Pluck Reusable Components

  42. /** * filters out campaigns. * * @param ArrayManipulation $campaigns

    * @param array $filtersSpec * * @throws Exception */ public static function filterCampaignsList(ArrayManipulation $campai $filterRules = array(); foreach ($filtersSpec as $filter) { if ($filter['name'] == '-custom-ysdb') { $operator = $filter['comparator']; $expectedValue = $filter['value']; $campaignFbIds = self::pluckFbCampaignIds($campaigns->getValue if (!$campaignFbIds) { continue; } // get yesterday spend. used in `yday spnd/dly bdgt` filter $spec = DWEndpointSpec::create() ->setIds($campaignFbIds) ->addFields(array('spend')) ->setDateFrom((new DateTime())->modify('-1 DAY')) ->setDateTo(new DateTime()) campaign ManageCampaignGroups.php campaign_groups
  43. None
  44. /** * @param $filtersArray * * @return array */ public

    static function parseFiltersArray($filtersArray) { $pre = array(); $post = array(); $preFiltersNames = array( 'statusIds' => 'statusIds', 'name' => 'name', 'budget' => 'budget', 'objective' => 'objective', 'spend_cap' => 'spend_cap' ); $in_array_match = function (&$value, $array) { foreach ($array as $pattern => $newValue) { if (preg_match('/^' . $pattern . '$/', $value)) { $value = $newValue; return true; } } return false; }; campaign ManageCampaignGroups.php campaign_groups
  45. ManageCampaigns.php /** * @param $filtersArray * * @return array */

    public static function parseFiltersArray($filtersArray) { $pre = array(); $post = array(); $preFiltersNames = array( 'dimensionTag_\d+' => 'dimensionTag', 'statusIds' => 'statusIds', 'bidTypes' => 'bidTypes', 'campaignGroupIds' => 'campaignGroupIds', 'name' => 'name', 'budget' => 'budget', 'optimisationGoal' => 'optimisationGoal', 'billingEvent' => 'billingEvent', '-custom-dayparting' => '-custom-dayparting', '-custom-dimension' => '-custom-dimension', ); $in_array_match = function (&$value, $array) { foreach ($array as $pattern => $newValue) { if (preg_match('/^' . $pattern . '$/', $value)) { $value = $newValue; return true; campaign campaign_groups
  46. /** * @param $filtersArray * * @return array */ public

    static function parseFiltersArray($filtersArray) { $pre = array(); $post = array(); $preFiltersNames = array( 'dimensionTag_\d+' => 'dimensionTag', 'statusIds' => 'statusIds', 'bidTypes' => 'bidTypes', 'bidModel' => 'bidModel', 'campaignIds' => 'campaignIds', 'campaignGroupIds' => 'campaignGroupIds', 'uploadId' => 'uploadId', 'creative_title' => 'creative_title', 'creative_body' => 'creative_body', 'name' => 'name', 'maxBid' => 'maxBid', '-custom-dimension' => '-custom-dimension', ); $in_array_match = function (&$value, $array) { foreach ($array as $pattern => $newValue) { if (preg_match('/^' . $pattern . '$/', $value)) { $value = $newValue; campaign campaign_groups campaign_ads ManageAds.php
  47. abstract class CampaignManagement { abstract protected static function getFilters(): array;

    public static function parseFiltersArray(): array { $pre = array(); $post = array(); $preFiltersNames = static::getFilters(); $in_array_match = function (&$value, $array) { foreach ($array as $pattern => $newValue) { if (preg_match('/^' . $pattern . '$/', $value)) { $value = $newValue; return true; } } return false; }; foreach ($filtersArray as $filter) { if ($in_array_match($filter['name'], $preFiltersNames)) { campaign campaign_groups campaign_ads CampaignManagement.php Management
  48. abstract class CampaignManagement { abstract protected static function getFilters(): array;

    public static function parseFiltersArray(): array { $pre = array(); $post = array(); $preFiltersNames = static::getFilters(); $in_array_match = function (&$value, $array) { foreach ($array as $pattern => $newValue) { if (preg_match('/^' . $pattern . '$/', $value)) { $value = $newValue; return true; } } return false; }; foreach ($filtersArray as $filter) { if ($in_array_match($filter['name'], $preFiltersNames)) { campaign campaign_groups campaign_ads CampaignManagement.php Management
  49. abstract class CampaignManagement { abstract protected static function getFilters(): array;

    public static function parseFiltersArray(): array { $pre = array(); $post = array(); $preFiltersNames = static::getFilters(); $in_array_match = function (&$value, $array) { foreach ($array as $pattern => $newValue) { if (preg_match('/^' . $pattern . '$/', $value)) { $value = $newValue; return true; } } return false; }; foreach ($filtersArray as $filter) { if ($in_array_match($filter['name'], $preFiltersNames)) { campaign campaign_groups campaign_ads CampaignManagement.php Management
  50. class ManageAdsPage extends CampaignManagement { protected static function getFilters(): array

    { return array( 'dimensionTag_\d+' => 'dimensionTag', 'statusIds' => 'statusIds', 'bidTypes' => 'bidTypes', 'bidModel' => 'bidModel', 'campaignIds' => 'campaignIds', 'campaignGroupIds' => 'campaignGroupIds', 'uploadId' => 'uploadId', 'creative_title' => 'creative_title', 'creative_body' => 'creative_body', 'name' => 'name', 'maxBid' => 'maxBid', '-custom-dimension' => '-custom-dimension', ); } } campaign campaign_groups campaign_ads ManageAds.php Management
  51. class ManageAdsPage extends CampaignManagement { protected static function getFilters(): array

    { return array( 'dimensionTag_\d+' => 'dimensionTag', 'statusIds' => 'statusIds', 'bidTypes' => 'bidTypes', 'bidModel' => 'bidModel', 'campaignIds' => 'campaignIds', 'campaignGroupIds' => 'campaignGroupIds', 'uploadId' => 'uploadId', 'creative_title' => 'creative_title', 'creative_body' => 'creative_body', 'name' => 'name', 'maxBid' => 'maxBid', '-custom-dimension' => '-custom-dimension', ); } } campaign campaign_groups campaign_ads ManageAds.php Management
  52. class ManageAdsPage extends CampaignManagement { protected static function getFilters(): array

    { return array( 'dimensionTag_\d+' => 'dimensionTag', 'statusIds' => 'statusIds', 'bidTypes' => 'bidTypes', 'bidModel' => 'bidModel', 'campaignIds' => 'campaignIds', 'campaignGroupIds' => 'campaignGroupIds', 'uploadId' => 'uploadId', 'creative_title' => 'creative_title', 'creative_body' => 'creative_body', 'name' => 'name', 'maxBid' => 'maxBid', '-custom-dimension' => '-custom-dimension', ); } } campaign campaign_groups campaign_ads ManageAds.php Management
  53. $ php vendor/bin/phpcpd . --exclude-regexps=vendor

  54. Found 31 clones with 1426 duplicated lines in 42 files:

  55. None
  56. Code Review

  57. We’re not really sure what that code does

  58. But it works so we just leave it alone We’re

    not really sure what that code does
  59. LAVA FLOW

  60. Undocumented, complex, important-looking functions / classes / segments unrelated to

    the system architecture Symptoms
  61. Unjustifiable variables & code fragments distributed throughout system Symptoms

  62. Lots of ‘TODO / to complete’ comments Symptoms

  63. Large blocks of commented-out code with no explanation Symptoms

  64. R&D code finds its way into production Typical Causes

  65. Lone-wolf working on the code Typical Causes

  66. Shifting goals of the software project Typical Causes

  67. Lack of architectural forethought / design Typical Causes

  68. Dig In To The Problem

  69. actions.class.php actions <?php class inlinebuilderActions extends sfActions { public function

    executeProcess(sfWebRequest $request) { $uploadId = $request->getParameter('uploadId'); $clientId = $this->getUser()->getAttribute('clientSessionId'); $inlineBuilderUpload = Doctrine::getTable('InlineBuilderUpload')- >findOneByIdAndClientId($uploadId, $clientId); if ($inlineBuilderUpload instanceof InlineBuilderUpload) { # We only allow to process builds from the last step of the builde it (saved) if (in_array($inlineBuilderUpload->getStatus(), array(InlineBuilderUpload::STATUS_EDITING,InlineBuilderUpload::STATUS_SA $inlineBuilderUpload->setStatus(InlineBuilderUpload::STATUS_IN_Q $inlineBuilderUpload->save(); } } /* $bulkUpload = $inlineBuilderUpload->loadBulkUpload(); $conn = sfContext::getInstance()->getDatabaseManager()->getDatabase( >getDoctrineConnection();
  70. actions.class.php actions } } /* $bulkUpload = $inlineBuilderUpload->loadBulkUpload(); $conn =

    sfContext::getInstance()->getDatabaseManager()->getDatabase('d >getDoctrineConnection(); try { $conn->beginTransaction(); $bulkUpload->saveIntoDB(); // Commit the transaction when done $conn->commit(); $inlineBuilderUpload->setStatus(InlineBuilderUpload::STATUS_PROCESSE $inlineBuilderUpload->save(); } catch (Exception $e) { // Rollback if transaction fail $conn->rollback(); $bulkError = new BulkError(); $bulkError->setClass('BulkDB'); $bulkError->setFunction('BulkDB.saveIntoDB'); $bulkError->setType(BulkError::TYPE_ERROR); $bulkError->setMessage($e->getMessage());
  71. actions.class.php actions $bulkError->setFunction('BulkDB.saveIntoDB'); $bulkError->setType(BulkError::TYPE_ERROR); $bulkError->setMessage($e->getMessage()); $bulkError->setObject('inlineBuilderUpload'); $bulkError->setAccountId($inlineBuilderUpload->getAccountId()); $bulkError->save(); $inlineBuilderUpload->setStatus(InlineBuilderUpload::STATUS_ERROR); $inlineBuilderUpload->save();

    } */ $this->redirect('/inlinebuilder/uploads'); } public function executeDownloadOLD(sfWebRequest $request) { // ... } public function executeDownload(sfWebRequest $request) { // ... } public function executeOldDownload(sfWebRequest $request) { // ... } }
  72. Refactor

  73. Detective Work

  74. $ git blame actions.class.php

  75. a315cb4a (Jordi 2014-07-01) /* 9dedbd66 (Jordi 2014-06-30) $conn = sfContext::getInstance()->getDatabaseManager()->

    9dedbd66 (Jordi 2014-06-30) 144398e4 (Jordi 2014-06-30) try { 9dedbd66 (Jordi 2014-06-30) $conn->beginTransaction(); 9dedbd66 (Jordi 2014-06-30) 144398e4 (Jordi 2014-06-30) $bulkUpload->saveIntoDB(); 144398e4 (Jordi 2014-06-30) 9dedbd66 (Jordi 2014-06-30) $conn->commit(); 9dedbd66 (Jordi 2014-06-30)
  76. a315cb4a (Jordi 2014-07-01) /* 9dedbd66 (Jordi 2014-06-30) $conn = sfContext::getInstance()->getDatabaseManager()->

    9dedbd66 (Jordi 2014-06-30) 144398e4 (Jordi 2014-06-30) try { 9dedbd66 (Jordi 2014-06-30) $conn->beginTransaction(); 9dedbd66 (Jordi 2014-06-30) 144398e4 (Jordi 2014-06-30) $bulkUpload->saveIntoDB(); 144398e4 (Jordi 2014-06-30) 9dedbd66 (Jordi 2014-06-30) $conn->commit(); 9dedbd66 (Jordi 2014-06-30)
  77. a315cb4a (Jordi 2014-07-01) /* 9dedbd66 (Jordi 2014-06-30) $conn = sfContext::getInstance()->getDatabaseManager()->

    9dedbd66 (Jordi 2014-06-30) 144398e4 (Jordi 2014-06-30) try { 9dedbd66 (Jordi 2014-06-30) $conn->beginTransaction(); 9dedbd66 (Jordi 2014-06-30) 144398e4 (Jordi 2014-06-30) $bulkUpload->saveIntoDB(); 144398e4 (Jordi 2014-06-30) 9dedbd66 (Jordi 2014-06-30) $conn->commit(); 9dedbd66 (Jordi 2014-06-30)
  78. a315cb4a (Jordi 2014-07-01) /* 9dedbd66 (Jordi 2014-06-30) $conn = sfContext::getInstance()->getDatabaseManager()->

    9dedbd66 (Jordi 2014-06-30) 144398e4 (Jordi 2014-06-30) try { 9dedbd66 (Jordi 2014-06-30) $conn->beginTransaction(); 9dedbd66 (Jordi 2014-06-30) 144398e4 (Jordi 2014-06-30) $bulkUpload->saveIntoDB(); 144398e4 (Jordi 2014-06-30) 9dedbd66 (Jordi 2014-06-30) $conn->commit(); 9dedbd66 (Jordi 2014-06-30)
  79. a315cb4a (Jordi 2014-07-01) /* 9dedbd66 (Jordi 2014-06-30) $conn = sfContext::getInstance()->getDatabaseManager()->

    9dedbd66 (Jordi 2014-06-30) 144398e4 (Jordi 2014-06-30) try { 9dedbd66 (Jordi 2014-06-30) $conn->beginTransaction(); 9dedbd66 (Jordi 2014-06-30) 144398e4 (Jordi 2014-06-30) $bulkUpload->saveIntoDB(); 144398e4 (Jordi 2014-06-30) 9dedbd66 (Jordi 2014-06-30) $conn->commit(); 9dedbd66 (Jordi 2014-06-30)
  80. a315cb4a (Jordi 2014-07-01) /* 9dedbd66 (Jordi 2014-06-30) $conn = sfContext::getInstance()->getDatabaseManager()->

    9dedbd66 (Jordi 2014-06-30) 144398e4 (Jordi 2014-06-30) try { 9dedbd66 (Jordi 2014-06-30) $conn->beginTransaction(); 9dedbd66 (Jordi 2014-06-30) 144398e4 (Jordi 2014-06-30) $bulkUpload->saveIntoDB(); 144398e4 (Jordi 2014-06-30) 9dedbd66 (Jordi 2014-06-30) $conn->commit(); 9dedbd66 (Jordi 2014-06-30)
  81. $ git show a315cb4a

  82. commit a315cb4a741f7dec51376477c8c66419991aa22d Author: Jordi Date: Tue Jul 1 13:08:15 2014

    +0100 [Inline Builder] - DB Process -- Cron added. + task/BuilderTask.class.php
  83. commit a315cb4a741f7dec51376477c8c66419991aa22d Author: Jordi Date: Tue Jul 1 13:08:15 2014

    +0100 [Inline Builder] - DB Process -- Cron added. + task/BuilderTask.class.php
  84. BuilderTask.class.php task <?php class BuilderTask extends sfBaseTask { private function

    _createInlineBuilders($inlineBuilders) { TerminalHelper::log("Found " . count($inlineBuilders) . " Inline Buil created", "yellow"); if(count($inlineBuilders) > 0) { /** @var InlineBuilderUpload $inlineBuilder */ foreach ($inlineBuilders as $inlineBuilder) { TerminalHelper::log("Loading Bulk Upload for Inline Job " . $inli "yellow"); $bulkUpload = $inlineBuilder->loadBulkUpload(); $conn = sfContext::getInstance()->getDatabaseManager()->getDataba >getDoctrineConnection(); try { $conn->beginTransaction(); TerminalHelper::log("Saving Job to DB", "yellow"); $bulkUpload->saveIntoDB(); actions
  85. actions.class.php actions it (saved) if (in_array($inlineBuilderUpload->getStatus(), array(InlineBuilderUpload::STATUS_EDITING,InlineBuilderUpload::STATUS_SAVE $inlineBuilderUpload->setStatus(InlineBuilderUpload::STATUS_IN_QUE $inlineBuilderUpload->save(); }

    } /* * DELETE ALL THE COMMENTED-OUT CODE! */ $this->redirect('/inlinebuilder/uploads'); } public function executeDownloadOLD(sfWebRequest $request) { // ... } public function executeDownload(sfWebRequest $request) { // ... } public function executeOldDownload(sfWebRequest $request) { // ... } } task
  86. actions.class.php actions it (saved) if (in_array($inlineBuilderUpload->getStatus(), array(InlineBuilderUpload::STATUS_EDITING,InlineBuilderUpload::STATUS_SAVE $inlineBuilderUpload->setStatus(InlineBuilderUpload::STATUS_IN_QUE $inlineBuilderUpload->save(); }

    } /* * DELETE ALL THE COMMENTED-OUT CODE! */ $this->redirect('/inlinebuilder/uploads'); } public function executeDownloadOLD(sfWebRequest $request) { // ... } public function executeDownload(sfWebRequest $request) { // ... } public function executeOldDownload(sfWebRequest $request) { // ... } } task
  87. actions.class.php actions it (saved) if (in_array($inlineBuilderUpload->getStatus(), array(InlineBuilderUpload::STATUS_EDITING,InlineBuilderUpload::STATUS_SAVE $inlineBuilderUpload->setStatus(InlineBuilderUpload::STATUS_IN_QUE $inlineBuilderUpload->save(); }

    } /* * DELETE ALL THE COMMENTED-OUT CODE! */ $this->redirect('/inlinebuilder/uploads'); } public function executeDownloadOLD(sfWebRequest $request) { // ... } public function executeDownload(sfWebRequest $request) { // ... } public function executeOldDownload(sfWebRequest $request) { // ... } } task
  88. $ git log -- actions.class.php

  89. commit d4e72ab65850ef49fd829e367fe6970025f180bb Author: Daniel Date: Thu Oct 15 15:34:31 2015

    +0300 Excel download button last page of inline builder. PR-4971 commit 937a9d05b5a9120e9345d8784814ff5dc7a1f51a Author: Radu Date: Thu Oct 15 10:14:09 2015 +0100 PR-4972 #resolve Duplicate Excel download from the upload's page
  90. commit d4e72ab65850ef49fd829e367fe6970025f180bb Author: Daniel Date: Thu Oct 15 15:34:31 2015

    +0300 Excel download button last page of inline builder. PR-4971 commit 937a9d05b5a9120e9345d8784814ff5dc7a1f51a Author: Radu Date: Thu Oct 15 10:14:09 2015 +0100 PR-4972 #resolve Duplicate Excel download from the upload's page
  91. commit d4e72ab65850ef49fd829e367fe6970025f180bb Author: Daniel Date: Thu Oct 15 15:34:31 2015

    +0300 Excel download button last page of inline builder. PR-4971 commit 937a9d05b5a9120e9345d8784814ff5dc7a1f51a Author: Radu Date: Thu Oct 15 10:14:09 2015 +0100 PR-4972 #resolve Duplicate Excel download from the upload's page
  92. commit d4e72ab65850ef49fd829e367fe6970025f180bb Author: Daniel Date: Thu Oct 15 15:34:31 2015

    +0300 Excel download button last page of inline builder. PR-4971 commit 937a9d05b5a9120e9345d8784814ff5dc7a1f51a Author: Radu Date: Thu Oct 15 10:14:09 2015 +0100 PR-4972 #resolve Duplicate Excel download from the upload's page
  93. $ git show 937a9d05b

  94. + <a href="/inlinebuilder/downloadOLD?uploadId=<?php echo $uploadId ?>">Excel</a> + <?php if (sfContext::getInstance()->getUser()->hasPermission('new_excel'))

    {?> + <a href="/inlinebuilder/download?uploadId=<?php echo $uploadId ?>”>New Excel</a> + <?php} ?>
  95. + <a href="/inlinebuilder/downloadOLD?uploadId=<?php echo $uploadId ?>">Excel</a> + <?php if (sfContext::getInstance()->getUser()->hasPermission('new_excel'))

    {?> + <a href="/inlinebuilder/download?uploadId=<?php echo $uploadId ?>”>New Excel</a> + <?php} ?>
  96. + <a href="/inlinebuilder/downloadOLD?uploadId=<?php echo $uploadId ?>">Excel</a> + <?php if (sfContext::getInstance()->getUser()->hasPermission('new_excel'))

    {?> + <a href="/inlinebuilder/download?uploadId=<?php echo $uploadId ?>”>New Excel</a> + <?php} ?>
  97. + <a href="/inlinebuilder/downloadOLD?uploadId=<?php echo $uploadId ?>">Excel</a> + <?php if (sfContext::getInstance()->getUser()->hasPermission('new_excel'))

    {?> + <a href="/inlinebuilder/download?uploadId=<?php echo $uploadId ?>”>New Excel</a> + <?php} ?>
  98. actions.class.php actions task public function executeDownloadOLD(sfWebRequest $request) { // ...

    } public function executeDownload(sfWebRequest $request) { // ... } public function executeOldDownload(sfWebRequest $request) { // ... } }
  99. actions.class.php actions public function executeDownloadOLD(sfWebRequest $request) { // ... }

    public function executeDownload(sfWebRequest $request) { // ... } public function executeOldDownload(sfWebRequest $request) { // ... } } task
  100. actions.class.php actions task public function executeDownload(sfWebRequest $request) { // ...

    } public function executeOldDownload(sfWebRequest $request) { // ... } }
  101. actions.class.php actions task public function executeVersion2Download(sfWebRequest $request) { // ...

    } public function executeVersion1Download(sfWebRequest $request) { // ... } }
  102. None
  103. Invest In Educating Your Developers

  104. Redefine The Architecture Whenever You See A Lava Flow Forming

  105. That code is a mess!

  106. That code is a mess! Making a change in one

    place breaks something in another
  107. SPAGHETTI CODE

  108. Code cannot be re-used Symptoms

  109. Objects are frequently named as processes Symptoms

  110. Minimal relationships between objects Symptoms

  111. Many object methods take no parameters, and utilise class or

    global variables Symptoms
  112. Difficult to retain staff with OOP background Symptoms

  113. Side-effects everywhere Symptoms

  114. Poor application performance Symptoms

  115. Programmers are new to OOP Typical Causes

  116. No mentoring / inefficient code reviews Typical Causes

  117. No thought of architectural design prior to implementation Typical Causes

  118. Developers working in isolation Typical Causes

  119. 10af6d6 adding privacy policy page 93ddb94 i bcb7319 ii 8747627

    Finishing up Website Usage Page 2414eb8 iwq 2a5910d ii eadc6e8 ii 0d23c1d ii e6a97b1 wq 5780487 i 64eea96 ii 6914478 qwe 1f06a5d iq 07d15b8 wr 1d6df82 echo fc014f1 Adding new background c7ed903 Here we go 9040b40 Testing 4523f29 testing 66e7e60 iok 965d026 Making modifications a5f58f8 Adding Google Manager codes 08f91d9 wq ff555c1 removing header 23d07b1 Removing content 61e480d ok trying this out
  120. 10af6d6 adding privacy policy page 93ddb94 i bcb7319 ii 8747627

    Finishing up Website Usage Page 2414eb8 iwq 2a5910d ii eadc6e8 ii 0d23c1d ii e6a97b1 wq 5780487 i 64eea96 ii 6914478 qwe 1f06a5d iq 07d15b8 wr 1d6df82 echo fc014f1 Adding new background c7ed903 Here we go 9040b40 Testing 4523f29 testing 66e7e60 iok 965d026 Making modifications a5f58f8 Adding Google Manager codes 08f91d9 wq ff555c1 removing header 23d07b1 Removing content 61e480d ok trying this out
  121. campaigns.php functions function deleteCampaignGuestsViaCampaignId($campaign_id) { $dbh = getNewPDOInstance(‘MY_DB_NAME’); $sql =

    'DELETE FROM campaign_guest WHERE campaign_id = ‘ . $campaign_id; $stmt = $dbh->prepare($sql); if ($stmt->execute()) { return true; } } function deletePhotosFromCampaignFolderViaCampaignId($campaign_id) { global $root_prefix; global $rootd; $path = $root_prefix . "/uploads/campaigns/" . $campaign_id; $real_file_path = $rootd . "/uploads/campaigns/" . $campaign_id; $photos = scandir($_SERVER['DOCUMENT_ROOT'] . $path); // print_r($photos); foreach ($photos as $key => $photo) { // this is to skip . and .. in the folder. if ("." === substr($photo, 0, 1)) {
  122. function deleteCampaignGuestsViaCampaignId($campaign_id) { $dbh = getNewPDOInstance(‘MY_DB_NAME’); $sql = 'DELETE FROM

    campaign_guest WHERE campaign_id = ‘ . $campaign_id; $stmt = $dbh->prepare($sql); if ($stmt->execute()) { return true; } } function deletePhotosFromCampaignFolderViaCampaignId($campaign_id) { global $root_prefix; global $rootd; $path = $root_prefix . "/uploads/campaigns/" . $campaign_id; $real_file_path = $rootd . "/uploads/campaigns/" . $campaign_id; $photos = scandir($_SERVER['DOCUMENT_ROOT'] . $path); // print_r($photos); foreach ($photos as $key => $photo) { // this is to skip . and .. in the folder. if ("." === substr($photo, 0, 1)) { campaigns.php functions
  123. function deleteCampaignGuestsViaCampaignId($campaign_id) { $dbh = getNewPDOInstance(‘MY_DB_NAME’); $sql = 'DELETE FROM

    campaign_guest WHERE campaign_id = ‘ . $campaign_id; $stmt = $dbh->prepare($sql); if ($stmt->execute()) { return true; } } function deletePhotosFromCampaignFolderViaCampaignId($campaign_id) { global $root_prefix; global $rootd; $path = $root_prefix . "/uploads/campaigns/" . $campaign_id; $real_file_path = $rootd . "/uploads/campaigns/" . $campaign_id; $photos = scandir($_SERVER['DOCUMENT_ROOT'] . $path); // print_r($photos); foreach ($photos as $key => $photo) { // this is to skip . and .. in the folder. if ("." === substr($photo, 0, 1)) { campaigns.php functions
  124. function createUpdateLoginClient($client){ $password_hash = password_hash($client['password'], PASSWORD_DEFAULT); $id = $client['id']; $dbh

    = getNewPDOInstance(‘login_clients'); if($id){ // ... } if($client['password'] && $id){ // ... } if(!$id){ $client_id = createClient($client['user_name'], $client['email'], $ // ... } if($result){ return true; } else{ return true; } } function createClient($email){ $dbh = getNewPDOInstance('MY_DB_NAME'); // ... } campaigns.php functions clients.php
  125. function createUpdateLoginClient($client){ $password_hash = password_hash($client['password'], PASSWORD_DEFAULT); $id = $client['id']; $dbh

    = getNewPDOInstance(‘login_clients'); if($id){ // ... } if($client['password'] && $id){ // ... } if(!$id){ $client_id = createClient($client['user_name'], $client['email'], $ // ... } if($result){ return true; } else{ return true; } } function createClient($email){ $dbh = getNewPDOInstance('MY_DB_NAME'); // ... } campaigns.php functions clients.php
  126. function createUpdateLoginClient($client){ $password_hash = password_hash($client['password'], PASSWORD_DEFAULT); $id = $client['id']; $dbh

    = getNewPDOInstance(‘login_clients'); if($id){ // ... } if($client['password'] && $id){ // ... } if(!$id){ $client_id = createClient($client['user_name'], $client['email'], $ // ... } if($result){ return true; } else{ return true; } } function createClient($email){ $dbh = getNewPDOInstance('MY_DB_NAME'); // ... } campaigns.php functions clients.php
  127. function createUpdateLoginClient($client){ $password_hash = password_hash($client['password'], PASSWORD_DEFAULT); $id = $client['id']; $dbh

    = getNewPDOInstance(‘login_clients'); if($id){ // ... } if($client['password'] && $id){ // ... } if(!$id){ $client_id = createClient($client['user_name'], $client['email'], $ // ... } if($result){ return true; } else{ return true; } } function createClient($email){ $dbh = getNewPDOInstance('MY_DB_NAME'); // ... } campaigns.php functions clients.php
  128. index.php functions <?php error_reporting(0); //ini_set('display_errors', E_ALL); $IN_PRODUCTION = true; $root_prefix

    = true||$IN_PRODUCTION?”/campaign”:""; $rootd = $_SERVER['DOCUMENT_ROOT'].$root_prefix."/"; require $rootd.'/lib/init/init.php'; $do = _get("do"); $get = _get("get"); $page = _get("page"); $app = _get("app"); $folder = _get("folder"); if (isset($app)) { header('Content-Type: application/json'); $path = $rootd.'lib/app/'.$app.'.php'; if (!file_exists($path)) exit(header("location: /404")); include $path; }elseif (isset($do)) { $path = $rootd.'lib/do/'.$do.'.php'; if (!file_exists($path)) exit(header("location: /404")); include $path;
  129. index.php functions if($folder){ $path = $rootd.'lib/get/'.$folder.'/'.$get.'.php'; }else{ $path = $rootd.'lib/get/'.$get.'.php';

    } if (!file_exists($path)) exit(header("location: /404")); include $path; } elseif (isset($page)) { $path = $rootd.'lib/pages_data/'.$page.'.php'; if (!file_exists($path)){ error_log('lol ' . $path); exit(header("location: /404")); } include $rootd.'lib/init/html.php'; include $path; } elseif (isset($_SESSION['my_id'])) { $path = $rootd.'lib/pages_data/home.php'; if (!file_exists($path)) exit(header("location: /404")); include $rootd.'lib/init/html.php'; include $path; } else { $path = $rootd.'lib/pages_data/sign-up.php'; if (!file_exists($path)) exit(header("location: /404")); include $rootd.'lib/init/html.php'; include $path; }
  130. index.php functions if($folder){ $path = $rootd.'lib/get/'.$folder.'/'.$get.'.php'; }else{ $path = $rootd.'lib/get/'.$get.'.php';

    } if (!file_exists($path)) exit(header("location: /404")); include $path; } elseif (isset($page)) { $path = $rootd.'lib/pages_data/'.$page.'.php'; if (!file_exists($path)){ error_log('lol ' . $path); exit(header("location: /404")); } include $rootd.'lib/init/html.php'; include $path; } elseif (isset($_SESSION['my_id'])) { $path = $rootd.'lib/pages_data/home.php'; if (!file_exists($path)) exit(header("location: /404")); include $rootd.'lib/init/html.php'; include $path; } else { $path = $rootd.'lib/pages_data/sign-up.php'; if (!file_exists($path)) exit(header("location: /404")); include $rootd.'lib/init/html.php'; include $path; }
  131. // Init MySQL logins function getNewPDOInstance($db_name) { require $_SERVER['DOCUMENT_ROOT']."/master_config.php"; $db_hostname

    = $lab_hostname; $db_username = $lab_username; $db_password = $lab_password; if($is_live & $db_name == “MY_DB_NAME” || $db_name == “OTHER_DB_NAME” || $db_name == “YET_ANOTHER_DB_NAME” ){ $db_hostname = $tracking_machine_hostname; $db_username = $tracking_machine_username; $db_password = $tracking_machine_password; } if($is_live & $db_name == "login"){ $db_hostname = $login_machine_hostname; $db_username = $login_machine_username; $db_password = $login_machine_password; } index.php functions database connection.php
  132. // Init MySQL logins function getNewPDOInstance($db_name) { require $_SERVER['DOCUMENT_ROOT']."/master_config.php"; $db_hostname

    = $lab_hostname; $db_username = $lab_username; $db_password = $lab_password; if($is_live & $db_name == “MY_DB_NAME” || $db_name == “OTHER_DB_NAME” || $db_name == “YET_ANOTHER_DB_NAME” ){ $db_hostname = $tracking_machine_hostname; $db_username = $tracking_machine_username; $db_password = $tracking_machine_password; } if($is_live & $db_name == "login"){ $db_hostname = $login_machine_hostname; $db_username = $login_machine_username; $db_password = $login_machine_password; } index.php functions database connection.php
  133. // Init MySQL logins function getNewPDOInstance($db_name) { require $_SERVER['DOCUMENT_ROOT']."/master_config.php"; $db_hostname

    = $lab_hostname; $db_username = $lab_username; $db_password = $lab_password; if($is_live & $db_name == “MY_DB_NAME” || $db_name == “OTHER_DB_NAME” || $db_name == “YET_ANOTHER_DB_NAME” ){ $db_hostname = $tracking_machine_hostname; $db_username = $tracking_machine_username; $db_password = $tracking_machine_password; } if($is_live & $db_name == "login"){ $db_hostname = $login_machine_hostname; $db_username = $login_machine_username; $db_password = $login_machine_password; } index.php functions database connection.php
  134. // Init MySQL logins function getNewPDOInstance($db_name) { require $_SERVER['DOCUMENT_ROOT']."/master_config.php"; $db_hostname

    = $lab_hostname; $db_username = $lab_username; $db_password = $lab_password; if($is_live & $db_name == “MY_DB_NAME” || $db_name == “OTHER_DB_NAME” || $db_name == “YET_ANOTHER_DB_NAME” ){ $db_hostname = $tracking_machine_hostname; $db_username = $tracking_machine_username; $db_password = $tracking_machine_password; } if($is_live & $db_name == "login"){ $db_hostname = $login_machine_hostname; $db_username = $login_machine_username; $db_password = $login_machine_password; } index.php functions database connection.php
  135. // Init MySQL logins function getNewPDOInstance($db_name) { require $_SERVER['DOCUMENT_ROOT']."/master_config.php"; $db_hostname

    = $lab_hostname; $db_username = $lab_username; $db_password = $lab_password; if($is_live & $db_name == “MY_DB_NAME” || $db_name == “OTHER_DB_NAME” || $db_name == “YET_ANOTHER_DB_NAME” ){ $db_hostname = $tracking_machine_hostname; $db_username = $tracking_machine_username; $db_password = $tracking_machine_password; } if($is_live & $db_name == "login"){ $db_hostname = $login_machine_hostname; $db_username = $login_machine_username; $db_password = $login_machine_password; } index.php functions database connection.php
  136. // Init MySQL logins function getNewPDOInstance($db_name) { require $_SERVER['DOCUMENT_ROOT']."/master_config.php"; $db_hostname

    = $lab_hostname; $db_username = $lab_username; $db_password = $lab_password; if($is_live & $db_name == “MY_DB_NAME” || $db_name == “OTHER_DB_NAME” || $db_name == “YET_ANOTHER_DB_NAME” ){ $db_hostname = $tracking_machine_hostname; $db_username = $tracking_machine_username; $db_password = $tracking_machine_password; } if($is_live & $db_name == "login"){ $db_hostname = $login_machine_hostname; $db_username = $login_machine_username; $db_password = $login_machine_password; } index.php functions database connection.php
  137. // Init MySQL logins function getNewPDOInstance($db_name) { require $_SERVER['DOCUMENT_ROOT']."/master_config.php"; $db_hostname

    = $lab_hostname; $db_username = $lab_username; $db_password = $lab_password; if($is_live & $db_name == “MY_DB_NAME” || $db_name == “OTHER_DB_NAME” || $db_name == “YET_ANOTHER_DB_NAME” ){ $db_hostname = $tracking_machine_hostname; $db_username = $tracking_machine_username; $db_password = $tracking_machine_password; } if($is_live & $db_name == "login"){ $db_hostname = $login_machine_hostname; $db_username = $login_machine_username; $db_password = $login_machine_password; } index.php functions database connection.php
  138. // Init MySQL logins function getNewPDOInstance($db_name) { require $_SERVER['DOCUMENT_ROOT']."/master_config.php"; $db_hostname

    = $lab_hostname; $db_username = $lab_username; $db_password = $lab_password; if($is_live & $db_name == “MY_DB_NAME” || $db_name == “OTHER_DB_NAME” || $db_name == “YET_ANOTHER_DB_NAME” ){ $db_hostname = $tracking_machine_hostname; $db_username = $tracking_machine_username; $db_password = $tracking_machine_password; } if($is_live & $db_name == "login"){ $db_hostname = $login_machine_hostname; $db_username = $login_machine_username; $db_password = $login_machine_password; } index.php functions database connection.php
  139. $db_username = $login_machine_username; $db_password = $login_machine_password; } if($is_live & $db_name

    == “login_clients"){ $db_hostname = $login_clients_machine_hostname; $db_username = $login_clients_machine_username; $db_password = $login_clients_machine_password; } $dbh = null; try { $dbh = new PDO("mysql:host=$db_hostname;dbname=$db_name;charset=utf8mb4 $db_password); } catch(PDOException $e) { error_log("Error in newPDOInstance: ".$e->getMessage()); print_r($e); return false; } //print_r($e); return $dbh; } index.php functions database connection.php
  140. $db_username = $login_machine_username; $db_password = $login_machine_password; } if($is_live & $db_name

    == “login_clients"){ $db_hostname = $login_clients_machine_hostname; $db_username = $login_clients_machine_username; $db_password = $login_clients_machine_password; } $dbh = null; try { $dbh = new PDO("mysql:host=$db_hostname;dbname=$db_name;charset=utf8mb4 $db_password); } catch(PDOException $e) { error_log("Error in newPDOInstance: ".$e->getMessage()); print_r($e); return false; } //print_r($e); return $dbh; } index.php functions database connection.php
  141. $db_username = $login_machine_username; $db_password = $login_machine_password; } if($is_live & $db_name

    == “login_clients"){ $db_hostname = $login_clients_machine_hostname; $db_username = $login_clients_machine_username; $db_password = $login_clients_machine_password; } $dbh = null; try { $dbh = new PDO("mysql:host=$db_hostname;dbname=$db_name;charset=utf8mb4 $db_password); } catch(PDOException $e) { error_log("Error in newPDOInstance: ".$e->getMessage()); print_r($e); return false; } //print_r($e); return $dbh; } index.php functions database connection.php
  142. $db_username = $login_machine_username; $db_password = $login_machine_password; } if($is_live & $db_name

    == “login_clients"){ $db_hostname = $login_clients_machine_hostname; $db_username = $login_clients_machine_username; $db_password = $login_clients_machine_password; } $dbh = null; try { $dbh = new PDO("mysql:host=$db_hostname;dbname=$db_name;charset=utf8mb4 $db_password); } catch(PDOException $e) { error_log("Error in newPDOInstance: ".$e->getMessage()); print_r($e); return false; } //print_r($e); return $dbh; } index.php functions database connection.php
  143. index.php functions database <?php $is_live = true; $lab_hostname = '...';

    $lab_username = '...'; $lab_password = '...'; $tracking_machine_hostname = '...'; $tracking_machine_username = '...'; $tracking_machine_password = '...'; $login_machine_hostname = '...'; $login_machine_username = '...'; $login_machine_password = '...'; $login_clients_machine_hostname = '...'; $login_clients_machine_username = '...'; $login_clients_machine_password = '...'; master_config.php
  144. Refactor

  145. Encapsulate Elements With Getters & Setters

  146. index.php functions database <?php $is_live = true; $lab_hostname = '...';

    $lab_username = '...'; $lab_password = '...'; $tracking_machine_hostname = '...'; $tracking_machine_username = '...'; $tracking_machine_password = '...'; $login_machine_hostname = '...'; $login_machine_username = '...'; $login_machine_password = '...'; $login_clients_machine_hostname = '...'; $login_clients_machine_username = '...'; $login_clients_machine_password = '...'; master_config.php
  147. index.php functions database <?php class Config { private const IS_LIVE

    = true; private const LAB_HOSTNAME = '...'; private const LAB_USERNAME = '...'; private const LAB_PASSWORD = '...'; // ... public static function isLive(): bool { return self::IS_LIVE; } public static function labDBHostname(): string { return self::LAB_HOSTNAME; } } Config.php
  148. index.php functions database class Database { private $default = null;

    private $lab = null; private $login_clients = null; public function getConnectionFor(string $databaseName): \PDO { if (false === array_key_exists( $databaseName, get_object_vars($this)) ) { throw new \InvalidArgumentException(‘Invalid DB requested'); } if (null === $this->{$databaseName}) { $this->{$databaseName} = $this->createConnection($databaseName); } return $this->{$databaseName}; } Config.php Database.php
  149. index.php functions database class Database { private $default = null;

    private $lab = null; private $login_clients = null; public function getConnectionFor(string $databaseName): \PDO { if (false === array_key_exists( $databaseName, get_object_vars($this)) ) { throw new \InvalidArgumentException(‘Invalid DB requested'); } if (null === $this->{$databaseName}) { $this->{$databaseName} = $this->createConnection($databaseName); } return $this->{$databaseName}; } Config.php Database.php
  150. index.php functions database class Database { private $default = null;

    private $lab = null; private $login_clients = null; public function getConnectionFor(string $databaseName): \PDO { if (false === array_key_exists( $databaseName, get_object_vars($this)) ) { throw new \InvalidArgumentException(‘Invalid DB requested'); } if (null === $this->{$databaseName}) { $this->{$databaseName} = $this->createConnection($databaseName); } return $this->{$databaseName}; } Config.php Database.php
  151. index.php functions database class Database { private $default = null;

    private $lab = null; private $login_clients = null; public function getConnectionFor(string $databaseName): \PDO { if (false === array_key_exists( $databaseName, get_object_vars($this)) ) { throw new \InvalidArgumentException(‘Invalid DB requested'); } if (null === $this->{$databaseName}) { $this->{$databaseName} = $this->createConnection($databaseName); } return $this->{$databaseName}; } Config.php Database.php
  152. index.php functions database private function createConnection(string $databaseName): \PDO { if

    ('lab' === $databaseName) { return new \PDO( Config::labDBHostname(), Config::labDBUsername(), Config::labDBPassword() ); } if ('login_clients' === $databaseName) { return new \PDO( // ... ); } } Config.php Database.php
  153. index.php functions database private function createConnection(string $databaseName): \PDO { if

    ('lab' === $databaseName) { return new \PDO( Config::labDBHostname(), Config::labDBUsername(), Config::labDBPassword() ); } if ('login_clients' === $databaseName) { return new \PDO( // ... ); } } Config.php Database.php
  154. index.php repository database Config.php functions ClientRepository.php class ClientRepository { private

    $db; public function __construct(Database $db) { $this->db = $db; } function createUpdateLoginClient($client) { $password_hash = password_hash($client['password'], PASSWORD_DEFAUL $id = $client['id']; $dbh = this->db->getConnectionFor('login_clients'); if ($id) { // ... } if ($client['password'] && $id) { // ... } if (!$id) { $client_id = $this->createClient($client['user_name'], $client[ // ... } }
  155. Pluck Reusable Components

  156. { function createUpdateLoginClient($client) { if ($id) { // ... }

    if ($client['password'] && $id) { // ... } if (!$id) { $client_id = $this->createClient( $client['user_name'], $client[‘email'], $client['security_clearance'] ); // ... } } function createClient($email) { $this->db->getConnectionFor('login_clients'); // ... } } index.php repository database Config.php functions ClientRepository.php
  157. { function createUpdateLoginClient($client) { if ($id) { // ... }

    if ($client['password'] && $id) { // ... } if (!$id) { $client_id = $this->createClient( $client['user_name'], $client[‘email'], $client['security_clearance'] ); // ... } } function createClient($email) { $this->db->getConnectionFor('login_clients'); // ... } } index.php repository database Config.php functions ClientRepository.php
  158. Even consistent bad code is better than inconsistent spaghetti Remember

  159. class Client { private $id; private $email; private $password; private

    $securityClearance; public function __construct( int $id, string $email, string $password, string $securityClearance = 'CLIENT' ) { $this->id = $id; $this->email = $email; $this->password = $password; $this->securityClearance = $securityClearance; } public function getEmail(): string { return $this->email; } public function getPassword(): string index.php repository database Config.php functions Client.php model
  160. index.php repository database Config.php functions Client.php model class Client {

    private $id; private $email; private $password; private $securityClearance; public function __construct( int $id, string $email, string $password, string $securityClearance = 'CLIENT' ) { $this->id = $id; $this->email = $email; $this->password = $password; $this->securityClearance = $securityClearance; } public function getEmail(): string { return $this->email; } public function getPassword(): string
  161. index.php repository database Config.php functions Client.php model class Client {

    private $id; private $email; private $password; private $securityClearance; public function __construct( int $id, string $email, string $password, string $securityClearance = 'CLIENT' ) { $this->id = $id; $this->email = $email; $this->password = $password; $this->securityClearance = $securityClearance; } public function getEmail(): string { return $this->email; } public function getPassword(): string
  162. index.php repository database Config.php functions Client.php model class Client {

    private $id; private $email; private $password; private $securityClearance; public function __construct( int $id, string $email, string $password, string $securityClearance = 'CLIENT' ) { $this->id = $id; $this->email = $email; $this->password = $password; $this->securityClearance = $securityClearance; } public function getEmail(): string { return $this->email; } public function getPassword(): string
  163. { function createUpdateLoginClient(Client $client) { $password_hash = password_hash($client->getPassword(), PASSWORD_DEF $this->db->getConnectionFor('login_clients');

    if ($client->getId()) { // ... } if ($client->getPassword() && $client->getId()) { // ... } if (!$client->getId()) { $client_id = $this->createClient($client); // ... } } function createClient(Client $client) { $this->db->getConnectionFor('login_clients'); // ... } } index.php repository database Config.php functions ClientRepository.php model
  164. { function createUpdateLoginClient(Client $client) { $password_hash = password_hash($client->getPassword(), PASSWORD_DEF $this->db->getConnectionFor('login_clients');

    if ($client->getId()) { // ... } if ($client->getPassword() && $client->getId()) { // ... } if (!$client->getId()) { $client_id = $this->createClient($client); // ... } } function createClient(Client $client) { $this->db->getConnectionFor('login_clients'); // ... } } index.php repository database Config.php functions ClientRepository.php model
  165. Identify & Eleminate Lava Flows As You Go

  166. None
  167. Take Time To Analyse The Domain Before Starting Work

  168. Develop A Model Before Implementation, Rather Than Concurrently

  169. Don’t touch that class

  170. It’s the heart of the application Don’t touch that class

  171. THE BLOB

  172. Single class with large number of properties & methods (>60

    is typical) Symptoms
  173. Single class encapsulates a disparate collection of properties & methods

    Symptoms
  174. Class is too inefficient or excessively complex for reuse and

    testing, or uses excessive resources even for simple operations Symptoms
  175. Functionality is added piecemeal without ever refactoring components out Typical

    Causes
  176. Lack of object oriented architecture Typical Causes

  177. Lack of any architecture Typical Causes

  178. Lack of architectural enforcement Typical Causes

  179. <?php class ReportFactory { /** * class to query the

    db for reports grids while using PDO prepared queri * temporary tables to increase performance. can return both a main selec * and several 'tmp_tables' queries which are required for the main query * * tmp tables must be executed first in the calling script as they provid * cleanup operations and facilitate the execution of the main query. * * example usage: * * $factory = new ReportFactory; * $factory->setSuperCampaign($super); * $factory->setReportType('persona'); * $factory->addSortColumn($col, $order); * * foreach($factory->getTmpTablesSql() AS $tmp) { * $stmt = $conn->prepare($tmp); * $stmt->execute($pdo_tmp_params); * } * * $stmt = $conn->prepare($factory->getSql()); * $stmt->execute($pdo_params); * $array = $stmt->fetchAll(); * * note: $pdo_params is an array of parameters such as 'clientId', reporting ReportFactory.php
  180. <?php class ReportFactory { /** * class to query the

    db for reports grids while using PDO prepared queri * temporary tables to increase performance. can return both a main selec * and several 'tmp_tables' queries which are required for the main query * * tmp tables must be executed first in the calling script as they provid * cleanup operations and facilitate the execution of the main query. * * example usage: * * $factory = new ReportFactory; * $factory->setSuperCampaign($super); * $factory->setReportType('persona'); * $factory->addSortColumn($col, $order); * * foreach($factory->getTmpTablesSql() AS $tmp) { * $stmt = $conn->prepare($tmp); * $stmt->execute($pdo_tmp_params); * } * * $stmt = $conn->prepare($factory->getSql()); * $stmt->execute($pdo_params); * $array = $stmt->fetchAll(); * * note: $pdo_params is an array of parameters such as 'clientId', reporting ReportFactory.php
  181. <?php class ReportFactory { /** * class to query the

    db for reports grids while using PDO prepared queri * temporary tables to increase performance. can return both a main selec * and several 'tmp_tables' queries which are required for the main query * * tmp tables must be executed first in the calling script as they provid * cleanup operations and facilitate the execution of the main query. * * example usage: * * $factory = new ReportFactory; * $factory->setSuperCampaign($super); * $factory->setReportType('persona'); * $factory->addSortColumn($col, $order); * * foreach($factory->getTmpTablesSql() AS $tmp) { * $stmt = $conn->prepare($tmp); * $stmt->execute($pdo_tmp_params); * } * * $stmt = $conn->prepare($factory->getSql()); * $stmt->execute($pdo_params); * $array = $stmt->fetchAll(); * * note: $pdo_params is an array of parameters such as 'clientId', reporting ReportFactory.php
  182. CampaignID DateTime Event 1234 2019-01-01 13:37:00 click 1234 2019-01-01 12:34:56

    view 9876 2019-01-02 18:00:01 view 1234 2019-01-02 23:33:32 click … … … … … … … … …
  183. CampaignID DateTime Event 1234 2019-01-01 13:37:00 click 1234 2019-01-01 12:34:56

    view 9876 2019-01-02 18:00:01 view 1234 2019-01-02 23:33:32 click … … … … … … … … … CampaignID Date Clicks Likes Follows … 1234 2019-01-01 39480120 1398473 32424 … 3482 2019-01-01 23482134 235019934 94512 … 9876 2019-01-02 9324243 123489132 405202 … 9231 2019-01-02 1249823 509847 3248 …
  184. CampaignID DateTime Event 1234 2019-01-01 13:37:00 click 1234 2019-01-01 12:34:56

    view 9876 2019-01-02 18:00:01 view 1234 2019-01-02 23:33:32 click … … … … … … … … … CampaignID Date Event Total 1234 2019-01-01 click 1398473 1234 2019-01-01 view 235019934 9876 2019-01-02 view 123489132 1234 2019-01-02 click 509847 CampaignID Date Event Total 1234 2019-01-01 click 1398473 1234 2019-01-01 view 235019934 9876 2019-01-02 view 123489132 1234 2019-01-02 click 509847 CampaignID Date Event Total 1234 2019-01-01 click 1398473 1234 2019-01-01 view 235019934 9876 2019-01-02 view 123489132 1234 2019-01-02 click 509847 CampaignID Date Clicks Likes Follows … 1234 2019-01-01 39480120 1398473 32424 … 3482 2019-01-01 23482134 235019934 94512 … 9876 2019-01-02 9324243 123489132 405202 … 9231 2019-01-02 1249823 509847 3248 …
  185. Identify Relationships According To Contracts

  186. function _createTmpTables() {} function _getScheduledReportsSelectSql() {} function _getCampaignSelectSql() {} function

    _getPersonaSelectSql() {} function _getImageSelectSql() {} function _getTitleSelectSql() {} function _getOverviewSelectSql() {} function _getExcelDownloadSelectSql() {} function _getCampaignsOverviewSelectSql() {} function _createSuperCampaignsTable() {} function _createCampaignGroupsTable() {} function _createSuperCampaignsStatusesTable() {} function _createCampaignGroupsStatusesTable() {} function _createSuperCampaignsStatsTable() {} function _createCampaignGroupsStatsTable() {} function _createCampaignsStatsTable() {} function addSortColumn($col, $order) {} function setReportType($report_type) {} function addTmpOr($status) {} function setWhere($where) {} function addWhere($where) {} function getWhere() {} function setColumns($col) {} function addColumn($col) {} function setParameters($params) {} function addParam($param) {}
  187. function _createTmpTables() {} function _getScheduledReportsSelectSql() {} function _getCampaignSelectSql() {} function

    _getPersonaSelectSql() {} function _getImageSelectSql() {} function _getTitleSelectSql() {} function _getOverviewSelectSql() {} function _getExcelDownloadSelectSql() {} function _getCampaignsOverviewSelectSql() {} function _createSuperCampaignsTable() {} function _createCampaignGroupsTable() {} function _createSuperCampaignsStatusesTable() {} function _createCampaignGroupsStatusesTable() {} function _createSuperCampaignsStatsTable() {} function _createCampaignGroupsStatsTable() {} function _createCampaignsStatsTable() {} function addSortColumn($col, $order) {} function setReportType($report_type) {} function addTmpOr($status) {} function setWhere($where) {} function addWhere($where) {} function getWhere() {} function setColumns($col) {} function addColumn($col) {} function setParameters($params) {} function addParam($param) {}
  188. function _createTmpTables() {} function _getScheduledReportsSelectSql() {} function _getCampaignSelectSql() {} function

    _getPersonaSelectSql() {} function _getImageSelectSql() {} function _getTitleSelectSql() {} function _getOverviewSelectSql() {} function _getExcelDownloadSelectSql() {} function _getCampaignsOverviewSelectSql() {} function _createSuperCampaignsTable() {} function _createCampaignGroupsTable() {} function _createSuperCampaignsStatusesTable() {} function _createCampaignGroupsStatusesTable() {} function _createSuperCampaignsStatsTable() {} function _createCampaignGroupsStatsTable() {} function _createCampaignsStatsTable() {} function addSortColumn($col, $order) {} function setReportType($report_type) {} function addTmpOr($status) {} function setWhere($where) {} function addWhere($where) {} function getWhere() {} function setColumns($col) {} function addColumn($col) {} function setParameters($params) {} function addParam($param) {}
  189. function _createTmpTables() {} function _getScheduledReportsSelectSql() {} function _getCampaignSelectSql() {} function

    _getPersonaSelectSql() {} function _getImageSelectSql() {} function _getTitleSelectSql() {} function _getOverviewSelectSql() {} function _getExcelDownloadSelectSql() {} function _getCampaignsOverviewSelectSql() {} function _createSuperCampaignsTable() {} function _createCampaignGroupsTable() {} function _createSuperCampaignsStatusesTable() {} function _createCampaignGroupsStatusesTable() {} function _createSuperCampaignsStatsTable() {} function _createCampaignGroupsStatsTable() {} function _createCampaignsStatsTable() {} function addSortColumn($col, $order) {} function setReportType($report_type) {} function addTmpOr($status) {} function setWhere($where) {} function addWhere($where) {} function getWhere() {} function setColumns($col) {} function addColumn($col) {} function setParameters($params) {} function addParam($param) {}
  190. Refactor

  191. Identify Natural Homes For The Contracts

  192. function _createTmpTables() {} function _getScheduledReportsSelectSql() {} function _getCampaignSelectSql() {} function

    _getPersonaSelectSql() {} function _getImageSelectSql() {} function _getTitleSelectSql() {} function _getOverviewSelectSql() {} function _getExcelDownloadSelectSql() {} function _getCampaignsOverviewSelectSql() {} function _createSuperCampaignsTable() {} function _createCampaignGroupsTable() {} function _createSuperCampaignsStatusesTable() {} function _createCampaignGroupsStatusesTable() {} function _createSuperCampaignsStatsTable() {} function _createCampaignGroupsStatsTable() {} function _createCampaignsStatsTable() {} function addSortColumn($col, $order) {} function setReportType($report_type) {} function addTmpOr($status) {} function setWhere($where) {} function addWhere($where) {} function getWhere() {} function setColumns($col) {} function addColumn($col) {} function setParameters($params) {} function addParam($param) {}
  193. <?php class QueryBuilder { public function addSortColumn($col, $order) {} public

    function setReportType($report_type) {} public function addTmpOr($status) {} public function setWhere($where) {} // ... } reporting QueryBuilder.php Tmp
  194. <?php class ReportFactory { private $queryBuilder; private $sql; public function

    __construct() { $this->queryBuilder = new QueryBuilder(); } public function getQueryBuilder() { return $this->queryBuilder; } public function getSql() { return $sql; } // ... } reporting ReportFactory.php Tmp
  195. function someAction() { $factory = new ReportFactory(); // ... $factory->addWhere('status

    = '. $st); $factory->addTmpOr('(c.status <= '.$st.')'); $factory->addWhere('(time_start <= "' . date("Y-m-d H:i:s", time()) . '" OR time_start is null)’ ); } reporting Controller.php Tmp
  196. function someAction() { $factory = new ReportFactory(); $builder = $factory->getQueryBuilder();

    // ... $builder->addWhere('status = '. $st); $builder->addTmpOr('(c.status <= '.$st.')'); $builder->addWhere('(time_start <= "' . date("Y-m-d H:i:s", time()) . '" OR time_start is null)’ ); } reporting Controller.php Tmp
  197. function _createTmpTables() {} function _getScheduledReportsSelectSql() {} function _getCampaignSelectSql() {} function

    _getPersonaSelectSql() {} function _getImageSelectSql() {} function _getTitleSelectSql() {} function _getOverviewSelectSql() {} function _getExcelDownloadSelectSql() {} function _getCampaignsOverviewSelectSql() {} function _createSuperCampaignsTable() {} function _createCampaignGroupsTable() {} function _createSuperCampaignsStatusesTable() {} function _createCampaignGroupsStatusesTable() {} function _createSuperCampaignsStatsTable() {} function _createCampaignGroupsStatsTable() {} function _createCampaignsStatsTable() {} function addSortColumn($col, $order) {} function setReportType($report_type) {} function addTmpOr($status) {} function setWhere($where) {} function addWhere($where) {} function getWhere() {} function setColumns($col) {} function addColumn($col) {} function setParameters($params) {} function addParam($param) {}
  198. function _createTmpTables() {} function _getScheduledReportsSelectSql() {} function _getCampaignSelectSql() {} function

    _getPersonaSelectSql() {} function _getImageSelectSql() {} function _getTitleSelectSql() {} function _getOverviewSelectSql() {} function _getExcelDownloadSelectSql() {} function _getCampaignsOverviewSelectSql() {} function _createSuperCampaignsTable() {} function _createCampaignGroupsTable() {} function _createSuperCampaignsStatusesTable() {} function _createCampaignGroupsStatusesTable() {} function _createSuperCampaignsStatsTable() {} function _createCampaignGroupsStatsTable() {} function _createCampaignsStatsTable() {}
  199. function _createTmpTables() {} function _getScheduledReportsSelectSql() {} function _getCampaignSelectSql() {} function

    _getPersonaSelectSql() {} function _getImageSelectSql() {} function _getTitleSelectSql() {} function _getOverviewSelectSql() {} function _getExcelDownloadSelectSql() {} function _getCampaignsOverviewSelectSql() {} function _createSuperCampaignsTable() {} function _createCampaignGroupsTable() {} function _createSuperCampaignsStatusesTable() {} function _createCampaignGroupsStatusesTable() {} function _createSuperCampaignsStatsTable() {} function _createCampaignGroupsStatsTable() {} function _createCampaignsStatsTable() {}
  200. <?php class TemporaryTable { public function createTmpTables(string $reportType) {} private

    function createSuperCampaignsTable() {} private function createCampaignGroupsTable() {} private function createSuperCampaignsStatusesTable() {} private function createCampaignGroupsStatusesTable() {} private function createSuperCampaignsStatsTable() {} private function createCampaignGroupsStatsTable() {} private function createCampaignsStatsTable() {} // ... } reporting TmpTable.php Tmp QueryBuilder.php
  201. <?php class TemporaryTable { public function createTmpTables(string $reportType) { switch

    ($reportType) { case 'clicks': $this->createCampaignGroupsTable(); $this->createCampaignGroupsStatsTable(); $this->createClicksMetricsTable(); break; case 'views': // ... break; } } } reporting TmpTable.php Tmp QueryBuilder.php
  202. function _createTmpTables() {} function _getScheduledReportsSelectSql() {} function _getCampaignSelectSql() {} function

    _getPersonaSelectSql() {} function _getImageSelectSql() {} function _getTitleSelectSql() {} function _getOverviewSelectSql() {} function _getExcelDownloadSelectSql() {} function _getCampaignsOverviewSelectSql() {} function _createSuperCampaignsTable() {} function _createCampaignGroupsTable() {} function _createSuperCampaignsStatusesTable() {} function _createCampaignGroupsStatusesTable() {} function _createSuperCampaignsStatsTable() {} function _createCampaignGroupsStatsTable() {} function _createCampaignsStatsTable() {}
  203. function _getScheduledReportsSelectSql() {} function _getCampaignSelectSql() {} function _getPersonaSelectSql() {} function

    _getImageSelectSql() {} function _getTitleSelectSql() {} function _getOverviewSelectSql() {} function _getExcelDownloadSelectSql() {} function _getCampaignsOverviewSelectSql() {}
  204. <?php class MetricsQuery { private $tmpTable; public function __construct(TmpTable $tmpTable)

    { $this->tmpTable = $tmpTable; } public function getScheduledReportsSelectSql() {} public function getCampaignSelectSql() {} public function getPersonaSelectSql() {} public function getImageSelectSql() {} public function getTitleSelectSql() {} public function getOverviewSelectSql() {} // ... } reporting MetricsQuery.php Tmp QueryBuilder.php TmpTable.php
  205. Remove Couplings

  206. Controller

  207. ReportFactory getQueryBuilder getSql … Controller

  208. ReportFactory getQueryBuilder getSql … Controller

  209. ReportFactory getQueryBuilder getSql … Controller QueryBuilder addColumn addSortColumn addWhere …

  210. MetricsQuery getCampaignSelectSql getImageSelectSql getTitleSelectSql ReportFactory getQueryBuilder getSql … … Controller

    QueryBuilder addColumn addSortColumn addWhere …
  211. MetricsQuery getCampaignSelectSql getImageSelectSql getTitleSelectSql ReportFactory getQueryBuilder getSql … … TmpTable

    createTmpTables createCampaignsTable createStatsTable … Controller QueryBuilder addColumn addSortColumn addWhere …
  212. MetricsQuery getCampaignSelectSql getImageSelectSql getTitleSelectSql ReportFactory getQueryBuilder getSql … … TmpTable

    createTmpTables createCampaignsTable createStatsTable … Controller QueryBuilder addColumn addSortColumn addWhere …
  213. <?php abstract class MetricsReport extends ReportFactory { private $connection; public

    function __construct(\PDO $connection) { $this->connection = $connection; } abstract public function setQueryConditions(); public function getReport() { $this->setQueryConditions(); $sql = $this->getSql(); $query = $this->pdo->prepare($sql); return $query->execute(); } } reporting MetricsQuery.php Tmp QueryBuilder.php TmpTable.php MetricsReport.php
  214. <?php abstract class MetricsReport extends ReportFactory { private $connection; public

    function __construct(\PDO $connection) { $this->connection = $connection; } abstract public function setQueryConditions(); public function getReport() { $this->setQueryConditions(); $sql = $this->getSql(); $query = $this->pdo->prepare($sql); return $query->execute(); } } reporting MetricsQuery.php Tmp QueryBuilder.php TmpTable.php MetricsReport.php
  215. <?php abstract class MetricsReport extends ReportFactory { private $connection; public

    function __construct(\PDO $connection) { $this->connection = $connection; } abstract public function setQueryConditions(); public function getReport() { $this->setQueryConditions(); $sql = $this->getSql(); $query = $this->pdo->prepare($sql); return $query->execute(); } } reporting MetricsQuery.php Tmp QueryBuilder.php TmpTable.php MetricsReport.php
  216. function someAction() { $factory = new ReportFactory(); // ... $factory->addWhere('status

    = '. $st); $factory->addTmpOr('(c.status <= '.$st.')'); $factory->addWhere('(time_start <= "' . date("Y-m-d H:i:s", time()) . '" OR time_start is null)’ ); } reporting Controller.php Tmp
  217. function someAction() { $report = new SomeMetricsReport(); return $report->getResult(); }

    reporting Controller.php Tmp
  218. MetricsQuery getCampaignSelectSql getImageSelectSql getTitleSelectSql ReportFactory getQueryBuilder getSql … … TmpTable

    createTmpTables createCampaignsTable createStatsTable … Controller QueryBuilder addColumn addSortColumn addWhere …
  219. MetricsQuery getCampaignSelectSql getImageSelectSql getTitleSelectSql ReportFactory getQueryBuilder getSql … … TmpTable

    createTmpTables createCampaignsTable createStatsTable … Controller QueryBuilder addColumn addSortColumn addWhere … MetricsReport ReportFactory setQueryConditions
  220. <?php abstract class MetricsReport extends ReportFactory { private $connection; public

    function __construct(\PDO $connection) { $this->connection = $connection; } abstract public function setQueryConditions(); public function getReport() { $this->setQueryConditions(); $sql = $this->getSql(); $query = $this->pdo->prepare($sql); return $query->execute(); } } reporting MetricsQuery.php Tmp QueryBuilder.php TmpTable.php MetricsReport.php
  221. <?php abstract class MetricsReport extends ReportFactory { private $connection; public

    function __construct(\PDO $connection) { $this->connection = $connection; } abstract public function getQuery(); public function getReport() { $sql = $this->getQuery(); $query = $this->pdo->prepare($sql); return $query->execute(); } } reporting MetricsQuery.php Tmp QueryBuilder.php TmpTable.php MetricsReport.php
  222. MetricsQuery getCampaignSelectSql getImageSelectSql getTitleSelectSql ReportFactory getQueryBuilder getSql … … TmpTable

    createTmpTables createCampaignsTable createStatsTable … Controller QueryBuilder addColumn addSortColumn addWhere … getQuery MetricsReport ReportFactory
  223. MetricsQuery getCampaignSelectSql getImageSelectSql getTitleSelectSql ReportFactory getQueryBuilder getSql … … TmpTable

    createTmpTables createCampaignsTable createStatsTable … Controller QueryBuilder addColumn addSortColumn addWhere … getQuery MetricsReport ReportFactory
  224. MetricsQuery getCampaignSelectSql getImageSelectSql getTitleSelectSql ReportFactory getQueryBuilder getSql … … TmpTable

    createTmpTables createCampaignsTable createStatsTable … Controller QueryBuilder addColumn addSortColumn addWhere … MetricsReport getQuery
  225. MetricsQuery getCampaignSelectSql getImageSelectSql getTitleSelectSql … TmpTable createTmpTables createCampaignsTable createStatsTable …

    Controller QueryBuilder addColumn addSortColumn addWhere … MetricsReport getQuery
  226. None
  227. <?php interface TmpTableSql { public function getSql(): string; } reporting

    TmpTableSql.php Query Tmp
  228. <?php class SomeTempTable implements TmpTableSql { private $sql; public function

    __construct(TmpTableSql $sql) { $this->sql = $sql; } public function getSql(): string { return $this->sql->getSql() . ' CREATE TABLE ... ;'; } } reporting TmpTableSql.php Query Tmp SomeTmpTable.php
  229. The solution doesn’t have to be perfect… Remember

  230. … Just less shit than it was before Remember

  231. MetricsQuery getCampaignSelectSql getImageSelectSql getTitleSelectSql … TmpTable createTmpTables createCampaignsTable createStatsTable …

    Controller QueryBuilder addColumn addSortColumn addWhere … MetricsReport getQuery
  232. MetricsQuery getCampaignSelectSql getImageSelectSql getTitleSelectSql … << interface >> TmpTable getSql

    Controller QueryBuilder addColumn addSortColumn addWhere … MetricsReport getQuery
  233. MetricsQuery getCampaignSelectSql getImageSelectSql getTitleSelectSql … << interface >> TmpTable getSql

    Controller MetricsReport getQuery
  234. <?php class MetricsReport { private $connection; public function __construct(\PDO $connection)

    { $this->connection = $connection; } abstract protected function setupTables(): TmpTable; abstract protected function getSql(): string; public function getReport() { $tmpTables = $this->connection->prepare($this->setupTables()); $tmpTables->execute(); $query = $this->connection->prepare($this->getSql()); $query->execute(); return $query->fetchAll(); } reporting TmpTableSql.php Query Tmp MetricsReport.php SomeTmpTable.php
  235. <?php class MetricsReport { private $connection; public function __construct(\PDO $connection)

    { $this->connection = $connection; } abstract protected function setupTables(): TmpTable; abstract protected function getSql(): string; public function getReport() { $tmpTables = $this->connection->prepare($this->setupTables()); $tmpTables->execute(); $query = $this->connection->prepare($this->getSql()); $query->execute(); return $query->fetchAll(); } reporting TmpTableSql.php Query Tmp MetricsReport.php SomeTmpTable.php
  236. <?php class MetricsReport { private $connection; public function __construct(\PDO $connection)

    { $this->connection = $connection; } abstract protected function setupTables(): TmpTable; abstract protected function getSql(): string; public function getReport() { $tmpTables = $this->connection->prepare($this->setupTables()); $tmpTables->execute(); $query = $this->connection->prepare($this->getSql()); $query->execute(); return $query->fetchAll(); } reporting TmpTableSql.php Query Tmp MetricsReport.php SomeTmpTable.php
  237. MetricsQuery getCampaignSelectSql getImageSelectSql getTitleSelectSql … << interface >> TmpTable getSql

    Controller MetricsReport setupTables getSql getReport
  238. MetricsQuery getCampaignSelectSql getImageSelectSql getTitleSelectSql … << interface >> TmpTable getSql

    Controller MetricsReport setupTables getSql getReport
  239. << interface >> TmpTable getSql Controller MetricsReport setupTables getSql getReport

  240. reporting TmpTableSql.php Query Tmp MetricsReport.php SomeTmpTable.php

  241. TmpTableSql.php Query MetricsReport.php SomeTmpTable.php

  242. None
  243. Code Review

  244. The project is almost finished, we’ve been a great team

  245. The project is almost finished, we’ve been a great team

    Everybody got on so well & worked so hard together
  246. The project is almost finished, we’ve been a great team

    Everybody got on so well & worked so hard together It’s going to be a disaster
  247. FEAR OF SUCCESS

  248. People involved in the project begin to obsess over things

    which could go wrong Symptoms
  249. Insecurities about professional competence begin to surface Symptoms

  250. Conflicts between team members arise just before project completion Symptoms

  251. Irrational decisions are made & inappropriate resolutions to problems are

    taken Symptoms
  252. Negative publicity on the project is generated by people involved

    with the work Symptoms
  253. People have ‘termination issues’ Typical Causes

  254. Team members build relationships which they subconsciously don’t want to

    break up at the end of the project Typical Causes
  255. People have assumed roles / status within the team which

    they don’t want to lose when the project is completed Typical Causes
  256. People do crazy things Typical Causes

  257. Declare Success

  258. Celebrate Achievements & Praise Team Members

  259. We spent 6 months waiting for requirements from management

  260. We spent 6 months waiting for requirements from management But

    they’ll cancel the project unless it’s completed soon
  261. We spent 6 months waiting for requirements from management They’ve

    given us 4 weeks But they’ll cancel the project unless it’s completed soon
  262. FIRE DRILL

  263. Management prevents development teams from making progress by telling them

    to wait Symptoms
  264. Management prevents development teams from making progress by giving unclear

    or conflicting requirements Symptoms
  265. After a few months management calls an emergency launch meeting,

    setting unrealistic deadlines & threatening to cancel the project Symptoms
  266. Managers have too many projects competing for their attention at

    any given time Typical Causes
  267. Shelter

  268. Iterative Progress

  269. Internal Progress

  270. External Progress

  271. Notes

  272. Pick The Low Hanging Fruit

  273. Learning & Experience

  274. Training

  275. Leadership

  276. Teamwork

  277. None
  278. None
  279. WE ARE HIRING!