SlideShare a Scribd company logo
MIN-MAXING
Software Costs
Konstantin K.
@everzet
TECHNICAL
DEBT
Without
frameworks
Simple
frameworks
Enterprise
frameworks
IS MESS
PART OF
A COURSE?
MIN-MAXING
SOFTWARE COSTS
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
SOFTWARE COSTS
1. Cost of Creation
2. Cost of Change
3. Cost of Control
COST OF CREATION
Project Lifetime
Beginning 3 months 6 months 9 months ...
Cost of Creation
CONVENTIONS
OPTIMISE
FOR CREATION
$ bin/rails generate controller welcome index
from django.contrib import admin
from . import models
admin.site.register(models.Article)
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 {
...
}
CREATION
IS LIMITED BY
THE LIFE SPAN
Creation
→
Change
Project Lifetime
Beginning 3 months 6 months 9 months ...
Cost of Creation
Creation
→
Change
Project Lifetime
Beginning 3 months 6 months 9 months ...
Cost of Creation
Pure observation &
personal experience
Creation
→
Change
Project Lifetime
Beginning 3 months 6 months 9 months ...
Cost of Creation
Conventional Project
Creation
→
Change
Project Lifetime
Beginning 3 months 6 months 9 months ...
Cost of Creation
Conventional Project
Conventional Project
Convention-based projects either
die a hero or live long enough to
see themselves become the villain.
COST OF CHANGE
Project Lifetime
Beginning 3 months 6 months 9 months ...
Cost of Creation Cost of Change
THE SEARCH
FUNCTION
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');
}
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');
}
HOW LONG WOULD
IT TAKETO ADD TAGS
SUPPORT?
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');
}
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');
}
{
$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');
{
$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');
CHANGE
KICKS IN AFTER
CREATION STOPS
Creation
→
Change
Project Lifetime
Beginning 3 months 6 months 9 months ...
Cost of Creation Cost of Change
Creation
→
Change
Project Lifetime
Beginning 3 months 6 months 9 months ...
Cost of Creation Cost of Change
Enterprise Project
Creation
→
Change
Project Lifetime
Beginning 3 months 6 months 9 months ...
Cost of Creation Cost of Change
Enterprise Project
Enterprise Project
FORCE DYNAMICS
CONVENTION-BASED
FRAMEWORK
Your first feature
Controlled Code
Controlled Code
Your second feature
Controlled Code
Controlled Code
Controlled Code
Delegated Code
The framework / library code
Controlled Code
Delegated Code Controlled Code
Delegated Code
Change
Controlled Code
Delegated Code
Change
Controlled Code
Delegated Code
Change
Controlled Code
Owned Code Delegated Code
Change
Controlled Code
Owned Code Delegated Code
Change
Controlled Code
Owned Code Delegated Code
Change
Controlled Code
Owned Code Delegated Code Controlled Code
Owned Code Controlled Code
– An engineer
“Ah, to hell with that!”
Owned Code
Owned Code
Change
Owned Code
ENTERPRISE
FRAMEWORK
Controlled Code
Your first feature
Controlled Code
Controlled Code
Your second feature
Controlled Code
Controlled Code
Change
Controlled Code
Change
Controlled Code
Change
– A business person
“Why does it always take so long?”
Controlled CodeOwned Code
Change
Controlled CodeOwned Code
Change
Controlled CodeOwned Code
Change
Controlled CodeOwned Code
Owned Code
CONTROL HAS COST
Project Lifetime
Beginning 3 months 6 months 9 months ...
Cost of Creation Cost of Change Cost of Control
CONTROL
IS A LIMITER FOR A
COST OF CHANGE
Project Lifetime
Beginning 3 months 6 months 9 months ...
Cost of Creation Cost of Change Cost of Control
CONTROLLING
TOO FEW
TOO LATE
Project Lifetime
Beginning 3 months 6 months 9 months ...
Cost of Creation Cost of Change Cost of Control
CONTROLLING
TOO MUCH
TOO EARLY
Project Lifetime
Beginning 3 months 6 months 9 months ...
Cost of Creation Cost of Change Cost of Control
OWNING MORE
THANYOU CONTROL
IS UNSUSTAINABLE
CONTROLLING
EVERYTHING
IS EXPENSIVE
MIN-MAXING
SOFTWARE COSTS
4 RULES OF MIN-MAXING
1. Begin from owning nothing ( )
2. Take ownership reluctantly ( )
3. Control everything you own ( )
4. Continuously reassess your control ( )
HEALTHY YOUNG PROJECT
Owned Code Delegated Code Controlled Code
UNHEALTHY YOUNG PROJECT
Owned Code Delegated Code Controlled Code
UNHEALTHY YOUNG PROJECT
Owned Code Delegated Code Controlled Code
HEALTHY MATURE PROJECT
Owned Code Delegated Code Controlled Code
UNHEALTHY “MATURE” PROJECT
Owned Code Delegated Code Controlled Code
UNHEALTHY “MATURE” PROJECT
Owned Code Delegated Code Controlled Code
1. BEGIN
FROM OWNING
NOTHING
2.TAKE
OWNERSHIP
RELUCTANTLY
3. CONTROL
EVERYTHING
YOU OWN
4. CONTINUOUSLY
REASSESSYOUR
CONTROL
Project Lifetime
Beginning 3 months 6 months 9 months ...
Cost of Creation Cost of Change Cost of Control
Control everything you write.
Avoid writing anything.
Deliver business impact,
not software.
THANK YOU
FORYOURTIME!
Konstantin K.
@everzet

More Related Content

What's hot

Decoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDDDecoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDD
Aleix Vergés
 
Design Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et PimpleDesign Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et Pimple
Hugo Hamon
 
Symfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il clienteSymfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il cliente
Leonardo Proietti
 
Silex meets SOAP & REST
Silex meets SOAP & RESTSilex meets SOAP & REST
Silex meets SOAP & REST
Hugo Hamon
 
Models and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and HobgoblinsModels and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and Hobgoblins
Ross Tuck
 
Mocking Demystified
Mocking DemystifiedMocking Demystified
Mocking Demystified
Marcello Duarte
 
Database Design Patterns
Database Design PatternsDatabase Design Patterns
Database Design Patterns
Hugo Hamon
 
Crafting beautiful software
Crafting beautiful softwareCrafting beautiful software
Crafting beautiful software
Jorn Oomen
 
The History of PHPersistence
The History of PHPersistenceThe History of PHPersistence
The History of PHPersistence
Hugo Hamon
 
Rich Model And Layered Architecture in SF2 Application
Rich Model And Layered Architecture in SF2 ApplicationRich Model And Layered Architecture in SF2 Application
Rich Model And Layered Architecture in SF2 Application
Kirill Chebunin
 
PHPCon 2016: PHP7 by Witek Adamus / XSolve
PHPCon 2016: PHP7 by Witek Adamus / XSolvePHPCon 2016: PHP7 by Witek Adamus / XSolve
PHPCon 2016: PHP7 by Witek Adamus / XSolve
XSolve
 
Command Bus To Awesome Town
Command Bus To Awesome TownCommand Bus To Awesome Town
Command Bus To Awesome Town
Ross Tuck
 
Introduction to Zend Framework web services
Introduction to Zend Framework web servicesIntroduction to Zend Framework web services
Introduction to Zend Framework web services
Michelangelo van Dam
 
Forget about index.php and build you applications around HTTP!
Forget about index.php and build you applications around HTTP!Forget about index.php and build you applications around HTTP!
Forget about index.php and build you applications around HTTP!
Kacper Gunia
 
PHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4DevelopersPHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4Developers
Kacper Gunia
 
CQRS and Event Sourcing in a Symfony application
CQRS and Event Sourcing in a Symfony applicationCQRS and Event Sourcing in a Symfony application
CQRS and Event Sourcing in a Symfony application
Samuel ROZE
 
Advanced php testing in action
Advanced php testing in actionAdvanced php testing in action
Advanced php testing in action
Jace Ju
 
Forget about Index.php and build you applications around HTTP - PHPers Cracow
Forget about Index.php and build you applications around HTTP - PHPers CracowForget about Index.php and build you applications around HTTP - PHPers Cracow
Forget about Index.php and build you applications around HTTP - PHPers Cracow
Kacper Gunia
 
Adding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsAdding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy Applications
Sam Hennessy
 
November Camp - Spec BDD with PHPSpec 2
November Camp - Spec BDD with PHPSpec 2November Camp - Spec BDD with PHPSpec 2
November Camp - Spec BDD with PHPSpec 2
Kacper Gunia
 

What's hot (20)

Decoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDDDecoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDD
 
Design Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et PimpleDesign Patterns avec PHP 5.3, Symfony et Pimple
Design Patterns avec PHP 5.3, Symfony et Pimple
 
Symfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il clienteSymfony2, creare bundle e valore per il cliente
Symfony2, creare bundle e valore per il cliente
 
Silex meets SOAP & REST
Silex meets SOAP & RESTSilex meets SOAP & REST
Silex meets SOAP & REST
 
Models and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and HobgoblinsModels and Service Layers, Hemoglobin and Hobgoblins
Models and Service Layers, Hemoglobin and Hobgoblins
 
Mocking Demystified
Mocking DemystifiedMocking Demystified
Mocking Demystified
 
Database Design Patterns
Database Design PatternsDatabase Design Patterns
Database Design Patterns
 
Crafting beautiful software
Crafting beautiful softwareCrafting beautiful software
Crafting beautiful software
 
The History of PHPersistence
The History of PHPersistenceThe History of PHPersistence
The History of PHPersistence
 
Rich Model And Layered Architecture in SF2 Application
Rich Model And Layered Architecture in SF2 ApplicationRich Model And Layered Architecture in SF2 Application
Rich Model And Layered Architecture in SF2 Application
 
PHPCon 2016: PHP7 by Witek Adamus / XSolve
PHPCon 2016: PHP7 by Witek Adamus / XSolvePHPCon 2016: PHP7 by Witek Adamus / XSolve
PHPCon 2016: PHP7 by Witek Adamus / XSolve
 
Command Bus To Awesome Town
Command Bus To Awesome TownCommand Bus To Awesome Town
Command Bus To Awesome Town
 
Introduction to Zend Framework web services
Introduction to Zend Framework web servicesIntroduction to Zend Framework web services
Introduction to Zend Framework web services
 
Forget about index.php and build you applications around HTTP!
Forget about index.php and build you applications around HTTP!Forget about index.php and build you applications around HTTP!
Forget about index.php and build you applications around HTTP!
 
PHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4DevelopersPHPSpec - the only Design Tool you need - 4Developers
PHPSpec - the only Design Tool you need - 4Developers
 
CQRS and Event Sourcing in a Symfony application
CQRS and Event Sourcing in a Symfony applicationCQRS and Event Sourcing in a Symfony application
CQRS and Event Sourcing in a Symfony application
 
Advanced php testing in action
Advanced php testing in actionAdvanced php testing in action
Advanced php testing in action
 
Forget about Index.php and build you applications around HTTP - PHPers Cracow
Forget about Index.php and build you applications around HTTP - PHPers CracowForget about Index.php and build you applications around HTTP - PHPers Cracow
Forget about Index.php and build you applications around HTTP - PHPers Cracow
 
Adding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy ApplicationsAdding Dependency Injection to Legacy Applications
Adding Dependency Injection to Legacy Applications
 
November Camp - Spec BDD with PHPSpec 2
November Camp - Spec BDD with PHPSpec 2November Camp - Spec BDD with PHPSpec 2
November Camp - Spec BDD with PHPSpec 2
 

Viewers also liked

Moving away from legacy code with BDD
Moving away from legacy code with BDDMoving away from legacy code with BDD
Moving away from legacy code with BDD
Konstantin Kudryashov
 
Modern Agile Project Toolbox
Modern Agile Project ToolboxModern Agile Project Toolbox
Modern Agile Project Toolbox
Konstantin Kudryashov
 
Moving away from legacy code (AgileCymru)
Moving away from legacy code  (AgileCymru)Moving away from legacy code  (AgileCymru)
Moving away from legacy code (AgileCymru)
Konstantin Kudryashov
 
Taking back BDD
Taking back BDDTaking back BDD
Taking back BDD
Konstantin Kudryashov
 
BDD by example
BDD by exampleBDD by example
BDD by example
Xavier Fornés Arrabal
 
Enabling agile devliery through enabling BDD in PHP projects
Enabling agile devliery through enabling BDD in PHP projectsEnabling agile devliery through enabling BDD in PHP projects
Enabling agile devliery through enabling BDD in PHP projects
Konstantin Kudryashov
 
Modern Project Toolbox
Modern Project ToolboxModern Project Toolbox
Modern Project Toolbox
Konstantin Kudryashov
 
Being effective with legacy projects
Being effective with legacy projectsBeing effective with legacy projects
Being effective with legacy projects
Konstantin Kudryashov
 
BDD в PHP с Behat и Mink
BDD в PHP с Behat и MinkBDD в PHP с Behat и Mink
BDD в PHP с Behat и Mink
Konstantin Kudryashov
 
Bridging The Communication Gap, Fast
Bridging The Communication Gap, Fast Bridging The Communication Gap, Fast
Bridging The Communication Gap, Fast
Konstantin Kudryashov
 
Moving away from legacy code with BDD
Moving away from legacy code with BDDMoving away from legacy code with BDD
Moving away from legacy code with BDD
Konstantin Kudryashov
 

Viewers also liked (12)

Moving away from legacy code with BDD
Moving away from legacy code with BDDMoving away from legacy code with BDD
Moving away from legacy code with BDD
 
Modern Agile Project Toolbox
Modern Agile Project ToolboxModern Agile Project Toolbox
Modern Agile Project Toolbox
 
Moving away from legacy code (AgileCymru)
Moving away from legacy code  (AgileCymru)Moving away from legacy code  (AgileCymru)
Moving away from legacy code (AgileCymru)
 
Taking back BDD
Taking back BDDTaking back BDD
Taking back BDD
 
BDD by example
BDD by exampleBDD by example
BDD by example
 
Enabling agile devliery through enabling BDD in PHP projects
Enabling agile devliery through enabling BDD in PHP projectsEnabling agile devliery through enabling BDD in PHP projects
Enabling agile devliery through enabling BDD in PHP projects
 
Modern Project Toolbox
Modern Project ToolboxModern Project Toolbox
Modern Project Toolbox
 
Being effective with legacy projects
Being effective with legacy projectsBeing effective with legacy projects
Being effective with legacy projects
 
BDD в PHP с Behat и Mink
BDD в PHP с Behat и MinkBDD в PHP с Behat и Mink
BDD в PHP с Behat и Mink
 
BDD для PHP проектов
BDD для PHP проектовBDD для PHP проектов
BDD для PHP проектов
 
Bridging The Communication Gap, Fast
Bridging The Communication Gap, Fast Bridging The Communication Gap, Fast
Bridging The Communication Gap, Fast
 
Moving away from legacy code with BDD
Moving away from legacy code with BDDMoving away from legacy code with BDD
Moving away from legacy code with BDD
 

Similar to Min-Maxing Software Costs

Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011
 Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011 Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011
Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011
Alessandro Nadalin
 
PHPUnit elevato alla Symfony2
PHPUnit elevato alla Symfony2PHPUnit elevato alla Symfony2
PHPUnit elevato alla Symfony2
eugenio pombi
 
Zend framework service
Zend framework serviceZend framework service
Zend framework service
Michelangelo van Dam
 
Zend framework service
Zend framework serviceZend framework service
Zend framework service
Michelangelo van Dam
 
Understanding backbonejs
Understanding backbonejsUnderstanding backbonejs
Understanding backbonejs
Nick Lee
 
Bag Of Tricks From Iusethis
Bag Of Tricks From IusethisBag Of Tricks From Iusethis
Bag Of Tricks From Iusethis
Marcus Ramberg
 
Virtual Madness @ Etsy
Virtual Madness @ EtsyVirtual Madness @ Etsy
Virtual Madness @ Etsy
Nishan Subedi
 
前端MVC 豆瓣说
前端MVC 豆瓣说前端MVC 豆瓣说
前端MVC 豆瓣说Ting Lv
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For Beginners
Jonathan Wage
 
Why is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenariosWhy is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenarios
Divante
 
Unit testing with zend framework tek11
Unit testing with zend framework tek11Unit testing with zend framework tek11
Unit testing with zend framework tek11
Michelangelo van Dam
 
Unit testing zend framework apps
Unit testing zend framework appsUnit testing zend framework apps
Unit testing zend framework apps
Michelangelo van Dam
 
Things I Believe Now That I'm Old
Things I Believe Now That I'm OldThings I Believe Now That I'm Old
Things I Believe Now That I'm Old
Ross Tuck
 
Tidy Up Your Code
Tidy Up Your CodeTidy Up Your Code
Tidy Up Your Code
Abbas Ali
 
Refactoring using Codeception
Refactoring using CodeceptionRefactoring using Codeception
Refactoring using Codeception
Jeroen van Dijk
 
(PHPers Wrocław #5) How to write valuable unit test?
(PHPers Wrocław #5) How to write valuable unit test?(PHPers Wrocław #5) How to write valuable unit test?
(PHPers Wrocław #5) How to write valuable unit test?
RST Software Masters
 
Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)Fabien Potencier
 
Unit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBeneluxUnit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBenelux
Michelangelo van Dam
 
50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 Minutes50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 Minutes
Azim Kurt
 

Similar to Min-Maxing Software Costs (20)

Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011
 Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011 Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011
Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011
 
Bacbkone js
Bacbkone jsBacbkone js
Bacbkone js
 
PHPUnit elevato alla Symfony2
PHPUnit elevato alla Symfony2PHPUnit elevato alla Symfony2
PHPUnit elevato alla Symfony2
 
Zend framework service
Zend framework serviceZend framework service
Zend framework service
 
Zend framework service
Zend framework serviceZend framework service
Zend framework service
 
Understanding backbonejs
Understanding backbonejsUnderstanding backbonejs
Understanding backbonejs
 
Bag Of Tricks From Iusethis
Bag Of Tricks From IusethisBag Of Tricks From Iusethis
Bag Of Tricks From Iusethis
 
Virtual Madness @ Etsy
Virtual Madness @ EtsyVirtual Madness @ Etsy
Virtual Madness @ Etsy
 
前端MVC 豆瓣说
前端MVC 豆瓣说前端MVC 豆瓣说
前端MVC 豆瓣说
 
Doctrine For Beginners
Doctrine For BeginnersDoctrine For Beginners
Doctrine For Beginners
 
Why is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenariosWhy is crud a bad idea - focus on real scenarios
Why is crud a bad idea - focus on real scenarios
 
Unit testing with zend framework tek11
Unit testing with zend framework tek11Unit testing with zend framework tek11
Unit testing with zend framework tek11
 
Unit testing zend framework apps
Unit testing zend framework appsUnit testing zend framework apps
Unit testing zend framework apps
 
Things I Believe Now That I'm Old
Things I Believe Now That I'm OldThings I Believe Now That I'm Old
Things I Believe Now That I'm Old
 
Tidy Up Your Code
Tidy Up Your CodeTidy Up Your Code
Tidy Up Your Code
 
Refactoring using Codeception
Refactoring using CodeceptionRefactoring using Codeception
Refactoring using Codeception
 
(PHPers Wrocław #5) How to write valuable unit test?
(PHPers Wrocław #5) How to write valuable unit test?(PHPers Wrocław #5) How to write valuable unit test?
(PHPers Wrocław #5) How to write valuable unit test?
 
Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)Be RESTful (Symfony Camp 2008)
Be RESTful (Symfony Camp 2008)
 
Unit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBeneluxUnit testing with zend framework PHPBenelux
Unit testing with zend framework PHPBenelux
 
50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 Minutes50 Laravel Tricks in 50 Minutes
50 Laravel Tricks in 50 Minutes
 

Recently uploaded

PHP Frameworks: I want to break free (IPC Berlin 2024)
PHP Frameworks: I want to break free (IPC Berlin 2024)PHP Frameworks: I want to break free (IPC Berlin 2024)
PHP Frameworks: I want to break free (IPC Berlin 2024)
Ralf Eggert
 
FIDO Alliance Osaka Seminar: Passkeys at Amazon.pdf
FIDO Alliance Osaka Seminar: Passkeys at Amazon.pdfFIDO Alliance Osaka Seminar: Passkeys at Amazon.pdf
FIDO Alliance Osaka Seminar: Passkeys at Amazon.pdf
FIDO Alliance
 
Mission to Decommission: Importance of Decommissioning Products to Increase E...
Mission to Decommission: Importance of Decommissioning Products to Increase E...Mission to Decommission: Importance of Decommissioning Products to Increase E...
Mission to Decommission: Importance of Decommissioning Products to Increase E...
Product School
 
Epistemic Interaction - tuning interfaces to provide information for AI support
Epistemic Interaction - tuning interfaces to provide information for AI supportEpistemic Interaction - tuning interfaces to provide information for AI support
Epistemic Interaction - tuning interfaces to provide information for AI support
Alan Dix
 
Designing Great Products: The Power of Design and Leadership by Chief Designe...
Designing Great Products: The Power of Design and Leadership by Chief Designe...Designing Great Products: The Power of Design and Leadership by Chief Designe...
Designing Great Products: The Power of Design and Leadership by Chief Designe...
Product School
 
Assuring Contact Center Experiences for Your Customers With ThousandEyes
Assuring Contact Center Experiences for Your Customers With ThousandEyesAssuring Contact Center Experiences for Your Customers With ThousandEyes
Assuring Contact Center Experiences for Your Customers With ThousandEyes
ThousandEyes
 
Transcript: Selling digital books in 2024: Insights from industry leaders - T...
Transcript: Selling digital books in 2024: Insights from industry leaders - T...Transcript: Selling digital books in 2024: Insights from industry leaders - T...
Transcript: Selling digital books in 2024: Insights from industry leaders - T...
BookNet Canada
 
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdfFIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance
 
Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024
Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024
Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024
Tobias Schneck
 
Unsubscribed: Combat Subscription Fatigue With a Membership Mentality by Head...
Unsubscribed: Combat Subscription Fatigue With a Membership Mentality by Head...Unsubscribed: Combat Subscription Fatigue With a Membership Mentality by Head...
Unsubscribed: Combat Subscription Fatigue With a Membership Mentality by Head...
Product School
 
Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...
Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...
Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...
Thierry Lestable
 
The Future of Platform Engineering
The Future of Platform EngineeringThe Future of Platform Engineering
The Future of Platform Engineering
Jemma Hussein Allen
 
The Art of the Pitch: WordPress Relationships and Sales
The Art of the Pitch: WordPress Relationships and SalesThe Art of the Pitch: WordPress Relationships and Sales
The Art of the Pitch: WordPress Relationships and Sales
Laura Byrne
 
AI for Every Business: Unlocking Your Product's Universal Potential by VP of ...
AI for Every Business: Unlocking Your Product's Universal Potential by VP of ...AI for Every Business: Unlocking Your Product's Universal Potential by VP of ...
AI for Every Business: Unlocking Your Product's Universal Potential by VP of ...
Product School
 
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdf
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdfSmart TV Buyer Insights Survey 2024 by 91mobiles.pdf
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdf
91mobiles
 
Knowledge engineering: from people to machines and back
Knowledge engineering: from people to machines and backKnowledge engineering: from people to machines and back
Knowledge engineering: from people to machines and back
Elena Simperl
 
From Daily Decisions to Bottom Line: Connecting Product Work to Revenue by VP...
From Daily Decisions to Bottom Line: Connecting Product Work to Revenue by VP...From Daily Decisions to Bottom Line: Connecting Product Work to Revenue by VP...
From Daily Decisions to Bottom Line: Connecting Product Work to Revenue by VP...
Product School
 
Connector Corner: Automate dynamic content and events by pushing a button
Connector Corner: Automate dynamic content and events by pushing a buttonConnector Corner: Automate dynamic content and events by pushing a button
Connector Corner: Automate dynamic content and events by pushing a button
DianaGray10
 
Key Trends Shaping the Future of Infrastructure.pdf
Key Trends Shaping the Future of Infrastructure.pdfKey Trends Shaping the Future of Infrastructure.pdf
Key Trends Shaping the Future of Infrastructure.pdf
Cheryl Hung
 
From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...
From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...
From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...
Product School
 

Recently uploaded (20)

PHP Frameworks: I want to break free (IPC Berlin 2024)
PHP Frameworks: I want to break free (IPC Berlin 2024)PHP Frameworks: I want to break free (IPC Berlin 2024)
PHP Frameworks: I want to break free (IPC Berlin 2024)
 
FIDO Alliance Osaka Seminar: Passkeys at Amazon.pdf
FIDO Alliance Osaka Seminar: Passkeys at Amazon.pdfFIDO Alliance Osaka Seminar: Passkeys at Amazon.pdf
FIDO Alliance Osaka Seminar: Passkeys at Amazon.pdf
 
Mission to Decommission: Importance of Decommissioning Products to Increase E...
Mission to Decommission: Importance of Decommissioning Products to Increase E...Mission to Decommission: Importance of Decommissioning Products to Increase E...
Mission to Decommission: Importance of Decommissioning Products to Increase E...
 
Epistemic Interaction - tuning interfaces to provide information for AI support
Epistemic Interaction - tuning interfaces to provide information for AI supportEpistemic Interaction - tuning interfaces to provide information for AI support
Epistemic Interaction - tuning interfaces to provide information for AI support
 
Designing Great Products: The Power of Design and Leadership by Chief Designe...
Designing Great Products: The Power of Design and Leadership by Chief Designe...Designing Great Products: The Power of Design and Leadership by Chief Designe...
Designing Great Products: The Power of Design and Leadership by Chief Designe...
 
Assuring Contact Center Experiences for Your Customers With ThousandEyes
Assuring Contact Center Experiences for Your Customers With ThousandEyesAssuring Contact Center Experiences for Your Customers With ThousandEyes
Assuring Contact Center Experiences for Your Customers With ThousandEyes
 
Transcript: Selling digital books in 2024: Insights from industry leaders - T...
Transcript: Selling digital books in 2024: Insights from industry leaders - T...Transcript: Selling digital books in 2024: Insights from industry leaders - T...
Transcript: Selling digital books in 2024: Insights from industry leaders - T...
 
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdfFIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
FIDO Alliance Osaka Seminar: The WebAuthn API and Discoverable Credentials.pdf
 
Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024
Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024
Kubernetes & AI - Beauty and the Beast !?! @KCD Istanbul 2024
 
Unsubscribed: Combat Subscription Fatigue With a Membership Mentality by Head...
Unsubscribed: Combat Subscription Fatigue With a Membership Mentality by Head...Unsubscribed: Combat Subscription Fatigue With a Membership Mentality by Head...
Unsubscribed: Combat Subscription Fatigue With a Membership Mentality by Head...
 
Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...
Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...
Empowering NextGen Mobility via Large Action Model Infrastructure (LAMI): pav...
 
The Future of Platform Engineering
The Future of Platform EngineeringThe Future of Platform Engineering
The Future of Platform Engineering
 
The Art of the Pitch: WordPress Relationships and Sales
The Art of the Pitch: WordPress Relationships and SalesThe Art of the Pitch: WordPress Relationships and Sales
The Art of the Pitch: WordPress Relationships and Sales
 
AI for Every Business: Unlocking Your Product's Universal Potential by VP of ...
AI for Every Business: Unlocking Your Product's Universal Potential by VP of ...AI for Every Business: Unlocking Your Product's Universal Potential by VP of ...
AI for Every Business: Unlocking Your Product's Universal Potential by VP of ...
 
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdf
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdfSmart TV Buyer Insights Survey 2024 by 91mobiles.pdf
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdf
 
Knowledge engineering: from people to machines and back
Knowledge engineering: from people to machines and backKnowledge engineering: from people to machines and back
Knowledge engineering: from people to machines and back
 
From Daily Decisions to Bottom Line: Connecting Product Work to Revenue by VP...
From Daily Decisions to Bottom Line: Connecting Product Work to Revenue by VP...From Daily Decisions to Bottom Line: Connecting Product Work to Revenue by VP...
From Daily Decisions to Bottom Line: Connecting Product Work to Revenue by VP...
 
Connector Corner: Automate dynamic content and events by pushing a button
Connector Corner: Automate dynamic content and events by pushing a buttonConnector Corner: Automate dynamic content and events by pushing a button
Connector Corner: Automate dynamic content and events by pushing a button
 
Key Trends Shaping the Future of Infrastructure.pdf
Key Trends Shaping the Future of Infrastructure.pdfKey Trends Shaping the Future of Infrastructure.pdf
Key Trends Shaping the Future of Infrastructure.pdf
 
From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...
From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...
From Siloed Products to Connected Ecosystem: Building a Sustainable and Scala...
 

Min-Maxing Software Costs

  • 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. SOFTWARE COSTS 1. Cost of Creation 2. Cost of Change 3. Cost of Control
  • 9. Project Lifetime Beginning 3 months 6 months 9 months ... Cost of Creation
  • 11. $ bin/rails generate controller welcome index
  • 12. from django.contrib import admin from . import models admin.site.register(models.Article)
  • 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 { ... }
  • 15. Creation → Change Project Lifetime Beginning 3 months 6 months 9 months ... Cost of Creation
  • 16. Creation → Change Project Lifetime Beginning 3 months 6 months 9 months ... Cost of Creation Pure observation & personal experience
  • 17. Creation → Change Project Lifetime Beginning 3 months 6 months 9 months ... Cost of Creation Conventional Project
  • 18. Creation → Change Project Lifetime Beginning 3 months 6 months 9 months ... Cost of Creation Conventional Project Conventional Project
  • 19. Convention-based projects either die a hero or live long enough to see themselves become the villain.
  • 21. Project Lifetime Beginning 3 months 6 months 9 months ... Cost of Creation Cost of Change
  • 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. 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. HOW LONG WOULD IT TAKETO ADD TAGS SUPPORT?
  • 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. 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. { $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. { $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');
  • 31. Creation → Change Project Lifetime Beginning 3 months 6 months 9 months ... Cost of Creation Cost of Change
  • 32. Creation → Change Project Lifetime Beginning 3 months 6 months 9 months ... Cost of Creation Cost of Change Enterprise Project
  • 33. Creation → Change Project Lifetime Beginning 3 months 6 months 9 months ... Cost of Creation Cost of Change Enterprise Project Enterprise Project
  • 36.
  • 42. Delegated Code The framework / library code Controlled Code
  • 47. Owned Code Delegated Code Change Controlled Code
  • 48. Owned Code Delegated Code Change Controlled Code
  • 49. Owned Code Delegated Code Change Controlled Code
  • 50. Owned Code Delegated Code Controlled Code
  • 52. – An engineer “Ah, to hell with that!”
  • 57.
  • 65. – A business person “Why does it always take so long?”
  • 72. Project Lifetime Beginning 3 months 6 months 9 months ... Cost of Creation Cost of Change Cost of Control
  • 73. CONTROL IS A LIMITER FOR A COST OF CHANGE
  • 74. Project Lifetime Beginning 3 months 6 months 9 months ... Cost of Creation Cost of Change Cost of Control
  • 76. Project Lifetime Beginning 3 months 6 months 9 months ... Cost of Creation Cost of Change Cost of Control
  • 78. Project Lifetime Beginning 3 months 6 months 9 months ... Cost of Creation Cost of Change Cost of Control
  • 82. 4 RULES OF MIN-MAXING 1. Begin from owning nothing ( ) 2. Take ownership reluctantly ( ) 3. Control everything you own ( ) 4. Continuously reassess your control ( )
  • 83. HEALTHY YOUNG PROJECT Owned Code Delegated Code Controlled Code
  • 84. UNHEALTHY YOUNG PROJECT Owned Code Delegated Code Controlled Code
  • 85. UNHEALTHY YOUNG PROJECT Owned Code Delegated Code Controlled Code
  • 86. HEALTHY MATURE PROJECT Owned Code Delegated Code Controlled Code
  • 87. UNHEALTHY “MATURE” PROJECT Owned Code Delegated Code Controlled Code
  • 88. UNHEALTHY “MATURE” PROJECT Owned Code Delegated Code Controlled Code
  • 93. Project Lifetime Beginning 3 months 6 months 9 months ... Cost of Creation Cost of Change Cost of Control
  • 94. Control everything you write. Avoid writing anything.