<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>47楼的涂鸦本</title>
	<atom:link href="http://yan-yan.info/feed/" rel="self" type="application/rss+xml" />
	<link>http://yan-yan.info</link>
	<description></description>
	<lastBuildDate>Wed, 07 Jul 2010 08:08:11 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Two patches for Lithium (li3)</title>
		<link>http://yan-yan.info/2010/07/two-patches-for-lithium-li3/</link>
		<comments>http://yan-yan.info/2010/07/two-patches-for-lithium-li3/#comments</comments>
		<pubDate>Wed, 07 Jul 2010 08:08:11 +0000</pubDate>
		<dc:creator>Leechael</dc:creator>
				<category><![CDATA[php]]></category>
		<category><![CDATA[li3]]></category>
		<category><![CDATA[lithium]]></category>
		<category><![CDATA[mysql]]></category>

		<guid isPermaLink="false">http://yan-yan.info/?p=141</guid>
		<description><![CDATA[First, added method _toBoolean to Lithium\data\source\database\adapter\MySql:

    protected function _toBoolean($value) {
        return (int) parent::_toBoolean($value);
    }

Now Lithium treat tinyint(1) in MySQL as boolean type, but when you insert/update with a field has been defined as tinyint(1), Lithium will try insert boolean type, then an [...]]]></description>
			<content:encoded><![CDATA[<p>First, added method <code>_toBoolean</code> to <em>Lithium\data\source\database\adapter\MySql</em>:</p>
<pre>
    protected function _toBoolean($value) {
        return (int) parent::_toBoolean($value);
    }
</pre>
<p>Now Lithium treat tinyint(1) in MySQL as boolean type, but when you insert/update with a field has been defined as tinyint(1), Lithium will try insert boolean type, then an SQL error will throws.</p>
<p>The second patch is for <em>Lithium\data\source\Database</em>. Try this:</p>
<pre>
class User extends Lithium\data\Model {}

$user = User::create();
$user->name = 'Leechael';
$user->email = 'blah@blah.blah';
$user->blah = 'Field that NOT exists in schema';
$user->save();
</pre>
<p>Than you will got an SQL error, said <q>unknown column `blah`</q>. The solution is add a few lines in method Database::create() and Database::update(). First one, go to line 190:</p>
<pre>
while (list($field, $value) = each($data['fields'])) { // This line is what we looking for!
    // Insert following block!
    if (!isset($schema[$field])) {
        continue;
    }
// other codes ....
</pre>
<p>Second, go to line line 274 (after previous patched):</p>
<pre>
while (list($field, $value) = each($data['fields'])) { // You looking for this!
    if (!isset($schema[$field])) {
        continue;
    }
// other codes ....
</pre>
<p>Done.</p>
]]></content:encoded>
			<wfw:commentRss>http://yan-yan.info/2010/07/two-patches-for-lithium-li3/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Setup a helper for Lithium (a.k.a. li3)</title>
		<link>http://yan-yan.info/2010/07/setup-a-helper-for-lithium-a-k-a-li3/</link>
		<comments>http://yan-yan.info/2010/07/setup-a-helper-for-lithium-a-k-a-li3/#comments</comments>
		<pubDate>Mon, 05 Jul 2010 03:25:17 +0000</pubDate>
		<dc:creator>Leechael</dc:creator>
				<category><![CDATA[php]]></category>
		<category><![CDATA[li3]]></category>
		<category><![CDATA[lithium]]></category>

		<guid isPermaLink="false">http://yan-yan.info/?p=137</guid>
		<description><![CDATA[Save your helper class under app/extensions/helper/, like Navigator.php, than calls $this->Navigator in your template.
It&#8217;s fucking easy but leaks document. Hope this helps you.
]]></description>
			<content:encoded><![CDATA[<p>Save your helper class under <code>app/extensions/helper/</code>, like <em>Navigator.php</em>, than calls <code>$this->Navigator</code> in your template.</p>
<p>It&#8217;s fucking easy but leaks document. Hope this helps you.</p>
]]></content:encoded>
			<wfw:commentRss>http://yan-yan.info/2010/07/setup-a-helper-for-lithium-a-k-a-li3/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>PHP Snippet: ip4_in_range</title>
		<link>http://yan-yan.info/2010/06/php-codes-ip4_in_range/</link>
		<comments>http://yan-yan.info/2010/06/php-codes-ip4_in_range/#comments</comments>
		<pubDate>Wed, 30 Jun 2010 05:29:53 +0000</pubDate>
		<dc:creator>Leechael</dc:creator>
				<category><![CDATA[php]]></category>
		<category><![CDATA[ip]]></category>
		<category><![CDATA[snippet]]></category>

		<guid isPermaLink="false">http://yan-yan.info/?p=131</guid>
		<description><![CDATA[ip 范围检测函数，老生常谈了，贴一下自己造的轮子：

function ip4_in_range ($ip, $start, $end = null) {
    if (func_num_args() === 2) {
        if (strpos($start, '*') !== false) {
            $end = str_replace('*', '255', $start);
          [...]]]></description>
			<content:encoded><![CDATA[<p>ip 范围检测函数，老生常谈了，贴一下自己造的轮子：</p>
<pre>
function ip4_in_range ($ip, $start, $end = null) {
    if (func_num_args() === 2) {
        if (strpos($start, '*') !== false) {
            $end = str_replace('*', '255', $start);
            $start = str_replace('*', 0, $start);
        } elseif (strpos($start, '/') !== false) {
            $ip_dec = ip2long($ip);
            list($range, $netmask) = explode('/', $start);
            $netmask_dec = ~ (pow(2, (32 - intval($netmask))) - 1);
            $range_dec = ip2long($range);
            return (($ip_dec &#038; $netmask_dec) === ($range_dec &#038; $netmask_dec));
        } else {
            trigger_error('ip4_in_range: Parameter $start maybe in invalid format.');
            return false;
        }
    }
    extract(array_map(function ($ip) {
        return (float) sprintf("%u", ip2long($ip));
    }, compact('ip', 'start', 'end')));
    if ($start > $end) {
        list($start, $end) = array($end, $start);
    }
    return ($ip >= $start &#038;&#038; $ip <= $end);
}
</pre>
<p>只简单的做了几个小测试，都通过了。用法：</p>
<ul>
<li>ip4_in_range('10.0.1.13', '10.0.1.0/24');</li>
<li>ip4_in_range('10.0.1.13', '10.0.1.*');</li>
<li>ip4_in_range('10.0.1.13', '10.0.1.10', '10.0.1.20');</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://yan-yan.info/2010/06/php-codes-ip4_in_range/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Ubuntu Installing Note</title>
		<link>http://yan-yan.info/2010/06/installing-ubuntu-via-netboot/</link>
		<comments>http://yan-yan.info/2010/06/installing-ubuntu-via-netboot/#comments</comments>
		<pubDate>Tue, 29 Jun 2010 06:59:23 +0000</pubDate>
		<dc:creator>Leechael</dc:creator>
				<category><![CDATA[linux]]></category>
		<category><![CDATA[ubuntu]]></category>

		<guid isPermaLink="false">http://yan-yan.info/?p=119</guid>
		<description><![CDATA[
首先保证两个机器位于同一网段。
下载必须的文件：http://archive.ubuntu.com/ubuntu/dists/maverick/main/installer-i386/current/images/netboot/ 中的 mini.iso，netboot.tar.gz，pxelinux.0， pxelinux.cfg 文件夹及其下的 default 文件。解压 netboot.tar.gz，注意不要给 netboot.tar.gz 中的 pxelinux.0 和 pxelinux.cfg 覆盖下载回来的同名文件。这里是下载 Ubuntu Maverick，时间不同了可能选择的版本也不同了。
下载 TFTPD32 的最新版本：http://tftpd32.jounin.net/tftpd32.html。
 TFTPD32 与前述下载的文件必须存于同一文件夹中。打开后点击 DHCP Server 的选项卡，IP Pool starting address 填入通过 netboot 安装 ubuntu 的机器(Target PC)的 IP，Boot File 填 pxelinux.0，WING/DNS Server、Default Router、Mask 按照实际情况填写，保存。
启动 Target PC，注意打开并设定网络启动，然后看看 TFTPD32 中显示的机器 MAC 地址是否 Target PC 的 MAC 地址。多看 Log viewer 中的信息。在文件复制完毕后，在 Target PC [...]]]></description>
			<content:encoded><![CDATA[<ol>
<li>首先保证两个机器位于同一网段。</li>
<li>下载必须的文件：<a href="http://archive.ubuntu.com/ubuntu/dists/maverick/main/installer-i386/current/images/netboot/">http://archive.ubuntu.com/ubuntu/dists/maverick/main/installer-i386/current/images/netboot/</a> 中的 mini.iso，netboot.tar.gz，pxelinux.0， pxelinux.cfg 文件夹及其下的 default 文件。解压 netboot.tar.gz，注意不要给 netboot.tar.gz 中的 pxelinux.0 和 pxelinux.cfg 覆盖下载回来的同名文件。这里是下载 Ubuntu Maverick，时间不同了可能选择的版本也不同了。</li>
<li>下载 TFTPD32 的最新版本：<a href="http://tftpd32.jounin.net/tftpd32.html">http://tftpd32.jounin.net/tftpd32.html</a>。</li>
<li> TFTPD32 与前述下载的文件必须存于同一文件夹中。打开后点击 DHCP Server 的选项卡，IP Pool starting address 填入通过 netboot 安装 ubuntu 的机器(Target PC)的 IP，Boot File 填 pxelinux.0，WING/DNS Server、Default Router、Mask 按照实际情况填写，保存。</li>
<li>启动 Target PC，注意打开并设定网络启动，然后看看 TFTPD32 中显示的机器 MAC 地址是否 Target PC 的 MAC 地址。多看 Log viewer 中的信息。在文件复制完毕后，在 Target PC 上开始安装 Ubuntu。</li>
<li><strong>务必</strong>使用 LVM。这次安装的 500GB 硬盘分区方案：
<ul style="margin-left: 2em">
<li style="list-style-type:circle">/boot &#8211; 250M, ext2(还会选择其他 filesystem 么？)</li>
<li style="list-style-type:circle">/swap &#8211; 3G</li>
<li style="list-style-type:circle">/ &#8211; 30G, btrfs</li>
<li style="list-style-type:circle">/home &#8211; 200G, /ext4</li>
<li style="list-style-type:circle">/var &#8211; 分配所有的剩余空间，/ext4</li>
</ul>
</li>
<li>修改 /etc/apt/sources.list，<code>sudo apt-get update &#038;&#038; sudo apt-get upgrade</code></li>
<li><code>sudo apt-get install openssh-server</code>. See also: <a href="http://www.cyberciti.biz/tips/linux-unix-bsd-openssh-server-best-practices.html">Top 20 OpenSSH Server Best Security Practices</a></li>
<li><code>sudo apt-get install mysql-server apache2 libapache2-mod-php5 php5 php5-cli php5-mcrypt php5-curl php5-gd php5-mysql php5-sqlite php-pear</code></li>
<li><code>sudo apt-get install dnsmasq sysstat</code></li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://yan-yan.info/2010/06/installing-ubuntu-via-netboot/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP Framework，以及团队</title>
		<link>http://yan-yan.info/2010/06/php-framework-and-team/</link>
		<comments>http://yan-yan.info/2010/06/php-framework-and-team/#comments</comments>
		<pubDate>Fri, 11 Jun 2010 21:28:27 +0000</pubDate>
		<dc:creator>Leechael</dc:creator>
				<category><![CDATA[php]]></category>
		<category><![CDATA[team]]></category>
		<category><![CDATA[weekly]]></category>

		<guid isPermaLink="false">http://yan-yan.info/?p=117</guid>
		<description><![CDATA[这一次选 Framework 的过程比较简单。Zend Framework 直接忽略，个人感觉它更像一个 toolkits 或者 library。Symfony 的感觉就是 Model 太臃肿，做 ORM 的 Propel 和 Doctrine 实际上都不讨我喜欢，此外通过脚本实现 duplicated 的代码还是放弃罢了。个人感觉写脚本做 script generator 的 framework 就有点过了。考虑 Kohana，但 Singleton 使用过度了，感觉这就不利于做 Unit Test，写 Mock 也会比较困难的感觉；或者可能是自己没有通读 Kohana 源码的关系。暂时用着 Lithium，可能也有不便于测试，或者 Singleton 过多的问题，但目前感觉还是不错的。Lithium 缺少文档，下一步大概就是读一读源码了，不过得看看工作怎么安排是好了。
目前的团队有三个人——忽略“鸡”这个角色。对于实践 Scrum，我并没有底，毕竟自己也是半吊子的水平，对 scrum 的认识仅限于《硝烟中的Scrum和XP》一书。也经过好一段子日子的思考了，打算在接下来的日子，

先把使用 Google Calendar 和 Gmail 这两个使用习惯培养起来，
然后实践 Daily Scrum Meeting，先从交流和每人能确定自己的工作目标做起。

由于端午节假期，我与另一位团队成员也得回学校处理毕业相关事宜，所以一口气吃成胖子也是不现实的。或者在接下来做的一个外包单子中实践上述的两点。
《Get Things Done》的重读还没有完成。这个星期奔波在旅途中就去了两天（再次来回肇庆-广州），回广州后的第二天就是病了的感觉，也是有气无力地混了一天多。感觉这一周没完成了点什么，看来对产出物的定义还是需要有的。
]]></description>
			<content:encoded><![CDATA[<p>这一次选 Framework 的过程比较简单。Zend Framework 直接忽略，个人感觉它更像一个 toolkits 或者 library。Symfony 的感觉就是 Model 太臃肿，做 ORM 的 Propel 和 Doctrine 实际上都不讨我喜欢，此外通过脚本实现 duplicated 的代码还是放弃罢了。个人感觉写脚本做 script generator 的 framework 就有点过了。考虑 Kohana，但 Singleton 使用过度了，感觉这就不利于做 Unit Test，写 Mock 也会比较困难的感觉；或者可能是自己没有通读 Kohana 源码的关系。暂时用着 Lithium，可能也有不便于测试，或者 Singleton 过多的问题，但目前感觉还是不错的。Lithium 缺少文档，下一步大概就是读一读源码了，不过得看看工作怎么安排是好了。</p>
<p>目前的团队有三个人——忽略“鸡”这个角色。对于实践 Scrum，我并没有底，毕竟自己也是半吊子的水平，对 scrum 的认识仅限于《<a href="http://www.infoq.com/cn/minibooks/scrum-xp-from-the-trenches">硝烟中的Scrum和XP</a>》一书。也经过好一段子日子的思考了，打算在接下来的日子，</p>
<ol>
<li>先把使用 Google Calendar 和 Gmail 这两个使用习惯培养起来，</li>
<li>然后实践 Daily Scrum Meeting，先从交流和每人能确定自己的工作目标做起。</li>
</ol>
<p>由于端午节假期，我与另一位团队成员也得回学校处理毕业相关事宜，所以一口气吃成胖子也是不现实的。或者在接下来做的一个外包单子中实践上述的两点。</p>
<p>《Get Things Done》的重读还没有完成。这个星期奔波在旅途中就去了两天（再次来回肇庆-广州），回广州后的第二天就是病了的感觉，也是有气无力地混了一天多。感觉这一周没完成了点什么，看来对产出物的定义还是需要有的。</p>
]]></content:encoded>
			<wfw:commentRss>http://yan-yan.info/2010/06/php-framework-and-team/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>问题解决笔记三则</title>
		<link>http://yan-yan.info/2010/05/%e9%97%ae%e9%a2%98%e8%a7%a3%e5%86%b3%e7%ac%94%e8%ae%b0%e4%b8%89%e5%88%99/</link>
		<comments>http://yan-yan.info/2010/05/%e9%97%ae%e9%a2%98%e8%a7%a3%e5%86%b3%e7%ac%94%e8%ae%b0%e4%b8%89%e5%88%99/#comments</comments>
		<pubDate>Sun, 23 May 2010 09:56:16 +0000</pubDate>
		<dc:creator>Leechael</dc:creator>
				<category><![CDATA[mac]]></category>
		<category><![CDATA[homebrew]]></category>
		<category><![CDATA[macports]]></category>
		<category><![CDATA[macvim]]></category>
		<category><![CDATA[mysql]]></category>

		<guid isPermaLink="false">http://yan-yan.info/?p=112</guid>
		<description><![CDATA[改用 homebrew 作为包管理工具
改用了 homebrew 作为包管理工具。 @jjgod 有一篇对 homebrew 的介绍，不过文中说的安装方法有点繁琐；我本就是想打算用 homebrew 来管理这些工具的。
官方提供了一个 Ruby 安装脚本，因此也可以通过这个方法进行安装：

ruby -e "$(curl http://gist.github.com/raw/323731/install_homebrew.rb)"

由于 homebrew 的哲学之一，就是避免使用 sudo，将程序的 owner 更改为 root，所以需要更改默认安装目录的权限来避免因为权限问题而出现的操作失败：

sudo chown -R `whoami` /usr/local

我是完整卸载 MacPorts 后才安装的 homebrew 的，自然得把缺的东西的装回来，倒是得先把一些工具装回来：

    brew install git wget rsync

至于 Mercurial，homebrew 给出一个提示让我使用 pip 安装：

    brew install pip &#038;&#038; pip install mercurial

MySQL 无法启动
由于改用了 homebrew 作为包管理工具，原来 [...]]]></description>
			<content:encoded><![CDATA[<h3>改用 homebrew 作为包管理工具</h3>
<p>改用了 homebrew 作为包管理工具。 <a href="http://twitter.com/jjgod">@jjgod</a> 有一篇<a href="http://blog.jjgod.org/2009/12/21/homebrew-package-management/">对 homebrew 的介绍</a>，不过文中说的安装方法有点繁琐；我本就是想打算用 homebrew 来管理这些工具的。</p>
<p>官方提供了一个 <a href="http://gist.github.com/323731">Ruby 安装脚本</a>，因此也可以通过这个方法进行安装：</p>
<blockquote><p>
<code>ruby -e "$(curl http://gist.github.com/raw/323731/install_homebrew.rb)"</code>
</p></blockquote>
<p>由于 homebrew 的哲学之一，就是避免使用 sudo，将程序的 owner 更改为 root，所以需要更改默认安装目录的权限来避免因为权限问题而出现的操作失败：</p>
<blockquote><p>
<code>sudo chown -R `whoami` /usr/local</code>
</p></blockquote>
<p>我是完整卸载 MacPorts 后才安装的 homebrew 的，自然得把缺的东西的装回来，倒是得先把一些工具装回来：</p>
<blockquote><p>
    <code>brew install git wget rsync</code>
</p></blockquote>
<p>至于 Mercurial，homebrew 给出一个提示让我使用 pip 安装：</p>
<blockquote><p>
    <code>brew install pip &#038;&#038; pip install mercurial</code>
</p></blockquote>
<h3>MySQL 无法启动</h3>
<p>由于改用了 homebrew 作为包管理工具，原来 MacPorts 安装的 MySQL 得重新安装，但安装结束后却得到了一个错误，<q>ERROR! Manager of pid-file quit without updating file</q>，求救于 Google 大神，发现是 MacPorts <a href="http://nixcraft.com/databases-servers/14483-starting-mysql-error-manager-pid-file-quit-without-updating-file.html#post24524">卸载时没有清理干净 MySQL 进程导致的问题</a>，<code>killall mysql</code>，然后进行剩余步骤：</p>
<blockquote>
<p>
<code>mysql_install_db</code><br />
<code>launchctl unload -w ~/Library/LaunchAgents/com.mysql.mysqld.plist</code><br />
<code>cp /usr/local/Cellar/mysql/5*/com.mysql.mysqld.plist ~/Library/LaunchAgents</code><br />
<code>launchctl load -w ~/Library/LaunchAgents/com.mysql.mysqld.plist</code>
</p>
</blockquote>
<p>还得给 .profile 文件加上 alias，<q>alias mysqlctl=&#8221;/usr/local/Cellar/mysql/5.1.47/share/mysql/mysql.server&#8221;</q>，保存，<code>source ~/.profile</code>，然后 <code>mysqlctl start</code>，搞定。</p>
<h3>MacVim 的配置问题</h3>
<p>用上了 <a href="http://www.vim.org/scripts/script.php?script_id=2975">fugitive.vim</a> 才知道 MacVim 的 $PATH 是有问题的，打开 Preferences，激活 Launch Vim processes in a login shell 这个选项，打开一个新的 MacVim 窗口，<code>:!echo $PATH</code>，问题就这样解决了。</p>
]]></content:encoded>
			<wfw:commentRss>http://yan-yan.info/2010/05/%e9%97%ae%e9%a2%98%e8%a7%a3%e5%86%b3%e7%ac%94%e8%ae%b0%e4%b8%89%e5%88%99/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>一次项目流产的小结</title>
		<link>http://yan-yan.info/2010/05/though-on-workflow-of-pinfox-development/</link>
		<comments>http://yan-yan.info/2010/05/though-on-workflow-of-pinfox-development/#comments</comments>
		<pubDate>Sat, 22 May 2010 16:44:45 +0000</pubDate>
		<dc:creator>Leechael</dc:creator>
				<category><![CDATA[work]]></category>

		<guid isPermaLink="false">http://yan-yan.info/?p=108</guid>
		<description><![CDATA[由于只得到一个模糊的答复，但我还是得先承认目前的状况：这个项目流产了，我是这个项目流产的最大关系人。
个人的各种问题和原因，我不多说，我现在只想说技术相关的。
这是来自 MySpace.cn/9911 的项目，做一个 Firefox 的插件，类似 Twitterfox，现在叫做 Echofon 的那个小东西。Firefox 的插件所使用的技术难度不大，JavaScript + XUL，再加点 CSS，很简单，打包成为 xpi 的时候可能还需要知道怎么用 bash，官方有一个自动打包的脚本，但有问题，需要自己修一修。一个对前端熟悉的开发者，再加点 Linux 平台下工作的经验，大致也有足够的基础开始开发了。
单元测试
我并非 quick and dirty 类型的开发者，如果我看代码的结构或者架构有点不爽了，我会小部分、甚至重构整个程序。对此，一个有效的 Unit Test 是最重要的保证。可惜，我没有写 Unit Test。JavaScript 有好些 Unit Test 框架，都是针对 Web 项目的，基于 HTML 的，并非 XUL 的。实际上，并没有一个用于单元测试的工具。当时我曾想过自己做一个简陋的，但终究没有做起来。基于浏览器的测试框架，大致可以通过 F5 刷新来完成测试，而基于 XUL 的，似乎只能通过手动点击更新 chrome 的按钮（通过开发辅助插件）。
我想过一个基于 Python 方案的方案， SpiderMonkey 有 Python 类库的实现。但是这充其量只能算是一个 JavaScript Parser，做语法检测还行，做单元测试是不足够的。
或者，会存在一个足够好的方案来达到自动化测试的目的，但我没有在项目初期做好一个完整的测试方案，若是想着做一个不断完善的东西，一个不断在重构中成长的软件（姑且算作是吧），没有完善的自动化测试方案，简直是笑话。达到工业级质量的软件，必须有单元测试作为后盾。
开发环境、版本控制和自动构建脚本
在项目初期，开发环境是 Windows XP，中途曾转换到 Ubuntu，也有短时间地在公司的 Mac 上进行开发（主要是界面的调整），但开发环境的不停变更，对项目开发，铁定是有影响的。先不说开发环境的配置需要大量的时间，光是一个小问题就足够烦恼了：自动构建脚本。
如果真的存在自动构建脚本，我想，率先需要测试的，肯定是这个东西。而且，这个东西还得能够自动侦测开发环境的不同而出现的差异，自动选择方案。bash [...]]]></description>
			<content:encoded><![CDATA[<p>由于只得到一个模糊的答复，但我还是得先承认目前的状况：这个项目流产了，我是这个项目流产的最大关系人。</p>
<p>个人的各种问题和原因，我不多说，我现在只想说技术相关的。</p>
<p>这是来自 MySpace.cn/9911 的项目，做一个 Firefox 的插件，类似 Twitterfox，现在叫做 Echofon 的那个小东西。<strong>Firefox 的插件所使用的技术难度不大，JavaScript + XUL，再加点 CSS，</strong>很简单，打包成为 xpi 的时候可能还需要知道怎么用 bash，官方有一个自动打包的脚本，但有问题，需要自己修一修。一个对前端熟悉的开发者，再加点 Linux 平台下工作的经验，大致也有足够的基础开始开发了。</p>
<h3>单元测试</h3>
<p>我并非 quick and dirty 类型的开发者，如果我看代码的结构或者架构有点不爽了，我会小部分、甚至重构整个程序。对此，一个有效的 Unit Test 是最重要的保证。可惜，我没有写 Unit Test。JavaScript 有好些 Unit Test 框架，都是针对 Web 项目的，基于 HTML 的，并非 XUL 的。实际上，并没有一个用于单元测试的工具。当时我曾想过自己做一个简陋的，但终究没有做起来。基于浏览器的测试框架，大致可以通过 F5 刷新来完成测试，而基于 XUL 的，似乎只能通过手动点击更新 chrome 的按钮（通过开发辅助插件）。</p>
<p>我想过一个基于 Python 方案的方案， SpiderMonkey 有 Python 类库的实现。但是这充其量只能算是一个 JavaScript Parser，做语法检测还行，做单元测试是不足够的。</p>
<p>或者，会存在一个足够好的方案来达到自动化测试的目的，但我没有在项目初期做好一个完整的测试方案，若是想着做一个不断完善的东西，一个不断在重构中成长的软件（姑且算作是吧），<em>没有完善的自动化测试方案，简直是笑话。达到工业级质量的软件，必须有单元测试作为后盾。</em></p>
<h3>开发环境、版本控制和自动构建脚本</h3>
<p>在项目初期，开发环境是 Windows XP，中途曾转换到 Ubuntu，也有短时间地在公司的 Mac 上进行开发（主要是界面的调整），但开发环境的不停变更，对项目开发，铁定是有影响的。先不说开发环境的配置需要大量的时间，光是一个小问题就足够烦恼了：自动构建脚本。</p>
<p>如果真的存在自动构建脚本，我想，率先需要测试的，肯定是这个东西。而且，这个东西还得能够自动侦测开发环境的不同而出现的差异，自动选择方案。bash shell 我一直觉得是一个很神奇很强大的东西，组合各个小工具写成的 script 可以很强大，可惜，这并不等于迁移到另一机器上就一定能够顺利运行。这也并非说这不好，至少，<strong>需要考虑自动构建脚本的健壮性问题。</strong></p>
<p>至于版本控制，我认为我是弄得一塌糊涂的，一个像是在进行版本控制的开发过程；由于开发机的不固定，有点像是把 git 当作 svn 使用了。对于个人开发者来说，问题本是不大，但作为中央节点的机器硬盘出现了问题──中央节点的代码仓库坏了。当时最新的代码版本在某个开发机上，问题影响不大，但若真的需要版本回滚，或者其他──问题就出现了。这本是一个小问题，但要<em>把 git 用得像 git，而非 svn</em> ──这很重要，十分的重要。</p>
<h3>类库</h3>
<p>开始之时，XHR 和数据库的封装都是自己实现的。由于我固执地认为，需要把取回的消息本地存一份，所以我曾经有好几天的时间，用于钻研怎么在 Firefox 中用好 SQLite。问题是，在 Firefox 中，SQLite 是一个很奇怪的东西。对于并发的读，SQLite 没有问题。但是对于并发的写，例如多个进程或者线程，以异步的方式，在一个 IO 速度不稳定的设备（互联网）上取回数据，然后写入──这样，SQLite 很容易就会出现 deadlock。另一方面，SQLite API 的使用方法相对怪异（相对于习惯 PHP 所实现的 SQLite 封装的我来说）。虽然最近对于这个东西的开发中，我放弃了使用 SQLite 作为后端储存、甚至说没有储存除了帐号信息以后的数据，实际上也并不需要储存这些数据。</p>
<p>继续说类库。在中段有一些时候，不知道从哪里看到 jQuery 也可以在 XUL 的环境中使用，但实际上，需要修改很多很多的东西才能正常使用，如上文所属，没有单元测试保证，这样的改动就意味着人工测试，也意味着开发成本的增加、开发时间的增加。我勉强改了点，想着改名叫作 gQuery ── g stand for Gecko ── 但结果，也会随着现在这个东西的流产也流产了。</p>
<p>开发难度</p>
<p>可以说，开发这样的浏览器插件，是一个很有趣的挑战，现在回想起来就像跑进一个物种丰富的原始森林中觅食，你没有工具，虽然身边都是丰富的原材料。</p>
<p>先说界面。苏小雨的 CSS 2.1 手册是划时代的产品，这让我们知道了 CSS 2.1 有哪些属性，作用如何，但是整理这个出来也就是一个巨大的工程了。有这样的一个手册，我们可以知道哪个属性可以干点什么，作用是什么。再看看 developer.mozilla.org 上的这个页面：<a href="https://developer.mozilla.org/en/CSS_Reference">https://developer.mozilla.org/en/CSS_Reference</a></p>
<p>很不错，列出了很多。那么，试试把 -moz-appearance 找出来。Ctrl + F 是找不出来的，得找 Google，然后，你终于找到一个像样的说明了：<a href="https://developer.mozilla.org/en/CSS/-moz-appearance">https://developer.mozilla.org/en/CSS/-moz-appearance</a>。</p>
<p>这足够吗？</p>
<p>Firefox 有一点不好：它能保证同样的 HTML 页面、同样的 CSS 代码在不同平台的 Firefox 下有一致的显示效果，却不能保证同样的 XUL + CSS 代码在不同平台的 Firefox 下有一致的显示效果。若非我所在的公司给我配了 Mac Mini 作为开发机，我怕是怎么也不知道，原来还得针对 Mac 的 Firefox 写 hack。对了，前面说的 -moz-appearance ── 这个属性得好好记住，跨平台的样式问题，首先考虑是不是它的问题就好了，摆弄 border、background 什么的，弄不懂按钮的样式，但加一句 <code>-moz-appearance: none</code> 以后，请随意折腾，像折腾 HTML 一样。</p>
<p>Firefox 在 Linux 下的效率被人诟病，我想着还有 XUL 的原因。scrollbox 是一个很不错的东西，试试鼠标按着 scrollbox 滚动条的下拉按钮不放，同期看看CPU占用率──无论在 Windows 还是 Ubuntu，在同一机器上，CPU 占用率都轻松突破 90%。或者看官的机器好一点，不会出现这么大的一个数字。</p>
<p>现在，我会认为这是一个开发难度很大的项目了。看看 <a href="http://code.google.com/p/zuosafox/">zuosafox</a>，对比它和 Twitterfox 的代码（<strong>我敢说 ZuosaFox 是抄袭和改动 TwitterFox 的源码做出来</strong>）；细看 Twitterfox，他们的功能也不多，功能增加基本上停止了──请看源码架构，当然，没有好的测试工具怕也是原因之一。</p>
<h3>小结</h3>
<p>写了这么多，也不知道还有什么要写了──现已将近六月，这个项目由开始到现在，已经五个月了，我在其中所犯的错误太多，也不一一细数了。大致总结好了。</p>
<ol>
<li>别把开发看得太简单。根据项目需求分析功能，然后调研。项目一上来就要求有这个那个的功能──稍等！先试试完成第一步，一个小小的功能，稳定了、符合了，重构，加功能，测试、重构。TDD 是一个良好的开发习惯。</li>
<li>即使增加一个小小的功能，对整个程序的架构也会有很大的影响。</li>
<li>良好的封装，完善的测试，是保持持续重构和优化的基础。</li>
<li>接上，工若善其事，必先礼其器，要甚至考虑，你所用的工具是否足够，例如单元测试框架，例如开发框架，例如文档和社区。</li>
</ol>
<p>最后，我想着免不了对 9911 API 说点什么。这是一个有问题的 API，每个 URI，输出的同一个数据集、譬如说，user，都会存在问题，在 URI A 中，user 可能附带了字段 FOO，但在 URI B 的输出中，user 这个数据集就不包含 FOO 这个字段。他们大致没有好好地用 OO 来掩饰他们不良好的封装。试想一个只是写了一条 SQL 语句跑出结果、再扔 json_encode 中、最后把结果返回的脚本。封装不良好的一个问题就是，对测试不良好，但就我的感觉，我认为他们甚至没有单元测试。我希望我这两条推断都是错的。</p>
]]></content:encoded>
			<wfw:commentRss>http://yan-yan.info/2010/05/though-on-workflow-of-pinfox-development/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Thought on Work</title>
		<link>http://yan-yan.info/2010/05/thought-on-work/</link>
		<comments>http://yan-yan.info/2010/05/thought-on-work/#comments</comments>
		<pubDate>Fri, 21 May 2010 15:15:54 +0000</pubDate>
		<dc:creator>Leechael</dc:creator>
				<category><![CDATA[work]]></category>
		<category><![CDATA[scrum]]></category>
		<category><![CDATA[tdd]]></category>
		<category><![CDATA[thought]]></category>

		<guid isPermaLink="false">http://yan-yan.info/?p=105</guid>
		<description><![CDATA[年初接了一个工作，但拖至今天仍未能完成，对方告知，“公司或者会砍掉这个项目”。暂缓说更多在这项工作中出现的问题，或者真的要总结就是一篇长长的东西，只说自己的原因：拖沓，然后受困于架构的问题，事情多了就顾此失彼。
昨晚一口气读了 IBM DeveloperWorks 上连载的《演化架构与紧急设计系列》，似乎略有所悟：自己知道 TDD，却从来没有真正身体力行地实践 TDD，这会不会是一个原因呢？
再看这篇，《懂了才去定制》，里面说的一段话：

一个学生如果对要学的东西还没有真正吃透，那么请不要急于寻求突破；一个开发团队如果对将要采用的新方法还没一个完整的认识，那么也请不要盲目定制。
因为你很可能还不懂自己究竟在做什么。

虽然自己读了《硝烟中的Scrum和XP》，如果没有身体力行，没有完整理解，没有尝试，那么这那是在实践敏捷开发呢？
需要更多反思才是。
说到Scrum，倒是想到了一个好处，就是把工作都白纸黑字量化了，分成一个个backlog，不能再说因为可见的变化不存在，那么就是没有完成工作。下午公司的每周例会上，自己的低效受到了批评，但另一方面，我在思考当前项目的问题时，所做的都是背后的处理，再多的代码、改了又改，也只是看不到的变化，而业绩多少的定义，怕是更着重表面上看到的。而自己都是把想法在脑里打转，想一点做一点，遇到问题、需要修改大片代码，然后把时间就浪费在重构和手动测试中。一个可演化的架构，一个更快捷的自动化测试，Test-Driven Development——或者是自己需要的。这话我说得不肯定，但至少是目前的一根救命稻草吧。
]]></description>
			<content:encoded><![CDATA[<p>年初接了一个工作，但拖至今天仍未能完成，对方告知，“公司或者会砍掉这个项目”。暂缓说更多在这项工作中出现的问题，或者真的要总结就是一篇长长的东西，只说自己的原因：拖沓，然后受困于架构的问题，事情多了就顾此失彼。</p>
<p>昨晚一口气读了 IBM DeveloperWorks 上连载的《<a href="http://www.ibm.com/developerworks/cn/java/j-eaed/">演化架构与紧急设计系列</a>》，似乎略有所悟：自己知道 TDD，却从来没有真正身体力行地实践 TDD，这会不会是一个原因呢？</p>
<p>再看这篇，《<a href="http://dyang.github.com/agile/2010/02/09/customization.html">懂了才去定制</a>》，里面说的一段话：</p>
<blockquote><p>
一个学生如果对要学的东西还没有真正吃透，那么请不要急于寻求突破；一个开发团队如果对将要采用的新方法还没一个完整的认识，那么也请不要盲目定制。</p>
<p><a href="http://en.wikipedia.org/wiki/Shu_ha_ri">因为你很可能还不懂自己究竟在做什么。</a></p>
</blockquote>
<p>虽然自己读了《<a href="http://book.douban.com/subject/3390446/">硝烟中的Scrum和XP</a>》，如果没有身体力行，没有完整理解，没有尝试，那么这那是在实践敏捷开发呢？</p>
<p>需要更多反思才是。</p>
<p>说到Scrum，倒是想到了一个好处，就是把工作都白纸黑字量化了，分成一个个backlog，不能再说因为可见的变化不存在，那么就是没有完成工作。下午公司的每周例会上，自己的低效受到了批评，但另一方面，我在思考当前项目的问题时，所做的都是背后的处理，再多的代码、改了又改，也只是看不到的变化，而业绩多少的定义，怕是更着重表面上看到的。而自己都是把想法在脑里打转，想一点做一点，遇到问题、需要修改大片代码，然后把时间就浪费在重构和手动测试中。一个可演化的架构，一个更快捷的自动化测试，Test-Driven Development——或者是自己需要的。这话我说得不肯定，但至少是目前的一根救命稻草吧。</p>
]]></content:encoded>
			<wfw:commentRss>http://yan-yan.info/2010/05/thought-on-work/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>在 shell prompt 中显示 git 的变更</title>
		<link>http://yan-yan.info/2010/05/git-prompt/</link>
		<comments>http://yan-yan.info/2010/05/git-prompt/#comments</comments>
		<pubDate>Wed, 19 May 2010 18:46:26 +0000</pubDate>
		<dc:creator>Leechael</dc:creator>
				<category><![CDATA[linux]]></category>
		<category><![CDATA[bash]]></category>
		<category><![CDATA[git]]></category>
		<category><![CDATA[prompt]]></category>

		<guid isPermaLink="false">http://yan-yan.info/?p=96</guid>
		<description><![CDATA[看到这样的一个东西，git-prompt。
闲话略过不提，大概记下自己应用这东西的笔记。
首先是 checkout 代码：git clone http://github.com/lvv/git-prompt.git ~/src/；这里假设你用户根目录下有一个叫做 src 的文件夹。
在 Linux 中是 ~/.bashrc，在 Mac OS X 下是 ~/.profile，使用趁手的编辑打开这个文件，我这里是 vim ~/.profile，加入这句：source ~/src/git-prompt/git-prompt.sh。
至此，安装完成。需要修改配置的话，把 git-prompt.conf 复制到用户根目录下并改名为 .git-prompt.conf，通过修改这个文件进行配置即可。
我不喜欢那么长的一行 prompt，而且还会把你自定义的 PS1 给强制无效化。前一个问题简单修改一下 git-prompt.sh 就可以了，后一个问题就跟整个运作机制有关了。
我对 git-prompt.sh 的修改很简单，将 601 行的 

head_local="${head_local+$vcs_color$head_local }"

改为：
head_local="\n${head_local+$vcs_color$head_local }"
将 659 行的

PS1="$colors_reset$rc$head_local$color_who_where$dir_color$cwd$tai    l_local$dir_color$prompt_char $colors_reset"

改为：

PS1="\n$color_who_where$dir_color$cwd$tail_local$dir_color$colors_re    set$rc$head_local$colors_reset$prompt_char "

完事。
]]></description>
			<content:encoded><![CDATA[<p>看到这样的一个东西，<a href="http://github.com/lvv/git-prompt/">git-prompt</a>。</p>
<p>闲话略过不提，大概记下自己应用这东西的笔记。</p>
<p>首先是 checkout 代码：<code>git clone http://github.com/lvv/git-prompt.git ~/src/</code>；这里假设你用户根目录下有一个叫做 <em>src</em> 的文件夹。</p>
<p>在 Linux 中是 ~/.bashrc，在 Mac OS X 下是 ~/.profile，使用趁手的编辑打开这个文件，我这里是 <code>vim ~/.profile</code>，加入这句：<code>source ~/src/git-prompt/git-prompt.sh</code>。</p>
<p>至此，安装完成。需要修改配置的话，把 <strong>git-prompt.conf</strong> 复制到用户根目录下并改名为 <strong>.git-prompt.conf</strong>，通过修改这个文件进行配置即可。</p>
<p>我不喜欢那么长的一行 prompt，而且还会把你自定义的 <code>PS1</code> 给强制无效化。前一个问题简单修改一下 git-prompt.sh 就可以了，后一个问题就跟整个运作机制有关了。</p>
<p>我对 git-prompt.sh 的修改很简单，将 601 行的 </p>
<blockquote><p>
<code>head_local="${head_local+$vcs_color$head_local }"</code>
</p></blockquote>
<p>改为：</p>
<blockquote><p><code>head_local="\n${head_local+$vcs_color$head_local }"</code></p></blockquote>
<p>将 659 行的</p>
<blockquote><p>
<code>PS1="$colors_reset$rc$head_local$color_who_where$dir_color$cwd$tai    l_local$dir_color$prompt_char $colors_reset"</code>
</p></blockquote>
<p>改为：</p>
<blockquote><p>
<code>PS1="\n$color_who_where$dir_color$cwd$tail_local$dir_color$colors_re    set$rc$head_local$colors_reset$prompt_char "</code>
</p></blockquote>
<p>完事。</p>
]]></content:encoded>
			<wfw:commentRss>http://yan-yan.info/2010/05/git-prompt/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>哲学家的追求</title>
		<link>http://yan-yan.info/2010/04/chase-of-philosophers/</link>
		<comments>http://yan-yan.info/2010/04/chase-of-philosophers/#comments</comments>
		<pubDate>Thu, 08 Apr 2010 01:21:26 +0000</pubDate>
		<dc:creator>Leechael</dc:creator>
				<category><![CDATA[quotations]]></category>
		<category><![CDATA[George Santayana]]></category>
		<category><![CDATA[philosophy]]></category>
		<category><![CDATA[quotes]]></category>

		<guid isPermaLink="false">http://yan-yan.info/?p=91</guid>
		<description><![CDATA[
觊觎真理需要独特的热情。每个哲学家都说他在追求真理，但事实并非如此。正如一个哲学家所评论的那样，哲学家们往往不能获取真理的一个理由是，他们往往并不渴望获取真理。真正忙于探索真理的人是科学家、博物学家、历史学家……专业哲学家们通常是只会道歉的人，即他们沉迷于为某些被赋予的假象或者某些有鼓动性的思想而辩解。就像律师或侦探，他们受人雇用，分析案情，以确定他们能搜集多少辩护所需的证据或疑似证据以及能举出多少支持控告的反证；因为他们知道，他们正在为嫌犯辩护，人们怀疑，也许他们自己的良知也怀疑，此人犯有伪造罪。他们并不觊觎真理，只是向往胜利和消除他们的疑虑。他们辩护的是某种体系，即，某种关于事物整体的观点，而实际上人类对此一无所知。假如人们只是对了解事情的真相、事情的来龙去脉感兴趣，就不会建立起任何体系，我们的某种流行的或继承的观点恰当而充分，不理会所有有望成功之士而有意坚持这一点，正是形成体系的因素。一个体系可能包含许多事物，其细节真实可靠；但是作为一个体系──此体系包含无限的可能性，我们的经验和逻辑均无法对此有所影响──它必须是想象力的结晶、人类的独白。它也许表现人类经验，也许富有诗意；然而，真正觊觎真理的人，无论是谁，怎么可能认为这就是真理？

George Santayana
]]></description>
			<content:encoded><![CDATA[<blockquote><p>
觊觎真理需要独特的热情。每个哲学家都说他在追求真理，但事实并非如此。正如一个哲学家所评论的那样，哲学家们往往不能获取真理的一个理由是，他们往往并不渴望获取真理。真正忙于探索真理的人是科学家、博物学家、历史学家……专业哲学家们通常是只会道歉的人，即他们沉迷于为某些被赋予的假象或者某些有鼓动性的思想而辩解。就像律师或侦探，他们受人雇用，分析案情，以确定他们能搜集多少辩护所需的证据或疑似证据以及能举出多少支持控告的反证；因为他们知道，他们正在为嫌犯辩护，人们怀疑，也许他们自己的良知也怀疑，此人犯有伪造罪。他们并不觊觎真理，只是向往胜利和消除他们的疑虑。他们辩护的是某种体系，即，某种关于事物整体的观点，而实际上人类对此一无所知。假如人们只是对了解事情的真相、事情的来龙去脉感兴趣，就不会建立起任何体系，我们的某种流行的或继承的观点恰当而充分，不理会所有有望成功之士而有意坚持这一点，正是形成体系的因素。一个体系可能包含许多事物，其细节真实可靠；但是作为一个体系──此体系包含无限的可能性，我们的经验和逻辑均无法对此有所影响──它必须是想象力的结晶、人类的独白。它也许表现人类经验，也许富有诗意；然而，真正觊觎真理的人，无论是谁，怎么可能认为这就是真理？
</p></blockquote>
<p><cite><a href="http://en.wikipedia.org/wiki/George_Santayana">George Santayana</a></cite></p>
]]></content:encoded>
			<wfw:commentRss>http://yan-yan.info/2010/04/chase-of-philosophers/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>
