Archive for the ‘Bug Archive’ Category

Do write portable code

Tuesday, September 4th, 2007

最近遇到两个 bug ,都是因为代码写得不具有可移植性而造成的。Javascript 本身就由于浏览器的差异有许多需要注意的地方,写代码的时候一定要万分小心,多多测试。

数组常量值末尾多余的逗号

Javascript 里面用类似 [1,2,3] 的方法来初始化数组是众所周知的。但是有一个地方却比较容易犯错误。特别是对于熟悉 C 语言的人来说,C 语言里面明确说明,以下这种情况里面,多余的逗号是可以接受的:

int array[] = {1,2,};

然而作为一个弱类型的语言,语法相对会比较随意的 javascript ,在这里却有一个陷井。事实上,标准可以允许中间或着头部的位置出现多余的逗号,这样会把多余的 undefined 的对象加入到数组中。不过末尾的逗号却会因为浏览器的不同而产生不同的结果。例如

var arr = [1,2,3,];
alert(arr.length);

在 IE6 下会得到 4 ,而在 Firefox 2 下得到的是 3 。且不论谁对谁错,但这已经是不得不去面对的事实了。这次我就遇到了这个问题,事实上,我是在 rails 的 ERB 模板里面生成一个 javascript 的数组常量。大概像这个样子:

var data = [
    <% for entry in @entries -%>
        <%= format_entry entry %>,
    <% end -%>
    ];

这样产生了末尾多余的逗号,导致了在 Firefox 和 IE 下产生了不同的结果。最后对最后一个元素特殊对待才解决了问题:

var data = [
    <% for entry in @entries[0..-2] -%>
        <%= format_entry entry %>,
    <% end -%>
    <%= format_entry @entries[-1] %>
    ];

时间的格式

我要在 javascript 里面通过字符串解析出时间来。在 Ruby 里面的 Time 类有一个方便的 to_s 方法可以直接把时间转化为字符串,它得到的格式是这样的 Tue Sep 04 13:48:25 +0800 2007 。我便直接使用了这个格式,一直都是好好的,后来拿到 IE 下面去跑的时候,根本跑不出结果来。调试了很久才发现 IE 下面没有能把那个时间解析出来,IE 不认那个 “+0800″ 。最后我用 strftime 格式化成了一种通用的格式把这个问题解决了。

Typical Chinese Programmer

Friday, June 1st, 2007

FatMouse 在 Java 课上介绍了 Typical Chinese Programmer 的概念。我不知道这个名词从何而来,在 Google 上似乎也没有找到有用的信息。不过 FatMouse 说 Typical Chinese Programmer 是这样的一类人,他们想当然地写程序:

  • 比如,他们做出来的界面上有一堆按钮,他们假定用户会先点击“按钮一”,再点击“按钮二”然后是“按钮三”。如果用户不按照这个顺序点击,那么程序就会莫名其妙地 Crash 掉。然而他们也并没有给任何提示给用户以说明点击顺序,或者是在第一个按钮点击之前禁用掉第二、第三个按钮,因为他们认为用户一定不会笨到不按照他们心里想的顺序去点击按钮的。
  • 再比如,他们打开文件从来不判断文件是否打开成功。“怎么会打开失败呢?”他们这样想。他们觉得,每次都检查是否打开成功实在是太麻烦,打开失败的概率实在太小了,要真的打开失败了,就到时候再加上判断语句吧。

于是,他们写出来的程序里面充满了不确定因素。

我最近两天都在调试一个 C++ 程序,后来发现有一次错误就是由于队友犯了 Typical Chinese Programmer 式的错误造成的。

程序是由 Qt 搭建而成的。最开始发现了一个极其棘手的 Bug ,我在我的 Linux 极其上根本无法重现这个 Bug ,但是在队友的 Windows 机器上则很容易重现,要命的是,如果启动调试模式,则很难碰到问题,如果是直接运行,则必定出问题。在 Windows 下 GUI 程序输出到 stdout 、stderr 之类的东西都看不到,又不能启动调试器,实在是很无奈。问题出在 GUI 线程被 freeze 掉了,后来发现其实所有的线程都 wait 了,而调用堆栈上显示的是 Qt 在 emit 一个信号的时候进入锁定状态。

从 Qt 4 开始支持多线程之间传递信号,它使用一个 Queue 的机制来传递多线程之间的信号,而程序在 Linux 版的 Qt 下运行得很好,到 Windows 下就立即死锁,我又把队友们的程序整个浏览了一遍,觉得都没有哪里会出现把 GUI 线程锁掉的情况,就不得不怀疑是 Windows 版的 Qt 在这里可能会有 Bug ,可惜我也没有搜索到有用的 Bug 报告一类的。

最后我手动指定 Qt 即使在多线程之间传送信号也不使用 Queue 的方法,然后改动了一下同步,尽量避免线程之间直接调用信号槽会造成的问题。之后 GUI 线程就不会死掉了。不过程序的功能仍然有问题。一个本该被 wake 起来的线程根本没有醒过来,这个线程一开始打开一个文件,然后 wait ,等待别人把它 wake 起来,然后对文件执行一些操作。

经过多次尝试,我们发现程序打开 mp3 和 avi 文件都不行,但是可以打开压缩包。实在是非常郁闷,最后我们把 mp3 打包为压缩文件,发现也不行。终于发现原来所有的影音文件都放在一个路径包含中文的目录下,而压缩包的路径里没有中文。而队友在打开文件的时候由于编码问题 (Qt 内部使用 unicode 编码) 打开含有中文路径的文件失败,但是他却没有做任何判断!导致线程里面直接出错,根本没有 run 起来,就更别谈 wake 了。

总之不管 Typical Chinese Programmer 这个词存在不存在,在真实项目中尽量写出健壮的程序实在是非常必要的,要不然大半的时间就要在痛苦的调试中渡过了。

引用已经析构了的局部变量

Monday, May 21st, 2007

在平常的编程里面,这样的代码算是再普通不过了:

void func()
{
    SomeType local_obj;
    another_func(local_obj);
    // ...
}

然而最近我却遇到了问题。在使用 boost::asio 的时候,使用了这种常规的做法。结果遭遇段错误。后来才意识到是在进行异步调用。 another_func 拿着 local_obj (事实上,真正的代码是一个 local 的容器,我把它的 iterator 传递到了一个异步调用里面)却在 func 返回的时候就已经被析构了。不过,在异步调用里面也可以很容易地自动管理对象的生存期,很常用的方法就是使用 boost::shared_ptr 了,在 asio 的例子里面也随处可见。

现在刚刚开始接触异步调用、多线程编程方面的东西,很多东西还不熟悉,需要多多实践啊。