为 Nokia N810 开发感知 GPS 的应用程序,第 2 部分: 考虑选择

好的软件需要好的计划

了解代码设计、库选择、单元测试和用户界面选择这些对您最有意义的内容。

Paul Ferrill, CTO, ATAC

Paul Ferrill 已经为计算机出版物撰写文章超过 20 年了。他最初为 PC Magazine 撰写关于 LANtastic 和 Novell Netware 的早期版本等产品的评论。Paul 拥有 BSEE 和 MSEE 学位,曾经为许多计算机平台和体系结构编写过软件。



2009 年 2 月 17 日

这个 三部分系列第 1 部分 提供了为 Nokia N810 Internet Tablet 构建应用程序的基础知识,包括选择目标语言 (Python)、选择和配置开发环境 (Eclipse 和 Pluthon) 以及构建一个简单的示例应用程序来确认环境能够正常工作。本部分讨论可供选择的辅助库、编写单元测试和用户界面选择。

使用 Python 构建应用程序就像是用各种可替换的部件组装自行车。尽管一些选择是完全独立的,但是其他选择必须基于特定的配置做出。例如,在 26 英寸的自行车架上不能安装 29 英寸的轮胎。为 N810 构建应用程序选择要使用的 Python 库需要采用相似的方式。

在开发任何应用程序时,首先应该确定一组基本需求。对于此项目,我选择构建一个工具来跟踪全球定位系统 (GPS) 路标点(waypoint)和计算到前一个或后一个路标点的距离。与许多 “用户定义的” 应用程序一样,这些需求来自越野旅行的实际经验,在越野旅行中常常需要装载地图上的多个点,从而判断已经行进的距离和到目的地的剩余距离。这种工具可以作为简单的 GPS 记录器或徒步旅行工具。

代码设计

Python 本质上是一种面向对象语言,因为 Python 中的所有东西都是对象。这种语言的语法和结构使开发人员很容易开发简单且容易理解的函数。在设计应用程序时,首先确定一系列需求;然后使用某种功能分解方法决定需要编写哪些代码来满足这些需求。

对于这个应用程序,一项顶层需求是能够输入和保存路标点信息。为了满足此需求,必须有一些用户界面元素,比如一个文本框、Save 按钮以及保存信息的方法,这需要使用某种数据库。发现需要一个数据库,就引出了其他代码功能,比如在首次运行应用程序时进行数据库初始化,然后在数据库中保存和获取信息。

编写创建空数据库的函数是演示小型单一功能例程的好例子。对于此项目,这段代码与清单 1 相似。

清单 1. 创建空数据库的 Python 例程
def create_db():
	"""Creates an empty database."""
    
	query('''CREATE TABLE 'places' (
	id INTEGER PRIMARY KEY AUTOINCREMENT,
	lat INTEGER,
	lon INTEGER,
	comment TEXT
);''')

可以看到嵌入的 SQL 语句 CREATE TABLE,数据库表名为 places,它有四个字段 idlatloncomment。这种代码很容易阅读和测试。

另一个良好的代码设计实践是对相似的函数进行分组并把它们放在单一文件中。把所有数据库代码放在一个名为 dbroutines.py 的文件中,这样就更容易管理和查找它们。这还可以为代码存储库提供一个良好、稳定且符合逻辑的结构。

决定如何创建用户界面是另一个必须提前做出的设计决策。对于基于 PyGTK 的应用程序,常常使用一种基于代码的方式构建用户界面:这个过程基本上是通过 Python 代码创建所有用户界面特性。例如,清单 2 中的代码创建典型的应用程序中常见的菜单项列表,然后完全在代码中创建其他用户界面元素。

清单 2. 构建基本 UI 的 Python 例程
def init_ui(id='main'):
    """Build the UI."""
 
    global APP
    APP = ui.App('GPS Logger', id=id,
                 
                 menu=(
                       ui.Menu.Item(label='New', callback=on_new),
                       ui.Menu.Item(label='Open', callback=on_open),
                       ui.Menu.Item(label='Save', callback=on_save),
                       ui.Menu.Item(label='Save As', callback=on_save_as),
                       ),
                 
                 top=(
                      ui.Group('dev_stat', horizontal=True, border=None, 
                               children=(ui.Label(label='Status:'),
                                         ui.Label('status', 'Ready'),
                                         )
                               ),
                      ui.Group('controls', horizontal=True, border=None,
                               children=(ui.Button('start_btn',
                                                   label='Start',
                                                   callback=on_start),
                                         ui.Button('stop_btn',
                                                   label='Stop',
                                                   callback=on_stop))
                               ),
                      ),
                 
                 center=ui.Table('log', 'GPS Log',
                                 headers=('Time', 'Latitude',
                                          'Longitude',
                                          'Altitude (meters)'),
                                 types=(str, str, str, str)
                                 )
                 )

库选择

在 Google 上搜索一下,就会找到许多 Python 库。实际上,必须进行更有针对性的搜索,否则会得到过多的结果。对于此项目,我需要找到几个非常特殊的库,从而简化编程任务。首先,需要一个能够与 N810 中的 GPS 硬件进行通信的库。然后是能够帮助根据 GPS 坐标计算距离的库。最后需要一个 UI 库,从而简化信息的输入和显示。

许多 Python 库仅仅是原来用其他语言(比如 C)编写的代码的包装器。我为此项目找到的这种库之一包装了 Maemo version 4.0 liblocation API(更多信息参见 参考资料 中的链接)。我最初是在 internet tablet talk 网站 上找到了关于这个库的条目,作者在这个网站上采用 GNU Lesser General Public License (LGPL) version 3 开放源码许可协议发布了示例代码。代码的链接和更多信息参见 参考资料

基本的 Python 发行版提供许多库,其中包含马上可以使用的例程和函数。另外,以前单独发布的一些库也已经合并在主 Python 项目中了。其中之一是用于访问 SQLite 数据库的 SQLite 库。这个库提供了程序可能需要的所有数据库功能,使用起来非常简便。Python 的最新版本 (2.6.1) 附带 SQLite3,此项目也使用这个库。

为了计算两个 GPS 坐标之间的距离,我找到了 geopy;这是一个经过多年开发的库并附带大量示例(参见 参考资料 中的链接)。它还提供与许多地理编码器工具的接口,包括 Google、Yahoo! 和 GeoNames,这有助于把地址转换为经纬度坐标,但是需要有有效的 Internet 连接,才能进行查询。对于这个应用程序,它提供一种健壮的距离计算库和使用不同方法的选项。


测试,测试

构建生产就绪应用程序需要做大量测试。对于软件开发周期中的测试问题,最流行的解决方式之一涉及单元测试 的概念。在编写代码时,就要考虑到以可控制的方式作为单独的单元测试每个函数。向需要输入数据的函数提供值,就应该产生已知的结果。

最新的 Python 发行版包含 PyUnit,它提供一种结构化的单元测试构建方法。PyUnit 基于 Java™ JUnit 技术,大体上采用相同的方式(参见 参考资料 中 JUnit 信息的链接)。为了测试访问硬件的代码,必须编写一段按照与硬件相似的方式做出响应的代码。按照单元测试的术语,这称为模拟对象(mock object),更多信息参见 参考资料 中的链接。我为此项目创建了两个模拟对象,分别模拟 GPS 设备和 UI。清单 3 给出模拟设备对象的代码。

清单 3. 模拟设备单元测试代码
class MockDevice:
    """Partially simulates gps argument given to the on_changed callback."""
 
    @staticmethod
    def struct():
        class value:
            class fix:
                latitude = 0.0
                longitude = 0.0
                altitude = 0
        
        return value

清单 4 给出 GPS 代码的一个简单测试用例。

清单 4. GPS 测试用例
class TestGPS(unittest.TestCase):
    def setUp(self):
        unittest.TestCase.setUp(self)
        logger.init_gps()
        logger.ui.get_app_by_id = lambda id: MockUI()
        
    def test_changed(self):
        logger.on_changed(MockDevice)

尽管为这么小的应用程序创建单元测试似乎有点儿过分了,但是实际上即使对于最小的项目,使用这种方式也是良好的编程习惯。实际上,与大型项目相比,在小的应用程序项目中养成习惯更容易。


UI 选择

对于 N810,最合适的 UI 框架显然是 Hildon,因为它原来是 Nokia 为 Maemo 操作系统开发的。它已经被 GNOME 项目接管了,Ubuntu Mobile 和 Embedded Editions 也选用了它。在 N810 上默认安装 Hildon,可以用它创建外观和感觉与现有应用程序相似的 UI。

为 N810 这样的小型设备开发 UI,需要在屏幕布局和用户交互方面多做一些考虑。与以前的型号相比,N810 的一个优点是引入了滑盖键盘。这显著增强了文本输入功能。在设计用户输入屏幕时,它为开发人员提供了更大的灵活性,因为不太需要考虑适应手指操作了。

另一个比较?适的 UI 库由 Enlightenment Project 提供(参见 参考资料 中的链接)。Enlightenment(或简称为 e)的最基本形式是用于 Linux® 操作系统的窗口管理器和桌面 shell。这个项目还提供许多用于创建应用程序的基本构造块,以及用于 Maemo 的特殊版本(参见 参考资料 中的链接)。当前有许多基于 Enlightenment Foundation Libraries (EFL) 的 Maemo 应用程序,包括 Carman(它可以把 N810 与兼容 OBD2 的汽车连接起来,显示诊断代码和引擎性能数据)和 Canola multimedia 应用程序。这两个应用程序演示了许多专门针对小屏幕设备的尖端 UI 特性。

使用 EFL 的缺点是,即使是构建简单的应用程序,也需要相当大的工作量。尽管有 Python-EFL 包装器,但是仍然必须生成大量代码和图像。其基本概念与把表示层和代码分隔开的其他框架相似。使用与 GNU Image Manipulation Program (GIMP) 相似的图形化设计工具设计所需的 UI。然后,必须创建一个描述界面的 Edje Data Collections (EDC) 文件。对于这个程序,我认为不需要使用这么复杂的工具,但是 参考资料 中提供了更多信息的链接。

我在 Maemo Garage 网站(参见 参考资料)上找到的另一个库是 easy 库。这个库提供许多辅助函数,可以简化对低层 API 的访问。这个库的一个特色是用于基于 PyGTK 的快速 GUI 开发的框架。UI 部分基于 Eagle(这是 GTK+ 之上的一个抽象层)。它提供构建基本应用程序所需的所有东西。

要构建对用户操作做出响应的代码,就需要编写在操作发生时执行的代码。这些例程通常称为回调函数,因为在操作发生时会反向调用它们。回调函数还可以与计时器等东西连接,当计时器到期时,就会执行特定的代码段。对于这个应用程序,有用于 GPS 功能、在数据库中访问和保存数据以及启动和停止应用程序的回调函数。


结束语

采用良好的软件工程实践构建应用程序是一个过程。它涉及多个通常需要依次完成的步骤。有时候,可能会发现某个设计决策是没有意义的,这时就必须后退几步。最好在软件设计和开发过程的早期发现这类问题。越早发现设计问题,就越容易解决。

本系列的最后一期将把所有东西组合起来,并看看如何实际部署应用程序。它将回答所有开发人员都关心的问题:应用程序什么时候准备好了?除了 bug 报告和修复之外,还要讨论迭代式开发和特性改进。

参考资料

学习

获得产品和技术

  • Rob Brewer 的 liblocation 包装器代码和其他 Maemo 应用程序的相关信息请参见 Rob Brewer's Projects page
  • 了解 Python 语言的地理编码库 geopy
  • Enlightenment Foundation 为 Maemo 操作系统提供一种特殊的库版本 EFL Maemo Edition
  • 使用可直接从 developerWorks 下载的 IBM 试用软件 构建您的下一个 Linux 开发项目。

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Linux
ArticleID=369870
ArticleTitle=为 Nokia N810 开发感知 GPS 的应用程序,第 2 部分: 考虑选择
publish-date=02172009