Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Min-Maxing Software Costs

595 views

Published on

Software development is riddled with explicit and implicit costs. Every decision you make has a cost attached to it. When you're writing code, you're making an investment, the size of which will for a long time define the costs of your future growth. Making right decision about these investments is very tricky and the cost of wrong decisions might be crippling for both business and teams that support it.

Extreme Programming and Test Driven Development in particular are practices that are aiming at supporting development effort by making it easier to introduce change. That said, sometimes those tools can become a problem of its own when applied in the wrong way or for the wrong context. Understanding software cost forces is a very important skill of successful teams and something that helps understand how to apply XP and TDD in different contexts.

Published in: Technology
  • Be the first to comment

Min-Maxing Software Costs

  1. 1. MIN-MAXING Software Costs Konstantin K. @everzet
  2. 2. TECHNICAL DEBT
  3. 3. Without frameworks Simple frameworks Enterprise frameworks
  4. 4. IS MESS PART OF A COURSE?
  5. 5. MIN-MAXING SOFTWARE COSTS
  6. 6. SOFTWARE FORCES • Creation - Introduction of a brand new feature • Change - Business-driven modification of existing feature • Ownership - Physical capability to change a feature • Control - Capability to sustainably change a feature
  7. 7. SOFTWARE COSTS 1. Cost of Creation 2. Cost of Change 3. Cost of Control
  8. 8. COST OF CREATION
  9. 9. Project Lifetime Beginning 3 months 6 months 9 months ... Cost of Creation
  10. 10. CONVENTIONS OPTIMISE FOR CREATION
  11. 11. $ bin/rails generate controller welcome index
  12. 12. from django.contrib import admin from . import models admin.site.register(models.Article)
  13. 13. package hello; import java.util.concurrent.atomic.AtomicLong; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController public class GreetingController { ... }
  14. 14. CREATION IS LIMITED BY THE LIFE SPAN
  15. 15. Creation → Change Project Lifetime Beginning 3 months 6 months 9 months ... Cost of Creation
  16. 16. Creation → Change Project Lifetime Beginning 3 months 6 months 9 months ... Cost of Creation Pure observation & personal experience
  17. 17. Creation → Change Project Lifetime Beginning 3 months 6 months 9 months ... Cost of Creation Conventional Project
  18. 18. Creation → Change Project Lifetime Beginning 3 months 6 months 9 months ... Cost of Creation Conventional Project Conventional Project
  19. 19. Convention-based projects either die a hero or live long enough to see themselves become the villain.
  20. 20. COST OF CHANGE
  21. 21. Project Lifetime Beginning 3 months 6 months 9 months ... Cost of Creation Cost of Change
  22. 22. THE SEARCH FUNCTION
  23. 23. public function searchAction(Request $req) { $form = $this->createForm(new SearchQueryType, new SearchQuery); $normalizedOrderBys = $this->getNormalizedOrderBys($filteredOrderBys); $this->computeSearchQuery($req, $filteredOrderBys); if ($req->query->has('search_query')) { /** @var $solarium Solarium_Client */ $solarium = $this->get('solarium.client'); $select = $solarium->createSelect(); // configure dismax $dismax = $select->getDisMax(); $dismax->setQueryFields(array('name^4', 'description', 'tags', 'text', 'text_ngram', 'name_split^2')); if ($req->query->has('search_query')) { $form->bind($req); if ($form->isValid()) { $escapedQuery = $select->getHelper()->escapeTerm($form->getData()->getQuery()); $escapedQuery = preg_replace('/(^| )-(S)/', '$1-$2', $escapedQuery); $escapedQuery = preg_replace('/(^| )+(S)/', '$1+$2', $escapedQuery); if ((substr_count($escapedQuery, '"') % 2) == 0) { $escapedQuery = str_replace('"', '"', $escapedQuery); } $select->setQuery($escapedQuery); } } } elseif ($req->getRequestFormat() === 'json') { return JsonResponse::create(array( 'error' => 'Missing search query, example: ?q=example' ), 400)->setCallback($req->query->get('callback')); } return $this->render('SomeAppWebBundle:Web:search.html.twig'); }
  24. 24. public function searchAction(Request $req) { $form = $this->createForm(new SearchQueryType, new SearchQuery); $normalizedOrderBys = $this->getNormalizedOrderBys($filteredOrderBys); $this->computeSearchQuery($req, $filteredOrderBys); if ($req->query->has('search_query')) { /** @var $solarium Solarium_Client */ $solarium = $this->get('solarium.client'); $select = $solarium->createSelect(); // configure dismax $dismax = $select->getDisMax(); $dismax->setQueryFields(array('name^4', 'description', 'tags', 'text', 'text_ngram', 'name_split^2')); if ($req->query->has('search_query')) { $form->bind($req); if ($form->isValid()) { $escapedQuery = $select->getHelper()->escapeTerm($form->getData()->getQuery()); $escapedQuery = preg_replace('/(^| )-(S)/', '$1-$2', $escapedQuery); $escapedQuery = preg_replace('/(^| )+(S)/', '$1+$2', $escapedQuery); if ((substr_count($escapedQuery, '"') % 2) == 0) { $escapedQuery = str_replace('"', '"', $escapedQuery); } $select->setQuery($escapedQuery); } } } elseif ($req->getRequestFormat() === 'json') { return JsonResponse::create(array( 'error' => 'Missing search query, example: ?q=example' ), 400)->setCallback($req->query->get('callback')); } return $this->render('SomeAppWebBundle:Web:search.html.twig'); }
  25. 25. HOW LONG WOULD IT TAKETO ADD TAGS SUPPORT?
  26. 26. public function searchAction(Request $req) { $form = $this->createForm(new SearchQueryType, new SearchQuery); $normalizedOrderBys = $this->getNormalizedOrderBys($filteredOrderBys); $this->computeSearchQuery($req, $filteredOrderBys); if ($req->query->has('search_query')) { /** @var $solarium Solarium_Client */ $solarium = $this->get('solarium.client'); $select = $solarium->createSelect(); // configure dismax $dismax = $select->getDisMax(); $dismax->setQueryFields(array('name^4', 'description', 'tags', 'text', 'text_ngram', 'name_split^2')); if ($req->query->has('search_query')) { $form->bind($req); if ($form->isValid()) { $escapedQuery = $select->getHelper()->escapeTerm($form->getData()->getQuery()); $escapedQuery = preg_replace('/(^| )-(S)/', '$1-$2', $escapedQuery); $escapedQuery = preg_replace('/(^| )+(S)/', '$1+$2', $escapedQuery); if ((substr_count($escapedQuery, '"') % 2) == 0) { $escapedQuery = str_replace('"', '"', $escapedQuery); } $select->setQuery($escapedQuery); } } } elseif ($req->getRequestFormat() === 'json') { return JsonResponse::create(array( 'error' => 'Missing search query, example: ?q=example' ), 400)->setCallback($req->query->get('callback')); } return $this->render('SomeAppWebBundle:Web:search.html.twig'); }
  27. 27. public function searchAction(Request $req) { $form = $this->createForm(new SearchQueryType, new SearchQuery); $this->computeSearchQuery($req, $filteredOrderBys); $typeFilter = $req->query->get('type'); if ($req->query->has('search_query') || $typeFilter) { /** @var $solarium Solarium_Client */ $solarium = $this->get('solarium.client'); $select = $solarium->createSelect(); // configure dismax $dismax = $select->getDisMax(); $dismax->setQueryFields(array('name^4', 'description', 'tags', 'text', 'text_ngram', 'name_split^2')); $dismax->setPhraseFields(array('description')); $dismax->setBoostFunctions(array('log(trendiness)^10')); $dismax->setMinimumMatch(1); $dismax->setQueryParser('edismax'); // filter by type if ($typeFilter) { $filterQueryTerm = sprintf('type:"%s"', $select->getHelper()->escapeTerm($typeFilter)); $filterQuery = $select->createFilterQuery('type')->setQuery($filterQueryTerm); $select->addFilterQuery($filterQuery); } if ($req->query->has('search_query')) { $form->bind($req); if ($form->isValid()) { $escapedQuery = $select->getHelper()->escapeTerm($form->getData()->getQuery()); $escapedQuery = preg_replace('/(^| )-(S)/', '$1-$2', $escapedQuery); $escapedQuery = preg_replace('/(^| )+(S)/', '$1+$2', $escapedQuery); if ((substr_count($escapedQuery, '"') % 2) == 0) { $escapedQuery = str_replace('"', '"', $escapedQuery); } $select->setQuery($escapedQuery); } } $paginator = new Pagerfanta(new SolariumAdapter($solarium, $select)); $perPage = $req->query->getInt('per_page', 15); if ($perPage <= 0 || $perPage > 100) { if ($req->getRequestFormat() === 'json') { return JsonResponse::create(array( 'status' => 'error', 'message' => 'The optional packages per_page parameter must be an integer between 1 and 100 (default: 15)', ), 400)->setCallback($req->query->get('callback')); } $perPage = max(0, min(100, $perPage)); } } elseif ($req->getRequestFormat() === 'json') { return JsonResponse::create(array( 'error' => 'Missing search query, example: ?q=example' ), 400)->setCallback($req->query->get('callback')); } return $this->render('SomeAppWebBundle:Web:search.html.twig'); }
  28. 28. { $form = $this->createForm(new SearchQueryType, new SearchQuery); $filteredOrderBys = $this->getFilteredOrderedBys($req); $normalizedOrderBys = $this->getNormalizedOrderBys($filteredOrderBys); $this->computeSearchQuery($req, $filteredOrderBys); $typeFilter = $req->query->get('type'); $tagsFilter = $req->query->get('tags'); if ($req->query->has('search_query') || $typeFilter || $tagsFilter) { /** @var $solarium Solarium_Client */ $solarium = $this->get('solarium.client'); $select = $solarium->createSelect(); // configure dismax $dismax = $select->getDisMax(); $dismax->setQueryFields(array('name^4', 'description', 'tags', 'text', 'text_ngram', 'name_split^2')); $dismax->setPhraseFields(array('description')); $dismax->setBoostFunctions(array('log(trendiness)^10')); $dismax->setMinimumMatch(1); $dismax->setQueryParser('edismax'); // filter by type if ($typeFilter) { $filterQueryTerm = sprintf('type:"%s"', $select->getHelper()->escapeTerm($typeFilter)); $filterQuery = $select->createFilterQuery('type')->setQuery($filterQueryTerm); $select->addFilterQuery($filterQuery); } // filter by tags if ($tagsFilter) { $tags = array(); foreach ((array) $tagsFilter as $tag) { $tags[] = $select->getHelper()->escapeTerm($tag); } $filterQueryTerm = sprintf('tags:("%s")', implode('" AND "', $tags)); $filterQuery = $select->createFilterQuery('tags')->setQuery($filterQueryTerm); $select->addFilterQuery($filterQuery); } if (!empty($filteredOrderBys)) { $select->addSorts($normalizedOrderBys); } if ($req->query->has('search_query')) { $form->bind($req); if ($form->isValid()) { $escapedQuery = $select->getHelper()->escapeTerm($form->getData()->getQuery()); $escapedQuery = preg_replace('/(^| )-(S)/', '$1-$2', $escapedQuery); $escapedQuery = preg_replace('/(^| )+(S)/', '$1+$2', $escapedQuery); if ((substr_count($escapedQuery, '"') % 2) == 0) { $escapedQuery = str_replace('"', '"', $escapedQuery); } $select->setQuery($escapedQuery); } } $paginator = new Pagerfanta(new SolariumAdapter($solarium, $select)); $perPage = $req->query->getInt('per_page', 15); if ($perPage <= 0 || $perPage > 100) { if ($req->getRequestFormat() === 'json') { return JsonResponse::create(array( 'status' => 'error', 'message' => 'The optional packages per_page parameter must be an integer between 1 and 100 (default: 15)', ), 400)->setCallback($req->query->get('callback')); } $perPage = max(0, min(100, $perPage)); } $paginator->setMaxPerPage($perPage); $paginator->setCurrentPage($req->query->get('page', 1), false, true); $metadata = array(); foreach ($paginator as $package) { if (is_numeric($package->id)) { $metadata['downloads'][$package->id] = $package->downloads; $metadata['favers'][$package->id] = $package->favers; } } if ($req->getRequestFormat() === 'json') { try { $result = array( 'results' => array(), 'total' => $paginator->getNbResults(), ); } catch (Solarium_Client_HttpException $e) { return JsonResponse::create(array( 'status' => 'error', 'message' => 'Could not connect to the search server', ), 500)->setCallback($req->query->get('callback')); } return JsonResponse::create($result)->setCallback($req->query->get('callback')); } if ($req->isXmlHttpRequest()) { try { return $this->render('PackagistWebBundle:Web:search.html.twig', array( 'packages' => $paginator, 'meta' => $metadata, 'noLayout' => true, )); } catch (Twig_Error_Runtime $e) { if (!$e->getPrevious() instanceof Solarium_Client_HttpException) { throw $e; } return JsonResponse::create(array( 'status' => 'error', 'message' => 'Could not connect to the search server', ), 500)->setCallback($req->query->get('callback')); } } return $this->render('PackagistWebBundle:Web:search.html.twig', array( 'packages' => $paginator, 'meta' => $metadata, )); } elseif ($req->getRequestFormat() === 'json') { return JsonResponse::create(array( 'error' => 'Missing search query, example: ?q=example' ), 400)->setCallback($req->query->get('callback')); } return $this->render('PackagistWebBundle:Web:search.html.twig');
  29. 29. { $form = $this->createForm(new SearchQueryType, new SearchQuery); $filteredOrderBys = $this->getFilteredOrderedBys($req); $normalizedOrderBys = $this->getNormalizedOrderBys($filteredOrderBys); $this->computeSearchQuery($req, $filteredOrderBys); $typeFilter = $req->query->get('type'); $tagsFilter = $req->query->get('tags'); if ($req->query->has('search_query') || $typeFilter || $tagsFilter) { /** @var $solarium Solarium_Client */ $solarium = $this->get('solarium.client'); $select = $solarium->createSelect(); // configure dismax $dismax = $select->getDisMax(); $dismax->setQueryFields(array('name^4', 'description', 'tags', 'text', 'text_ngram', 'name_split^2')); $dismax->setPhraseFields(array('description')); $dismax->setBoostFunctions(array('log(trendiness)^10')); $dismax->setMinimumMatch(1); $dismax->setQueryParser('edismax'); // filter by type if ($typeFilter) { $filterQueryTerm = sprintf('type:"%s"', $select->getHelper()->escapeTerm($typeFilter)); $filterQuery = $select->createFilterQuery('type')->setQuery($filterQueryTerm); $select->addFilterQuery($filterQuery); } // filter by tags if ($tagsFilter) { $tags = array(); foreach ((array) $tagsFilter as $tag) { $tags[] = $select->getHelper()->escapeTerm($tag); } $filterQueryTerm = sprintf('tags:("%s")', implode('" AND "', $tags)); $filterQuery = $select->createFilterQuery('tags')->setQuery($filterQueryTerm); $select->addFilterQuery($filterQuery); } if (!empty($filteredOrderBys)) { $select->addSorts($normalizedOrderBys); } if ($req->query->has('search_query')) { $form->bind($req); if ($form->isValid()) { $escapedQuery = $select->getHelper()->escapeTerm($form->getData()->getQuery()); $escapedQuery = preg_replace('/(^| )-(S)/', '$1-$2', $escapedQuery); $escapedQuery = preg_replace('/(^| )+(S)/', '$1+$2', $escapedQuery); if ((substr_count($escapedQuery, '"') % 2) == 0) { $escapedQuery = str_replace('"', '"', $escapedQuery); } $select->setQuery($escapedQuery); } } $paginator = new Pagerfanta(new SolariumAdapter($solarium, $select)); $perPage = $req->query->getInt('per_page', 15); if ($perPage <= 0 || $perPage > 100) { if ($req->getRequestFormat() === 'json') { return JsonResponse::create(array( 'status' => 'error', 'message' => 'The optional packages per_page parameter must be an integer between 1 and 100 (default: 15)', ), 400)->setCallback($req->query->get('callback')); } $perPage = max(0, min(100, $perPage)); } $paginator->setMaxPerPage($perPage); $paginator->setCurrentPage($req->query->get('page', 1), false, true); $metadata = array(); foreach ($paginator as $package) { if (is_numeric($package->id)) { $metadata['downloads'][$package->id] = $package->downloads; $metadata['favers'][$package->id] = $package->favers; } } if ($req->getRequestFormat() === 'json') { try { $result = array( 'results' => array(), 'total' => $paginator->getNbResults(), ); } catch (Solarium_Client_HttpException $e) { return JsonResponse::create(array( 'status' => 'error', 'message' => 'Could not connect to the search server', ), 500)->setCallback($req->query->get('callback')); } return JsonResponse::create($result)->setCallback($req->query->get('callback')); } if ($req->isXmlHttpRequest()) { try { return $this->render('PackagistWebBundle:Web:search.html.twig', array( 'packages' => $paginator, 'meta' => $metadata, 'noLayout' => true, )); } catch (Twig_Error_Runtime $e) { if (!$e->getPrevious() instanceof Solarium_Client_HttpException) { throw $e; } return JsonResponse::create(array( 'status' => 'error', 'message' => 'Could not connect to the search server', ), 500)->setCallback($req->query->get('callback')); } } return $this->render('PackagistWebBundle:Web:search.html.twig', array( 'packages' => $paginator, 'meta' => $metadata, )); } elseif ($req->getRequestFormat() === 'json') { return JsonResponse::create(array( 'error' => 'Missing search query, example: ?q=example' ), 400)->setCallback($req->query->get('callback')); } return $this->render('PackagistWebBundle:Web:search.html.twig'); { $form = $this->createForm(new SearchQueryType, new SearchQuery); $filteredOrderBys = $this->getFilteredOrderedBys($req); $normalizedOrderBys = $this->getNormalizedOrderBys($filteredOrderBys); $this->computeSearchQuery($req, $filteredOrderBys); $typeFilter = $req->query->get('type'); $tagsFilter = $req->query->get('tags'); if ($req->query->has('search_query') || $typeFilter || $tagsFilter) { /** @var $solarium Solarium_Client */ $solarium = $this->get('solarium.client'); $select = $solarium->createSelect(); // configure dismax $dismax = $select->getDisMax(); $dismax->setQueryFields(array('name^4', 'description', 'tags', 'text', 'text_ngram', 'name_split^2')); $dismax->setPhraseFields(array('description')); $dismax->setBoostFunctions(array('log(trendiness)^10')); $dismax->setMinimumMatch(1); $dismax->setQueryParser('edismax'); // filter by type if ($typeFilter) { $filterQueryTerm = sprintf('type:"%s"', $select->getHelper()->escapeTerm($typeFilter)); $filterQuery = $select->createFilterQuery('type')->setQuery($filterQueryTerm); $select->addFilterQuery($filterQuery); } // filter by tags if ($tagsFilter) { $tags = array(); foreach ((array) $tagsFilter as $tag) { $tags[] = $select->getHelper()->escapeTerm($tag); } $filterQueryTerm = sprintf('tags:("%s")', implode('" AND "', $tags)); $filterQuery = $select->createFilterQuery('tags')->setQuery($filterQueryTerm); $select->addFilterQuery($filterQuery); } if (!empty($filteredOrderBys)) { $select->addSorts($normalizedOrderBys); } if ($req->query->has('search_query')) { $form->bind($req); if ($form->isValid()) { $escapedQuery = $select->getHelper()->escapeTerm($form->getData()->getQuery()); $escapedQuery = preg_replace('/(^| )-(S)/', '$1-$2', $escapedQuery); $escapedQuery = preg_replace('/(^| )+(S)/', '$1+$2', $escapedQuery); if ((substr_count($escapedQuery, '"') % 2) == 0) { $escapedQuery = str_replace('"', '"', $escapedQuery); } $select->setQuery($escapedQuery); } } $paginator = new Pagerfanta(new SolariumAdapter($solarium, $select)); $perPage = $req->query->getInt('per_page', 15); if ($perPage <= 0 || $perPage > 100) { if ($req->getRequestFormat() === 'json') { return JsonResponse::create(array( 'status' => 'error', 'message' => 'The optional packages per_page parameter must be an integer between 1 and 100 (default: 15)', ), 400)->setCallback($req->query->get('callback')); } $perPage = max(0, min(100, $perPage)); } $paginator->setMaxPerPage($perPage); $paginator->setCurrentPage($req->query->get('page', 1), false, true); $metadata = array(); foreach ($paginator as $package) { if (is_numeric($package->id)) { $metadata['downloads'][$package->id] = $package->downloads; $metadata['favers'][$package->id] = $package->favers; } } if ($req->getRequestFormat() === 'json') { try { $result = array( 'results' => array(), 'total' => $paginator->getNbResults(), ); } catch (Solarium_Client_HttpException $e) { return JsonResponse::create(array( 'status' => 'error', 'message' => 'Could not connect to the search server', ), 500)->setCallback($req->query->get('callback')); } return JsonResponse::create($result)->setCallback($req->query->get('callback')); } if ($req->isXmlHttpRequest()) { try { return $this->render('PackagistWebBundle:Web:search.html.twig', array( 'packages' => $paginator, 'meta' => $metadata, 'noLayout' => true, )); } catch (Twig_Error_Runtime $e) { if (!$e->getPrevious() instanceof Solarium_Client_HttpException) { throw $e; } return JsonResponse::create(array( 'status' => 'error', 'message' => 'Could not connect to the search server', ), 500)->setCallback($req->query->get('callback')); } } return $this->render('PackagistWebBundle:Web:search.html.twig', array( 'packages' => $paginator, 'meta' => $metadata, )); } elseif ($req->getRequestFormat() === 'json') { return JsonResponse::create(array( 'error' => 'Missing search query, example: ?q=example' ), 400)->setCallback($req->query->get('callback')); } return $this->render('PackagistWebBundle:Web:search.html.twig');
  30. 30. CHANGE KICKS IN AFTER CREATION STOPS
  31. 31. Creation → Change Project Lifetime Beginning 3 months 6 months 9 months ... Cost of Creation Cost of Change
  32. 32. Creation → Change Project Lifetime Beginning 3 months 6 months 9 months ... Cost of Creation Cost of Change Enterprise Project
  33. 33. Creation → Change Project Lifetime Beginning 3 months 6 months 9 months ... Cost of Creation Cost of Change Enterprise Project Enterprise Project
  34. 34. FORCE DYNAMICS
  35. 35. CONVENTION-BASED FRAMEWORK
  36. 36. Your first feature Controlled Code
  37. 37. Controlled Code
  38. 38. Your second feature Controlled Code
  39. 39. Controlled Code
  40. 40. Controlled Code
  41. 41. Delegated Code The framework / library code Controlled Code
  42. 42. Delegated Code Controlled Code
  43. 43. Delegated Code Change Controlled Code
  44. 44. Delegated Code Change Controlled Code
  45. 45. Delegated Code Change Controlled Code
  46. 46. Owned Code Delegated Code Change Controlled Code
  47. 47. Owned Code Delegated Code Change Controlled Code
  48. 48. Owned Code Delegated Code Change Controlled Code
  49. 49. Owned Code Delegated Code Controlled Code
  50. 50. Owned Code Controlled Code
  51. 51. – An engineer “Ah, to hell with that!”
  52. 52. Owned Code
  53. 53. Owned Code Change
  54. 54. Owned Code
  55. 55. ENTERPRISE FRAMEWORK
  56. 56. Controlled Code Your first feature
  57. 57. Controlled Code
  58. 58. Controlled Code Your second feature
  59. 59. Controlled Code
  60. 60. Controlled Code Change
  61. 61. Controlled Code Change
  62. 62. Controlled Code Change
  63. 63. – A business person “Why does it always take so long?”
  64. 64. Controlled CodeOwned Code Change
  65. 65. Controlled CodeOwned Code Change
  66. 66. Controlled CodeOwned Code Change
  67. 67. Controlled CodeOwned Code
  68. 68. Owned Code
  69. 69. CONTROL HAS COST
  70. 70. Project Lifetime Beginning 3 months 6 months 9 months ... Cost of Creation Cost of Change Cost of Control
  71. 71. CONTROL IS A LIMITER FOR A COST OF CHANGE
  72. 72. Project Lifetime Beginning 3 months 6 months 9 months ... Cost of Creation Cost of Change Cost of Control
  73. 73. CONTROLLING TOO FEW TOO LATE
  74. 74. Project Lifetime Beginning 3 months 6 months 9 months ... Cost of Creation Cost of Change Cost of Control
  75. 75. CONTROLLING TOO MUCH TOO EARLY
  76. 76. Project Lifetime Beginning 3 months 6 months 9 months ... Cost of Creation Cost of Change Cost of Control
  77. 77. OWNING MORE THANYOU CONTROL IS UNSUSTAINABLE
  78. 78. CONTROLLING EVERYTHING IS EXPENSIVE
  79. 79. MIN-MAXING SOFTWARE COSTS
  80. 80. 4 RULES OF MIN-MAXING 1. Begin from owning nothing ( ) 2. Take ownership reluctantly ( ) 3. Control everything you own ( ) 4. Continuously reassess your control ( )
  81. 81. HEALTHY YOUNG PROJECT Owned Code Delegated Code Controlled Code
  82. 82. UNHEALTHY YOUNG PROJECT Owned Code Delegated Code Controlled Code
  83. 83. UNHEALTHY YOUNG PROJECT Owned Code Delegated Code Controlled Code
  84. 84. HEALTHY MATURE PROJECT Owned Code Delegated Code Controlled Code
  85. 85. UNHEALTHY “MATURE” PROJECT Owned Code Delegated Code Controlled Code
  86. 86. UNHEALTHY “MATURE” PROJECT Owned Code Delegated Code Controlled Code
  87. 87. 1. BEGIN FROM OWNING NOTHING
  88. 88. 2.TAKE OWNERSHIP RELUCTANTLY
  89. 89. 3. CONTROL EVERYTHING YOU OWN
  90. 90. 4. CONTINUOUSLY REASSESSYOUR CONTROL
  91. 91. Project Lifetime Beginning 3 months 6 months 9 months ... Cost of Creation Cost of Change Cost of Control
  92. 92. Control everything you write. Avoid writing anything.
  93. 93. Deliver business impact, not software.
  94. 94. THANK YOU FORYOURTIME! Konstantin K. @everzet

×