您现在的位置是:网站首页 > 代码异味与设计模式重构机会文章详情
代码异味与设计模式重构机会
陈川
【
JavaScript
】
57477人已围观
9472字
代码异味是软件开发中常见的问题,它们暗示着代码可能存在设计缺陷或潜在的技术债务。通过识别这些异味并结合设计模式进行重构,可以显著提升代码的可维护性、可扩展性和可读性。JavaScript作为一门灵活的语言,尤其容易在不经意间引入代码异味,而设计模式则提供了成熟的解决方案。
重复代码
重复代码是最明显的代码异味之一。当相同的逻辑出现在多个地方时,不仅增加了维护成本,还容易导致修改遗漏。例如:
// 重复的验证逻辑
function validateUser(user) {
if (!user.name || user.name.trim() === '') {
throw new Error('用户名不能为空');
}
if (!user.email || !user.email.includes('@')) {
throw new Error('邮箱格式不正确');
}
}
function validateOrder(order) {
if (!order.product || order.product.trim() === '') {
throw new Error('产品名称不能为空');
}
if (!order.email || !order.email.includes('@')) {
throw new Error('邮箱格式不正确');
}
}
这种情况下,可以采用策略模式或模板方法模式进行重构:
// 策略模式重构
const validators = {
notEmpty: (value) => !!value && value.trim() !== '',
isEmail: (value) => value.includes('@')
};
function validate(data, rules) {
for (const [field, rule] of Object.entries(rules)) {
if (!validators[rule](data[field])) {
throw new Error(`${field}验证失败`);
}
}
}
// 使用方式
validate(user, {
name: 'notEmpty',
email: 'isEmail'
});
过长的函数
当一个函数超过50行代码时,就可能成为维护的噩梦。这种函数通常做了太多事情,违反了单一职责原则。例如:
function processOrder(order) {
// 验证订单
if (!order.id) throw new Error('缺少订单ID');
if (!order.items || order.items.length === 0) throw new Error('订单项不能为空');
// 计算总价
let total = 0;
order.items.forEach(item => {
total += item.price * item.quantity;
});
// 应用折扣
if (order.customer.isVIP) {
total *= 0.9;
}
// 生成发票
const invoice = {
orderId: order.id,
total,
date: new Date()
};
// 发送通知
if (order.customer.notificationPref === 'email') {
sendEmail(order.customer.email, '您的订单已处理', `订单总价: ${total}`);
} else if (order.customer.notificationPref === 'sms') {
sendSMS(order.customer.phone, `您的订单${order.id}已处理`);
}
return invoice;
}
可以应用命令模式或职责链模式将其分解:
class OrderProcessor {
constructor() {
this.steps = [
new ValidationStep(),
new PricingStep(),
new DiscountStep(),
new InvoiceGenerationStep(),
new NotificationStep()
];
}
process(order) {
return this.steps.reduce((result, step) => {
return step.execute(order, result);
}, {});
}
}
class ValidationStep {
execute(order) {
if (!order.id) throw new Error('缺少订单ID');
if (!order.items || order.items.length === 0) throw new Error('订单项不能为空');
return order;
}
}
// 其他步骤类类似...
过大的类
当一个类拥有太多属性和方法时,它可能承担了太多职责。例如:
class UserManager {
constructor() {
this.users = [];
}
addUser(user) { /*...*/ }
removeUser(id) { /*...*/ }
findUser(id) { /*...*/ }
validateUser(user) { /*...*/ }
sendWelcomeEmail(user) { /*...*/ }
generateReport() { /*...*/ }
backupUsers() { /*...*/ }
restoreBackup() { /*...*/ }
}
这种情况适合使用单一职责原则和外观模式进行重构:
class UserRepository {
constructor() {
this.users = [];
}
add(user) { /*...*/ }
remove(id) { /*...*/ }
find(id) { /*...*/ }
}
class UserValidator {
validate(user) { /*...*/ }
}
class UserNotifier {
sendWelcomeEmail(user) { /*...*/ }
}
class UserManagerFacade {
constructor() {
this.repository = new UserRepository();
this.validator = new UserValidator();
this.notifier = new UserNotifier();
}
registerUser(user) {
this.validator.validate(user);
this.repository.add(user);
this.notifier.sendWelcomeEmail(user);
}
}
过度使用基本类型
使用基本类型(如字符串、数字)来表示领域概念会导致代码缺乏表达力。例如:
function createOrder(productId, quantity, customerType) {
// customerType可能是'regular', 'vip', 'premium'等
// ...
}
可以使用对象字面量模式或状态模式改进:
class Customer {
constructor(type) {
this.type = type;
}
get discountRate() {
switch(this.type) {
case 'vip': return 0.2;
case 'premium': return 0.3;
default: return 0;
}
}
}
function createOrder(productId, quantity, customer) {
const discount = customer.discountRate;
// ...
}
过度嵌套的条件语句
深层嵌套的if-else或switch语句难以理解和维护。例如:
function getShippingCost(order) {
if (order.customer.isVIP) {
return 0;
} else {
if (order.items.length > 5) {
return 10;
} else {
if (order.total > 100) {
return 5;
} else {
return 15;
}
}
}
}
可以应用策略模式或责任链模式重构:
const shippingRules = [
{
condition: order => order.customer.isVIP,
cost: () => 0
},
{
condition: order => order.items.length > 5,
cost: () => 10
},
{
condition: order => order.total > 100,
cost: () => 5
},
{
condition: () => true, // 默认情况
cost: () => 15
}
];
function getShippingCost(order) {
const rule = shippingRules.find(r => r.condition(order));
return rule.cost();
}
数据泥团
当一组数据总是一起出现时,它们应该被封装成对象。例如:
// 散落在各处的相关数据
const userName = '张三';
const userAge = 30;
const userEmail = 'zhang@example.com';
function sendBirthdayGreeting(name, age, email) {
// ...
}
应该使用对象封装:
class User {
constructor(name, age, email) {
this.name = name;
this.age = age;
this.email = email;
}
sendBirthdayGreeting() {
// ...
}
}
const user = new User('张三', 30, 'zhang@example.com');
user.sendBirthdayGreeting();
发散式变化
当一个类因为不同原因需要频繁修改时,就存在发散式变化问题。例如:
class ReportGenerator {
generatePDF(data) { /*...*/ }
generateHTML(data) { /*...*/ }
generateCSV(data) { /*...*/ }
validateData(data) { /*...*/ } // 验证逻辑
fetchData() { /*...*/ } // 数据获取逻辑
}
可以应用桥接模式分离抽象和实现:
// 抽象部分
class Report {
constructor(dataFetcher, validator, formatter) {
this.dataFetcher = dataFetcher;
this.validator = validator;
this.formatter = formatter;
}
generate() {
const data = this.dataFetcher.fetch();
this.validator.validate(data);
return this.formatter.format(data);
}
}
// 实现部分
class PDFFormatter {
format(data) { /*...*/ }
}
class DataValidator {
validate(data) { /*...*/ }
}
霰弹式修改
与发散式变化相反,当需要对多个类做相同修改时,就存在霰弹式修改。例如多个类中都包含日志逻辑:
class OrderService {
constructor() {
this.logger = new Logger();
}
createOrder() {
this.logger.log('Creating order');
// ...
}
}
class UserService {
constructor() {
this.logger = new Logger();
}
createUser() {
this.logger.log('Creating user');
// ...
}
}
可以使用装饰器模式或**面向切面编程(AOP)**重构:
function withLogging(target, name, descriptor) {
const original = descriptor.value;
descriptor.value = function(...args) {
console.log(`Calling ${name} with`, args);
const result = original.apply(this, args);
console.log(`Called ${name}, result`, result);
return result;
};
return descriptor;
}
class OrderService {
@withLogging
createOrder() {
// ...
}
}
过度耦合
当类之间关系过于紧密时,修改一个类会影响其他类。例如:
class Order {
constructor() {
this.notifier = new EmailNotifier();
}
confirm() {
// ...
this.notifier.sendConfirmation();
}
}
可以应用依赖注入和控制反转解耦:
class Order {
constructor(notifier) {
this.notifier = notifier;
}
confirm() {
// ...
this.notifier.sendConfirmation();
}
}
// 使用时
const order = new Order(new EmailNotifier());
// 或
const order = new Order(new SMSNotifier());
临时字段
当一个对象的某些字段只在特定情况下使用时,这些字段就是临时字段。例如:
class Customer {
constructor() {
this.discountCalculationData = null; // 只在计算折扣时使用
}
calculateDiscount() {
this.discountCalculationData = this.fetchDiscountData();
// 复杂的计算逻辑...
}
}
可以使用引入外加函数或策略模式重构:
class DiscountCalculator {
constructor(customer) {
this.customer = customer;
}
calculate() {
const data = this.fetchDiscountData();
// 计算逻辑...
}
fetchDiscountData() {
// ...
}
}
// 使用
const discount = new DiscountCalculator(customer).calculate();
不恰当的继承
继承被过度使用时,会导致脆弱的类层次结构。例如:
class Animal {
eat() { /*...*/ }
sleep() { /*...*/ }
}
class Bird extends Animal {
fly() { /*...*/ }
}
class Penguin extends Bird {
// 企鹅不会飞,但继承了fly方法
}
可以应用组合优于继承原则和策略模式重构:
class Animal {
constructor(movementStrategy) {
this.movement = movementStrategy;
}
move() {
this.movement.execute();
}
}
const flyMovement = {
execute: () => console.log('Flying')
};
const swimMovement = {
execute: () => console.log('Swimming')
};
const bird = new Animal(flyMovement);
const penguin = new Animal(swimMovement);
过度使用注释
当代码需要大量注释才能理解时,通常意味着代码本身不够清晰。例如:
// 检查用户是否有权限
// 参数user是用户对象
// 返回true如果有权限,否则false
function checkPermission(user) {
// 管理员有全部权限
if (user.role === 'admin') return true;
// 普通用户需要检查权限列表
if (user.permissions) {
// 查找是否有需要的权限
return user.permissions.some(p => p === 'requiredPermission');
}
// 默认无权限
return false;
}
可以通过提取方法和引入解释性变量重构:
function checkPermission(user) {
return isAdmin(user) || hasRequiredPermission(user);
}
function isAdmin(user) {
return user.role === 'admin';
}
function hasRequiredPermission(user) {
const requiredPermission = 'requiredPermission';
return user.permissions?.includes(requiredPermission) ?? false;
}
重构的权衡
虽然设计模式提供了强大的工具来消除代码异味,但过度使用模式也会导致代码过于复杂。关键在于识别真正的痛点,而不是为了使用模式而重构。例如,对于简单的条件语句,直接使用if-else可能比引入策略模式更合适。重构应该以实际需求为导向,而不是纯粹的理论完美。
上一篇: 模式重构的识别与实施步骤
下一篇: 遗留系统向设计模式演进策略