Code Smells 代码异味
1. Duplicated Code 代码重复
例子
已经写过类似的Python代码,但是没有放在函数里,所以能难使用
import turtle
t = turtle.Pen()
t.pensize(5)
#1绿色枝干
t.color("green")
t.left(90)
t.forward(50)
#2蓝色叶子
t.color("blue")
#两片叶子
for i in range(2):
for j in range(15):
t.forward(5)
t.right(6)
t.right(90)
#3紫色枝干
t.color("purple")
t.forward(150)
#4红色的花
t.color("red")
# 花
for i in range(6):
for i in range(2):
for j in range(15):
t.forward(5)
t.right(6)
t.right(90)
t.left(60)
解决方案: 把重复的部分放入函数调用
import turtle
#绘制叶子的函数
def draw_leaf():
for i in range(2):
for j in range(15):
t.forward(5)
t.right(6)
t.right(90)
t = turtle.Pen()
t.pensize(5)
#1绿色枝干
t.color("green")
t.left(90)
t.forward(50)
#2蓝色叶子
t.color("blue")
draw_leaf()
#3紫色枝干
t.color("purple")
t.forward(150)
#4红色的花
t.color("red")
for i in range(6):
draw_leaf()
t.left(60)
已经写过类似功能的函数,但是需要修改后才能使用,但是函数没有办法修改
def init_board():
# initialize 3x3 board
board = []
for i in range(3):
row = []
for j in range(3):
row.append(0)
board.append(row)
# 一段很长的代码都需要init_board_3....但是用户说需要5x5的盘,于是
board = []
for i in range(5):
row = []
for j in range(5):
row.append(0)
board.append(row)
...
解决方案:
def init_board(board_size):
# initialize 3x3 board
board = []
for i in range(board_size):
row = []
for j in range(board_size):
row.append(0)
board.append(row)
init_board(3)
...
init_board(5)
...
例子
已经写过类似功能的函数,但是只需要其中一部分
# 2048.py
import random
def init_board():
# 新建空白棋盘,随机两个位置放入2或4
board = []
for i in range(4):
row = []
for j in range(4):
row.append(0)
board.append(row)
for y in range(2):
while True:
choice=[2,4]
i=random.randint(0,3)
j=random.randint(0,3)
if board[i][j]!=0:
pass
else:
board[i][j]=random.choice(choice)
break
return board
...
# 现在更改了游戏规则,需要增加全部都是2的棋盘
board = []
for i in range(4):
row = []
for j in range(4):
row.append(2)
board.append(row)
...
重构后:
# 2048.py
import random
def init_board(size,init_num = 0):
# 新建空白棋盘,随机两个位置放入2或4
board = []
for i in range(size):
row = []
for j in range(size):
row.append(init_num)
board.append(row)
def init_random_positions(board):
for y in range(2):
while True:
choice=[2,4]
i=random.randint(0,len(board)-1)
j=random.randint(0,len(board)-1)
if board[i][j]!=0:
pass
else:
board[i][j]=random.choice(choice)
break
return board
...
# 现在更改了游戏规则,需要增加全部都是2的棋盘
board = init_board(4,init_num = 2):
...
解决方案
- 将重复的代码放入函数
- 设置重要的参数
- 拆分函数,让函数能适应各种情况
2. 长函数
- 因为写代码比读代码容易,所以在方法变成一个丑陋的、巨大的庞然大物之前是感觉不到的
- “代码只有两行,为它创建一个完整的函数是没有意义的……”这意味着又加了一行又一行,产生了一堆乱七八糟的代码
例子
t = turtle.Pen()
t.pensize(5)
t.left(90)
def draw_leaf():
for i in range(2):
for j in range(15):
t.forward(5)
t.right(6)
t.right(90)
def draw_flower():
#1绿色枝干
t.color("green")
t.forward(50)
#2蓝色叶子
t.color("blue")
draw_leaf()
#3紫色枝干
t.color("purple")
t.forward(150)
#4红色的花
t.color("red")
for i in range(6):
draw_leaf()
解决方案
- 拆分函数
- 尝试给代码加上注释
t = turtle.Pen()
t.pensize(5)
t.left(90)
def draw_leaf(color):
t.color(color)
for i in range(2):
for j in range(15):
t.forward(5)
t.right(6)
t.right(90)
def draw_branch(color,length):
# 枝干
t.color(color)
t.forward(length)
def draw_petal(color,petal_num):
# 花瓣
for i in range(petal_num):
draw_leaf()
t.left(360/petal_num)
def draw_flower():
#1绿色枝干
draw_branch("green", 50)
#2蓝色叶子
draw_leaf("blue")
#3紫色枝干
draw_branch("purple",150)
#4红色的花
draw_petal("red", 6)
3. Divergent Change 发散式变化
- 当添加一个新功能的时候,需要修改很多地方
- 添加新的功能会很困难
- 通常发生在添加新功能的时候
例子
def taste1():
something()
...
def tasteN():
something()
def smell1():
something()
...
def smellN():
something()
def taste(fruit_name):
if fruit_name == "apple":
taste1()
elif fruit_name == "pear":
taste2()
elif fruit_name == "watermelon":
taste3()
elif ...
tasteN()
def smell(fruit_name):
if fruit_name == "apple":
smell1()
elif fruit_name == "pear":
smell2()
elif fruit_name == "watermelon":
smell3()
elif ...
smellN()
fruit_name = "apple"
如果fruit_name
换成其他水果,就需要对这些函数都添加一遍
解决方案
避免使用这类一连串 if 语句
def taste1():
something()
def smell1():
something()
def taste(fruit):
fruit['taste']()
def smell(fruit):
fruit['smell']()
fruit = {
"name": "apple",
"taste": taste1,
"smell": smell1,
}
4. Shotgun Surgery 霰弹枪手术
- 当修改一个函数的时候,其他函数都要跟着修正
- 通常Debug的时候
- 危害
- 增加开发人员的工作量
- 代码的潜在疏忽
例子
# 原要求:每个函数你都需要检测 money 不能小于0且不能超过1000
def save_money(money):
if money < 0 or money > 1000:
print("money can only in range between 0 and 1000")
else:
_here_save_money()
def transfer_money(money,target):
if money < 0 or money > 1000:
print("money can only in range between 0 and 1000")
else:
_transfer_money()
....
# 新要求:money 不能小于0且不能超过500 而且money不等于100
# 这样就需要重新修改这些函数
解决方案
拆分函数,把公共部分拿出来
# 要求:money 不能小于0且不能超过500 而且money不等于100,
rule = {
"lower_bound":0,
"upper_bound":1000,
"sensitive_values":100,
}
def print_rule_invalid_msg(rule):
print("cannot in range",rule['lower_bound'], "and", ...)
def check_money_validation(money, rule = rule):
if (money < rule['lower_bound'] or
money < rule['upper_bound'] or
money == rule['sensitive_values']):
# start
print_rule_invalid_msg(rule)
return False
else:
return True
def save_money(money):
if check_money_validation(money):
_here_save_money()
def transfer_money(money,target):
if check_money_validation(money)
_transfer_money()
如果可能需要应对不同的情况 仿Strategy Pattern
def check_upper_lower_bound(moneey,lower,upper):
if money< lower or money > upper:
return False
else:
return True
def check_sensitive_amounts(moneey,amounts):
if money in amounts:
return False
else:
return True
def print_rule_invalid_msg(rule):
"""
Some code here
"""
print(...)
"""
例:假如Money 不能在区间100-150,300-350之内,且不能等于120 和170
"""
rules = [
lambda money: check_upper_lower_bound(money,100,150),
lambda money: check_upper_lower_bound(money,300,350),
lambda money: check_sensitive_amounts(money,[120,170]),
]
def check_money_validation(money,rules):
for rule in rules:
if not rule():
return False
return True
def save_money(money):
"""
init rules here
"""
if check_money_validation(money,rules):
_here_save_money()
def transfer_money(money,target):
"""
init rules here
"""
if check_money_validation(money,rules)
_transfer_money()
5. Long Parameter List 长参数群
- 函数的参数过多
- 在将几种类型的算法合并到一个方法中之后,可能会出现一长串参数
- 参数传递会很困难,容易出错
- 不容易Debug
例子:
def draw_flower(
upper_left_point,
lower_right_point,
lower_branch_color,
lower_branch_length,
leaf_color,
leaf_length,
upper_branch_color,
upper_branch_length,
petal_color,
petal_num,
pen_size,
):
t.pensize(pen_size)
#----------------
# 画笔归位代码
#----------------
#1绿色枝干
draw_branch(lower_branch_color, lower_branch_length)
#2蓝色叶子
draw_leaf(leaf_color,leaf_length)
#3紫色枝干
draw_branch(upper_branch_color,upper_branch_length)
#4红色的花
draw_petal(petal_color, petal_num)
def draw_flowers(
num_flowers,
upper_left_point,
lower_right_point,
lower_branch_color,
lower_branch_length,
leaf_color,
leaf_length,
upper_branch_color,
upper_branch_length,
petal_color,
petal_num,
pen_size):
for i in range(num_flowers)
draw_flower(
upper_left_point,
lower_right_point,
lower_branch_color,
lower_branch_length,
leaf_color,
leaf_length,
upper_branch_color,
upper_branch_length,
petal_color,
petal_num,
pen_size
)
# some code to adjust direction
# some code to do with flowers
原来设想upper_left_point和lower_right_point作为花的bounding box的位置,但是发现代码与愿相违
如果需要将upper_left_point, lower_right_point 换成起始点x,y怎么办
解决方案
在python 中可以使用 *args或**kwarg的方法,或者设定默认值
flower_config = {
"num_flowers": 6,
"upper_left_point": (0,0),
"lower_right_point":(20,20),
"lower_branch_color":"green",
"lower_branch_length": 50,
"leaf_color":"yellow",
"leaf_length":10,
"upper_branch_color": "purple",
"upper_branch_length": 150,
"petal_color": "purple",
"petal_num":6 ,
"pen_size":5
}
def back_to_position(x,y):
somecode()
def draw_flower(**configs):
t.pensize(configs.pen_size)
back_to_position()
#1绿色枝干
draw_branch(configs.lower_branch_color, configs.lower_branch_length)
#2蓝色叶子
draw_leaf(configs.leaf_color,configs.leaf_length)
#3紫色枝干
draw_branch(configs.upper_branch_color,configs.upper_branch_length)
#4红色的花
draw_petal(configs.petal_color, configs.petal_num)
def draw_flowers(num_flowers,**flower_configs):
for i in range(num_flowers):
draw_flower(follow_configs)
draw_flowers(6, flower_configs)
6. Function Chains 信息链
- A函数调用B函数,B函数调用C函数,…
- 如果一个比较深的函数出现问题,其他都会影响
- Debug 困难
例子
def cal_common_factors(a,b):
#------------
# Some code to calculate common factors
#------------
return common_factors
def cal_common_factors_3(a,b,c)
common_factors = []
for common_factor in cal_common_factors(a,b):
common_factors.append(common_factors(c,common_factor))
return common_factors
def cal_common_factors_4(a,b,c,d):
common_factors = []
for common_factor in cal_common_factors_3(a,b,c)
common_factors.append(common_factors(d,common_factor))
return common_factors
解决方案
def cal_common_factors_2(a,b):
#------------
# Some code to calculate common factors
#------------
return common_factors
def cal_common_factors(*args):
#------------
# Some code to calculate common factors using cal_common_factors_2
#------------
dosomething()