跳转到内容

RapidSMS 开发者指南/编码规范和文档

来自维基教科书,开放书籍,开放世界

文档对于每一块代码都很重要,但对 RapidSMS 来说更为重要。

由于 RapidSMS 和大多数为其开发的应用程序的开放性,以及其背后的开发重点和社区,因此非常需要重用和改进。

适当的文档是让您的应用程序被社区重用和改进的必要条件

编码规范

[编辑 | 编辑源代码]

RapidSMS 社区遵循 PEP8 编码规范。它是一种如何编写代码的约定,可以确保代码的可读性和易于贡献。

该标准写得很好,请阅读它。以下是一些重点

  • 行不应超过 79 个字符
  • 文件末尾没有新行
  • 运算符前后各有一个空格
  • 类或函数之前有 2 行间隔

此外,除了 PEP8 标准,RapidSMS 还要求每个文件在 shebang 之后包含格式化和编码注释。因此,您的文件应始终以以下内容开头

#!/usr/bin/env python
# vim: ai ts=4 sts=4 et sw=4 coding=utf-8

实际符合 PEP8 代码的示例

def handle(self, message):

    if not re.match(r'^ping( +|$)', message.text.lower()):
        return False

    identifier_match = re.match(r'^ping (?P<identifier>.+).*$', \
                                message.text.lower(), re.U)
    if not identifier_match:
        identifier = False
    else:
        identifier = identifier_match.group('identifier')

    if self.disallow:
        return False

    if self.allow or \
        (self.allowed and self.allowed.count(message.peer) > 0) or \
        (self.func and self.func(message)):

        now = datetime.now()
        if identifier:
            message.respond(_(u"%(pingID)s on %(date)s") % \
                            {'pingID': identifier, \
                            'date': format_datetime(now, \
                            locale=self.locale)})
        else:
            message.respond(_(u"pong on %(date)s") % \
                            {'date': format_datetime(now, \
                            locale=self.locale)})
        return True

常规注释在 RapidSMS 应用程序中非常有用

  • 帮助初学者从示例中学习
  • 允许其他开发人员用英语阅读您的代码,而不是代码
  • 将来当您不知道为什么编写了那行代码时,它将帮助您自己。
  • 通过迫使您简洁地描述您编写的内容来帮助您构建一致的程序;从而发现错误。

上面的例子实际上有一些注释

def handle(self, message):

    # We only want to answer ping alone, or ping followed by a space
    # and other characters
    if not re.match(r'^ping( +|$)', message.text.lower()):
        return False

    identifier_match = re.match(r'^ping (?P<identifier>.+).*$', \
                                message.text.lower(), re.U)
    if not identifier_match:
        identifier = False
    else:
        identifier = identifier_match.group('identifier')

    # deny has higher priority
    if self.disallow:
        return False

    # allow or number in auth= or function returned True
    if self.allow or \
        (self.allowed and self.allowed.count(message.peer) > 0) or \
        (self.func and self.func(message)):

        now = datetime.now()
        if identifier:
            message.respond(_(u"%(pingID)s on %(date)s") % \
                            {'pingID': identifier, \
                            'date': format_datetime(now, \
                            locale=self.locale)})
        else:
            message.respond(_(u"pong on %(date)s") % \
                            {'date': format_datetime(now, \
                            locale=self.locale)})
        return True

文档字符串

[编辑 | 编辑源代码]

内联文档是 Python 的一项功能,它允许您在类、函数和模块的代码中编写一些注释。然后,Python 会自动解析这些注释并将它们格式化为漂亮的文档。

示例

def handle(self, message):
    ''' check authorization and respond

    if auth contained deny string => return
    if auth contained allow string => answer
    if auth contained number and number is asking => reply
    if auth_func contained function and it returned True => reply
    else return'''

    # We only want to answer ping alone, or ping followed by a space
    # and other characters
    if not re.match(r'^ping( +|$)', message.text.lower()):
        return False

我们在方法开头添加了一个多行注释(用三引号)。Python 理解在模块函数方法开头的所有多行注释都是文档字符串。

该文档字符串可以只有一行长(尽管它仍然需要使用三引号)。

在上面的示例中,我们首先添加了简短的描述(这是一个约定),然后在换行后添加了更多详细的信息。

要访问文档,只需启动 Python shell 并对目标对象调用help()

./rapidsms shell
>> from apps.ping import app
>> help(app.App.handle)
Help on method handle in module apps.ping.app:

handle(self, message) unbound apps.ping.app.App method
    check authorization and respond
    
    if auth contained deny string => return
    if auth contained allow string => answer
    if auth contained number and number is asking => reply
    if auth_func contained function and it returned True => reply
    else return

这意味着任何开发人员现在都可以从 shell 中访问格式良好的文档。

它也由外部工具用于生成独立的 HTML 或其他格式的文档。

为了让您的应用程序可以重用,社区要求使用文档字符串。确保您为所有模块(文件)、类、函数和方法添加文档字符串。

完整示例

#!/usr/bin/env python
# vim: ai ts=4 sts=4 et sw=4 coding=utf-8
# maintainer: rgaudin

''' Reply to `ping` messages to confirm system is up and running. '''

import re
from datetime import datetime

import rapidsms
from django.utils.translation import ugettext_lazy as _
from babel.dates import format_datetime
from bonjour.utils import *


def import_function(func):
    ''' import a function from full python path string

    returns function.'''Before
    if '.' not in func:
        f = eval(func)
    else:
        s = func.rsplit(".", 1)
        x = __import__(s[0], fromlist=[s[0]])
        f = getattr(x, s[1])
    return f


def parse_numbers(sauth):
    ''' transform a string of comma separated cell numbers into a list

    return array. '''
    nums = sauth.replace(" ", "").split(",")
    return [num for num in nums if num != ""]


class App (rapidsms.app.App):

    ''' Reply to `ping` messages to confirm system is up and running.

    One can specify a number or authentication function to
    limit users who can ping the system. '''

    def configure(self, auth_func=None, auth=None):
        ''' set up authentication mechanism
        configured from [ping] in rapidsms.ini '''

        # store locale
        self.locale = Bonjour.locale()

        # add custom function
        try:
            self.func = import_function(auth_func)
        except:
            self.func = None

        # add defined numbers to a list
        try:
            self.allowed = parse_numbers(auth)
        except:
            self.allowed = []

        # allow everybody trigger
        self.allow = auth in ('*', 'all', 'true', 'True')

        # deny everybody trigger
        self.disallow = auth in ('none', 'false', 'False')

    def handle(self, message):
        ''' check authorization and respond

        if auth contained deny string => return
        if auth contained allow string => answer
        if auth contained number and number is asking => reply
        if auth_func contained function and it returned True => reply
        else return'''

        # We only want to answer ping alone, or ping followed by a space
        # and other characters
        if not re.match(r'^ping( +|$)', message.text.lower()):
            return False

        identifier_match = re.match(r'^ping (?P<identifier>.+).*$', \
                                    message.text.lower(), re.U)
        if not identifier_match:
            identifier = False
        else:
            identifier = identifier_match.group('identifier')

        # deny has higher priority
        if self.disallow:
            return False

        # allow or number in auth= or function returned True
        if self.allow or \
            (self.allowed and message.peer in self.allowed) or \
            (self.func and self.func(message)):

            now = datetime.now()
            if identifier:
                message.respond(_(u"%(pingID)s on %(date)s") % \
                                {'pingID': identifier, \
                                'date': format_datetime(now, \
                                locale=self.locale)})
            else:
                message.respond(_(u"pong on %(date)s") % \
                                {'date': format_datetime(now, \
                                locale=self.locale)})
            return True

即使您的代码编写良好并正确注释,也不希望有人花几个小时查看源文件只是为了检查他正在寻找的功能是否存在。

这就是为什么您必须至少创建一个文件(按照约定,在项目的根目录中命名为README)来描述您的应用程序。

它应该包含

  • 应用程序名称
  • 您的姓名和联系方式
  • 对其功能的描述
  • 它有哪些依赖项
  • 如何安装/使用它。

如果您的应用程序很复杂,请创建一个docs/文件夹并将任何进一步的文档添加到其中。

重要说明

[编辑 | 编辑源代码]

非常重要的是,您在编写代码时就编写这些注释和文档字符串,因为如果您不这样做,会导致此文档中的错误,而糟糕的文档比没有文档更糟糕。这是您想要养成的一种习惯。

国际化 · 自定义管理员界面

华夏公益教科书