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

推荐订阅源

V
Visual Studio Blog
Project Zero
Project Zero
阮一峰的网络日志
阮一峰的网络日志
博客园 - 【当耐特】
大猫的无限游戏
大猫的无限游戏
The Register - Security
The Register - Security
C
Check Point Blog
Attack and Defense Labs
Attack and Defense Labs
L
LangChain Blog
Simon Willison's Weblog
Simon Willison's Weblog
S
Schneier on Security
Recorded Future
Recorded Future
GbyAI
GbyAI
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Y
Y Combinator Blog
量子位
A
About on SuperTechFans
I
Intezer
T
Threat Research - Cisco Blogs
MongoDB | Blog
MongoDB | Blog
U
Unit 42
C
CERT Recently Published Vulnerability Notes
Scott Helme
Scott Helme
Cisco Talos Blog
Cisco Talos Blog
P
Palo Alto Networks Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Spread Privacy
Spread Privacy
M
MIT News - Artificial intelligence
雷峰网
雷峰网
博客园 - 聂微东
NISL@THU
NISL@THU
The Hacker News
The Hacker News
G
Google Developers Blog
F
Full Disclosure
博客园 - Franky
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
P
Privacy & Cybersecurity Law Blog
博客园 - 叶小钗
酷 壳 – CoolShell
酷 壳 – CoolShell
T
The Blog of Author Tim Ferriss
Security Latest
Security Latest
T
Tenable Blog
Know Your Adversary
Know Your Adversary
Stack Overflow Blog
Stack Overflow Blog
K
Kaspersky official blog
Blog — PlanetScale
Blog — PlanetScale
博客园 - 司徒正美
C
Cybersecurity and Infrastructure Security Agency CISA
Martin Fowler
Martin Fowler
Schneier on Security
Schneier on 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流程. 在平时, 就有意识地控制代码质量