无聊(5)

2010/08/07

以前总会以为,每天进步这么慢,究竟该到何年马月才能成长起来..这不知道算不算是一种焦虑,总之我想快点撕下身上菜鸟的标签,因为,在号为Cheat House Is Not Available的这个国度里,不是牛人,是会被世俗烙上”不成功罪”的,而我并非多么清高的人,难免沾上些许俗气.好在,今天看完了<把时间作为朋友>,才知道,原来这不过是所谓的instant gratification的心理,每个人内心都有欲望马上能够得到满足的毛病.我想,唯一能求助的便只剩下耐心了.人们常常说,捷径是两点间的最长路径,想来也是有点道理的.急功近利是一种风险高于回报的举动,人不可能一口吃成胖子.渐渐的,我明白了,其实这就像学习C++这门相当elusive的语言一样,,没有看上十几本书,就认为自己无所不晓,恐怕得好好三思.整天意淫着能够遇见一本秘籍,学的一招半式便能无敌于天下,这太不现实了.真正的进步,需要坦然,从容,而且努力,它们能带来效率,但是,比起do the thing right,我们更需要的是do the right thing.

能讓人眼睛一亮的組,他們一開始就花了比較多力氣確定要做什麼才是對的,一旦確定後,雖然做的事情可能比較小,但因為做了「對的事」並且也把事情「做對 了」(do the right thing and do the thing right),成果就能讓非常人印象深刻。

正如vgod所说的那样,尽管Do the right thing and do the thing right是非常简单的概念,但即使在天才云集的MIT,能够理解的,也不过只有少数.

最后,真的很想念我的好友们,”从现在开始我和他们就已经走上了不同的道路,可能20年后我只能指着电视对我孩子说“老爸当年和他是同学”——我的名片上印着公司和头衔,而他的名片上印的是他的Wikipedia页面。

我一定要努力,do the right thing.

ps. 我开始实习了,暂不清楚公司对于员工发表博客的原则,所以便先做缄默吧.几个月内可能都不会更新了,谢谢关心. :p

Posted in My Thoughts1 条评论

宏的一点小技巧

2010/07/19

最好用的调试方法肯定是printf了:-),比如说,我们可以这么打印z的值:

z = x + y;
printf ("z = %d\n", z);

然而,我们常常只对printf的部分感兴趣,因此,我们可以使用#ifdef宏

z = x + y;
# ifdef DEBUG_MODULE_A
printf ("z = %d\n", z);
# endif

但这又引出了一个问题,每次都要写#ifdef未免有点cumbersome了.为了使代码轻松一点,我们可以这么写:

z = x + y;
debug_module_a ("z = %d\n", z);

或者:

debug ( module_a , "z = %d\n", z);

写的规范点,代码就是这个样子了,

z = x + y;
DBG(("z = %d\n", z));

DBG的定义为:

# ifdef PRGDEBUG
# define DBG (x) printf x
# else
# define DBG (x) /* nothing */
# endif

其实,上面的做法都是很常见的.但在添加模块后,调试宏应该怎么写就确实能够区分出做好的好坏了.

首先,为了简洁,我们当然还是希望打印状态时可以这么写的,

DBG (( MOD_PARSER , "z = %d\n", z ));

这里的模块编号MOD_PARSER也可以是MOD_SOLVER或者是M0D_PRINTER,因为多了这么一个参数,printf的方法便失效了,所以,我们还需要额外定义一个状态输出函数:

void dbg_printer ( int module , const char *fmt , ...) {}

宏的定义为:

# ifdef PRGDEBUG
# define DBG (x) dbg_printer x
# else
# define DBG (x) /* nothing */
# endif

使用如下:

z = x + y;
DBG (( MOD_PARSER , "z = %d\n", z ));

我们知道,既然需要动态地添加模块,那么程序往往是会提供一个配置文件的,比如说一个名为”debug_modules.conf”的文件,内容如下:

ADD_MOD (0, PARSER )
ADD_MOD (1, SOLVER )
ADD_MOD (2, PRINTER )
//后面可能是一些不必使用的模块配置
UNNEEDED_MOD(3, PREPROCESSOR)

它表示程序将加载的模块.这时又引出了一个问题:在调试相关的代码中,我们需要提供一个函数读配置文件和所需模块所在的行号,这时代码也许就变成了:

# define DBG (x) dbg_set_pos ( __FILE__ , __LINE__ ), \
dbg_printer x
static const char * file_name ;
static int line_num ;
void dbg_printer ( int module , const char *fmt , ...) { ... }
void dbg_set_pos ( const char * file_name , int line_num ) { ... }

这时代码就变得相当难看了,怎么才能优雅起来呢?一个不错的方法是,使用宏连接:定义一个头文件”debug.h”,内容为:

...
# define ADD_MOD (num , id) MOD_ ## id = 1 << num ,
enum _debug_modules_t {
# include "debug_modules.conf"
};
# undef ADD_MOD
...

在调试代码中,直接包含该头文件即可,头文件内容将被预处理器自动转换为:

enum _debug_modules_t {
MOD_PARSER = 1 << 0,
MOD_SOLVER = 1 << 1,
MOD_PRINTER = 1 << 2,
};

Tags: Posted in Programming我抢沙发

开始实习了

2010/07/14

终于开始实习了,小组组长是很好人的一面面试官,导师是好安静的小白..需要开始努力了.带了几本书到深圳,不知道有没有时间看,虽然有些是看过了的.

  • APUE
  • UNP v1~v2
  • TCP/IP v1
  • CLRS
  • CSAPP
  • Expert C programming
  • Design Patterns
  • More Effective C++
  • Inside The C++ Object Model
  • Understanding The Linux Kernel
  • Linux 内核完全剖析
  • 程序员的自我修养
  • 编程之美

以前总以为 ,跟着牛人一起学习,耳濡目染肯定进步快些.最近总算有点明白了,其实牛人并没有那么多的时间照顾菜鸟;再者,牛人也是从翻了无数经典的菜鸟成长起来的.既然有如此多的大神,愿意慷慨地将自己的时间花在著书立说之上,用深刻,全面,系统的知识指引着我们,那么,多看好书,成长的速度不是应该更快一些吗..这总是没错的.

P.S. 今天狠狠地蹭了组长一顿饭.

P.S.2 电脑前一天坏了,酒店网速又好慢..洗洗睡吧.

Posted in Programming我抢沙发

资源的分配和释放

2010/07/09

资源需要分配,同样也是需要销毁的.<程序员修炼之道>中原则35,”finish what you start”说的便是这个道理..

下面是一个很糟糕的程序,它打开文件,读取信息,更新字段,然后写回结果.’

FILE *fd;

void readObject(const char *filePath, Object *obj)
{
	fd = fopen(filePath, "r+");
	fread(obj, sizeof(*obj), 1, fd);
}

void WriteObject(Object *obj)
{
	rewind(fd);
	fwrite(obj, sizeof(*obj), 1, fd);
	fclose(fd);
}

void updateObject(const char *filePath, int weight)
{
	Object obj;
	readObject(filePath, &obj);
	obj.weight = weight;
	writeObject(&obj);
}

乍看起来,程序可以工作得很好.但是,因为共享fd,readObject和writeObject却紧紧的耦合在一起了.可以很轻松的举一个例子,证明两个函数耦合带来的问题.比如说,要求仅在weight大于100时才做更新,那么updateObject函数将被修改为,

void updateObject(const char *filePath, int weight)
{
	Object obj;
	readObject(filePath, &obj);
	if(weight > 100)
	{
		obj.weight = weight;
		writeObject(&obj);
	}
}

这就引入了一个问题,WriteObject在某些情况下并不会被调用,以致于文件未被关闭,这将浪费系统的资源.

一种解决方案可以是

void updateObject(const char *filePath, int weight)
{
	Object obj;
	readObject(filePath, &obj);
	if(weight > 100)
	{
		obj.weight = weight;
		writeObject(&obj);
	}
	else fclose(fd);
}

这看起来貌似不错,不管新的weight是多少,文件总是会被关闭..但其实,这是一个非常糟糕的修改,readObject,writeObject和updateObject三个函数由于共享fd耦合在一起了.简单的web服务器中的代码就是如此,爬出一个陷阱,却掉进了一个更大的坑,这不得不说是种悲剧.

要解决这个问题,可以遵守一个原则,资源的分配和销毁这一责任由同一个函数承担,打开和关闭在同一地方,而且配对出现.重构如下:

void readObject(FILE *fd, Object *obj)
{
	fread(obj, sizeof(*obj), 1, fd);
}

void WriteObject(FILE *fd, Object *obj)
{
	rewind(fd);
	fwrite(obj, sizeof(*obj), 1, fd);
}

void updateObject(const char *filePath, int weight)
{
	FILE *fd;
	Object obj;

	fd = fopen(filePath, "r+");
	readObject(fd, &obj);
	if(weight > 100)
	{
		obj.weight = weight;
		writeObject(fd, &obj);
	}
	fclose(fd);
}

这样做还有一个附加的好处–去掉丑陋的全局变量FILE *fd.

异常下的资源分配和释放

在使用异常时,很容易写出这样的代码,

void doit()
{
	Node *n = new Node;
	try
	{
		// do something
	}
	catch(...)
	{
		delete n;
		throw;
	}
	delete n;
}

注意到,n是在两个地方被释放的,一次是在程序正常路径上,一次是在异常处理中.这显然是违反DRY(Dont repeat yourself)原则的,也许会像前面一样导致维护问题.

解决上面的问题,其实可以用到3种小技巧.一是,利用C++的性质–局部变量在作用域外会被自动销毁.如果允许的话,我们可以将’n'从指针变为栈上的实际的对象.

void doit1()
{
	Node n;
	try
	{
		// do something
	}
	catch(...)
	{
		throw;
	}
}

这样,不管是否抛出异常,C++都将自动析构Node对象.

当然,由于各种原因,用对象替代指针的做法可能是不被允许的.变通方法很简单,我们可以将指针包装在另外一个类中,

class NodePointer
{
	Node *n;
public:
	NodePointer() { n = new Node; }
	~NodePointer() {delete n; }
};

void doit2()
{
	NodePointer n;
	try
	{
		// do something
	}
	catch(...)
	{
		throw;
	}
}

最后一种方法是直接利用C++库中的模板类auto_ptr,它将自动包装动态分配的对象.

void doit3()
{
	auto_ptr<Node> p (new Node);  // 用p->访问.
	try
	{
		// do something
	}
	catch(...)
	{
		throw;
	}
}

Tags: Posted in Programming我抢沙发

我看见的那份C++

2010/06/23

园子里语言之争又热了起来,我想不知天高地厚的说一下我所理解的C++.

首先应该定下一个论调,C++之父Bjarne Stroustrup在谈及C++的设计和演化时说到,”计算机和程序设计语言可以被当做是一种艺术性的工作,但审美主义因素应该是去辅佐或提升其有用性,而不是取代或损伤它.”,这段真知灼见客观而不偏激,相信赞同的人还是多数的.对一门语言,无论是拥护还是批判,出发点首先应该是爱之深或是责之切,光是动动嘴皮,挑起一滩浑水,是很不负责的.而在两种语言之间,比较很少是公平的,更多时候纯粹是无意义的.要做到公允,评论者应当不偏不倚,但局限于个人的认识,我们总是无知且不自觉地偏向于某个特定的领域,某种风格的程序设计或是程序员社区中的某种文化.

对C++的攻击主要集中在C++的存储管理上,而这些攻击更多地集中在C++缺乏一种有效的垃圾收集机制..也许,垃圾收集是C++所剩无几的可能加入还没有加入的特性了.其实,垃圾收集机制的确是一种很不错的思想,对用户而言,它能够简化设计,让库的设计和构造更加简单,而且可以排除掉许多错误的根源;对多数应用而言,垃圾收集比人肉存储管理更为可靠.但是,垃圾收集机制也不是尽善尽美的,它可能会导致

  1. 严重的空间浪费和时间开销,例如极端情况下的多米诺效应般的缺页导致的内存抖动.
  2. 垃圾收集机制的实现和移植将带来不可避免的复杂问题,例如某些垃圾收集方案可能提出了对对象布局和对象创建的一些限制.
  3. 一些隐含着服务中断的垃圾收集技术将使得C++不合适做许多低层次的工作,比如实时应用,设备驱动程序,操作系统内核上,而这却是C++的一大应用领域.

够了,已经有充分多的论据可以反对这种观点:每个应用在有了垃圾收集机制后悔变得更好.但是,也有足够多的证据可以反对另一种观点:没有那个应用因为有了垃圾收集机制而做得更好.Bjarne Stroustrup得到的一个结论是,”从原则上和可行性上,垃圾收集都是必须的.但是,对于今天的用户,普遍的C++使用和硬件,我们还无法承受将C++的语义和它的基本库定义在垃圾收集系统之上的负担.”当然,这段话是Bjarne Stroustrup在上古时代1994年说的了,我们并不清楚Bjarne Stroustrup至今是否仍旧这么认为的.至少,对多数程序员来说,垃圾收集实在是太有用了,它可以解放我们的心智,让我们的天赋脱离细节的纠缠,真正的将精力用在思想的历练和设计的考虑之上.

前面仅仅是一个C++新特性缺位的小例子,还有的一个例子可以是内置的数据库支持.实际上,为了公正的评价C++,我们应该看到它的设计目标:抽象.Java和C#的大行其道足够说明,这一设计目标本身是没有什么问题的.C++可以被看做是为了描述模块化而做的一种C的扩充,它并不是一门全新的试验性语言,而是一个几乎与C兼容的C的超集.Stephen Jay Gould说,任何纽带的连接力都比不上遗传链.为了兼容,C++继承了太多C的污浊,为了效益它也无法抛弃C的危险的丑陋的某些特性.这就使得C++的设计尽管解决了C的某些问题(Dennis M.Ritchile语),却又保留了C的一些问题.C++的过度设计和过度优雅多多少少有点宗教上的形而上了,它带给程序员的负担太多,而带来的新的思维方式又太少.比如说,D&E这本书让我觉得,为了抽象,C++被模板搞得支离破碎,更显艰涩难懂;而且C++的抽象一味强调了OO,这样的一味偏重将阻挡我们的视野,实际上,抽象就是为了看清本质,OO不是唯一的,function programming的想法也是相当不错的,这就限制了我们在更高维度上的思考,总的来说,我们可以说,C++迈出了很重要的一步,但对当代的设计架构来说,这一步又显得太小了.这样说也许有点强人所难了,但是,C++毕竟只是一门工具,尊重点说,它是无数牛人的智力产物,但是,时代总是在进步的,语言也是在进化的,我们没有必要抱残守缺,增长见识比起无端争论来的更为实际.

<The Practice of Programming>的作者Rob Pike在<Systems Software Research is Irrelevant>一文中痛斥当下操作系统的固步自封,不思进取.我不敢说C++是不思进取的,但是,正如Bjarne Stroustrup说的那样,C++语言和它背后的设计理念,编程思想本身是不会演化的,真正演化的是C++用户对于实际问题的理解,以及我们对于为了帮助解决这个问题而需要的工具的理解..对于C++这门语言来说,它已经太过庞大太过复杂了,所有的修改都需要经过极为谨慎的考虑,而特性的添加更是令人心惊胆颤.也许,C++已经开始走向了封闭,它只是一种选择的桥梁,远远不是一个完美的答案.

最后,上面的废话无非就是一种废话,还是希望自己用好C,用好C++.

Tags: Posted in Programming1 条评论