您的位置  > 互联网

《重构--改善既有代码设计》和《代码整洁之道》

我希望你能和我一起踏上这段旅程,并工整地写下:

什么是重构?

简单的理解就是在不改变软件可观察行为的情况下改进软件的内部结构,以提高可理解性,降低修改成本。

我们什么时候开始重构? 1. 这是下面列出的我们想要实现的目标和任务(选择的是我们已经实践过的) 2. 添加(添加参数)

描述:如果一个函数无需任何参数(包括使用其他函数)就能解决您的问题,那就太好了。 但在我们日常开发中,我们经常需要给函数添加参数。

动机:所以使用这种重构的动机很简单,你要添加一个函数,而修改后的函数需要一些以前没有的信息。

// ① 函数参数 (理论上少于等于2个)
function createMenu(title, body, buttonText, cancellable{
  ...
}
// ② 超过2个参数 使用对象统一传递
interface IMenuConfig {
  title?: string;
  body?: string;
  buttonText?: string;
  cancellable?: boolean;
}
const menuConfig: IMenuConfig = {
  title: 'Foo',
  body: 'Bar',
  buttonText: 'Baz',
  cancellable: true,
};

function createMenu(menuConfig: IMenuConfig{}

我们通过将多个参数合并为一个来解决上面的问题,减少过长的参数(是代码数据的臭味,臭味不是翻译的尴尬,而是臭味包括以下内容),这将是后面一步步介绍。

代码(重复代码)

长(函数太长)

大班(班级太大)

Long List(参数列表太长)

(发散变化)等等...

function add(...rest: number[]): number;
function add(...rest: string[]): string;
function add(...rest: any[]{
    let first = rest[0];
    if (typeof first === 'number') {
        return rest.reduce((pre, cur) => pre + cur);
    }
    if (typeof first === 'string') {
        return rest.join('');
    }
}
console.log(add(12))
console.log(add('a''b''c'));
//不过如上代码我们可以更加简洁的完成它
interface ConfigFn {
  (value: T): void;
}
var getData2: ConfigFn = function <T>(value: T): T {
  return value;
};
getData2<string>('老袁');

当然,我们还有很多其他关于函数的重构规则,稍后我们会慢慢给大家介绍。

3. to(将双向关联改为单向关联)

描述:两个类之间存在双向关联,但其中一个类现在不再需要另一个类的属性。

动机:双向关联很有用,但它们是有代价的,即维护双向链接。 大量的双向链接很容易导致对象死亡,但仍然存在堆区域导致内存泄漏。 此外,双向连接之间还存在依赖关系。 如果这是两个没有打包的独立文件,比如在node中运行,这就是跨文件依赖。 跨文件依赖关系创建了一个耦合系统,导致任何微小的更改都会产生许多不可预测的后果。 因此,仅当您确实需要双向关联时才使用它。

// 打折商品订单类
class Order {
  private _customer: Customer;
  public getCustomer(): Customer {
    return this._customer;
  }
  public setCustomer(customerName: string, customer: Customer): void {
    const { _customer } = this;
    if (_customer) {
      // 假设我们规定一个打折商品只能购买一单 价格实时计算
      customer.friendOrders.delete(customerName);
    } else {
      this._customer = customer;
      customer.friendOrders.set(customerName, this);
    }
  }
  public disCountedPrice(): number {
    return Math.random() * this._customer.getDiscount();
  }
}
class Customer {
  private _orders = new Map<string, Order>();
  public addOrder(orderName: string, order: Order): void {
    order.setCustomer(orderName, this);
  }
  public getDiscount(): number {
    //根据用户等级获取除折扣以外的价格
    return 0.8;
  }
  public getPriceFor(order: Order): number {
    return order.disCountedPrice();
  }
  get friendOrders() {
    return this._orders;
  }
}
const john = new Customer();
const basket = new Order();
john.addOrder('shoes', basket);
const priceShoes = john.getPriceFor(basket);
console.log('①', priceShoes);
john.addOrder('clothes', basket);
const priceClothes = john.getPriceFor(basket);
console.log('②', priceClothes);
console.log('一共消费', priceShoes + priceClothes);

/* ①以上这段的核心是为了计算最后商品的价格
*  ②获取商品的价格我们分为了2步骤 一个是获取用户的VIP级别
*  ③然后获取了商品本身的随机打折数  最终相乘
*  ④其核心代码是addOrder好给Order的_customer赋值 
     其实完全没必要
*/

仔细想一想,先有Order,我们完全可以去掉这个链接。 订单依赖于获取折扣信息,因此:

//修改Order 然后外部传入customer 将两个类彻底分开
public disCountedPrice(customer: Customer): number {
    return Math.random() * customer.getDiscount();
}
//或者直接修改Customer
public getPriceFor(order: Order): number {
    return order.disCountedPrice(this);
}

最后还有一个暴力的方法,我们可以修改我们主动寻找的实例。 这样就维持了单向,最后我们就可以真正取消它了。 核心就是去掉back指针。

4. to(将单向关联更改为双向关联)

这正好与上面相反,也就是说,如果两个类需要互相制约,某个类必须控制某个类,正好符合将单向关联改为双向关联,那么 Vue 的双向数据绑定 和数据的关系正好是最合适的。

前端圈最好的刷题工具

扫码获取前端问答工具,解锁800+真实前端面试题并附答案和分析,全部来自BAT、TMD等一二线互联网公司。

我们不会每次都给你写太多的重构规则,争取每天花几分钟的时间来真正理解重构。 明天见~如果有什么意见或者反馈,欢迎在下方留言。

作者【老袁】

2020 年 7 月 28 日

精彩文章回顾,点此直接前往。