Mac OS X Lion Spotlight 优化指南

原创文章,转载请标明出处:Soul Apogee (http://bigasp.com),谢谢。

Spotlight是一个让人又爱又恨的东西,一方面他确实相当好用,但是另外一方面,他也确实占用了不少系统资源,在进行大量文件操作的时候,经常将CPU占满,可以将我可怜的macbook pro烧到80度左右,所以不能不好好的折腾他一下了,这里总结一些这两天摸索出来的Spotlight调优的方法,希望能对大家也有些帮助。
本人使用Mac现在总时间还不超过一周,所以很多东西理解的可能是不对的(什么可能啊,是应该吧),有不正确的地方,还请大家多多指正。

以下优化是在Mac OS X Lion 10.7.2 的系统上实践出来的,其他版本的系统可能有一些不一样,大家可以Google一下具体的方法,但是基本步骤应该是差不多的。
如果没有大量的文件操作,Spotlight对系统的影响还是不大的,占用CPU一般是3% – 5%左右,没有太大必要进行优化。但是如果发现最近电脑温度很高,或者电池消耗很快,就可以检查一下Spotlight,参照这篇文章进行优化了。

Spotlight简介

Spotlight是Mac系统中的查找服务,为了能使查找更加的迅速,Mac系统会在后台运行一个索引服务,在有文件操作或者资料更新,如iTunes,Mail等,就会为他们建立一个索引,用于加速Spotlight的查找。
所以,Spotlight其实分为两个部分:前台Spotlight的查找服务Search.bundle,和后台的索引服务mds。
优化Spotlight,其实就是优化这两个服务。

基本优化方案:减少需要索引的内容

根据上面的介绍,那么我们可以想到,最简单的优化就是减少需要索引的内容。

首先,在Spotlight的设置中选择我们需要索引的内容。
打开系统偏好设置->Spotlight,在搜索结果这个Tab栏内,我们把所有我们不关心的内容全部勾选掉。
我只勾选了:应用程序,系统偏好设置,通讯录和音乐。这些对我来说够用了。
spotlight_search_result:
spotlight_search_result

然后,将我们不需要索引的文件夹都加入Spotlight的黑名单。
在系统偏好设置->Spotlight中的隐私Tab中,我们可以选择那些目录是Spotlight不用去索引的,我们可以利用这个目录来大大减少我们需要索引的文件量。
打开Finder,点击菜单中的前往->前往文件夹,转到根目录/, 将文件夹中除了应用程序和用户的目录,全部放入黑名单。

需要注意的是:
其实用户目录也可以放入黑名单,Spotlight中显示的音乐,联系人和邮件,其实是靠和这些软件数据库直接联系来建立索引的,所以和文件夹的索引没有太大的联系,也可以放入黑名单。这里看大家的喜好,我全放进去了。

另外还需要注意的是:这里需要在Finder里面显示所有隐藏文件,因为一些和系统相关的目录,如/usr,其实都没有索引的必要,都可以放入黑名单。如果显示文件的方法不会的话,大家可以去Google一下。
spotlight_privacy:
spotlight_privacy

最后,我们还需要再设置一下Spotlight需要索引的硬盘,一些移动硬盘和U盘什么的,其实就不需要索引了。
打开终端,输入下面的命令:

# 关闭所有磁盘的索引服务
sudo mdutil -a -i off

# 只打开主硬盘的索引服务,我的主硬盘被挂载到/Volumes/Macintosh HD目录下(默认设置),大家可以根据具体情况修改。
sudo mdutil -i on "/Volumes/Macintosh HD"

如果上面的指令执行错误,可以使用如下命令来恢复对所有磁盘的索引。

sudo mdutil -a -i on

好,到此我们已经完成了第一步优化了。
此时大家可以看到Spotlight在重建索引了,在我的机器上,原来完全建立一次索引需要2小时,现在只需要几分钟了。

替换优化方案 I:替换Spotlight的前端部分

OK,在完成基本优化方案之后,既然我们已经将Spotlight阉割成了这般田地。那我们干脆换掉Spotlight使用其他的软件替代好了。
这里有一个不错的免费软件推荐:Alfred。大家也可以直接在App Store中搜索安装即可,过程就不赘述了。

隐藏任务栏中的Spotlight图标
这个方案问题的关键在于:既然Spotlight都不需要了,我们也就不需要显示它了。
关闭它的方法很简单,在终端里面输入如下指令即可:

sudo mv /System/Library/CoreServices/Search.bundle /System/Library/CoreServices/Search2.bundle
ps aux | grep SystemUIServer | grep -v grep | awk '{print $2}' | xargs kill

现在我们就会发现桌面上的Spotlight图标不见了,这一步优化也就宣告完成了。

当然想要恢复也很简单,输入如下命令即可恢复:

sudo mv /System/Library/CoreServices/Search2.bundle /System/Library/CoreServices/Search.bundle
ps aux | grep SystemUIServer | grep -v grep | awk '{print $2}' | xargs kill

替换优化方案 II:替换整个Spotlight

完全停止Spotlight的意思是不仅仅关闭掉Spotlight的桌面搜索,还停止掉Spotlight的后台索引服务。
所以友情提醒:前方有怪兽!将Spotlight关闭之后,依赖于Spotlight的索引服务的软件将无法正常运行,如Alfred将无法显示搜索结果(依赖于Spotlight索引服务mds),请小心处理,如果引起诡异的问题,与笔者无关。

这个方案是我暂时最喜欢的,因为在系统中Spotlight的索引服务还是随时在运行的,所以它依然会占用我们的系统资源,而我们又无法控制,完全替换他,才是我们最好的选择。
好,让我们开始吧。

完全停止Spotlight后台索引
在完成替换优化方案I之后,我们可以输入如下命令来完全停止Spotlight的索引。

sudo launchctl unload -w /System/Library/LaunchDaemons/com.apple.metadata.mds.plist

在输入完这条指令之后,大家可以查看一下当前系统内是否还存在mds和mdsworker的进程。他们应该都退出了。
至此,Spotlight算是基本被我们给干掉了。

当然,如果我们后悔了,我们也可以通过如下命令来恢复。

sudo launchctl load -w /System/Library/LaunchDaemons/com.apple.metadata.mds.plist

恢复完成之后,不要忘了检查基本优化方案里面的内容哦,因为停止后台索引服务后,索引工作也停止了。

安装替代软件
那在没有后台索引服务的情况下,我们还能不能享受到和Spotlight这种类似的功能呢?
这里再推荐一个小软件:QuickSilver
这款软件里面会建立自己的索引,虽然就功能而言,不及Alfred强大,但也已经相差无几,对于图片,视频的索引是否有必要,个人还是持保留态度的,原因相信大家都懂的,所以基本上对我来说已经够用了。
在这个软件里面,我们可以自己设定软件的索引范围,或者禁止它自动更新索引,这样,就可以让我们最大程度上的控制这个后台服务了。

总结

到此为止,整个Spotlight的优化算是完成了,我可怜的mbp温度终于降低一些了,而且也不会因为大量文件操作导致温度飙升了。总的来说,折腾还是有些效果的,希望能对大家也有些帮助吧。

解决QT在VS下中文乱码的问题

我们用VS编译出来的QT程序,经常会遇见中文乱码,其实解决方法很简单:
在main函数所在文件的包含如下文件:

#include <QtCore/QTextCodec>

然后,在main函数的开头加入这么一句话就可以了。

QTextCodec::setCodecForTr(QTextCodec::codecForName("GB2312"));

之后在调用的时候,所有的字符串记得使用tr()函数包裹起来。比如:

QMessageBox::warning(this, tr("出错啦!"), errorInfo, tr("OK"));

这个是因为VS默认的编码是GB2312,而函数tr的作用,是将默认的字符串转换成QT内部的编码:Unicode,所以我们这里只需要设置好编码即可。
如果碰到一些2312里面没有的生僻字,我们也可以尝试使用Windows默认的多字符编码GB18030来解决。

Posted in 03 Binary Life. Tags: , , . 6 条评论 »

如何正确使用C++多重继承

原创文章,转载请标明出处:Soul Apogee (http://bigasp.com),谢谢。

C++多重继承一直是一个让人搞不太清楚的一个问题,但是有时候为了实现多个接口,多重继承是基本不可避免,当然在Windows下我们有强大的COM来帮我们搞定这个事情,不过如果你想自己实现一套类似于COM的东西出来的时候,麻烦事情就来了。

在COM里面,有两个很基础的,而且我们都会用到的特性:
1. 纯虚接口:一般使用一个只有纯虚函数的类来作为接口
2. 引用计数:在C++中一般都使用引用计数来管理对象的生命周期

这两个功能在一般设计C++接口的时候也经常用到。其实说到底,上面这两个特性牵扯到的是多重继承的二个表现:
1. 多重继承中的数据存储
2. 多重继承中的虚函数

在COM中,纯虚接口是使用的interface来定义的,引用计数是通过IUnknown接口来实现的,所有的接口都是从IUnknown这种接口中派生出来的。当我们需要某一个类实现多个接口的时候,就经常会遇见这样的多重继承:
multi-inheritance-com:
multi-inheritance-com
哦?!是不是很眼熟,ios,istream,ostream,iostream。。各种C++书籍最喜欢用的一个示例。好吧,现在我们先自己实现一个吧,看看到底要怎么使用多重继承。

多重继承中对象的的数据存储

#include <stdio.h>

class IBase
{
public:
    IBase() : n(0) {}
    virtual ~IBase() {}
    void show() { printf("%dn", n); }
    int inc() { return ++n; }
    int dec() { return --n; }

protected:
    int n;
};

class IA : public IBase
{
public:
    virtual ~IA() {}
};

class IB : public IBase
{
public:
    virtual ~IB() {}
};

class CImpl : public IA, public IB
{
public:
    virtual ~CImpl() {}
};

int main(int argc, char* argv[])
{
    CImpl o;
    IA *pA = &o;
    IB *pB = &o;

    pA->inc();
    pA->show();

    pB->dec();
    pB->show();

    return 0;
}

编译,OK,成功了!好,运行试一试。
run-result-1:
run-result-1
为什么是1和-1呢?明明n只在继承的一个类IBase里面有,一次加1,一次减一,结果不是应该是1和0么?是不是很奇怪?

这便是使用多重继承的时候经常产生的第一个问题:多副本的数据存储。
当然这个问题很好解决,只需要使用虚继承即可解决。只需要在IA和IB的定义中,在public IBase前加入virtual关键字即可。

class IA : virtual public IBase
class IB : virtual public IBase

现在再让我们来看一看运行结果:
run-result-2:
run-result-2

结果已经正确了,为什么会发生这种情况呢?虚继承到底干了些什么呢?

我们先来看看在没有使用虚继承的情况下,CImpl的在内存中是怎么样的:
cimpl-memory-normal:
cimpl-memory-normal

对于普通的public继承(非虚继承),C++会将基类和派生类的内容放在一块,合起来组成一个完整的派生类,在内存上看,它们是连在一起的。按照这样的规则,在这里,IA和IB中就会各包含一个IBase,而IA,IB和CImpl中的部分内容又共同组成了CImpl。在将CImpl对象o的指针转型成IA的指针pA过程中,指针将被移动到IA所在的部分区域,同样在转型成IB的过程中,指针将被移动到IB所在的部分区域(也就是说,转型之后,指针的值都不一样)。在之后的操作中,pA操作的便是IA这个部分中的IBase,而pB操作的便是IB这个部分中的IBase,最后IA部分中的IBase变成了1,而IB部分中的IBase变成了-1。所以输出的结果也就变成了1和-1了。

之后我们修改成了虚继承,看看到底发生了什么?
cimpl-memory-virtual:
cimpl-memory-virtual
原来的IA和IB中的IBase部分变成了一个指向基类的指针,而基类也变成了一个单独的部分。这样一旦对基类做任何的修改,都会通过这个指针重定向到这个独立的基类上去,于是,就不存在多副本的数据存储了,这个诡异的问题也就解决了。但是当然从这个图上我们也可以看到,使用虚继承后,访问数据多了一次跳转,这多出的一次跳转将导致效率的下降一倍甚至更多,所以如果一个类使用的非常频繁,很明显应该尽量避免使用虚继承。

二义性

当然数据的存储只是使用多重继承中遇到的一个问题,现在我们来看另外一个问题,函数的二义性。
首先我们先把数据的存储抛开,单纯的来看一个只有函数的继承关系。

class IBase
{
public:
    virtual ~IBase() {}
    void foo() { }
};

class IA : public IBase
{
public:
    virtual ~IA() {}
};

class IB : public IBase
{
public:
    virtual ~IB() {}
};

class CImpl : public IA, public IB
{
public:
    virtual ~CImpl() {}
};

int main(int argc, char* argv[])
{
    CImpl o;
    o.foo();    // 直接调用CImpl的foo函数

    return 0;
}

编译一下,试试。
error C2385: ambiguous access of ‘foo’
could be the ‘foo’ in base ‘IBase’
or could be the ‘foo’ in base ‘IBase’
error C3861: ‘foo’: identifier not found

出错了!杯具。。为什么?错误还这么奇怪,神马叫做可以是IBase中的foo又可以是IBase中的foo呢?

这就是使用多重继承的时候经常产生的第二个问题:二义性。
在使用多重继承时,如果有两个被继承的类拥有共同的基类,那么就很容易出现这种情况。那什么是二义性呢?
我们先来看一个更简单的继承关系:

class A
{
public:
    void foo();
};

class B : public A
{
public:
    void foo();
};

class C : public B
{
public:
    void foo();
};

我们可以把继承关系中,两个类之间沿着基类方向的相隔的继承级数看成一个距离,那么C到A的距离是2,B到A的距离就是1。当然距离不能为负。
当我们对ABC中某个对象调用foo函数的时候,编译器会优先选择离当前指针类型的距离最短的一个函数实现去调用,也就是说,foo函数的查找路径是C->B->A,找到一个最近的去调用。
而对于我们当前这个继承关系来说,IA和IB还是各包含一份IBase的实例,虽然在内存里这里仅仅是包含一份数据,但是在编译的过程中,IA和IB中还包含了一份从IBase中继承下来的函数列表。所以有两个包含有foo函数类与CImpl类的距离是一样的,所以在对CImpl调用foo函数,就产生了所谓的二义性,除非我们指定使用IA::foo或者IB::foo,否则编译器将无法决定使用哪一个基类的foo函数。

o.IA::foo();    // 指定调用CImpl从IA部分继承过来的foo函数,这样就可以编译通过了。

当然如果我们这样写代码也是不行的:

IBase *pBase = &o;    // 指针转义时的二义性,不知道是使用IA中的IBase部分,还是IB中的IBase部分
o.inc();                    // 数据访问时的二义性,不知道是访问IA中IBase部分的n,还是IB中IBase部分的n

多重继承中的虚函数

既然直接使用多重继承会有如此多的问题,那么我们能不能通过虚函数来解决这个问题呢?

这里小小的提一下,刚刚二义性里面说到两个类的距离,对于编译器来说,一般是找离当前的类距离最近的函数实现来调用(或者数据来访问),而虚函数则是让编译器做相反的事情:找一个离当前类反向距离最远的函数实现来调用。

好,我们先把上面的程序做一点点小改变,把foo()函数变成一个虚函数,看看有什么变化。

class IBase
{
public:
    virtual ~IBase() {}
    virtual void foo() {}    // 变成虚函数了
};

编译,结果还是失败。
error C2385: ambiguous access of ‘foo’
could be the ‘foo’ in base ‘IBase’
or could be the ‘foo’ in base ‘IBase’
error C3861: ‘foo’: identifier not found

产生问题的原因依然是二义性。即便换成virtual函数,也不能改变二义性这个问题。为什么呢?
因为我们是用的.运算符来访问的,而不是用指针,所以这里虚函数和普通函数没有任何区别。=.=。。。
好,我们再来小小的修改一下,把他变成指针,让他通过虚表去访问,看看行不行。

CImpl *p = &o;
p->foo();

编译,结果。。。还是一样失败。。。
好吧,我们可以把调用foo()的几句话都去掉,来看看CImpl中生成的虚表到底是个什么样子。
debug-result-vptr-1:
debug-result-vptr-1

在这个实例中,IA和CImpl部分公用一个虚表,而IB则使用另外的一个虚表(两个虚表这个特性主要是在指针类型转换的时候有用,这里就不说了)。
在这IA的虚表中存在一个指向IBase::foo()的指针,在IB的虚表中也存在一个指向IBase::foo()的指针,所以在CImpl中,可以找到两个IBase::foo()函数的指针,这样,编译器就无法确定到底应该使用哪一个IBase::foo()函数作为他自己的foo()函数了。二义性也就产生了。

既然如此,那解决起来就没有什么别的办法了,只能把foo函数的最终在CImpl中实现一次了。

class CImpl : public IA, public IB
{
public:
    virtual ~CImpl() {}
    virtual void foo() { }
};

int main(int argc, char* argv[])
{
    CImpl o;
    o.foo();

    CImpl *p = &o;
    p->foo();

    return 0;
}

编译一下,通过了!对于o.foo()来说,这当然是意料之中,离CImpl距离最近的foo函数实现,就是CImpl自己嘛,当然没有问题。
对于后面这个p->foo()的调用,编译器现在也已经可以决定对于CImpl这个类来说,离他最远的foo函数调用是谁了——也是他自己。
所以这里就不会产生二义性的问题了。

在多重继承中编译器对this指针的修正
这里再让我们来看看这次编译出来的虚表,看看还有什么发现。
debug-result-vptr-2:
debug-result-vptr-2

0x004112a3 [thunk]:CImpl::foo`adjustor{4}’ (void) *
这个看上去很怪的函数是什么呢?我们反汇编一下他看看。
virtual-function-wrapper:
virtual-function-wrapper

这里可以看到有一句汇编指令:sub ecx, 4。这条指令的左右其实是在修正this指针。
因为从IB的虚表来的请求,this指针都是指向CImpl中IB的部分,而当调用CImpl中的foo函数时,如果还使用IB的this指针,那么程序就会出错,所以这里需要先将this指针修正为CImpl所在的地址,才能调用CImpl的foo函数。

在程序运行的时候,this指针一般被存储在ecx寄存器中,或者当前函数的第一个参数传递进去,不过不同的语言或者不同的编译器编译出来的代码可能会不一样。

我们这里的析构函数都是虚函数,所以我们还可以在截图中看到,编译器会对析构函数做同样的处理。

如何同时解决数据访问和二义性问题呢

貌似到现在都只提到最简单的一种多重继承的情况,但是实际上我们已经遇到了很多的问题了,既然多重继承中会有这么多问题,那我们有没有什么比较通用的方法能把他们一起解决了呢?
方法肯定是有的:
1. 使用虚继承
这算是一种确实可行的方法,只是说会带来额外的时间和空间的开销,访问任何一个数据,都需要通过虚继承表进行跳转,不过一般来说够用了。

2. 虚函数当接口,继承多个接口,统一实现
这个思想就类似于COM了,只是说COM用的是纯虚函数,对于那些会产生二义性的类,我们在最后都实现一边,这样就不会有问题了。这样带来的时间开销也仅仅是调用时查询一次虚表。但是麻烦的地方就是,有时候继承一下,你可能就要实现一下了,比如引用计数神马的,当然你也可以通过模版来简化你的代码。

class IBase
{
public:
    virtual ~IBase() {}
    virtual void show() = 0;
};

class IA : public IBase
{
public:
    virtual ~IA() {}
    virtual int inc() = 0;
};

class IB : public IBase
{
public:
    virtual ~IB() {}
    virtual int dec() = 0;
};

class CImpl : public IA, public IB
{
public:
    CImpl() : n(0) {}
    virtual ~CImpl() {}
    int inc() { return ++n; }
    int dec() { return --n; }
    void show() { printf("%dn", n); }

private:
    int n;
};

3. 通过纯虚函数实现模版方法,将函数转移
这种实现比较复杂,wtl中用的比较多,一般是用在引用计数上,好处很明显,就是可以继承,不用每个类都实现一个引用计数,而只用将新的基类的引用计数转移至原本存在的类上就可以了。

class IBase
{
public:
    virtual ~IBase() {}
    void foo() {}
};

class IA : public IBase
{
public:
    virtual ~IA() {}
};

class IShifter
{
public:
    virtual ~IShifter() {}
    void foo() { do_foo(); }

protected:
    virtual void do_foo() = 0;
};

class IB : public IShifter
{
public:
    virtual ~IB() {}
};

class CImpl : public IA, public IB
{
public:
    virtual ~CImpl() {}
    void foo() { IA::foo(); }

protected:
    virtual void do_foo() { IA::foo(); }
};
Posted in 03 Binary Life. Tags: , . 一条评论 »

Chrome学习笔记(一):线程模型,消息循环

原创文章,转载请标明出处:Soul Apogee (http://bigasp.com),谢谢。

看Chrome已经有一段时间了,但是一直都没有沉淀些内容下来,是该写写笔记什么的了,免得自己忘记了。看的都是Windows平台下的代码,所以记录也都是记录的。。。废话。。
那么首先,先从最基础的东西记录起吧:Chrome的线程模型和消息循环。

多线程的麻烦

多线程编程一直是一件麻烦的事情,线程执行的不确定性,资源的并发访问,一直困扰着众多程序员们。为了解决多线程编程的麻烦,大家想出了很多经典的方案:如:对资源直接加锁,角色模型CSPFP等等。他们的思想基本分为两类:一类是对存在并发访问资源直接加锁,第二类是避免资源被并发访问。前者存在许多问题,如死锁,优先级反转等等,而相对来说,后者会好很多,角色模型,CSP和FP就都属于后者,Chrome也是使用后者的思想来实现多线程的。

Chrome的线程模型

为了实现多线程,Chrome思路是简单且尽可能的少用锁,所以它在实现中并没有使用如角色模型之类的复杂的结构,而只是引入了自己的消息循环作为多线程的基础。它足够简单,方便使用,而且很容易实现跨平台。
相比平时的消息循环(如:Windows的消息循环,Linux中的epoll模型),它唯一增加的功能就是可以运行自定义的任务:Task。
如果在一个线程里面需要访问另一个线程的数据,则把接下来要运行的函数和参数包装成一个Task,并将其传递给另外一个线程,由另外一个线程来执行这个Task,从而避免关键数据的并发访问,而又因为任务执行是有顺序的,这样就保证了代码执行的确定性。
chrome-messageloop-task-simple:
chrome-messageloop-task-simple
其实,这就是一个典型的Command模式,而通过这个模式,简单的在线程之间传递Task,就实现了Chrome的多线程模型。

Task

1. Task
为了统一所有消息循环中的任务调用方式,所有的任务的基类都是这个Task类,他唯一的方法就是run(),MessageLoop只需要调用这个虚函数即可。
如果为了简化大家的开发,Chrome可谓下足了功夫,光是一个Task,就提供了各式各样的派生类供大家使用,并提供了良好的实现。

  • 派生出来的Task有:CancalableTask,ReleaseTask,QuitTask等等。
  • 根据调用条件的不同,将Task又分为即时处理的Task、延时处理的Task和Idle时处理的Task。
  • 为了简化开发,还引入了RunnableMethod,封装对象的方法,减少我们自己实现Task的时间。
  • 调用PostTask时,还需要传入一个TrackedObject,用于追踪Task的产生位置,为调试做准备。
  • 2. RunnableMethod
    RunnableMethod是一个很非常有用的类,这个方法通过模版将一个对象和他的方法和参数封装成一个Task,抛入另外一个线程去工作,其中为了保证对象的生命周期,对象的指针必须有引用计数,如果这个Task跨线程调用的话,这个引用计数必须是线程安全的。参数则通过Tuple来进行封装。在Task执行的时候通过另外一个模版将Tuple解开成参数即可。

    线程和消息循环

    Chrome将其线程分为了三类:普通线程,UI线程和IO线程。他们之间的区别是:

  • 普通线程:只能执行Task,没有其他的功能。
  • UI线程:所有的窗口都需要跑在UI线程上,它除了能执行Task以外,还能执行和界面相关的消息循环。
  • IO线程:和本地文件读写,或者网络收发相关的操作都运行在这个线程上,它除了能执行Task以外,还能执行和IO操作相关的事件回调。
  • 由于这三类线程中Task的执行部分基本是一样的,而其他的功能却完成不同,为了实现这不同的三类线程,Chrome将消息循环分成了两个部分:MessageLoop和MessagePump。
    chrome-thread-and-messageloop:
    chrome-thread-and-messageloop
    MessagePump被提取出来负责执行Task的时机和处理线程本身的消息 ,如:UI线程的Windows消息,IO线程的IO事件。
    MessageLoop则仅仅是做Task的管理,它实现了MessagePump的Delegate的接口,这样MessagePump就可以告诉MessageLoop何时应该处理Task了。
    另外实现上虽然Chrome为这三种线程实现了三套MessageLoop,但是它们之间的区别,也仅限于暴露出现的MessagePump的接口不同而已。
    chrome-messageloop-class-diagram:
    chrome-messageloop-class-diagram

    消息循环之MessageLoop

    1. 减少锁的请求
    一般我们在实现任务队列时,会简单的将任务队列加锁,以避免多线程的访问,但是这样每取一次任务,都要访问一次锁。一旦任务变多,效率上必然成问题。
    Chrome为了实现上尽可能少的使用锁,在接收任务时用了两个队列:输入队列和工作队列。
    当向MessageLoop中内抛Task时,首先会将Task抛入MessageLoop的输入队列中,等到工作队列处理完成之后,再将当前的输入队列放入工作队列中,继续执行。
    chrome-messageloop-task:
    chrome-messageloop-task
    这样,就只有当将Task放入输入队列时才需要加锁,而平时执行Task时是无锁的,这样就减少了对锁的占用时间。

    2. 延时任务
    为了实现延时任务,在MessageLoop中除了输入队列和工作队列,还有两个队列:延迟延迟任务队列和需在顶层执行的延迟任务队列。
    在工作队列执行的时候,如果发现当前任务是延迟任务,则将任务放入此延迟队列,之后再处理,而如果发现当前消息循环处于嵌套状态,而任务本身不允许嵌套,则放入需在顶层执行的延迟任务队列。
    一旦MessagePump产生了执行延迟任务的回调,则将从这两个队列中拿出任务出来执行。

    消息循环之MessagePump

    MessagePump是用来从系统获取消息回调,触发MessageLoop执行Task的类,对于不同种类的MessageLoop都有一个相对应的MessagePump,这是为了将不同线程的任务执行触发方式封装起来,并且为MessageLoop提供跨平台的功能,chrome才将这个变化的部分封装成了MessagePump。所以在MessagePump的实现中,大家就可以找到很多不同类型的MessagePump:如MessagePumpWin,MessagePumpLibEvent,这些就是不同平台上或者不同线程上的封装。

    Windows上的MessagePump有三种:MessagePumpDefault,MessagePumpForUI和MessagePumpForIO,他们分别对应着MessageLoop,MessageLoopForUI和MessageLoopForIO。

    下面我们从底层循环的实现,如何实现延时Task等等方面来看一下这些不同的MessagePump的实现方式:

    1. MessagePumpDefault
    MessagePumpDefault是用于支持最基础的MessageLoop的消息泵,他中间其实是用一个for循环,在中间死循环,每次循环都回调MessageLoop要求其处理新来的Task。不过这样CPU还不满了?当然Chrome不会仅仅这么傻,在这个Pump中还申请了一个Event,在Task都执行完毕了之后,就会开始等待这个Event,直到下个Task到来时SetEvent,或者通过等待超时到某个延迟Task可以被执行。

    2. MessagePumpForUI
    MessagePumpForUI是用于支持MessageLoopForUI的消息泵,他和默认消息泵的区别是他中间会运行一个Windows的消息循环,用于分发窗口的消息,另外他还增加了一些和窗口相关的Observer等等。
    各位应该也想到了一个问题:如果在某个任务中出现了模态对话框等等的Windows内部的消息循环,那么新来的消息应该如何处理呢?
    其实在这个消息泵启动的时候,会创建一个消息窗口,一旦有新的任务到来,都会像这个窗口Post一个消息,这样利用这个窗口,即便在Windows内部消息循环存在的情况下,也可以正常触发执行Task的逻辑。
    既然有了消息窗口,那么触发延时Task的就很简单了,只需要给这个窗口设置一个定时器就可以了。

    3. MessagePumpForIO
    MessagePumpForIO是用于支持MessageLoopForIO的消息泵,他和默认消息泵的区别在于,他底层实际上是一个用完成端口组成的消息循环,这样不管是本地文件的读写,或者是网络收发,都可以看作是一次IO事件。而一旦有任务或者有延时Task到来,这个消息泵就会向完成端口中发送一个自定义的IO事件,从而触发MessageLoop处理Task。

    [WordPress Plugin] UBB Master——管理你自己的UBB代码

    前几天想在wordpress里面插入一小段ubb来生成一些链接,结果发现居然没有,于是自己写了一个很弱的小插件来管理自己的ubb代码。语言暂时只有英文,不过内容是中文也是可以用的。
    Wordpress的用户信息真的不是很友好啊,找了半天也没有找到我自己的项目列表,寒。
    现在代码总算是提交了,不过也不知道什么时候才能生效,姑且先这么放着,看看明天结果如何。

    ============= 我是欢乐的分隔线 =============

    插件名称:ubb-master
    插件地址:http://wordpress.org/extend/plugins/ubb-master/
    插件用法:
    一段典型的ubb代码,一般如下:
    [search]put your keyword here…[/search]

    然后我们就可以这样替换这段ubb代码了:
    !{content}: 在[ubb]和[/ubb]标签之间的内容。
    !{encoded_content}: 经过urlencode之后的!{content}
    !{attr:attribute_name}: [ubb]标签中名为attribute_name的属性
    !{encoded_attr:attribute_name}: 经过urlencode之后的!{attr:attribute_name}

    比如:
    如果我们将search的ubb格式定为如下这段代码:
    <a target=”blank” href=”http://www.google.com/search?ie=UTF-8&q=!{encoded_content}”>!{content}</a>

    那么上面这段ubb在输出中将会被转变为如下的内容:
    <a target=”blank” href=”http://www.google.com/search?ie=UTF-8&q=put+your+keyword+here%26%238230%3B”>put your keyword here…</a>

    这样,我们就可以自己定义自己的ubb代码,而不用四处找适合自己的wordpress插件了。

    Google Breakpad 完全解析(二) —— Windows前台实现篇

    Table of contents for Google Breakpad 完全解析

    1. Google Breakpad 完全解析(一) —— Windows入门篇
    2. Google Breakpad 完全解析(二) —— Windows前台实现篇

    原创文章,转载请标明出处:Soul Apogee (http://bigasp.com),谢谢。

    好,看完了如何使用breakpad,我们现在看看breakpad在Windows下到底是如何实现的呢?

    代码结构

    在我们来看breakpad是如何实现其强大的功能之前,我们先来看一下他的代码结构吧。

    Google breakpad的源代码都在src的目录下,他分为如下几个文件夹:
    client:这下面包含了前台应用程序中捕捉dump的部分代码,里面按照平台分成各个子文件夹
    common:前台后台都会用到的部分基础代码,字符串转换,内存读写,md5神马的
    google_breakpad:breakpad中公共的头文件
    processor:用于在后台处理崩溃的核心代码
    testing:测试工程
    third_party:第三方库
    tools:一些小工具,用于处理dump文件和符号表

    我们先来看Windows下前台实现的部分,也就是client文件夹下的代码。

    breakpad的崩溃捕获机制

    在Windows下捕获崩溃,大家很容易会想到那个捕获结构化异常的Api:SetUnhandledExceptionFilter

    breakpad中也使用了这个Api来实现的崩溃捕获,另外,breakpad还捕获了另外两种C++运行库提供的崩溃,一种是使用_set_purecall_handler捕获纯虚函数调用产生的崩溃,还有一种是使用_set_invalid_parameter_handler捕获错误的参数调用产生的崩溃。

        if (handler_types & HANDLER_EXCEPTION)
          previous_filter_ = SetUnhandledExceptionFilter(HandleException);
    
    #if _MSC_VER >= 1400  // MSVC 2005/8
        if (handler_types & HANDLER_INVALID_PARAMETER)
          previous_iph_ = _set_invalid_parameter_handler(HandleInvalidParameter);
    #endif  // _MSC_VER >= 1400
    
        if (handler_types & HANDLER_PURECALL)
          previous_pch_ = _set_purecall_handler(HandlePureVirtualCall);
    

    另外由于C++运行库提供的崩溃回调中,并不会提供当前的线程现场和崩溃信息,所以breakpad会自己生成好这些信息,然后请求生成dump。
    这里值得一说的是,在非异常崩溃处理中,breakpad获取线程现场使用的函数是RtlCaptureContext而不是GetThreadContext。
    RtlCaptureContext只能捕获当前线程的现场,而GetThreadContext可以捕获任意线程的现场,只要有这个线程的句柄即可。
    但是GetThreadContext有两个不好的地方:不能获取当前线程的现场;获取现场前必须先用SuspendThread暂停目标线程。
    而RtlCaptureContext虽然只能获取当前线程的现场,但是调用他时可以不用暂停线程的运行。
    对于breakpad来说,崩溃发生后越早获取现场就越好,所以breakpad使用RtlCaptureContext函数作为他的线程获取函数。

    breakpad中的C/S结构

    由于breakpad是在进程外抓取dump,所以breakpad需要实现一个C/S结构来处理崩溃进程抓取dump的请求。

    1. breakpad跨进程通信的实现
    breakpad中使用了命名管道来实现IPC。

    在客户端,初始化ExceptionHandler的时候,如果指定了PipeName,也就表示此时需要使用进程外的dump抓取,ExceptionHandler,会建立一个 CrashGenerationClient的对象,由这个对象连接服务端,将自己注册到服务端上去。
    大家可以参看exception_handler.cc中的ExceptionHandler::Initialize函数。

    在服务端,初始化CrashGenerationServer的时候,就会建立一个命名管道,并等待客户端来连接。一旦有客户端连接上来,服务端会为每一个客户端生成一个ClientInfo的对象,之后用这个对象来管理所有的客户端,一旦有崩溃发生,服务端都会从这个对象中取出dump所需要的信息。
    大家可以参看crash_generation_server.cc中的CrashGenerationServer::HandleReadDoneState函数。

    2. breakpad捕获崩溃生成dump的流程
    breakpad进程外生成dump的流程大概如下:
    google-breakpad-out-of-process-dump:
    google-breakpad-out-of-process-dump
    这段流程的代码就是crash_generation_client.cc和crash_generation_server.cc。

    有两个简单的问题,这里说明一下,高手们就请直接忽略吧,咩哈哈:
    在服务端如何为客户端生成事件句柄?
    使用DuplicateHandle,即可把任意一个内核对象的句柄复制到其他进程,并且可以指定产生的句柄的权限。

    如何异步的等待一个事件?
    使用RegisterWaitForSingleObject,即可异步的等待一个事件,当事件发生的时候,就可以回调到一个指定的回调函数中,但是要注意的是,RegisterWaitForSingleObject会在一个新的线程中来等待这个事件,此处很容易产生多线程的调用,需要注意线程问题。

    3. 服务端关键数据结构:ClientInfo
    ClientInfo是服务端中最重要的数据结构,服务端通过它来管理所有的客户端。客户端注册时,会保存或生成里面所有的信息,在客户端请求生成dump的时候,服务端就会通过ClientInfo获取所有客户端的信息。ClientInfo中保存了如下信息:

    • 客户端进程pid和句柄
    • 生成Minidump的类型
    • 自定义的客户端信息
    • 客户端崩溃的线程ID
    • 客户端崩溃的信息
    • 客户端请求崩溃所使用的事件句柄

    这里有一个问题:在客户端发生崩溃时,服务器如何通过ClientInfo获取到客户端的崩溃信息呢?

    客户端中有几个用于保存崩溃信息的变量,在注册时,客户端会将这几个变量的地址发送至服务端,服务端将其保存在ClientInfo中,然后当崩溃发生的时候,服务端就可以通过ReadProcessMemory读取客户端中的信息,从而生成dump。这样做就避免了每次发生崩溃,都要通过Pipe将崩溃信息传递到服务端中去了。

    这些变量分别是:崩溃的线程ID,EXCEPTION_POINTERS和MDRawAssertionInfo。
    EXCEPTION_POINTERS和MDRawAssertionInfo的区别在于,异常崩溃的信息会被写入EXCEPTION_POINTERS,非异常崩溃(非法参数和纯虚函数调用)的信息会被写入MDRawAssertionInfo中。

    dump文件的上传

    在breakpad的工程中,有一个工程叫做:crash_report_sender,里面是一个上传崩溃文件的类,他的实现很简单,他使用Windows Internet Api来完成dump文件的上传。
    在使用crash_report_sender时,可以为其指定一个checkpoint_file。

    explicit CrashReportSender(const wstring &checkpoint_file);
    

    这个文件只有一个作用,就是用来保存上次上传崩溃的时间和今天上传过的崩溃的次数。通过这个文件,我们就可以来设置每日上传的崩溃的最大数量。

    CrashReportSender::CrashReportSender(const wstring &checkpoint_file)
        : checkpoint_file_(checkpoint_file),
          max_reports_per_day_(-1),
          last_sent_date_(-1),
          reports_sent_(0) {
      FILE *fd;
      if (OpenCheckpointFile(L"r", &fd) == 0) {
        ReadCheckpoint(fd);
        fclose(fd);
      }
    }
    
    ReportResult CrashReportSender::SendCrashReport(
        const wstring &url, const map<wstring, wstring> &parameters,
        const wstring &dump_file_name, wstring *report_code) {
      int today = GetCurrentDate();
      if (today == last_sent_date_ &&
          max_reports_per_day_ != -1 &&
          reports_sent_ >= max_reports_per_day_) {
        return RESULT_THROTTLED;
      }
    
      // 上传文件部分代码,省略
    }
    

    调整每日上传崩溃的最大数量的函数是set_max_reports_per_day。

    需要注意的是:在上传dump文件的时候,crash_report_sender并不会对dump文件进行分析,而是直接上传整个dump文件,如果你需要上传的dump文件非常大的话,可以考虑把崩溃分析处理的逻辑放入前台,通过去重或者直接上传分析结果,减少上传的文件大小。

    breakpad存在的问题

    进程外生成dump有很多好处,其中最大的好处就是不会被崩溃进程影响,这样dump的过程就不容易出错,但是这样也有一定的弊端。

    1. 部分崩溃无法抓取
    在一些极端的崩溃,如堆栈溢出之类的崩溃,进程外抓取dump有时候会失败。

    2. 无法抓取死锁或者其他原因导致的进程僵死
    breakpad现在没有检测进程死锁的代码,也没有在服务端控制客户端请求dump的代码,所以现在breakpad无法抓取死锁等进程僵死的问题。不过因为breakpad的定位是处理崩溃,如果有这种需要的童鞋,可以自行修改breakpad的代码,添加这些功能。

    3. 对服务端有依赖
    如果指定了在使用进程外抓取dump,breakpad对服务端就有依赖。主要体现在抓取dump时,如果服务端不存在,客户端将无法正常抓取dump,甚至有时会出现阻塞。

    当然对于这些问题,随着breakpad的发展肯定会越来越完善。如果,你遇到了了这些问题,而又绕过不了,那就改代码,并且提交给breakpad吧,开源项目就是这么发展的。

    好,到此breakpad的Windows实现就已经说完了,如果有神马问题,还请多多指教。谢谢大家。

    Google Breakpad 完全解析(一) —— Windows入门篇

    Table of contents for Google Breakpad 完全解析

    1. Google Breakpad 完全解析(一) —— Windows入门篇
    2. Google Breakpad 完全解析(二) —— Windows前台实现篇

    原创文章,转载请标明出处:Soul Apogee (http://bigasp.com),谢谢。

    Google breakpad是一个非常实用的跨平台的崩溃转储和分析模块,他支持Windows,Linux和Mac和Solaris。由于他本身跨平台,所以很大的减少我们在平台移植时的工作,毕竟崩溃转储,每个平台下都不同,使用起来很难统一,而Google breakpad就帮我们做到了这一点,不管是哪个平台下的崩溃,都能够进行统一的分析。现在很多工程都在使用他:最著名的几个如Chrome,Firefox,Picasa和Google Earth。另外他的License是BSD的,也就是说,我们即便是在商业软件中使用,也是合法的,哈哈,这么好的东西,我们能放过么?现在就让我们来看看这个神奇的软件吧。

    原理简介

    breakpad抓取dump的方式和一般我们抓取dump的方式不一样。在breakpad的wiki上有一幅图可以很好的概括他的原理。

    breakpad把应用程序分成三个部分,代码,breakpad客户端和调试信息。

    1. 在build system中,通过symbol dumper用平台相关的调试信息生成平台无关的symbol文件。这样做的好处很明显,一旦平台无关了,所有平台的崩溃就可以做统一的分析了。
    2. breakpad采取进程外转储和分析崩溃的方式,他使用C/S结构,客户端用来捕获当前进程中发生的崩溃,并通知服务端崩溃发生。服务端用来响应客户端,抓取dump文件。这样做的目的是为了减少崩溃进程对dump的影响。
    3. Dump生成后转发到崩溃分析器中,这个部分可以在本地也可以在服务器上,他对Dump文件进行解析,生成可读的堆栈信息。

    这就是breakpad处理dump大概的流程。

    对于原理的介绍google写的已经相当好了。更多的详细信息,可以直接移步到breakpad的wiki

    安装和编译

    breakpad的编译比较曲折,所以在此记录一下。

    编译breakpad,请确认你的机器上装有以下的软件:
    1. python 2.4.3
    请不要使用python3,会报错。另外python2中推荐这个版本,使用新的版本在编译其他google的工程时有时会报错

    2. Windows SDK 7
    如果没有这个,编译会报错。另外这个是在线安装,时间很久,最好并行做其他的事情。

    3. VS2005的补丁
    KB918559
    KB926601
    KB935225
    KB943969
    KB947315

    已经安装了以上软件的童鞋,就可以开始进行下面的工作鸟

    1. 使用svn把代码checkout下来

    # Non-members may check out a read-only working copy anonymously over HTTP.
    svn checkout http://google-breakpad.googlecode.com/svn/trunk/ google-breakpad-read-only
    

    2. 设置Windows SDK 7
    装过其他版本Windows SDK的童鞋,记得一定要进行这一步,SDK的安装程序,并不会帮你设置VS。
    运行开始菜单->程序->Microsoft Windows SDK v7.0->Visual Studio Registration->Windows SDK Configuration Tool,选择v7.0,点击Make Current。

    3. 为python设置环境变量
    由于breakpad使用python来生成Windows下的工程文件,所以需要将python所在目录,设置到环境变量PATH中去。

    4. 生成Windows工程文件

    cd "源码目录/src/tools/gyp"
    
    # 注意,此处不能使用全路径,不然会出错
    gyp.bat "../../client/windows/breakpad_client.gyp"
    

    此时,在src/client/windows下就可以看到生成好的breakpad_client.sln了。运行吧!

    5. Hello World!
    编译build all,现在一般是不会报错了,如果报错,请检查是不是漏了什么步骤,特别是补丁。
    编译完成之后,运行crash_generation_app吧,这是他的测试程序,dump的默认位置保存在C:Dumps下,请注意先建立好目录,不然会无法使用。
    启动测试程序之后,此时还不能抓取dump,因为这个是breakpad中的服务器端,需要再启动一个测试程序,在第二个测试程序中,我们就可以试验Client菜单中的各种崩溃了。这些崩溃都会被抓住转存到C:Dumps目录下。

    如何使用breakpad

    在Windows下使用breakpad的方法很简单,只需要创建一个ExceptionHandler的类即可,大家可以在crash_generation_app这个工程中找到示例代码,也可以直接移步Wiki,上面说的也很详细。

    1.进程内抓取Dump文件

    进程内抓取Dump文件是最简单的breakpad的用法。使用方法很简单:

    const std::wstring s_strCrashDir = L"c:\dumps";
    
    bool
    InitBreakpad()
    {
        google_breakpad::ExceptionHandler *pCrashHandler =
            new google_breakpad::ExceptionHandler(s_strCrashDir,
            onExceptionFilter,
            onMinidumpDumped,
            NULL,
            google_breakpad::ExceptionHandler::HANDLER_ALL,
            MiniDumpNormal,
            NULL,
            NULL);
    
        if(pCrashHandler == NULL) {
            return false;
        }
    
        return true;
    }
    

    2.进程外抓取Dump文件

    使用进程外抓取Dump时,需要指定服务端和客户端,在服务端中需要创建CrashGenerationServer的实例,而在客户端中则只需要创建ExceptionHandler即可。此外,如果服务端自己需要抓进程内的Dump,请将pipe的参数置为NULL。

    const wchar_t s_pPipeName[] = L"\\.\pipe\breakpad\crash_handler_server";
    const std::wstring s_strCrashDir = L"c:\dumps";
    
    bool
    InitBreakpad()
    {
        google_breakpad::CrashGenerationServer *pCrashServer =
            new google_breakpad::CrashGenerationServer(s_pPipeName,
            NULL,
            onClientConnected,
            NULL,
            onClientDumpRequest,
            NULL,
            onClientExited,
            NULL,
            true,
            &s_strCrashDir);
    
        if(pCrashServer == NULL) {
            return false;
        }
    
        // 如果已经服务端已经启动了,此处启动会失败
        if(!pCrashServer->Start()) {
            delete pCrashServer;
            pCrashServer = NULL;
        }
    
        google_breakpad::ExceptionHandler *pCrashHandler =
            new google_breakpad::ExceptionHandler(s_strCrashDir,
            onExceptionFilter,
            onMinidumpDumped,
            NULL,
            google_breakpad::ExceptionHandler::HANDLER_ALL,
            MiniDumpNormal,
            (pCrashServer == NULL) ? s_pPipeName : NULL, // 如果是服务端,则直接使用进程内dump
            NULL);
    
        if(pCrashHandler == NULL) {
            return false;
        }
    
        return true;
    }
    

    使用breakpad的时候,有两个地方需要注意:
    1. 记得把breakpad的solution下的几个工程,包含到你开发的工程中,或者直接包含他们的lib。
    common:基础功能,包含一个对GUID的封装和http上传的类。
    exception_handler:用来捕获崩溃的类。
    crash_generation_server:breakpad的服务端,用来在产生崩溃时抓取dump。
    crash_generation_client:breakpad的客户端,用来捕获当前进程的崩溃。

    2. 在初始化breakpad之前,记得先创建好dump文件的目录,不然breakpad服务端将不能正常的写dump,这会导致breakpad客户端在崩溃时无限等待服务端dump写完的消息,最后失去响应。

    《重构》读书笔记 —— 如何让你的代码变得更好

    原创文章,转载请标明出处:Soul Apogee (http://bigasp.com),谢谢。

    好不容易读完了重构——2010年下半年唯一读了的一本书。只能说这半年以来很忙,来了北京之后连个喘息的机会都没有,半年都没有管过博客了,写写读后感,就当作是除除草吧。
    以下只是我的一些读书笔记,不过虽然是笔记,但是里面并没有关于重构方法的细节记录,而只是粗略的记下了我理解的重构的大致思路。和《重构》这本书不同,这本书本身说的很细,作者把每一步重构的都拆成了很小的步骤,每一步都可以轻松的回滚,所以有兴趣的可以去看看这本书,肯定都会有一定收获的。
    我现在只是一名小菜,过段时间之后,我自己回头看这篇文章,可能都会觉得自己很傻,所以如有不对,还请多多指教,如果实在看不顺眼,就请纯当笑话看吧。
    好,下面进入正题。

    注:因为是读书笔记,所以可能会有抄袭等等奇奇怪怪的问题,如果发现有版权问题,请联系我,我会尽快删除本文。

    ===================== 我是欢乐的分隔线 =====================

    重构的目的

    我们总希望自己的代码能写的更加好看,以至于我MM把我写代码比喻成为打扮自己。没有错,代码就是一个程序员的外表。而重构就是为了让你的代码更加好看。
    但是仅仅是好看而已么?
    不是,好看意味着简洁,好理解,好维护,好扩展。这就是我们真正要达到的目的。
    那这些目的最终又是为了完成什么呢?我们最后再说。

    设计模式和重构

    设计模式想必大家肯定已经不陌生了,平时在开发的时候,大家也肯定用过各种各样的模式来解决遇到的问题。而当你现在手头的代码使用的模式不能很好的支持你继续开发的时候(可能是不够灵活,也可能是过于灵活),你就需要使用重构来修改它,将你的代码变得更加优雅。

    重构与测试

    重构与测试的关系为什么要写在前面?因为测试实在是太重要了。
    我在最近的项目中备受重构的挫折,为什么?因为没有测试。和很多程序员一样,经常迫于项目的压力,没有时间去实现一些测试用例,或者有些测试用例不方便实现,等到需要重构的时候,问题就出现了。

    “我不敢改这段代码,改了要是出错了怎么办?”
    “但是这个版本一定要有这个功能啊。”
    “那我尝试着改吧,出了错再调。”

    如果连这段代码的作者都这么说,谁还敢改这段代码?而偏偏修改中又引入了其他的Bug,结果就是和这个功能相关的所有人都为此买单。大家的时间就这么浪费在了无意义的事情上。如何避免这个问题呢?测试!这是我自己受到的血淋淋的教训。

    写测试代码的时候,经常会遇见两个问题:

    Q:“刚开发完一个功能,紧接着就要开发另外一个,没有时间来写这些测试代码啊?”
    A:在开发这个功能之前,请先些测试用例,这样不仅仅节省了写测试用例的时间,而且能帮助你更快的设计接口,避免走弯路,因为你已经在考虑如何使用这个接口了。

    Q:“我还有开发任务,没有办法写全面的测试用例啊!”
    A:一个全面的测试程序基本是不可能写出来的,功能一直在加,开发一直在进行,没有那么多时间给我们补全测试用例。但是我们不能因噎废食,测试对我们有好处,我们只要实现可能导致出错,或者实现原来出过错的测试用例即可。能找出大部分问题,总比什么问题都找不出,之后去补救的好。

    重构的手段

    不要把重构看成一件很大或者需要花费很多时间的工程,也许它只是添加或者删除一个函数的参数,也许是从一种设计模式切换到另外一种,当然也有需要大规模的修改代码的结构的时候,具体看你遇见的问题来进行操作。

    但是请记住:不管过程如何,重构的目的和本质总是不变的。不要为了重构而重构。

    如何让代码变得更加好看,答案其实很废话:封装。把不变的部分放在一块,把变化的部分提取出来封装在另外一块。
    而何时封装和如何封装,正是整本书在讲的内容。我归纳了一下,大概如下:

    1. 让人迷惑的临时变量

    临时变量容易让人产生迷惑,一个变量被来回的赋值,在某一段代码中他表达的是一个意思,在另外一段代码中,表达的又是另外一个意思,到最后就会让人产生疑惑,这个变量到底是什么?所以使用临时变量一定要小心。如果遇见代码中有很多临时变量,我们可以通过以下一些步骤来消除他们:
    1) 如果一个临时变量在代码中表示了多个意思,那么将代码分段,给每段的临时变量取上不同的名字
    2) 将能用函数查询代替的临时变量全变成函数查询,期间请先不要担心效率
    3) 将能提取的代码提取成让人更好理解的函数
    4) 如果还有顽固的让人不爽的临时变量,请修改你的算法

    当然并不是所有临时变量都是不好的,我们的目的是让代码更加易懂,所以只要这个临时变量能帮助我们理解这段代码,那这个变量就是有意义的。比如用一个临时变量来代替一个复杂的表达式。

    2. 简化条件判断

    条件判断是程序开发中不可避免的一部分,而当某些语句执行的条件比较复杂的时候,如果处理不好,各种各样的条件判断就会阻碍我们理解这段代码真正的目的。而以下就几个方法,能将复杂的条件判断变得清晰。
    1) 用易懂的临时变量或者查询来代替复杂的条件。比如isValid()函数,就比xxx & FLAG_XXX != 0 && xxxx != “” …,要清晰许多。
    2) 确定这段代码的真实目的,对于处理前的条件判断,尽量使用ASSERT和卫语句,发现错误就尽快退出处理,减少条件判断嵌套的层次。
    3) 如果是类型的判断,请引入子类+多态来解决这个问题。
    4) 对于NULL的处理,可以引入NULL对象。通过多态,定义在对象为NULL时的行为。

    在写代码时,请不要在意如“一个函数,一个出口”,“使用breakcontinue是破坏代码的行为”之类的,一直被人灌输的思想。只要能让代码易懂,请尽情的使用这些关键字。

    3. 减少重复代码

    重复代码是万恶之源(当然还有各种万恶之源,如调试器,我们都忽略吧),这点想必只要做过开发的人都经历过,改了这里忘了改那里。既然是相同的代码,那么就请放在一起来吧,尽量不要让重复的代码在你的代码中出现。避免重复代码的方法,有下面几种:
    1) 如果代码完全一样,可以直接提取公共函数
    2) 如果你使用的是一个库,无法修改他本身的代码,那么我们可以建立代理函数或者代理类来减少重复代码
    3) 对于相似的代码,可以使用模版方法,Trait技术来减少重复的代码

    减少重复代码还有一个很重要的方法,那就是避免纯数据类。

    纯数据类是一个很神奇的东西,OO开发带来的一个很大的优点就是可以把数据和数据的操作封装在一起,让代码看起来更加自然。所以在代码中应该尽量避免出现纯数据类,如果经常出现对一个纯数据类的操作,请将他直接封装在这个数据类中。
    比如:文件路径,本身它是一个字符串,习惯Windows开发的童鞋喜欢直接调用Window Api来对路径进行操作,但是有些操作需要多个Api组合完成。这时候,我们无意中就会写出很多重复的代码,实际上,我们把他封装成一个文件路径的类,就可以很好的避免这些重复代码的产生。

    4. 封装变化

    如何让代码看起来简单?有一个标准,看起来变化越少的代码,越易懂。如果一段只有几行的简单代码,那肯定是很容易懂的。既然这样,那么如果我们把变化的部分都封装起来,那么代码肯定看起来就简单了。
    如何封装变化,大家可以猛烈的参考设计模式。如果没有看过,Gang of FourHead First的都不错。
    对于经常用的方法,我这里总结了一下(以下不变化的意思是,不变或者变化较少):

    1) 数据变化不会导致行为发生变化
    在这种情况下,你需要做的就仅仅是封装数据了。
    l 尽量避免纯数据类,因为行为是固定的,所以请把行为放入数据类中,让它变得更加有用。
    l 有一些基本类型实际上也是纯数据类,如上文提到的文件路径。
    l 如果这个变化的数据是几个固定的型别码,那么请用类将它封装起来,再提供几个静态变量表示各种类型,避免出现超出范围的型别码。

     

    2) 数据变化会导致行为发生变化
    l 一般这种情况都是因为数据本身表示类型,这种情况下,请引入子类+多态。解决这种问题设计模式有很多:状态模式,职责链和命令模式等等。

    3) 即使数据不变化,行为本身也可能发生变化
    l 如果行为本身会有变化,此时,我们就需要使用反封装的方法,将数据操作提取出来。此类有一个典型的设计模式:Strategy

    在封装变化的时候,还有一个问题要注意:
    如果出现了“一个新的变化,你要修改很多个类的方法”的问题,请将这些方法移入一个类中,或者提取出一个公有的类。避免这种问题。

    5. 整理你的代码

    有一个很有趣的关于bug数量的规律:一个模块的逻辑代码行数在200-400行之间的时候,bug数量是最少的。根据这个规律,如果你某个模块的代码过多,那么你就应该开始拆分他了。
    而对于实现来说,除了一些变态的Trick,一般越是短小的代码越容易理解,所以如果你写了一段惊世骇俗的冗长代码,请尽快缩短他,不然后来的人要理解他就比较痛苦了。

    1) 过长的函数
    一段非常长的函数,常会让希望理解他的人望而却步。因为他没有办法很快的理解这段函数的目的,深陷在各种逻辑的泥沼中不能自拔。
    l 对于过长的函数,可以先检查里面是否有重复代码,或者可以分段,如果有,请提取成新的函数
    l 检查里面是否有临时变量,看能不能用上述方法简化
    l 如果还是过长,对于缩短代码,这里有一个杀手锏,把这个函数变成一个方法类。
    如:有一个很复杂的函数:ComplexBehavior(xxx),其中的逻辑无法简化。那么可以把它转化成一个叫做ComplexBehavior的类,在其中提取方法,达到缩短过长函数的目的。之后把调用的形式变成ComplexBehavior(xxx).run()即可。

    2) 过长的参数列表
    如果一个函数如果参数较多,可能会导致这个函数难以理解,不方便使用,而且这种函数经常还会遇见需要添加新的参数的情况,这种修改有时是很痛苦的。而在OO的程序中,参数列表一般要短很多。为什么呢?答案就是封装。如何封装?
    l 首先,看有没有能省去的参数,是否存在在对象成员变量中的参数,如果有,请删除它
    l 然后,自然而然想到的,就是整合参数,因为很可能这些参数之间是互相关联的,那么把这些互相关联的参数,提取成一个对象吧
    l 不要想着提高效率或者这个函数不需要某个对象的某些参数,而不把这个对象传入(除非是其他部分的接口)。这种效率不是我们应该考虑的事情,这种提高一般都是零头,而对于不需要的情况,你也不知道什么时候他就需要这些东西了,都传进去总是不会错的。
    l 如果还是不行,请继续用那个杀手锏,建立一个方法类,把所有的参数都转化成他的成员变量。

    3) 过长的类
    在代码中,大家是不是经常遇见吓人的万能的工具类呢?如果一个类,他本身想做很多事情,那么这个类必然会变得过长。
    l 在设计一个类的时候,请首先确定好这个类的目的是什么
    l 对于已经过长的类,请先检查有没有重复代码,有请先消除
    l 确定好他到底想做那些事情,然后将各个部分分别提取成单独的类
    l 如果功能已经很单一了,只是实现本身复杂,请将他的实现分解成多个类
    l 如果这个类多个方法很类似,只是使用方式不同,请尝试提取出共有的接口,然后找出确定使用方式的型别码,然后利用多态来简化这个类

    6. 胶合层

    在代码中,由于自顶向下设计和自下向上实现产生的冲突导致的,用于连接两部分的代码,就是胶合层。他一般出现在接口和代码真正实现的连接处。胶合层有时候是不可避免的,但是如果过厚,就会导致代码难以理解,找很久也发现不了代码的真实在做的事情,所以一般胶合层是越薄越好。如果发现你的代码中间出现了不必要的胶合层,请删除他们。
    什么样是不必要的胶合层呢?如果他的存在并不能使你的代码看起来更加简单,而他又没有什么特殊的用途,那么他就是不必要的胶合层。比如一些可有可无的AdapterProxy

    7. 过多的注释

    写注释是件好事,但是如果发现有一大段代码在解释一些写的非常烂的代码的时候,那么尝试重构这段代码吧。因为不管注释写的再多,我们总归是要去理解这段奇特的代码的,所以让代码本身变得可读性更高才是正确的选择。

    需要注意的是,在实施重构的时候,请使用“建立新函数之后替换原函数”的方式来实现重构,而不是直接修改,这样才能保证每一步足够小,而且能回滚,举一个例子,删除函数参数。

    重构与性能

    程序员写代码的时候,最喜欢做的一件事情,那就是下意识的检查自己写过的代码。下面这些问题,不知道你是否问过自己。

    “这里用for循环查找,万一数据量大一点,效率会有问题吧?”
    “这里用map是不是比vector更好一些?”
    “这里遍历询问好么?要不要让每个模块先注册一下他关注的内容?”
    “这种实现方式会不会绕太多弯路了,速度会很慢吧”

    除非你对程序这部分会承担的数据量或者他所使用组件的性能有非常清楚的了解,否则,请不要幻想着程序会慢在哪些地方,然后擅自的优化代码。因为事实可能并非你的想象,往往对于vectormap的优化,在时间上表现的都只是一个零头。而如果因为这个修改,使你这部分代码变得晦涩难懂,那就太得不偿失了。在优化性能之前,请先实现好整个原型,有了这个,你才能好好的雕琢它。写代码的时候,请谨记KISS原则,优化是之后的事情。

    那到底是哪一部分代码导致我的程序性能不好呢?问性能分析工具吧。

    Linux开发的童鞋请移步gprofhttp://www.cs.utah.edu/dept/old/texinfo/as/gprof_toc.html
    Windows开发的童鞋请自行搜索:aqtime

    重构的本质

    开头说了很多重构的目的,提高可理解性,降低其修改成本等等。我们再深究一些,完成这些都是为了什么?所有这些重构的手法,为的是什么?为了让代码简单?如果只是为了让代码简单,书中就不会提及如产生变化会导致多个模块调整等等的问题。那重构到底为了什么?
    正交性!
    看过Unix编程艺术的人应该都知道,这就是重构的本质。如何让每一个动作,只改变一件事情,而不会影响其他。这就是重构想完成的事情。

    结束

    好了,乱七八糟的写了这么多,至此,就是我看《重构》这本书的一些心得。

    ===================== 我是欢乐的分隔线 =====================

    如果你看到了这个地方,我只能说你强悍,都是海大空的文字,连个像样的图或者代码实例都没有,所以如果你没有开始开一篇博客,就必须看完的强迫症,真的多谢你的耐心。

    换字体啦~

    ABitNo那里看到文泉驿又出新字体了,叫做微米黑。用了以下效果确实不错,只是命令行下的mono字体还是有时候会出现重叠的Bug,大家有兴趣可以试一试,哈哈。

    下载文泉驿微米黑:http://wenq.org/?MicroHei

    OK! 上图~ (缩略图比较小,点击可以看大图)

    screenshot3:
    screenshot3
    screenshot4:
    screenshot4
    screenshot5:
    screenshot5

    Posted in 03 Binary Life. Tags: , . 没有评论 »

    在Ubuntu下安装NBC/NXC环境

        最近在弄了台NXT,摆弄了一下觉得挺好玩..但是他的官方只发布了Windows和Mac OS的编程环境,居然没有基于Linux的…对于光驱报废以久而Windows又崩溃以久的我来说..无异是一个悲剧…不过Linux是强大的…弄了几天终于把这台NXT摆平了,现把过程记录如下,方便自己以后复习也方便那些用Linux刚准备玩NXT的大家…嘿嘿..
        我的系统是Ubuntu 8.04,不同的系统可能某些步骤会有少许不同,大家自己看经验来吧。。
        Linux下NXT的编程环境有很多,NBC/NXC(基于C), NXJ(基于Java的), nxtPython等等,这里我们选用NBC/NXC的环境,毕竟C熟悉一些…嘿嘿…

    首先,下载NBC/NXC专用固件
    这一步必做不可,这一步不做,运行程序将出现File IDE Bad的信息。并且这一步需要给NXT充满电后才能执行操作,不然后果…呃…准备返厂吧…
    1. 去http://code.google.com/p/libnxt/,下载libnxt
    由于这个工程原本是用于NXJ下的,所以编译的时候需要用ant,但是ant貌似很难编译通过,建议使用SCons,http://www.scons.org/,Ubuntu可以直接从源里面获取

    BASH代码
    1. sudo apt-get install scons 

    2. 解压后,打开终端进入该文件夹,用SCons编译,输入

    BASH代码
    1. scons  
    2. # 如果使用的是ant,就输入  
    3. ant -f build.xml  
    4. # 但是我怎么都没有成功,重写build.xml,用网上牛人的build.xml都不行…囧…技术有限阿…. 

    3. 用SCons一般不会有编译错误,编译完成之后,运行

    BASHL代码
    1. sudo cp ./fwflash /usr/local/bin  

    4. 下载NBC/NXC专用固件,http://bricxcc.sourceforge.net/lms_arm_jch.zip
    解压后进入终端,来到当前文件夹,开始刷机

    5. 刷机前请大家务必充满电,并且保证NXT通过USB连接到计算机上。
    首先找一个很细的棍子,比如牙签,棉签之类,然后在NXT机身背面,USB口的下方最靠外侧的小洞底部有一个很小的硬件重置按钮,如图:
    dsc02891:
    dsc02891
    插了吸管的那一个就是,硬件重置按钮大家仔细看就能看到。
    现在用你找的小棍子按下那个按钮,保持5秒中左右,NXT就会发出一声嘎哒声,之后松开按钮,过几秒钟后,NXT就会发出连续的嘎哒声,这时再把按钮按下去,保持几秒钟后,NXT就又会发出一声嘎哒声,之后松开按钮,再过几秒钟,NXT就又会发出连续的嘎哒声,这个时候就已经进入了硬件重置模式了。
    有部分NXT一开机就在发出嘎哒声,这个时候并不是他坏了,而且他里面没有固件的原因,这样的NXT按下硬件重置按钮,保持5秒钟后,会听见NXT发出一声嘎哒声后嘎哒声停止,这时松开按钮,大概4秒钟之后,嘎哒声会重新响起,这个时候就可以开始刷机了。

    6. NBC/NXC固件解压后会产生四个文件, lms_arm_nbcnxc_105.rfw, lms_arm_nbcnxc_106.rfw, LMS_ARM_NBCNXC_105.a79, LMS_ARM_NBCNXC_106.a79, 我们使用106的内核

    BASH代码
    1. sudo fwflash ./lms_arm_nbcnxc_106.rfw  

    刷机很快,很快就能看到结果。
    注意sudo一定要加,不然会出现USB不能正确配置之类的错误提示。

    第二步,配置使得NXT可以与Linux通信
    这里用的是John Hansen的方法,很管用,这里先对原作者表示感谢…
    1. 进入命令行,添加一个用户组,名为legonxt

    BASH代码
    1. sudo addgroup legonxt 

    2. 把你当前的用户添加到该组中

    BASH代码
    1. sudo adduser YourUserName legonxt 

    3. 创建设备配置文件

    BASH代码
    1. sudo gedit /etc/udev/rules.d/45-legonxt.rules  
    2. # 输入以下内容  
    3. SUBSYSTEM=="usb_device", ACTION=="add", SYSFS{idVendor}=="0694",  
    4. SYSFS{idProduct}=="0002", SYMLINK+="legonxt-%k", GROUP="legonxt", MODE="0660", RUN+="/etc/udev/legonxt.sh"  
    5. # 保存并退出gedit  
    6.   
    7. sudo gedit /etc/udev/legonxt.sh  
    8. # 输入以下内容  
    9. #!/bin/bash  
    10. #  
    11. GROUP=legonxt  
    12.   
    13. if [ "${ACTION}" = "add" ] && [ -f "${DEVICE}" ]  
    14. then  
    15.     chmod o-rwx "${DEVICE}"  
    16.     chgrp "${GROUP}" "${DEVICE}"  
    17.     chmod g+rw "${DEVICE}"  
    18. fi  
    19. # 保存并退出gedit  
    20.   
    21. sudo chmod a+x /etc/udev/legonxt.sh  

    好的,重启之后,现在你已经可以通过USB来连接NXT了,当NXT开机接入后,在/dev下会出现如同legonxt-x-y的设备文件,x和y取决于你插入的USB口的编号。
    现在开始连接蓝牙,注意一定要把NXT的蓝牙打开,在NXT的主菜单里面有选项。

    4. 安装蓝牙工具

    BASH代码
    1. sudo apt-get install bluetooth  

    5. 探测NXT,这个时候要确保NXT开机并且是处于蓝牙打开的状态

    BASH代码
    1. hcitool scan  

    不久就可以看到结果
    Scanning …
        00:16:53:05:B5:D9    NXT
    这个就是NXT的蓝牙地址。

    6. 现在修改蓝牙配置文件

    BASH代码
    1. sudo gedit /etc/bluetooth/rfcomm.conf  
    2. # 加入以下字串  
    3. rfcomm0 {  
    4.     # Automatically bind the device at startup  
    5.     bind yes;  
    6.     # Bluetooth address of the device  
    7.     device 00:16:53:05:B5:D9; # 这里要替换成你自己NXT的蓝牙地址  
    8.     # RFCOMM channel for the connection  
    9.     channel    1;  
    10.     # Description of the connection  
    11.     comment "NXT";  
    12. }  
    13. # 保存并退出gedit  

    7. 重启蓝牙适配器

    BASH代码
    1. sudo /etc/init.d/bluetooth restart  
    2. # 除了Ubuntu的其他系统运行下面命令来重启  
    3. sudo /etc/init.d/bluez-utils restart  

    8. 检查连接,终端输入

    Python代码
    1. rfcomm  
    2. # 你可以看到类似以下的信息,证明设备已经找到  
    3. rfcomm0: 00:16:53:05:B5:D9 channel 1 clean  
    4. # 再输入  
    5. ls -l /dev/rfcomm0  
    6. # 可以看到类似如下的信息,一定要确保设备文件有读和写的权限  
    7. crw-rw—- 1 root dialout 2160 2008-11-29 09:31 /dev/rfcomm0  
    8. # 再输入  
    9. groups  
    10. # 可以看到用户组,注意legonxt的用户组要在里面  
    11. asp adm dialout cdrom floppy audio dip video plugdev fuse lpadmin admin legonxt  

    9. 连接你的NXT
    第一次你尝试连接NXT的时候,会请求交换PIN码,一般就使用默认的1234即可,一旦交换,以后连接将不需要此步操作。

    现在你的NXT已经可以正确连接到你的电脑了。

    第三步,建立编程环境
    1. 进入NBC/NXC的官方网站的下载页 http://bricxcc.sourceforge.net/nbc/beta/index.html,下载最新的NBC/NXC,建议大家直接下载Executable的版本,这样可以节省编译的时间并且避免去解决编译中出现的错误。

    2. 下载下来之后解压,然后进入终端并进入当前目录后运行

    BASH代码
    1. sudo cp ./nbc /usr/local/bin  

    3. 下载NeXTTool,这个工具本来没有Linux下的,但是感谢John Hansen,重新编译了一个Linux下的NeXTTool,可以让大家很方便的与NXT进行连接
    NeXTTool For Linux: nexttool (48)

    4. 解压,然后进入终端并进入当前目录后运行

    BASH代码
    1. sudo cp ./NeXTTool /usr/local/bin  

    这样编程环境就建立好了,至于IDE…因为是基于C的,eclipse,codeblock,anjuta,都可以做IDE,或者干脆用vim+make…因为他的makefile很简单,自己写就可以了..嘿嘿…

    第四步,下载你的第一个程序
    打开你喜欢的文本编辑器,输入

    NXC代码
    1. task main() {  
    2.     TextOut(0, LCD_LINE1, "HELLO WORLD!"); 
    3.     Wait(1000);
    4. }  

    保存成1.nxc,用USB连接好你的NXT,在终端输入

    BASH代码
    1. nbc -d -S=usb ./1.nxc  

    接下来就在NXT上看看回显吧,嘿嘿。。

    Posted in 03 Binary Life. Tags: , , , . 2 条评论 »

    [Python] aFileSplit 文件分割器

    最近正在学Python,做为一个小练习,写了一个脚本,能分割文件,大家如果有用就收去吧…

    介绍:
        aFileSplit是一个命令行下简单的文件分割器,当前能够支持GBK和UTF-8的编码,也可以把文件看做二进制文件进行分割。

    使用方法:
    afilesplit.py [options] file1 file2 …
    Options:
    -b                           把文件看做二进制文件分割
    -s size                    按照固定大小分割文件,size的单位是Byte,最小为10
    -l line                      按照固定行数分割文件,最小为1
    -o savedir              指定输出目录
    -h                           显示帮助
    -f dos|unix|mac      把文件保存成dos或者unix或着mac的格式,不选则保存成系统默认格式
    -z                           输出信息成Zenity能识别的格式,方便大家写脚本
    -d                           调试模式

    另附上一脚本,给大家写脚本作为参考..

    安装方法:
    1. 下载afilesplit.zip,解压后进入终端到解压后的目录
    sudo cp ./afilesplit.py /usr/bin
    2. 下载4aFileSplit.zip,解压并复制文件到~/.gnome2/nautilus-scripts目录下。
    3. 打开终端,进入到~/.gnome2/nautilus-scripts下,将该文件变为可执行
    chmod +x *

    4afilesplit (65)
    afilesplit (62)

    [Nautilus Scripts] 把选定的WMA转换成MP3

    最近在下专辑,但是下载下来之后都是wma格式,由于wma和linux下很多播放器(Rhythmbox, Songbird等)都不能很好的兼容,使用起来比较麻烦,所以写了这个脚本,希望也可以方便一下大家….

    工具介绍:
    把选定的WMA格式的文件转换成MP3的文件,如果在Nautilus下没有选定任何文件,那么将不进行转换。

    安装方法:
    1. 下载文件,解压并复制文件到~/.gnome2/nautilus-scripts目录下。
    2. 打开终端,进入到~/.gnome2/nautilus-scripts下,将该文件变为可执行
    chmod -R +x *

    关于转换成MP3的比特率:
    用gedit打开文件,查找 BITRATE=
    后面写上想转换成的比特率即可,个人推荐192,因为实际上192和320已经没有什么区别了,但是大小小了将近一倍,而192和128却相差很远,一般可以明显感觉到128的mp3声音很闷….

    注意:
    转换的时候,lame把wav转换成mp3的时候需要花费较长的时间,一般一个mp3需要大概40s到50s,请较为耐心的等待下。

    需求:
    zenity (一般现在的Linux都已经内置了)
    lame (用于将wav格式的文件转换成mp3,一般Linux也应该都有吧)
    mplayer (用来将wma转换成wav格式,这个没有的要装一下)

    由于本人水平有限,所以大家有建议,或者有兼容性更好的办法,关于更好更直观的zenity –progress使用方法都可以联系我…

    wma2mp3 (71)
    Posted in 03 Binary Life. Tags: , , . 没有评论 »

    [Nautilus Scripts] FSUtils 常用脚本包

    一个很简单的脚本包,但是都是平时常用的东西,于是就做了了脚本包,不知道对大家有用没。

    工具介绍:
    FileConv:
    DOS 2 Unix: 将DOS文件格式的文本文件转换成Unix文件格式
    MAC 2 Unix: 将MAC文件格式的文本文件转换成Unix文件格式
    Unix 2 DOS: 将Unix文件格式的文本文件转换成DOS文件格式
    Unix 2 MAC: 将Unix文件格式的文本文件转换成MAC文件格式

    Utils:
    GEdit ( GB18030 ): 用gedit以GB18030的编码打开文件,支持多文件
    GEdit ( GBK ): 用gedit以GBK的编码打开文件,支持多文件
    GEdit ( UTF-8 ): 用gedit以UTF-8的编码打开文件,支持多文件
    Terminal Here: 打开终端,并进入当前目录

    Root:
    Browse Here As Root: 以Root身份查看当前文件夹
    GEdit As Root ( GB18030 ): 以Root身份用gedit以GB18030的编码打开文件,支持多文件
    GEdit As Root ( GBK ): 以Root身份用gedit以GBK的编码打开文件,支持多文件
    GEdit As Root ( UTF-8 ): 以Root身份用gedit以UTF-8的编码打开文件,支持多文件
    Terminal Here As Root: 以Root方式进入终端,并且进入当前目录

    安装方法:
    1. 下载文件,并复制文件到~/.gnome2/nautilus-scripts目录下。
    2. 打开终端,进入到~/.gnome2/nautilus-scripts下,将该文件变为可执行
    chmod -R +x *

    fsutils (60)
    Posted in 03 Binary Life. Tags: , , . 没有评论 »

    [Nautilus Scripts] ImageScriptPack1.0 图像控制脚本包

    在ImageResizer的基础上做了些改进,并加入了其他类似的脚本,希望能有点用。

    安装方法:
    1. 下载文件,并复制文件到~/.gnome2/nautilus-scripts目录下。
    2. 打开终端,进入到~/.gnome2/nautilus-scripts下,将该文件变为可执行
    chmod -R +x *

    脚本介绍:
    Decolor: 去色脚本
    Flip: 纵向翻转
    Flop: 横向翻转
    Resize: 更改图像大小
    Rotate: 旋转,正的数值表示顺时针转的角度,负的数值表示逆时针转的角度,请大家尽量不要使用非90的倍数角
    Trim: 删除图像边缘空白
    Convert/2 BMP: 把指定图片转换成BMP格式的文件
    Convert/2 GIF: 把指定图片转换成GIF格式的文件
    Convert/2 PNG: 把指定图片转换成PNG格式的文件
    Convert/2 JPEG: 把指定图片转换成JPEG格式的文件

    注意事项:
    1. 以下所有的脚本都只能对JPG, GIF, PNG, BMP类型的图像起作用。
    2. 选定图像后运行脚本,则只转换选定的图像,否则将转换整个文件夹下所有的图像。
    3. Resize脚本会把转换后的图像输出到"当前文件夹/resize"文件夹下,而其他脚本将直接修改源图像,如果出现误操作,将不可恢复,大家使用时请小心。

    修正说明:
    + 修正了ImageResizer中,中文支持不好的问题。
    + 修正了部分Bug。

    imagescriptpack (65)
    Posted in 03 Binary Life. Tags: , . 没有评论 »

    [Nautilus Scripts] ImageResizer 更改选定图片的分辨率

         昨天帮老师整理四六级照片,所以写了这个脚本,可以更改当前文件夹内所有图片或者选定图片的分辨率,大家从数码相机里面拷出的照片一般都需要集体调整大小,这个时候就可以使用这个脚本。
         当没有选定任何图片的时候运行这个脚本,将更改当前文件夹下所有的JPG、GIF、PNG、BMP的图片的分辨率,如果选择了图片,就更改选定图片的分辨率,更改分辨率后的图片,将保存到"当前目录/resized"目录下。

    名称:ImageResizer
    版本:v1.0
    功能:更改当前文件夹内所有图片或者选定图片的分辨率
    安装方法:
    1. 下载文件,并复制文件到~/.gnome2/nautilus-scripts目录下。
    2. 打开终端,进入到~/.gnome2/nautilus-scripts下,将该文件变为可执行
    chmod +x ImageResizer
    需求: ImageMagick, Zenity

    imageresizer (61)