重构:改善既有代码的设计

重构:改善既有代码的设计,第1张

1. 第一组重构 1 提炼函数 1.1 提炼函数的动机
  • 何时重构:需要花时间才能弄懂一段代码
  • 对于api,我们不需要去弄明白代码如何实现,只需要知道功能,则不需要重构
  • 大量短小函数会让程序跑的更快,更容易被缓存
  • 当函数超过10行就应该考虑提炼函数
1.2 做法
  1. 创造新函数,根据意图进行命名(”做什么“来命名,而不是“怎么做”命名)
  2. 将待提炼代码复制到新的函数中
  3. 若提炼的代码引用了作用域限于源函数,则以参数的形式传递给新函数
  4. 在源函数中,将被提炼代码段替换为对目标函数的调用
  5. 测试
  6. 若其他代码与被提炼代码有相似处,使用以函数调用来取代内敛代码
import datetime

class Invoice(object):
    def __init__(self,orders,name):
        self.orders=orders
        self.customer=name



def printOwing(invoice:Invoice)->None:
    outstanding=0
    # 打印账单头
    print("*******************************")
    print("****Customer Owes ****")
    print("*******************************")

    # 计算总欠款
    for o in invoice.orders:
        outstanding+=o

    # 计算还款时间
    today=datetime.datetime.now().date()
    invoice.dueDate=today+datetime.timedelta(days=30)

    # 打印账单详情
    print("name: ",invoice.customer)
    print("amount: ",outstanding)
    print("due: ",invoice.dueDate)

if __name__ == '__main__':
    orders=[1,2,3,4,4]
    invorice=Invoice(orders,"zhangsan")
    printOwing(invorice)
  • 打印账单头:没有局部变量
  • 计算总欠款:引用了源函数的变量
  • 计算还款时间:引用了源函数的变量
  • 打印账单详情:引用源函数的变量,还引用了计算总欠款处理后的变量

import datetime

class Invoice(object):
    def __init__(self,orders,name):
        self.orders=orders
        self.customer=name


def printOwing(invoice:Invoice)->None:
    printBanner()
    outstanding = calculateOutstanding(invoice)
    recordDueDate(invoice)
    printDetails(invoice, outstanding)


def printDetails(invoice, outstanding):
    print("name: ", invoice.customer)
    print("amount: ", outstanding)
    print("due: ", invoice.dueDate)


def recordDueDate(invoice):
    today = datetime.datetime.now().date()
    invoice.dueDate = today + datetime.timedelta(days=30)


def calculateOutstanding(invoice):
    outstanding=0
    for o in invoice.orders:
        outstanding += o
    return outstanding


def printBanner():
    print("*******************************")
    print("****Customer Owes ****")
    print("*******************************")

if __name__ == '__main__':
    orders=[1,2,3,4,4]
    invorice=Invoice(orders,"zhangsan")
    printOwing(invorice)

2. 内联函数

即将函数调用替换成代码段

2.1 动机
  • 函数的内部代码和函数名称一样清晰易读
  • 对于一群组织不合理的函数,可以先将它们内联到大型函数中,再以我们喜欢的方式重写提炼成小函数
2.2 做法
  1. 首先检查函数,确定它不具有多态性(如果一个函数属于一个类,并且有子类继承了这个函数,那么就无法内联
  2. 找出这个函数的所有调用点
  3. 将这个函数的所有调用点都替换成函数本体
  4. 每次替换后,进行测试(小步小步这样好测试
  5. 删除函数的定义
class Customer(object):
    def __init__(self,name,location):
        self.name=name
        self.location=location


def gatherCustomerData(out:dict,acustomer:Customer):
    out["name"]=acustomer.name
    out["location"]=acustomer.location
    
def reportLines(aCustomer:Customer):
    lines={}
    gatherCustomerData(lines,aCustomer)
    return lines


if __name__ == '__main__':
    aCustomer=Customer("zhangsan","hanan")
    lines = reportLines(aCustomer)
    for line in lines:
        print(lines[line])
 
  • 收集数据可以内联

class Customer(object):
    def __init__(self,name,location):
        self.name=name
        self.location=location

def reportLines(aCustomer:Customer):
    lines={}
    lines["name"]=aCustomer.name
    lines["location"]=aCustomer.location
    return lines
3. 提炼变量

引入解释性变量

3.1 动机
  • 表达式复杂难懂
3.2 做法
  1. 确认要提炼的表达式有没有副作用
  2. 声明一个不可修改的变量,把你想要提炼的表达式复制一份,并将其赋值给这个变量。
  3. 用这个变量来取代原来的表达式
  4. 测试
class Order(object):
    def __init__(self,itemPrice,quantity):
        self.itemPrice=itemPrice
        self.quantity=quantity

def price(order:Order):
    return order.quantity*order.itemPrice-\
           max(0,order.quantity-500)*order.itemPrice*0.05+\
           min(order.quantity*order.itemPrice*0.1,100)

if __name__ == '__main__':
    order=Order(9,888)
    amout = price(order)
    print(amout)

class Order(object):
    def __init__(self,itemPrice,quantity):
        self.itemPrice=itemPrice
        self.quantity=quantity

def price(order:Order):
    basePrice=order.quantity*order.itemPrice
    quantityDiscount=max(0,order.quantity-500)*order.itemPrice*0.05
    shipping=min(order.quantity*order.itemPrice*0.1,100)
    return basePrice-quantityDiscount+shipping
    
if __name__ == '__main__':
    order=Order(9,888)
    amout = price(order)
    print(amout)
4. 内联变量 4.1 动机
  • 当变量名并不比表达式本身更具有表现力时,我们可以进行内联变量
4.2 做法
  1. 确认变量复制语句的右侧的表达式没有副作用
  2. 如果变量没有被声明为不可修改,则先将其变为不可修改,并执行测试(确保该变量只被赋值一次
  3. 找到第一处使用该变量的地方,替换成表达式
  4. 测试
  5. 重复前面两步,直到所有该变量处都被替换
  6. 删除变量声明和赋值语句
  7. 测试
5. 改变函数声明

改变函数签名

5.1 函数最重要的是什么
  • 函数名字:好的函数名字,就是通过函数名就知道函数的用途
  • 参数列表:增加函数应用范围,改变连接模块条件,去除耦合

反例

class Person(object):
    def __init__(self,phone):
        self.phone=phone

def format_phone_number(person:Person):
    """
    将个人电话号码转换成特定格式
    """
  • 方法跟Person耦合,有必要吗。

正例

class Person(object):
    def __init__(self,phone):
        self.phone=phone

def format_phone_number(phone:str):
    """
    将电话号码转换成特定格式
    """
  • 只依赖需要的内容。
5.2 为何更改函数申明
  • 为了使函数名更好的解释函数用途
  • 参数列表时函数的上下文,为了使得参数列表更合理
5.3 做法 5.3.1 简单做法
  • 修改函数参数
  • 修改函数声明
  • 替换与测试
5.3.2 迁移式做法
  1. 有必要先对函数体内部加以重构,使得后面的提炼步骤易于开展
  2. 使用提炼函数将函数体提炼成一个新的函数(可以起一个临时的名字)
  3. 如果提炼出的函数需要新增参数,用前面的简单做法添加即可
  4. 测试
  5. 对旧函数使用内联函数
  6. 如果新函数使用了临时名字,再次使用改变函数声明将其改回原来的名字
  7. 测试

如果需要改名的函数时一个对外界提供的API,则在提炼出新函数之后就可以暂停重构了,可以将原来的函数声明标位不推荐使用(deprecated)。这样给客户端一点时间转用新的函数。

5.4 case1

class Book(object):
    def __init__(self)->None:
        self._reservations=[]
        
    # 接收用户预订
    def add_reservation(self,customer):
        self._reservations.append(customer)

if __name__ == '__main__':
    a=Book().add_reservation("zhangsan")
  • 添加参数重构
class Book(object):
    def __init__(self)->None:
        self._reservations=[]

    # 接收用户预订
    def add_reservation(self,customer):
        """
        不推荐使用
        """
        self.add_reservation_v2(customer,False)
        
    # 新的接收用户预订方法
    def add_reservation_v2(self,customer,is_priority):
        # 根据优先级进行一系列处理,省略。。。。
        self._reservations.append(customer)

if __name__ == '__main__':
    a=Book().add_reservation("zhangsan")
5.5 case2
# 判断一个人是否来自新西兰
def in_new_england(a_customer):
    return a_customer.address.state in ["MA","CT","ME","VT","NH","RI"]
# 判断一个人是否来自新西兰

def in_new_england(a_customer):
    """
    已废弃
    """
    state_code=a_customer.address.state
    return in_new_enland_v2(state_code)

# 新版本
def in_new_enland_v2(state_code):
    return state_code in ["MA","CT","ME","VT","NH","RI"]
6. 引入参数对象 6.1 动机
  • 当一组变量总是结伴而行,出没于一个有一个函数。可以考虑将其构造参数对象
6.2 做法
  1. 如果没有何时的数据结构,就创建一个
  2. 测试
  3. 使用改变函数申明给原来的函数新增一个参数,类型是新建的数据结构
  4. 测试
  5. 调整所有调用者,传入新数据结构的适当实例,每修改一处,执行测试
  6. 用新数据结构的每项元素,逐一取代参数列表中与之对应的参数项,然后删除原来的参数,测试
7. 函数组合成类 7.1 动机
  • 当一组函数形影不离地 *** 作统一块数据,那么这时候就该考虑建立一个类了。
  • 类能明确地给这些函数提供一个共用的环境,在对象内部调用这些函数可以少传许多参数。
7.2 做法
  1. 将多个函数共用的数据记录加以封装成一个数据结构,然后作为类的属性
  2. 将使用这个数据记录的每个函数,都搬移到这个新建类中
  3. 用以处理数据记录的逻辑可以用提炼函数提炼出来,加入新类中
8. 函数组合成变换函数 8.1 动机
  • 在软件设计时,通常需要把数据喂给一个程序,让它计算出各种派生信息,而且这些派生信息可以在多处被用到。
  • 此时可以将这些派生数据和源数据一起收集起来。
  • 做法就是,利用源数据创建一个类,这个类包含源数据和源数据的派生信息。
8.2 做法
  1. 创建一个变换函数,它输入是源数据,输出是带有附加信息的数据
  2. 在变换函数中,调用了计算附加信息的函数。
9. 拆分阶段 9.1 动机
  • 当一段代码在同时处理两件不同的事情,就应该想着把它拆分成各自独立的模块
  • 如构造与表示分离
9.2 做法
  1. 将第二阶段的代码提炼成独立的函数(比如第二阶段是表示数据)
  2. 测试
  3. 引入一个中转数据结构,将其作为参数添加到提炼出的新函数的参数列表中
  4. 测试
  5. 逐一检查提炼出的”第二阶段函数“的每个参数,如果某个参数被第一阶段用到,则就将其移动到中转数据结构中。每次搬移都要进行测试。
  6. 对第一阶段的代码运用提炼函数,让提炼出的函数返回中转数据结构
def priceOrder(product,quantity,shippingMethod):
    # 基本价格
    basePrice = product.basePrice * quantity
    # 折扣
    discount = max(quantity - product.discountThreshold, 0) * \
               product.basePrice * product.discountRate;
    # 最终价格
    price = applyShipping(basePrice, shippingMethod, quantity, discount)
    return price

def applyShipping(basePrice, shippingMethod, quantity, discount):
    # 每件运费
    shippingPerCase = shippingMethod.discountedFee \
        if basePrice > shippingMethod.discountThreshold \
        else shippingMethod.feePerCase
    # 总运费
    shippingCost = quantity * shippingPerCase
    # 最终价格
    price = basePrice - discount + shippingCost
    return price
def calculatePricingData(product, quantity):
    priceData = PricingData()
    # 基本价格
    priceData.basePrice = product.basePrice * quantity
    # 折扣
    priceData.discount = max(quantity - product.discountThreshold, 0) * \
               product.basePrice * product.discountRate;
    # 数量
    priceData.quantity=quantity
    return priceData

def applyShipping(priceData, shippingMethod):
    # 每件运费
    shippingPerCase = shippingMethod.discountedFee \
        if priceData.basePrice > shippingMethod.discountThreshold \
        else shippingMethod.feePerCase
    # 总运费
    shippingCost = priceData.quantity * shippingPerCase
    # 最终价格
    price = priceData.basePrice - priceData.discount + shippingCost
    return price

def priceOrder(product,quantity,shippingMethod):
    # 构造计算所需要的数据
    priceData = calculatePricingData(product, quantity)
    # 计算总价
    return applyShipping(priceData, shippingMethod)
2. 封装

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/917360.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-16
下一篇 2022-05-16

发表评论

登录后才能评论

评论列表(0条)

保存