Monthly Archives: April 2011

Java: Reading a pdf file from URL into Byte array/ByteBuffer in an applet.

I’m trying to figure out why this particular snippet of code isn’t working for me. I’ve got an applet which is supposed to read a .pdf and display it with a pdf-renderer library, but for some reason when I read in the .pdf files which sit on my server, they end up as being corrupt. I’ve tested it by writing the files back out again.

I’ve tried viewing the applet in both IE and Firefox and the corrupt files occur. Funny thing is, when I trying viewing the applet in Safari (for Windows), the file is actually fine! I understand the JVM might be different, but I am still lost. I’ve compiled in Java 1.5. JVMs are 1.6. The snippet which reads the file is below.

public static ByteBuffer getAsByteArray(URL url) throws IOException {
        ByteArrayOutputStream tmpOut = new ByteArrayOutputStream();

        URLConnection connection = url.openConnection();
        int contentLength = connection.getContentLength();
        InputStream in = url.openStream();
        byte[] buf = new byte[512];
        int len;
        while (true) {
            len = in.read(buf);
            if (len == -1) {
                break;
            }
            tmpOut.write(buf, 0, len);
        }
        tmpOut.close();
        ByteBuffer bb = ByteBuffer.wrap(tmpOut.toByteArray(), 0,
                                        tmpOut.size());
        //Lines below used to test if file is corrupt
        //FileOutputStream fos = new FileOutputStream("C:\\abc.pdf");
        //fos.write(tmpOut.toByteArray());
        return bb;
}

I must be missing something, and I’ve been banging my head trying to figure it out. Any help is greatly appreciated. Thanks.

 


 

Edit: To further clarify my situation, the difference in the file before I read then with the snippet and after, is that the ones I output after reading are significantly smaller than they originally are. When opening them, they are not recognized as .pdf files. There are no exceptions being thrown that I ignore, and I have tried flushing to no avail.

This snippet works in Safari, meaning the files are read in it’s entirety, with no difference in size, and can be opened with any .pdf reader. In IE and Firefox, the files always end up being corrupted, consistently the same smaller size.

I monitored the len variable (when reading a 59kb file), hoping to see how many bytes get read in at each loop. In IE and Firefox, at 18kb, the in.read(buf) returns a -1 as if the file has ended. Safari does not do this.

I’ll keep at it, and I appreciate all the suggestions so far.

 

come from : http://stackoverflow.com/questions/637100/java-reading-a-pdf-file-from-url-into-byte-array-bytebuffer-in-an-applet

Matcher: groupCount()

/*
Group 0: 1
Group 1: 1
Group 2: null
Group 3: null
Group 0: 2
Group 1: 2
Group 2: null
Group 3: null
Group 0: 3
Group 1: 3
Group 2: null
Group 3: null
Group 0: 4.5
Group 1: 4.5
Group 2: .5
Group 3: null
*/
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MainClass {

public static void main(String[] av) {
String regEx = “[+|-]?(\\d+(\\.\\d*)?)|(\\.\\d+)”;
String str = “a b c d e 1 2 3 4.5 “;
Pattern pattern = Pattern.compile(regEx);
Matcher m = pattern.matcher(str);
while(m.find()) {
for(int i = 0; i<=m.groupCount() ; i++) {
System.out.println(“Group ” + i + “: ” + m.group(i)); // Group i substring
}
}
}

}

LVS+Keepalived 介绍

LVS+Keepalived 介绍

LVS

LVS是Linux Virtual Server的简写,意即Linux虚拟服务器,是一个虚拟的服务器集群系统。本项目在1998年5月由章文嵩博士成立,是中国国内最早出现的自由软件项目之一。目前有三种IP负载均衡技术(VS/NAT、VS/TUN和VS/DR);
十种调度算法(rrr|wrr|lc|wlc|lblc|lblcr|dh|sh|sed|nq)。
Keepalvied
Keepalived在这里主要用作RealServer的健康状态检查以及LoadBalance主机和BackUP主机之间failover的实现

Click here to open new window CTRL+Mouse wheel to zoom in/out

IP配置信息:

  • LVS-DR-Master          192.168.2.166
  • LVS-DR-BACKUP          192.168.2.167
  • LVS-DR-VIP             192.168.2.170
  • WEB1-Realserver        192.168.2.171
  • WEB2-Realserver        192.168.2.172
  • GateWay                192.168.2.253
  • 浏览器与服务器交互原理以及用java模拟浏览器操作

    * 1,在HTTP的WEB应用中, 应用客户端和服务器之间的状态是通过Session来维持的, 而Session的本质就是Cookie,
    * 简单的讲,当浏览器向服务器发送Http请求的时候, HTTP服务器会产生一个SessionID,这个SessionID就唯一的标识了一个客户端到服务器的请求会话过程.
    * 就如同一次会议开始时,主办方给每位到场的嘉宾一个临时的编号胸牌一样, 可以通过这个编号记录每个嘉宾(客户端)的活动(请求状态).
    * 为了保持这个状态, 当服务端向客户端回应的时候,会附带Cookie信息,当然,Cookie里面就包含了SessionID
    * 客户端在执行一系列操作时向服务端发送请求时,也会带上这个SessionID, 一般来说,Session也是一个URL QueryParameter ,就是说,session可以以Key-Value的形式通过URL传递
    * 比如,http://www.51etest.com/dede/login.php?PHPSESSIONID=7dg3dsf19SDf73wqc32fdsf
    * 一般而言,浏览器会自动把此Session信息放入Header报文体中进行传递.
    * 如果浏览器不支持Cookie,那么,浏览器会自动把SessionID附加到URL中去.
    *
    * 2,在这个例子中,以登陆这个功能点进行讲解.
    * 首先,我们登陆的页面是http://www.51etest.com/dede, 我们第一次访问这个页面后,可以从服务器过来的Http Response报文中的Header中找出服务器与浏览器向关联的数据 — Cookie,
    * 而且Session的值也在Cookie中. 于是,我们可以通过分析Set-Cookie这个Header中的参数的值,找到Seesion的Key-Value段.
    * 然后,我们再向服务器发送请求,请求URL为:post@@http://www.51etest.com/dede/login.php@@userid=admin&pwd=tidus2005&gotopage=/dede/&dopost=login
    * 服务器验证登陆成功了, 并且在此次会话变量中增加了我们登陆成功的标识.
    *
    * 3,增加一个广告定义
    * 增加一个广告定义其实就是一个添加数据的过程,无非是我们把我们要添加的数据通过参数的形式告诉指定url页面,页面获取后添加到数据库去而已.
    * 此url地址为:
    * post@@http://www.51etest.com/dede/ad_add.php@@dopost=save&tagname=test&typeid=0&adname=test&starttime=2008-05-29
    * 因为这个页面会先判断我是否登陆
    * 而判断的依据,前面讲了,就是根据我请求时的SessionID找到指定的Session数据区中是否存在我的登陆信息,
    * 所以我当然要把访问登陆页面时获取的SessionID原封不动的再发回去
    * 相当于对服务器说,这是我刚刚来时,你发我的临时身份证,我现在可以形势我的权利。
    *
    * 这就是整个Java后台登陆网站,然后添加数据的过程。

     
    /** 
     *  
     */ 
    package sky.dong.test; 
     
    import java.io.BufferedReader; 
    import java.io.InputStreamReader; 
     
    import org.apache.commons.httpclient.Cookie; 
    import org.apache.commons.httpclient.Header; 
    import org.apache.commons.httpclient.HttpClient; 
    import org.apache.commons.httpclient.NameValuePair; 
    import org.apache.commons.httpclient.cookie.CookiePolicy; 
    import org.apache.commons.httpclient.methods.PostMethod; 
    import org.apache.commons.httpclient.params.HttpMethodParams; 
     
    /** 
     * @author 核弹头 
     * Email:happyman_dong@sina.com 版权所有 盗版必究 
     * @since 2009-8-11 
     * @version 1.0 
     */ 
    public class HttpLoginTest { 
     
        public static void main(String[] args) { 
            String url = "http://discuzdemo.c88.53dns.com/logging.php?action=login&loginsubmit=yes&floatlogin=yes";//论坛的登陆页面 
            String url2="http://discuzdemo.c88.53dns.com/post.php?infloat=yes&action=newthread&fid=2&extra=&topicsubmit=yes&inajax=1";//论坛的发贴页面 
            HttpClient httpClient = new HttpClient(); 
            //httpClient.getHostConfiguration().setProxy("222.247.62.195", 8080); 
            httpClient.getParams().setCookiePolicy( 
                    CookiePolicy.BROWSER_COMPATIBILITY); 
            PostMethod postMethod = new PostMethod(url); 
            PostMethod postMethod2 = new PostMethod(url2); 
            NameValuePair[] data = { 
                    new NameValuePair("username", "123"), 
                    new NameValuePair("referer", 
                            "http://discuzdemo.c88.53dns.com/index.php"), 
                    new NameValuePair("password", "123"), 
                    new NameValuePair("loginfield", "username"), 
                    new NameValuePair("questionid", "0"), 
                    new NameValuePair("formhash", "fc922ca7") }; 
            postMethod.setRequestHeader("Referer", 
                    "http://discuzdemo.c88.53dns.com/index.php"); 
            postMethod.setRequestHeader("Host", "discuzdemo.c88.53dns.com"); 
            // postMethod.setRequestHeader("Connection", "keep-alive"); 
            // postMethod.setRequestHeader("Cookie", "jbu_oldtopics=D123D; 
            // jbu_fid2=1249912623; smile=1D1; jbu_onlineusernum=2; 
            // jbu_sid=amveZM"); 
            postMethod 
                    .setRequestHeader( 
                            "User-Agent", 
                            "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2"); 
            postMethod 
                    .setRequestHeader("Accept", 
                            "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); 
            // postMethod.setRequestHeader("Accept-Encoding", "gzip,deflate"); 
            // postMethod.setRequestHeader("Accept-Language", "zh-cn"); 
            // postMethod.setRequestHeader("Accept-Charset", 
            // "GB2312,utf-8;q=0.7,*;q=0.7"); 
            postMethod.setRequestBody(data); 
            try { 
                httpClient.executeMethod(postMethod); 
                StringBuffer response = new StringBuffer(); 
                BufferedReader reader = new BufferedReader(new InputStreamReader( 
                        postMethod.getResponseBodyAsStream(), "gb2312"));//以gb2312编码方式打印从服务器端返回的请求 
                String line; 
                while ((line = reader.readLine()) != null) { 
                    response.append(line).append( 
                            System.getProperty("line.separator")); 
                } 
                reader.close(); 
                Header header = postMethod.getResponseHeader("Set-Cookie"); 
                Cookie[] cookies=httpClient.getState().getCookies();//取出登陆成功后,服务器返回的cookies信息,里面保存了服务器端给的“临时证” 
                String tmpcookies=""; 
                for(Cookie c:cookies){ 
                    tmpcookies=tmpcookies+c.toString()+";"; 
                    System.out.println(c); 
                } 
                System.out.println(tmpcookies); 
    //            System.out.println(header.getValue()); 
                System.out.println(response); 
                NameValuePair[] data2 = { 
                        new NameValuePair("subject", "测试自动发贴"), 
                        new NameValuePair("message", 
                                "能否发贴成功呢?测试一下就知道了"), 
                        new NameValuePair("updateswfattach", "0"), 
                        new NameValuePair("wysiwyg", "0"), 
                        new NameValuePair("checkbox", "0"), 
                        new NameValuePair("handlekey", "newthread"), 
                        new NameValuePair("formhash", "885493ec") }; 
                postMethod2.setRequestHeader("cookie",tmpcookies);//将“临时证明”放入下一次的发贴请求操作中 
                postMethod2.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET, "gbk");//因为发贴时候有中文,设置一下请求编码 
                postMethod2.setRequestHeader("Referer", 
                        "http://discuzdemo.c88.53dns.com/forumdisplay.php?fid=4"); 
                postMethod2.setRequestHeader("Host", "discuzdemo.c88.53dns.com"); 
                // postMethod.setRequestHeader("Connection", "keep-alive"); 
                // postMethod.setRequestHeader("Cookie", "jbu_oldtopics=D123D; 
                // jbu_fid2=1249912623; smile=1D1; jbu_onlineusernum=2; 
                // jbu_sid=amveZM"); 
                postMethod2 
                        .setRequestHeader( 
                                "User-Agent", 
                                "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2"); 
                postMethod2 
                        .setRequestHeader("Accept", 
                                "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");//以上操作是模拟浏览器的操作,使用服务器混淆 
                 
                postMethod2.setRequestBody(data2); 
                httpClient.executeMethod(postMethod2); 
                StringBuffer response1 = new StringBuffer(); 
                BufferedReader reader1 = new BufferedReader(new InputStreamReader( 
                        postMethod2.getResponseBodyAsStream(), "gb2312")); 
                String line1; 
                while ((line1 = reader1.readLine()) != null) { 
                    response1.append(line1).append( 
                            System.getProperty("line.separator")); 
                } 
                reader1.close(); 
                System.out.println(response1); 
            } catch (Exception e) { 
                System.out.println(e.getMessage()); 
                // TODO: handle exception 
            } finally { 
                postMethod.releaseConnection(); 
                postMethod2.releaseConnection(); 
            } 
     
        } 
     
    } 
     
    

    以上代码完成一个登陆论坛后在指定的版块自动发贴的功能

     

    Bash 小技巧:给目录加上书签,快速切换目录

    原文地址:http://www.zhuoqun.net/html/y2011/1635.html

    当我们在命令行下面做开发的时候,很大一部分时间都浪费在了目录切换上面,相信不少人每天敲 “cd” 都敲得想吐。如果目录层次多一点,Tab 键也会饱受摧残。虽然 Bash 有内置的 “cd -”, “pushd” 和 “popd” 命令,但用起来都不是很顺手。

    昨天在 Twitter 上看到了 Huy Nguyen 的一篇文章:Quick Bash Tip : Directory Bookmarks,用几行简单的 Bash 脚本巧妙地给目录加上了书签,这样你就可以给最常用的那几个工作目录加上书签,不需要每次都敲 cd 了。

    昨天 Huy Nguyen 的这篇文章被发布到了 Hacker News 上,然后引来了很多人评论,其中不少评论都是非常有价值的,甚至还有人直接在评论里对 Huy Nguyen 的脚本做了改进。Huy Nguyen 看到评论之后也修改了自己最初写的脚本,并放到了 github 上(https://github.com/huyng/bashmarks),你可以用 git clone 命令把那个脚本下载下来,或者直接把源码复制到你的 ~/.bashrc 中。

    这个脚本只有三个命令:

    s – 给当前目录加上书签,书签名为 bookmark_name
    g – 跳到名为 bookmark_name 的书签
    l – 列出所有的书签

    如果想要删除已经保存的书签,请直接编辑 $HOME/.sdirs 文件删除相应的行。

    评论中也有人推荐了另外一个功能更为强大的名为 z 的脚本,这个脚本也在 github 上(https://github.com/rupa/z),有兴趣的可以试用一下。

    BTW, 经常在国外很多技术文章下面看到很多精彩评论,有些甚至比文章本身更加精彩,所以我很多时候都把每个评论仔细看完。评论里没有人说“沙发”,没有人说“傻 逼,这都不懂”,没有人说“菜鸟,早就有人写出同样的东西了!连z都没听说过,我只用它!”,大家都很礼貌,即使是有反对意见也都是逻辑清晰地列出自己的 理由。这让我非常惊诧和羡慕。

    作者发布一篇文章,读者提供反馈,然后作者改进原文,文章的改动历史和评论都成为文章不可分割的一部分,作者和读者都可以不断思考和获益。反过来 说,也正是因为这样的氛围才使得很多人敢于分享自己的看法,敢于写出自己的观点,敢于否认自己最初的看法并不断修正。真希望国内在别人文章下面评论的人也 可以有那样的耐心和礼貌,以及与作者对话的逻辑和才能。至少,要把别人的文章看完再发言。

     

    开发与研发(下)

    原文地址:http://www.zhuoqun.net/html/y2011/1590.html

    接上篇

    研发

    相对于开发来说,我个人更喜欢研发一点。研发和开发的一个不同之处就是研发有更多的“研究”成分在里面,也就是说研发的时候会有更多“光明正大”的 学习时间,这对于那些对技术本身有追求的工程师来说是很有吸引力的。有一些人做工程师是为了可以创造出好的产品,然后挣大钱或者改变世界;也有一些人做工 程师是因为对技术本身有兴趣,想要好好研究。可以凭借技术名利双收变身成功人士固然很有吸引力,但不关心世事钻研一些自己喜欢的东西也自有它的乐趣在。

    如果说开发产品是“输出”,那么学习思考就是“输入”,只有输出没有输入整个人就会废掉,完全沦为一颗螺丝钉。在很多公司尤其是那种经常加班赶项目 的公司,你每天都会处于很忙碌的状态,脑子里想的都是赶紧把指定的任务完成上线。因为时间紧,所以你在开发过程中遇到什么问题都是只求解决,没有心思和时 间去搞明白为什么会出现那种问题,在这样的工作状态下完全没有办法积累工作经验,看上去好像工作了五年,其实是工作了一年,然后重复了四年。

    做研发一般不会直接为产品贡献代码,更多做的是一些基础架构或者实验性的产品,所以它有几个很明显的好处。首先,很少开会。其次,没有产品经理。第 三,一般都会把质量放在第一位,时间不会特别紧。这是三个非常巨大的优势,这意味着你绝大部分时间都可以安心学习、思考、设计、编程,幸福指数会飙升。如 果你是做基础架构,那么代码质量就会有硬性要求,你不得不写得健壮、易用、松耦合并且易于调试,要花心思和时间细细打磨,对个人的能力提高、习惯养成和经 验积累都非常有帮助;如果你是做实验性的产品,那么你就有大量的机会和时间去调研最新的技术,而且最棒的是你可以在产品当中使用它们——这对于开发线上产 品的工程师来说是不太可能的,因为不成熟的新技术存在太多未知的风险。

    此外,做研发对工程师的素质要求很高,需要很好的技术基础、学习能力和研究能力——我把它看作是一个优点。从个人角度来说,我宁愿一家公司招聘非常严格需 要竭尽全力才可以进去,因为严格的招聘可以保证团队所有成员的质量,不用担心进去之后会“和臭棋篓子下棋”。既然选择去做研发,那么基本可以说明你是一个 对技术有追求的人,也肯定希望周围是一群和你一样的人,而不是连基础知识都不够熟悉的家伙。只有这样一群“互相看得起”的人在一块研究、学习、思考、切磋 才会其乐无穷,才能够产生更多创意,做出好玩的东西。

    当然,做研发也有不好的地方。只有大公司才有研发部门,这些公司一般都已经上市或者员工已经很多,你不太可能有机会一夜暴富。当你埋头做了几年研发 之后,某一天去参加同学会,发现大学时候那个数据结构不及格总是求你让他拷贝编程作业的张三衣着光鲜四处敬酒。他所在的公司刚刚上市,因为进去得早,现在 他变成了百万富翁而且荣升高层。于是你忽然开始怀疑自己当初的选择,连学习和编程的乐趣都变得很不真实。所以,如果你渴望建功立业,那么就不要选择做研 发,或者做几年研发之后就出来闯荡。成功需要的条件很多,而编程只是你的优势之一,只有这一个优势你需要太多的运气才可以得到你想要的。

    不过,我们也可以换个角度看。“乱世放不下一张安静的书桌”,现在到处都无比浮躁,有个地方可以让你安安心心做一些自己喜欢的事情已经非常难得,多 少人拼命挣钱就是为了可以和你一样做自己喜欢的事情。尽管那么多人在叫嚷“搞原子弹的不如卖茶叶蛋的”,但总有一些人愿意去追求人类最高财富——知识和艺术家般的技艺

    本来做研发成就感会少一点,作为一个 Twitter 的开发工程师看到那么多人在用 Twitter 肯定会特别开心,相比之下某个在 Google 做基础研究的工程师的成就感可能没那么强烈。不过在国内环境比较神奇,开发工程师非但成就感不多,反而会不少挨骂,还经常会有负罪感,相信做过邮件推广和 广告弹窗的工程师都深有体会。这样一来,研发工程师的“清苦”反而变成了一个优点,可以远离很多“不得不做”的违背良心的事情。

    相信很多工程师在入行之前是喜欢技术的,但是工作之后发现完全不是自己当初想象的那个样子,然后就变得失望麻木,不再对技术有热情。其实你可以把热 情延续下去,只不过要去做研发,而不是做开发。大部分由于兴趣而不是生计学习编程的人,内心真正渴望的都是去做研发,只不过没有人告诉他们开发和研发的巨 大差别。现在不少大公司都有自己的研发部门,有一些还成立了自己的研究院,想要一直做技术的同学不妨尝试一下。

    如何选择

    很多人在大学里之所以会选择计算机为自己的专业,并不是因为自己对计算机和编程有兴趣,而是因为计算机是“热门专业”,在毕业之后也浑浑噩噩地找了 一份工作进入了这个行业,做着自己并不喜欢的事情;还有一些人则是毕业之后找不到工作,然后看到一些培训机构的广告就去报名学习编程,希望广告上描绘的 “月薪过万”不只是一场梦。于是就有了越来越多的“代码民工”,在形形色色的大小公司做着又脏又累的工作,只为了“混口饭吃”。

    我并不想批评这些人,毕竟在这个大环境下有着太多无奈,逼得我们无从选择。对于这样一些只想找一份好工作的人,是被骗到这个行业中来的。仔细回忆一 下,这些年来我们看到的业界新闻,了解到的互联网公司文化,大部分都是有关诸如 Google, Facebook 等国外公司的;我们平时学习和使用的技术,几乎都是国外发明的。这让我们深信互联网就是那样美好,那些激动人心的东西触手可及,但请你关上电脑出门好好看 一下周围:这是在中国。互联网没有国界,但互联网公司有。Google 和 Facebook 这样的公司看上去离我们很近,我们每天也使用它们的产品,但国内的互联网公司可能要几百年之后才会有那样的气质和文化。所以如果你不幸误入了这个行业,还 是及早打算改行或者转型做管理比较好,这样就不需要再学习自己并不喜欢的“枯燥”技术了。

    对于那些“真的”对技术有兴趣的人,要么去做一个同时具备软件设计能力的开发人员,也就是富有创造力的 Hacker;要么去做一个自得其乐的研发工程师。虽然环境恶劣,但是任何东西都挡不住真正的热爱。在这个几乎人人都把金钱作为衡量标准的社会里,你真是得到了上天的眷顾,不仅能够以自己喜欢的事情谋生,而且收入还过得去。

    Hacker 是适合创业的,因为他拥有创造一个产品的全部能力。电影《社交网络》让很多以写代码为生的人产生了幻觉,Facebook 创始人传奇般的经历好像在向全世界宣布:世界是程序员的。很多人只是激动地看到扎克伯格的技术能力,但是却忽视了他的软件设计能力和对产品细节的重视程 度,好像只要埋头编程就可以做出 Facebook。除了优秀的技术能力之外,扎克伯格的思考能力和创造力同样出类拔萃,可以感受得到他眼里的世界是不一样的。我们的工程师又有多少人对生 活中的事物有独特而深刻的理解呢?独立思考也应该是 Hacker 的必备技能。

    很多工程师都觉得自己会编程,只是缺少一个“好的 idea”;很多非技术人员则觉得自己有一个“好的 idea”,但是缺少编程能力来实现。要做一个产品,好的 idea 和实现它的能力缺一不可。然而,我们可以看到最后成功的往往是那些非技术人员,因为他们可以清楚地看到编程是一件可以学习的事情;而工程师们则往往天真地 认为好的 idea 靠的是“灵机一动”,不会有意识地培养自己的观察能力和想象力。很多好的 idea 都是来自于平日对生活的敏锐观察和思考,然后这些点在某个时候忽然连成了一条线,把它简单地归结为“天才”是懒惰的做法。

    “成为一个 Hacker”和“做研发”,很难说二者哪一个更困难。Hacker 在技术上可以不是一流,但他运用技术创造产品的综合能力肯定是一流的;而研发更注重技术上的造诣和理解程度,关注的是深度而不是广度。如果想要做研发,那 么就要好好把基础知识研究透彻,比如数据结构、算法和网络协议等,不然很容易就会遇到瓶颈。我遇到过的每一位研发工程师都是技术上的大牛,在很多技术问题 上都有非常深刻的见解;他们会从本质上分析问题,而不只是纠结于语言细节。

    如果你想要通过自己的作品改变世界,那么就好好提高一下编程之外的能力,做一个好的 Hacker;如果只想埋头技术,就应该选择去做研发。不过,无论是想要做一个 Hacker 还是一个研发工程师,都需要长年累月地不断学习和思考。听上去好像非常辛苦,不过每一个热爱技术的人应该都会把学习和思考当作一种乐趣,而不是一种苦役。 如果你无法享受学习和思考的乐趣,那么还是不要在技术这条路上走下去了,你会活得特别累,并且毫无幸福可言。

    在这个充斥着“代码民工”并且缺乏“技术文化”的国度,我们只是关心怎么样可以活得更舒服,似乎忘记了编程本身所具有的迷人色彩。Joel Spolsky 说过,许许多多的人选择编程,首要的原因就是,他们宁愿将自己的时间花在一个公平有序的地方,一个严格的能者上庸者下的地方,一个只要你是对的就能赢得任 何争论的地方。此外,我觉得选择编程还可以获得最大限度的自由和独立。因为找工作的时候只需要凭借自己的编程能力,所以不需要见人说人话见鬼说鬼话,不需 要去结交权贵达人,不需要去为了所谓人脉去混圈子,也不需要看到邮件列表里有领导的邮件就去“顶”。平日里写写代码,其它时间喝酒吃肉,只交性情相投的朋 友,武侠小说里的畅快适意也不过如此。这种独立和自由是极为宝贵的,你可知道有多少人在醉酒之后哭喊“安能摧眉折腰事权贵,使我不得开心颜”?

    所以说,编程这件事情关乎公平,关乎自由,关乎美。而作为一个拥有编程能力的人,你可以亲手创造美。只有艺术家才可以创造美。希望有越来越多的人可以真正领会到编程的魅力所在,喜欢上这种艺术。正如 Raymond 所说,软件设计和实现应该是一门充满快乐的艺术,一种高水平的游戏。你需要用心。你需要去游戏。你需要乐于探索。

    黑客事业之未来, 全依赖我们今日之创造。

    最后推荐一些文章和书,这些文章和书大部分都与技术细节无关,它们讨论的是基于编程的令人心醉的文化,也适合非技术人员阅读。

    1. 如何成为一名黑客。所有学习编程的都应该多看几遍这篇文章,至少把 Hacker 和 Cracker 的区别弄清楚。

    2. 大教堂和市集。这是一篇关于 Linux 的经典文章。这里需要声明一下,我对那些 Windows 程序员没有偏见,只是我觉得作为一个以编程为职业的人,如果不参观一下 Linux/Unix 的深邃世界,未免太过狭隘。

    3. UNIX编程艺术。 这本书虽然名字叫做“编程艺术”,但里面并不讲授如何编程,而是全面展示了迷人的 Unix 哲学和文化。看完之后你会发现,那些看上去不修边幅、整日对着电脑屏幕编写代码的邋遢程序员,对于美竟然会有那么高的追求。“美在计算机科学中的地位,要 比在其他任何技术中的地位都重要,因为软件太复杂了。美是抵御复杂的终极武器。” 这本书的作者 Raymond 同样是《如何成为一名黑客》和 《大教堂和市集》的作者。

    4. 黑客与画家。这篇文章是 Paul Graham 写的,文中详细描述了黑客与画家的相似之处。这里所说的“黑客”和《如何成为一名黑客》中所说的“黑客”略有不同,但你可以看到他们很多共同点。本文也已 经被收录到 《Hackers and Painters》一书,该书的中文版《黑客和画家——Paul Graham文集》由阮一峰翻译,应该很快就会面世,我十分期待。

    5.创造者的品味。作者同样是 Paul Graham,文章观点独到,见解深刻,每读一次都有新的收获。

    6. 软件随想录:程序员部落酋长Joel谈软件。这本书是 Joel Spolsky 的精华文章结集,作者写文章写得非常有趣,擅长讲故事,前几天我翻译的那篇《程序员阿士顿的故事》就是他的手笔。本书由阮一峰翻译,翻译质量非常高,有兴趣的可以先去试读几篇

    7. About Face3交互设计精髓。本书是交互设计领域的经典著作,作者之一 Alan Cooper 原来也是知名程序员,被称为 “Visual Basic 之父”,所以这本书里面对程序员的批评还是很中肯的。另外,书中“设计体贴的软件”的核心思想非常棒,值得程序员好好阅读和思考。

     

    开发与研发(上)

    原文地址:http://www.zhuoqun.net/html/y2011/1573.html

    按:这几天我一直在写这篇东西,本来是胸有成竹,没想到后来越写越发现自己在这个题目下有太多话想说,而以我现在的能力又不能很好地概括总结,以至 于越写越长,文章结构也变得混乱,到后来修改的时候每次都要考虑好久才能下笔,所以决定拆成两部分来发,以便阅读。这篇写得我心力交瘁,质量不算好,凑合 着看吧。

    同样是写程序,不同的岗位工作内容不一样,对程序质量以及工程师的要求也不一样。程序开发大概可以划分成两类:开发和研发,相应也就有开发工程师和 研发工程师。很多人觉得做开发和做研发没什么区别,“都是一样对着电脑写程序啊”,但其实这两者是完全不一样的,下面我想抛开公司对员工的期望、社会对工 程师的需求等其它因素,单纯从国内互联网行业“工程师个人发展”的角度来说一下我个人对这两类工作的看法。

    开发

    开发一般是指产品开发,开发工程师直接为产品贡献代码。每个公司都有自己的产品线,拿 Google 来说吧,它有 Gmail, Chrome 等产品,每个产品都有很多开发工程师在后面支持,这些产品的开发、维护以及升级都是由相应的开发工程师负责的。由于开发工程师的工作直接关系到产品的质量 和在线情况,所以开发工程师的责任是很重的,他可能经常为了下个版本的发布而加班,为了产品的故障不得不在休假的时候打开电脑工作,甚至在过年的时候都会 接到领导的电话。所以你看到那些总抱怨加班太多,总是说自己是“IT民工”的,大部分都是开发工程师。在工程师当中,大部分人都是做产品开发的,毕竟公司 都是要靠产品盈利,招聘的大部分人也要直接为产品服务。

    做开发是很辛苦,但也有好处,因为需要对产品线负责,所以会是公司的核心,裁员对你威胁不大,如果你负责的产品恰好又是盈利产品的话,那么加薪、奖 金、集体出游等福利都不会少。如果你足够幸运地加入了一家快速发展的创业公司,说不定一下子就发家了。还有很重要的一点是,作为产品的开发人员可以看到自 己做的东西被那么多人使用,那是一种莫大的鼓励和肯定。

    苦闷的开发工程师

    尽管我很尊重开发工程师,但是我不得不承认,在国内大部分的公司,做开发工程师是没有前途的。首先,从微博到开心,有多少国内的产品不是山寨的?这 也罢了,最恶心的是有一些产品经理连产品设计图都懒得自己画,直接去截取别人产品的图片,假如我是一个人人网的开发工程师,每天看到产品经理把 Facebook 新上线功能的截图拿过来让我做,你让我如何对产品有荣誉感和认同感?而如果一个开发工程师对自己做的东西没有荣誉感和认同感,那么他坚守自己的岗位要么是 因为公司给的钱多,要么是因为他还没有找到下家。我个人认为,做开发最大的一个好处就是可以亲手实现一个“自己的作品”,就算平时很累,但最后完成它的时 候也还是会无比满足,这点被剥夺了之后,和饭店打工的服务员有什么两样?不一样是为了糊口吗?

    我不知道别人怎样,但我自参加工作以来就一直纠结于此——甚至开发的大部分产品都不好意思写上自己的名字;直到前不久有机会去做一个公司内部使用的 平台,才终于有个作品让自己觉得满意。相信很多开发工程师参加工作之前都对互联网上很多诸如Gmail, Facebook 等优秀的产品耳熟能详,自己也常梦想做出那样的产品,但万万没有想到的是,工作之后要学习的第一课就是“不要对自己做的东西有感情”——有了感情你就不愿 意做广告弹窗,不愿意看到它下线,不愿意为了短期利益伤害用户。与此同时,你还要继续听产品经理和老大们满怀激情地说“我们一定要让用户喜欢我们的产 品”。一个连开发工程师本人都觉得无聊的产品如何让用户真正喜欢呢?拿搜索巨人来说吧,Google 把社交网站看作是某种形式的娱乐而不是有用的工具,所以它会在社交领域失败,再牛的技术也无法遮盖情感上的空白。不过话说回来,这好像对于国内大部分的公司都不是问题,因为它们做一款产品只是想从用户那里拿到钱,如果以后用户流失了就下线,然后再开发一个新的。他们要的不是用户的长期感情,而是一夜情,开发工程师就是一夜情的工具。

    其次,国内几乎所有公司的技术流程和技术积累都做得很烂,大部分都只是片面地追求开发速度。我们在大学里受到的教育是“文档和注释很重要”,工作之 后才发现文档和注释是很稀有的东西,只有特别负责任的工程师才会挤时间去写。有一个很有意思的现象是,国内很多产品发布之后会特别自豪地说“XX 是我们开发团队在时间紧迫的情况下,封闭开发了X 天就完成的!只有最牛的工程师才能创造这样的奇迹!!多少个凌晨,XX写字楼上只有我们办公室的灯还亮着……”,然后你会觉得“好感动啊”,但冷静下来想 一想,这种拼命赶工做出来的东西质量会过硬吗?抛开产品质量不谈,没有时间写文档、没有时间写注释、没有时间做 code review, 没有时间做阶段总结……没有了这些,作为一个开发工程师你通过这个项目可以提升多少呢?所以好多开发工程师一开始是“代码民工”,过了几年还是“代码民 工”,而一个人年富力强的时间又有几年呢?怪不得那么多人说工程师和妓女一样,都是吃青春饭的。

    发展方向

    我个人认为,国内的开发工程师大概有三个发展方向:1.做管理。 2. 去做架构等与产品关系不那么紧密的研发。3. 提升其它方面的能力,做 “A+ Player”,然后自己创业。我对管理没有研究,也没有兴趣,这里就不说了。研发我会在下篇中细说,这里主要说一下第三条。

    为什么要关注代码之外的事情

    如果你只会埋头写代码,那么代码写得再好也可能不会是一个好的开发工程师。做开发不是做学术研究,你的任务不是去钻研技术,而是利用自己的技术把产 品做出来。尽管技术能力是基础,但如果无法把能力很好地应用到开发当中,那么你在团队中就没什么价值。举个例子,如果你不能很好地理解产品需求,那么就会 根据自己的理解去做技术方面的架构和编码,等到后来发现了再去修改就特别麻烦,这个时候技术能力强反而成了坏事,南辕北辙的故事我想大家都听说过。

    很多开发工程师属于那种“很本分”的人,从来不会提出意见,不关心产品形态和细节,只是去做产品经理提出的需求。我觉得别人把工程师叫做“代码民 工”也就算了,但是工程师对自己做的东西完全没有看法,那就是甘心沦落为民工了。这也有文化的原因,国内的公司都喜欢那些不爱抱怨的员工,因为他们听话而 且符合中国传统的价值观,但我更喜欢那些爱抱怨并且抱怨得有道理的人,因为国内(不只是互联网上面)粗制滥造的东西实在太他妈的多了,不抱怨才不正常,有 不满才会去思考如何做得更好。

    曾经听到有人谈论如何管理技术人员的时候说:“管理技术人员很简单,找一个比他们都牛的人就行了。” 这个人很了解工程师的脾气。工程师去判断其他工程师的时候,往往只看他的技术能力,觉得谁的技术好谁就最牛,其它的都无所谓。没错,技术牛的工程师写的代 码质量很高,但这只是一个方面而已,判断一个人在团队中是不是“很牛”要看他对团队对产品的整体贡献,而不是他的个人能力。他能很好地理解产品需求吗?能 很好地理解设计师的意图吗?和团队其他成员沟通顺利吗?写出的代码方便测试吗?会对产品提出好的建议吗?……这些都是判断一个开发工程师的标准,整体素质 越高在团队中的价值也就越大。

    所以要想做一个好的开发工程师,就要在写好代码的同时努力提高其它方面的能力。我知道大部分的工程师都喜欢和机器而不是和人打交道,所以遇到和产品 经理、设计师以及 QA 等部门协调沟通的时候就皱眉头。协调沟通确实是一件闹心的事情,但从另一方面来说,这是开发工程师的一个得天独厚的优势:你可以深入接触产品生产线上的所 有环节。需求评审的时候,你可以了解产品设计;开发界面的时候,你可以了解到视觉和交互设计;测试的时候,你可以了解到产品测试的细节;上线的时候,你也 可以多观察 Ops 同事的操作。如果你可以在协调沟通的时候学会换位思考,多从对方的角度看问题,多想一下“他为什么要这么做”,那么不知不觉就会对各个领域有一些了解,进 而发现原来每个领域都大有学问,就不会因为周围那些学艺不精的人而轻视他们所在的领域。

    学习设计

    对于工程师来说,测试和上线都是技术性的工作,和开发有很多相通的地方,而产品设计、交互设计和视觉设计等设计领域则比较陌生。对于自己不了解的东 西,我们的看法往往会趋于两个极端:要么是看得高深莫测,要么是看得一文不值。其实对于大部分的东西,只要不笨并且愿意下功夫学习,总是可以学会的。尽管 达到大师的水平可能需要传说中的“天赋”,但做到中等水平并不是特别困难。关于设计领域我一直在断断续续地在学习,到现在可能连略窥门径也算不上,这里只 是说一下我个人对设计的理解和心得,供大家参考。

    产品设计

    产品设计看上去比较简单,因为只要清楚自己想要做什么,那么自然可以慢慢勾勒出产品的形态和功能。要做好产品设计,就需要平时多下一些功夫,多研究 一下互联网上那些已有的产品,另外还需要多看一些诸如社会学、历史等“闲书”,举个例子,假如你想开发一款针对台湾用户的产品,那么了解一下台湾的文化肯 定是有必要的。总之,学习产品设计是慢功夫,没有什么速成的捷径,只有一点一滴地不断积累才能培养出敏锐的产品意识和深刻的洞察力。

    工程师学习产品设计有一个优势,那就是设计出来的产品是自己亲手实现的,你可以在实现的过程中不断重新反思原来的设计,然后加以修改和完善。这就好 像写文章一样,很多时候你写东西的时候并不清楚自己具体要写什么,但只要是下笔开始写,写着写着就会发现新的想法,写作的过程同时也是思考的过程。写作和 写代码很像,它们不仅可以表达想法,还可以创造想法。

    视觉设计

    很多工程师听到视觉设计会立刻退避三舍,觉得自己“不会画画”、“不懂配色”是不可能学习视觉设计的。诚然,视觉设计是需要更多艺术方面的基本功, 要完全掌握需要长期的训练,但我们还是可以从简单的学起,慢慢培养对设计的感觉。我个人在这方面所知非常有限,但是对视觉设计中的完美主义印象深刻。

    编程的时候,如果你的某行代码多了一个空行可能不会有什么问题,但在视觉设计中差了 1 个像素或者 10% 的透明度就是不可容忍的,很多设计师要求的都是 “Pixel-Perfect”——像素级别的完美。如果你不苛刻地追求完美,几个这样的“小瑕疵”就可以把整个作品毁掉。在我没有接触过视觉设计的时候 很难理解这一点,切页面的时候并不会特别仔细地去看设计图,而且为了降低技术难度会想当然地篡改设计师的意图,比如把一些微小的渐变用纯色代替,这是很无 知的做法。所以当设计师要求你做一个 1px 的修改的时候,即使会花掉你几个小时的时间也要听他的——只有这样才可以把界面做到百分之一百的完美。当然,设计师自己做不到完美另当别论。

    此外,作为一个页面设计师,从职位名称上来看他的最终作品应该是页面,而不只是视觉效果图。所以我觉得页面设计师应该精通 CSS,只有自己才可以精确实现自己的设计意图。对于那些没有受过设计训练的工程师来说,很难注意到页面上色彩、字体和渐变的细节,让他们精确实现一个设 计师的意图几乎是不可能的。精通 CSS 对于页面设计师来说并不算一个过分的要求,很多国外的设计师甚至可以自己用 PHP 写出产品原型,相比之下,国内的页面设计师进化得实在太慢了。

    交互设计

    交互设计是有关行为的设计,它更关注如何让产品更好用。举个例子,网页中一般都有很多超链接,当你把鼠标移动到超链接上的时候,鼠标形状会变成手型,暗示它是可以点击的,而且访问过的超链接和普通超链接的颜色是不同的,这样就很好地引导了用户行为。

    之前我一直把设计和“视觉设计”等同起来,但在深入了解了之后发现,对于互联网产品来说,交互设计要比视觉设计重要得多,而且交互设计相对于视觉设 计也更加有迹可循,对“感觉”要求没那么高,工程师完全可以把重点放在交互设计上。如果交互设计做得好,视觉设计遵循一些标准,那么完全可以做出一款“不 难看并且好用”的产品。没有人特别夸赞 Google 的产品“好看”,但它们都特别好用,Google 注重的是易用、快速,用户体验是很棒的。

    互联网行业的大部分页面设计师(Web Designer)都是学习平面设计出身的,但我觉得网页和软件设计更像是“显示器里面的工业设计”。很多平面设计师设计出的页面很好看,好像海报一样, 非常适合打印出来,但往往对交互方面重视不够。不太好看影响不会很大,但不好用就没有办法留住用户,而且有时候太注重外观的视觉效果反而会分散用户的注意 力进而影响产品的使用,这种 “eye candy” 是糟糕的设计。现在专门培养交互设计师的机构不多,我很希望对互联网有兴趣的工业设计师们到这个行业中来。

    关于设计我就说这么多,以后有机会再另外撰文专门探讨这些主题。值得一提的是,没有人可以真正把设计和开发全部精通,如果深入到细节,无论设计和开 发都会占用你大量的时间和脑力。单从设计来说,需要掌握的就有颜色、字体排印(Typography)、排版(Layout)、交互设计等,其中每一种技 能又涵盖无数细节,真的是要皓首穷经才可以在其中的某个领域成为大师。不过,即使你对这些知识只是有一个大致的了解,以后在看一款产品的时候也可以从功 能、交互、排版、页面代码、整体性能以及URL语义化等各个方面进行全面而细致的分析,明白它哪里做得好,哪里做得不好,而不是在那里想当然地说“真酷” 或者“狗屎”。真正了解什么是好的什么是差的,自己做东西的时候才会心中有数。

    一专多能的好处

    很多人可能会说:“一个人要是可以把所有事情都搞定,那还要其他人干嘛?我更相信团队的力量。” 没错,一个人就算从设计到开发都精通,如果只有他一个人做东西,开发效率也不会高。但是若你真的花心思去了解那些“与代码无关的事情”,你就会在写代码的 时候更多考虑到产品经理/设计师的想法,对产品经理/设计师疏忽的地方也可以及时提醒,让自己真正地融入整个团队。目标并不一定要实现,它是用来指明方向 的。开发工程师提高自己的产品意识和设计能力绝对不会是白费心血,不然的话你就只是一个实现产品的工具。你只会回答别人提出的问题,而好的问题要比好的答 案有价值得多。

    当你各方面能力提高得差不多的时候,应该就可以出来创业了(注意,我说的是创业,不是去创业公司打工)。因为对各个领域都有一定的了解,平时也经常 接触到各个领域的人,那么在创业的时候你就很清楚自己需要什么样的产品经理/设计师,知道具有什么样能力的产品经理/设计师才是最好的,这样就可以从一开 始就保证团队的质量和气质。很多互联网的业界前辈都说过“要招聘最好的人”,但问题是你如何判断一个人是不是该领域最好的呢?如果一个人对程序和设计一窍 不通,满脑子都是商业运作,你觉得他有可能找出最好的工程师和设计师吗?有一次和一个创业公司的CEO聊天,他和我讲他们“只招聘 Geek”,后来我才发现他其实根本不知道什么是 Geek,只是不知道从那里听到 Geek 这个词,他真正想要的应该是那种只知道写代码愿意没日没夜任劳任怨给他当牛做马的人。国内大部分的创业公司就是这样,老大们喊着技术密集型的口号,实际上 做着劳动密集型的事情,金玉其外,败絮其中。你可以和他们不一样。

    我自己并没有创业的经历,也没有创业的打算,所以对创业的理解可能很片面而且天真。但是我相信,找到最好的人永远都是关键,不然即便后来成功了,也 不过是多了一家靠人数取胜的血汗工厂。假如你选择成为移动互联网的独立开发者,对一个产品各个环节的全局把握也是有必要的。如果一个团队的每个人都能独当 一面并且可以很好地理解其他人的意图和专业技能,就算最后在商业上失败了,那也会是一个幸福的团队,比那些除了盈利之外找不到任何亮点的团队好太多。

    对产品经理的偏见

    在“开发”这个小节的最后,我想多说一点自己对产品经理这个角色的看法。在国内绝大多数公司,开发工程师的作用就是把产品经理的想法以代码的方式写 出来,“代码民工”这个称呼倒是很恰当。我对互联网行业的产品经理们一直感到很奇怪:他们没有能力把自己的想法实现出来,但是却几乎总是认为自己比其他人 更理解产品;当工程师对产品提出自己的意见的时候,他们往往会心中不屑但尽量保持礼貌挤出微笑说一句:“呵呵,工程师不是普通用户”。一个产品本来就是需 要很多人齐心协力一起完成的,产品经理和工程师的地位也是平等的,但是由于产品经理在工作流的上游,所以情况往往演变成工程师在为产品经理工作。如果产品 经理真的对产品负责也就罢了,可惜的是大公司的产品经理大部分是对KPI负责,小公司的产品经理大部分是对老板的个人好恶负责,结果就是工程师跟在产品经 理屁股后面做一些莫名其妙的事情。我接触到的几乎所有开发工程师都对他们的产品经理头疼不已,据他们说,好的产品经理就像真正的爱情,是极为稀有和可遇不 可求的。

    按照现在大部分公司的分工方式,产品经理是产品的总负责人。根据我个人的理解,产品经理之于产品,应该相当于导演之于电影,建筑师之于建筑。一个导 演如果对拍摄一窍不通,那么就很难控制镜头的表现力;一个建筑师如果对建筑材料和结构一无所知,就不可能把握建筑整体的感觉。那为什么那么多人会觉得产品 经理可以不懂技术不懂视觉设计,只需要写好文档画个框图然后交给别人去做就可以做出好的产品呢?本来是一个需要对各个领域融会贯通最难做得好的角色,现在 反而被很多人视为清闲的差事,不爱干活的人纷纷想要转去做产品经理,实在是可悲至极。

    我一直坚信好的工程师是不需要产品经理的。如果一个产品非要有一个什么产品经理的话,Google 的很多产品都不会出现,DropBox 这种只招聘工程师的公司也早就完蛋了。很多伟大的产品都是几个工程师想到一个点子然后慢慢做出来的,比如 Paypal 和 Google. 但需要说明的是,我讨厌产品经理并不是说我推崇“技术导向”——无论怎样产品都应该是让用户使用的,而不是用来炫耀技术的,只不过工程师不需要产品经理也 可以设计好一个产品并且实现它。产品设计不是产品经理的专利。

    想知道懂得设计的工程师没有产品经理的时候可以做出什么东西吗?去看一下 Livid 做的 V2EX 就知道了。在国内,设计和代码都有品味的网站可不多,我觉得 Livid 同学真是开发工程师的典范。

    接下来我们说一下“研发”。

    下半部分请看这里