BCS1430
Dr. Ashish Sai
📅 Week 3 Lecture 1
💻 BCS1430.ashish.nl
📍 EPD150 MSM Conference Hall


Small, Behavior-Preserving Transformations: Minor yet cumulative modifications for major restructuring.
Systematic Process: Ensures the external behavior remains consistent while improving internal architecture.
Consider a video store’s calculation logic:
class Customer {
private List<Rental> rentals;
public double calculateTotalAmount() {
double totalAmount = 0;
for (int i = 0; i < rentals.size(); i++) {
Rental rental = rentals.get(i);
double amount = 0;
switch (rental.getMovie().getPriceCode()) {
case Movie.REGULAR:
amount = 2;
break;
case Movie.NEW_RELEASE:
amount = 3;
break;
case Movie.CHILDRENS:
amount = 1.5;
break;
default:
amount = 0;
break;
}
totalAmount += amount;
}
return totalAmount;
}
}Goal: Simplify the calculateTotalAmount method by creating smaller, more readable chunks of code.
Extract the switch statement into amountFor in the Rental class:
Look for opportunities to:
Break down long methods.
Move operations closer to data.
Replace conditionals with polymorphism.
Before: Switch statements based on movie type.
After: Use polymorphism with subclasses like RegularMovie, NewReleaseMovie, and ChildrensMovie.
getCharge.public abstract class Movie {
private String title;
public Movie(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
abstract double getCharge();
}
public class RegularMovie extends Movie {
public RegularMovie(String title) {
super(title);
}
@Override
double getCharge() {
return 2;
}
}
public class NewReleaseMovie extends Movie {
public NewReleaseMovie(String title) {
super(title);
}
@Override
double getCharge() {
return 3;
}
}
public class ChildrensMovie extends Movie {
public ChildrensMovie(String title) {
super(title);
}
@Override
double getCharge() {
return 1.5;
}
}Flexibility: Adding a new movie type is straightforward.
Adherence to Open/Closed Principle: System is open for extension but closed for modification.
Encapsulation Achieved: Broke down a complex method into smaller, manageable parts, enhancing readability and flexibility.
Refactoring Impact: Small, progressive enhancements yielding substantial overall improvements.
Do not change the observable behavior of the code.
Make small, incremental changes.
Test after each change.
Focus on simplicity and clarity.
What? Can code “smell” 👃 ????
Well it doesn’t have a nose…. but it definitely can stink 😷!
Code Smells: Indicators of potential issues in your code that may require refactoring.
Importance: Recognizing smells is the first step towards improving code quality.
Common Smells: Include Duplicated Code, Long Method, Large Class, and more.
Use smells as a guide, not a strict rule, to identify areas for improvement.

Image Credit: Refactoring.Guru
What It Is: Methods with too many lines of code.
Problems: Hard to understand and maintain. Often contain hidden bugs.
public class ReportGenerator {
public void generateReport(DataSet data) {
// Initialization and setup
String report = "";
// Complex data processing
for (DataPoint point : data) {
// Detailed data analysis and report generation
report += analyzeDataPoint(point);
}
// More processing and formatting
// Final report compilation
System.out.println(report);
}
} public class ReportGenerator {
public void generateReport(DataSet data) {
String report = processDataForReport(data);
outputReport(report);
}
private String processDataForReport(DataSet data) {
String report = "";
for (DataPoint point : data) {
report += analyzeDataPoint(point);
}
return report;
}
private void outputReport(String report) {
// Final report compilation and output
System.out.println(report);
}
private String analyzeDataPoint(DataPoint point) {
// Detailed data analysis
// Return analysis result as String
}
}Extract Method: Break down into smaller methods with clear names indicating their purpose.
Replace Temp with Query: Replace temporary variables with queries to the method itself.
Introduce Parameter Object or Preserve Whole Object: Group parameters into objects.
Problem Code:
Refactored Code:
public void printReport(List<Report> reports) {
for(Report report : reports) {
printHeader(report);
printDetails(report);
printFooter(report);
}
}
private void printHeader(Report report) { /*...*/ }
private void printDetails(Report report) { /*...*/ }
private void printFooter(Report report) { /*...*/ }Problem Code:
Refactored Code:
public double calculateTotal() {
if(basePrice() > 1000) {
return basePrice() * 0.95;
} else {
return basePrice() * 0.98;
}
}
private double basePrice() {
return quantity * itemPrice;
}Problem Code:
Refactored Code:
public class ReservationRequest {
private Date start;
private Date end;
private Guest guest;
private Room room;
// Constructor and accessors
}
public void createReservation(ReservationRequest request) {
// Logic using request object
}
Image Credit: Refactoring.Guru
What It Is: Classes with too many responsibilities.
Problems: Difficult to understand, maintain, and modify.
public class Employee {
private String name;
private String address;
private String phoneNumber;
private double salary;
// ... many more attributes
public void calculatePay() {
// Method to calculate pay
}
public void save() {
// Method to save employee details
}
// ... many more methods
}Employee class is handling personal details, pay calculations, and data storage, among other things. public class Employee {
private String name;
private EmployeeDetails details;
private PayCalculator payCalculator;
// Employee class now delegates responsibilities
}
public class EmployeeDetails {
private String address;
private String phoneNumber;
// ... other personal details
}
public class PayCalculator {
private double salary;
public void calculatePay() {
// Method to calculate pay
}
}Refactoring Techniques:
Extract Class: Create new classes to handle parts of the functionality.
Extract Subclass or Extract Interface: Use when part of the behavior is used in only some instances.
Problem Code:
Refactored Code:
class Order {
private Customer customer;
private List<Item> items;
private PaymentDetails paymentDetails;
private ShippingDetails shippingDetails;
// Simplified Order class
}
class PaymentDetails {
private Address billingAddress;
// ... payment related fields and methods
}
class ShippingDetails {
private Address shippingAddress;
// ... shipping related fields and methods
}Problem Code:
Refactored Code (Extract Subclass):
class Order {
// ... common fields and methods
}
class PriorityOrder extends Order {
// Priority order specific fields and methods
@Override
void processOrder() {
// Priority order processing
}
}
class NormalOrder extends Order {
// Normal order specific fields and methods
@Override
void processOrder() {
// Normal order processing
}
}Refactored Code (Extract Interface):
interface Order {
void processOrder();
}
class PriorityOrder implements Order {
// Priority order specific fields and methods
public void processOrder() {
// Priority order processing
}
}
class NormalOrder implements Order {
// Normal order specific fields and methods
public void processOrder() {
// Normal order processing
}
}
Image Credit: Refactoring.Guru
public class CustomerInfo {
private String name;
private String email;
// Constructors, getters, and setters...
}
public class OrderDetails {
private String orderItem;
private int quantity;
// Constructors, getters, and setters...
}
public class PaymentInfo {
private String paymentMethod;
private String cardNumber;
private String expiryDate;
private String cvv;
// Constructors, getters, and setters...
}
// The method now takes these objects as parameters:
public void processOrder(CustomerInfo customerInfo, OrderDetails orderDetails, PaymentInfo paymentInfo) {
// Method logic using the provided objects...
}Alternative: Replace parameters with method calls to retrieve the needed information.
Example: Method Calls Instead of Parameters
// Assuming there are methods to retrieve customer and order details:
CustomerInfo customerInfo = getCustomerInfo(customerId);
OrderDetails orderDetails = getOrderDetails(orderId);
PaymentInfo paymentInfo = getPaymentInfo(paymentId);
// The method can be simplified further:
public void processOrder(CustomerInfo customerInfo, OrderDetails orderDetails, PaymentInfo paymentInfo) {
// Method logic...
}
Identification: Overuse of primitive types instead of small objects for simple tasks.
Example:
Refactoring Techniques:
Replace Data Value with Object: Create an object for the type of data you have.
Replace Array with Object: Use an object to represent a group of related data instead of an array.
Problem Code:
Refactored Code:
class Phone {
private String number;
public Phone(String number) {
this.number = number;
}
public String formatNumber() {
// Format number (e.g., add dashes)
return number;
}
}
class User {
private String name; // Still primitive type, appropriate here
private Phone phone; // Replaced with object
public User(String name, String phoneNumber) {
this.name = name;
this.phone = new Phone(phoneNumber);
}
public void displayUserInfo() {
System.out.println("Name: " + name + ", Phone: " + phone.formatNumber());
}
}Problem Code:
Refactored Code:
class User {
private String name;
private String phone;
private String address;
public User(String name, String phone, String address) {
this.name = name;
this.phone = phone;
this.address = address;
}
public void displayUserInfo() {
System.out.println("Name: " + name + ", Phone: " + phone + ", Address: " + address);
}
}
Image Credit: Refactoring.Guru
Identification: Groups of data that always appear together but aren’t organized into a structure.
Example:
Refactoring Techniques:
Problem Code:
class CustomerService {
public void createCustomer(String firstName, String lastName, String street, String city, String zip) {
// Logic to create a customer
}
}createCustomer method takes multiple parameters related to customer and address, making it cumbersome and prone to errors.Refactored Code:
class Customer {
String firstName;
String lastName;
Address address;
}
class Address {
String street;
String city;
String zip;
}
class CustomerService {
public void createCustomer(Customer customer) {
// Logic to create a customer
}
}Definition: Poor OOP application leading to design flaws.
Characteristics: Complex and rigid code; misused inheritance and polymorphism.
Cause: Misunderstanding or misapplying OOP principles; forcing problems into unsuitable solutions.

Image Credit: Refactoring.Guru

public class AnimalSound {
public String makeSound(String animalType) {
switch (animalType) {
case "dog":
return "Bark";
case "cat":
return "Meow";
case "cow":
return "Moo";
// More cases for different animals
default:
throw new IllegalArgumentException("Unknown animal type");
}
}
}makeSound method.interface Animal {
String makeSound();
}
class Dog implements Animal {
public String makeSound() { return "Bark"; }
}
class Cat implements Animal {
public String makeSound() { return "Meow"; }
}
class Cow implements Animal {
public String makeSound() { return "Moo"; }
}
// Using the Animal interface
public class AnimalSound {
public String makeSound(Animal animal) {
return animal.makeSound();
}
}Refactoring Techniques:
Replace Conditional with Polymorphism: Use polymorphism to handle varying behavior based on type.
Replace Type Code with Subclasses: Create subclasses for each type code.
Problem Code:
Refactored Code:
abstract class Employee {
abstract double calculatePay();
}
class CommissionedEmployee extends Employee {
double calculatePay() {
return calculateCommissionedPay();
}
}
class HourlyEmployee extends Employee {
double calculatePay() {
return calculateHourlyPay();
}
}
// Usage:
Employee employee = new CommissionedEmployee();
double pay = employee.calculatePay();Problem Code:
Refactored Code:
abstract class Employee {
abstract double calculatePay();
}
class CommissionedEmployee extends Employee {
double calculatePay() {
return calculateCommissionedPay();
}
}
class HourlyEmployee extends Employee {
double calculatePay() {
return calculateHourlyPay();
}
}
class SalariedEmployee extends Employee {
double calculatePay() {
return calculateSalariedPay();
}
}
// Usage:
Employee employee = new HourlyEmployee();
double pay = employee.calculatePay();
Image Credit: Refactoring.Guru

Image Credit: Refactoring.Guru
public class ProductManager {
public void addProduct(Product product) {
// Add logic
}
public void displayProduct(Product product) {
// Display logic
}
public void orderProduct(Product product) {
// Order logic
}
// More methods related to products
}// After Extract Class
public class ProductDisplay {
public void displayProduct(Product product) {
// Display logic
}
}
public class ProductOrder {
public void orderProduct(Product product) {
// Order logic
}
}Definition: Redundant code elements that reduce clarity and efficiency.
Characteristics: Includes excessive comments, duplicate or obsolete code, and underused or functionality-lacking classes.
Cause: Arises from inadequate maintenance or speculative coding without future utilization.

Image Credit: Refactoring.Guru
What It Is: The same code structure appearing in more than one place.
Problems: Increases the likelihood of errors and makes the code harder to maintain.
// Example of Duplicated Code
public void processOrder() {
// ... some code ...
double orderTotal = price * quantity;
double tax = orderTotal * 0.05;
double finalPrice = orderTotal + tax;
// ... more code ...
}
public void calculateBill() {
// ... some code ...
double billTotal = itemPrice * itemCount;
double tax = billTotal * 0.05;
double finalAmount = billTotal + tax;
// ... more code ...
}// Extracted method to handle tax and final price calculation
public double calculateTotalWithTax(double total) {
double tax = total * 0.05;
return total + tax;
}
public void processOrder() {
// ... some code ...
double orderTotal = price * quantity;
double finalPrice = calculateTotalWithTax(orderTotal);
// ... more code ...
}
public void calculateBill() {
// ... some code ...
double billTotal = itemPrice * itemCount;
double finalAmount = calculateTotalWithTax(billTotal);
// ... more code ...
}By extracting the common logic into a method, we reduce duplication and improve maintainability.
Identification: Look for similar code segments across different methods or classes.
Examples:
Copy-pasted loops or conditionals.
Repeated code blocks in different parts of the application.
Problem:
Refactored Code:
class ReportGenerator {
private int calculateSum(List<Integer> data) {
int sum = 0;
for(Integer value : data) { sum += value; }
return sum;
}
void generateReport() {
// ... some code ...
System.out.println("Sum: " + calculateSum(data));
// ... more code ...
}
}Problem:
Refactored Code:
class Animal {
void eat() { System.out.println("Eating..."); }
}
class Dog extends Animal {}
class Cat extends Animal {}Definition: Problems causing excessive class dependencies or overuse of delegation.
Characteristics: Excessive inter-class dependencies, intrusive access, long method chains, and unnecessary delegation.
Cause: Design flaws leading to tight coupling and delegation, affecting modularity and maintenance.

Image Credit: Refactoring.Guru
Identification: A method that seems more interested in a class other than the one it actually is in.
Example:
Refactoring Techniques:
Move Method: Move the method to the class it is most interested in.
Extract Method: If only part of the method suffers from feature envy, extract that part.
Problem Code:
class ReportGenerator {
// ... other methods ...
public void generateReport(Data data) {
System.out.println("Report Title: " + data.getTitle());
System.out.println("Report Data: " + data.getFormattedData());
// Several other lines interacting with 'Data' class
}
}
class Data {
String getTitle() { /* ... */ }
String getFormattedData() { /* ... */ }
// ... other methods ...
}Refactored Code:
class ReportGenerator {
// ... other methods ...
public void generateReport(Data data) {
data.printReportDetails();
}
}
class Data {
// ... other methods ...
void printReportDetails() {
System.out.println("Report Title: " + getTitle());
System.out.println("Report Data: " + getFormattedData());
// Moved method content here
}
}Problem Code:
class ReportGenerator {
public void generateReport(Data data) {
// ... some code working with ReportGenerator's own data ...
System.out.println("Report Data: " + data.getFormattedData());
// ... more code working with ReportGenerator's own data ...
}
}
class Data {
String getFormattedData() { /* ... */ }
// ... other methods ...
}Refactored Code:
class ReportGenerator {
public void generateReport(Data data) {
// ... some code working with ReportGenerator's own data ...
printDataDetails(data);
// ... more code working with ReportGenerator's own data ...
}
private void printDataDetails(Data data) {
System.out.println("Report Data: " + data.getFormattedData());
// Isolated the part that was showing feature envy
}
}
class Data {
// ... other methods ...
}
Image Credit: Refactoring.Guru
a->b()->c()->d().// After applying Hide Delegate
public class Order {
public String getCustomerZipCode() {
return this.getCustomer().getAddress().getZipCode();
}
}
Comments
Image Credit: Refactoring.Guru