47
ESCAPING DEPENDENCY HELL Michael Haeuslmann - IPC Munich 2016 Source: Escape from Hell Constantine by Rommeu

Escaping Dependency Hell

Embed Size (px)

Citation preview

Page 1: Escaping Dependency Hell

ESCAPINGDEPENDENCY HELL

Michael Haeuslmann - IPC Munich 2016Source: Escape from Hell Constantine by Rommeu

Page 2: Escaping Dependency Hell

AGENDA1. Discuss

What is dependency hell?Why should we care?Are all dependencies created equal?

2. Analyse ( )DependenciesArchitecture

3. FixUntestable codeDependency (mis-)management

Page 3: Escaping Dependency Hell

MICHAEL HAEUSLMANNINDEPENDENT FREELANCER (PHPRAGMATIC.COM)

married, love to travel, board games, arch-nerd, ...developing in PHP for ~8 yearsmostly legacy applications or open sourceprofessional work in PHPprivate projects in Java, JavaScript, Swi, ...

Page 4: Escaping Dependency Hell

PART I: DISCUSS

Page 5: Escaping Dependency Hell

WHAT IS DEPENDENCY HELL?

Page 6: Escaping Dependency Hell

WHY SHOULD WE CARE?

» If you can’t understand it, you can’t change it. «

- Eric Evans

Page 7: Escaping Dependency Hell

WHY SHOULD WE CARE?COMPLEX VS. COMPLICATED

Big soware systems are complex (many moving parts)

Not all parts are complicated

BUT mismanaging complexity leads to morecomplicated problems

Page 8: Escaping Dependency Hell

WHAT ARE DEPENDENCIES?Cross-Team dependencies Team A requires results from Team B which requires results from Team C ... 3rd-party dependencies Composer packages etc. Soware dependencies Internal or external dependencies on class or package level

Page 9: Escaping Dependency Hell

WHY DEPENDENCY MANAGEMENT TOOLSMATTER?

Difficulty of any given task depends on its dependencies We don't want to do everything ourselves Managing dependencies in the right way helps with:

Understanding Maintenance

Page 10: Escaping Dependency Hell

PART II: ANALYSE

Page 11: Escaping Dependency Hell

WHAT SHOULD DEPENDENCY ANALYSIS TELLUS?

Where should we start refactoring? Why does [SomeClass] always break? What does our architecture actually look like? Is our architecture the way it should be?

Page 12: Escaping Dependency Hell

TOOLSPHP DEPEND BY MANUEL PICHLER

many metrics many metrics hard to maintain too vague

Page 13: Escaping Dependency Hell

TOOLSPHP DEPEND BY MANUEL PICHLER

Page 14: Escaping Dependency Hell

TOOLS

made with ♥ for OSS @ University of Rosenheim

generates UML and other dependency visualizations

detects even the sneakiest dependencies

hackable (grep, sed, awk, ...)

for the nerds: written using functional style

a lot more to come

Page 15: Escaping Dependency Hell

FEATURES Text For quick feedback, debugging, UNIX pipes etc. UML & DSM Detailed dependency & architectural analysis (Metrics)

Page 16: Escaping Dependency Hell

DEPHPEND - TEXT OUTPUT$> php dephpend.phar help text _ _____ _ _ _____ _ | | | __ \| | | | __ \ | | __| | ___| |__) | |__| | |__) |__ _ __ __| | / _` |/ _ \ ___/| __ | ___/ _ \ '_ \ / _` | | (_| | __/ | | | | | | | __/ | | | (_| | \__,_|\___|_| |_| |_|_| \___|_| |_|\__,_| version 0.1

Usage: text [options] [­­] ()...

$> php dephpend.phar text ~/workspace/dephpend/src

Mihaeu\PhpDependencies\Util\AbstractMap ­­> Mihaeu\PhpDependencies\Util\CollectionMihaeu\PhpDependencies\Util\DI ­­> Mihaeu\PhpDependencies\Analyser\Analyser...

(*) make sure XDebug is not enabled or use php -n

Page 17: Escaping Dependency Hell

DEPHPEND - TEXT OUTPUT$> php dephpend.phar text ~/workspace/dephpend/src ­­no­classes | sort

Mihaeu\PhpDependencies\Analyser ­­> Mihaeu\PhpDependencies\DependenciesMihaeu\PhpDependencies\Analyser ­­> Mihaeu\PhpDependencies\OSMihaeu\PhpDependencies\Analyser ­­> Mihaeu\PhpDependencies\UtilMihaeu\PhpDependencies\Analyser ­­> PhpParserMihaeu\PhpDependencies\Analyser ­­> PhpParser\NodeMihaeu\PhpDependencies\Analyser ­­> PhpParser\Node\ExprMihaeu\PhpDependencies\Analyser ­­> PhpParser\Node\NameMihaeu\PhpDependencies\Analyser ­­> PhpParser\Node\StmtMihaeu\PhpDependencies\Analyser ­­> PhpParser\NodeVisitor...

$> php dephpend.phar text ~/workspace/dephpend/src ­­no­classes \ | grep ­e 'Analyser ­­> .*OS'

Mihaeu\PhpDependencies\Analyser ­­> Mihaeu\PhpDependencies\OS

Page 18: Escaping Dependency Hell

DEPHPEND - TEXT OUTPUTMake it yours!

#!/usr/bin/env sh

php build/dephpend.phar text ~/workspace/dephpend/src ­­no­classes | grep \

­e 'Analyser ­­> .*OS' \

­e 'OS ­­> .*Analyser'

if [ "$?" ­eq 0 ]; then

echo 'Architecture violation!'

exit 1

fi

Page 19: Escaping Dependency Hell

DEPHPEND - TEXT OUTPUT

<?php

$output = shell_exec('php dephpend.phar text ' .'~/workspace/myMVCFramework/src ­­no­classes');$constraints = [ 'Model.* ­­> .*View', 'View.* ­­> .*Model',];

if (preg_match('/('.implode(')|(', $constraints).')/x', $output)) echo 'Architecture violation'.PHP_EOL; exit(1);

Page 20: Escaping Dependency Hell

DEPHPEND - UML (SORT OF)dePHPend packages

$> php dephpend.phar uml ~/workspace/dephpend/src ­­no­classes ­­output=uml.png

Page 21: Escaping Dependency Hell
Page 22: Escaping Dependency Hell

DEPHPEND - UML (SORT OF)Symfony components

$> php ­d memory_limit=512M dephpend.phar uml \ ~/workspace/symfony/src/Symfony/Component \ ­­no­classes \ ­­depth 3 \ ­­exclude­regex='/Test/' \ ­­output=uml.png

Page 23: Escaping Dependency Hell

DEPHPEND - UML (SORT OF)Symfony HTTP Kernel

Page 24: Escaping Dependency Hell

DEPENDENCY STRUCTURE MATRIX(DSM)

same data as graph diagrams (e.g. UML class diagram) quick overview for large apps

Page 25: Escaping Dependency Hell

NDEPEND EXAMPLE

Page 26: Escaping Dependency Hell

DEPHPEND DSM:

Page 27: Escaping Dependency Hell

PART III: FIX

Page 28: Escaping Dependency Hell

OBSCURE/NASTY DEPENDENCIES

Some dependencies cannot be detected by any tool (or developer):

Fake collections Overuse of scalar values (int, string, ...) Temporal dependencies ...

Page 29: Escaping Dependency Hell

WHY DO WE CARE?

We want code which is ... ... easier to understand ... easier to maintain ... easier to test

Page 30: Escaping Dependency Hell

BE EXPLICIT!

What is explicit/implicit?

Can your IDE provide assistance for it? (Ctrl + Le click or mouse over)Can you be sure it is what it says it is?

function sendNewsletter( array $customers, string $message ); function sendNewsletter( CustomerCollection $customers, Message $message );

Page 31: Escaping Dependency Hell

DON'T MAKE ME LOOK IT UP/** * @var mixed $email * @var string|Email|array $email */function addEmail($email) if (is_array($email)) // pray everything inside the array actually is an email foreach ($email as $singleEmail) addEmail($singleEmail); else if (is_string($email)) addEmail(new Email($email)); else if ($email instanceof Email) this­>emails[] = email; else throw new InvalidArgumentException('Bad argument type');

Page 32: Escaping Dependency Hell

DON'T MAKE ME LOOK IT UPfunction addEmail(Email $email) $this­>emails[] = $email;

function addEmailString(string $email) $this­>addEmail(new Email($email));

function addEmailArray(array $emails) foreach ($emails as $email) /** @var Email $email */ if (is_string($email)) $this­>addEmailstring($email); else if ($email instanceof Email) $this­>addEmail($email); else throw new InvalidArgumentException('Bad argument type');

function addEmailCollection(EmailCollection $emails) $emails­>each(function (Email $email) $this­>addEmail($email); );

Page 33: Escaping Dependency Hell

PRINCIPLES OF OO: SOLID

Single responsibility principle Open/closed principle Liskov substitution principle Interface segregation principle Dependency inversion principle

Page 34: Escaping Dependency Hell

DEPENDENCY INVERSIONBAD:

class CustomerRepository public function __construct() $this­>db = new MySQLDatabase(new DefaultConfig());

BETTER:class CustomerRepository public function __construct(MySQLDatabase $db) $this­>db = $db;

GOOD:class CustomerRepository public function __construct(Database $db) $this­>db = $db;

Page 35: Escaping Dependency Hell

Easier to understand and test, less likely to break

Page 36: Escaping Dependency Hell

DEPENDENCY INJECTIONCONTAINERS

$container = new Pimple\Container();

$container['cstmrrepo'] = function ($database) return new CustomerRepository($database);;

$cstmrRepo = $container['cstmrrepo'];

Too easy? obscure dependencies using YAMLadd another 3rd party libraryadd overhead by parsing meta format/sarcasm

Page 37: Escaping Dependency Hell

AVOID IMPLICIT DEPENDENCIES IN FAVOR OFEXPLICIT ONES

// why not do it yourselves?class DependencyInjectionContainer

// eager load public function getCustomerRepository() : CustomerRepository return new CustomerRepository($this­>otherDeps);

// OR: lazy load public function getCustomerRepository() : CustomerRepository if (null === $this­>customerRepository) $this­>customerRepository = new CustomerRepository($this­>otherDeps); return $this­>customerRepository;

$dependencyInjectionContainer­>getCustomerRepository();

Page 38: Escaping Dependency Hell

SERVICE LOCATORclass CustomerRepository public function __construct(ServiceLocator $serviceLocator) $this­>db = $serviceLocator­>getDb();

new CustomerRepository($serviceLocator);

Page 39: Escaping Dependency Hell

Same or similar implementation, but different usage:

CHOOSE DEPENDENCY INJECTIONCONTAINERS OVER SERVICE LOCATORS

Service Locator provides access to everythingMight as well use globals...(but not really)Target class knows more than it should= more reasons to change (=break)

Always choose REAL Dependency Injection

Page 40: Escaping Dependency Hell

WHERE TO GO FROM HERE?support more types of dependencies

improve visualizationcachingCI integration

php dephpend.phar test­features [] creating objects [] using traits ... [] known variable passed into method without type hints...

Contributions, ideas, feedback, bug reports are welcome!

Page 41: Escaping Dependency Hell

QUESTIONS

???LINKS

https://dephpend.comhttps://github.com/mihaeu/githubhttps://pdepend.org

Page 42: Escaping Dependency Hell

HOW DOES IT WORK?

STATIC ANALYSIS

Transform the code to make parsing easierInfer direct types (require, new, type hints, ...)Infer indirect types (DICs, ...)

DYNAMIC ANALYSIS

profile the applicationtrack function tracescollect all possible input values

Page 43: Escaping Dependency Hell

STATIC ANALYSISEasy right?

use SomeNamespace\SomeClass;

class MyClass extends MyParent implements MyInterface

/**

* @return AnotherClass

*/

public function someFunction(SomeClass $someClass) : AnotherClass

StaticClass::staticFunction();

return new AnotherClass();

Page 44: Escaping Dependency Hell

STATIC ANALYSISOr is it?

class MyClass

public function someMethod($dependency)

return call_user_func('globalFunction', $dependency);

Page 45: Escaping Dependency Hell

or:

DYNAMIC ANALYSISXDebug to the rescue!

; php.ini

zend_extension=/path/to/xdebug.so

[xdebug]xdebug.profiler_enable = 1xdebug.auto_trace=1xdebug.collect_params=1xdebug.collect_return=3xdebug.collect_assignments=1xdebug.trace_format=1xdebug.trace_options=1

# https://github.com/mihaeu/dephpend/blob/develop/bin/dephpendphp­trace ­­dynamic=/path/to/trace­file.xt ­S localhost:8080

Page 46: Escaping Dependency Hell

DYNAMIC ANALYSISTRACE START [2016­10­19 16:59:03]1 0 0 0.000189 363984 main 1 /home/mike/workspace/dephpend/bin/dephpend 0 0

2 1 0 0.000209 363984 get_declared_classes 0 /home/mike/workspace/dephpend/bin/dephpend 80

...

11 211058 0 3.503452 4856528 strpos 0 /home/mike/workspace/dephpend/vendor/symfony/console/Formatter/OutputFormatter.php 177 2 string(15111) string(2)

...3 200813 R long 3.504303 238672TRACE END [2016­10­19 16:59:07]

Page 47: Escaping Dependency Hell

DYNAMIC ANALYSIS

Parse the trace file and merge with static results

$> php dephpend.phar text src \ ­­dynamic=/path/to/trace­file.xt \ ­­filter­from=YourNamespace \ ­­exclude­regex='(Test)|(Mock)'

There are no secrets at runtime!