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
@everzet
@inviqa
What is this talk about?
Harm that "Laravel Facades"
inflict on not-suspecting
developers.
Bad idea that is Active Record
and Eloquent.
Other framework's superiority
over Laravel.
Killing kittens.
And other subjective and
nonconstructive crap like that ...
... is not in this talk.
Actually in this talk
1. Introducing & making sense of development costs
2. Highlighting the context of tools & practices ...
Context,
the talk
Software Costs
Software Costs
Software Costs
1. Time to write & test code
2. Time to change code & tests
3. Time to refactor code & tests
Software Costs
1. Time to write & test code - Cost of Introduction
2. Time to change code & tests - Cost of Change
3. Time...
Cost of
Introduction
Cost of Introduction
Time it takes to introduce new,
naturally independent
application logic.
Attributes
— Has direct correlation to
business value
— Has direct correlation to LOC
— Relatively easy to optimise by
gen...
Dynamics
— Visible from the outset
— Loses relevancy over the
project lifetime
— Stable across projects
Dynamics
— Visible from the outset
— Loses relevancy over the
project lifetime
— Stable across projects
Cost of Introduction is relatively
easy to optimise.
Optimising for CoI:
Convenience Layer
Service Locator
// --- Explicit Dependency
public function __construct(Cache $cache) {
$this->cache = $cache;
}
public fun...
Base Class
// --- Base Controller
class MyController extends Controller
{
public function indexAction()
{
$homepageUrl = $...
Optimising for CoI:
Generalisation
Active Record
// --- Custom Mapping
class DbalCustomerRepository implements CustomerRepository
{
public function findCusto...
Event Dispatcher
// --- Event Subscriber
interface MyListener {
public function emailWasSent($email, $text);
}
// ...
publ...
Dependency Injection Container
// --- Dependency Inversion Principle
$contoller = new MyController(
new Router(),
new Cach...
No matter what you think,
optimising for CoI (Cost of
Introduction) is not inherently a
bad thing.
A Cut-Off of the product
If the product life is short enough
to not encounter loss of CoI
relevancy, then the CoI is the
only cost worth optimising...
Convenience based projects
either die a hero or live long
enough to see themselves
become the villain.
Cost of
Change
Cost of Change
Time it takes to adapt the
existing application logic to new
business realities.
Attributes
— Has direct correlation to
business value
— Has no direct correlation to
LOC
— Affected by generalisation
Dynamics
— Invisible from the outset
— Gains relevancy during the
project lifetime
— Exponentially increases over
time
Cost of Change increases
exponentially over time.
public function searchAction(Request $req)
{
$form = $this->createForm(new SearchQueryType, new SearchQuery);
$normalizedO...
public function searchAction(Request $req)
{
$form = $this->createForm(new SearchQueryType, new SearchQuery);
$this->compu...
public function searchAction(Request $req)
{
$form = $this->createForm(new SearchQueryType, new SearchQuery);
$filteredOrd...
Exponential increase of Cost of
Change is not inherently a
problem of every product.
A Cut-Off of the product
If the product life is long enough
to encounter exponential growth
of CoC, then the CoC is the cost
worth optimising for.
If you want to change the world
with your product, then the
change is the primary thing your
product must prepare for.
Optimising for Cost of
Introduction in most cases has a
negative effect on the Cost of
Change curve.
That's why some engineers try to
increase the Cost of Introduction
in attempt to affect the Cost of
Change curve.
Cost of Introduction and Change
Increasing Cost of Introduction
Cost of Change with increased Cost of Introduction
Upfront Design (aka Waterfall)
Illusion that one can control
cost of change by applying
enough analysis upfront.
Upfront Design fails to achieve
long-lasting effect because both
rate and nature of change for
arbitrary domain is
unpredi...
Cost of
Ownership
Cost of Ownership
Time it takes to maintain the
owned application logic to
support its ongoing change.
Attributes
— Intermediate between Cost of
Introduction & Cost of
Change
— Has no direct correlation to
business value
— Ha...
Dynamics
— Always invisible
— Always relevant
— Stable over time, but adds up
Cost of Ownership is the cost you
pay for the right to change a
particular part (module, class,
method) of application
con...
Testing
Unit testing
Refactoring
Introducing Cost of Ownership
allows you to balance two other
costs.
Cost of Introduction and Change
Cost of Introduction and Ownership
Cost of Ownership effect on Cost of Change curve
Emergent Design
Usual result of ongoing
ownership.
Cost of Ownership of everything
Cost of Ownership of everything
Owning everything fails to
achieve ever-increasing benefits,
because you rarely need to
change the entire system.
End-to-end testing
is owning everything
Cost of Ownership of the wrong thing
Ownership wouldn't help if
you're owning the wrong thing.
Exclusive end-to-end testing
is owning the wrong thing
You do want to own everything
worth owning.
But you don't know what's worth
owning at the beginning of the
project.
Software Costs recap
1. Cost of Introduction - Linear. Relevant at the
beginning. Very easy to optimise for.
2. Cost of Ch...
Gaming
Software Costs
Own only the logic you need to
change.
Write only the logic you need to
own.
Own everything you write.
Own everything you write.
Try to not write anything.
Own everything you write.
Try to not write anything.
Reuse everything else.
1. Document the need
2. Spike - Experiment with
tools available
3. Document changes &
constraints
4. Stabilise - Claim
ownership when the thing
grows outside of tool
boundaries
5. Isolate Religiously
Steps
1. Document the need
2. Spike
3. Document changes & constraints
4. Stabilise
5. Isolate Religiously
Unit testing
is owning
Refactoring
is owning
Test Driven Development
is an ownership technique
Gaming Software Costs
1. Document
2. Spike & Stabilise
3. Use TDD for stabilisation
CC credits
- money.jpg - https://flic.kr/p/s6895e
- time.jpg - https://flic.kr/p/4tNrxq
- cheating.jpg - https://flic.kr/p...
Thank you!
Questions?Please, leave feedback: https://joind.in/15022
Min-Maxing Software Costs - Laracon EU 2015
Upcoming SlideShare
Loading in …5
×

Min-Maxing Software Costs - Laracon EU 2015

13,503 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. In this talk you will learn how to see, understand and game some of these forces in your favour.

Published in: Technology

Min-Maxing Software Costs - Laracon EU 2015

  1. 1. Min-Maxing Software Costs
  2. 2. @everzet
  3. 3. @inviqa
  4. 4. What is this talk about?
  5. 5. Harm that "Laravel Facades" inflict on not-suspecting developers.
  6. 6. Bad idea that is Active Record and Eloquent.
  7. 7. Other framework's superiority over Laravel.
  8. 8. Killing kittens.
  9. 9. And other subjective and nonconstructive crap like that ...
  10. 10. ... is not in this talk.
  11. 11. Actually in this talk 1. Introducing & making sense of development costs 2. Highlighting the context of tools & practices we use 3. Years of observation & experience, not data collection and analysis
  12. 12. Context, the talk
  13. 13. Software Costs
  14. 14. Software Costs
  15. 15. Software Costs 1. Time to write & test code 2. Time to change code & tests 3. Time to refactor code & tests
  16. 16. Software Costs 1. Time to write & test code - Cost of Introduction 2. Time to change code & tests - Cost of Change 3. Time to refactor code & tests - Cost of Ownership
  17. 17. Cost of Introduction
  18. 18. Cost of Introduction Time it takes to introduce new, naturally independent application logic.
  19. 19. Attributes — Has direct correlation to business value — Has direct correlation to LOC — Relatively easy to optimise by generalisation
  20. 20. Dynamics — Visible from the outset — Loses relevancy over the project lifetime — Stable across projects
  21. 21. Dynamics — Visible from the outset — Loses relevancy over the project lifetime — Stable across projects
  22. 22. Cost of Introduction is relatively easy to optimise.
  23. 23. Optimising for CoI: Convenience Layer
  24. 24. Service Locator // --- Explicit Dependency public function __construct(Cache $cache) { $this->cache = $cache; } public function index() { $photos = $this->cache->get('photos'); // ... } // --- "Laravel Facade" public function index() { $photos = Cache::get('photos'); // ... }
  25. 25. Base Class // --- Base Controller class MyController extends Controller { public function indexAction() { $homepageUrl = $this->generateUrl('homepage'); // ... } }
  26. 26. Optimising for CoI: Generalisation
  27. 27. Active Record // --- Custom Mapping class DbalCustomerRepository implements CustomerRepository { public function findCustomerWithName($name) { // ... } } // --- Eloquent use IlluminateDatabaseEloquentModel; class Customer extends Model { // ... }
  28. 28. Event Dispatcher // --- Event Subscriber interface MyListener { public function emailWasSent($email, $text); } // ... public function sendEmail() { // ... $this->myListenerInstance->emailWasSent($email, $text); } // --- Event Dispatcher $eventDispatcher->dispatch('email.sent', new Event($email, $text));
  29. 29. Dependency Injection Container // --- Dependency Inversion Principle $contoller = new MyController( new Router(), new Cache(new Configuration()) ); // --- Dependency Injection Container $controller = $container->get('controller.my');
  30. 30. No matter what you think, optimising for CoI (Cost of Introduction) is not inherently a bad thing.
  31. 31. A Cut-Off of the product
  32. 32. If the product life is short enough to not encounter loss of CoI relevancy, then the CoI is the only cost worth optimising for.
  33. 33. Convenience based projects either die a hero or live long enough to see themselves become the villain.
  34. 34. Cost of Change
  35. 35. Cost of Change Time it takes to adapt the existing application logic to new business realities.
  36. 36. Attributes — Has direct correlation to business value — Has no direct correlation to LOC — Affected by generalisation
  37. 37. Dynamics — Invisible from the outset — Gains relevancy during the project lifetime — Exponentially increases over time
  38. 38. Cost of Change increases exponentially over time.
  39. 39. 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'); }
  40. 40. 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'); }
  41. 41. public function searchAction(Request $req) { $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'); }
  42. 42. Exponential increase of Cost of Change is not inherently a problem of every product.
  43. 43. A Cut-Off of the product
  44. 44. If the product life is long enough to encounter exponential growth of CoC, then the CoC is the cost worth optimising for.
  45. 45. If you want to change the world with your product, then the change is the primary thing your product must prepare for.
  46. 46. Optimising for Cost of Introduction in most cases has a negative effect on the Cost of Change curve.
  47. 47. That's why some engineers try to increase the Cost of Introduction in attempt to affect the Cost of Change curve.
  48. 48. Cost of Introduction and Change
  49. 49. Increasing Cost of Introduction
  50. 50. Cost of Change with increased Cost of Introduction
  51. 51. Upfront Design (aka Waterfall) Illusion that one can control cost of change by applying enough analysis upfront.
  52. 52. Upfront Design fails to achieve long-lasting effect because both rate and nature of change for arbitrary domain is unpredictable.
  53. 53. Cost of Ownership
  54. 54. Cost of Ownership Time it takes to maintain the owned application logic to support its ongoing change.
  55. 55. Attributes — Intermediate between Cost of Introduction & Cost of Change — Has no direct correlation to business value — Has direct correlation to LOC
  56. 56. Dynamics — Always invisible — Always relevant — Stable over time, but adds up
  57. 57. Cost of Ownership is the cost you pay for the right to change a particular part (module, class, method) of application continuosly and sustainably.
  58. 58. Testing
  59. 59. Unit testing
  60. 60. Refactoring
  61. 61. Introducing Cost of Ownership allows you to balance two other costs.
  62. 62. Cost of Introduction and Change
  63. 63. Cost of Introduction and Ownership
  64. 64. Cost of Ownership effect on Cost of Change curve
  65. 65. Emergent Design Usual result of ongoing ownership.
  66. 66. Cost of Ownership of everything
  67. 67. Cost of Ownership of everything
  68. 68. Owning everything fails to achieve ever-increasing benefits, because you rarely need to change the entire system.
  69. 69. End-to-end testing is owning everything
  70. 70. Cost of Ownership of the wrong thing
  71. 71. Ownership wouldn't help if you're owning the wrong thing.
  72. 72. Exclusive end-to-end testing is owning the wrong thing
  73. 73. You do want to own everything worth owning.
  74. 74. But you don't know what's worth owning at the beginning of the project.
  75. 75. Software Costs recap 1. Cost of Introduction - Linear. Relevant at the beginning. Very easy to optimise for. 2. Cost of Change - Exponential. Relevant everywhere except the beginning. Hard to optimise for. 3. Cost of Ownership - Linear. Relevant throughout. Owning the wrong thing is bad.
  76. 76. Gaming Software Costs
  77. 77. Own only the logic you need to change.
  78. 78. Write only the logic you need to own.
  79. 79. Own everything you write.
  80. 80. Own everything you write. Try to not write anything.
  81. 81. Own everything you write. Try to not write anything. Reuse everything else.
  82. 82. 1. Document the need
  83. 83. 2. Spike - Experiment with tools available
  84. 84. 3. Document changes & constraints
  85. 85. 4. Stabilise - Claim ownership when the thing grows outside of tool boundaries
  86. 86. 5. Isolate Religiously
  87. 87. Steps 1. Document the need 2. Spike 3. Document changes & constraints 4. Stabilise 5. Isolate Religiously
  88. 88. Unit testing is owning
  89. 89. Refactoring is owning
  90. 90. Test Driven Development is an ownership technique
  91. 91. Gaming Software Costs 1. Document 2. Spike & Stabilise 3. Use TDD for stabilisation
  92. 92. CC credits - money.jpg - https://flic.kr/p/s6895e - time.jpg - https://flic.kr/p/4tNrxq - cheating.jpg - https://flic.kr/p/7FCr59 - developing.jpg - https://flic.kr/p/bHLu96 - change.jpg - https://flic.kr/p/6PtfXL - ownership.jpg - https://flic.kr/p/bwJSRV - pair_programming.jpg - https://flic.kr/p/QNdeB - unit_tests.jpg - https://flic.kr/p/7KEnN7 - testing.jpg - https://flic.kr/p/tpCxq - test_driven.jpg - https://flic.kr/p/7Lx9Kk - refactoring.jpg - https://flic.kr/p/dUmmRN - context.jpg - https://flic.kr/p/93iAmM
  93. 93. Thank you!
  94. 94. Questions?Please, leave feedback: https://joind.in/15022

×