Two Instances
It’s usually fine to leave two similar code snippets as-is. The cost of refactoring may outweigh the benefits.
Ce contenu n’est pas encore disponible dans votre langue.
The Rule of Three is an empirical guideline in code refactoring. It helps developers decide when to refactor similar code snippets to avoid duplication. The rule states:
When you have two instances of similar code, it’s usually okay to leave them as-is. But when you have three instances, it’s time to refactor and extract the common logic into a new procedure.
This rule was popularized by Martin Fowler in his book Refactoring: Improving the Design of Existing Code and is attributed to Don Roberts.
Two Instances
It’s usually fine to leave two similar code snippets as-is. The cost of refactoring may outweigh the benefits.
Three Instances
When you encounter a third instance of similar code, it’s time to refactor. Extract the common logic into a reusable function or method.
Beyond Three
If you already have a reusable procedure, adding more instances is straightforward and maintains consistency.
Suppose you have the following duplicated logging logic in multiple places:
// File 1console.log(`[INFO] ${new Date().toISOString()}: User ${userId} logged in.`);
// File 2console.log(`[INFO] ${new Date().toISOString()}: User ${userId} logged out.`);
// File 3console.log(`[INFO] ${new Date().toISOString()}: User ${userId} updated profile.`);function logInfo(message, userId) { console.log(`[INFO] ${new Date().toISOString()}: User ${userId} ${message}.`);}
// File 1logInfo("logged in", userId);
// File 2logInfo("logged out", userId);
// File 3logInfo("updated profile", userId);Suppose you have the following duplicated discount calculation logic:
# File 1def calculate_discount(price, discount_rate): return price * (1 - discount_rate)
# File 2def apply_discount(price, discount_rate): return price * (1 - discount_rate)
# File 3def get_final_price(price, discount_rate): return price * (1 - discount_rate)def calculate_discount(price, discount_rate): """Calculate the final price after applying a discount.""" return price * (1 - discount_rate)
# File 1, 2, and 3from utils.discount import calculate_discountSuppose you have the following duplicated input validation logic:
// File 1public boolean isValidEmail(String email) { return email != null && email.matches("^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$");}
// File 2public boolean checkEmail(String email) { return email != null && email.matches("^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$");}
// File 3public boolean validateEmail(String email) { return email != null && email.matches("^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$");}public class ValidationUtils { public static boolean isValidEmail(String email) { return email != null && email.matches("^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$"); }}
// File 1, 2, and 3import utils.ValidationUtils;ValidationUtils.isValidEmail(email);Start Small
Begin by refactoring small, manageable chunks of code. This reduces the risk of introducing bugs.
Test After Refactoring
Always test your code after refactoring to ensure the changes work as expected.
Document Your Changes
Add comments or documentation to explain the purpose of the new function or method.
Use Meaningful Names
Name your extracted functions or methods clearly to reflect their purpose.
Suppose you’re working on a web application with multiple endpoints that validate user input. Initially, the validation logic is duplicated across several files:
app.post('/register', (req, res) => { if (!req.body.email || !req.body.email.includes('@')) { return res.status(400).send('Invalid email'); } // ...});
// routes/profile.jsapp.put('/update', (req, res) => { if (!req.body.email || !req.body.email.includes('@')) { return res.status(400).send('Invalid email'); } // ...});
// routes/auth.jsapp.post('/login', (req, res) => { if (!req.body.email || !req.body.email.includes('@')) { return res.status(400).send('Invalid email'); } // ...});function validateEmail(email) { if (!email || !email.includes('@')) { throw new Error('Invalid email'); }}
// routes/user.jsapp.post('/register', (req, res) => { try { validateEmail(req.body.email); // ... } catch (error) { return res.status(400).send(error.message); }});
// routes/profile.jsapp.put('/update', (req, res) => { try { validateEmail(req.body.email); // ... } catch (error) { return res.status(400).send(error.message); }});
// routes/auth.jsapp.post('/login', (req, res) => { try { validateEmail(req.body.email); // ... } catch (error) { return res.status(400).send(error.message); }});DRY (Don't Repeat Yourself)
A principle of software development aimed at reducing repetition of software patterns.
Code Smells
Indicators of potential problems in the codebase, such as duplicated code, long methods, or large classes.
SOLID Principles
A set of five design principles to make software designs more understandable, flexible, and maintainable.