Click here to load reader

Code smells in PHP

  • View
    10.701

  • Download
    0

Embed Size (px)

Text of Code smells in PHP

  • 1.Code smells in PHPDagfinn Reiersl, ABC Startsiden1

2. Who am I?Dagfinn Reiersl dagfinn@reiersol.com Twitter: @dagfinnr Blog: agilephp.comMostly PHP since 1999Wrote PHP in ActionCode quality / agile development enthusiastDagfinn Reiersl, ABC Startsiden 2 3. What is a code smell?Train your nose to tell you... ...when to refactor ...what to refactor ...how to refactorDagfinn Reiersl, ABC Startsiden 3 4. Its more like a diagnosis A disease has a treatment, a code smell has a refactoring (or several) Code smell distinctons are important For each smell, there is one or more refactorings http://industriallogic.com/papers/smellstorefactorings.pdfDagfinn Reiersl, ABC Startsiden 4 5. What is refactoring? Improving the design of existing code Maintain behavior Change the structure Make it more readable, eliminate duplication Proceed by small steps Keep code working alwaysDagfinn Reiersl, ABC Startsiden 5 6. The bible of refactoring Martin Fowler...is not JesusChrist, and his books are notthe Bible. Except this one really is thebible. Java examples, but mostlyPHP-relevantDagfinn Reiersl, ABC Startsiden6 7. Refactoring is a specific, learnable skill Learn to apply specific, named refactorings. Refactorings have specific instructions Learn to go in baby steps Test between each step Undo if you get lost Weird intermediate results are OKDagfinn Reiersl, ABC Startsiden7 8. Refactoring in PHP Very little tool support This is both a bad thing and a good thing Extract Method is particularly crucial, but unsupported by toolsDagfinn Reiersl, ABC Startsiden8 9. Why refactor? Make code easier to read (saves time) Make it easier to find bugs Learn design principles Discover new abstractions Clean, maintainable codeDagfinn Reiersl, ABC Startsiden9 10. How much is enough? Make it as clean as you possibly can, if circumstances allow Boy Scout Rule When you change code, youre likely to change it again soon Better code needs less refactoring Better code is easier to refactorDagfinn Reiersl, ABC Startsiden10 11. Why duplication is so bad Harder to maintain Harder to debug Incomplete bug fixesOriginal codeOriginal code First copyDebugFirst copySecond copySecond copyDagfinn Reiersl, ABC Startsiden 11 12. Automated test coverage is essential Unit tests primarily Tests make it easy to fix when you break something Acceptance tests helpful sometimes Manual testing only in special, desperate circumstances Legacy code paradoxDagfinn Reiersl, ABC Startsiden12 13. Another bible: Clean Code Lots of smells and heuristicsDagfinn Reiersl, ABC Startsiden13 14. Dont take examples personally Im using somewhat real open-source examples No personal criticism implied Refactoring examples must be somewhere in the middle (notawful, not perfect) Awful is too hard to refactor (=advanced material) Perfect doesnt exist Just pretend I wrote all of it ;-)Dagfinn Reiersl, ABC Startsiden 14 15. Duplicated Code// even if we are interacting between a table defined in a// class and a/ table via extension, ensure to persist the// definitionif (($tableDefinition = $this->_table->getDefinition()) !== null&& ($dependentTable->getDefinition() == null)) {$dependentTable->setOptions(array(Table::DEFINITION => $tableDefinition));}...// even if we are interacting between a table defined in a// class and a/ table via extension, ensure to persist the// definitionDagfinn Reiersl, ABC Startsiden= $this->_table->getDefinition()) !== 15if (($tableDefinitionnull 16. Long Method Long methods are evil Hard to read (time-consuming) Hard to test Tend to have duplicate logic Hard to override specific behaviorsDagfinn Reiersl, ABC Startsiden16 17. How long? The first rule of functions is that they should be small The second rule of functions is that they should be smaller thanthat. - Robert C. Martin, Clean Code My experience: the cleanest code has mostly 2-5 line methods But dont do it if it doesnt make sense Do One Thing One level of abstraction onlyDagfinn Reiersl, ABC Startsiden 17 18. Refactoring a Long Method Split method into smaller methods Extract Method is the most important refactoring// Execute cascading updates against dependent tables.// Do this only if primary key value(s) were changed.if (count($pkDiffData) > 0) {$depTables = $this->_getTable()->getDependentTables();if (!empty($depTables)) {$pkNew = $this->_getPrimaryKey(true);$pkOld = $this->_getPrimaryKey(false);foreach ($depTables as $tableClass) {$t = $this->_getTableFromString($tableClass);$t->_cascadeUpdate($this->getTableClass(), $pkOld, $pkNew);}}Dagfinn Reiersl, ABC Startsiden 18 19. Extract Method: mechanics1.Copy the code into a new method2.Find all temporary variables3.Return all of them from the methodIn PHP, unlike Java, we can return multiple variabless4.Find the ones that are initialized in the method5.Pass all of those into the method6.The result is ugly, but a step forwardDagfinn Reiersl, ABC Startsiden 19 20. Extract Method: resultprivate function executeCascadingUpdates($pkDiffData) {if (count($pkDiffData) > 0) {$depTables = $this->_getTable()->getDependentTables();if (!empty($depTables)) {$pkNew = $this->_getPrimaryKey(true);$pkOld = $this->_getPrimaryKey(false);foreach ($depTables as $tableClass) {$t = $this->_getTableFromString($tableClass);$t->_cascadeUpdate( $this->getTableClass(), $pkOld, $pkNew);}}}return array($pkDiffData,$tableClass,$depTables,$pkNew,$pkOld,$t);}Dagfinn Reiersl, ABC Startsiden 20 21. Validation Overcrowdingfunction setTable(Table $table){$tableClass = get_class($table);if (! $table instanceof $this->_tableClass) {require_once My_Exception.php;throw new My_exception("blah blah");}$this->_table = $table;$this->_tableClass = $tableClass;$info = $this->_table->info();if ($info[cols] != array_keys($this->_data)) {require_once My_Exception.php;throw new My_exception("blah blah");}if (!array_intersect((array)$this->_primary, info[primary])== (array) $this->_primary) {require_once My_Exception.php;throw new My_exception("blah blah");}$this->_connected = true;Dagfinn Reiersl, ABC Startsiden 21 22. Extracting Validation Dont waste time reading validation code Extract validation (logging, error handling) into separatemethod(s)function setTable(Table $table) {$this-validateTable($table);$this->_table = $table;$this->_tableClass = get_class($table);$this->_connected = true;}Dagfinn Reiersl, ABC Startsiden 22 23. Large Class As methods get smaller, there will be more of them Hard to keep track of all the methods Class has multiple responsibilities A class should have only one reason to change Duplication is likelyDagfinn Reiersl, ABC Startsiden 23 24. Refactoring a Large Class Primarily Extract Class Look for patterns in method namesfunction __construct($url = null, $useBrackets = true)...function initialize()...function getURL()...function addQueryString($name, $value, $preencoded = false)...function removeQueryString($name)...function addRawQueryString($querystring)...function getQueryString()...function _parseRawQuerystring($querystring)...function resolvePath($path)...function getStandardPort($scheme)...function setProtocol($protocol, $port = null)...function setOption($optionName, $value)...function getOption($optionName)...Dagfinn Reiersl, ABC Startsiden 24 25. Refactoring a Large Class Look for Patterns in method names (see previous) Subset of data and methods that go together Subset of data that change together Mechanics in short Create a new class Copy variables and methods into it Change methods one by one to delegate to the new class You must have automated testsDagfinn Reiersl, ABC Startsiden 25 26. Primitive ObsessionPeople new to objects usually are reluctant to use small objectsfor small tasks, such as money classes that combine numberand currency, ranges with an upper and lower, and specialstrings such as telphone numbers and ZIP codes.- Martin Fowler, RefactoringDagfinn Reiersl, ABC Startsiden26 27. Primitive obsession: non-OO dates Will this work?strftime($arrivaltime); Plain PHP date handling is ambiguous, obscure and error-prone Use objects instead$datetime = new DateTime(2008-08-03 14:52:10);echo $datetime->format(jS, F Y) . "n";Dagfinn Reiersl, ABC Startsiden27 28. Primitive obsession example The primary key is an array (when?) or a scalar (when?) Are these names or values?if (is_array($primaryKey)) {$newPrimaryKey = $primaryKey;} else {$tempPrimaryKey = (array) $this->_primary;$newPrimaryKey = array( current($tempPrimaryKey) => $primaryKey);}return $newPrimaryKey;Dagfinn Reiersl, ABC Startsiden28 29. Primitive obsession example Huh?/*** Were any of the changed columns part of the primary key?*/$pkDiffData = array_intersect_key( $diffData, array_flip((array)$this->_primary));} Its clever, obscure, therefore error-prone I think I prefer this:foreach ((array)$this->_primary as $pkName) {if (array_key_exists($pkName,$diffData))$pkDiffData[$pkName] = $diffData[$pkName];}Dagfinn Reiersl, ABC Startsiden 29 30. Primary key classclass PrimaryKey { public function __construct($primitive) { $this->primitive = $primitive; } public function isCompoundKey() { return is_array($this->primitive); } public function getSequenceColumn() { return array_shift($this->asArray()); } public function asArray() { return (array) $this->primitive; } public function filter($data) { return array_intersect_key( $data, array_flip($this->asArray())); }}Dagfinn Reiersl, ABC Startsiden 30 31. Benefits of the PrimaryKey class More expressive client code Details can be found in one place Less duplication Easier to add features Much easier to testDagfinn Reiersl, ABC Startsiden31 32. More expressive code and tests What this comment is telling us.../** * [The class] assumes that if you have a compound primary key * and one of the columns in the key uses a sequence, * its the _first_ column in the compound key. */ ...can be expressed as a test.

Search related