[EuroPython2021笔记] Python反面模式 -- 用空格的比用tab的收入高

Posted 有数可据

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[EuroPython2021笔记] Python反面模式 -- 用空格的比用tab的收入高相关的知识,希望对你有一定的参考价值。

本文是Euro Python 2021大会演讲Python Anti-Patterns的学习笔记。这篇演讲的作者是Vinicius Gubiani Ferreira。

英文简介

By Vinicius Gubiani Ferreira

Most people heard at least once or focused really hard on studying design patterns. But did you know there are also lots of anti-patterns we should try to avoid?

In this talk I intend to present some of the most known anti-design patterns that we’ll promise never to use … but for some reason or slip end up using it anyway. I will start showing more generic anti-patterns that apply to all languages and software in general, and move on to Python specifics, by using The Little Book of Python Anti-Patterns.

Since there will be Python code, attendees should fell confort with some basic to intermediate Python knowledge. For example: know about classes, constructors, parameters/arguments, etc.

翻译

很多人听说过或者努力学习过设计模式。但是你知道还有我们应该避免的反面模式么?

在这次演讲中,我会展示一些常见的反面模式,并且,我们要保证以后避免使用。我会从各种语言都有的反面模式开始讲起,然后再介绍Python的反面模式。我使用的书是《The Little Book of Python Anti-Patterns》

既然我们讲的是Python,与会者应该有Python的初级或者中级水平。比如,了解类,构造器,参数,等。

作者简介

Vinicius Gubiani Ferreira 是AZION的高级后端开发工程师。AZION是一家severless and 边缘计算公司,致力于让网络更快,更安全。他也是python的葡萄牙语的翻译者。业余爱好有喝啤酒,骑自行车等。

(中国人一定会装逼的说,我喜欢文学,艺术,我知道茴香豆的茴字有100种写法。。。)

通用反面模式

首先,我们要明白什么是模式,模式就是针对反复出现的问题的通用的解决方案。至少出现三次才叫反复出现。模式会被大规模的采用。可以认为是一种内聚方法。模式是可靠,高效的。

而反面模式一开始看起来挺好,直到出现问题。有时候,反面模式不仅不能解决问题,反而会引发更大的问题。

《Anti-Patterns》这本书,介绍了三种反面模式:

  • Development 开发
  • Architechure 架构
  • Project Management 项目管理

Boat Anchor 船锚, 是指哪些已经不需要的废代码。这里的解决方案就是尽快删除它们。

Spaghetti Code 意大利面代码,指那些没有结构和清晰度的代码。这些代码很难维护。解决的方法就是重构。

God Code 上帝代码,看起来有一些结构。但是有限。比如,一个文件500行代码。解决的方法就是拆分。之后就会易于维护了。

Vendor Lock-in 供应商锁定, 代码逻辑对特定供应商的依赖太重。有一天,我们决定不在使用这个供应商了。这时候,我们的麻烦就大了。解决的方法是,抽象出一个抽象层。我们的代码只和抽象层打交道。当供应商换了。我们只需要重写抽象的具体实现。上层建筑无需更改。

Cargo Cult Programming 盲目信仰, 有人从Stackoverflow复制了一段代码,还没看明白,就盲目的放到项目里了,出了问题也不知道怎么回事。解决的方案就是,要明白自己的代码要干什么。要花时间去看懂。

Premature optimization 不成熟的优化, 有人说他是万恶之源(The root of all evil), 97%的情况,我们不需要优化。解决的方法就是先要找出性能的指标和瓶颈。如果我们做不成熟的优化,那么我们的代码会变得没有必要的复杂。

Magic Numbers 魔术数字, 他们看起来完全随机,但是如果我们改了这些数字,就会引发很多奇怪的bug。两个解决方案,要么加上注释。或者使用枚举。

Gold plating 镀金, 开发一些客户不需要的功能。解决的方法就是开会,开会,开会。

(我怀疑你一个程序员,能推翻项目经理的决定么?)

Python 反面模式

没有异常类型

Bad code sample

try:
    do_somthing()
except:
    pass

Good code sample

try:
    do_somthing()
except ValueError:
    logging.exception("Caught error!")
    pass

我们不应该捕获了所有异常,然后假装无事发生。我们应该把异常记录下来,不然我们就不知道发生了什么。我们尤其应该捕获特定的异常,而不是所有异常。

处理文件时不使用内容管理器

Bad code sample

f = open("file.txt", "r")
content = f.read()
1 / 0
f.close()

Good code sample

with open("file.txt", "r") as f:
    content = f.read()
    1 / 0

当我们读写文件的时候,我们可能发生任何错误。如果我们不将数据写入文件(关闭文件),数据可能会丢失,文件也可能会被污染。用了内容管理器,无论是否异常,文件都会被关闭。

(在.py文件中,我一定会使用内容管理器。只有在jupyter notebook中,我才会省略内容管理器。我的习惯是在jupyter notebook探索,当然探索的代码都不是正式代码。等探索跑通了,在写成正式的python文件。)

返回多种类型

Bad code sample

def get_secret_code(password):
    if password != "bicycle":
        return None
    return "42"

Good code sample

def get_secret_code(password):
    if password != "bicycle":
        raise ValueError
    return "42"

如果有多种返回类型,会难为维护。最好只使用一种类型返回,空值或者找不到数据可以抛异常。

在类的外面使用被保护的成员对象

Bad code sample

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

tr = Rectangle(5, 6)
memberprint(
    "Widht::d".format(r._width)
)

Good code sample

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

    @property
    def width(self):
        return self._width

tr = Rectangle(5, 6)
memberprint(
    "Widht::d".format(r.width)
)

在其他语言中,被保护的成员,在外部是无法被访问的。但是python里面,你还是可以访问。不过,这并不代表你就应该去访问。还是应该遵守软件开发的基本守则。

应该实现公共的getter和setter。

使用关键字

Bad code sample

list = [1, 2, 3, 4]
cars = list()

Good code sample

numbers = [1, 2, 3, 4]
cars = list()

更夸张的是,你居然可以赋值给关键字,或者已经使用的名字。这样做可能对整个系统造成不可预料的破坏。

使用tab,甚至tab和空格混合

Bad code sample

# Indentation with tabs

Good code sample

# Indentation with 4 spaces

(PEP 8里面说了,使用4个空格来缩进代码。如果你不遵守,说明你没读PEP 8。我给你个链接,你去读读吧。)

https://pep8.org/

用空格的程序员比用tab的程序员每年多2万美元工资。

这里有一篇stackoverflow的文章,这这么说的。

https://stackoverflow.blog/2017/06/15/developers-use-spaces-make-money-use-tabs/

For…Else

Bad code sample

my_list = [1, 2, 3, 4]
magic_number = 4
found = False

for number in my_list:
    if number == magic_number:
        found = True
        print("Magic number found")
        break
        
if not found:
    print("Magic number not found")

Good code sample

my_list = [1, 2, 3, 4]
magic_number = 4

for number in my_list:
    if number == magic_number:
        print("Magic number found")
        break
else:
    print("Magic number not found")

上面的bad code就是我们通常的写法,在其他语言里,我们都会这样写。不过,python有更好的解决方案,就是在for后面跟一个else,如果for循环执行完毕,没有跳出,则执行else。

(我看到这里有些头大,我经常看欧美的程序员大叔的视频,每一个人说到这里的时候,都不建议使用For…Else。在他之前,我应该遇到3个人专家,反对for…else了。反正我不会用For…else。你用不用,随你。很多时候,尤其在中国,因为项目的需要,老板随便叫两个人来,就让你维护python代码了,他不管你们有没有学过python。写For…Else是为难后面的程序员,所以我不写。当然,也指不定哪天,我“想通”了。这个是争议话题。我觉得,老师在介绍争议话题的时候,应该介绍反面的观点。而不是灌输和一言堂。)

不用get()获取默认值

Bad code sample

dictionary = "message": "Hello!"
data = ""
if "message" in dictionary:
    data = dictionary["message"]
print(data)

Good code sample

dictionary = "message": "Hello!"
data = dictionary.get("message", "")
print(data)

Python的原则是easier to ask forgiveness than permission

而Java的原则是look before you leap。

后面的代码看起来舒服多了。

用通用符引用所有成员

Bad code sample

from math import *

Good code sample

from math import ceil

如果你引用几个库,那么这几个库里面很可能有同名成员,那你就麻烦大了。

使用全局变量

Bad code sample

WIDTH = 0
HEIGHT = 0

def area(w, h):
    global WIDTH
    global HEIGHT
    WIDTH = w
    HEIGHT = h
    return WIDTH * HEIGHT

Good code sample

WIDTH = 0
HEIGHT = 0

class Rectangle(w, h):
    def __init__(self, width, height):
        self.width = width
        self.height = height
        
    def area(self):
	    return self.width * self.height

尽量避免使用全局变量

用一个字符命名变量

Bad code sample

l = [1, 2, 3, 4]

Good code sample

car_ids = [1, 2, 3, 4]

尽量使用有明确含义的名字,而不是模棱两可的名字。

错误的比较方法

Bad code sample

flag = True
if flag == True:
    print("This works!")

Good code sample

flag = True
if flag:
    print("This works!")

if flag is True:
    print("This works!")    

如果要检查一个值是否为True,只需要把这个值直接作为条件即可。比较的时候,有 == 运算符 和 is 运算符,他们的含义并不一样。True 等于 1 ,但是True不是1。所以 True == 1 返回 True,True is 1 返回 False。

用type比较类型

Bad code sample

c = Circle(2)
r = Rectangle(3, 4)
if type(r) is not type(c):
    print("object types do not match")

Good code sample

r = Rectangle(3, 4)
if isinstance(r, types.ListType):
    print("object r is a list")

isinstance会检查一个变量的类型以及其所有的父类。而直接比较type,比较的是当前的类。

(这里我没看出来bad code哪里不好了,如果它的目的是比较两个形状是不是同样的形状,那么我认为它可以这样写。)

函数返回值没有使用named tuple

Bad code sample

def get_name():
    return "Rechard", "Jones"

name = get_name()
# no idea which is which
print(name[0], name[1])

Good code sample

from collections import namedtuple

def get_name():
    name = namedtupe(
    	"name":["first", "last"]
    )
    return name("Richard", "Jones")

name = get_name()
print(name.first, name.last)

不区分单复数

作者并没有提到这个,但是我发现这点是中国程序员常犯的错误。

Bad code sample

car_id = [1, 2, 3, 4]

sheep = ['喜羊羊''美羊羊']
for isheep in sheep:
    print(f"咩,我是isheep")

Good code sample

car_ids = [1, 2, 3, 4]

sheep_list = ['喜羊羊''美羊羊']
for sheep in sheep_list:
    print(f"咩,我是sheep")

上面的错误代码中,list不区分单复数。以至于到了要遍历list的时候,只能在sheep前面加i了。我的做法是,尽量区分单复数,当单复数同形的时候,复数就叫xxx_list,单数还叫xxx。

引用

大会页面:

https://ep2021.europython.eu/talks/yXoQCzR-python-anti-patterns/

视频地址(优酷):

https://v.youku.com/v_show/id_XNTgxMTU5NjEwOA==.html

PDF地址:

https://ep2021.europython.eu/media/conference/slides/yXoQCzR-python-anti-patterns.pdf

《Python反面模式小册子》:

https://github.com/quantifiedcode/python-anti-patterns

以上是关于[EuroPython2021笔记] Python反面模式 -- 用空格的比用tab的收入高的主要内容,如果未能解决你的问题,请参考以下文章

[EuroPython 2021笔记] Python 3.10新功能开发者亲述:模式匹配案例实战

[EuroPython 2021笔记] Python 3.10新功能开发者亲述:模式匹配案例实战

[EuroPython 2021笔记] Python 3.10新功能开发者亲述:模式匹配案例实战

[EuroPython2021笔记] Yoichi Takai: 在python 3.10中使用静态类

更快的python -- EuroPython 2016讲座笔记

[EuroPython2021笔记] functools 漫游指南