重构 - 读书笔记(Python示例)

栏目: Python · 发布时间: 8年前

内容简介:重构 - 读书笔记(Python示例)

去年十二月, 重读时, 输出了几篇博文, 主要几章重构技巧梳理6/ 7 / 8 / 9 / 10 /11, 这周重读时, 从另一个角度总结一下

我们总是想着, 找个时间重构, 额, 其实, 重构更应该放在平时, 每一次去变更代码时处理. 毕竟, 所谓的重构契机有时候太过遥远; 而如果不做重构, 痛苦的是每时每刻维护代码的自己

如果你发现自己需要为程序添加一个特性, 而代码结构使你无法很方便地达成目的, 那就先重构那个程序, 使特性的添加比较容易进行, 然后再添加特性

另外, 如果可能, 尽量加单元测试, 哪怕一次只增加一两个, 一段时间后, 你会发现, 你会感谢过去的自己

原则

  • 小步前进, 频繁测试
  • 隔离变化
  • 控制可见范围, 让变量/常量/函数/类等, 在最小的范围内可见. 例如设为私有变量/私有函数, 移除不必要的设值函数
  • 重构时, 不要关注性能. 到性能优化阶段, 再关注性能. 不同阶段关注点不一样, 不要过早优化. 很多时候, 性能并不是瓶颈, 可读性和可维护性更重要
  • 任何时候, 都不要拷贝代码, 拷贝类, 甚至拷贝源码文件

1. 命名

  • 好的名字, 清晰表达其含义. 命名至关重要
  • 好的代码应该清楚表达出自己的功能, 变量名称是代码清晰的关键
  • 如果为了提高代码的可读性, 需要修改某些名字, 大胆去改!
  • IDE/单元测试/好的查找替换工具
  • 建议读 编写可读代码的艺术 这本书.

2. 常量和临时变量

提取常量

你有一个字面数值, 带有特别含义. 创建一个常量, 根据其意义为它命名, 并将上述字面数值替换为这个常量

def potential_energy(mass, height):
    return mass * 9.81 * height

to

GRAVITATIONAL_CONSTANT = 9.81
def potential_energy(mass, height):
    return mass * GRAVITATIONAL_CONSTANT * height

任何时候, 都不要拷贝常量, 当你发现要改一个数据, 要到非常多的文件去改字面值时, 你就需要意识到, 该提取常量了

加入: 引入解释性变量

一个复杂的表达式, 将复杂表达式或其中一部分放入临时变量, 以变量名称来解释表达式用途

if "MAC" in platform.upper() and "IE" in browser.upper() and was_initialized() and resize > 0:
    #do something

to

is_macos = "MAC" in platform.upper()
is_ie_browser = "IE" in browser.upper()
was_resized = resize > 0

if is_macos and is_ie_browser and was_initialized() and was_resized:
    # do something

分解: 分解临时变量

某个临时变量被赋值超过一次, 非循环变量, 也不用于收集计算结果.每次赋值, 创砸一个独立, 对应的临时变量

单一职责原则

tmp = 2 * (height * width)
print tmp

tmp = height * width
print tmp

to

perimeter = 2 * (height * width)
print perimeter

area = height * width
print area

去除: 移除临时变量

临时变量仅被一个简单表达式赋值一次, 可以去除这个临时变量

临时变量, 简单表达式, 另外, 需要考虑使用次数, 如果仅使用一次, 可以去除, 如果多次, 则需谨慎考虑对可读性的而影响

best_price = order.base_price()
return best_price > 1000

to

return order.base_price > 1000

移除: 控制标记

在一系列布尔表达式中, 某个变量带有"控制标记"(control flag)的作用. 以break语句或return取代控制标记

def dosomething():
    is_success = False
    if xxx:
       is_success = True

    if yyy:
       is_success = False
    ...
    return is_success

to

def dosomething():
    if xxx:
        return True

    if yyy:
        return True
    ...
    return False # 一定不要忘记

注意力相关.

这类逻辑中, 很痛苦的是, 你必须无时无刻关注这些控制标记的值, 追踪 变量在每一个逻辑之后的变化, 会带来额外的思考负担, 从而让代码变得不易读.

3. 函数

拆分: Extract Method提炼函数

你有一段代码可以被组织在一起并独立出来, 将这段代码放进一个独立函数中, 并让函数名称解释该函数的用途

def print_owing(double amount):
    print_banner()

    // print details
    print "this is the detail: "
    print "amnount: %s" % amount

to

def print_details(amount):
    print "this is the detail: "
    print "amnount: %s" % amount

def print_owing(double amount):
    print_banner()
    print_details(amount)

去除: Inline Method内联函数

一个函数的本体与名称同样清楚易懂, 在函数调用点插入函数本体, 然后移除该函数

小型函数, 函数太过简单了, 可能只有一个表达式, 去除函数!

def is_length_valid(x):
    return len(x) > 10

print 'the length is %s' % ('valid' if is_length_valid(x) else 'invalid')

to

print 'the length is %s' % ('valid' if len(x) > 10 else 'invalid)

合并: 合并多个函数, 使用参数

若干函数做了类似的工作. 但在函数本体中却包含了不同的值. 建立单一函数, 以参数表达那些不同的值

def five_percent_raise():
    pass
def ten_percent_raise():
    pass

to

def percent_raise(percent):
    pass

副作用: 函数不应该有副作用

某个函数既返回对象状态值, 又修改对象状态. 建立两个不同函数, 一个负责查询, 一个负责修改.

单一职责原则, 一个函数不应该做两件事, 函数粒度尽量小.

4. 表达式

guard(注意力相关)

过多的条件逻辑, 难以理解正常的执行路径. 在 python 中的特征是, 缩进太深

coolshell中曾经讨论过的问题 如何重构“箭头型”代码 , 而在python中的现象是, 缩进嵌套层级太深, 有时候甚至有十几层缩进, 整体难以理解

而减少嵌套缩进的方式是, 使用 guard 语句, 尽早返回,

注意力相关, 尽早 return , 你也就不用关心已经过去的逻辑了, 只需关注后面代码的逻辑.

if _is_dead:
    result = dead_amount()
else:
    if _is_separated:
        result = separated_amount()
    else:
        if _is_retired:
            result = retired_amount()
        else:
            result = normal_payamount()
return result

to

if _is_dead:
    return dead_amount()

if _is_separated:
    return separated_amount()

if _is_retired:
    return retired_amount()

return normal_payamount()

合并: 合并条件表达式

你有一系列条件测试, 都得到相同结果. 将这些测试合并成一个条件表达式, 并将这个条件表达式提炼成为一个独立函数

if _seniority < 2:
    return 0
if _months_disabled > 10:
    return 0
if _is_part_time:
    return 0

to

if is_not_eligible_for_disability:
    return 0

分解: 分解复杂条件表达式

你有一个复杂的条件语句(if-then-else). 从if, the, else三个段落中分别提炼出独立函数

if date < SUMMER_START) or date > SUMMER_END:
    charge = quantity * _winter_rate + _winter_servioce_charge
else:
    charge = quantity * _summer_rate

to

if not_summber(date):
    charge = winter_charge(quantity)
else:
    charge = summber_charge(quantity)

提取: 合并重复的条件片段

在条件表达式的每个分支上有着相同的一段代码. 将这段重复代码搬移到条件表达式之外

if is_special:
    total = price * 0.95
    send()
else:
    total = price * 0.98
    send()

to

if is_special:
    total = price * 0.95
else:
    total = price * 0.98
send()

这是维护系统, 特别是中后期很容易忽略的问题. 很容易在代码中出现, 特别是遇到那种 加需求 的地方, 通常, 会选择不动原来的代码, 加个分支, 复制代码下来改. 但这样的后果是, 逐步地, 会发现每个分支中都有重复代码.

5. 参数及返回值

参数和返回值: 提取对象

如果参数/返回值是一组相关的数值, 且总是一起出现, 可以考虑提取成一个对象.

def get_width_height():
    ....

    return width, height

def get_area(width, height):
    return width, height

to

class Rectangle(object):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

def get_shape():
    ....
    return Rectangle(height, width)

类似的还有: start_time/end_time -> TimeRange /

减少参数

对象调用了某个函数, 并将所得结果作为参数, 传递给另一个函数. 而接受该参数的函数本身也能调用前一个函数. 让参数接收者去除该参数, 并直接调用前一个函数

base_price = quantity * item_price
discount_level = get_discount_level()
final_price = discounted_price(base_price, discount_level)

to

base_price = quantity * item_price
final_price = discounted_price(base_price)

6. 类

搬移: 函数/字段

  • 搬移函数: 某个函数与所在类之外的另一个类有更多的交互, 调用或被调用(例如: 使用另一个对象的次数比使用自己所在对象的次数还多). 即, 跟另一个类更相关. 则搬移过去
  • 搬移字段: 某个字段被其所在类之外的另一个类更多地用到

拆分: 拆分类

某个类做了应该由两个类做的事. 类太大/太臃肿. 建立一个新类, 将相关字段和函数从旧类版移到新类

特征: 类中某些字段是有关系的整体, 或者有相同的前缀

class Persion(object):
    def __init__(self, name, age, office_area_code, office_number):
        self.name = name
        self.age = age
        self.office_area_code = office_area_code
        self.office_number = office_number
    def get_phone_number(self):
        return "%s-%s" % (self.office_area_code, self.office_number)

to

class Person(object):
    def __init__(self, name, age, office_area_code, office_number):
        self.name = name
        self.age = age
        self.phone_number = PhoneNumber(office_area_code, office_number)

    def get_phone_number(self):
        return self.phone_number.get_number()

class PhoneNumber(object):
    def __init__(self, area_code ,number):
        self.area_code = area_code
        self.number = number
    def get_number(self):
        return "%s-%s" % (self.area_code, self.number)

去除

一个类没有做太多的事情, 不再有独立存在的理由.

7. 模式

原则:

  • 慎用
  • 只使用你理解的模式
  • 只在符合的业务场景使用对应模式

adapter

你需要为提供服务的类增加功能, 但是你无法修改这个类.

使用组合(推荐, 持有对象)/继承(加子类), 持有该对象, 增加对应附加功能

adapter思维.

使用场景: 使用一些第三方库处理外部依赖, 例如依赖一个系统, 业务A (requests)/ es (Elasticsearch)/ redis (redispy), 但是, 基于第三方系统, 你需要有自己业务相关的统一处理逻辑, 此时, 你可以建立一个 XXClient , 持有第三方组件底层调用逻辑, 同时封装自身业务逻辑, 在上层直接调用

facade

适配模式中举的例子, 也有 facade 的思想, 将复杂的东西, 统一封装, 对外提供相对简单清晰地接口

template method

出现的次数也很高

装饰器

python中最常用

其他

根据使用场景, 应用策略/桥梁/工厂/观察者等等, 具体看业务场景

举例

重构一个相对较大的 django 项目

  • 明确业务对象, 对象概念, 对象边界
  • 明确分层
  • 明确代码目录结构, 划分模块, 明确每个模块可以放入的东西
  • 粗粒度重构: 移动模块/类/函数, 根据前几步的划分, 将模块/类/函数等, 移动到对应模块中, 同时, 修改 import 和调用点
  • 中粒度重构: 根据 django 项目本身划分, 移动函数
  • 中粒度重构: Extract Method. 读具体函数代码, 遇到 重复代码 / 过长函数 / 过大的类 / 超大的if-else或switch / 包含大段注释的代码 等, 思考, 提炼函数, 放入对应模块
  • 细粒度重构: 提取常量 / 提取枚举 / 修改模块名类名函数名变量名

举例:

  • 对于 django 项目, 原则 fat models, helper modules, thin views, stupid templates
  • fat model , 将对象本身相关的, 尽量放入 models , 这个对象相关的, 可以加入补充一系列 porperty / classmethod / staticmethod , 可以有效地降低使用这个对象时调用处的代码复杂度. 例如, 每次取兑现改一个字段都需要进行转换, 则搞个 property 替换每次都需要的转换逻辑. (找拿到 model 对象后的处理逻辑代码中那些反复出现的, 重复的)
  • 将对象查询相关的, 全部迁移到 manager 中, 需要先通过 Model.objects 查询然后做各种事情的, 迁移放入到 manager
  • utils , 将业务逻辑无关的 工具 函数等, 统一归入 utils 模块中; 将业务有关但多个 application 共用的 utils 放入到 common.utils 模块中, 而将 appication 依赖的局部 utils , 放入到 application.utils
  • constants , 同上, 区分通用, 还是某个 applications 中使用
  • thin view , 业务逻辑, 尽量瘦小简短
  • stupid template , 模板, 尽量傻瓜, 不要包含复杂计算/判断逻辑, 将复杂迁移到后端代码

其他

善用工具, 有方案设计评审, 平时通过 pull request , 走 code review , 有代码风格自动检查, 要求单元测试, 走cicd流程. 在平时, 就有意识地控制代码质量


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Head First HTML5 Programming

Head First HTML5 Programming

Eric Freeman、Elisabeth Robson / O'Reilly Media / 2011-10-18 / USD 49.99

What can HTML5 do for you? If you're a web developer looking to use this new version of HTML, you might be wondering how much has really changed. Head First HTML5 Programming introduces the key featur......一起来看看 《Head First HTML5 Programming》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具