Each level of indentation represents a new mental context that the programmer must always keep in mind.
The more levels there are, the greater the cognitive load.
Simpler methods, easier to understand, test, and maintain.
The code becomes self-documenting as each complex operation is described with a meaningful name.
function checkUser(user) {
if (user.isActive()) {
if (user.hasPermission('admin')) {
if (user.getLoginAttempts() < 3) {
return true;
}
}
}
return false;
}
function checkUser(user) {
if (!user.isActive()) return false;
if (!user.hasPermission('admin')) return false;
if (user.getLoginAttempts() >= 3) return false;
return true;
}
Refactoring strategies:
Use early returns to avoid unnecessary indentation
Apply the Extract Method pattern for every loop or nested condition
Break complex methods into a sequence of calls to smaller methods
If/else structures create multiple execution paths that increase complexity and make it difficult to follow the program flow.
Promotes a linear control flow, reduces code nesting, and encourages the use of guard clauses, polymorphism, and strategy patterns.
The code becomes more predictable and easier to read.
function processPayment(payment) {
if (payment.isValid()) {
return payment.process();
} else {
throw new Error("Pagamento non valido");
}
}
function processPayment(payment) {
if (!payment.isValid()) {
throw new Error("Pagamento non valido");
}
return payment.process();
}
function calculateShipping(order) {
if (order.isInternational()) {
return order.getWeight() * 10;
} else if (order.isExpress()) {
return order.getWeight() * 5;
} else {
return order.getWeight() * 2;
}
}
function calculateShipping(order) {
if (order.isInternational()) return shippingRates.international(order);
if (order.isExpress()) return shippingRates.express(order);
return shippingRates.standard(order);
}
const shippingRates = {
international: order => order.getWeight() * 10,
express: order => order.getWeight() * 5,
standard: order => order.getWeight() * 2
};
Strategy Pattern
// order.type = "international" | "express" | "standard"
function calculateShipping(order) {
const shippingRateStrategy = shippingRates[order.type]
?? shippingRates["standard"];
return shippingRateStrategy(order);
}
Refactoring strategies:
Abbreviations create a cryptic language that requires implicit knowledge to be understood.
function procPymnt(usr, amt, curr, pType) {
const tx = curr === "EUR" ? amt * 0.22 : amt * 0.1;
const tamt = amt + tx;
if (usr.pHist && usr.pHist.lstPymt > 0 && pType === "cc") {
return { sts: "ok", ttl: tamt, fee: tamt * 0.03 };
}
return { sts: "err", ttl: tamt, msg: "inv pymt method" };
}
function processPayment(user, amount, currency, paymentType) {
const tax = currency === "EUR" ? amount * 0.22 : amount * 0.1;
const totalAmount = amount + tax;
if (user.paymentHistory && user.paymentHistory.lastPayment > 0 && paymentType === "creditCard") {
return { status: "ok", total: totalAmount, fee: totalAmount * 0.03 };
}
return { status: "error", total: totalAmount, message: "Invalid payment method" };
}
function processPayment(user, amount, currency, paymentType) {
const tax = currency === "EUR"
? amount * 0.22
: amount * 0.10;
const totalAmount = amount + tax;
if (
user.paymentHistory
&& user.paymentHistory.lastPayment > 0
&& paymentType === "creditCard"
){
return {
status: "ok",
total: totalAmount,
fee: totalAmount * 0.03
};
}
return {
status: "error",
total: totalAmount,
message: "Invalid payment method"
};
}
Refactoring strategies:
Long chains of methods create hidden dependencies and violate the Law of Demeter ("talk only to your immediate friends").
function calculateShippingCost(order) {
const shippingCost = order.getCustomer().getAddress().getCountry().getShippingRate() * order.getTotalWeight();
if (order.getCustomer().getMembershipLevel().hasDiscount()) {
return shippingCost * 0.9;
}
return shippingCost;
}
function calculateShippingCost(order) {
const customer = order.getCustomer();
const address = customer.getAddress();
const country = address.getCountry();
const shippingRate = country.getShippingRate();
const totalWeight = order.getTotalWeight();
const shippingCost = shippingRate * totalWeight;
const membership = customer.getMembershipLevel();
return membership.hasDiscount()
? shippingCost * 0.9
: shippingCost;
}
Refactoring strategies:
Large entities (classes, packages) tend to accumulate responsibilities and become difficult to manage.
class UserService {
constructor(database) {
this.database = database;
}
createUser(userData) { /* logica per creare un utente */ }
updateUser(id, data) { /* logica per aggiornare un utente */ }
removeUser(id) { /* logica per eliminare un utente */ }
authenticateUser(email, password) { /* logica per autenticare */ }
resetPassword(email) { /* logica per reset password */ }
sendWelcomeEmail(user) { /* logica per inviare email */ }
generateUserReport(user) { /* logica per generare report */ }
validateUserData(data) { /* logica per validare dati */ }
}
class UserRepository {
constructor(database) {
this.database = database;
}
create(userData) { /* logica per creare un utente */ }
update(id, data) { /* logica per aggiornare un utente */ }
remove(id) { /* logica per eliminare un utente */ }
findById(id) { /* logica per trovare un utente */ }
}
class Authenticator {
constructor(userRepository) {
this.userRepository = userRepository;
}
authenticate(email, password) { /* logica di autenticazione */ }
resetPassword(email) { /* logica per reset password */ }
}
Refactoring strategies:
Too many instance variables often indicate that a class is doing too much and violating the single responsibility principle.
Enforces a rigorous breakdown of the domain, increases class cohesion, reduces coupling, and helps discover new objects and abstractions that may have been overlooked.
Increases testability and robustness of the system.
class OrderService {
constructor(
userRepository,
productRepository,
inventoryService,
paymentGateway,
orderRepository,
emailService,
logger,
taxCalculator,
shippingService,
discountCalculator
) {
this.userRepository = userRepository;
this.productRepository = productRepository;
this.inventoryService = inventoryService;
this.paymentGateway = paymentGateway;
this.orderRepository = orderRepository;
this.emailService = emailService;
this.logger = logger;
this.taxCalculator = taxCalculator;
this.shippingService = shippingService;
this.discountCalculator = discountCalculator;
}
createOrder(userId, items) {
// Implementazione con molte dipendenze
}
}
class OrderService {
constructor(orderFactory, orderRepository) {
this.orderFactory = orderFactory;
this.orderRepository = orderRepository;
}
createOrder(orderRequest) {
const order = this.orderFactory.createFrom(orderRequest);
return this.orderRepository.save(order);
}
}
class OrderFactory {
constructor(productRepository, inventoryService) {
this.productRepository = productRepository;
this.inventoryService = inventoryService;
}
createFrom(orderRequest) {
// Crea l'ordine usando le dipendenze
}
}
class OrderNotifier {
constructor(emailService, logger) {
this.emailService = emailService;
this.logger = logger;
}
notifyOrderCreated(order) {
// Invia notifiche
}
}
Refactoring strategies:
Exposing the internal state of objects through getters and setters violates encapsulation and shifts the responsibility of manipulating data outside the object.
This generates unexpected and unforeseen behaviors.
Reinforces the principle "tell, don't ask", improves encapsulation, shifts behavior where the data resides, and leads to objects with richer and more cohesive behaviors.
class BankAccount {
constructor() {
this._balance = 0;
}
get balance() {
return this._balance;
}
set balance(amount) {
if (amount < 0) throw new Error('Balance cannot be negative');
this._balance = amount;
}
}
function deposit(bankAccount, amount) {
bankAccount.balance = bankAccount.balance + amount;
}
function withdraw(bankAccount, amount) {
if (amount > bankAccount.balance)
throw new Error('Insufficient funds');
bankAccount.balance = bankAccount.balance - amount;
}
class BankAccount {
constructor(initialBalance = 0) {
this.balance = initialBalance;
}
deposit(amount) {
if (amount <= 0) throw new Error('Deposit amount must be positive');
this.balance += amount;
return this.balance;
}
withdraw(amount) {
if (amount <= 0) throw new Error('Withdrawal amount must be positive');
if (amount > this.balance) throw new Error('Insufficient funds');
this.balance -= amount;
return this.balance;
}
transferTo(destinationAccount, amount) {
this.withdraw(amount);
destinationAccount.deposit(amount);
}
}
Refactoring strategies:
canAfford(amount)
instead of getBalance()
)The direct use of primitives is a symptom of "primitive obsession", where domain concepts are reduced to simple types without behavior.
const age = 30;
const email = "christian.nastasi@email.it"
const age = 440;
const age2 = "Ventiquattro";
const email = "christian.nastasi@email.it@qualcosa.it"
const email2 = "pippo";
class Age {
constructor(value) {
if (!Number.isInteger(value)) throw new Error('Age must be an integer');
if (value < 0) throw new Error('Age cannot be negative');
if (value > 120) throw new Error('Age cannot be greater than 120');
this.value = value;
Object.freeze(this);
}
equals(other) {
return other instanceof Age
&& this.value === other.value;
}
isAdult() {
return this.value >= 18;
}
toString() {
return `${this.value} years old`;
}
}
Refactoring strategies:
Any collection should be encapsulated in a class that provides specific behaviors for what the collection contains, instead of directly manipulating raw arrays or lists.
function calculateTotalPrice(products) {
let total = 0;
for (const product of products) {
total += product.price
}
return total;
}
const products = [
{name: 'Apple', price: 0.50},
{name: 'Orange', price: 0.70},
{name: 'Banana', price: 0.90},
];
class ShoppingCart {
constructor(products = []) {
this.products = products;
}
addProduct(product) {
this.products.push(product);
}
calculateTotal() {
return this.products.reduce((t, p) => t + p.price, 0);
}
/* ... */
}
Refactoring strategies: