跳轉到內容

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/資料夾並將任何進一步的文件新增到其中。

重要說明

[編輯 | 編輯原始碼]

非常重要的是,您在編寫程式碼時就編寫這些註釋和文件字串,因為如果您不這樣做,會導致此文件中的錯誤,而糟糕的文件比沒有文件更糟糕。這是您想要養成的一種習慣。

國際化 · 自定義管理員介面

華夏公益教科書