Ogni livello di indentazione rappresenta un nuovo contesto mentale che il programmatore deve tenere sempre presente.
Più livelli ci sono, maggiore è il carico cognitivo.
Metodi più semplici, più facili da comprendere, testare e manutenere.
Il codice diventa auto-documentante poiché ogni operazione complessa viene descritta con un nome significativo.
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;
}
Strategie di refactoring:
Utilizzare early returns per evitare indentazioni superflue
Applicare il pattern Extract Method per ogni ciclo o condizione nidificata
Suddividere metodi complessi in una sequenza di chiamate a metodi più piccoli
Le strutture if/else creano percorsi di esecuzione multipli che aumentano la complessità e rendono difficile seguire il flusso del programma.
Promuove un flusso di controllo lineare, riduce il nesting del codice e incoraggia l'uso di guard clauses, polimorfismo e pattern strategy.
Il codice diventa più prevedibile e facile da leggere.
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);
}
Strategie di refactoring:
Le abbreviazioni creano un linguaggio criptico che richiede conoscenze implicite per essere compreso.
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"
};
}
Strategie di refactoring:
Lunghe catene di metodi creano dipendenze nascoste e violano la Law of Demeter ("parla solo con i tuoi amici immediati").
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;
}
Strategie di refactoring:
Entità grandi (classi, pacchetti) tendono ad accumulare responsabilità e a diventare difficili da gestire.
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 */ }
}
Strategie di refactoring:
Troppe variabili di istanza spesso indicano che una classe sta facendo troppo e violando il principio di responsabilità singola.
Forza una rigorosa scomposizione del dominio, aumenta la coesione delle classi, riduce l'accoppiamento e aiuta a scoprire nuovi oggetti e astrazioni che potrebbero essere stati trascurati.
Aumenta la testabilità e la robustezza del sistema.
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
}
}
Strategie di refactoring:
Esporre lo stato interno degli oggetti attraverso getter e setter viola l'incapsulamento e sposta la responsabilità di manipolare i dati al di fuori dell'oggetto.
Si generano comportamenti inaspettati e non previsti.
Rafforza il principio "tell, don't ask", migliora l'incapsulamento, sposta il comportamento dove risiedono i dati e porta a oggetti con comportamenti più ricchi e coesi.
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);
}
}
Strategie di refactoring:
canAfford(amount)
invece di getBalance()
)L'uso diretto di primitivi è un sintomo di "primitive obsession", dove i concetti di dominio vengono ridotti a tipi semplici senza comportamento.
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`;
}
}
Strategie di refactoring:
Qualsiasi collezione dovrebbe essere incapsulata in una classe che fornisce comportamenti specifici per ciò che la collezione contiene, invece di manipolare direttamente array o liste grezze.
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);
}
/* ... */
}
Strategie di refactoring: