Code and Design Smells. Are They a Real Threat?

Post on 16-Apr-2017

671 views 0 download

Transcript of Code and Design Smells. Are They a Real Threat?

Code and design smells. Are they a real threat?

Antoni Rasul

Lehman's laws of software evolution

• Originally formulated by Manny Lehman in 1974 and later refined, as a result of empirical study within IBM

• Aimed at finding scientific properties of evolution of different classes of software

• System types:

• S-type (specified) Systems

• P-type (problem solving) Systems

• E-type (evolutionary) Systems

E-type Systems laws of evolution (since 1996)

I. Continuing Change

II. Increasing Complexity

III. Self Regulation

IV. Conservation of Organizational Stability

V. Conservation of Familiarity

VI. Continuing Growth

VII. Declining Quality

VIII.Feedback System

Technical debt

• Monetary metaphor

• Impossible to measure precisely

• If not repaid regularly – accumulates interest

Time

Effort

Interest payment

Feature

Warning signs of rising debt

• The quality of your software is degrading

• The bug backlog is increasing

• The bug threshold in release is rising

• Your productivity is slipping

• Fictional deadlines

• The team members don't care

Effective ways to reduce the debt

• Define the debt

• Revise the Definition of Done

• Testing tasks part of story

• Done means code cleaned, refactored, deployed and tested

• Use tools like Sonar to visualise (also for management)

• Refactor

• Identify code smells and plan for change

• Automate

Refactoring

• Refactoring: a change made to the internal structure of software to make it easier to understand and cheaper to modify without changing its observable behavior.

• Refactor when:

• Adding a function

• Fixing a bug

• Doing a code review

• Perform larger refactorings in small increments

Code smells

• Duplicated Code

• Shotgun surgery

• Feature envy

• Switch statements

• Refused bequest

• Telescoping constructors

• Big Ball of Mud

Techniques

• Extract method/class

• Substitute algorithm

• Introduce parameter object

• Replace type conditional with Polymorphism/Strategy

• Fluent interface with Essence pattern

Duplicated Code or Structure

• In same class

Extract Method

• In sibling classes

Extract Method

Pull method Up

• In unrelated classes

Substitute algorithm

Extract Class

Use extracted class in other places

Shotgun surgery

• Adding simple feature requires small changes in gazillion classes

• Difficult to eliminate in big systems

• Removing duplicates first – reduces the smell

• Code generation tools help reduce the problem further

Feature envy

• One class’s method is extensively using data and methods

of another class

• Usually easy to spot and fix – move the method to the class

where it belongs

• If method uses several classes – move it to the one with

most used data

• Use Move Method, possibly with Extract Method

Switch by type in multiple places - problem

public class PriceCalculator { public double calculatePrice(Customer customer, Car car, Period period) { double tax = 0; double discount = 0; switch(customer.getType()) { case REGULAR: discount = 0; tax = 0.2; case BUSINESS: discount = 0.5; tax = 0.2; case BIG_BUSINESS: discount = 0.2; tax = 0.1; } return period.getDays() * car.getDayPrice() * (1 - discount * tax); } }

Switch by type - Replace type code by polymorphism

public interface Customer { CustomerType getType(); } public interface CustomerType { double getDiscount(); double getTax(); } public class RegularCustomer implements CustomerType { ... } public class BusinessCustomer implements CustomerType { ... } public class BigBusinessCustomer implements CustomerType { ... }

Switch by type - refactored

public class PriceCalculator { public double calculatePrice(Customer customer, Car car, Period period) { final double tax = customer.getType().getTax(); final double discount = customer.getType().getDiscount(); return period.getDays() * car.getDayPrice() * (1 - discount * tax); } }

• If price calculation algorithm would depend on Customer type – use Strategy

Refused bequest

public interface Car { String getModel(); double getDayPrice(); } public class Truck implements Car { public String getMaxCargoVolume(){...}; } public class CarFleet implements Car { private List<Car> cars; public String getModel() { return null; // Hmmmmm Let's return null here } public double getDayPrice() { return this.cars.stream().mapToDouble(c -> c.getDayPrice()).sum(); } }

• When subclass inherits data/methods that it doesn’t want

• When sublcass refuses to meaningfully implement all interface methods

Refused bequest - ideas

• Hierarchy is wrong

• Reshuffle hierarchy, so that parent contains only what’s common

• Another solution – pull the violating class out of the hierarchy and use a delegate to the instance of (former) parent class

Refused bequest – example refactored

public interface RentableItem { double getDayPrice(); } public interface Car extends RentableItem { String getModel(); } public interface CarFleet extends RentableItem { double getFleetDiscount(); } public class StandardFleet implements CarFleet { private List<Car> cars; public double getDayPrice() { return this.cars.stream().mapToDouble(c -> c.getDayPrice()).sum(); } public double getFleetDiscount() { return 0.1; } }

Telescoping constructors

public class RentalContract { ... public RentalContract(Customer cust, List<Car> cars, Date startDate) {

this(cust, car, startDate, null); } public RentalContract(Customer cust, List<Car> cars, Date startDate, Date endDate) {

this(cust, cars, null, null, startDate, endDate); } public RentalContract(Customer customer, List<Car> cars, BigDecimal extraDiscount, String description, Date startDate, Date endDate) {

this.customer = customer; this.cars = cars; this.extraDiscount = extraDiscount; this.description = description; this.startDate = startDate; this.endDate = endDate;

} ...

}

Telescoping constructors – required fields

public RentalContract( Customer customer, List<Car> cars, BigDecimal extraDiscount, String description, Date startDate, Date endDate) {...}

• Instantiating is cumbersome, error-prone

• Optional fields are mixed with required ones

Telescoping constructors – refactoring ideas

• Introduce parameter object from startDate and endDate

public class DateRange { private Date start; private Date end; ... public DateRange withStart(Date start) {

this.start = start; return this;

} public DateRange withEnd(Date end) {

this.end = end; return this;

} }

• Use Essence pattern composed of: • Builder • Fluent interface • Validation of required fields before object construction

Telescoping constructors – fluent builder public class RentalContractBuilder { ... public RentalContractBuilder withCustomer(Customer customer) { this.customer = customer; return this; } public RentalContractBuilder addCar(Car car) { cars.add(car); return this; } public RentalContractBuilder withCars(List<Car> cars) { this.cars = cars; return this; } public RentalContractBuilder withDateRange(DateRange dateRange) { this.dateRange = dateRange; return this; } public void validate() { customerValidator.validate(customer); carValidator.validate(cars); datesValidator.validate(dateRange); } public RentalContract build() { validate(); return new RentalContract(this); } }

Telescoping constructors – end result

public class RentalContract { ... RentalContract(RentalContractBuilder contractBuilder) { this.customer = contractBuilder.getCustomer(); this.cars = contractBuilder.getCars(); this.dateRange = contractBuilder.getDateRange(); } }

RentalContract rentalContract = new RentalContractBuilder() .withCustomer(customer) .addCar(mustang) .addCar(camaro) .withDateRange(new DateRange().withStart(new Date())) .build();

• Constructor with default access

• We are sure that rentalContract is a valid instance after build()

Big Ball of Mud – The Mikado Method

• Allows for systematic reshaping of the system

• Easy to use

• Provides stability to the codebase while changing it

• Fits in an incremental process

• Scalable, the whole team can participate

The Mikado Method

• Set a goal

• Experiment

• Visualize

• Undo

The Mikado Method - flow

Draw goal

Implement goal naively

Errors?

Come up with immediate solutions and draw them as

sub-goals

Revert changes

Select next goal

Yes

No Change makes sense?

Check in !

All goals completed?

Done!

Yes

Yes

Yes

No

No

Mikado graph

Mikado Goal

Prerequisite: immediate

solution Another

prerequisite

More prerequisites ..linked to

previous

Can have common

dependencies

Summary

• Forgive the predecessors

• Take small steps

• Work as a team

• Convince management

• Willingness to change