本文是 The Best of the Best Practices (BOBP) Guide for Python 的中文翻译,并加入了一些我的说明和理解。

总体原则

价值观

「为别人开发你也想要使用的工具。」 - Kenneth Reitz (requests 等知名库作者)

你自己都不想用的东西做出来有什么意义呢?

「简洁总是胜过可用。」 - Pieter Hintjens (ZeroMQ)

我对函数式编程的看法一直是「看场景」, 甚至于我经常会对比性能,义无反顾的使用性能更好、代码简洁的那种。

「满足 90% 的使用场景。忽略那些说不的人。」 - Kenneth Reitz

程序员都有完美主义情怀,但是其实往往我们是在偏激的看事情,做自认为很酷很有用的功能,而用户其实并不买账。

「优美胜过丑陋。」 - PEP 20

「为开源(甚至是闭源项目)而开发。」

一般开发准则

「明确胜过含蓄。」- PEP 20

不要留坑,我经常看到一些复杂的代码,这些代码的作者写的时候明显知道自己在做什么,但是别人很难维护和看懂. 所以我对自己的职业的基本要求就是:那天我离职了,后来接手的人不会经常骂我

「可读性应当被重视」- PEP 20

「任何人都可以解决任何问题」- 可汗学院开发文档

「一旦发现破窗(设计错误,决策失误或编码质量低),马上修补。

我们改 bug 有个原则 - 测试要覆盖到出 bug 的地方。有问题及时解决

「现在做也要胜过不去做。」- PEP 20

明日复明日,明日何其多。我们在代码 review 的时候,问题需要在提出的时候就去改,永远不会说下一次再说,因为下一次大多时候是没有下一次了

「测试要彻底。撰写新功能文档。」 「相对于人力驱动型开发,测试驱动型开发更重要。」 「这些准则可能,应该是很可能在未来会改变。」

特殊准则

风格

感觉合理的话,就遵循 PEP 8

命名

  • 变量、函数、方法、包、模块小写,并使用下划线分隔单词(lower_case_with_underscores)
  • 类、异常首字母大写(CapWords)
  • 受保护的方法和内部函数单下划线开头(_single_leading_underscore (self, ...))
  • 私有的方法双下划线开头(__double_leading_underscore (self, ...))
  • 常量字母全部大写,单词间用下划线分隔(ALL_CAPS_WITH_UNDERSCORES)
一般命名准则

尽量不要使用只有一个字母的变量名(例如,l,I,O 等)。

例外:在很简短的代码块中,如果变量名的意思可以从上下文明显地看出来。比如下面的例子:

for e in elements:
    e.mutate()

避免重复变量名,正确的做法:

import audio

core = audio.Core()
controller = audio.Controller()

错误的做法:

import audio

core = audio.AudioCore()
controller = audio.AudioController()

「反向标记」更好,正确的做法:

elements = ...
elements_active = ...
elements_defunct = ...

错误的做法:

elements = ...
active_elements = ...
defunct_elements ...

避免使用 getter 和 setter 方法,正确的做法

person.age = 42

错误的做法:

person.set_age(42)

缩进

用 4 个空格符 —— 永远别用 Tab 制表符。

模块引用

引用整个模块,而不是模块中的单个标识符。举个例子,假设一个 cantee 模块下面,有一个 canteen/sessions.py 文件,

正确的做法:

import canteen
import canteen.sessions
from canteen import sessions

错误的做法:

from canteen import get_user  # Symbol from canteen/__init__.py
from canteen.sessions import get_session  # Symbol from canteen/sessions.py

例外:如果第三方代码的文档中明确说明要单个引用。

理由:避免循环引用。 看这里

把代码引用部分放在文件的顶部,按下面的顺序分成三个部分,每个部分之间空一行。

  1. 系统引用
  2. 第三方引用
  3. 本地引用

理由:明确显示每个模块的引用来源。

文档

遵循 PEP 257 提出的文档字符串准则。reStructuredText (reST) 和 Sphinx 有助于确保文档符合标准。

对于功能明显的函数,撰写一行文档字符串:

"""Return the pathname of ``foo``."""

多行文档字符串应包括:

  • 一行摘要 * 合适的话,请描述使用场景
  • 参数
  • 返回数据类型和语义信息,除非返回 None
"""Train a model to classify Foos and Bars.

Usage::

    >>> import klassify
    >>> data = [("green", "foo"), ("orange", "bar")]
    >>> classifier = klassify.train(data)

:param train_data: A list of tuples of the form ``(color, label)``.
:rtype: A :class:`Classifier <Classifier>`
"""

注意:

  • 使用主动词(Return),而不是描述性的单词(Returns)。
  • 在类的文档字符串中为__init__方法撰写文档。
class Person(object):
    """A simple representation of a human being.

    :param name: A string, the person's name.
    :param age: An int, the person's age.
    """
    def __init__(self, name, age):
        self.name = name
        self.age = age
关于注释

尽量少用。与其写很多注释,不如提高代码可读性。通常情况下,短小的方法比注释更有效。

错误的做法:

# If the sign is a stop sign
if sign.color == 'red' and sign.sides == 8:
    stop()

正确的做法:

def is_stop_sign(sign):
    return sign.color == 'red' and sign.sides == 8

if is_stop_sign(sign):
    stop()

但是的确要写注释时,请牢记:「遵循斯托克与怀特所写的《风格的要素》。」 —— PEP 8

每行的长度

不要过分在意。80 到 100 个字符都是没问题的。

使用括号延续当前行。

wiki = (
    "The Colt Python is a .357 Magnum caliber revolver formerly manufactured "
    "by Colt's Manufacturing Company of Hartford, Connecticut. It is sometimes "
    'referred to as a "Combat Magnum". It was first introduced in 1955, the '
    "same year as Smith & Wesson's M29 .44 Magnum."
)

测试

尽量争取测试全部代码,但也不必执着于覆盖率。

一般测试准则

  • 使用较长的、描述性的名称。通常情况下,这能避免在测试方法中再写文档。
  • 测试之间应该是孤立的。不要与真实地数据库或网络进行交互。使用单独的测试数据库,测试完即可销毁,或者是使用模拟对象。
  • 使用 工厂模式 ,而不是 fixture。
  • 别让不完整的测试通过,否则你就有可能忘记。你应该加上一些占位语句,比如 assert False, "TODO: finish me"

单元测试

  • 每次聚焦一个很小的功能点。
  • 运行速度要快,但是速度慢总比不测试好。
  • 通常,每一个类或模型都应该有一个测试用例类。
import unittest
import factories

class PersonTest(unittest.TestCase):

    def setUp(self):
        self.person = factories.PersonFactory()

    def test_has_age_in_dog_years(self):
        self.assertEqual(self.person.dog_years, self.person.age / 7)

功能测试

功能测试是更高层次的测试,更接近最终用户如何与应用交互这一层面。通常用在网络应用与图形应用测试。

  • 按照场景撰写测试。测试用例的测试方法命名应该看上去像场景描述。
  • 在编写代码之前,通过注释说明具体场景信息。
import unittest

class TestAUser(unittest.TestCase):

    def test_can_write_a_blog_post(self):
        # Goes to the her dashboard
        ...
        # Clicks "New Post"
        ...
        # Fills out the post form
        ...
        # Clicks "Submit"
        ...
        # Can see the new post
        ...

请注意,测试用例的类名称和测试方法的名称放在一起,就像是「测试一名用户能否发布博文」。

引用