Escaping Dependency Hell

Post on 12-Apr-2017

131 views 0 download

Transcript of Escaping Dependency Hell

ESCAPINGDEPENDENCY HELL

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

AGENDA1. Discuss

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

2. Analyse ( )DependenciesArchitecture

3. FixUntestable codeDependency (mis-)management

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, ...

PART I: DISCUSS

WHAT IS DEPENDENCY HELL?

WHY SHOULD WE CARE?

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

- Eric Evans

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

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

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

PART II: ANALYSE

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?

TOOLSPHP DEPEND BY MANUEL PICHLER

many metrics many metrics hard to maintain too vague

TOOLSPHP DEPEND BY MANUEL PICHLER

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

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

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

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

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

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);

DEPHPEND - UML (SORT OF)dePHPend packages

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

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

DEPHPEND - UML (SORT OF)Symfony HTTP Kernel

DEPENDENCY STRUCTURE MATRIX(DSM)

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

NDEPEND EXAMPLE

DEPHPEND DSM:

PART III: FIX

OBSCURE/NASTY DEPENDENCIES

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

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

WHY DO WE CARE?

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

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 );

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');

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); );

PRINCIPLES OF OO: SOLID

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

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;

Easier to understand and test, less likely to break

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

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();

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

new CustomerRepository($serviceLocator);

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

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!

QUESTIONS

???LINKS

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

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

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();

STATIC ANALYSISOr is it?

class MyClass

public function someMethod($dependency)

return call_user_func('globalFunction', $dependency);

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

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]

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!