48
Comment relire du code pourri sans se faguer AFUP, Paris, 2016

Comment relire du code pourri sans se fatiguer

Embed Size (px)

Citation preview

Page 1: Comment relire du code pourri sans se fatiguer

Comment relire du code pourri sans se fatiguer

AFUP, Paris, 2016

Page 2: Comment relire du code pourri sans se fatiguer

Agenda

Comment appréhender du code

L'analyse statique pour PHP

La session dont vous êtes le héros

Page 3: Comment relire du code pourri sans se fatiguer

Un projet à relire

On ne sait pas ce que ca fait

On ne l'a jamais vu

On ne l'execute pas

On ne connait pas ses auteurs

Peut-on se former une opinion ?

Page 4: Comment relire du code pourri sans se fatiguer

Comment relire du code?

Relire est humainement possible et artisanal

Les tests unitaires sont peu adaptés à la relecture

L'analyse dynamique est peu adaptée à la relecture

Il faut explorer le code

ne pas se contenter des situations actuelles

Page 5: Comment relire du code pourri sans se fatiguer

Conférencier

Damien Seguy

Exakat CTO

Ik ben een boterham

Analyse statique de code PHP : SCAP

Page 6: Comment relire du code pourri sans se fatiguer

Le code est structuré

Le code est une base de données structurée

Les outils pour l'analyser sont l'analyse statique

Page 7: Comment relire du code pourri sans se fatiguer

Migration PHP 7.0->7.1

Inco

mpa

tible

Nouv

eaut

és

Page 8: Comment relire du code pourri sans se fatiguer

PHP LINT

php -l <fichier.php>

Execution parallele :

jakub-onderka/php-paralell-lint

Différentes versions de PHP : 7.0, 7.1, 7.2, 5.6, 5.5

Page 9: Comment relire du code pourri sans se fatiguer

Checked 5982 files in 28.4 seconds Syntax error found in 4 files

------------------------------------------------------------ Parse error: /vendor2/symfony/symfony/src/Symfony/Component/Validator/Constraints/False.php:22 20| * @api 21| */ > 22| class False extends IsFalse 23| { 24| } Fatal error: Cannot use 'False' as class name as it is reserved ------------------------------------------------------------ Parse error: /vendor2/symfony/symfony/src/Symfony/Component/Validator/Constraints/Null.php:22 20| * @api 21| */ > 22| class Null extends IsNull 23| { 24| } Fatal error: Cannot use 'Null' as class name as it is reserved ------------------------------------------------------------ Parse error: /vendor2/symfony/symfony/src/Symfony/Component/Validator/Constraints/True.php:22 20| * @api 21| */ > 22| class True extends IsTrue 23| { 24| } Fatal error: Cannot use 'True' as class name as it is reserved ------------------------------------------------------------ Parse error: /vendor_user/windid_client/src/windid/service/base/WindidUtility.php:93 91| $imageInfo = @getimagesize($file); 92| $exts = array('1'=>'gif', '2'=>'jpg', '3'=>'png'); > 93| if (!isset($exts[$imageInfo[2]])) continue; 94| $ext = $exts[$imageInfo[2]]; 95| $filename = rand(1000,9999). '.'.$ext; Fatal error: 'continue' not

PHP LINT - 7.0/1/2

Page 10: Comment relire du code pourri sans se fatiguer

Checked 5982 files in 29.7 seconds Syntax error found in 1 file

------------------------------------------------------------ Parse error: /vendor2/mockery/mockery/tests/Mockery/MockingVariadicArgumentsTest.php:52 50| abstract class TestWithVariadicArguments 51| { > 52| public function foo(...$bar) 53| { 54| return $bar; Unexpected '.', expecting '&' or variable (T_VARIABLE)

PHP LINT - 5.5

PHP LINT - 5.6Checked 5982 files in 31 seconds No syntax error found

Page 11: Comment relire du code pourri sans se fatiguer

PHP LINT

Pas compatible avec PHP 7.0 +

Pas compatible avec PHP 5.5-

Utilise Symfony

@getimagesize ? vendor2 ?

5982 fichiers

Page 12: Comment relire du code pourri sans se fatiguer

Calcul de métriques

PHPloc, PHPmetrics, PHP MD

Donne des notes d'ensemble au code

Complexité cyclomatique, nombre de LOC…

Page 13: Comment relire du code pourri sans se fatiguer

PHPLOC Directories 1143 Files 5982

Size Lines of Code (LOC) 835199 Comment Lines of Code (CLOC) 252075 (30.18%) Non-Comment Lines of Code (NCLOC) 583124 (69.82%) Logical Lines of Code (LLOC) 195283 (23.38%) Classes 178062 (91.18%) Average Class Length 29 Minimum Class Length 0 Maximum Class Length 3141 Average Method Length 4 Minimum Method Length 0 Maximum Method Length 879 Functions 1477 (0.76%) Average Function Length 1 Not in classes or functions 15744 (8.06%)

Cyclomatic Complexity Average Complexity per LLOC 0.30 Average Complexity per Class 10.82 Minimum Class Complexity 1.00 Maximum Class Complexity 1177.00 Average Complexity per Method 2.65 Minimum Method Complexity 1.00 Maximum Method Complexity 387.00

[...]

Page 14: Comment relire du code pourri sans se fatiguer

PHPLOC Dependencies Global Accesses 2158 Global Constants 1738 (80.54%) Global Variables 31 (1.44%) Super-Global Variables 389 (18.03%) Attribute Accesses 50986 Non-Static 49206 (96.51%) Static 1780 (3.49%) Method Calls 113735 Non-Static 103683 (91.16%) Static 10052 (8.84%)

Structure Namespaces 851 Interfaces 693 Traits 11 Classes 5245 Abstract Classes 301 (5.74%) Concrete Classes 4944 (94.26%) Methods 39581 Scope Non-Static Methods 37468 (94.66%) Static Methods 2113 (5.34%) Visibility Public Methods 31500 (79.58%) Non-Public Methods 8081 (20.42%) Functions 1185 Named Functions 306 (25.82%) Anonymous Functions 879 (74.18%) Constants 2658 Global Constants 361 (13.58%) Class Constants 2297 (86.42%)

Page 15: Comment relire du code pourri sans se fatiguer

PHPMetrics

Page 16: Comment relire du code pourri sans se fatiguer

PHPMetrics

Page 17: Comment relire du code pourri sans se fatiguer

Revue de code automatisée

Relecture du code par une application

regex relit PHP

Extraction de points intéressants

Fonctionnement par mots clé

PHP7cc

Page 18: Comment relire du code pourri sans se fatiguer

php7ccFile: /vendor_user/windid_client/wind/convert/WindGeneralConverter.php > Line 33: PHP 4 constructors are now deprecated public function WindGeneralConverter($sourceLang = '', $targetLang = '', $forceTable = false) { }

File: /vendor2/symfony/symfony/src/Symfony/Component/Validator/Constraints/Null.php > Line 22: Reserved name "null" used as a class, interface or trait name class Null extends \Symfony\Component\Validator\Constraints\IsNull { }

File: /vendor_user/windid_client/wind/filter/WindHandlerInterceptorChain.php > Line 61: Function argument(s) returned by "func_get_args" might have been modified func_get_args();

File: /vendor_user/windid_client/wind/http/session/handler/WindSessionHandler.php > Line 156: Check that callbacks that are passed to "session_set_save_handler" and return false or -1 (if any) operate correctly session_set_save_handler(array($this, 'open'), array($this, 'close'), array($this, 'read'), array($this, 'write'), array($this, 'destroy'), array($this, 'gc'));

File: /vendor_user/windid_client/wind/security/WindMcryptCbc.php > Line 31: Removed function "mcrypt_cbc" called mcrypt_cbc(MCRYPT_DES, $key, $string, MCRYPT_ENCRYPT, $iv); > Line 49: Removed function "mcrypt_cbc" called mcrypt_cbc(MCRYPT_DES, $key, $string, MCRYPT_DECRYPT, $iv);

Total : 83 rapports

Page 19: Comment relire du code pourri sans se fatiguer

php7cc

composer require sstale/php7cc

Fonctionne avec des regex et des mots clés

Migration PHP 7

5982 fichiers en 69.275 secondes

Page 20: Comment relire du code pourri sans se fatiguer

Revue de code automatisée

Relecture du code

Extraction de points intéressants

Fonctionnement par AST

PHP7mar, Phan, exakat

Page 21: Comment relire du code pourri sans se fatiguer

Revue de code automatisée

PHP 5 / 7 Calisthenics ClearPHP

Performance

Page 22: Comment relire du code pourri sans se fatiguer

AST

<?php

class Foo { function bar($arg) { return \StrToUpper($arg + 2); } }

$foo = new Foo(); $foo->bar(__FILE__);

?>

Page 23: Comment relire du code pourri sans se fatiguer

Sémantique et définitions

PHP7mar : nikic/php5-ast

PHAN : ext/ast (PHP 7 only)

Exakat : AST en base de données

SonarQube : AST maison

PHPstorm : AST interne à l'IDE

Page 24: Comment relire du code pourri sans se fatiguer

Sémantique et définitions

Suppression espaces, commentaires, documentations

Suppression des délimiteurs

( ) { } [ ] " ' ` ; :

Capacité à relier définitions et usage

Page 25: Comment relire du code pourri sans se fatiguer

PHANsrc/Org/OrgBundle/Controller/OrgController.php:12 PhanTypeMismatchArgument Argument 1 (data) is bool but \Org\OrgBundle\Controller\OrgController::createJsonResponse() takes array defined at src/Topxia/WebBundle/Controller/BaseController.php:120

Total : 13315 résultats1235 issues

vendor_user/windid_client/wind/mail/protocol/WindPop3.php:186 PhanUndeclaredTypeParameter Parameter of undeclared type \baoolean

276 issues

vendor_user/windid_client/wind/base/WindFactory.php:325 PhanTypeArraySuspicious Suspicious array access to bool

184 issues

vendor2/imagine/imagine/lib/Imagine/Image/AbstractLayers.php:49 PhanParamSignatureMismatch Declaration of function get($offset) should be compatible with function get(int $offset) : \Imagine\Image\ImageInterface defined in vendor2/imagine/imagine/lib/Imagine/Image/LayersInterface.php:97

src/Classroom/ClassroomBundle/Controller/ClassroomAdminController.php:84 PhanUndeclaredMethod Call to undeclared method \Classroom\ClassroomBundle\Controller \ClassroomAdminController::createErrorResponse

1919 issues

808 issues

Page 26: Comment relire du code pourri sans se fatiguer

PHAN [PhanRedefineFunction] => 31 [PhanTypeMismatchForeach] => 31 [PhanUndeclaredClassInstanceof] => 32 [PhanDeprecatedFunction] => 43 [PhanUndeclaredClassConstant] => 49 [PhanUndeclaredTypeProperty] => 60 [PhanParamSignatureMismatchInternal] => 79 [PhanUndeclaredClassCatch] => 94 [PhanParamTooMany] => 101 [PhanUndeclaredFunction] => 102 [PhanTypeMissingReturn] => 126 [PhanStaticCallToNonStatic] => 164 [PhanTypeArraySuspicious] => 184 [PhanTypeMismatchDefault] => 194 [PhanNonClassMethodCall] => 202 [PhanUndeclaredTypeParameter] => 276 [PhanUndeclaredConstant] => 417 [PhanUndeclaredVariable] => 432 [PhanTypeMismatchProperty] => 530 [PhanUndeclaredMethod] => 808 [PhanTypeMismatchArgumentInternal] => 854 [PhanUndeclaredClassMethod] => 1082 [PhanTypeMismatchReturn] => 1236 [PhanTypeMismatchArgument] => 1716 [PhanParamSignatureMismatch] => 1840 [PhanUndeclaredProperty] => 2485

53 analyses

Page 27: Comment relire du code pourri sans se fatiguer

PHANsrc/Classroom/Service/Classroom/Tests/ClassroomReviewServiceTest.php:227 PhanDeprecatedFunction Call to deprecated function \Classroom\Service\Classroom\Tests\ClassroomReviewServiceTest::setExpectedException()

defined at vendor/phpunit/phpunit/src/Framework/TestCase.php:466

43 issues

Page 28: Comment relire du code pourri sans se fatiguer

Avantages PHP 7

Type hint, return type hint, scalaires

Phan comprend les annotations PHPDOC

Le code dynamique reste difficile à analyser

Page 29: Comment relire du code pourri sans se fatiguer

Flow Control diagram

Diagramme de flux de contrôle

Basé sur l'AST, inclut le suivi de la séquence

Outil de base pour RIPS

Page 30: Comment relire du code pourri sans se fatiguer

<?php     $x = source();          if ($x < 10) {         $y = $x + 1;         $x = corrige($y);     } else {         $y = $x;     }

Page 31: Comment relire du code pourri sans se fatiguer

Flow Control Graph

$x = source();

if ($x < 10) 

$y = $x + 1;

$x = corrige($y);

$y = $x;

PHP

Exit

ElseThen

<?php     $x = source();          if ($x < 10) {         $y = $x + 1;         $x = corrige($y);     } else {         $y = $x;     }

Page 32: Comment relire du code pourri sans se fatiguer

Program Dependency Graph

$x = source();

if ($x < 10) 

$y = $x + 1;

$x = corrige($y);

$y = $x;Depend de $x

Depend de $x

Depend de $y

Depend de $x

Depend de $x

Depend de $x

<?php     $x = source();          if ($x < 10) {         $y = $x + 1;         $x = corrige($y);     } else {         $y = $x;     }

Page 33: Comment relire du code pourri sans se fatiguer
Page 34: Comment relire du code pourri sans se fatiguer

Et PHP dans tout ça?

La majorité des analyses portent sur des concepts informatiques

Il existe des analyses qui portent sur PHP lui-même

Les pratiques courantes

Les spécificités du langage

Page 35: Comment relire du code pourri sans se fatiguer

Analyses Exakat function __destruct() { throw …} : 0,5%

substr($a, 2, 4) == 'abc' : 1 %

function foo($a, $a, $a) {} : 1%

!!(expression) : 7%

$a ? $b ? $c : $d : $e ; 20%

foreach($a as &$b) {} : 21%

if (strpos($a, $b)) {} : 51 %

include('file.php') : 51%

Page 36: Comment relire du code pourri sans se fatiguer

Guide de code propre

Bonnes pratiques

maison, PSR, calisthenics, d'autres techno

Les mantras de code

Le manuel

Les guides de migration

Page 37: Comment relire du code pourri sans se fatiguer

PHP

Liste defonctionnalitésutilisées

Page 38: Comment relire du code pourri sans se fatiguer

PHP

Liste d' extensionsutilisées

Page 39: Comment relire du code pourri sans se fatiguer

Et mon application ?

Découvrez les inventaires

Liste des valeurs utilisées dans le code

Liste des entiers, décimaux, chaines, tableaux

Liste de noms de classes, interfaces, variables, méthodes

Page 40: Comment relire du code pourri sans se fatiguer

Les erreurs

Chaines utiliséesdans les die, exitet new Exception

Page 41: Comment relire du code pourri sans se fatiguer

Les variables

$orderBy 685 $token 690 $response 721 $paginator 752 $temp 828 $params 891 $value 925 $type 955 $thread 968 $order 982 $member 1042 $classroom 1115 $limit 1222 $start 1320 $currentUser 1334 $userId 1352 $file 1391 $data 1408 $i 1494 $lesson 1504 $sql 1528 $courseId 1626 $key 1716 $fields 2214 $result 2600 $course 2742 $request 3219 $id 3529 $conditions 3870 $user 4505

271 variables utilisées 1 fois

Page 42: Comment relire du code pourri sans se fatiguer

Les classesOrderServiceTest 2 FileController 2 ClassroomDataTag 2 LiveCourseController 2 UploadFileController 2 NoteController 2 BlockController 2 OrderLogDaoImpl 2 OpenCourseController 2 FileFilter 2 CourseOrderController 2 Member 2 CoinController 2 ThreadServiceTest 3 BaseProcessor 3 MobileController 3 UserController 3 CategoryDaoImpl 3 CourseReviewController 3 TeacherController 3 AlipaySubmit 3 ThreadServiceImpl 3 ThreadPostDaoImpl 3 AlipayNotify 3 ThreadDaoImpl 3 CourseController 5 DefaultController 5 DefaultControllerTest 5 Configuration 6

Page 43: Comment relire du code pourri sans se fatiguer

Les comparaisons

none 9 vip 10 .. 10 yes 10 material 11 coin 11 created 11 teacher 12 closed 12 error 13 1 14 RMB 15 0 16 paid 16 lesson 18 liveOpen 19 trend 19 cloud 19 ok 20 Coin 21 classroom 22 video 25 self 25 testpaper 27 success 32 live 32 course 39 published 43 _empty_ 71 POST 237

Chaines utilisées dans une comparaison$a == 'ok'

On compte juste les b

Page 44: Comment relire du code pourri sans se fatiguer

Pour aller plus loin

Deptrac

Vérifie que les classes restent bien dans leurs couches

Deptrac traque les dépendances entre classes qui doivent rester indépendantes

Configuré manuellement

Page 45: Comment relire du code pourri sans se fatiguer

Pour aller plus loinLe code dynamique

40% du code est en fait statique

Transpilage : https://github.com/jaytaph/Transphpile

PHP inspections : Intégrées à l'éditeur phpStorm

sensio labs insight : SCA pour le framework

Intégrer l'analyse statique dans son IC

Page 46: Comment relire du code pourri sans se fatiguer

Liste des SCAP citésDeptrac

Exakat

PHP7mar

Phan

PHP Inspections

Phploc

PHPMD

PHP 7 cc

PHPmetrics

RIPS

Transphpile

Page 47: Comment relire du code pourri sans se fatiguer

Edusoho

Page 48: Comment relire du code pourri sans se fatiguer

Merci! https://joind.in/talk/dd616

http://exakat.io/ - @exakat