# 十九、自动获取用户故障报表 我曾经受雇于在美国一间最大的互联网服务供货商撰写客户端应用程序,所开发的产品拥有 数以百万的用户,即使一个最罕见的错误都可能影响数百甚至数千的用户。虽然如此,每次当 我们决定按下按钮以释出最新版本的程序时我还是充满信心。我还记得我曾经告诉我爸「这 次的试用版本看起来棒极了。昨天我们在北美洲只收到十二个错误报告。」 十二个,嗯?还是十三个? 不,是十二个。我们应用了一种日渐普及、由客户端自动报告错误的技术。所得的报告都记 录到错误追踪数据库内汇总成摘要,以便开发人员寻找并修正只在客户端发生的错误。这类 错误往往难以在我们的测试实验室内测得到,因为事实上我们无法全部设置或模拟成客户所 有超乎寻常的计算机组合。 错误侦测及报告功能正日渐普及。现在互联网浏览器和窗口系统都己经内建这类功能,而且 越来越多客户希望他们的桌面应用程序懂得自行回报所遇到的错误。 能够知道世界上每一个角落找到的每个错误,对于建立一份你所开发的产品能在这个疯狂的 世界上安然无恙的信心而言非常重要。尤其对于像开发消费者应用程序领域的我们而言更 为重要。很多时候你不能倚靠客户告诉你他们所遇到的错误,他们可能没有足够的技术背景, 更重要的是,要是没有自动报告错误的机制,他们根本不会有兴趣在忙着干自己重要的工作 时还抽空给你发一份完整而有用的错误报告。 现在我创立了自己的公司,更深深地相信自动收集客户端错误的重要。差不多所有我们在Fog Creek Software编写的程序都有好些方法能透过我们的FogBugz错误追踪数据库回报错误。 我们的产品CityDesk之内的产品,以至FogBugz本身(一个安装在客户端的网页服务器上的 网页应用程序)都内建了这项功能。两者皆可以透过互联网汇报错误。就连我们为内部开发 的应用程序,比如说营运Fog Creek网上商店的电子商务程序,都会用相同的方式通知开发团 队程序运作期间所遇到的错误。 ## 剖释错误 好吧,你的程序当掉了。在几乎每个程序执行环境下,总有些方法能让你由某个地方回复、重 新开始作业。以往我们只会让程序在出问题的的地方乖乖挂掉,现在我们选择弹出一个对话 框。 这么多年来我学到一件事,就是你所问的问题越多,想回答的人就越少。所以现在我们只会问 最少的而又能帮助分析错误的问题。比如说「你正在做什么?」、「你的电邮地址?」我们 强调提供电邮地址是自愿性质,以排解关于个人隐私的疑虑。令人意外的是有些盲目恐惧是 如此深入民心而且根深蒂固。在消费者市场内,许多人都己经被十一点钟新闻报导教育成永 不要交出他们的电邮地址以避免收到垃圾邮件。结果是,如果你需要人们汇报错误的时候填 写电邮地址,他们就干脆不提交报告。 基本上所有你所需要的数据都可自动取得,例如操作系统的版本,系统拥有的内存大小等等。 ## 数据收集 接下来的问题是,要收集什么样的数据才可以帮助我们的开发人员找到造成错误的地方?我 们很难抗拒把所有抓得到的数据都抓起来的这种诱惑。抓下每个你可以抓到的位信息,抓下 用户系统内所有DLL及COM控件的版本,以至把所有内存内的数据都倒出来制作一个备份(有 些会叫这做核心转储Core Dump)。 然而在做了开发人员几年后、以及在从不知道自己拿着份核心转储数据可以干些什么的情况 下,我终于发现这些都是非必要的数据。以下都是我们实际上会收集的数据: * 产品版本 * 操作系统版本及微软Internet Explore「版本(有相当多的窗口功能都是来自Internet Explore「和它的组件,以致这项数据对于了解包括图象接口应用程序在内 的程序对窗口操作系统上的表现非常重要) * 发生错误的程序文件以及程序代码行数错误消息正文 * 独一无二的错误码 * 来自用户关于他们正在进行中的作业的描述 * 用户的电邮地址 有这些就足够了!这些年来我们发现单是知道发生错误的程序代码行数就己经足够帮助修正 大部份的问题。在少数以这项信息不足以解决问题的个案上,你可以透过直接联络遇上问题 的用户以取得更多的信息帮忙解决问题。收集少量信息的好处在于令回报错误的程序变得非 常快速,进而减少用户对回报问题感到不耐烦的机会。单是检查所有DLL和COM控件的版本就 可以花上好一段时间,如果数据是由调制解调器上传的话就又得花更多的时间,而这些数据是 可以提供帮助修正错误的有用资讯。即使你发现错误只会在程序应用一个微软系统DLL的某 个版本时才会发生,你又可以做些什么?你仍然需要改写程序来避开错误。 ## 致电回家 感谢上天互联网的无处不在,在大部份情况下透过网页传送信息都是把信息送回家的最佳方 法。只要送出一个标准的HTTP需求,你的错误报告几乎可以穿越于客户环境内任何种类的防 火墙。更妙的是,现在几乎每一种编程环境都有内建的链接库支持发送HTTP需求及读取响应。 举例来说,在窗口平台上你就可以在WININET链接库内找到内建的函数式去利用Internet Explore「的网络传输程序代码来传送HTTP需求和读取响应。利用这些函数式最好的是,如果 用户曾经设定他的浏览器透过代理服务器来在防火墙内传取数据,WININET呼叫程序会自动 采用相同的设定操作,而过程中你无付出任何额外的努力更改或设定你的程序就能正常发挥 功用。 我们的FogBugz在收到HTTP需求送来的错误报告后,会传回一个很短的XML文件表示己经收到 错误报告,文件亦同时包含一个展示给用户的讯息。关于这点,稍后我将会谈得更多。 如果你的应用程序是一个网页浏览器应用程序,你可以采用一个更简单的做法:展示一个包 含一张可递交数据到服务器上的表格的网页。你只需要将表格的行动路径指向你的错误追踪 数据库即可。请参考图象2。 在好些种类的应用程序当中,你或许会想将错误报告先写到档案或登录内,等到用户重新启 动应用程序时才送出,而非在错误发生时直接送出。我称呼这种技巧为延迟传送。这种做 法虽然会延后收到报告的时间,但好处是一旦发生的错误严重至令你的应用程序瘫痪到无法 送出错误报告时,你仍然可在稍后取得报告。 现在所有要送交Fog Creek的错误报告都会送抵一个位于我们公众服务服务器上的URL。而我 们的错误追踪数据库则由透过这个URL接收错误报告。事实上这个URL是公众可以接触到错 误追踪数据库的唯一路径。除了接收报告以外的所有功能都己被死锁,客户可以提交错误报 告,但无法读取数据库内的任何数据。 图象3展现的是当一个错误报告抵达我们的数据库时的模样。我们可以设定将收到的报告自 动送给开发团队内的其中一位工作人员,不过最近为免打扰同事,我们选择将报告都送到 被建立的虚拟人”CityDesk New Bug”手上。每次我们想要过滤出收到的报告,只需找出那些 被分派到虚拟同事手中的错误,然后再逐一决定是否需要跟进修正。所有需要修正的错误将 会被派到真实存在的同事手上。 ## 办认重复的错误 应用自动错误报告收集时很重要的一点是,同一个错误可以在许多不同的人手上发生许多次, 而你绝不会希望每次同一个错误被报告时都会在你的错误追踪数据库内制做一个新的错误 记录。我们应付这个情况的方法是以错误信息内的重要数据为错误报告编上独特的标识符串。 字符串的编码方法十分谨慎,分别来自两个用户的同一错误必须获编相同的标识符串。在好 些实验以后,我们发现最好的编法就是在字符串内加入错误码、文件名、函数式名称、错 误行数和产品版本。图象3内展示了标识符串”E「「o「91 (global:lsRoot:0) V1.0.32”。字 符串说明档案global.bas于执行函数式IsRoot位于第0行的程序代码时发生91号错误,而产 品的版本为1.0 build 32。相当偶然地,我们总是将编译版本数目为双数的产品编为内部使 用版本,而编译版本数目为单数的则是会送交到客户手上的版本,这样我就可以一眼知道某 个错误报告到底发生在开发人员或客户身上。 FogBugz往后遇到标识符串相同的当机回报时,会自动附加到这个案例中,并不会重开一个 新案例。这样能让程序员在同一个地方看到所有相同的当机状况。 标识符串的格式设计得花点心思。我们以前会把当机错误讯息整个含在标识符串里。不过很 快就发现错误讯息会被译成不同的语言,于是每种错误都会分散成英德西法及其他几种我 认不出的语言。我们解决的方法是把错误讯息放在当机报告内,但是把不随语言而变的错误 标识符放在报告的标题中,这些重复的回报就少多了。 另外标题也经过特别安排,可以方便搜寻特定问题。由于我们在标题中使用「档名:函数名: 行数」的格式(注意其中的冒号),所以只要搜寻「:函数名:」就很容易找出某函数相关的 问题。我们基于相同的原因在版本号码前加上字母V,这样就能搜寻V1或V1.0或V1.0.32。如 果没有这个V字母,想找版本1时就会找出所有标题里刚好有1字符的错误报告了。 当错误被确认时,我们可以把某个旗标(FogBugz接口里的Scout Will)由「继续回报」改成 「停止回报」,之后就会忽略标识符串相同的当机回报。我们甚至可以设定一串文字讯息(在 FogBugz接口里是针对自动送出的案例中出现的ScoutMsg),可以自动传给往后遇到相同当 机状况的使用者。当我们要使用避开错误的作法(workaround)时也会用这个功能提出建议。 就像是「嘿,下次你要存档前要记得先拍拍自己的头再摸摸自己的肚子!」 重复回报的常见原因之一,就是当在当机处理程序本身。这并不一定是说当机处理的程序有 问题,或许只是因为原先的当机太严重,以致再也没有程序能正常运行。