TechDiscuss.net Shaunak Sontakke
Code Smells & Refactoring Those Smells
TechDiscuss.net Shaunak Sontakke
Shaunak Sontakke
● Bachelor of Engineering, MBA in Systems
● Software Engineer @ Entrata
● Full Stack Developer for 13 years, HusBand for 6 years and Dad for 4 years
● Passionate about clean architecture, clean code, pragmatic development
● Love reading/listening Self-Help Books, Developer Podcasts
● Blog: TechDiscuss.net. Email: [email protected]
TechDiscuss.net Shaunak Sontakke
Hello Everyones,
Todays are a great day. I is happy because we are learning new thing’s.
TechDiscuss.net Shaunak Sontakke
Code Smells
● Deeper problems
● Not bugs, nor errors
● Possibly○ Weakness○ Defects○ Inefficiency○ Design principles violation
● Ignore? Sure for cascading failures
● Agile software development term
● Code Smell Catalog gives you vocabulary in code review discussions
● Code Smells vs Anti-Pattern
TechDiscuss.net Shaunak Sontakke
Code Smells Catalog
1. Alternative classes w/ different interfaces
2. Comments
3. Data class
4. Data clumps
5. Dead code
6. Divergent change
7. Duplicated code
8. Feature envy
9. Inappropriate intimacy
10. Incomplete library client
11. Large class
12. Lazy class
13. Long method
14. Long parameter list
15. Message chains
16. Middle man
17. Parallel inheritance hierarchies
18. Primitive obsession
19. Refused bequest
20. Shotgun surgery
21. Speculative generality
22. Switch statements
23. Temporary field
TechDiscuss.net Shaunak Sontakke
Code Smells Catalog
1. Alternative classes w/ different interfaces
2. Comments
3. Data class
4. Data clumps
5. Dead code
6. Divergent change
7. Duplicated code
8. Feature envy
9. Inappropriate intimacy
10. Incomplete library client
11. Large class
12. Lazy class
13. Long method
14. Long parameter list
15. Message chains
16. Middle man
17. Parallel inheritance hierarchies
18. Primitive obsession
19. Refused bequest
20. Shotgun surgery
21. Speculative generality
22. Switch statements
23. Temporary field
TechDiscuss.net Shaunak Sontakke
Code Smells Catalog
1. Long method
2. Large class
3. Data clumps
4. Long parameter list
5. Primitive obsession
1. Alternative classes w/ different interfaces
2. Refused bequest
3. Switch statements
4. Temporary field
1. Divergent change
2. Shotgun surgery
3. Parallel inheritance hierarchies
1. Feature envy
2. Inappropriate intimacy
3. Message chains
4. Middle man
1. Comments
2. Data class
3. Dead code
4. Duplicated code
5. Lazy class
6. Speculative generality
Blo
aters
Disp
ensab
lesC
ou
plers
Ch
ange
Preven
ters
Ob
ject- O
rientatio
n
Ab
users
TechDiscuss.net Shaunak Sontakke
Code Smell Groups
Change Preventers
If you want to change something in one place you have to
make many changes in other places too
Excessive coupling between classes
Couplers Object-Orientation Abusers
Incomplete or incorrect
implementation of OOP
Bloaters
Grown to a large size
Dispensables
Absence of these would make the code
clean and improve code readability
TechDiscuss.net Shaunak Sontakke
Long method
● Ask questions if the method is more than 10 lines
● Probably violating design principles like SRP, KISS
Bloaters
Grown to a large size
TechDiscuss.net Shaunak Sontakke
Long method
Large class
● Class doing too many things
● Too many member variables
● Breeding ground for duplicate code
Bloaters
Grown to a large size
TechDiscuss.net Shaunak Sontakke
Data clumps
Long method
Large class
● Like children - enjoy hanging around in groups
● Same set of variables seen together
● name_first-name_last, start_date-end_date
Bloaters
Grown to a large size
TechDiscuss.net Shaunak Sontakke
Long method
Long parameter list
Large class
Data clumps
● More parameters ~ doing more things
● Another object hiding there
● SonarQube defaults: 4 is good
● Uncle Bob’s Clean Code: More than 3 requires
special justification
function doSomething($param1, $param2, $param3, $param4, $param5) { ...}
Bloaters
Grown to a large size
TechDiscuss.net Shaunak Sontakke
Long method
Primitive obsession
Long parameter list
Large class
Data clumps
● Developers prefer primitive types like floats for
money / currency classes, 2 string dates vs
DateRange object
● Developers reluctant to use small objects for small
tasks
Bloaters
Grown to a large size
TechDiscuss.net Shaunak Sontakke
Long method
Primitive obsession
Long parameter list
Large class
Data clumps
Bloaters
Grown to a large size
TechDiscuss.net Shaunak Sontakke
Divergent change
● Requires you to change multiple unrelated
methods when you want to do a small change in
class
● Adding a new coupon code functionality? Change
shipping function, printing function, order function,
etc.
● Applicable to changes in one class
Change Preventers
If you want to change something in one place you have to
make many changes in other places too
TechDiscuss.net Shaunak Sontakke
Divergent change
Shotgun surgery
● One reason for changing multiple classes
● Similar to Divergent Change
● Divergent change is many changes to one class,
while shotgun surgery is single change modifying
multiple classes
Change Preventers
If you want to change something in one place you have to
make many changes in other places too
TechDiscuss.net Shaunak Sontakke
Divergent change
Shotgun surgery
Parallel inheritance hierarchies
Departments
HR Marketing
Privilege
HRPrivileges MarketingPrivileges
Sales SalesPriveleges
Change Preventers
If you want to change something in one place you have to
make many changes in other places too
TechDiscuss.net Shaunak Sontakke
Divergent change
Shotgun surgery
Parallel inheritance hierarchies
Change Preventers
If you want to change something in one place you have to
make many changes in other places too
TechDiscuss.net Shaunak Sontakke
Feature envy
● Method is more interested in a another class
● Accesses more public features of other class than
its own
class SalaryCalculator {
public function calculateTotal() { $tax = $this->calculateTax(); }
public function calculateTax() { $employee = new Employee(); $grossTotal = $employee->getTotalPay()
+ $employee->getTotalBonus();
// ... return $tax; }}
Excessive coupling between classes
Couplers
TechDiscuss.net Shaunak Sontakke
Feature envy
Inappropriate intimacy
● Class touches internal fields and methods of
another class
● Compromises other class's encapsulation
class Customer { public function sendSms( $name, Contact $contact ) { $message = 'Hello '. $name; $message .= 'Contact:' . $contact->getPhoneNumber() . PHP_EOL; $message .= 'Email:' . $contact->getEmail() . PHP_EOL; }}Excessive coupling
between classes
Couplers
TechDiscuss.net Shaunak Sontakke
● Class that uses another class’s method, which in
turn uses another class’s and so on
● Chain: Employee->EmployeeConfig->ConfigFeature envy
Inappropriate intimacy
Message chains
class Employee { public function getConfiguration() { $this->employeeConfig->getConfiguration(); }}class EmployeeConfig { public function getConfiguration() { $this->config->getConfiguration(); }}class Config { public function getConfiguration() { $this->loadConfiguration(); }}
Excessive coupling between classes
Couplers
TechDiscuss.net Shaunak Sontakke
● Class exists just to delegate to another
● Is there a real purpose of this class?Feature envy
Middle man
Inappropriate intimacy
Message chains
class Customer {
/** @var Person */ private $person;
public function getNameFirst() { return $this->person->getNameFirst(); }
public function getNameLast() { return $this->person->getNameLast(); }
public function getGender() { return $this->person->getGender(); }}
Excessive coupling between classes
Couplers
TechDiscuss.net Shaunak Sontakke
Feature envy
Middle man
Inappropriate intimacy
Message chains
Excessive coupling between classes
Couplers
TechDiscuss.net Shaunak Sontakke
<?php
class Animal {
public function getSpeed() { switch( $this->type ) { case LION: return $this->getBaseSpeed(); case PUMA: return $this->getBaseSpeed() * 1.5; case COUGAR: return ( $this->isTired ) ? 0 : $this->getBaseSpeed() * 2; default: return $this->getBaseSpeed(); } }}
Switch statements
// It’s not a smell when its in factoryclass AnimalFactory {
public static function getAnimal( $animalType ) { switch( $animalType ) { case LION: return new Lion(); case PUMA: return new Puma(); case COUGAR: return new Cougar(); default: return new Lion(); }
}}
● Same switch cases scattered at multiple places
● Addition of new case in such switch
Object-Orientation Abusers
Incomplete or incorrect
implementation of OOP
TechDiscuss.net Shaunak Sontakke
Switch statements
Temporary field
● Member variables are set only in certain
circumstances. Otherwise they are empty
● Understanding such variable existence can drive
you nuts
● Not all member variables are required for every
method, but they are just present
Object-Orientation Abusers
Incomplete or incorrect
implementation of OOP
TechDiscuss.net Shaunak Sontakke
● Class inherits from a base class but doesn't use any
of the inherited fields or methodsSwitch statements
Temporary field
Refused bequest
class Animal {
private $numberOfLegs;
public function getNumberOfLegs() { return $this->numberOfLegs; }}
class Cat extends Animal {
public function getNumberOfLegs() { parent::getNumberOfLegs(); }}
class GoldFish extends Animal {
public function getNumberOfLegs() { throw NotImplementedException(); }}
Object-Orientation Abusers
Incomplete or incorrect
implementation of OOP
TechDiscuss.net Shaunak Sontakke
● Two classes perform identical functions but have
different signatures.
● Difference in interfaces of similar classesSwitch statements
Alternative classes w/ different interfaces
Temporary field
Refused bequest
Object-Orientation Abusers
Incomplete or incorrect
implementation of OOP
TechDiscuss.net Shaunak Sontakke
Switch statements
Alternative classes w/ different interfaces
Temporary field
Refused bequest
Object-Orientation Abusers
Incomplete or incorrect
implementation of OOP
TechDiscuss.net Shaunak Sontakke
Comments
● Method is filled with explanations
● When you feel like writing a comment, first try "to
refactor so that the comment becomes
superfluous"
● Having Comments vs. Need Comments
Dispensables
Absence of these would make the code
clean and improve code readability
TechDiscuss.net Shaunak Sontakke
Comments
Duplicated code
Dispensables
Absence of these would make the code
clean and improve code readability
TechDiscuss.net Shaunak Sontakke
Comments
Duplicated code
Lazy class
● Class doesn’t do enough to earn your attention
● May be a result of previous refactoring
● Added for future development but was never used
Dispensables
Absence of these would make the code
clean and improve code readability
TechDiscuss.net Shaunak Sontakke
Comments
Data class
Duplicated code
Lazy class
● Members, getters, setters
● Dump data holder
● Beware of DTO or POPO
Dispensables
Absence of these would make the code
clean and improve code readability
TechDiscuss.net Shaunak Sontakke
Comments
Dead code
Data class
Duplicated code
Lazy class
Dispensables
Absence of these would make the code
clean and improve code readability
● Requirements changed - nobody had time to clean
up the old code
● Unreachable code in lengthy conditionals
TechDiscuss.net Shaunak Sontakke
Comments
Dead code
Data class
Duplicated code
Lazy class
Speculative generality
● Premature Generalization
● Add parameters - somebody would need it in future
Dispensables
Absence of these would make the code
clean and improve code readability
TechDiscuss.net Shaunak Sontakke
Comments
Dead code
Data class
Duplicated code
Lazy class
Speculative generality
Dispensables
Absence of these would make the code
clean and improve code readability
TechDiscuss.net Shaunak Sontakke
Refactoring
● Systematic approach for restructuring the code
● Art of improving the internal structure of code without modifying observable behavior
● Small restructuring, less likely to go wrong
● Easy to learn techniques
● Done when code is working
● There are around 300 formal refactoring technique
● Building a strong house vs building it faster
TechDiscuss.net Shaunak Sontakke
What is not refactoring
● Adding new features
● Bug fixing
● Performance improvement
● Replacing large chunks of code
● Optimisation vs Refactoring
TechDiscuss.net Shaunak Sontakke
Good time for Refactoring
● Cyclomatic Complexity
● Code Smell○ Duplicate code….refactor!○ Method at wrong place….refactor!○ Function doing more than its name….refactor!○ Lengthy function….refactor!○ Conditions are lengthy….refactor!○ If it stinks….refactor!
● Rule of Three
○ When doing it first time, just do it
○ 3rd time you do same, you refactor
○ 2nd time, you wince duplication, but do the duplication anyways
TechDiscuss.net Shaunak Sontakke
Good time for Refactoring
● Micro-Refactoring
● Proactive Refactoring: Managers appreciate when we eliminate the need for special refactoring
tasks
● Refactor during any feature type of task○ PhpUnit Test comes handy○ Tasks would be QAed○ Would take more time? Talk to manager ○ // @FIXME and // @TODO comments
● Refactor as you do a code review
● When adding new UnitTests - not mockable code
TechDiscuss.net Shaunak Sontakke
When not to refactor
● When you should rewrite from scratch
● Remember, code has to work before you start
● When you are close to deadline
TechDiscuss.net Shaunak Sontakke
Extract Methodfunction printInvoice( Order $order ) { $invoiceLines = [];
// prints header $invoiceLines[] = '--------------'; $invoiceLines[] = 'Invoice'; $invoiceLines[] = '--------------';
// Line Items if( $order->getTotal() <> 0 ) { $invoiceLines[] = 'Details'; } else { $invoiceLines[] = 'Total:' . $order->getTotal(); }
print_r( $invoiceLines );
}
function printInvoice( Order $order ) { $invoiceLines = []; $invoiceLines = generateHeaders( $invoiceLines ); $invoiceLines = generateInvoiceBody( $order, $invoiceLines );
print_r( $invoiceLines );
}
function generateHeaders( array $invoiceLines ) { $invoiceLines[] = '--------------'; $invoiceLines[] = 'Invoice'; $invoiceLines[] = '--------------';
return $invoiceLines;}
function generateInvoiceBody( Order $order, array $invoiceLines ) { if( $order->getTotal() <> 0 ) { $invoiceLines[] = 'Details'; } else { $invoiceLines[] = 'Total:' . $order->getTotal(); }
return $invoiceLines;}
TechDiscuss.net Shaunak Sontakke
Eliminates
1. Duplicated code
2. Comments
3. Data class
4. Long method
5. Feature envy
6. Message chains
7. Switch statements
TechDiscuss.net Shaunak Sontakke
Inline Methodfunction applyDiscounts( $order ) {
// .. . if( getOrderTotal( $order ) > 500 ) { $shippingAmount = 0; }
}
function getOrderTotal( $order ) { return $order->getOrderTotal();}
function applyDiscounts( $order ) {
if( $order->getOrderTotal() > 500 ) { $shippingAmount = 0; }
}
TechDiscuss.net Shaunak Sontakke
Eliminates
1. Speculative generality
TechDiscuss.net Shaunak Sontakke
Extract Variablefunction handleLogin( $request ) {
if( isset( $request->getAttribute( 'username' ) ) && 'admin' == $request->getAttribute( 'username' ) ) { $attachmentPath = __DIR__ . '/' . $request->getAttribute( 'username' ) . '.txt'; }
}
function handleLogin( $request ) {
$username = $request->getAttribute( 'username' );
if( isset( $username ) && 'admin' == $username ) { $attachmentPath = __DIR__ . '/' . $username . '.txt'; }
}
TechDiscuss.net Shaunak Sontakke
Eliminates
1. Duplicated code
2. Comments
TechDiscuss.net Shaunak Sontakke
Inline Tempfunction calculateOrderTotal( $order ) { $total = $order->getTotal() + $order->getShippingAmount(); return $total;}
function calculateOrderTotal( $order ) { return $order->getTotal() + $order->getShippingAmount();}
TechDiscuss.net Shaunak Sontakke
Helps in these refactoring
1. Extract Method
2. Replace Temp with Query
TechDiscuss.net Shaunak Sontakke
Replace Temp with Queryfunction calculateDiscount( $order ) {
$itemTotal = $this->quantity * $this->itemPrice;
if( $itemTotal >= 500 ) { return $itemTotal * 0.02; } else { return $itemTotal; }}
function calculateDiscount( $order ) {
if( getItemTotal() >= 500 ) { return getItemTotal() * 0.02; } else { return getItemTotal(); }}
function getItemTotal() { return $this->quantity * $this->itemPrice;}
● Balance between Performance vs Readability● Helps in UnitTesting
TechDiscuss.net Shaunak Sontakke
Eliminates
1. Long method
2. Duplicated code
TechDiscuss.net Shaunak Sontakke
Move Methodclass SalaryCalculator {
public function calculateTotal() { // ...
$tax = $this->calculateTax(); }
public function calculateTax() { $employee = new Employee(); $grossTotal = $employee->getTotalPay()
+ $employee->getTotalBonus();
// ... return $tax; }}
class Employee {
public function getTotalPay() {
}
public function getTotalBonus() {
}}
class SalaryCalculator {
public function calculateTotal() { // ... $employee = new Employee(); $tax = $employee->calculateTax(); }
}
class Employee {
public function getTotalPay() {
}
public function getTotalBonus() {
}
public function calculateTax() {
$grossTotal = $this->getTotalPay() + $this->getTotalBonus();
// ... return $tax; }}
TechDiscuss.net Shaunak Sontakke
Eliminates
1. Shotgun surgery
2. Parallel inheritance hierarchies
3. Feature envy
4. Message chains
5. Inappropriate intimacy
6. Switch statements
7. Data class
TechDiscuss.net Shaunak Sontakke
Move Fieldclass Car {
/** @var Driver */ public $driver;
public function useNOS() { $this->driver->currentSpeed; }}
class Driver { public $currentSpeed;
}
class Car {
public $currentSpeed;
public function useNOS() { $this->currentSpeed; }}
class Driver {
}
TechDiscuss.net Shaunak Sontakke
Eliminates
1. Shotgun surgery
2. Parallel inheritance hierarchies
3. Inappropriate intimacy
TechDiscuss.net Shaunak Sontakke
Extract Classclass Person {
private $name; private $officeAreaCode; private $officeNumber;
public function getTelephoneNumber() {
}}
class Person {
private $name;
}
class TelephoneNumber {
private $officeAreaCode; private $officeNumber;
public function getTelephoneNumber() {
}}
TechDiscuss.net Shaunak Sontakke
Eliminates
1. Duplicated code
2. Large class
3. Divergent change
4. Data clumps
5. Primitive obsession
6. Temporary field
7. Inappropriate intimacy
TechDiscuss.net Shaunak Sontakke
Inline Classclass Shape {
/** @var \Color */ private $color;
public function draw() { $color = $this->color->getFavoriteColor(); }
}
class Color {
private $favoriteColor = 'blue';
public function getFavoriteColor() { return $this->favoriteColor; }}
class Shape {
private $favoriteColor = 'blue';
public function draw() { $color = $this->favoriteColor; }
}
TechDiscuss.net Shaunak Sontakke
Eliminates
1. Shotgun surgery
2. Lazy class
3. Speculative generality
TechDiscuss.net Shaunak Sontakke
Replace Array with Object$record = [];$record[0] = 'BYU';$record[1] = 20;
$record = new Team();$record->setName( 'BYU' );$record->setWins( 20 );
TechDiscuss.net Shaunak Sontakke
Eliminates
1. Primitive obsession
TechDiscuss.net Shaunak Sontakke
Decompose Conditional
if( ( $orderTotal >= 100 || $quantity > 5 ) && $orderDate >= SALE_START && $orderDate <= SALE_END ) { $discountPercent = 0.05;}
if( isDiscountApplicable() ) { $discountPercent = 0.05;}
function isDiscountApplicable() { return ( $orderTotal >= 100 || $quantity > 5 ) && $orderDate >= SALE_START && $orderDate <= SALE_END;}
TechDiscuss.net Shaunak Sontakke
Eliminates
1. Long method
TechDiscuss.net Shaunak Sontakke
Consolidate Conditional Expressionfunction disabilityAmount() { if( $this->seniority < 2 ) { return 0; } if( $this->monthsDisabled > 12 ) { return 0; } if( $this->isPartTime ) { return 0; } // compute the disability amount...
function disabilityAmount() { if( !$this->isEligibleForDisability() ) { return 0; } // compute the disability amount...
TechDiscuss.net Shaunak Sontakke
Eliminates
1. Duplicated code
TechDiscuss.net Shaunak Sontakke
Replace Conditional with Polymorphismclass Animal {
public function getSpeed() { switch( $this->type ) { case LION: return $this->getBaseSpeed(); case PUMA: return $this->getBaseSpeed() * 1.5; case COUGAR: return ( $this->isTired ) ? 0 : $this->getBaseSpeed() * 2; default: return $this->getBaseSpeed(); } }}
abstract class Animal {
abstract function getSpeed();}
class Lion extends Animal {
public function getSpeed() { return $this->getBaseSpeed(); }}
class Cougar extends Animal {
public function getSpeed() { return ( $this->isTired ) ? 0 : $this->getBaseSpeed() * 2; }}
$speed = $wildAnimal->getSpeed();
TechDiscuss.net Shaunak Sontakke
Eliminates
1. Switch statements
TechDiscuss.net Shaunak Sontakke
Introduce Null Objectclass Logger {
public function error( $message ) { if( ENV == 'production' ) { echo 'RED:' . $message; } else { return; } }
public function alert( $message ) { if( ENV == 'production' ) { echo 'YELLOW:' . $message; } else { return; } }}
class NullLogger {
public function error( $message ) { return NULL; }
public function alert( $message ) { return NULL; }}
class Logger {
public function error( $message ) { echo 'RED:' . $message; }
public function alert( $message ) { echo 'YELLOW:' . $message; }}
if( ENV == 'production' ) { $logger = new Logger();} else { $logger = new NullLogger();}
TechDiscuss.net Shaunak Sontakke
Eliminates
1. Switch statements
2. Temporary field
TechDiscuss.net Shaunak Sontakke
Parameterize Methodclass Car {
public function shiftToDrive() {
}
public function shiftToNeutral() {
}
public function shiftToReverse() {
}}
class Car {
public function shift( $position ) {
}}
TechDiscuss.net Shaunak Sontakke
Eliminates
1. Duplicated code
TechDiscuss.net Shaunak Sontakke
Preserve Whole Objectfunction formatName( $lastName, $firstName, $honorifics ) {
}
$lastName = $customer->getLastName();$firstName = $customer->getFirstName();$honorifics = $customer->getHonorifics();
echo formatName( $lastName, $firstName, $honorifics );
function formatName( Customer $customer ) {
}
echo formatName( $customer );
TechDiscuss.net Shaunak Sontakke
Eliminates
1. Primitive obsession
2. Long parameter list
3. Long method
4. Data clumps
TechDiscuss.net Shaunak Sontakke
Introduce Parameter Objectfunction formatPostalAddress( $addressLine1, $addressLine2, $state, $postalCode, $country ) {
}
class PostalAddress { private $addressLine1; private $addressLine2; private $state; private $postalCode; private $country;
// getters & setters}
function formatPostalAddress( PostalAddress $postalAddress ) {
}
TechDiscuss.net Shaunak Sontakke
Eliminates
1. Long parameter list
2. Data clumps
3. Primitive obsession
4. Long method
TechDiscuss.net Shaunak Sontakke
Pull Up Methodabstract class Vehicle {
}
class Car extends Vehicle {
private $numOfPassengers;
protected function printPassengers() { echo 'Number of passengers = ' . $this->numOfPassengers; }}
class Truck extends Vehicle {
private $numOfPassengers;
protected function printPassengers() { echo 'Number of passengers = ' . $this->numOfPassengers; }}
abstract class Vehicle {
private $numOfPassengers;
protected function printPassengers() { echo 'Number of passengers = ' . $this->numOfPassengers; }}
class Car extends Vehicle {
}
class Truck extends Vehicle {
}
TechDiscuss.net Shaunak Sontakke
Eliminates
1. Duplicated code
TechDiscuss.net Shaunak Sontakke
Further Reading. Questions?
● Refactoring: Improving the Design of Existing Code
● Refactoring.guru
● https://rules.sonarsource.com/php
Graphics - freepik.com
TechDiscuss.net Shaunak Sontakke