2008年9月24日星期三

VeryCD版电驴(eMule)存在封锁

eMule是一个GPL程序,所以VeryCD的改版必须公开源码。今天听说VeryCD版有封锁的现象,所以贝壳抓源码来看看。如果大家认为老调重弹的话,不妨把文章拉到最后。
源码从此处下载:http://www.emule.org.cn/download/
最下方链接:http://download.verycd.com/eMule-VeryCD-src.rar
贝壳下到的文件大小13,703,064字节,打包时间2008-09-11。经过贝壳查找,在eMule-VeryCD-src\src \WordFilter发现两个文件,WordFilter.cpp 2008-03-12 09:57 13374和WordFilter.h
2007-11-20 17:56 1009。仔细阅读里面,发现有以下内容。
void CWordFilter::Init()
{
HANDLE hFile;
DWORD dwRead;
int nLen;
BOOL bResult;
CStringList list;

//m_count = 0;

CString saaa = thePrefs.GetMuleDirectory(EMULE_EXECUTEABLEDIR) + FLITER_FILE;
CString sbbb = thePrefs.GetMuleDirectory(EMULE_CONFIGDIR) + FLITER_FILE;

// 如果文件目录不对,程序移动一下,到config目录下 added by kernel1983 2006.07.31
if (PathFileExists(thePrefs.GetMuleDirectory(EMULE_EXECUTEABLEDIR) + FLITER_FILE))
MoveFile(thePrefs.GetMuleDirectory(EMULE_EXECUTEABLEDIR) + FLITER_FILE, thePrefs.GetMuleDirectory(EMULE_CONFIGDIR) + FLITER_FILE);

if (!PathFileExists(thePrefs.GetMuleDirectory(EMULE_CONFIGDIR) + FLITER_FILE))
{
// 不存在,所有的都过滤 added by kernel1983 2006.08.08
m_filterall = true;
return;
}

// Open file for read
hFile = CreateFile(thePrefs.GetMuleDirectory(EMULE_CONFIGDIR) + FLITER_FILE, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
//AddLogLine(false,_T(":%s\n"),thePrefs.GetConfigDir() + FLITER_FILE);
if(hFile == NULL || hFile == INVALID_HANDLE_VALUE)
{
// 读取错误,所有的都过滤 added by kernel1983 2006.08.08
m_filterall = true;
return;
}

DWORD dwSize = GetFileSize(hFile, NULL);

TCHAR * pszData = new TCHAR[(dwSize / sizeof(TCHAR)) + 1]; // 申请空间
bResult = ReadFile(hFile, pszData, dwSize, &dwRead, NULL); // 读入文件1
CloseHandle(hFile);
pszData[(dwSize / sizeof(TCHAR))] = 0;

if(bResult)
{
// 加入解码算法
{
std::string tempstr( (char*)pszData + 1 , ((int)dwSize - 1) > 0 ? dwSize -1 : 0 );

// 查看是否是老格式
char * pszData_a = (char*) pszData;

if( pszData_a[0] != 0x15 ) {
// 老格式,进行转换
CUnicodeToMultiByte wc2mb( thePrefs.GetMuleDirectory(EMULE_CONFIGDIR) + FLITER_FILE );
tempstr.assign( (char*)pszData , dwSize );
InternalBase64::encode2file( tempstr , std::string((LPCSTR)wc2mb , wc2mb.GetLength()) );

delete [] pszData;
// 重新载入
return Init();
}

vector vec = InternalBase64::decode( tempstr );
char * pszt = (char*) pszData;
for( size_t i = 0; i < vec.size() ; i++ ) {
pszt[i] = vec[i];
}
dwSize = vec.size();
}

TCHAR * pszTemp = wcstok(pszData + 1, _T("\r\n"));
while(pszTemp != NULL)
{
nLen = wcslen(pszTemp);
while(pszTemp[nLen - 1] == '\t' || pszTemp[nLen - 1] == ' ')
{
nLen --;
pszTemp[nLen] = 0;
}
while(*pszTemp == '\t' || *pszTemp == ' ')
{
pszTemp ++;
nLen --;
}
//AddLogLine(false,_T("pszTemp:%s"),pszTemp);
//AddLogLine(false,_T("nLen:%d"),nLen);
if(nLen > 0)list.AddTail(pszTemp);
//if(nLen == 8)AddLogLine(false,_T(":%d %d %d %d "),((char*)pszTemp)[0],((char*)pszTemp)[1],((char*)pszTemp)[2],((char*)pszTemp)[3]);
pszTemp = wcstok(NULL, _T("\r\n"));
}
}

delete[] pszData;

m_count = list.GetCount();
//AddLogLine(false,_T("m_count:%d"),m_count);

if(bResult && m_count > 0)
{
m_filterwords = new TCHAR*[m_count+1];
m_kmpvalue = new int*[m_count+1];
ZeroMemory(m_filterwords, sizeof(TCHAR *) * m_count);
ZeroMemory(m_kmpvalue, sizeof(int *) * m_count);
}

for(int i = 0; bResult && (i < m_count); i ++)
{
CString s = list.GetAt(list.FindIndex(i));
s.MakeLower();
nLen = s.GetLength();
//AddLogLine(false,_T("nLen:%d"),nLen);
m_filterwords[i] = new TCHAR[nLen + 1];
m_filterwords[i][nLen] = 0; // 最后一个字节设为0
m_kmpvalue[i] = new int[nLen];
//AddLogLine(false,_T("nLen:%d"),nLen);
_tcscpy(m_filterwords[i],s);
//AddLogLine(false,_T("m_filterwords[i]:%s"),m_filterwords[i]);
KMP_GetNext(m_filterwords[i], m_kmpvalue[i]); // 得到一个与内容有关的数值m_kmpvalue[i]
}

if(m_count == 0 || !bResult)
{
Free();
//m_filterall = true;
}
}


bool CWordFilter::VerifyString(const CString & sString) // 验证字符是否合法
{
bool bIsRm = sString.Right(3)==_T(".rm");
CString sReduceString=sString;
CString sInterpunctionString = _T("(),().。·;:-《》『』~ “”〓!【】★×┇");
try // VC-Huby[2007-03-20]:满足中国国情特色,加强过滤
{
int j=0;
for( int i=0; i< sString.GetLength(); i++ )
{
if( sString.GetAt(i)<=_T('/') && sString.GetAt(i)>=_T(' ') ) //从空格到'/'之间的字符减掉后再过滤
{
continue;
}
else if( sString.GetAt(i)<=_T('@') && sString.GetAt(i)>=_T(':') )
{
continue;
}
else if( sString.GetAt(i)<=_T('`') && sString.GetAt(i)>=_T('[') )
{
continue;
}
else if( sString.GetAt(i)<=_T('~') && sString.GetAt(i)>=_T('{') )
{
continue;
}
else if( sInterpunctionString.Find(sString.GetAt(i))>=0 )
{
continue;
}
else
{
sReduceString.SetAt(j,sString.GetAt(i));
j++;
}
}
if( j< sString.GetLength() )
sReduceString.SetAt(j,_T('\0'));
}
catch (...)
{
}

if(m_filterall){
//AddLogLine(false,_T("m_filterall"));
return true; // 检测不到文件,或者读取错误的情况下放弃过滤
}
if(m_count == 0){
//AddLogLine(false,_T("m_count == 0"));
return true; // 文件是空的时候,放弃过滤功能
}
CString strSearch = ((CString)sReduceString).MakeLower();

//vc-huby: 过滤中文字符超过15字符
//CString sReduceString2=strSearch;
int k=0;
for( int i=0; i< strSearch.GetLength(); i++ )
{
if( strSearch.GetAt(i)<=_T('9') && strSearch.GetAt(i)>=_T('0') )
{
continue;
}
if( strSearch.GetAt(i)<=_T('z') && strSearch.GetAt(i)>=_T('a') )
{
continue;
}
else
{
k++;
}
}

if( k>=20 && bIsRm )
return false;
//int m = sReduceString2.GetLength();
/*
if( k>=60 )
return false;*/


/*if (strSearch.GetLength() > 20)
{
return false;
}*/

for(int i = 0; i < m_count; i ++)
{
if(KMP_Match(strSearch, m_filterwords[i], m_kmpvalue[i]))
{
//AddLogLine(false,_T("KMP_Match"));
return false; // 关键词命中了,被fliter了
}
}
//AddLogLine(false,_T("漏掉的"));
return true;

}

void CWordFilter::Free() //
{
for(int i = 0; i < m_count; i ++)
{
if(m_filterwords[i])
delete[] m_filterwords[i];
if(m_kmpvalue[i])
delete[] m_kmpvalue[i];
}
delete[] m_filterwords;
delete[] m_kmpvalue;
}

CWordFilter::~CWordFilter()
{
Free();
}
其中WordFilter.h的第17行有以下定义。
#define FLITER_FILE _T("wordfilter.txt")
于是贝壳查看了C:\Program Files\eMule\config目录,在下面发现了wordfilter.txt 2007-09-30 12:58 10788。大家有兴趣自己看看里面的内容,贝壳就不贴了,贴出来绝对被封,死1090次。
下面说一点起效方式,也许大家很奇怪,这些内容是可以搜索的。贝壳仔细查看了代码,类在两处被引用了,一个是MFC初始化系统的时候初始化类,载入词典。另外一个是在SearchList.cpp 2007-11-20 17:56 22505,351行AddToList函数,第360行,内容如下。
// WordFilter added by kernel1983 2006.07.31
if(!WordFilter.VerifyString(toadd->GetFileName()))
{
delete toadd;
return false;
}
这个封锁手法尤其狠毒,并非封锁你的搜索,而是如果你的文件信息内有这些关键词,那么文件共享消息就不会被发送到服务器上,如同这个文件没有被共享一样。这样既没有用户会发现被封锁的事实(因为有少量其他客户端的数据会被检索出来),又能达到封锁的目地。
当然,贝壳理解VeryCD这帮人的苦心,毕竟他们还住在中国,不过估计从此后,贝壳和朋友的机器上不会装VeryCD了。

2008年9月22日星期一

百度、官员辞职和特供局

这几个东西有什么联系?其实没什么大联系,只是同样是没用的东西而已。
先说百度吧,大家可能不知道,google的一个原则就是“不作恶”。简单来说,不因为人为的理由而改变搜索结果。包括调整排名,屏蔽关键词等。google卖的关键词和百度不大一样,百度的是改变搜索结果,google则是在旁边显示广告栏,其显示特征明显不同于正常搜索结果。从商业角度来说,卖排名当然不如卖广告收入多,但是google的收入却比百度高很多。我以前也完全无法理解google费力坚持不做恶的理由,更无法理解为什么google的收入会远远超过百度,然而这次却明白了。大家应当听说某奶粉品牌300万搞定某搜索引擎的事情吧?当然,某搜索引擎否认了,我们也没有什么证据来说明这次的却是被搞定了。但是,百度卖排名是事实,卖关键词屏蔽也不是什么很难理解的事情吧?也许这次没有被搞定,然而在卖屏蔽却没有什么异议。然而如果这么说的话,那么百度,无疑就成为了遮盖大众知情权的帮凶了。当然,同样的问题google并非没有。google在进入中国后,和政府达成了协议。部分涉及国家的敏感词汇被封锁,大家可以看google最下方的提示。“据当地法律法规和政策,部分搜索结果未予显示。” 如果出现这个,就代表你的关键词有问题,部分内容被屏蔽了。作为进入中国的代价,我们理解这种事情。然而即使是这种程度的改变,依然被很多google的 fans质疑,认为google背叛了“不作恶”的原则。大家可以想想,一个搜索引擎受到国家约束而被迫改变结果尚且要引来怀疑。这时候,一个可以用钱买卖,结果改来该去的引擎,有用么?
因此我强烈的建议大家,使用google的搜索引擎。或者说,当你需要知道什么事情,怀疑什么事情,而这个事情又和强权的利益相关的时候,请使用google搜索引擎,因为百度可能被“买通”了。你不知道你搜索出来的“最有名”的培训公司是否真的好(这是我一个朋友的真实例子,他所在的培训公司就是打的百度排名,但公司有严重问题),你不知道你“搜索不到”有问题的词是否真的只是因为你个人的问题(例如这次的三鹿)。当你搜索到的东西其实只是一堆利益的集合的时候,我觉得这个搜索引擎也就没用了。
我当然不是对百度有意见,也不是对国产有意见。我本人就是中国的程序员,也有朋友在百度任职。然而中国的公义(好吧,这个词大了点)是不能因为利益而含糊的。说的更准确点,当你为了利益而含糊公义的时候,说不定你的儿女正在喝三鹿奶粉。
下面讲讲官员辞职。这次溃坝辞职的孟学农,其实是第二次辞职了。头一次是因为在北京,非典处置不力而辞职。这次又在山西,因为溃坝辞职。中国官方的解释是,孟学农的两次辞职为做太平梦的太平官敲响了警钟。然而贝壳要问,为什么辞职了两次?
大家理解官员辞职,基本就是,辞去职务。辞去后干什么?我们并不清楚。当然,辞职后又重入公职,说起来也不是不可以。但是,这样真的可以么?有人可能说说法规并不反对,但是我想问,一个已经有了明显的处置不力前科的人,为什么那么容易的又进入了公务员队伍呢?难道中国对农村户口的人进入公职防范的那么严格,对有处理问题不力前科的人反倒不必防范?辞职,下台躲躲风头,学学陈冠希,事情走了再出来,损失的只是以前积累的名气,是不是太容易了?进一步说,这次因为三鹿辞职的某些官员,下次又准备在哪里任职呢?大家明天又想喝些什么呢?
当然,还有一个更坏的理由,就是这些人因为没有背景,被屡次作为代罪羔羊。为什么说比辞职重新担任公职更糟糕呢?因为这代表真正应当负责的人屁股都不必动。辞职重新担任官职,好歹还有躲风头的时间,还要损失自己的名气和政绩。作为官员,在做事情前还是要想想这个代价的。然而花钱弄一堆不做事的替罪羊养起来,出了问题就让他们辞职顶罪,然后再给他们重新安排一个替罪羊的职务。这连想代价的时间都省了,反正这帮人的工资是国家支付,人民出钱。拿人民的钱来糊弄人民,没有比这个更好做的决定了。如果真的碰到这种情况(当然,这只是贝壳的猜测),那官员问责机制就会从“没什么用 ”彻底变成“什么用都没”。
最后一个,讲讲特供局。特供局的意义相信大家都清楚,就是给皇帝弄贡品的单位。这种局弄出来,肯定要被大家骂的。当官的只管自己吃喝好,不管老百姓生死。有人还这么说,只要特供局存在一天,当官的就不会真正在意老百姓的生死。只要产品安全到家了,总理可以在大街上随便买个番茄吃了。在这里贝壳不打算讨论政府和食品安全问题,只是想问另外一个问题。让一帮人浮于事吃拿卡要的家伙负责另外一帮同样家伙的食品,有用么?
以前给皇帝拿贡品的时候,下面的经手太监经常吃拿卡要雁过拔毛。只要给一定的好处,就可以打上贡品的标签。多收了是朝廷付钱,好吃的自己先吃掉,反正皇帝也不会来查——要是他有这个空,肯定自己来管了。最后经常会出现朝廷的采买太监吃的比皇帝都好的现象。同样的事实难保不会再特供局出现,毕竟东西经过特供局后一般不会再交付其他部门检查。那特供局完全有可能检验一下,扣下一些特别好的,收钱定贡品。反正只要交上去的东西没问题,就没人问他们负责。

2008年9月21日星期日

紧急修复

贝壳周四的时候收到消息,烟台的系统崩溃,于是在24小时之内走了一趟天堂和地狱间的旅行。
开始的时候,贝壳在查一些业务有关的资料。期间和一个同事开了几句玩笑,但是发现他一脸便秘的样子,和我说没空。贝壳很郁闷,怎么这么没面子?过了几分钟,事情就发展成贝壳也一脸便秘的没胆子了,原因是烟台的系统崩溃。由于远程无法连接,只能让客户去机房重起整个系统。可重起后也没有反应。于是贝壳怕了,马上通知了老板。老板马上做了决定,要我们当时飞去烟台,并且在几分钟内给我们搞定了机票。于是在贝壳头处理紧急问题的时候,就受到了"飞机-出租- 零反应"的待遇。
中间首先要感谢一下给我们做Oracle技术支持的纪锋老师,这次如果不是他的大力协助,恐怕问题不会这么快解决。我们在零时间往烟台赶的时候,纪老师也马上打车往机场走。我们是五点接到的问题通告,五点半就联络好了各种问题,乘公司的车子往机场走(主要怕下班高峰不好打车)。六点多点的时候,我们拿到了登机牌,去做安检,然后顺便讨论起问题原因。当时认为基本不可能是软件问题,因为软件问题重起后基本都可以解决,也不会弄的机器停机(这个最终被检验是正确的)。可能是维护问题或者硬件问题。按照机器安装时间来计算,硬件问题的可能居多(系统才刚刚交付几个月)。
飞机是8点50在烟台落的地,落地后我们心急火燎地坐出租往报社赶。车刚出机场,收到一个消息,问题消失了。我们顿时安心很多,要是问题继续出现导致更严重问题,怕我们全都吃不了兜着走。现在,虽然我们还要去找出根本原因,可总比被客户拷问着检查系统来的好的多。到了报业后,我们先检查了系统。第一个被发现的问题是备份机已经满了,怎么会这样?系统的设计容量是三年500G,按照现在的数据量估计,最高不会超过30G,可备份机上足足有100G的空间!我们倒推了数据,发现备份要用140G以上的空间。怎么会这样呢?
原因我们没有找到,不过按照纪老师给出的原因,是备份的时候大量的归档日志造成了数据量暴增。但是备份暴增怎么会造成系统不能访问呢?贝壳陷入了奇怪的感觉中。虽然直觉上觉得就是这个理由,但是实际上却无法确定。按照我和同事说的话,如果用这个理由来说服我,是无法说服的。但是如果在目前让我给出一个理由,恐怕只有这个了。当天比较晚了,因此没有进一步分析,只是让纪老师调整了备份策略就去睡觉了。
第二天,贝壳仔细检查了所有的系统日志,找到了真正引发错误的理由。Linux9号错误,原因是因为文件无法访问。可是,究竟为什么造成9号错误呢?又是为什么导致重起后错误不消失,过后错误又莫名消失呢?进一步分析日志找到了这后两个问题的理由,客户重起节点1未完成时,直接重起了节点2。RAC似乎在所有节点同时失效后无法自动重连,即使重起也不行,必须重起客户端。最后按照数据倒推,认定问题在本地磁盘耗尽上。只是开始为了检测数据库备份,执行了 crosscheck,释放了部分磁盘空间,因此查不出来。
从这次事故恢复来说,最大的问题在于客户那里没有人及时进行系统维护,最终导致了磁盘耗尽。因此说做一个系统简单,然而要长期维护系统,恐怕就没这么简单了。

2008年9月15日星期一

中国又出事了

这次是毒奶粉,08年真是多事之秋。贝壳分析了下,估计会进入世界十大事故的行列。
有这么严重么?有。到底有多少奶粉有毒?有多少流入了市场?有多少没有追回?当局能统计出来么?统计出来你信么?如果统计不出来或者你不信,那么我说有可能被做成了月饼进了你的肚子,或者被北京的残奥运动员吃了,你信么?
三鹿的销售体系,是很难统计出最终消费者的,因此我们对比台湾统计的结果。台湾从三鹿进口了有问题的奶粉1000袋,有600袋上下剩余,400袋上下销售,其中没有一袋是进入婴儿市场的,全部被做成食品进了大众的肚子。按照这个比例计算,三鹿出问题的没出问题的奶粉,到底有多少被做成食品,进了你我的肚子呢?谁也说不清楚。月饼,面包,冷饮,这些我们每日要吃的东西里面到底有没有问题呢?谁也不好说。毕竟现在食品成本上升,使用廉价的奶粉来替代昂贵的原料,恐怕是很多企业的第一选择。
这次风波问题更大的在于,还有很多残奥运动员在北京,他们的食品呢?你我当然知道,他们是吃不到廉价的奶产品的。问题是,他们是否愿意相信呢?如果这个问题没有一个很好的解答,恐怕中国这次的运动员餐赔本卖吆喝要变成赔本卖骂名了。
还有这次问题的发生原因,按照三鹿本身的说法,是不法分子造假。那么不说出问题的总数是多少,三鹿说过,召回有问题奶粉700吨。就贝壳看到的资料,原奶和奶粉的质量比大约是8:1。按照这个比例,三鹿有问题的原奶大约是5600吨。这么大量的奶就完全没检查?如果说是部分有问题,到底多大比例?比例低了是不会造成影响的。高了,高了还是得问有没有检查。还有,一个企业,不同产品使用的奶源不一样么?如果一样,为什么只有特定产品出现问题?如果不一样,这会造成成本的升高,为什么不一样?
另外,三鹿集团在这次的危机公关上有严重问题,石家庄当地政府加重了这个问题,而国家又放大了这个问题。三鹿集团说八月已经发现问题,开始召回奶粉。那九月初问题刚发现的时候,发表三鹿奶粉产品质量没有问题的声明是怎么回事?奶粉召回,有没有通知消费者?(也许是中国没有这个惯例,我和一堆朋友说的时候,他们都一脸惊诧,召回产品要通知消费者么?)有没有统计影响面?国家的质管部门呢?免检产品是否真的不需要抽检?那质检是怎么选免检的?口碑?口碑怎么评价?质量历史?企业质量没有问题历史才是常规,有问题历史的要着重查。如果免检产品真的免检,那质检部门有什么用处?如果食品质量有问题造成生病归卫生部门管,那么飞机质量问题造成坠机是否归国安部门管,汽车质量问题造成车祸是否归交通部门管?质检证书造假是否应当归公安管?
实际上,这次事件引发的最严重的问题,在于政府,质量检验体系失去了共信力。根据调查,92%的人不会再选择三鹿的任何产品,6成以上的人对政府质量体系持不信任态度。那么,这次政府说没有对你们造成影响,你们信不信?下次,政府说你们的房子没问题,你们信不信?如果不信,大家准备吃什么?喝什么?

2008年9月10日星期三

语言的对比

最近贝壳在学(或者准备学)三门语言,python,ruby,scheme。全部都是高级抽象语言,都是脚本语言。加上贝壳本身已经非常熟悉的几门语言,C/C++,Java,Asm,bash,贝壳这次是正宗的要"精通"七门语言了。当然,如果加上勉强会用如vb这些,十多种也不成问题。做为一个学了多种语言用了多种语言的程序员,我想写一下自己多这些语言的认识,作为后来者的参考。
首先是C,当然,是不包含C++特性的C。这门语言可以说是贝壳所见的语言中,最强大最广泛最具备生命力的语言。其他任何一种语言说到混编接口,基本就是C语言接口。C语言也可以模拟非常多的特性,COM,C++,都可以用C模拟。当然,模拟的复杂程度是另外回事情。并且可以编写其他语言的解析器/虚拟机,而这点来说其他语言很难做到。其实本质上说,C语言就是跨机器跨平台的万能汇编语言,所以才具备这些强大特性。C的核心思想是指针,其整个语言构架都是基于指针的。通过指针,我们直接操纵着内存地址的读写。当然有利必定有弊,C语言太难掌握,效率太低了。使用C写代码,就是和机器打交道。你得控制数据的读出写入,控制设备的初始化,控制内核交互。根本上说,一个强大的C程序员,并不强大在算法和创意上,而是强大在对系统的方方面面的了解上,强大在对基础原理的掌握上。
其次是C++,说实话,这语言有点高不成低不就。如果要掌握系统的方方面面,他不比C。如果要抽象要构架要快速要敏捷,他比不上所有的抽象语言。但是 C++的长处在于在系统的层级上引入了算法抽象,增强了编码效率。如果你确定需要在系统层级上编程,又不高兴从C的角度去写。C++可以极大的增加你写代码的速度。当然,他的弊端就是对底层的掌控力和抽象实现的复杂程度。C++(我指的当然是包括STL和Boost的)的长处在系统层级,所以你写代码的时候必须了解抽象的实现方式。然而抽象实现的越强大(例如boost的share_ptr),其底层的机制就越复杂,你掌握的时间也越长。而如果不了解底层,往往会发生很多很奇怪的问题。例如smart pointer的问题,在其他语言中,这是语言系统的bug。而在C++中,则是你自己的问题。因此,想要真正玩好C++的程序员,必定首先是一个C高手,而不是拿着C++的OO特性把C++当普通OO语言的人。
再次是Asm,这种语言可以说没有什么生命力,因为他变化的太快了。一旦硬件构架改革,汇编就要调整。而且抽象层级不够高。除非你正好做操作系统底层,编译器优化和系统破解,否则最好不要考虑这种语言。这种语言的核心构架是寄存器。
然后是java,当然,还有很类似的C#。这类语言开始,语言的抽象层次提高了,因此可以提供反射(python中叫做自省)。反射是高级语言中必要的特性,然而C/C++并没有提供,原因么则必须说一下编译型和解释型语言。一般语言分为编译型和解释型,编译型的代码成型后只需要相关的库支持(其主要目的是代码复用减少复杂度和内存消耗),而解释型的语言需要解释器。如果仅仅从方便使用角度说,解释型语言远远不如编译型(因为要单独安装解释器,当然,像 bash这类怪胎就表说了),而且解释型的语言运行效率仅有编译型的1/10左右。然后当今语言界,编译型语言远远比不上解释型使用广泛。其根本原因有三个,一个是编译过程,一个是反射,最后一个就是内存管理。
如果读者有编译代码的例子,应当知道,除非在同等的条件下,否则编译C++代码是很麻烦的事情。而配置同等的编译条件则彻底失去了C++跨平台的意义。因此C++在不同平台下反复编译的时候,需要考虑大量的问题来达到跨平台的目的。往往这种事情麻烦到需要作者亲自指导编译的地步。如果一个软件,发布的时候声明可以跨平台,然而使用前需要作者指导用户做一堆繁复的操作。估计这个软件只会受到专业用户的欢迎吧。
所谓的语言编译,其实需要经过两步,编译和链接。编译的主要目的是将每行代码翻译成对应的汇编语言,而链接则是将符号引用转换为地址引用。举例来说,我使用了一个变量str来存放一个字符串,这个变量str就是一个符号。我需要在上文中声明(declear)这个变量,以便编译器在编译的时候代换这个符号对应的内存地址,和理解如何使用这个符号(关于上文没有声明的情况下的错误,请看"向下引用"特性)。我使用str的第四个字符的时候,str[3]就会被翻译到固定的内存地址或者基于基址的偏移,机器完全不用理会str是什么。而这点,则是反射实现的最大障碍。反射可以提供一个对象是什么,有什么的信息,并且可以动态创建对象。有了反射以后,才可以实现序列化,分布式等等高级应用。而C类语言在编译后失去了符号是什么的信息,只剩下一个名字,链接后连名字都没了。这种情况下,你怎么知道一个对象是什么呢?
Java/C#可以提供反射,因此属于解释型语言。但是他们又不属于完全的解释型,而是解释型的一个特殊分支,中间代码。中间代码型的语言,需要编译,执行的时候又需要解释器。看起来没什么好处,可是在支持反射的基础上,大概可以以C代码1/2的速度运行,比纯解释快多了。原因何在呢?我们可以看看 C++为什么不支持反射。反射是保存针对执行结构的数据并且提供交互,而C++则在编译时生成后丢弃了这些数据。因此,理论上说只要保存了这些数据就可以实现C++的反射。这就是中间代码语言所做的事情。当然,考虑到跨平台特性,编译的结果并不是汇编代码,而是类似汇编的代码(Java叫P代码,C#叫 IL)。后JVM直接执行P代码,C#则通过引擎编译IL到本地代码。因此JVM执行的时候效率基本恒定,而C#初次执行速度慢,后来则是比C++慢不了多少。
最后就是Java/C#的最强特性,动态内存管理。使用这个特性,可以使得程序员彻底的从内存分配和管理的泥潭中脱身出来。白痴的程序员写的程序可用,强大的程序员写程序的效率提高。可是成也萧何败也萧何,内存不到底不回收,又有额外的内存开销,结果导致系统的缓存命中率下降。我们平时觉得Java类语言执行慢最大原因在这里,半解释才不是根本原因。
因此这些语言的特点就是中间代码,其核心思想是对象。这类语言的最大特性就是抽象和构架,使用强大的设计模式,将大型问题拆分成多个小型问题解决。在解决问题的时候,其代码量并不比C++少多少。
再然后是python和ruby,当然,某种程度上还有bash,只不过他弱了点。这类语言的核心思想是抽象数据,例如字典,字符串等。bash是围绕着字符串处理设计的,python是围绕着集合设计的。这些语言解决问题的速度非常快,但是模块化特征和抽象特征相对弱。一般情况下,和C++相比,解决问题的速度大概是1:5,代码量则是1:3。

2008年9月9日星期二

程序员的几个分类

程序员有高下之分,可高下怎么分?到底什么是高程?程序员要完成哪些任务,怎么评价是否完成的很好?
下面由贝壳同学来胡诌一下他的个人感想,以下的程序员都指商业程序员。当然,爱好者可以类推。
首先我们先讨论一个风马牛不相及的问题,程序值钱么?废话,人家账单大门兄都已经是世界首富了。可贝壳认为,程序不值钱,算法也不值钱,如果说值钱的话,就和软件光盘上面的光盘一样,是个成本性质的辛苦钱。软件里真正值钱的是软件的思想,我们大可以想象一下,写一个让人想穿脑子也想不出怎么用的程序——当然,会写的程序员想穿脑子——然后想想值钱不值钱。大家就会了解到,其实程序员,编程本身,是和装配线上的装配工一样的低附加值行业。不同之处在于,不同培训程度的人劳动生产率不同,而且生产率差异远远大于普通行业而已。好的Coding比差的生产率会高上数倍,而且很难多找几个差的来替换好的,水平不足。但是,做Coding,无论做多快,其价值只会线性增长。
那么为什么软件也被称为今年来发展最迅猛的产业呢?其实关键在于软件业让人可以实现以前无法实现的一些想法。例如,以前不会有人能做到让地球上所有的人 (好吧,是大多的人)坐下来一起讨论一个事情的方法。那么,今天我们的技术已经可以做到。有个人针对当前的技术,设计出一个很好的让所有人坐下来讨论事情的系统。包括一个BBS,带自动翻译系统。在线视频会议中心,可以附加购买在线翻译。一个邮件列表,带存档功能。一个文档编辑和管理系统,带同步编辑,版本管理和资料索引。当我说出上面这堆东西的时候,可能有的人已经晕了,当然,六牙四皂小姐估计已经不继续看了。不过做过一些时间和电脑有关商务的童鞋应当都理解这些东西的意义,并且可以想象这些东西带来的便利。在线翻译的支持系统,邮件列表存档系统,同步编辑,版本管理,资料索引,这些都是技术。尤其是资料索引和自动翻译,更是技术的巅峰之作。可是如果不是结合起来让客户用的舒服,这些东西有价值么?
软件业的价值在于将技术转换为客户的满意,并且最终转换成客户的钞票。越成功的规划,越能满足更多的客户,并且让他们付更多的钱。从这个角度讲,不论软件做的怎么样,微软的规划是全球一流的。同时,能促进这个过程的人,才是具有价值的。不过遗憾的说,到这步基本就不是程序员,而是CTO了——
程序员的最高价值,在于根据技术和行业,判断应当发展什么技术,采用什么框架,从而低成本,高效的做出让客户满意度最高的系统——而且不是一个系统,而是一堆。这有两个非常苛刻的要求同时存在,对于技术非常熟悉,视野开阔感觉敏锐,否则怎么去感觉技术的价值,判断应当研发的技术?就这点而言,许多程序员在超过30岁后往往都可以在熟悉的领域内做到,有条件的话大概可以在多数领域做到。然而最麻烦的在于,做这个事情的时候,你必须熟悉客户,熟悉客户需要什么。这点往往是不可能的!客户是不可理喻的!技术不是万能的!要知道,使用最好的技术,设计你最喜欢的系统,往往是客户最讨厌的事情。行业客户如此,通用客户更如此。
如果上点你做不到,自然有高水准(也许吧)的人来做,那么你可以做次之的工作。什么呢?执行他们制定的方向。上层的人会告诉你应当发展什么技术,采用什么框架。现在要求你——不用会——能够整合实现这个目标。说明白点,你可以招,但是要求能留下人,低成本。你可以培训,但是要求能做事。你可以研发,但是要求好用。你可以买,但是要求低成本。如果你能实现这些目标,那么同样,你也是有价值的。
这个层次的程序员往往是Project Manager(当然,很多PM根本不是程序员)/Team Leader/Core Programmer。对于他们的要求往往是兼顾技术,行政和人事的。他们需要能够组织研发,积累技术,产生产品。很遗憾的,个人编程能力往往又是次之。不过程序员是很艺术化和个性化的一群人,在这个位置上的人,如果没有相当的技术水准,很难镇住下面的人,用普通管理人员来管理程序员的结果往往是给程序员联合起来耍。因此一般情况下也需要了解大致的程序,并且最好有一定技术水准。
最下面一个层次的人,基本就是能够实现程序,会用上面决定的框架和技术,编码效率高,工资要求低——别的没了——
当然,以上是从职业分工来讲一个职业程序员的价值的,你可能说上面没道理,贝壳乱讲。不过事实是,职业的情况下就是上面的状态。当然,从业余爱好者,技术研发者来说,程序员又有另外一种不同的分法。
第一个层次,是刚刚学会技术。能够使用某种特定语言,按照一些例子编写一些程序。实话说,按照贝壳的程度,入手一般语言做到这点不超过7天。然而很多人会徘徊在这个水准无法进步,原因在于——他们能写程序了,而且写了能用。从这个引申开来,顺便说一下,程序员的进步是个很吊诡的事情。一方面来说,要勤于钻研技术才能进步。但另一方面来说,如果不够懒,是很难有足够动力学程序的。因此,好程序员都是勤于钻研的懒汉。
第二个层次,学会了使用框架,并且能够设计一些中度复杂的系统,开始接触第二语言。和初级程序员不同的是,他们能实现一套完整的系统,而不是一个个零散的功能了。这要求他们了解框架,什么时候触发什么函数,系统间怎么互相通讯。并且,有水准的还可以写一些小型的框架。
再上一个层次,了解软件工程对软件的意义,能够跨多种语言编程,灵活使用设计模式,能够设计复杂框架,习惯文档化。和上面的区别看起来不大,不过是能多用几种语言,朴素的设计被设计模式所规范,设计的框架复杂化,并且会写一堆无聊的文档。不过从这步开始,程序员开始了迈向大道的第一步,在这个层次以下的只能算爱好者。无论是研究技术,研究数学理论,还是什么,规范化都是必须而且是非常重要的。我们很难想象一堆工程师,各画各的图纸,最后房子还建的多快好省的。同样,作为高级程序员,头一步就是学会和别人合作。使用设计模式的规范进行设计,使用文档描述系统,可以跨越多种语言协作,了解多种语言思想,这是必须的。
再上一个层次,就已经不是程序员的境界了。作为程序员,上个水准已经到头了。更强的程序员意味更规范?效率更高?那是八级钳工!作为程序员,你可以不认识英文,你可以大字不识一个,然而你必须是个数学高手(其实现在数学高手大字不识一个几乎不可能)。作为程序员的巅峰,你可以很轻易和他人协作,使用合适的语言,然而无法规避的是对问题的抽象描述和求解。在贝壳作为程序员的这段时间里,无数次的碰到数学问题,有些往往是大学里面我们所不屑一顾的。例如蒙特卡洛法,拉格朗日乘子算法,这些在程序里面都有很重要的应用。有的时候更要自行抽象数学模型,并且设计满足时间限制和空间限制的解法。能够抽象问题,解决问题的,才是真正的技术系的高手。
OK,上面,贝壳从两个方面(工程和技术)论述了程序员的高下之分,作为他胡诌的结果,他目前的水准大致是——不知道。并且很遗憾的告诉大家,目前贝壳能看到的就这么多,再上面是什么样子——要么等到了再告诉您?

2008年9月6日星期六

C++下的Variant

所谓C++语言,是一种强类型语言。即是说,C++种的某个变量,在使用时类型是已经确定的。这个并不是设计者的喜好或者是偏心,而是C++中的变量都会被翻译成准确的内存地址和大小,如果类型不确定是不可能处理的。但是在事实中,我们经常要处理一种"变类型"。例如,我们可能需要解析表达式,这个时候我们可能用一个或者两个栈来解决这个问题。可栈里面塞的东西就精彩了,对象,函数,数据,都在里面。这时候,如果是python,我们可以直接用list,他是弱类型的。但是C++怎么办?
一般来说,我们会使用Variant类型来解决这个问题。这是C++面对对象机制和算子机制所派生出来的产物,能够让用户自行定义对象的行为。如果一个对象,可以表现的像这个又像那个,那不就解决问题了?因此在COM中就有一个variant。不过贝壳看过机制,是一堆东西的集合,非常的不美丽。今天贝壳又看到一个variant的实现,漂亮多了。
废话少说,上代码。
#include
using namespace std;
#include
using namespace boost;

int _tmain(int argc, _TCHAR* argv[])
{
any a;
a = 10;
printf ("%s: %d\n", a.type ().name (), any_cast(a));
a = 10.5;
printf ("%s: %f\n", a.type ().name (), any_cast(a));
a = string ("str");
printf ("%s: %s\n", a.type ().name (), any_cast(a).c_str ());
return 0;
}
当类型错误时,出现bad_cast exception。