ESCAPING
DEPENDENCY HELL
Michael Haeuslmann - IPC Munich 2016
Source: Escape from Hell Constantine by Rommeu
AGENDA
1. Discuss
What is dependency hell?
Why should we care?
Are all dependencies created equal?
2. Analyse ( )
Dependen...
MICHAEL HAEUSLMANN
INDEPENDENT FREELANCER (PHPRAGMATIC.COM)
married, love to travel, board games, arch-nerd, ...
developin...
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 so ware systems are complex
(many moving parts)
Not all parts are complica...
WHAT ARE DEPENDENCIES?
Cross-Team dependencies
Team A requires results from Team B
which requires results from Team C ...
...
WHY DEPENDENCY MANAGEMENT TOOLS
MATTER?
Difficulty of any given task depends on its dependencies
We don't want to do everyt...
PART II: ANALYSE
WHAT SHOULD DEPENDENCY ANALYSIS TELL
US?
Where should we start refactoring?
Why does [SomeClass] always break?
What does o...
TOOLS
PHP DEPEND BY MANUEL PICHLER
many metrics
many metrics
hard to maintain
too vague
TOOLS
PHP DEPEND BY MANUEL PICHLER
TOOLS
made with ♥ for OSS @ University of Rosenheim
generates UML and other dependency visualizations
detects even the sne...
FEATURES
Text
For quick feedback, debugging, UNIX pipes etc.
UML & DSM
Detailed dependency & architectural analysis
(Metri...
DEPHPEND - TEXT OUTPUT
$> php dephpend.phar help text
      _      _____  _    _ _____               _
     | |    |  __ |...
DEPHPEND - TEXT OUTPUT
$> php dephpend.phar text ~/workspace/dephpend/src ­­no­classes | sort
MihaeuPhpDependenciesAnalyse...
DEPHPEND - TEXT OUTPUT
Make it yours!
#!/usr/bin/env sh
php build/dephpend.phar text ~/workspace/dephpend/src ­­no­classes...
DEPHPEND - TEXT OUTPUT
<?php
$output = shell_exec('php dephpend.phar text '
    .'~/workspace/myMVCFramework/src ­­no­clas...
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...
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 sc...
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...
DON'T MAKE ME LOOK IT UP
/**
 * @var mixed $email
 * @var string|Email|array $email
 */
function addEmail($email) {
    if...
DON'T MAKE ME LOOK IT UP
function addEmail(Email $email) {
  $this­>emails[] = $email;
}
function addEmailString(string $e...
PRINCIPLES OF OO: SOLID
Single responsibility principle
Open/closed principle
Liskov substitution principle
Interface segr...
DEPENDENCY INVERSION
BAD:
class CustomerRepository {
    public function __construct() {
        $this­>db = new MySQLData...
Easier to understand and test, less likely to break
DEPENDENCY INJECTION
CONTAINERS
$container = new PimpleContainer();
$container['cstmrrepo'] = function ($database) {
    r...
AVOID IMPLICIT DEPENDENCIES IN FAVOR OF
EXPLICIT ONES
// why not do it yourselves?
class DependencyInjectionContainer {
  ...
SERVICE LOCATOR
class CustomerRepository {
    public function __construct(ServiceLocator $serviceLocator) {
        $this...
Same or similar implementation, but different usage:
CHOOSE DEPENDENCY INJECTION
CONTAINERS OVER SERVICE LOCATORS
Service L...
WHERE TO GO FROM HERE?
support more types of dependencies
improve visualization
caching
CI integration
php dephpend.phar t...
QUESTIONS
???
LINKS
https://dephpend.com
https://github.com/mihaeu/github
https://pdepend.org
HOW DOES IT WORK?
STATIC ANALYSIS
Transform the code to make parsing easier
Infer direct types (require, new, type hints, ...
STATIC ANALYSIS
Easy right?
use SomeNamespaceSomeClass;
class MyClass extends MyParent implements MyInterface {
    /**
  ...
STATIC ANALYSIS
Or is it?
class MyClass {
    public function someMethod($dependency) {
        return call_user_func('glo...
or:
DYNAMIC ANALYSIS
XDebug to the rescue!
; php.ini
zend_extension=/path/to/xdebug.so
[xdebug]
xdebug.profiler_enable = 1...
DYNAMIC ANALYSIS
TRACE START [2016­10­19 16:59:03]
1  0  0  0.000189  363984  {main}  1
        /home/mike/workspace/dephp...
DYNAMIC ANALYSIS
Parse the trace file and merge with static results
$> php dephpend.phar text src               
        ­...
Escaping Dependency Hell
Upcoming SlideShare
Loading in …5
×

Escaping Dependency Hell

190 views

Published on

Discover, analyse and fix dependency and architectural problems in PHP applications.

Published in: Technology
  • Be the first to comment

Escaping Dependency Hell

  1. 1. ESCAPING DEPENDENCY HELL Michael Haeuslmann - IPC Munich 2016 Source: Escape from Hell Constantine by Rommeu
  2. 2. AGENDA 1. Discuss What is dependency hell? Why should we care? Are all dependencies created equal? 2. Analyse ( ) Dependencies Architecture 3. Fix Untestable code Dependency (mis-)management
  3. 3. MICHAEL HAEUSLMANN INDEPENDENT FREELANCER (PHPRAGMATIC.COM) married, love to travel, board games, arch-nerd, ... developing in PHP for ~8 years mostly legacy applications or open source professional work in PHP private projects in Java, JavaScript, Swi , ...
  4. 4. PART I: DISCUSS
  5. 5. WHAT IS DEPENDENCY HELL?
  6. 6. WHY SHOULD WE CARE? » If you can’t understand it, you can’t change it. « - Eric Evans
  7. 7. WHY SHOULD WE CARE? COMPLEX VS. COMPLICATED Big so ware systems are complex (many moving parts) Not all parts are complicated BUT mismanaging complexity leads to more complicated problems
  8. 8. 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. So ware dependencies Internal or external dependencies on class or package level
  9. 9. WHY DEPENDENCY MANAGEMENT TOOLS MATTER? 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
  10. 10. PART II: ANALYSE
  11. 11. WHAT SHOULD DEPENDENCY ANALYSIS TELL US? 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?
  12. 12. TOOLS PHP DEPEND BY MANUEL PICHLER many metrics many metrics hard to maintain too vague
  13. 13. TOOLS PHP DEPEND BY MANUEL PICHLER
  14. 14. 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
  15. 15. FEATURES Text For quick feedback, debugging, UNIX pipes etc. UML & DSM Detailed dependency & architectural analysis (Metrics)
  16. 16. DEPHPEND - TEXT OUTPUT $> php dephpend.phar help text       _      _____  _    _ _____               _      | |    |  __ | |  | |  __              | |    __| | ___| |__) | |__| | |__) |__ _ __   __| |   / _` |/ _   ___/|  __  |  ___/ _  '_  / _` |  | (_| |  __/ |    | |  | | |  |  __/ | | | (_| |   __,_|___|_|    |_|  |_|_|   ___|_| |_|__,_| version 0.1   Usage:   text [options] [­­]  ()... $> php dephpend.phar text ~/workspace/dephpend/src MihaeuPhpDependenciesUtilAbstractMap ­­> MihaeuPhpDependenciesUtilCollection MihaeuPhpDependenciesUtilDI ­­> MihaeuPhpDependenciesAnalyserAnalyser ... (*) make sure XDebug is not enabled or use php -n
  17. 17. DEPHPEND - TEXT OUTPUT $> php dephpend.phar text ~/workspace/dephpend/src ­­no­classes | sort MihaeuPhpDependenciesAnalyser ­­> MihaeuPhpDependenciesDependencies MihaeuPhpDependenciesAnalyser ­­> MihaeuPhpDependenciesOS MihaeuPhpDependenciesAnalyser ­­> MihaeuPhpDependenciesUtil MihaeuPhpDependenciesAnalyser ­­> PhpParser MihaeuPhpDependenciesAnalyser ­­> PhpParserNode MihaeuPhpDependenciesAnalyser ­­> PhpParserNodeExpr MihaeuPhpDependenciesAnalyser ­­> PhpParserNodeName MihaeuPhpDependenciesAnalyser ­­> PhpParserNodeStmt MihaeuPhpDependenciesAnalyser ­­> PhpParserNodeVisitor ... $> php dephpend.phar text ~/workspace/dephpend/src ­­no­classes      | grep ­e 'Analyser ­­> .*OS' MihaeuPhpDependenciesAnalyser ­­> MihaeuPhpDependenciesOS
  18. 18. DEPHPEND - TEXT OUTPUT Make 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
  19. 19. 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); }
  20. 20. DEPHPEND - UML (SORT OF) dePHPend packages $> php dephpend.phar uml ~/workspace/dephpend/src ­­no­classes ­­output=uml.png
  21. 21. 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
  22. 22. DEPHPEND - UML (SORT OF) Symfony HTTP Kernel
  23. 23. DEPENDENCY STRUCTURE MATRIX (DSM) same data as graph diagrams (e.g. UML class diagram) quick overview for large apps
  24. 24. NDEPEND EXAMPLE
  25. 25. DEPHPEND DSM:
  26. 26. PART III: FIX
  27. 27. OBSCURE/NASTY DEPENDENCIES Some dependencies cannot be detected by any tool (or developer): Fake collections Overuse of scalar values (int, string, ...) Temporal dependencies ...
  28. 28. WHY DO WE CARE? We want code which is ... ... easier to understand ... easier to maintain ... easier to test
  29. 29. 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  );
  30. 30. 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');     } }
  31. 31. DON'T MAKE ME LOOK IT UP function 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);   }); }
  32. 32. PRINCIPLES OF OO: SOLID Single responsibility principle Open/closed principle Liskov substitution principle Interface segregation principle Dependency inversion principle
  33. 33. DEPENDENCY INVERSION BAD: 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;     } }
  34. 34. Easier to understand and test, less likely to break
  35. 35. DEPENDENCY INJECTION CONTAINERS $container = new PimpleContainer(); $container['cstmrrepo'] = function ($database) {     return new CustomerRepository($database); }; $cstmrRepo = $container['cstmrrepo']; Too easy? obscure dependencies using YAML add another 3rd party library add overhead by parsing meta format /sarcasm
  36. 36. AVOID IMPLICIT DEPENDENCIES IN FAVOR OF EXPLICIT 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();
  37. 37. SERVICE LOCATOR class CustomerRepository {     public function __construct(ServiceLocator $serviceLocator) {         $this­>db = $serviceLocator­>getDb();     } } new CustomerRepository($serviceLocator);
  38. 38. Same or similar implementation, but different usage: CHOOSE DEPENDENCY INJECTION CONTAINERS OVER SERVICE LOCATORS Service Locator provides access to everything Might as well use globals... (but not really) Target class knows more than it should = more reasons to change (=break) Always choose REAL Dependency Injection
  39. 39. WHERE TO GO FROM HERE? support more types of dependencies improve visualization caching CI 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!
  40. 40. QUESTIONS ??? LINKS https://dephpend.com https://github.com/mihaeu/github https://pdepend.org
  41. 41. HOW DOES IT WORK? STATIC ANALYSIS Transform the code to make parsing easier Infer direct types (require, new, type hints, ...) Infer indirect types (DICs, ...) DYNAMIC ANALYSIS profile the application track function traces collect all possible input values
  42. 42. STATIC ANALYSIS Easy right? use SomeNamespaceSomeClass; class MyClass extends MyParent implements MyInterface {     /**      * @return AnotherClass      */     public function someFunction(SomeClass $someClass) : AnotherClass {         StaticClass::staticFunction();         return new AnotherClass();     } }
  43. 43. STATIC ANALYSIS Or is it? class MyClass {     public function someMethod($dependency) {         return call_user_func('globalFunction', $dependency);     } }
  44. 44. or: DYNAMIC ANALYSIS XDebug to the rescue! ; php.ini zend_extension=/path/to/xdebug.so [xdebug] xdebug.profiler_enable = 1 xdebug.auto_trace=1 xdebug.collect_params=1 xdebug.collect_return=3 xdebug.collect_assignments=1 xdebug.trace_format=1 xdebug.trace_options=1 # https://github.com/mihaeu/dephpend/blob/develop/bin/dephpend php­trace ­­dynamic=/path/to/trace­file.xt ­S localhost:8080
  45. 45. DYNAMIC ANALYSIS TRACE 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  238672 TRACE END   [2016­10­19 16:59:07]                     
  46. 46. 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!

×