Movatterモバイル変換


[0]ホーム

URL:


WKLKEN THINKING
Jun 17, 2017

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

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


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

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

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

原则


1. 命名

2. 常量和临时变量

提取常量

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

defpotential_energy(mass,height):returnmass*9.81*height

to

GRAVITATIONAL_CONSTANT=9.81defpotential_energy(mass,height):returnmass*GRAVITATIONAL_CONSTANT*height

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

加入: 引入解释性变量

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

if"MAC"inplatform.upper()and"IE"inbrowser.upper()andwas_initialized()andresize>0:#do something

to

is_macos="MAC"inplatform.upper()is_ie_browser="IE"inbrowser.upper()was_resized=resize>0ifis_macosandis_ie_browserandwas_initialized()andwas_resized:# do something

分解: 分解临时变量

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

单一职责原则

tmp=2*(height*width)printtmptmp=height*widthprinttmp

to

perimeter=2*(height*width)printperimeterarea=height*widthprintarea

去除: 移除临时变量

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

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

best_price=order.base_price()returnbest_price>1000

to

returnorder.base_price>1000

移除: 控制标记

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

defdosomething():is_success=Falseifxxx:is_success=Trueifyyy:is_success=False...returnis_success

to

defdosomething():ifxxx:returnTrueifyyy:returnTrue...returnFalse# 一定不要忘记

注意力相关.

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

3. 函数

拆分: Extract Method提炼函数

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

defprint_owing(doubleamount):print_banner()//printdetailsprint"this is the detail: "print"amnount:%s"%amount

to

defprint_details(amount):print"this is the detail: "print"amnount:%s"%amountdefprint_owing(doubleamount):print_banner()print_details(amount)

去除: Inline Method内联函数

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

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

defis_length_valid(x):returnlen(x)>10print'the length is%s'%('valid'ifis_length_valid(x)else'invalid')

to

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

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

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

deffive_percent_raise():passdeften_percent_raise():pass

to

defpercent_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()returnresult

to

if_is_dead:returndead_amount()if_is_separated:returnseparated_amount()if_is_retired:returnretired_amount()returnnormal_payamount()

合并: 合并条件表达式

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

if_seniority<2:return0if_months_disabled>10:return0if_is_part_time:return0

to

ifis_not_eligible_for_disability:return0

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

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

ifdate<SUMMER_START)ordate>SUMMER_END:charge=quantity*_winter_rate+_winter_servioce_chargeelse:charge=quantity*_summer_rate

to

ifnot_summber(date):charge=winter_charge(quantity)else:charge=summber_charge(quantity)

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

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

ifis_special:total=price*0.95send()else:total=price*0.98send()

to

ifis_special:total=price*0.95else:total=price*0.98send()

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

5. 参数及返回值

参数和返回值: 提取对象

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

defget_width_height():....returnwidth,heightdefget_area(width,height):returnwidth,height

to

classRectangle(object):def__init__(self,width,height):self.width=widthself.height=heightdefarea(self):returnself.width*self.heightdefget_shape():....returnRectangle(height,width)

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

减少参数

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

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

to

base_price=quantity*item_pricefinal_price=discounted_price(base_price)

6. 类

搬移: 函数/字段

拆分: 拆分类

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

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

classPersion(object):def__init__(self,name,age,office_area_code,office_number):self.name=nameself.age=ageself.office_area_code=office_area_codeself.office_number=office_numberdefget_phone_number(self):return"%s-%s"%(self.office_area_code,self.office_number)

to

classPerson(object):def__init__(self,name,age,office_area_code,office_number):self.name=nameself.age=ageself.phone_number=PhoneNumber(office_area_code,office_number)defget_phone_number(self):returnself.phone_number.get_number()classPhoneNumber(object):def__init__(self,area_code,number):self.area_code=area_codeself.number=numberdefget_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项目

举例:

其他

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


Table of Contents
 Newer
k8s APIServer源码: go-restful框架
Older 
写给新人的沟通建议

[8]ページ先頭

©2009-2025 Movatter.jp