PHPRefactor

Table of Contents

1 Books

1.1 Wake, William C.; Refactoring Workbook; 2003

1.1.1 https://xp123.com/articles/refactoring-workbook/

  1. Taxonomy
    1. Mika Mäntylä
    2. Joshua Kerievsky

      przerwałem bo mam tylko draft a nie pełną książke

1.2 Fowler, Martin; 2018

  • With any introductory example, however, I run into a problem. If I pick a large program, describing it and how it is refactored is too complicated for a mortal reader to work through.(…) However, if I pick a program that is small enough to be comprehensible, refactoring does not look like it is worthwhile.
  • Thus, if I’m faced with modifying a program with hundreds of lines of code, I’d rather it be structured into a set of functions and other program elements that allow me to understand more easily what the program is doing. If the program lacks structure, it’s usually easier for me to add structure to the program first, and then make the change I need.
  • If the code works and doesn’t ever need to change, it’s perfectly fine to leave it alone. It would be nice to improve it, but unless someone needs to understand it, it isn’t causing any real harm. Yet as soon as someone does need to understand how that code works, and struggles to follow it, then you have to do something about it.
  • Refactoring changes the programs in small steps, so if you make a mistake, it is easy to find where the bug is.
  • Any fool can write code that a computer can understand. Good programmers write code that humans can understand.
  • The true test of good code is how easy it is to change it.
  • Refactoring (noun): a change made to the internal structure of software to make it easier to understand and cheaper to modify without changing its observable behavior
  • Refactoring (verb): to restructure software by applying a series of refactorings without changing its observable behavior.
  • Refactoring is very similar to performance optimization, as both involve carrying out code manipulations that don’t change the overall functionality of the program. The difference is the purpose: Refactoring is always done to make the code “easier to understand and cheaper to modify.” This might speed things up or slow things down. With performance optimization, I only care about speeding up the program, and am prepared to end up with code that is harder to work with if I really need that improved performance.
  • Without refactoring, the internal design—the architecture—of software tends to decay.As people change code to achieve short­term goals, often without a full comprehensionof the architecture, the code loses its structure. It becomes harder for me to see thedesign by reading the code. Loss of the structure of code has a cumulative effect. Theharder it is to see the design in the code, the harder it is for me to preserve it, and themore rapidly it decays. Regular refactoring helps keep the code in shape.
  • It reminds me of a statement Kent Beck often makes about himself: “I’m not a great programmer; I’m just a good programmer with great habits.” Refactoring helps me be much more effective at writing robust code.
  • Branches. As I write this, a common approach in teams is for each team member to work on abranch of the code base using a version control system, and do considerable work onthat branch before integrating with a mainline (often called master or trunk) sharedacross the team. Often, this involves building a whole feature on a branch, notintegrating into the mainline until the feature is ready to be released into production.Fans of this approach claim that it keeps the mainline clear of any in­process code,provides a clear version history of feature additions, and allows features to be revertedeasily should they cause problems.There are downsides to feature branches like this. The longer I work on an isolatedbranch, the harder the job of integrating my work with mainline is going to be when I’mdone. Most people reduce this pain by frequently merging or re­basing from mainlineto my branch. But this doesn’t really solve the problem when several people areworking on individual feature branches. I distinguish between merging and integration.If I merge mainline into my code, this is a oneway movement—my branch changes butthe mainline doesn’t. I use “integrate” to mean a two­way process that pulls changesfrom mainline into my branch and then pushes the result back into mainline, changingboth. If Rachel is working on her branch I don’t see her changes until she integrateswith mainline; at that point, I have to merge her changes into my feature branch, whichmay mean considerable work. The hard part of this work is dealing with semanticchanges. Modern version control systems can do wonders with merging complexchanges to the program text, but they are blind to the semantics of the code. If I’vechanged the name of a function, my version control tool may easily integrate mychanges with Rachel’s. But if, in her branch, she added a call to a function that I’verenamed in mine, the code will fail.The problem of complicated merges gets exponentially worse as the length of featurebranches increases. Integrating branches that are four weeks old is more than twice ashard as those that are a couple of weeks old. Many people, therefore, argue for keepingfeature branches short—perhaps just a couple of days. Others, such as me, want themeven shorter than that. This is an approach called Continuous Integration (CI), alsoknown as Trunk­Based Development. With CI, each team member integrates withmainline at least once per day. This prevents any branches diverting too far from eachother and thus greatly reduces the complexity of merges. CI doesn’t come for free: Itmeans you use practices to ensure the mainline is healthy, learn to break large featuresinto smaller chunks, and use feature toggles (aka feature flags) to switch off any in­process features that can’t be broken down.Fans of CI like it partly because it reduces the complexity of merges, but the dominantreason to favor CI is that it’s far more compatible with refactoring. Refactorings ofteninvolve making lots of little changes all over the code base—which are particularlyprone to semantic merge conflicts (such as renaming a widely used function). Many ofus have seen feature­branching teams that find refactorings so exacerbate mergeproblems that they stop refactoring. CI and re­factoring work well together, which iswhy Kent Beck combined them in Extreme Programming.I’m not saying that you should never use feature branches. If they are sufficiently short,their problems are much reduced. (Indeed, users of CI usually also use branches, butintegrate them with mainline each day.) Feature branches may be the right techniquefor open source projects where you have infrequent commits from programmers whoyou don’t know well (and thus don’t trust). But in a full­time development team, thecost that feature branches impose on refactoring is excessive. Even if you don’t go to fullCI, I certainly urge you to integrate as frequently as possible. You should also considerthe objective evidence [Forsgren et al.] that teams that use CI are more effective insoftware delivery.

131

1.3 Feathers, Michael; Working Effectively with Legacy Code; 2005

1.4 Others

author title topic info year  
Andrew Hunt, David Thomas The Pragmatic Programmer: From Journeyman to Master overall skip 1999 a lot of tips & tricks but too old
William J. Brown, Raphael C. Malveau, Hays W. "Skip" McCormick, Thomas J. Mowbray Antipatterns. Refactoring Software, Archtectures and Projects in Crisis     1998  
  Refactoring in Large Software Projects     2006  
Phillip A. Laplante, Colin J. Neill Antipatterns: Identification, Refactoring, and Management     2005  
  Fifty Quick Ideas To Improve Your Tests tests      
  Growing Object-Oriented Software, Guided by Tests        
  Working Effectively with Unit Tests        
  Scalable Internet Architectures        
  SQL for Smarties: Advanced SQL Programming database   2005  
  Mastering the SPL Library        
  Anthology The Thoughtworks Anthology - Essays on Software Technology and Innovation     2008  
  Anthology The ThoughtWorks Anthology 2 - More Essays on Software Technology and Innovation     2012  
Beck, Kent Extreme Programming Explained: Embrace Change overall can be read 1999  
Bernstein, David Scott Beyond Legacy Code     2015 Want to read
Bhargava, Aditya Grokking Algorithms: An illustrated guide for programmers and other curious people   must read 2017 Fully illustrated, friendly, easy to read guide, worth to read. When you will stumble upon a problem, you will know how to recognize it and chose the right Algorithm
Bloch, Joshua Effective Java     2018  
Buenosvinos, Carlos Domain-Driven Design in PHP     2016  
Bugayenko, Yegor Elegant Objects     2016 Want to read
Fowler, Martin Refactoring: Improving the Design of Existing Code refactoring must have 1999  
Fowler, Martin Patterns of Enterprise Application Architecture     2012  
Francesco Trucchia, Jacopo Romei Pro PHP Refactoring     2010 Old, without PHP 7 but still the best PHP book about refactoring. Great examples. Worth to have.
Ganesh, Samarthyam; Tushar, Sharma Refactoring for Software Design Smells: Managing Technical Debt     2015 Catalog of smells. Focus on smell point of view. Also they introduce smell classification scheme, naming scheme for design smells which helps to increase awerness of smells. So definetly must read. (Java)
Halladay, Steve Principle-Based Refactoring: Learning Software Design Principles by Applying Refactoring Rules     2012 Want to read
Jones, Paul M. Modernizing Legacy Apps In PHP     2014  
Junade, Ali Mastering PHP Design Patterns     2016 Little bit inmature, poorly written. Having a lot of tips and information about broad variaty of things which is a good place on website but not in the book. Code examples could be much better.
Karwin, Bill SQL Antipatterns - Avoiding The Pitfalls of Database Programming database   2010  
Kerievsky, Joshua Refactoring to Patterns     2004  
Martin, Robert C. Agile Software Development: Principles, Patterns, and Practices     2002  
Martin, Robert C. Clean Code: A Handbook of Agile Software Craftsmanship     2009  
Rahman, Mizanur PHP 7 Data Structures and Algorithms refactoring must have 2017  
Scott J Ambler and Pramod J. Sadalage Refactoring Databases - Evolutionary Database Design refactoring must read 2006 It's catalog of refactorings for database
Stephane Faroult, Pascal L'Hermite Refactoring SQL Application database   2008  
Tornhill, Adam Your Code as a Crime Scene - Use Forensic Techniques to Arrest Defects, Bottlenecks, and Bad Design in Your Programs     2015  
West, David Object Thinking     2004  
Zandstra, Matt PHP Objects, Patterns, and Practice   can be read 2008 Pretty old. But written with precise language and with great examples

2 Tutorials

Automated Tests with PHPUnit Anna Filina testing - PHPUnit
Catalog of Refactoring to Patterns Joshua Kerievsky patterns
Catalog of Refactorings Martin Fowler refactoring
Clean Application Development Adam Culp clean application
Clean Code PHP Piotr Plenik clean code
Detecting Code Smells Patkós Csaba refactoring
Doctrine Best Practices Marco Pivetta (Ocramius) doctrine
Encapsulation and SOLID Mark Seemann SOLID
Extremely Defensive PHP Programming Marco Pivetta (Ocramius) defensive programming
How to Refactor Like a Boss 1 Michael Cheng refactoring
How to Refactor Like a Boss 2 Michael Cheng refactoring
HTTP Smoke Testing Peter Heinz testing - smoke test
Introduction to Testing with PHPUnit Trevor Sawler testing - PHPUnit
Legacy Coderetreat (Java) Adrian Bolboaca refactoring
List of Static Analysis Tools   analysis
PHP: Testing Legacy Applications Chris Hartjes testing - PHPUnit
PHPUnit: Testing with a Bite Ryan Weaver testing - PHPUnit
Programming Foundations: Refactoring Code Simon Allardice refactoring
Programming Foundations: Test-Driven Development Simon Allardice testing - TDD
Refactoring 101 Adam Culp refactoring
Refactoring Legacy Code Patkós Csaba refactoring
Refactoring Legacy Code Adam Culp refactoring
Refactoring to Collections Adam Wathan collections
Solving the N+1 Problem Paul M. Jones database
Steps Toward Modernizing a Legacy Codebase Paul M. Jones refactoring
Techniques for Refactoring Code Patkós Csaba refactoring

3 Refactorings

3.1 Add PHPDoc

Smell: An array with elements of generic type or method which can throw Exception

public function printUsersNames(array $users): void
{
    foreach ($users as $user) {
        echo $user->getName();
    }
}

3.1.1 Refactor the code

Add PHPDoc - the Object[] notation in addition to an array type-hint to explain what kind of object is expected

/**
 * @param User[] $users
 */
public function printUsersNames(array $users): void
{
    foreach ($users as $user) {
        echo $user->getName();
    }
}

3.2 Add Type-Hint

Smell: A method with no type defined parameters or return type declaration

public function setNumber($number)
{
    $this->number = $number;
}

3.2.1 Refactor the code

public function setNumber(int $number): void
{
    $this->number = $number;
}

3.2.2 Helper

phpdoc-to-typehint adds automatically scalar type hints and return types to all functions and methods using existing PHPDoc annotations

3.3 Consolidate Conditional Expression

Smell: Sequence of conditionals with the same result

class Sale
{
    public function calculateShipping(Customer $customer)
    {
        if ($customer->isEmployee) return 0;
        if ($customer->isGoldCustomer) return 0;
        if ($customer->isHasACoupon) return 0;
        
        if($isUsa) return 10;
        if($isEurope) return 20;
    }
}

3.3.1 Write a test that pass

3.3.2 Refactor the code

Combine them into a single conditional expression and extract it.

class Sale
{
    public function calculateShipping(Customer $customer): int
    {
       if ($this->isFreeShipping()){
           return 0;
       }

        if($isUsa) {
            return 10;
        }

        if($isEurope) {
            return 20;
        }
    }

    private function isFreeShipping(): bool
    {
        return ($customer->isEmployee || $customer->isGoldCustomer || $customer->isHasACoupon);
    }
}

3.3.3 Pass a test

3.4 Consolidate Duplicate Conditional Fragments

Smell: The same fragment of code is in all branches of a conditional expression.

final class Sale
{
    public function calculateTotal(int $price)
    {
        if ($this->isSpecialDeal()) {
            $total = $price * 0.95;
            $this->setTotal($total);
        }
        else {
            $total = $price * 0.98;
            $this->setTotal($total);
        }
    }
}

3.4.1 Write a test that pass

3.4.2 Refactor the code

Move it outside of the expression.

final class Sale
{
    public function calculateTotal(int $price)
    {
        if ($this->isSpecialDeal()) {
            $total = $price * 0.95;
        }
        else {
            $total = $price * 0.98;
        }

        $this->setTotal($total);
    }
}

3.4.3 Pass a test

3.5 Decompose Conditional

Smell: Complicated conditional (if-else) statement.

class Sale
{
    public $expired_at;
    public $amount;

    public function getAmount()
    {
        if(null !== $this->expired_at && $this->expired_at < time())
        {
            $interest = 10;
            $this->amount = $this->amount + ($this->amount / 100 * $interest);
        }
        else
        {
            $discount = 10;
            $this->amount = $this->amount - ($this->amount / 100 * $discount);
        }
        return $this->amount;
    }
}

3.5.1 Write a test that pass

class SaleTest extends TestCase
{
    public function testAmount()
    {
        $sale = new Sale();
        $sale->amount = 10;
        $sale->expired_at = strtotime('-10 days');
        $this->assertEquals(10 + (10 / 100 * 10), $sale->getAmount());
        $sale = new Sale();

        $sale->amount = 10;
        $sale->expired_at = strtotime('+10 days');
        $this->assertEquals(10 - (10 / 100 * 10), $sale->getAmount());
    }
}

3.5.2 Refactor the code

Extract conditional code in a private method. We name the method isExpired() because our conditional chunk of code checks if the sale is expired. We create the private method isExpired() and, with the technique of extract method, we move chunks of code into the new method. The next step is to move each branch of the condition in a private method. We do the same as we did before for each branch. So we create the private method getAmountWithInterest() for the first branch and the method getAmountWithDiscount() for the second branch.

class Sale
{
    public $expired_at;
    public $amount;

    public function getAmount()
    {
        if ($this->isExpired()) {
            return $this->getAmountWithInterest();
        } else {
            return $this->getAmountWithDiscount();
        }
    }

    private function isExpired()
    {
        return !is_null($this->expired_at) && $this->expired_at < time();
    }

    private function getAmountWithInterest()
    {
        $interest = 10;
        return $this->amount + ($this->amount / 100 * $interest);
    }

    private function getAmountWithDiscount()
    {
        $discount = 10;
        return $this->amount - ($this->amount / 100 * $discount);
    }
}

3.5.3 Pass a test

3.6 Encapsulate Field

Smell: A public field

final class User
{
    /**
     * @var string
     */
    public $name;
}

3.6.1 Write a test that pass

3.6.2 Refactor the code

Make it private and provide accessors.

final class User
{
    /**
     * @var string
     */
    private $name;

    public function getName(): string
    {
        return $this->name;
    }

    public function setName(string $name): void
    {
        $this->name = $name;
    }
}

3.6.3 Run a test

3.7 Extract Class

SmellL Large Class

final class User
{
    private $name;
    private $surname;

    private $city;
    private $zipCode;
    private $street;
    private $state;
}

3.7.1 Write a test that pass

3.7.2 Refactor the code

Create a new class and move the relevant fields and methods from the old class into the new class.

final class User
{
    private $name;
    private $surname;

    private $address;
}

final class Address
{
    private $city;
    private $zipCode;
    private $street;
    private $state;
}

3.7.3 Pass a test

3.8 Extract Function

Alias: Extract Method Inverse of: Inline Function Smell: Code fragment that can be grouped together

public function printInvoice(Invoice $invoice): void
{
    echo 'Invoice';
    echo '<br>';
    echo $invoice->getNumber();
   
    echo 'phpRefactor: ';
    echo '<br>';
    echo $invoice->getDate();
}

3.8.1 Write a test that pass

3.8.2 Refactor the code

Turn the fragment into a method whose name explains the purpose of the method

public function printInvoice(Invoice $invoice): void
{
    printInvoiceHeader($invoice);
    printInvoiceFooter($invoice);
}

function printInvoiceHeader(Invoice $invoice): void
{
    echo 'Invoice';
    echo '<br>';
    echo $invoice->getNumber();
}

function printInvoiceFooter(Invoice $invoice): void
{
    echo 'phpRefactor: ';
    echo '<br>';
    echo $invoice->getDate();
}



3.8.3 Run a test

3.8.4 Notes

function printInvoice() {
    $footer=function() {
        echo "phpRefactor.com \n";
        echo "2019";
    };
    
    echo "Header \n";
    $footer();
}
printInvoice();

3.9 Extract Variable

Alias: Introduce Explaining Variable Smell: Complicated expression

if(($stock->checkStatus($order->getItem) > $order->getQuantity()) 
    && ($order->getTotal() > 99) 
    && ($order->getCustomer()->getBillingAddress() === $order->getShippingAddress()));

3.9.1 Write a test that pass

3.9.2 Refactor the code

Put the result of the expression, or parts of the expression, in a temporary variable with a name that explains the purpose

$freeShipping = $order->getTotal() > 99;
$stockAvailable = $stock->checkStatus($order->getItem) > $order->getQuantity();
$addressMatches = $order->getCustomer()->getBillingAddress() === $order->getShippingAddress();
if($stockAvailable && $freeShipping && $addressMatches);

3.9.3 Pass a test

3.10 Inline Class

Smell: A class isn't doing very much

final class User
{
    private $name;
    private $surname;

    private $telephoneNumber;
}

final class TelephoneNumber
{
    private $number;
}

3.11 DONE Inline Function

Smell: A function's body is just as clear as it's name

class Circle
{
    public const RADIUS = 2;

    public function getArea(): float
    {
        return $this->getValueOfPI() * self::RADIUS * self::RADIUS;
    }

    private function getValueOfPI(): float
    {
        return pi();
    }
}

3.11.1 Write a test that pass

use PHPUnit\Framework\TestCase;

class CircleTest extends TestCase
{   
    public function testGetArea()
    {
        $circle = new Circle();
        $this->assertEquals( 12.566370614359172, $circle->getArea());
    }
}
PHPUnit 7.4.3 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 80 ms, Memory: 10.00MB

OK (1 test, 1 assertion)

3.11.2 Refactor the code

Put the method's body into the body of its callers and remove the method.

public function getArea(): float
{
    return pi() * $radius * $radius;
}

3.11.3 Pass a test

3.12 Introduce Parameter Object

Smell: Long Parameter List and parameters that naturally go together

final class Account
{
    public function findAllTransactions(DateTime $start, DateTime $end)
    {
        ...
    }
}

3.12.1 Write a test that pass

3.12.2 Refactor the code

Replace them with an object. #+BEGINSRC php#+ENDSRC

3.12.3 Pass a test

3.13 Optimize Imports

Smell: Imports unused or not in alphabetically order. Multiple use statement

use SomeClass\Worker;
use SomeClass\Foo;
use SomeClass\UnusedClass;

3.13.1 Write a test that pass

3.13.2 Refactor the code

Remove unused imports. Sort imports alphabetically (ascending order). Splits multiple use statement imports into single use statement imports

use SomeClass\{Foo, Worker};

3.13.3 Pass a test

3.14 Move Method

Smell: Method accessing fields and methods in different class

final class Customer
{
    function printInvoice(Order $order)
    {
        echo "Invoice {$order->getId()}";
        echo "Date: {$order->getDate()}";
        echo "Customer: {$this->getName()}";

        $address = $order->getAddress();
        echo "City: {$address->getCity()}";
        echo "Address: {$address->getStreet()}";

        foreach ($order->getItems() as $item){

            echo "Name: {$item->getName()}";
            echo "Price: {$item->getPrice()}";
        }
    }
}   

3.14.1 Write a test that pass

3.14.2 Refactor the code

Move all its features into another class and delete it.

final class Order
{
    function printInvoice()
    {
        echo "Invoice {$this->getId()}";
        echo "Date: {$this->getDate()}";
        echo "Customer: {$this->getCustomer()->getName()}";

        $address = $this->getAddress();
        echo "City: {$address->getCity()}";
        echo "Address: {$address->getStreet()}";

        foreach ($this->getItems() as $item){

            echo "Name: {$item->getName()}";
            echo "Price: {$item->getPrice()}";
        }
    }
}

3.14.3 Pass a test

3.15 Parameterize Method

Smell: Several methods do similar things but with different values contained in the method body.

final class Employee
{
    /**
     * @var float
     */
    private $salary;

    public function setSalary(float $salary)
    {
        $this->salary = $salary;
    }

    public function getSalary(): float
    {
        return $this->salary;
    }

    public function fivePercentRaise()
    {
        $this->salary += $this->salary* (5 / 100);
    }

    public function tenPercentRaise()
    {
        $this->salary += $this->salary* (10 / 100) ;
    }
}

3.15.1 TODO Write a test that pass

3.15.2 Refactor the code

Create one method that uses a parameter for the different values.

final class Employee
{
    /**
     * @var float
     */
    private $salary;

    public function setSalary(float $salary)
    {
        $this->salary = $salary;
    }

    public function getSalary(): float
    {
        return $this->salary;
    }

    public function raise(float $percent)
    {
        $this->salary += $this->salary * ($percent / 100);
    }
}

3.15.3 Pass a test

3.16 Preserve Whole Object

Smell: More than one value from an object are passing as parameters in a method call

class September
{
    /**
     * @var float
     */
    private $highestTemp;

    /**
     * @var float
     */
    private $lowestTemp;

    public function __construct(float $highestTemp, float $lowestTemp)
    {
        $this->highestTemp = $highestTemp;
        $this->lowestTemp = $lowestTemp;
    }

    public function getHighestTemp(): float
    {
        return $this->highestTemp;
    }

    public function getLowestTemp(): float
    {
        return $this->lowestTemp;
    }
}

class Calculator
{
    public function calculateAverageTemperature(float $highestTemp, float $lowestTemp)
    {
        return ($highestTemp + $lowestTemp) / 2;
    }
}

$september = new September(15,5);
$calculator = new Calculator();

$averageTemperature = $calculator->calculateAverageTemperature(
            $september->getHighestTemp(),
            $september->getLowestTemp()
        );

3.16.1 Write a test that pass

public function testCalculateAverageTemperature()
{
        $september = new September(15,5);
        $calculator = new Calculator();

        $averageTemperature = $calculator->calculateAverageTemperature(
            $september->getHighestTemp(),
            $september->getLowestTemp());

        $this->assertEquals(10, $averageTemperature);
}

3.16.2 Refactor the code

Add object as a new parameter. Set it default value to null, that will help to manage the transitions towards the final version of the method.

class Calculator
{
    public function calculateAverageTemperature(float $highestTemp, float $lowestTemp, September $september = null)
    {
        return ($highestTemp + $lowestTemp) / 2;
    }
}

3.16.3 Pass a test

3.16.4 Refactor the code

Replace values with values coming from the whole object

class Calculator
{
    public function calculateAverageTemperature(float $highestTemp, float $lowestTemp, September $september = null)
    {
        return ($september->getHighestTemp() + $september->getLowestTemp()) / 2;
    }
}

3.16.5 Pass a test

3.16.6 Refactor the code

Remove useless parameters and default null value of $september object

class Calculator
{
    public function calculateAverageTemperature(September $september)
    {
        return ($september->getHighestTemp() + $september->getLowestTemp()) / 2;
    }
}

3.16.7 Pass a test

3.17 Pull Up Method

Smell: Subclasses have the same method.

class Employee
{
    /**
     * @var string
     */
    protected $name;
    
    public function __construct(string $name)
    {
        $this->name = $name;
    }
}

final class Salesman extends Employee
{
    public function getName()
    {
        return $this->name;
    }
}

final class Engineer extends Employee
{
    public function getName()
    {
        return $this->name;
    }
}

3.17.1 Write a test that pass

3.17.2 Refactor the code

Move the methods to the super class.

class Employee
{
    /**
     * @var string
     */
    protected $name;

    public function __construct(string $name)
    {
        $this->name = $name;
    }

    public function getName()
    {
        return $this->name;
    }
}

final class Salesman extends Employee {}

final class Engineer extends Employee {}

3.17.3 Pass a test

3.18 Remove Assignments to Parameters

Smell: Reassign to a parameter

public function discount(int $priceTotal): int
{
    if ($priceTotal > 100) {
        $priceTotal = $priceTotal - 10;
    }
    
    return $priceTotal;
}  

3.18.1 Write a test that pass

3.18.2 Refactor the code

Use a temporary variable instead

public function discount(int $priceTotal): int
{
    $result = $priceTotal;

    if ($priceTotal > 100) {
        $result = $result - 10;
    }

    return $result;
}

3.18.3 Pass a test

3.18.4 Info

The best practice is that if you pass parameters into a method then they should always represent what were passed in and never be reassigned to mean something else. Btw. in Java you can prevent variable’s reassignment by keyword 'final' before a parameter https://stackoverflow.com/questions/500508/why-should-i-use-the-keyword-final-on-a-method-parameter-in-java

3.19 Remove PHPDoc

Smell: PHPDoc is duplicating type-hint information Damage: Adds information which not provides additional value

/**
 * @param int $number
 * @return void
 */
public function setNumber(int $number): void
{
    $this->number = $number;
}  

3.19.1 Write a test that pass

3.19.2 Refactor the code

Remove PHPDoc if it's not provides additional value


3.19.3 Pass a test

3.20 Rename Method

Smell: The name of a method does not reveal it's purpose1

public function getInvcdtlmt()

3.20.1 Write a test that pass

3.20.2 Refactor the code

Change the name of the method

public function getInvoiceableCreditLimit()

3.20.3 Pass a test

3.21 Replace Global with Dependency Injection

Smell: Variable with 'global' keyword

final class Item
{
    public function fetch()
    {
        global $db;
        return $db->query(...);
    }
}

3.21.1 Write a test that pass

3.21.2 Refactor the code

Move global variable in class to the constructor

final class Item
{
    /**
     * @var Database
     */
    private $db;

    public function __construct(Database $db)
    {
        $this->db = $db;
    }

    public function fetch()
    {
        return $db->query(...);
    }
}

3.21.3 Pass a test

3.22 DONE Replace Magic Number With Symbolic Constant

Smell: Number with a particular meaning

final class Circle
{
    /**
     * @var float
     */
    private $radius;
    
    public function __construct(float $radius)
    {
        $this->radius = $radius;
    }
    
    public function getCircumference(): float
    {
        return $this->radius * 2 * 3.1416;
    }
}

3.22.1 Write a test that pass

use PHPUnit\Framework\TestCase;

class CircleTest extends TestCase
{   
    public function testGetCircumference()
    {
        $circle = new Circle(2);
        $this->assertEquals(12.5664, $circle->getCircumference());
    }
}
PHPUnit 7.5.2 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 151 ms, Memory: 10.00MB

OK (1 test, 1 assertion)

3.22.2 Refactor the code

Create a constant, name it after the meaning, and replace the number with it

final class Circle
{
    /**
     * @var float
     */
    private const PI = 3.1416;

    /**
     * @var float
     */
    private $radius;

    public function __construct(float $radius)
    {
        $this->radius = $radius;
    }

    public function getCircumference(): float
    {
        return $this->radius * 2 * self::PI;
    }
}

3.22.3 Run a test

PHPUnit 7.4.3 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 60 ms, Memory: 10.00MB

OK (1 test, 1 assertion)

3.22.4 Helper:

PHP Magic Number Detector is a tool to detect magic numbers in your PHP code

3.23 Replace Parameter with Method

Smell: A method runs different code depending on the values of parameters

final class EmailNotification
{
    public function send(string $to, string $body, string $from = null)
    {
        if($from){
            $this->mailer->send($to, $body, $from);
        }else{
            $this->mailer->send($to, $body, $this->defaultSender);
        }
    }
}

3.23.1 Write a test that pass

3.23.2 Refactor the code

Create a separate method for each value of the parameter

final class EmailNotification
{
    public function send(string $to, string $body, string $from)
    {
        $this->mailer->send($to, $body, $from);
    }
    
    public function sendFromDefaultSender(string $to, string $body)
    {
        $this->mailer->send($to, $body, $this->defaultSender);
    }
}

3.23.3 Pass a test

3.24 Replace Temp with Query

Smell: Using a temporary variable to hold the result of an expression. Damage: Temporary variable increase the temptation to write longer methods. Temporaries aren’t necessarily bad, but sometimes they attract new code.

public function getTotalPrice(): int
{
    $basePrice = $this->quantity * $this->itemPrice;

    if ($basePrice > self::DISCOUNT_POINT) {
        return $basePrice * self::DISCOUNT;
    }
    return $basePrice;
}

3.24.1 Write a test that pass

3.24.2 Refactor the code

Extract the expression into a method. Replace all references to the temp with the expression. The new method can then be used in other methods.

public function getTotalPrice(): int
{
    if ($this->getBasePrice() > self::DISCOUNT_POINT) {
        return $this->getBasePrice() * self::DISCOUNT;
    }
    return $this->getBasePrice();
}

public function getBasePrice(): int
{
    $this->quantity * $this->itemPrice;
}

3.24.3 Pass a test

3.24.4 Info

Now, but wait, you might say. Isn't this more inefficient? Because if we created the temp the old way, we'd only have to execute the expression once, but if we turn it into a method, we might be calling it many different times. And yes, you're absolutely right, but remember, the pure efficiency of the code is not our first goal in refactoring. Clarity is. The likelihood is that a typical expression you would deal with in this sort of refactoring is going to be so undemanding, it wouldn't be noticeable at all, even having to call it several more times. But if it is an intensive operation, an intensive expression, well you should really be working on that later, after you've refactored using profilers. And other tools to make sure you're not doing pointless, premature optimization. And the real benefit is that by creating this as its own method, we will also have use of it anywhere else in the class, which wasn't the case before. As the original temp was scoped to the original method. So, we won't be tempted to add more code to the original method just to have access to that temp.

3.25 Replacing Type Code with Subclasses

Smell: Immutable type code affecting the class behavior.

final class Account
{
    /**
     * @var int
     */
    private $accountType;

    /**
     * @var float
     */
    private $balance;

    /**
     * @var int
     */
    public const CHECKING = 0;

    /**
     * @var int
     */
    public const SAVINGS = 1;

    /**
     * @var int
     */
    public const INVESTMENT = 2;

    public function __construct(int $accountType)
    {
        $this->accountType = $accountType;
    }

    public function getAccountType(): int
    {
        return $this->accountType;
    }

    public function getBalance(): float
    {
        return $this->balance;
    }

    public function withdraw(float $amount): void
    {
        switch ($this->accountType){
            case self::CHECKING:
                $this->balance -= $amount;
                break;
            case self::SAVINGS:
                $this->balance -= $amount + 100;
                break;
            case self::INVESTMENT:
                $this->balance -= $amount + 300;
                break;
            default:
                throw new RuntimeException('Unknown Account Type');
        }
    }
}

3.25.1 Write a test that pass

3.25.2 Refactor the code

Replace the type code with subclasses.

abstract class Account
{
    /**
     * @var float
     */
    private $balance;

    public function getBalance(): float
    {
        return $this->balance;
    }
    
    abstract public function withdraw(float $amount): void
}

final class AccountChecking extends Account
{
    public function withdraw(float $amount): void
    {
        $this->balance -= $amount;
    }
}

final class AccountSavings extends Account
{
    public function withdraw(float $amount): void
    {
        $this->balance -= $amount + 100;
    }
}

final class AccountInvestment extends Account
{
    public function withdraw(float $amount): void
    {
        $this->balance -= $amount + 300;
    }
}

3.25.3 Pass a test

3.26 Separate Query from Modifier

Smell: A method that returns a value but also changes the state of an object.

final class Account
{
    /**
     * @var float
     */
    private $balance;
    
    public function withdrawAndGetBalance(float $amount): float
    {
        $this->balance -= $amount;
        return $this->balance;
    }
}

3.26.1 Write a test that pass

3.26.2 Refactor the code

Create two methods, one for the query and one for the modification.

final class Account
{
    /**
     * @var float
     */
    private $balance;

    public function getBalance(): float
    {
        return $this->balance;
    }

    public function withdraw(float $amount): void
    {
        $this->balance -= $amount;
    }
}

3.26.3 Pass a test

3.27 Split Temporary Variable

Smell: Temporary variable is assigned to more than once (overwrite), but is not a loop variable nor a collecting temporary variable.

$temp = $item.getPrice() * item.getQuantity();
echo "Total: $temp";
$temp = order.getTotal() - order.getDiscount();
echo "Price after discount: $temp;"

3.27.1 Write a test that pass

3.27.2 Refactor the code

Make a separate temporary variable for each assignment.

$totalPrice = $item.getPrice() * item.getQuantity();
echo "Total: $totalPrice";
$totalDiscountPrice = order.getTotal() - order.getDiscount();
echo "Price after discount: $totalDiscountPrice";

3.27.3 Pass a test

4 TODO Smells

4.1 Booch’s fundamental design principles

4.1.1 abstraction

  1. Missing Abstraction

    Clumps of data or encoded strings are used instead of creating a class or an interface

    PROBLEMS:

    • it can expose implementation details to different abstractions, violating the principle of encapsulation.
    • When data and associated behavior are spread across abstractions, it can

    lead to tight coupling between entities, resulting in brittle and non-reusable code. Hence, not creating necessary abstractions also violates the principle of modularization.

    POTENTIAL CAUSES:

    Inadequate design analysis When careful thought is not applied during design, it is easy to overlook creating abstractions and use primitive type values or strings to “get the work done.” In our experience, this often occurs when software is developed under tight deadlines or resource constraints.

    Lack of refactoring As requirements change, software evolves and entities that were earlier represented using strings or primitive types may need to be refactored into classes or interfaces. When the existing clumps of data or encoded strings are retained as they are without refactoring them, it can lead to a Missing Abstraction smell.

    Misguided focus on minor performance gains This smell often results when designers compromise design quality for minor performance gains. For instance, we have observed developers using arrays directly in the code instead of creating appropriate abstractions since they feel that indexing arrays is faster than accessing members in objects. In most contexts, the performance gains due to such “optimizations” are minimal, and do not justify the resultant trade-off in design quality.

    https://steemit.com/php/@crell/php-use-associative-arrays-basically-never

    REFACTOR - The refactoring for this smell is to create abstraction(s) that can internally make use of primitive type values or strings. For example, if a primitive type value is used as a “type-code,” then apply “replace type-code with class”.

    ALIAS: Primitive Obsession — This smell occurs when primitive types are used for encoding dates, currency, etc. instead of creating classes.

    Data clumps — This smell occurs when there are clumps of data items that occur together in lots of places instead of creating a class.

  2. Imperative Abstraction

    Consider the case of a large-sized financial application. This application employs classes named CreateReport, CopyReport, DisplayReport, etc. to deal with its report generation functionality. Each class has exactly one method definition named create, copy, display, etc., respectively, and suffers from Imperative Abstraction smell. The data items relating to a report such as name of the report, data elements that need to be displayed in the report, kind of report, etc. are housed in a “data class” named Report. The smell not only increases the number of classes (in this case there are at least four classes when ideally one could have been used), but also increases the complex- ity involved in development and maintenance because of the unnecessary separation of cohesive methods

    Reification “Reification” is the promotion or elevation of something that is not an object into an object. When we reify behavior, it is possible to store it, pass it, or transform it. Reifica- tion improves flexibility of the system at the cost of introducing some complexity [52]. Many design patterns [54] employ reification. Examples:

    • State pattern: Encoding a state-machine.

    • Command pattern: Encoding requests as command objects. A permitted excep- tion for this smell is when a Command pattern has been used to objectify method requests.

    • Strategy pattern: Parameterizing a procedure in terms of an operation it uses.

    In other words, when we consciously design in such a way to elevate non-objects to objects for better reusability, flexibility, and extensibility (i.e., for improving design quality), it is not a smell.

    ALIASES “Operation class” [51,52] — This smell occurs when an operation that should have been a method within a class has been turned into a class itself.

  3. Incomplete Abstraction

    An abstraction (entity or interface) does not support complementary or interrelated methods completely For example, if we need to be able to add or remove elements in a data structure, the type abstracting that data structure should support both add() and remove() methods. Supporting only one of them makes the abstraction incomplete and incoherent in the context of those interrelated methods.

    missing “complementary and symmetric” methods,

    Min/max Open/close Create/destroy Get/set Read/write Print/scan First/last Begin/end Start/stop Lock/unlock Show/hide Up/down First/last Push/pull Enable/disable Left/right On/off

    Sometimes, a designer may make a conscious design decision to not provide sym- metric or matching methods. For example, in a read-only collection, only add() method may be provided without the corresponding remove() method. In such a case, the abstraction may appear incomplete, but is not a smell.

    Sometimes, APIs choose to replace symmetrical methods with a method that takes a boolean argument (for instance, to enforce a particular naming convention such as naming convention that requires accessors to have prefixes “get,” “is,” or “set”). For example, classes such as java.awt.MenuItem and java.awt.Component originally supported disable() and enable() methods. These methods were dep- recated and are now replaced with setEnabled(boolean) method. Similarly, java. awt.Component has the method setVisible(boolean) that deprecates the methods show() and hide(). One would be tempted to mark these classes as Incomplete Abstractions since they lack symmetric methods, i.e., getEnabled() and getVisi- ble() respectively. However, since there is no need for corresponding getter methods (as these methods take a boolean argument), these classes do not have Incomplete Abstraction smell.

    ALIASES This smell is also known in literature as:

    • “Class supports incomplete behavior” [18]—This smell occurs when the public interface of a class is incomplete and does not support all the behavior needed by objects of that class.

    • “Half-hearted operations” [63]—This smell occurs when interrelated methods provided in an incomplete or in an inconsistent way; this smell could lead to runtime problems.

  4. Multifaceted Abstraction

    This smell arises when an abstraction has more than one responsibility assigned to it.

    In particular, the Single Responsibility Principle says that an abstraction should have a single well-defined responsibility and that responsibility should be entirely encapsulated within that abstraction.

    ALIASES This smell is also known in literature as:

    • “Divergent change” [7]—This smell occurs when a class is changed for differ- ent reasons.

    • “Conceptualization abuse” [30]—This smell occurs when two or more non- cohesive concepts have been packed into a single class of the system.

    • “Large class” [7,24,57,58]—This smell occurs when a class has “too many” responsibilities.

    • “Lack of cohesion” [59]—This smell occurs when there is a large type in a design with low cohesion, i.e., a “kitchen sink” type that represents many abstractions.

  5. Unnecessary Abstraction

    An abstraction that is not needed ALIASES: Irrelevant class - class does not have any meaningful behavior in the design Lazy class / Freeloader — class does “too little” Small class - class has no (or too few) variables or no (or too few) methods in it Mini-class - a public, non-nested class defines less than three methods and less than three attributes (including constants) in it No responsibility - class has no responsibility associated with it Agent classes - class serve as an “agent” (i.e., they only pass messages from one class to another), indicating that the class may be unnecessary

  6. Unutilized Abstraction

    UNUTILIZED ABSTRACTION An abstraction is left unused (either not directly used or not reachable). This smell manifests in two forms:

    • Unreferenced abstractions—Concrete classes that are not being used by anyone • Orphan abstractions—Stand-alone interfaces/abstract classes that do not have any derived abstractions

    This smell violates the principle YAGNI (You Aren’t Gonna Need It), which recommends not adding functionality until deemed necessary [53]

    When an abstraction is left unused in design, it does not serve a meaningful purpose in design, and hence violates the principle of abstraction.

    POTENTIAL CAUSES: Leftover garbage during maintenance or refactoring. Speculative generality - abstractions are introduced speculating that they may be required sometime in future.

    REFACTORING: remove the Unutilized Abstraction from the design.

    IMPACT: pollutes the design space and increases cognitive load. This impacts understandability. UNUTILIZED ABSTRACTION Two or more abstractions have identical names or identical implementation or both

    POTENTIAL CAUSES:

    Copy-paste programming The “get-the-work-done” mindset of a programmer leads him to copy and paste code instead of applying proper abstraction. Ad hoc maintenance When the software undergoes haphazard fixes or enhancements over many years, it leaves “crufts”6 with lots of redundant code in it. Lack of communication Often, in industrial software, code duplication occurs because different people work on the same code at different times in the life cycle of the software. They are not aware of existing classes or methods and end up re-inventing the wheel.

    REFACTORING: For identical name form, the suggested refactoring is to rename one of the abstrac- tions to a unique name. In the case of the identical implementation form of Duplicate Abstraction, if the implementations are exactly the same, one of the implementations can be removed. If the implementations are slightly different, then the common implementation in the duplicate abstractions can be factored out into a common class.

    IMPACT: it affects understandability of the design. Developers of client code will be confused and unclear about the choice of the abstraction that should be used by their code.

    identical implementation (i.e., they have duplicate code), it becomes difficult to maintain them. In summary, this smell indicates a violation of the DRY (Don’t Repeat Yourself) principle. If the DRY principle is not fol- lowed, a modification of an element within the system requires modifications to other logically unrelated elements making maintainability a nightmare. Since there is dupli- cation among abstractions in the design, this smell is named Duplicate Abstraction.

    3.7.6 ALIASES This smell is also known in literature as:

    • “Alternative classes with different interfaces” [7]—This smell occurs when classes do similar things, but have different names. • “Duplicate design artifacts” [74]—This smell occurs when equivalent design artifacts are replicated throughout the architecture.

  7. Duplicate Abstraction

    UNUTILIZED ABSTRACTION Two or more abstractions have identical names or identical implementation or both

    POTENTIAL CAUSES:

    Copy-paste programming The “get-the-work-done” mindset of a programmer leads him to copy and paste code instead of applying proper abstraction. Ad hoc maintenance When the software undergoes haphazard fixes or enhancements over many years, it leaves “crufts”6 with lots of redundant code in it. Lack of communication Often, in industrial software, code duplication occurs because different people work on the same code at different times in the life cycle of the software. They are not aware of existing classes or methods and end up re-inventing the wheel.

    REFACTORING: For identical name form, the suggested refactoring is to rename one of the abstrac- tions to a unique name. In the case of the identical implementation form of Duplicate Abstraction, if the implementations are exactly the same, one of the implementations can be removed. If the implementations are slightly different, then the common implementation in the duplicate abstractions can be factored out into a common class.

    IMPACT: it affects understandability of the design. Developers of client code will be confused and unclear about the choice of the abstraction that should be used by their code.

    identical implementation (i.e., they have duplicate code), it becomes difficult to maintain them. In summary, this smell indicates a violation of the DRY (Don’t Repeat Yourself) principle. If the DRY principle is not fol- lowed, a modification of an element within the system requires modifications to other logically unrelated elements making maintainability a nightmare. Since there is dupli- cation among abstractions in the design, this smell is named Duplicate Abstraction.

    3.7.6 ALIASES This smell is also known in literature as:

    • “Alternative classes with different interfaces” [7]—This smell occurs when classes do similar things, but have different names. • “Duplicate design artifacts” [74]—This smell occurs when equivalent design artifacts are replicated throughout the architecture.

4.1.2 encapsulation

4.1.3 modularization

4.1.4 hierarchy

4.2 Normal

4.2.1 Alias Duplicates

use App\Model\Category\Query as CategoryQuery;
use App\Model\Product\Contract\Query as ProductQueryInterface;
use App\Contract\Query as

https://www.tomasvotruba.cz/blog/2019/05/02/alias-as-a-code-smell/

4.2.2 Alternative Classes With Different Interfaces

4.2.3 Comments

If you need a comment to explain what a block of code does, try Extract Function (106). If the method is already extracted but you still need a comment to explain what it does, use Change Function Declaration (124) to rename it. If you need to state some rules about the required state of the system, use Introduce Assertion (302).

4.2.4 Data Class

4.2.5 Data Clumps

Fix: Use Extract Class (182) on the fields to turn the clumps into an object.

4.2.6 Divergent Change

Within Classes

4.2.7 Duplicated Code

Within Classes Same code structure in more than one place Don't Repeat Yourself (DRY)

  1. Same expression in different places

    The simplest duplicated code problem is when you have the same expression in two methods of the same class. Then all you have to do is Extract Function and invoke the code from both places.

4.2.8 Feature Envy

Between Classes A method accesses the data of another object more than its own data

4.2.9 Global Data

"Global data is especially nasty when it’s mutable. Global data that you can guarantee never changes after the program starts is relatively safe—if you have a language that can enforce that guarantee." Fix: Encapsulate Variable

4.2.10 Insider Trading

4.2.11 Large Class

Within Classes A class contains too many fields, methods, lines of code. Fix: Extract Class, Extract Superclass, Replace Type Code with Subclass

4.2.12 Lazy Element

It may be a function that’s named the same as its body code reads, or a class that is essentially one simple function. Fix: Inline Function, Inline Class, Collapse Hierarchy

4.2.13 Long Function

Within Classes Fix: Extract Function

4.2.14 Long Parameter List

Within Classes Fix: Replace Parameter with Query, Preserve Whole Object, INtroduce PArameter Object, Remove Flag Argument, Combine Functions into Class

4.2.15 Loops

Fix: Replace Loop with Pipeline

4.2.16 Message Chains

You see message chains when a client asks one object for another object, which the client then asks for yet another object, which the client then asks for yet another another object, and so on.

95

4.2.17 Middle Man

Fix: Remove Middle Man, Inline Function, Replace Superclass with Delegate, Replace Subclass with Delegate

4.2.18 Mysterious Name

Fix: Change Function Declaration, Rename Variable, Rename Field

4.2.19 Primitive Obsession

Primitive types: integers, floating point numbers and strings. Money, coordinates or ranges. Fix: Replace Primitive with Object

4.2.20 Refused Bequest

4.2.21 Repeated Switches

4.2.22 Shotgun Surgery

4.2.23 Speculative Generality

“Oh, I think we’ll need the ability to do this kind of thing someday”

4.2.24 Temporary Field

5 Codebases

Name Lines of Code Description Tutorials
Video Rental Shop 132 From Martin Fowler book "Refactoring: Improving the Design of Existing Code" converted to PHP Refactoring 101
Legacy Code Retreat - Trivia Game 196 Designed for Legacy Code Retreat events Refactoring Legacy Code
      Techniques for Refactoring Code
      Legacy Coderetreat (Java)

5.1 Video Rental Shop

phploc Size Lines of Code (LOC) 132 Comment Lines of Code (CLOC) 12 (9.09%) Non-Comment Lines of Code (NCLOC) 120 (90.91%) Logical Lines of Code (LLOC) 48 (36.36%) Classes 40 (83.33%) Average Class Length 13 Minimum Class Length 6 Maximum Class Length 24 Average Method Length 2 Minimum Method Length 1 Maximum Method Length 19 Functions 0 (0.00%) Average Function Length 0 Not in classes or functions 8 (16.67%)

Cyclomatic Complexity Average Complexity per LLOC 0.17 Average Complexity per Class 3.67 Minimum Class Complexity 1.00 Maximum Class Complexity 9.00 Average Complexity per Method 1.73 Minimum Method Complexity 1.00 Maximum Method Complexity 9.00

Dependencies Global Accesses 0 Global Constants 0 (0.00%) Global Variables 0 (0.00%) Super-Global Variables 0 (0.00%) Attribute Accesses 15 Non-Static 15 (100.00%) Static 0 (0.00%) Method Calls 14 Non-Static 14 (100.00%) Static 0 (0.00%)

Structure Namespaces 0 Interfaces 0 Traits 0 Classes 3 Abstract Classes 0 (0.00%) Concrete Classes 3 (100.00%) Methods 11 Scope Non-Static Methods 11 (100.00%) Static Methods 0 (0.00%) Visibility Public Methods 11 (100.00%) Non-Public Methods 0 (0.00%) Functions 0 Named Functions 0 (0.00%) Anonymous Functions 0 (0.00%) Constants 3 Global Constants 0 (0.00%) Class Constants 3 (100.00%)

5.2 Legacy Code Retreat - Trivia Game

phploc Size Lines of Code (LOC) 196 Comment Lines of Code (CLOC) 0 (0.00%) Non-Comment Lines of Code (NCLOC) 196 (100.00%) Logical Lines of Code (LLOC) 99 (50.51%) Classes 88 (88.89%) Average Class Length 88 Minimum Class Length 88 Maximum Class Length 88 Average Method Length 7 Minimum Method Length 1 Maximum Method Length 17 Functions 1 (1.01%) Average Function Length 1 Not in classes or functions 10 (10.10%)

Cyclomatic Complexity Average Complexity per LLOC 0.26 Average Complexity per Class 25.00 Minimum Class Complexity 25.00 Maximum Class Complexity 25.00 Average Complexity per Method 3.18 Minimum Method Complexity 1.00 Maximum Method Complexity 10.00

Dependencies Global Accesses 0 Global Constants 0 (0.00%) Global Variables 0 (0.00%) Super-Global Variables 0 (0.00%) Attribute Accesses 115 Non-Static 115 (100.00%) Static 0 (0.00%) Method Calls 21 Non-Static 21 (100.00%) Static 0 (0.00%)

Structure Namespaces 0 Interfaces 0 Traits 0 Classes 1 Abstract Classes 0 (0.00%) Concrete Classes 1 (100.00%) Methods 11 Scope Non-Static Methods 11 (100.00%) Static Methods 0 (0.00%) Visibility Public Methods 11 (100.00%) Non-Public Methods 0 (0.00%) Functions 1 Named Functions 1 (100.00%) Anonymous Functions 0 (0.00%) Constants 0 Global Constants 0 (0.00%) Class Constants 0 (0.00%)

6 Metrics

6.1 Cyclomatic Complexity Number (CCN)

Counts the available decision paths in a source code to determine it's complexity. Each decision path starts with one of the conditional statements from the following list:

  • ?
  • &&
  • ||
  • or
  • and
  • xor
  • case
  • catch
  • elseif
  • for
  • foreach
  • if
  • while

Cyclomatic Complexity Number is never less than 1, because there’s always at least one code path.

  • 1-4 has low complexity.
  • 5-7 is moderate and still easy to understand.
  • 6-10 has a high complexity.
  • 10+ is very complex and hard to understand.
final class CyclomaticComplexityNumber
{
    // Class Cyclomatic Complexity = 1
}

final class CyclomaticComplexityNumber
{

    public function one()
    {
        // Function Cyclomatic Complexity = 1
    }

    public function two()
    {
        // Function Cyclomatic Complexity = 1
    }

    // Class Cyclomatic Complexity = 1
}

final class CyclomaticComplexityNumber
{

    public function one()
    {
        if(true){

        }
        // Function Cyclomatic Complexity = 2
    }

    public function two()
    {
        // Function Cyclomatic Complexity = 1
    }

    // Class Cyclomatic Complexity = 1 + 1 = 2
}

final class CyclomaticComplexityNumber
{

    public function one()
    {
        if(true){

        }
        // Function Cyclomatic Complexity = 2
    }

    public function two()
    {
        if(true){

        }
        // Function Cyclomatic Complexity = 2
    }

    // Class Cyclomatic Complexity = 1 + 1 + 1 = 3
}

6.2 NPath Complexity

function foo($a, $b)
{
    if ($a > 10) {
        echo 1;
    } else {
        echo 2;
    }
    if ($a > $b) {
        echo 3;
    } else {
        echo 4;
    }
}

So here we have function with 4 possible outcomes, since we have 2 statements that have 2 possible outcomes each (2 * 2 = 4). That means that the functions Npath complexity is 4. If we would add another statement with 2 possible outcomes we would get a complexity of 8 since 2 * 2 * 2 = 8.

7 About

It's a simple site about refactoring in PHP.

You can contact me at slawomir.grochowski@gmail.com, https://twitter.com/s_grochowski, https://www.facebook.com/SlawomirGrochowski You can support this site: send some BitCoins 1D8xeRkxssTTLESfGZtPVoqVJDq7MJSqNx , send/buy one of the books I would like to read or just say Hello!

Author: slk

Created: 2019-05-06 pon 15:55

Validate