惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
Visual Studio Blog
T
The Exploit Database - CXSecurity.com
The GitHub Blog
The GitHub Blog
量子位
S
SegmentFault 最新的问题
小众软件
小众软件
酷 壳 – CoolShell
酷 壳 – CoolShell
P
Proofpoint News Feed
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
T
Threat Research - Cisco Blogs
I
Intezer
Hugging Face - Blog
Hugging Face - Blog
Spread Privacy
Spread Privacy
Microsoft Security Blog
Microsoft Security Blog
P
Proofpoint News Feed
Know Your Adversary
Know Your Adversary
人人都是产品经理
人人都是产品经理
G
Google Developers Blog
Cyberwarzone
Cyberwarzone
L
LINUX DO - 热门话题
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
H
Hackread – Cybersecurity News, Data Breaches, AI and More
J
Java Code Geeks
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
T
Tenable Blog
Jina AI
Jina AI
A
About on SuperTechFans
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
MyScale Blog
MyScale Blog
AWS News Blog
AWS News Blog
宝玉的分享
宝玉的分享
WordPress大学
WordPress大学
T
Threatpost
大猫的无限游戏
大猫的无限游戏
I
InfoQ
T
Tor Project blog
Project Zero
Project Zero
F
Full Disclosure
L
Lohrmann on Cybersecurity
P
Palo Alto Networks Blog
G
GRAHAM CLULEY
B
Blog
Apple Machine Learning Research
Apple Machine Learning Research
The Hacker News
The Hacker News
S
Securelist
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
V
V2EX
The Cloudflare Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security

Posts on WKLKEN THINKING

apisix 中的 lrucache apisix 中的服务发现机制 apisix 中的负载均衡 apisix etcd机制 聊聊框架 关于 k8s 的 zero downtime deployment 一些建议 apisix 遇到的一些问题 关于在除夕前一天换了一个洗衣机的故事 Django DRF 性能优化 DRF 的一些实践 Part1: Serializer DRF继承关系图 Better Code: 关于接口的灵活性 新的仓库: wklken/naming 缓存使用的一些经验 Better Code: 抽象: 可扩展性与可维护性的抉择 Better Code: 异常时, 该提示用户哪些信息? Better Code: 更好的异常日志打印 Go: some libs Go: go-redis/cache升级的坑 Go: logrus性能提升 Go: gin validation 远程办公的一点总结 Go: 开发过程中的一些bug 项目管理实践: 风险驱动开发 Go: 一种error wrap调用链处理方式 漫谈技术选型 Go: 基于 apitest 做handler层单元测试 Go: go-sql-driver interpolateparams参数优化 [分享]深度工作 你需要更多的思考时间 Django项目重构小结 工作七年小结: 学习,生活及其他 [分享]bash日常: bash-utils 极客时间推广海报 2017总结: 予时光以意义 k8s APIServer源码: api注册详细细节 k8s APIServer源码: api注册主体流程 k8s APIServer源码: 服务启动 k8s APIServer源码: go-restful框架 写给新人的沟通建议 vim 杂谈 - 关于快速编辑 vim 杂谈 - 关于移动 读书笔记-重构: 章11 处理概括关系 读书笔记-重构: 章10 简化函数调用 读书笔记-重构: 章9 简化表达式 读书笔记-重构: 章8 重新组织数据 读书笔记-重构: 章7 在对象之间搬移特性 读书笔记-重构: 章6 重新组织函数 Python 代码规范小结 [分享]关于vim ElasticSearch集群部署文档 Logstash+ElasticSearch处理mysql慢查询日志 [分享]关于代码调试DE那些事 Logstash+ElasticSearch+Kibana- 实现相对通用的数据收集分析 ELK维护的一些点(二) [分享]Python源码剖析-数据结构 一些Centos Python生产环境的部署命令 摘录<<6个月学会任何一种外语>> ELK 维护的一些点 也许是一个新的开始 一些vim的个性化配置 读书笔记-调试九法 这段时间的一些想法 Python 源码阅读 - 垃圾回收机制 我为什么要写博客 APUE笔记-第一章 UNIX基础知识 Python源码阅读-闭包的实现 Python源码阅读-内存管理机制(二) Python源码阅读-内存管理机制(一) Python-基础-数据结构小结 '活动'设计的一些trick 一些简单的Python测试题 我的tmux配置及说明【k-tmux】 Review and Restart 工作四周年小结 vim插件: surround & repeat[成对符号编辑] vim插件: gundo[时光机] vim插件: expand-region[区域选中] vim插件: quickrun[快速执行] vim插件: trailing-whitespace[行尾空格处理] vim插件: closetag[成对标签补全] vim插件: ctrlp[文件搜索] vim插件: airline[状态栏增强] vim插件: theme[主题] vim插件: tagbar[大纲式导航] vim插件: nerdcommenter[快速注释] vim插件: rainbow_parentheses[括号高亮] vim插件: syntastic[语法检查] vim插件: delimitmate[符号自动补全] vim插件: matchit[成对标签跳转] vim插件: easy-align[快速对齐] vim插件: multiple-cursors[多光标操作] vim插件: vim-signature[快速标记跳转] vim插件: easymotion[快速跳转] vim插件: vundle[管理插件] Elasticsearch几个问题的解决 分享一份 Vim 简介PPT k-vim 更新9.0版本 关于知识管理工具的思考 Logstash+ElasticSearch+Kibana处理nginx访问日志
重构 - 读书笔记(Python示例)
2017-06-17 · via Posts on WKLKEN THINKING

去年十二月, 重读时, 输出了几篇博文, 主要几章重构技巧梳理 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. 函数

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

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流程. 在平时, 就有意识地控制代码质量