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:
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.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.
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.
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.
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
}
}generateReport method is now concise, with clear sub-methods handling specific parts of the report generation process.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
}
}public class UserProfile {
private String firstName;
private String lastName;
// Other fields...
// Constructor
public UserProfile(String firstName, String lastName, /* other parameters */) {
this.firstName = firstName;
this.lastName = lastName;
// Initialize other fields...
}
// Getters and Setters...
}
// Now the method looks like this:
public void createProfile(UserProfile profile) {
// Method logic using profile object...
}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();
}
}Regular Reviews: Peer reviews and pair programming can help identify smells.
Refactoring Tools: Many IDEs and tools can point out common smells.
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.
Issues: Makes maintenance harder, increases the risk of errors during updates, and bloats the codebase.
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 {}Problem Code:
abstract class Game {
void play() {
start();
// Duplicate game loop logic in each subclass
end();
}
}
class Chess extends Game {
void start() { /*...*/ }
void end() { /*...*/ }
// Duplicate game loop logic
}
class Checkers extends Game {
void start() { /*...*/ }
void end() { /*...*/ }
// Duplicate game loop logic
}abstract class Game {
final void play() {
start();
runGame(); // Defined by subclasses
end();
}
abstract void start();
abstract void runGame();
abstract void end();
}
class Chess extends Game {
void start() { /*...*/ }
void runGame() { /* Specific Chess logic */ }
void end() { /*...*/ }
}
class Checkers extends Game {
void start() { /*...*/ }
void runGame() { /* Specific Checkers logic */ }
void end() { /*...*/ }
}Identification: Methods that span dozens of lines, often doing more than one thing.
Example:
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
}Identification: Classes with an excessive number of fields, methods, or lines of code.
Example:
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
}
}Identification: Methods with a long list of parameters, making them hard to understand and use.
Example:
Refactoring Techniques:
Introduce Parameter Object: Group related parameters into a single object.
Replace Method with Method Object: Turn the method into an object that has the original parameters as fields.
Problem Code:
Refactored Code:
class ReportParameters {
String title;
String author;
Date date;
Data data;
Format format;
// Constructor to initialize fields
ReportParameters(String title, String author, Date date, Data data, Format format) {
this.title = title;
this.author = author;
this.date = date;
this.data = data;
this.format = format;
}
}
public void createReport(ReportParameters params) {
// Use params.title, params.author, etc.
}Problem Code:
Refactored Code:
class ReportCreator {
private String title;
private String author;
private Date date;
private Data data;
private Format format;
ReportCreator(String title, String author, Date date, Data data, Format format) {
this.title = title;
this.author = author;
this.date = date;
this.data = data;
this.format = format;
}
public void create() {
// Method body with access to the fields directly
}
}
// Usage
ReportCreator creator = new ReportCreator(title, author, date, data, format);
creator.create();Identification: Excessive use of switch or complex if-else chains in the code.
Example:
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();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 ...
}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
}
}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);
}
}Purpose of Testing: Confirm unchanged behavior post-refactoring.
Continuous Testing: Implement continuous testing during refactoring.
Test Coverage: Extensive test coverage for thorough checks.
Scenario: Refactoring a method to calculate discounts based on customer type.
Before Refactoring: Complex method with multiple conditionals.
Test Case Example:
@Test
public void testCalculateDiscount() {
Customer customer = new Customer("Regular");
double discount = order.calculateDiscount(customer);
assertEquals(0.1, discount, "Regular customers should get a 10% discount.");
}Refactoring: Use polymorphism to handle different customer types.
After Refactoring: Cleaner method with customer type determining discount strategy.
Motivation: You have a code fragment that can be grouped together.
Mechanics: Identify the fragment, create a new method, and replace the old code with a call to the method.
Example:
// After
public void printOwing() {
printBanner();
printDetails(getOutstanding());
}
private void printDetails(double outstanding) {
System.out.println ("name: " + _name);
System.out.println ("amount: " + outstanding);
}Motivation: The name of the method does not clearly describe what the method does.
Mechanics: Change the method name and update all references to it.
Example:
// After
class Person {
String getOfficePhoneNumber() {
return officeTelephone.getTelephoneNumber();
}
}Motivation: A method’s body is just as clear as its name, or it’s used only in a few places.
Mechanics: Replace calls to the method with the method’s content and delete the method.
Example:
Motivation: A method or field is more related to another class than the one it currently is in.
Mechanics: Create a new method/field in the target class and adjust the code to reference the new location.
Example:
// After
class AccountType {
double overdraftCharge() {
if (this.isPremium()) {
// calculation
}
else {
// different calculation
}
}
}Definition: Legacy code often refers to code that is inherited from others or older systems.
Common Issues:
Poorly documented or undocumented.
Tightly coupled and hard to understand.
Lacking tests, making changes risky.
Background: Transition from DVD rental to streaming due to market evolution. Inadequate monolithic codebase for the shift.
Challenges: