Those Smells Code Smells & Refactoring€¦ · Code Smells Deeper problems Not bugs, nor errors...

Preview:

Citation preview

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: shaunak.s@gmail.com

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

Recommended