2009年3月17日星期二

钓鱼式攻击一点技术含量都没有?

某人Email给你:给我500块,预知你的未来,可以提前通知你,明天国王跟火箭队的比赛,火箭队会赢!

你当然不信啦,但是第二天的比赛,火箭队真的赢了,你说:巧合,巧合而已。

过了几天,他又email给你:给我500快,预知你的未来,可以提前通知你,明天湖人跟火箭队的比赛,湖人赢了。

你有点好奇,但你是绝对不会把钱给他的,但是第二天的比赛,湖人真赢了,你说:诶?难道是潜规则?

又过了几天,他又email给你:给我500快,预知你的未来,可以提前通知你,明天湖人跟七六人队的比赛,湖人赢了。

你开始猜,是不是真的啊?但第二天的比赛,湖人又赢了。

......

经过若干次循环后,你终于想要预知你的未来了。但是当你把钱汇出去没有得到答复时,你才知道上当了!

因为事情是这样的:

第一次发信8192封,一半说国王赢,一般说火箭赢;

第二次只给说火箭赢的4096人发信,分为2048人为湖人赢,2048为火箭赢;

第三次只给说湖人赢的2048人发信,分为1024人为湖人赢,1024为七六人赢;

......

很有可能他最终欺骗到的人只有10,但是这已经足够!

所以你可以说钓鱼式攻击一点技术含量都没有,不过可能你仍会因此被骗。

另外钓鱼者超多,请小心诱饵!

2009年3月15日星期日

UBUNTU不能启动vmware

很久以前装的vmware,今天想用,突然启动不了了。
进入terminal后输入sudo vmware
显示:

vmware is installed, but it has not been (correctly) configured
for this system. To (re-)configure it, invoke the following command:
/usr/bin/vmware-config.pl.

运行一下/usr/bin/vmware-config.pl

会重新编译,然后问题解决了,分析原因可能是因为自动升级的时候把内核升级了,所以要重新编译。

2009年3月14日星期六

Memcached源码分析(线程模型)

目前网上关于memcached的分析主要是内存管理部分,下面对memcached的线程模型做下简单分析
有不对的地方还请大家指正,对memcahced和libevent不熟悉的请先google之

先看下memcahced启动时线程处理的流程


memcached的多线程主要是通过实例化多个libevent实现的,分别是一个主线程和n个workers线程
无论是主线程还是workers线程全部通过libevent管理网络事件,实际上每个线程都是一个单独的libevent实例

主线程负责监听客户端的建立连接请求,以及accept 连接
workers线程负责处理已经建立好的连接的读写等事件

先看一下大致的图示:



首先看下主要的数据结构(thread.c):
  1. /* An item in the connection queue. */
  2. typedef struct conn_queue_item CQ_ITEM;
  3. struct conn_queue_item {
  4. int sfd;
  5. int init_state;
  6. int event_flags;
  7. int read_buffer_size;
  8. int is_udp;
  9. CQ_ITEM *next;
  10. };
CQ_ITEM 实际上是主线程accept后返回的已建立连接的fd的封装
  1. /* A connection queue. */
  2. typedef struct conn_queue CQ;
  3. struct conn_queue {
  4. CQ_ITEM *head;
  5. CQ_ITEM *tail;
  6. pthread_mutex_t lock;
  7. pthread_cond_t cond;
  8. };
CQ是一个管理CQ_ITEM的单向链表
  1. typedef struct {
  2. pthread_t thread_id; /* unique ID of this thread */
  3. struct event_base *base; /* libevent handle this thread uses */
  4. struct event notify_event; /* listen event for notify pipe */
  5. int notify_receive_fd; /* receiving end of notify pipe */
  6. int notify_send_fd; /* sending end of notify pipe */
  7. CQ new_conn_queue; /* queue of new connections to handle */
  8. } LIBEVENT_THREAD;
这是memcached里的线程结构的封装,可以看到每个线程都包含一个CQ队列,一条通知管道pipe
和一个libevent的实例event_base

另外一个重要的最重要的结构是对每个网络连接的封装conn

  1. typedef struct{
  2. int sfd;
  3. int state;
  4. struct event event;
  5. short which;
  6. char *rbuf;
  7. ... //这里省去了很多状态标志和读写buf信息等
  8. }conn;
memcached主要通过设置/转换连接的不同状态,来处理事件(核心函数是drive_machine)

下面看下线程的初始化流程:

在memcached.c的main函数中,首先对主线程的libevent做了初始化
  1. /* initialize main thread libevent instance */
  2. main_base = event_init();
然后初始化所有的workers线程,并启动,启动过程细节在后面会有描述
  1. /* start up worker threads if MT mode */
  2. thread_init(settings.num_threads, main_base);
接着主线程调用(这里只分析tcp的情况,目前memcached支持udp方式)
  1. server_socket(settings.port, 0)
这个方法主要是封装了创建监听socket,绑定地址,设置非阻塞模式并注册监听socket的
libevent 读事件等一系列操作
然后主线程调用
  1. /* enter the event loop */
  2. event_base_loop(main_base, 0);
这时主线程启动开始通过libevent来接受外部连接请求,整个启动过程完毕

下面看看thread_init是怎样启动所有workers线程的,看一下thread_init里的核心代码
  1. void thread_init(int nthreads, struct event_base *main_base) {
  2. //。。。省略
  3. threads = malloc(sizeof(LIBEVENT_THREAD) * nthreads);
  4. if (! threads) {
  5. perror("Can't allocate thread descriptors");
  6. exit(1);
  7. }
  8. threads[0].base = main_base;
  9. threads[0].thread_id = pthread_self();
  10. for (i = 0; i <>
  11. int fds[2];
  12. if (pipe(fds)) {
  13. perror("Can't create notify pipe");
  14. exit(1);
  15. }
  16. threads[i].notify_receive_fd = fds[0];
  17. threads[i].notify_send_fd = fds[1];
  18. setup_thread(&threads[i]);
  19. }
  20. /* Create threads after we've done all the libevent setup. */
  21. for (i = 1; i <>
  22. create_worker(worker_libevent, &threads[i]);
  23. }
  24. }
threads的声明是这样的
static LIBEVENT_THREAD *threads;

thread_init首先malloc线程的空间,然后第一个threads作为主线程,其余都是workers线程
然后为每个线程创建一个pipe,这个pipe被用来作为主线程通知workers线程有新的连接到达

看下setup_thread
  1. static void setup_thread(LIBEVENT_THREAD *me) {
  2. if (! me->base) {
  3. me->base = event_init();
  4. if (! me->base) {
  5. fprintf(stderr, "Can't allocate event base\n");
  6. exit(1);
  7. }
  8. }
  9. /* Listen for notifications from other threads */
  10. event_set(&me->notify_event, me->notify_receive_fd,
  11. EV_READ | EV_PERSIST, thread_libevent_process, me);
  12. event_base_set(me->base, &me->notify_event);
  13. if (event_add(&me->notify_event, 0) == -1) {
  14. fprintf(stderr, "Can't monitor libevent notify pipe\n");
  15. exit(1);
  16. }
  17. cq_init(&me->new_conn_queue);
  18. }
setup_thread主要是创建所有workers线程的libevent实例(主线程的libevent实例在main函数中已经建立)

由于之前 threads[0].base = main_base;所以第一个线程(主线程)在这里不会执行event_init()
然后就是注册所有workers线程的管道读端的libevent的读事件,等待主线程的通知
最后在该方法里将所有的workers的CQ初始化了

create_worker实际上就是真正启动了线程,pthread_create调用worker_libevent方法,该方法执行
event_base_loop启动该线程的libevent

这里我们需要记住每个workers线程目前只在自己线程的管道的读端有数据时可读时触发,并调用
thread_libevent_process方法

看一下这个函数
  1. static void thread_libevent_process(int fd, short which, void *arg){
  2. LIBEVENT_THREAD *me = arg;
  3. CQ_ITEM *item;
  4. char buf[1];
  5. if (read(fd, buf, 1) != 1)
  6. if (settings.verbose > 0)
  7. fprintf(stderr, "Can't read from libevent pipe\n");
  8. item = cq_peek(&me->new_conn_queue);
  9. if (NULL != item) {
  10. conn *c = conn_new(item->sfd, item->init_state, item->event_flags,
  11. item->read_buffer_size, item->is_udp, me->base);
  12. 。。。//省略
  13. }
  14. }
函数参数的fd是这个线程的管道读端的描述符
首先将管道的1个字节通知信号读出(这是必须的,在水平触发模式下如果不处理该事件,则会被循环通知,知道事件被处理)

cq_peek是从该线程的CQ队列中取队列头的一个CQ_ITEM,这个CQ_ITEM是被主线程丢到这个队列里的,item->sfd是已经建立的连接
的描述符,通过conn_new函数为该描述符注册libevent的读事件,me->base是代表自己的一个线程结构体,就是说对该描述符的事件
处理交给当前这个workers线程处理,conn_new方法的最重要的内容是:
  1. conn *conn_new(const int sfd, const int init_state, const int event_flags,
  2. const int read_buffer_size, const bool is_udp, struct event_base *base) {
  3. 。。。
  4. event_set(&c->event, sfd, event_flags, event_handler, (void *)c);
  5. event_base_set(base, &c->event);
  6. c->ev_flags = event_flags;
  7. if (event_add(&c->event, 0) == -1) {
  8. if (conn_add_to_freelist(c)) {
  9. conn_free(c);
  10. }
  11. perror("event_add");
  12. return NULL;
  13. }
  14. 。。。
  15. }
可以看到新的连接被注册了一个事件(实际是EV_READ|EV_PERSIST),由当前线程处理(因为这里的event_base是该workers线程自己的)
当该连接有可读数据时会回调event_handler函数,实际上event_handler里主要是调用memcached的核心方法drive_machine

最后看看主线程是如何通知workers线程处理新连接的,主线程的libevent注册的是监听socket描述字的可读事件,就是说
当有建立连接请求时,主线程会处理,回调的函数是也是event_handler(因为实际上主线程也是通过conn_new初始化的监听socket 的libevent可读事件)

最后看看memcached网络事件处理的最核心部分- drive_machine
需要铭记于心的是drive_machine是多线程环境执行的,主线程和workers都会执行drive_machine
  1. static void drive_machine(conn *c) {
  2. bool stop = false;
  3. int sfd, flags = 1;
  4. socklen_t addrlen;
  5. struct sockaddr_storage addr;
  6. int res;
  7. assert(c != NULL);
  8. while (!stop) {
  9. switch(c->state) {
  10. case conn_listening:
  11. addrlen = sizeof(addr);
  12. if ((sfd = accept(c->sfd, (struct sockaddr *)&addr, &addrlen)) == -1) {
  13. //省去n多错误情况处理
  14. break;
  15. }
  16. if ((flags = fcntl(sfd, F_GETFL, 0)) <>
  17. fcntl(sfd, F_SETFL, flags | O_NONBLOCK) <>
  18. perror("setting O_NONBLOCK");
  19. close(sfd);
  20. break;
  21. }
  22. dispatch_conn_new(sfd, conn_read, EV_READ | EV_PERSIST,
  23. DATA_BUFFER_SIZE, false);
  24. break;
  25. case conn_read:
  26. if (try_read_command(c) != 0) {
  27. continue;
  28. }
  29. ....//省略
  30. }
  31. }
首先大家不到被while循环误导(大部分做java的同学都会马上联想到是个周而复始的loop)其实while通常满足一个
case后就会break了,这里用while是考虑到垂直触发方式下,必须读到EWOULDBLOCK错误才可以

言归正传,drive_machine主要是通过当前连接的state来判断该进行何种处理,因为通过libevent注册了读写时间后回调的都是
这个核心函数,所以实际上我们在注册libevent相应事件时,会同时把事件状态写到该conn结构体里,libevent进行回调时会把
该conn结构作为参数传递过来,就是该方法的形参

memcached里连接的状态通过一个enum声明
  1. enum conn_states {
  2. conn_listening, /** the socket which listens for connections */
  3. conn_read, /** reading in a command line */
  4. conn_write, /** writing out a simple response */
  5. conn_nread, /** reading in a fixed number of bytes */
  6. conn_swallow, /** swallowing unnecessary bytes w/o storing */
  7. conn_closing, /** closing this connection */
  8. conn_mwrite, /** writing out many items sequentially */
  9. };
实际对于case conn_listening:这种情况是主线程自己处理的,workers线程永远不会执行此分支
我们看到主线程进行了accept后调用了
dispatch_conn_new(sfd, conn_read, EV_READ | EV_PERSIST,DATA_BUFFER_SIZE, false);

这个函数就是通知workers线程的地方,看看
  1. void dispatch_conn_new(int sfd, int init_state, int event_flags,
  2. int read_buffer_size, int is_udp) {
  3. CQ_ITEM *item = cqi_new();
  4. int thread = (last_thread + 1) % settings.num_threads;
  5. last_thread = thread;
  6. item->sfd = sfd;
  7. item->init_state = init_state;
  8. item->event_flags = event_flags;
  9. item->read_buffer_size = read_buffer_size;
  10. item->is_udp = is_udp;
  11. cq_push(&threads[thread].new_conn_queue, item);
  12. MEMCACHED_CONN_DISPATCH(sfd, threads[thread].thread_id);
  13. if (write(threads[thread].notify_send_fd, "", 1) != 1) {
  14. perror("Writing to thread notify pipe");
  15. }
  16. }
可以清楚的看到,主线程首先创建了一个新的CQ_ITEM,然后通过round robin策略选择了一个thread
并通过cq_push将这个CQ_ITEM放入了该线程的CQ队列里,那么对应的workers线程是怎么知道的呢

就是通过这个
write(threads[thread].notify_send_fd, "", 1)
向该线程管道写了1字节数据,则该线程的libevent立即回调了thread_libevent_process方法(上面已经描述过)

然后那个线程取出item,注册读时间,当该条连接上有数据时,最终也会回调drive_machine方法,也就是
drive_machine方法的 case conn_read:等全部是workers处理的,主线程只处理conn_listening 建立连接这个

这部分代码确实比较多,没法全部贴出来,请大家参考源码,最新版本1.2.6,我省去了很多优化的地方
比如,每个CQ_ITEM被malloc时会一次malloc很多个,以减小碎片的产生等等细节。

2009年3月13日星期五

一道简单题(还有1元在哪里呢)

有三个人去住旅馆,住三间房,每一间房10元,于是他们一共付给老板30元,
第二天,老板觉得三间房只需要25元就够了于是叫小弟退回5元给三位客人,
谁知小弟贪心,只退回每人1元,自己偷偷拿了2元,
这样一来便等于那三位客人每人各花了9元,于是三个人一共花了27元,再加上小弟独吞了不2元,总共是 29元。

可是当初他们三个人一共付出30元,那么还有1元在哪里呢 ?

我,一个写代码的

从事任何行业都一样,只有真正的爱上了这份工作,才会投入热情,才会在顺境中自我警醒,在逆境中寻找突破。这个行业的竞争很激烈,你停下来走,别人就立刻会跑步超过你,没有对这一行业的一种热情,就很难在困境中保持一种执着的态度坚持到底。



踏踏实实“扎马步”

今天无意中看了“校长”的“程序员&司机”,其中谈到了关于程序员速成的问题。其实速成班毕业的 “系统杀手”早已在遍布大江南北,只是在互联网时代,互联网的应用型软件生命周期越来越短,业务驱动主导的情况下,这种速成方式看起来反而提高了企业生产效率。但这样的人才也就只能写几个Facebook上的插件应用或者iGoogle上的Gadget,真的要出Google,Amazon,Yahoo改变互联网世界的企业,还是需要踏踏实实先学“扎马步”的人。

很多在学校的同学或者刚刚毕业的朋友都看什么热门学什么,Spring,AJAX,Hibernate等等,又有多少人在看Spring之前把J2SE的NIO,XML,Collection等先好好学习一下,在看AJAX之前把Http协议、DTD、XML Schema好好看一下,在学习Hibernate以前先把J2EE事务规范搞清楚。Java最大的好处就是开源,能够让人们站在更高的起点来作出更多的创新,但是对于学习者来说,不了解自己站在什么上面的时候,可能摔下来会很痛。在用的时候多问一些为什么,在遇到问题的时候多找找原因,在了解以后多提出一些优化的方案,这样才会进步的更快,走的更远。

记得我前一阵子回家的时候和妈妈聊起最近的工作,虽然妈妈不太明白,但是也知道我现在做的东西技术含量比较高,嘱咐我“千万不要什么都教给自己的同事,徒弟带出就不要师傅了”(这当然是老一辈的观念了)。我和她说:“不要担心,这种学的会的不教迟早也会,学不会的教了也学不会”。其实这里说的学的会的就是技术,而学不会的就是经验和能力。这个行业的人在日积月累过程中并不会去比较掌握的知识面有多广多深,毕竟这行业更新很快,其实能力强的人在多年的学习中就积累了很多的找问题,分析问题,总结问题,提出建议,发掘创新的能力,这些才是这行业人在发展中最宝贵的财富,也是一个人成长的标志。开始的过程中,踏踏实实地“扎马步”,了解一些最基本的知识,那么上层技术的发展对于他来说仅仅只是一个短暂的学习过程,甚至可以触类旁通。因此还是要奉劝每一个新入行的同学,踏踏实实,静下心来做技术,就算工作安排得都是一些浮躁和重复的工作,用高效的方式来结束那些重复劳动,多留一些时间给自己打基础。



逆境养兵、顺境攻城掠地

普通人的工作经历通常都是起伏不定的,一个人的能力是否能够得到体现,不仅仅靠自己的努力,有时候也需要“天时”、“地利”。马云比较有名的一句话:“今天很残酷,明天更残酷,后天很美好,但是大多数人死在明天晚上,看不到后天的太阳!!!”,其实也在说明一件事,就是很多时候需要一种坚持的精神才能得到宝贵的机会。

今天是我进入阿里巴巴满3年,这3年让我感触很深的是:1.逆境不要气馁,厚积薄发。2.顺境不要懈怠,一股作气,把握机会展现自己最大的能力。3.在逆境和顺境的转换过程中,创造机会,不要坐等机会,要学会不在其位,也谋其职。最后一点就拿我自己的亲身经历来说,我原来就职于一家通信公司,因此对于互联网应用的开发和架构设计要比很多人弱,进入阿里巴巴以后工作了半年(主要作业务开发),正好阿里软件创立,当时被分配到了阿里软件第一个产品负责客户模块,当时的应用是通过MDA框架配置搭建的,开发人员很大程度上不需要自己做太多的编码,但是这个平台并没有搭建过如此复杂的大型应用,因此存在着不少问题,当然这些问题都是通过业务产品线的人反馈给平台部的人,当时平台部门人员很少,但是却要修复和完善诺大一个平台,因此常常搁置开发人员的反馈。当时在自己工作之余就琢磨和研究平台,同时跟踪调试平台,最后直接给出解决方案,逐渐的就融入到了平台开发中,最后被吸收到了平台部门,进入平台部门以后遇到了两位很好的老大,根据我的特质给我安排了研究和学习的工作。接下去就是不断地参与阿里软件各个基础平台的构建,核心技术的研究和探索,找到了兴趣和工作的最佳结合点。因此,当你困惑的时候首先不是去抱怨,而是审视一下自己是否还有作的不够的,是否还有可以提升的空间,多给自己制造一些机会,也许我们不用等到后天,也不会死在明天夜里,明天早晨我们就看到了太阳。



海纳百川、冰冻三尺

很多朋友可能听老师或者前辈也说过类似的话,就是作为一个技术人员要广也要钻。就好比现在很多人都要DB Scale out,同时也要Scale up。我从自己的角度来说一下广和钻的看法。广:1.要有容人之量。(很多时候程序员最大的毛病就是喜欢在技术上比较,未尝不是好事,但是一个人的能力总归有限,多看看别人的,多听听别人的,也许能够让自己少用时间获得更多的收获,特别是自己战友的声音)2.触类旁通,多问个为什么,多跨过界去学习。在阿里巴巴,PD、SA、DBA、UI等等职位各司其职,作为开发的我们其实也应该去了解如何去画Use Case,如何假设服务器和应用环境,如何写一些略微复杂的SQL,了解一些DB的特性,如何能够简单的作出一些基础的页面,使用简单的css来美化一下门面。这些就是需要多跨过界,多虚心的去学习。钻:1.本职工作技术一定要扎实,每作一个技术点就要把技术吃透,同时延伸开来,发掘更多的技术亮点。2.多接触新鲜事物,但是有选择的去了解,有目的的去学习和实践(目的的源泉就是工作的需求)。3.学会分享,一个人自己搞懂一个技术很容易,一个人要把他熟悉的技术写下来就会发觉原来自己还有那么多没有搞清楚,一个人如果要把写下来的东西宣讲给别人听,他就会发现,原来写下来的仅仅是那么一小块,因此学会分享,从自己了解,到记录分享,到演讲传播就是一个不断深化和广化的过程。个人觉得小公司锻炼人(啥都自己干),大公司培养人(该干的要干好),因此自己常回头看看自己在广和钻上的不足,可以让自己进步的更快,学的更全面。



学中医积累经验,学西医寻找突破

中医以对人体经络血脉了解为基础,通过望闻问切来寻找病理根源,行医年限越久,找问题解决问题的经验越强。西医以科学技术为手段,通过试验化的方式不断寻找突破,并且将成果积累并且传递给更多的人,但是否年限越久越有能力,或者是使用得器材越广越资深,这点全要看个人对于医术的理解,如果仅仅停留在对器械的使用和对成果的依赖,那么只会成为一个庸医。当然这里绝对没有对中西医的差别化或者评价,仅仅要说明的是,在手段丰富的情况下,容易忽视了本质,只看到了皮毛,积累的时候多一些追根溯源,站在别人的成果上才更踏实,因此在对经验积累上向中医多学一些,在寻找突破,传播技术上多学一点西医的风格。不过说到低,还是要看学习的人,静的下心,沉得住气,才会有积累,才会有突破。





不做一个纯粹的“技术人员”

不做一个纯粹的“技术人员”,其实也就是说要培养自己多方面的能力,我仅仅把自己想到的一些点列出来说说:

1. 项目产品化的思想。现在就算在学校里面给导师作项目都讲究一个商业价值,更不要说在企业里工作。作为一个开发或者架构师最重要的就是要有产品化的概念,这也是项目是否成功的关键。软件的目的是为人服务,如何服务的好,那就要以一个产品的思路去做项目,而不是作为实验室的实验品,为客户提供好服务就会给公司带来商业价值,对自己的工作也会有很好的肯定。这是一个良性循环,反之则是恶性循环(多赢变成多输)。如何做到产品化,首先就是需要去了解需求,而不是布置需求,其次就是设计时多听取一些不同角色的意见,最后就是在客户的反馈过程中反省。

2. 多一些设计,少砌两块砖。代码写的再好,其实也只是用砖块砌墙砌的比较好罢了,这年代已经不会为了节省两块砖而给一个优秀工作者了,同时技术的日新月异,总是摆弄技巧,学习花拳绣腿已经跟不上时代了。多了解一些行业背景,多参与一些架构设计,将业务设计用良好的架构体系来实现,那才是一个称得上有能力的技术人员。

3. 学会前瞻,学会自己找事。记得我刚进平台组,最不适应的就是我的老大基本不太给我布置太详细的任务,这就好比进入大学,老师不给作业,自己反而心里没底了,其实自己找事的过程就是一个自己学习的过程,当我一天下来感觉没干什么,没学到什么,心里就开始发虚。如何能够前瞻性的去选择一些目标,如何对现有情况提出一些创新和建议,都是一种更高能力的要求。现在SIP组也是一样,在我们这个组里虽然现在每周还是布置一定工作,但是我对其他两个同学的要求也是希望能够有前瞻性,学会发现问题,预防问题,更甚者就是提出创新。当你具备了这种环境的时候,你就需要锻炼自己的能力了。

4. 做个让老大放心的人。这点也许很多人和我一样在业务上很早就让老大觉得可以安心睡觉了,但是其实另一方面,如何在商业角度看问题,如何培养新人,如何协调部门合作等等,都会让你的老大更加安心。另一方面来看,其实在这些能力的培养过程中,你不再局限于业务水平的提升,让自己在更多方面更加成熟。



六脉神剑

今天是我进入阿里巴巴3年整。在阿里巴巴有个说法,只有在阿里巴巴工作了3年,才能算是一个真正的阿里人,因为理解阿里巴巴的文化,需要三年时间的沉淀。这里就从一个写代码的角度分享一下阿里巴巴的六脉神剑文化。

客户第一:如果你是做架构的,作平台的,作开发工具的,那么客户就是和自己一样的开发者,多学习一下开源项目的精神,多从使用者角度去考虑问题,那么你的东西才会被更多的人认可和使用,永远不要去做一个“玩具”的开发者。如果你是做产品的,那么就多听,多想,多问,永远不要急着去写代码。



拥抱变化:敏捷开发的基本原则。互联网应用尤其如此,不要害怕变化,在需求和架构之间找到平衡点(说起来比较容易^_^)。



团队合作:一个人的力量始终有限,分享,交流,合作能够让自己事半功倍,学的更多,看得更远。



诚信:说到就要做到,做了就要做好,做软件开发一样也需要有责任感,贴满狗皮膏药的代码上如果注释是你的名字未来也会给你蒙羞。踏踏实实地用心去写代码,去设计架构,不经意间得到的要远远比那么一点工资来的多。



激情:还是那句话,你如果不爱这行,乘着年轻赶快转行。



敬业:专业执着,精益求精



很感谢各位能看完这篇感受分享,以上都仅仅是个人的一点感受,能够引起共鸣那么证明我们的经历很相似,如果能够给到你一点帮助,那写这些就真的有意义了。不论你在别人眼里是一个资深架构师还是开发人员,其实如果你爱这个行业的话,你应该就是一个写代码的,但是每个人的经历都是一本“写代码的自我修养”,珍惜自己的选择,让自己在兴趣和工作中找到最佳结合点。

2009年3月12日星期四

PHP框架的繁荣是正确的发展方向吗?

做ROR有一年了, 感觉非常好.配合敏捷实践(除了pair, 由于是和美国工程师远程合作.)开发速度的确快.一共三个人写代码,短短半年, 项目就基本结束了....
现在新项目即将到来, 客户在php和rails之间难以取舍. 我也打算趁此机会了解一下php. 由于项目定制性还是比较高,想通过成熟的CMS等系统来改改估计是没戏。 从头开发又觉得太慢。于是想从开源框架入手。
经过了解才发现, php新兴的一些框架基本上清一色的学习(或者叫抄袭,特别是cakePHP, 那简直抄得太厉害了.)rails。而且这些框架还发展的很好,越来越受到php社区的欢迎。 比如国外cakePHP,国内的Fleaphp, QeePHP等等,就不一一列举了.
昨天用cakePHP做了个简单的demo, 确实抄rails那是抄的相当直白。 甚至连rake都还有相应的东西代替。除了migration和filter我没找到对应的东西。让我一个不懂php的人,都还是可以很快地上手了.
一方面感叹php抄rails这种彻底,另一方面也感叹这些抄袭之作的确也带来了php开发效率的提升。虽然由于php本身的原因,框架的引入对性能的影响是比较大的。但是这些框架的出现大有重整php社区的意思。(至少客户就告诉我们,用rails不如用cakePHP,这样他们也不会引入更多风险。他们还介绍他们美国几个团队都又从rails转回cakePHP了.)
我就纳闷儿了,当时还觉得rails就是冲着php的市场去的。。。现在反而觉得rails的思想拯救了php...
大家觉得是应该继续说服客户呢? 还是就用山寨rails了呢?

Java界面开发学习笔记

前几天和朋友聊到Java界面开发的时候,他说苦于没有真正介绍一些可以提升软件用户体验的技巧方面的书籍,市面上的多数介绍Swing或者SWT的教程、学习笔记等,都是介绍了Swing/SWT的概念和一些控件的使用方法,但是如果要让读者学完教程后再实现一个有新意,亲切友好的界面时,大多数读者的反应是,确实会使用控件,但是不知道何时使用,何处使用。所以一些Java程序员对界面开发都采用了敬而远之的态度,也就出现了一些阿Q精神:“Java开发的界面能好到哪去,只要能凑活着用就行了”,“Swing本地化不好,速度慢,SWT不是Java语言标准,支持的平台太少”之类云云。
读完该系列文章后,你会发现Java也可以开发出很酷很炫的界面。Swing和SWT对于Java程序员来说都是无价之宝。
本系列文章主要以实例讲解的方式阐释一些窗口布局、控件重写、窗口特效以及Eclipse RCP技术。
具体内容暂定如下:
·SWT Designer插件介绍和使用
·使用Swing皮肤机制来构建多主题的程序
·拖拽效果的实现
·QQ窗口自动隐藏效果的实现
·构建自己的控件库
·欢迎界面的制作
·OutlookBar的介绍及使用
·声效的使用
·滚动字幕效果的实现
·居中显示和全屏显示
·面板的动态增删
·使用布局增加界面友好性
·Eclipse RCP技术的详细介绍
·NetBeans富客户端的介绍
以上仅为暂定内容,后续编写过程会有所增删,敬请关注。也希望大家提出宝贵意见和建议。

印度12岁最小"道德黑客" 4岁会写程序

据印度孟买《正午报》3月10日报道,在印度众多神秘的专业“道德黑客”中,有一个年仅12岁的小黑客正在印度反黑客领域发挥着举足轻重的作用。已经拥有计算机技术硕士学历的他,如今正配合着印度一家知名IT企业,维护印度互联网的安全。
为保护桑杰夫,媒体没有公开他的真实身份  12岁的桑杰夫(化名,因年纪过小和职业特殊,保护他的隐私。)简直就是现实版的《天才小医生》,每天,当和他年纪差不多的小朋友或者躺在家中沙发上懒洋洋地看电视,或者和一群朋友疯赶打闹时,他却像个小大人似的,在印度一家IT企业规规矩矩地“上班”。要问他是干什么工作的,说出来吓你一跳 ——道德黑客。   道德黑客是随着互联网技术迅速发展和黑客泛滥后,在全世界兴起的一个职业。道德黑客的主要内容就是掌握黑客攻击互联网的种种手段、技巧,找到应对的方法,维护互联网安全。别看桑杰夫年纪小,一脸稚气,但他已经是电脑技术领域的天才高手。   4岁时,桑杰夫在短短15天时间里完成了常人需要3个月才能完成的电脑程序课程。5岁时,桑杰夫学会了Corel Draw绘图软件,会做Flash,并掌握了HTML编程语言、Photoshop,是Paintshop方面的专家。6岁时,桑杰夫学会了Java、 Linux和SQL。7岁时,桑杰夫轻轻松松地通过了微软认证系统工程师(MCSE)考试,并读完了计算机领域的一个研究生课程。10岁时,桑杰夫获得了 ORACLE工程师认证和Java程序员认证,成为一名专业程序员。   目前,桑杰夫已经成为印度最年轻的“道德黑客”和电脑技术顾问,他配合孟买一家大型的IT安全技术公司,监控黑客的举动,维护互联网的安全。


----


汗了。。。 那么小就会。。。 我都30 的人了才熟练

第一次写博客。

第一次写博客。 也是第一次用博客。 以后会多写点自己的编程经验和大家分享。