Archive for » 2009 «

内联升最后的布鞋

今年过年回家,我用实习的工资第一次给外婆买了礼物,内联升的布鞋。

昨天,北京罕见的下起了大雪。白色的世界,有一个很美的词叫做银装素裹。

可白色,其实也有另一层很独特的意义。

今早,一觉醒来,再平常不过的一个星期一,却罕见并清晰记得梦里梦到了ex。

我无法不相信冥冥之中自有天意。

晚上,本以为是妈妈家常的问候电话,可电话那头格外清晰的哭腔告诉我:外婆永远的走了。

外婆年初查出是胃癌晚期,病情一直在恶化。虽然有心理准备,可是此刻,我的脑子仍然一片空白。

妈妈说外婆走的很突然,并没有太大的痛苦,也让我不用担心外公。

妈妈说外婆快走的时候已经说不出话,只是指着她的手镯,外公问是不是要把它给我未来的媳妇,外婆微笑地点了点头。

妈妈说外婆平时并不舍得穿我买的那双内联升,却在今天永远地穿上了它,安祥地走了。

此时的我,再也无法抑制,放声大哭。

此时的我,宁肯相信凤凰涅槃,相信死亡只是重生。

童年时候在外婆家的一幅幅画面在眼前掠过:

每周五坐在舅舅的自行车去外婆家,我晃着腿在上坡时给自行车“加速”;

睡觉前去鸡窝里把鸡蛋给掏出来递给外婆,就好像那是我的产品一样;

外婆在地上划一个圈,说不要跑出去,她一会就回来。我就静静地坐在圈里,等着外婆回来;

晚上在木板桥上乘凉,外公教我识别天上的北斗星;

夏天每天下午盼着那个半点响一次的大钟拨到4点,便跳下渠道去洗澡;

每周天要回家前,外公都会把我叫到屋里,给我一点零花钱,嘱咐不要告诉外婆;

外婆家的钥匙有一副是备用的,就放在门口的窗台上,拿着小铁盒盖着;

外婆家天花板上吊着很多篮子,小时候我跳起来也够不着,现在我已经要低着头以免碰着;

……

如果说内心有一丝安慰的话,可能就是我上大学后第一次在今年十一回家,否则我将后悔一辈子。很多朋友知道我回家,却很少知道我的目的。

外婆,你在天堂还好吗?

Category: memory  3 Comments

Twitter Weekly Updates for 2010-04-02

Twitter Weekly Updates for 2010-04-02

Twitter Weekly Updates for 2009-09-14

Refactoring——Reading Note C1

由于Qi组织了一个提高代码质量的学习过程,也强制性向我们提出了每篇两周blog的要求。对于写blog,一直是个比较懒的人,有时候会心血来潮地写上一些,但更多的时候是有些想法在脑子里一闪而过,却并没有及时记录,读书亦如此。正好借这个机会,将我在读重构这本书时的一些感悟记录下来。

P15. 任何一个傻瓜都能写出计算机可以理解的代码。惟有写出人类容易理解的代码,才是优秀的程序员。
这可以从两个方面来看,一是代码的格式,没有多余的空格,没有多余的tab,正确的注释等等。对于一个团队来说,制定一定统一的代码默认规范是很有必要的,Eclipse已经支持这一功能,如{是在函数同一行还是另起一行,final string用大写单词和_连接,if和后面的(…)必须接空格等等。虽然你可以用Ctrl+Shift+F来格式化,但这并不是不注意代码规范的借品,在写代码时初始便将这一套规则印在脑海中。在这一点上,我自己便是一个非常偏执的人。
另一点便是程序的逻辑结构,类结构化的书写方式亦或是故弄玄虚的show设计模式都不足取。作为一个程序员,最重要的是弄清楚究竟我们要的结果是什么,再进一步,面对最有可能的变化,我们所写的代码是否准备好了?思考有可能的变化与过度设计并不是矛盾的,XP提倡先动手做做看,但不是不经思考便挽起胳膊仓促上阵。以前在Deshi实习的时候另一个实习生写的代码经常会比较长,一个regular忍不住说:你这个函数有几米长。。。人不是机器,当他看到这种冗长的代码,首先就不会太高兴,如果没有好的工具支持,看这些代码是很痛苦的事情。

C-H-A-N-G-E
让我们首先将这6个字母在心里完整的默念几遍。如果一个程序写出来之后需求永远不会有变化,那么对于这个程序本身来说,在效率不至于太差的情况,它确实无所谓要求多高的OO结构技巧。但是事实上呢?绝对不是。而实际上大部分软件工程的理论,MDA也好,Design Pattern亦或Refatoring也罢,都直面并试图解决需求变化的问题。对于重构来说,它并不是从设计角度来解决的。按照Kent Beck等人的思想,重构就是重构,它只管更加合理的重新组织代码;而添加新功能就是添加新功能,它只是纯粹的往现在代码中添加新功能的代码。二者不要也不应该混淆。

基本的重构方法
第一章中由一个案例引出了一些重构的基本方法,如extract method,move method,replace temp with query等。
而在上一个GPS项目中,面对多个GPS厂商的标准支持时,我也不知不觉地用到了replace conditional with polymorpohism的重构方法。什么,你不相信?好吧,我承认是在新需求到来后我添加新功能时的写法。MS同时戴了Kent Beck所言的两顶帽子:D
更多的重构方法详解及在Eclipse中的支持,在读到第五章后再详细阐述。

Category: Refactoring  2 Comments

Flex初体验

严格的说来,这并不是Flex的初体验了。最早听到Flex这个名词是从Cowoo口中,那还是四年前的事了,之后也看过一些电子书。不过,总是徘徊在入门者周围。而最近有一个月的时间在实际项目用到,虽说只是个demo,但还是很不错的。

项目的需求是将DB2的xml特性运用在BI分析上,由于xml的动态性,相应的也就能动态地展示维度信息。而从技术上来说,一分为二,前台的Flex与后台的Service实现方式。这里就只谈Flex了,项目中涉及到的有以下几个:

前后台通信
Flex提供了几种方式,无论是HttpService,WebService,还是RemoteObject,交互方式只是在协议的不同。说到底无非也就是二进制流的来回传送,究竟是怎样交互,甚至你自己也可以定义一套。
这种将前后台严格区分开的通信方式较为灵活,比如你可以把前后台程序部署下两台不同的机器,同样的缺点在于你必须严格定义好通信协议,否则你不能保证传给后台的参数的有效性,也就不能保证后台返回的数据的正确性。还有一点,返回的数据的处理与显示应当按MVC适度分层。
现在越来越多的系统不用采用单一语言从前台写到后台的方式,各是各种语言各负其责,如Flex,JS等更适合做些browser端的操作,而后台的业务逻辑可以采用强类型的语言如Java,C#,然后二者之前通过某种协议串联起来,如restful webservice在Ruby就有良好的API支持。

XML处理与显示
ActionScript的XML API让我非常非常震惊,它是如此的顺手,以至于得到XML中任意条件的数据几乎只需一条语句就能搞定。只有想不到,没有做不到,其便捷性远远超过了Java语言中处理XML那种一板一眼的方式。而Flex的组件也与XML无缝地集成,比如Explorer Tree, Column Chart以及OLAPDataGrid。

事件处理机制
从AS的整体处理上来说,统一采用了事件监听机制,与Java Swing非常相似,只不是Flex中响应事件的是函数,而在Java中是一个对象(一般习惯写成匿名对象)。Flex中一般的可视化对象都已经有内制的事件处理机制,例如

<mx:Button id="searchButton" label="Search" click="search();"/>

而对于非可视化对象,一般的做法是继承flash.events.Event创建自己的Event类并创建与事件相关的属性,在这个事件触发时dispatchEvent()。而这个Event由谁来处理,它并不关心。注册了监听事件的组件则会收到相关通知,进而处理相关逻辑。其实对于可视化对象这套方法同样适用,甚至你都可以在mxml里面的click里面直接写某些简单的代码。而究竟是再抽象一层,还是相对暴露地写在mxml里,那就看你自己的考量咯。

优点及缺点
# 由于AS的动态性,Flex Builder有些地方是无法进行语法提示或者自动补齐的。比如没有自动导入所需包等。
# 没有一个简单的AS语法测试环境,最简单的Hello World也要基于MXML程序或者FLA程序来显示之后才能测试。

Reference
# Flex 3 Cookbook
# Programming Actionscript 3
# http://examples.adobe.com/flex3/componentexplorer/explorer.html

Tips
在项目过程中,会有一些Flex及AS中的tip,准备放在另一篇blog中记录之。

Category: flex  Leave a Comment

浅析java字符编码(在socket游走之间——续)

今天无意登到WP后台逛了逛,一巨干尸赫然在目。。。扫了一眼,貌似重见天日沿能存活,赶紧发出这篇两个月前写的draft。

============================不愿烂在草稿堆里的分割线=================================

书接上回,补上上次没写完的。

他山之石,可以攻玉

通过自己动手写的client和socket能细致地了解一些细节,但这个山寨版的产品还远远不能放到项目的生产环境上去。而成熟的开源API能为我们省去重复制造轮子的时间。

HttpClient

这个出自Apache的API包很好地封装了一个Http Client的全部细节。比如Header的拼装,发送正文的数据,接收回来报文的解析。有些动作是隐式的,看一些源代码会更清楚些,比如如果设置了Content-Type,它会用这个来编码正文流;并进而设置Content-Length的字节数。

1. 尽量不要用deprecated的方法。比如setRequestBody(String s),然后你send一个UTF-8的流,那么HttpClient做的动作就是给Content-Length的值设置为s.lengh(),这显然是不对的。length。既然length都没设置正确,要是还能在server端得到正确流就是奇闻了。正确的做法是使用**RequestEntity,例如这样:

RequestEntity entity = new ByteArrayRequestEntity(s.getBytes("UTF-8"));
postMethod.setRequestEntity(entity);

类似不被推荐的方法还有一样,这些大多是基于ASCII设计的,而现在的做法更好考虑了I18N编码的需求。

Servlet

用Servlet来当做你的Server当然是很方便的了,就它来当做客户端access的service。doGet()和doPost()方法接收客户端,然后Servlet规范提供了足够傻瓜的方法供你调用,比如servlet.getInpuStream()。这个方法和jdk socket API提供的socket.getInputStream()相比,区别在上篇文章中已经从实践结果上看到了。如果要问为什么会这样,就只能看看各web/app server对Servlet规范的实现源码了,比如Tomcat中的CoyoteInputStream。作为Servlet规范中request.getInputStream()方法的具体实现,CoyoteInputStream里的read()及相关方法都可以看看。

PS: 本想把Tomcat的这段代码实现贴些上来,可是看了一晚上仅仅是有些眉目,离清晰地剖析实现还是有段距离,所以也没能完全佐证那些结果。今晚的最大收获就是把tomcat在eclipse编译了一遍,然后打下断点,单步跟踪,还是能发现一些tomcat的原理,诸如启动流程,收到请求的处理周期等等。这些经验总结还是等有精力了再来做吧。

雕虫小技

Https

如果server端要求SSL的连接方式,在client端需要首先得到server端的证书,然后用jre的一个工具导入,然后在运行时将这个值设为临时的某个system property。这方面的文章google就有一大把,就不在此赘述了。

Concurrency

作为server端,要么用Servlet来做,它本身是多线程的。要么自己写一个standalone的java application来做server端,也不费事,这部分就不说了。

而作为client端,可以尝试一下jdk 1.5引入的Doug Lea的concurrent包。我在项目中是这样实现的,send处理逻辑部分:

public void run() {
    while (!stopFlag) {
        try {
            // judge condition
            if (…..) {
                /* access to the pool */
                semaphore.acquire();
                Runnable run = new Runnable() {
                    public void run() {
                        // sending reqeust logic ……

                    }
                };
                pool.execute(run);
            }
            else {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    getLogger().warn("Thread is interrupted.");
                }
            }
        } catch (Exception e) {
            getLogger().fatal("***", e);
            this.stopFlag = true;
        }
    }
    pool.shutdown();
    getLogger().info("*** is terminated.");
}

而semaphore和pool在这个类的构造方法里初始化:

public class *** implements Runnable {   
    ……
    private int poolSize;
    private final Semaphore semaphore;    /* initial a thread pool */
    private ExecutorService pool;    /* set the limit to access the pool */
    public ***() {
        super();
        ……
        poolSize = Integer.parseInt(propService.getProperty(
                "***_pool_size", "4"));
        pool = Executors.newFixedThreadPool(poolSize);
        semaphore = new Semaphore(poolSize);
    }
    ……
}

Concurrent包还有很多实用的功能与技巧,遇到再与大家分享:D

Category: java  2 Comments

搬家杂记

上周末,之前“蹭租”的房子到期,遂搬至另一个小区,周末两天的时间全花在搬东西,打扫卫生上了。不过在躺在床上那一霎,感觉还是值了,毕竟作为北漂一族,如何能使自己感到有“家”的感觉才是最真切的,哪怕这个“家”非常临时。而我也告别了之前住大房间——客厅的历史。

说到之前住的客厅,可能这是我7月份来北京之后比较失误的决定吧。当时正赶上奥运,房子不是很好找,就蹭在了三个本科同学租的三室一厅的客厅里。由于客厅的缘故,加上彼此之间很熟,所以自己的空间很小,以致于除了工作以外做的技术积累很少。可能帮小花做的GEF小项目算是一些,其他都很少。有时候在看书的时候,由于客厅没有书桌,只能到同学的房间,如果同学回来了也只能让出桌子。现在回头看,当时就很欠考虑,因为有一个自己独立的空间是非常重要的。年前有个上海autodesk的同学辞职,来北京找工作。到的前几天跟我们一起住,不过在一星期内就在外面租了房子,我们几个都说没必要这么急找房子,先一起住着嘛。不过他非常坚持,其实也就是空间的缘故了,如果跟我们一起住的话,供他梳理和积累知识以找工作的空间会非常有限。

除了搬家,这星期比较重要的事情就是拿到DB2某部门的口头offer了。上周五两小时的笔试,昨天上午三个技术官一个半小时的面试,再加上昨天下午一线经理全英文的面试,很tough的一个过程。自己的表现也只能说正常,算不上好或者完美,因为对笔试和面试都没有作太多的准备,一些只要准备就能答上的问题最后让自己都感到无奈了,比如问你Hibernate对实体的继承设计有哪些实现支持,这个问题我做Hibernate sharing的时候都谈到过,可当时就是记不住具体哪几种了,非常非常的尴尬。笔试面试还是得好好的准备,否则这个知识点答不全,那个也答不全,你的印象分也扣得差不多了。

不过这个offer也不能算很保险,因为最后一线说他那边没什么问题了,HC也不会有问题,不过最终得美国那边批,所以可能美国那边还会有电话面试。不管怎么样,保持一颗平常心吧,如果真有什么意外也不算是世界末日。记得10月底在面完自己部门的一线二线时,感觉offer就是囊中之物了。结果一线一封“Please seek other opportunities besides GBSC”的邮件彻底让我体检到了“人间悲剧”。。。

浅析java字符编码(在socket游走之间)

在引子中,介绍了一些在文件操作时需要注意的java字符编码细节。而在实际工程中,对java字符的操作更多情况下是在网络上以流的方式进行编解码。这篇文章就尝试着记录一些这方面的心得,与大家分享。

一般来说,工程中免不了与第三方厂商或系统交互,有时候你是作为发送方,有时候你是作为接收方。如果不采用类似于MQ这样的消息中间件,那么对于消息的交互细节,有一些是必须注意的。

自己动手,丰衣足食

为了完成一些基本的接收和发送动作,我们大可以自己动手,利用java中的socket api,实现一个如下的客户端和服务端。

Client:

   1: InetAddress addr = InetAddress.getLocalHost();
   2: int port = 9876;
   3: Socket socket = new Socket();
   4: SocketAddress socketAddr = new InetSocketAddress(addr, port);
   5:  
   6: String data = "中文123";
   7: socket.connect(socketAddr);
   8:  
   9: String path = "/";
  10: BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"));
  11: bw.write("POST " + path + " HTTP/1.0\r\n");
  12: bw.write("Content-Length: " + data.getBytes("UTF-8").length + "\r\n");
  13: bw.write("\r\n");
  14:  
  15: // send request with the data
  16: bw.write(data);
  17: bw.flush();
  18:  
  19: // get response
  20: BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
  21: String line = "";
  22: while ((line = br.readLine()) != null) {
  23:     System.out.println(line);
  24: }
  25: bw.close();
  26: br.close();

这里需要注意的是有几点:

1. 第12行Content-Length的大小一定要设置正确,这里的长度是POST正文数据的byte大小。一会联系server端的代码详细解释这个属性。

2. Content-Type作为Http header并不是必需的。有时候我们会发现有些流Content-Type是写着UTF-8,但实际上它的内容不是按照UTF-8编码的。所以这个属性对内容数据并没有约束力。

3. 第10行至第13行用了BufferedWriter进行数据发送,这里可以有多种选择,比如PrintStream。

Server:

刚才说到了Client发送的Content-Length指定了发送内容的byte大小,那么这个值在server端如何取得呢?一般的做法是通过一个BufferedReader对socket.getInputStream()进行adapt,然后就可以用readline()方法一行一行地将头信息读出来,由于Http Header都是ASCII字符,由于无论哪种解码方式Content-Length的值都能正确得到。

接下来读取body信息的方式就有一些陷阱在里面了。

陷阱1. 网络上的字符流不同于文件中固定的字符流,它是没有EOF的。所以什么时候停止是由你来决定的,如果你一直readLine()它是不会终止的。

陷阱2. 刚才已经得到了Content-Length,那么你是不是会尝试以下的代码呢?

   1: for (int i = 0; i < contentLength; i++) {
   2:     sb.append((char) br.read());

这段代码的结果在处理刚才发送的“中文123”时便会读了5个字符后无法往下读,这是由于br是按char来读,而并非byte,那么在读完5个字符后便停止了(但如同readLine()读完后一样,它并不会返回一个EOF)。而contentLength是9。

如果你说为什么不在发送时将contentLength指定为5呢?这就纯粹是为了解决问题而解决问题,更违背了HTTP的RFC规范。

规范的做法是首先不对socket.getInputStream()进行任何的adapt,即不进行decoding,先将body的byte[]数组完整取出来,再对其decoding。

Solution1. 如果socket的inputstream你只需要遍历一遍,那么用inputstream的read()方法先得到contentLength,就能顺利得到body的byte[]数组。

Solution2. 如果socket的inputstream你需要遍历多遍,比如首先要计算body的某一部分的md5值,那么通过socket的inputstream首先构造一个ByteArrayInputStream是更好的选择。不但可以调用mark()和reset()方法,更可以利用其有EOF直接调用readLine()方法。

   1: InputStream input = socket.getInputStream();
   2: byte[] buffer = new byte[1024];
   3: int readNum = input.read(buffer);
   4:  
   5: // wrap a local stream
   6: ByteArrayInputStream ins = new ByteArrayInputStream(buffer, 0, readNum);
   7: BufferedReader br = new BufferedReader(new InputStreamReader(ins, "UTF-8"));    // UTF-8 is necessary
   8: String line = "";
   9: long contentLength = 0;
  10: while ((line = br.readLine()) != null) {
  11:     if (line.startsWith("Content-Length")) {
  12:         contentLength = Long.parseLong(line.substring(line.indexOf(':') + 1).trim());
  13:     }
  14:     if (line.equals(""))
  15:         break;
  16: }
  17:  
  18: StringBuffer sb = new StringBuffer();
  19:  
  20: while ((line = br.readLine()) != null)
  21:     sb.append(line);
  22:  
  23: PrintStream out = new PrintStream(socket.getOutputStream());
  24: out.println("HTTP/1.0 200 OK");
  25: out.println("Content-Type: text/html;charset=UTF-8");
  26: out.println(); // blank will end http-header
  27: out.write("123中文".getBytes("UTF-8"));
  28:  
  29: socket.close();

几点说明:

1. 第2行到第3行的代码需要些小改动以适应大数据量post的情况。

2. 第23行到第27的代码与Client端利用BufferedWriter输出的情况类似,一些细节可以自行比较。

3. 你可能会注意到一个细节,为什么Client在read response(Client代码20-24行)可以毫无忌惮地使用BufferedReader来读?因为在Client端的read动作是在Server端发出close()动作之后,而这同样可以解释为什么在Server端socket.getInpuStream()没有EOF标志。

下一节将介绍一下client端及server端代表了先进生产力的API,自己制造粗糙的轮子并不是一件好事,嗯。

Category: java  Leave a Comment