2010-12-25

GWT 2.1.1 現身

今年十月的時候,我們推出 GWT 2.1,當中提供了一個 framework 與一套工具,使建立商業網站的 application 變得很容易。從那之後,我們就從 GWT 社群那兒得到一些很棒的意見回饋,所以今天我們很高興地宣佈:這些意見已經整合到最新的版本 GWT 2.1.1 啦。詳細的內容如後所述,不過如果你有興趣的話,也可以從這裡開始。

對於 Eclipse 的使用者,只要點螢幕左下角的「Google updates available」的圖示,或是選擇「Help」→「Check for update」。

我們還更新了 Google Plugin for Eclipse 與 GWT Designer 。這兩個都可以透過下面的連結來安裝:
每個產品的主要更新如下:

GWT SDK
GWT 2.1 推出的 RequestFactory 廣受 GWT 開發團隊以及 GWT open source 社群的關注。基於這些回饋意見,我們增加了這些東東:
  • 支援 non-static service object 的 service layer。
  • 支援 value object。
  • 一個 request 可以包含多個 method 呼叫。

Google Plugin for Eclipse
  • SpringSource Tool Suite (STS) 來加強 UiBinder 的錯誤回報機制。
  • 透過移除沒用到的 Java builder 以及利用最新版 STS 當中的 AspectJ 修正來優化 IDE 的使用經驗。
  • 更新 Speed Tracer,在啟動前就完成完整的 J2EE publish。

GWT Designer
我們很樂意聽到你所提出的問題或是意見回饋,可以的話就到 Google Web Toolkit Group 暢所欲言吧!

2010-12-12

App Engine 團隊快樂放假啦——SDK 1.4.0 釋出

原文網址:http://googleappengine.blogspot.com/2010/12/happy-holidays-from-app-engine-team-140.html

技術校正、審閱:tkcn

App Engine 1.4.0 版出來啦!這是我們本年度最重要的一個版本,裡頭包含一堆開發人員已經等很久的超棒特性:
  • Channel API — 一個雙向的 channel,無須 polling 就可以直接 pushing notification 到在 client 端運行的 JavaScript,使 server 能直接跟使用者的 browser 溝通。這個 service 使用與 Google Talk 一樣的技術,可以很輕鬆地建立即時的 application,例如多人遊戲、聊天室、或是任何協同作業為主的 application。
  • Always On—對於高優先等級、但是低流量或不定流量的 application,現在可以用 App Engine 的 Always On 功能來保留 instance。Always On 是一個進階版的功能,每個月需另外付 $9。它可以為你的 application 保留三個 instance,即使沒有任何流量也絕對不會關掉。這減緩了小流量或是變動流量的 application 在 loading request 的影響。
    App Engine 管理控制台中的 Instance 頁面,當中 Always On 是開啟的。
  • Warm Up Request—這個功能會預測是否需要更多的 instance,並且在使用者的流量傳入之前就先載入新的 instance。如此一來,就可以減少處理 request 的時間。透過 app.yaml 或是 appengine-web.xml 可以對所有 application 啟用這個功能,當 application 已經購買 Always On 也會預設開啟。一旦啟用這個功能,當 application 開始處理使用者的傳輸量前所可能要載入的新 instance 時,warm up request 就會送出。

同時,我們花了很多時間在減少或移除現存 App Engine API 的限制:

  • 拿掉背景工作的 30 秒限制—在這個版本中,我們大幅提高來自 Task Queue 跟 Cron 發出離線 request 的時間限制。現在可以執行長達 10 分鐘的程式而不會中斷。
  • 加大 API call 的大小限制—新的 API 架構讓我們可以開始取消許多 App Engine API 的 1MB 大小限制。這次已更改下列 API:
    • URLFetch 的 response 大小限制從 1MB 提升到 32MB。
    • 批次存取 Memcache 的次數也提高到 32MB。
    • Image API 的 request、response 大小限制從 1MB 提升到 32MB。

Mail API 所送出的附加檔案大小限制從 1MB 提昇到 10MB。
你可以想像,這當中的某些改變大幅度地擴展 application 的範圍,並輕鬆地用 App Engine 來完成。所以,趁熱下載 SDK 吧!

2010-08-26

對戰吧~GWT 踩地雷! [下]

跟電腦(web server)對戰的故事大概是這樣的:
  1. 跟 server 要求開始一場遊戲
  2. 跟 server 取得遊戲資訊
  3. 根據遊戲資訊繪製畫面
  4. 將玩家踩的地點傳送給 server
    1. 命中地雷→更新遊戲資訊
    2. 沒有命中→輪到 AI 踩地雷→更新遊戲資訊
  5. 檢查是否有某方獲勝?
    1. 有→結束。
    2. 沒有→回到步驟 3
粗體的部份是跟 server 有關的部份。
那麼,以 RPC、或說以程式的講法
server 只需要提供兩個 method
  • startGame():負責開遊戲 
    • 回傳:該場 game 的 uid
  • shoot():傳送玩家踩的地點
    • 參數:uid, x, y
    • 回傳:GameInfo 物件
因為懶得多設計一堆有的沒的
所以開場之後讓 client 端程式自動踩 (-1,-1) 這個非法位置
就會進入到步驟 2~4 的循環當中 [逃]

好了,終於要進入到 GWT RPC 的部份了。
這裡打算跳過理論、架構的部份
直接以程式碼來說明一切...

首先,是要有一個 MineService 的 interface
繼承自 com.google.gwt.user.client.rpc.RemoteService
裡頭就是宣告上頭說得那兩個 method
@RemoteServiceRelativePath("mineRPC")
public interface MineService extends RemoteService {
  public String startGame();
  public GameInfo shoot(String id, int x, int y) throws Exception;
}
至於那個 annotation 先跳過,後頭會解釋

再來是一個對應的 interface,名稱通常是後頭補 Async
public interface MineServiceAsync {
  void startGame(AsyncCallback<String> callback);
  void shoot(String id, int x, int y, AsyncCallback<GameInfo> callback);
}

這兩個 class 必須放在 gwt compiler 會處理的目錄下(例如 client)

有 interface 自然有實做的 class
MineService 對應實做 class 通常叫做 MineServiceImpl
會長成這樣子:
public class MineServiceImpl extends RemoteServiceServlet implements MineService {
  private AI_Interface ai = new RandomAI();  //FIXME change your ai here!
  
  @Override
  public String startGame() {
    String id = UUID.randomUUID().toString();
    MineGM setting = new MineGM();
    setServer(id, setting);
    return id+",Random";
  }
    
  private void setServer(String id, MineGM setting){
    this.getThreadLocalRequest().getSession().setAttribute(id+"ID", setting);
  }
  
  private MineGM getServer(String id) throws Exception{
    String name = id+"ID";
    if(this.getThreadLocalRequest().getSession().getAttribute(name)!=null){
      return (MineGM) this.getThreadLocalRequest().getSession().getAttribute(name);
    }else{
      throw new Exception("還沒開局");
    }
  }
  
  @Override
  public GameInfo shoot(String id, int x, int y) throws Exception{
    MineGM server = getServer(id);
    if(x==-1 || y==-1 || server.getMap()[x][y]!=-1){
      return MineGM.toGameInfo(server);
    }
    
    if(!server.shoot(x, y, MineGM.USER)){
      int[] xy = new int[2];
      do{
        ai.guess(MineGM.toGameInfo(server), xy);
      }while(server.shoot(xy[0], xy[1], MineGM.AI));
    }
    
    setServer(id, server);
    return MineGM.toGameInfo(server);
  }
}

這個 class 還會繼承 RemoteServiceServlet
往上追溯,parent 是 HttpServlet
也就是說,MineServiceImpl 也是一個 HttpServlet
雖然 GWT RPC 很神奇地包裝好許多東西
但終究還是 base on JSP
所以寫這個 class 時,就不用管 GWT 的重重限制
只要 web.xml 有設定正確就好

講到 web.xml,回頭講一下 MineService 的 annotation
用 RemoteServiceRelativePath 設定 servlet-mapping 會比較方便
<!-- in web.xml -->
<servlet-mapping>
 <servlet-name>mineRPC</servlet-name>
 <url-pattern>/_mine/mineRPC</url-pattern>
</servlet-mapping>

url-pattern 的值,前半段 _mine 是在 gwt.xml 中
設定 <module rename-to='_mine' >
後半段 mineRPC 就是在 MineService 設定的值
(沒有用這個 annotation,得要多好幾行煩死人的 code)

至於遊戲資訊,我選擇塞在 session 當中
在 RemoteServiceServlet 要取得 session 比較囉唆一點
得要這樣才能取得 session
this.getThreadLocalRequest().getSession()
其餘的程式碼... 嗯... 不在 GWT 的範圍當中,跳過 XD

server 端的程式碼解決了,現在來看 client 端
client 端必須透過 MineServiceAsync 來呼叫 RPC
不過用法有點奇怪...... Orz

首先要先這樣寫,取得一個 MineServiceAsync 的 object
MineServiceAsync msa = GWT.create(MineService.class);
然後就可以用 msa.shoot() 來告訴 server 要踩哪個位置
但是事情還沒完,除了標準的 parameter
得要傳一個為 AsyncCallback 的 parameter
這是讓 server 端處理完畢後可以 callback 的一個手段
初期通常都會用 anonymous class 來解決
所以程式碼會長得像這樣:
msa.shoot(this.gameID, hitX, hitY, new AsyncCallback<GameInfo>(){
      @Override
      public void onFailure(Throwable caught) {
        Window.alert("shoot : "+caught.getLocalizedMessage());
      }

      @Override
      public void onSuccess(GameInfo result) {
        setShootResult(result);
      }
    });

這邊要注意兩件事情

其一,RPC 傳遞/回傳的 class(及其 field)
除了 primitive data type 外
一定得 implements IsSerializable
還必須是 GWT 允許的 class
此外,自訂的 class 還必須讓在 GWT compiler 會處理的目錄下
不然實際跑起來就會有(很難看懂的)錯誤訊息

其二,呼叫完 msa.shoot() 之後
下一步並不會執行 onSuccess()/onFailure()
更正確來說,在寫 client 端程式碼的時候
並不會知道 onSuccess()/onFailure() 什麼時候會呼叫到
端看 server 處理以及網路傳輸的速度... etc
這是 callback 的特性,在踩地雷的 case 當中並不會造成困擾
但在其他實務上,如果發現怎麼 RPC 回傳值都不正確
那大概就是忘記這個性質所導致的...... Orz

喔對... 都忘記講一個大前提了
要用 GWT RPC,server 必須是 JSP container

同時也來講講 GWT RPC 的好處 \囧/
一言以蔽之就是「通通傳便便
client 不用組 query string 或 post 內容
server 端不用準備對應的 url
client/server 都不需要剖析傳遞的資料
甚至可以傳遞(客製化的) exception
在 client 端的 onFailure() 可以分門別類處理......
程式碼看起來、寫起來都很 OO、都很 Java
以一個 Java Programmer 來說,有什麼比這個更快樂的事情呢? XD
(連 xml 都沒有用到呢! [握拳])

好了,「對抗電腦版的踩地雷」拆解到這裡
只剩下地雷區要設定 click 的 handler
收到 GameInfo 之後要更新畫面
以及電腦 AI 設計這些功能
相信你一定可以自己寫的很開心的,就跳過不細談了

如果你想偷懶想拿寫好的程式碼來執行看看,
可以到這裡下載。也歡迎投稿你的 AI 設計

Enjoy GWT and have fun! \囧/

2010-08-23

對戰吧~GWT 踩地雷! [上]

上一次教完如何寫一個生命遊戲(好久以前啊 [遠目])
這次的題目同樣是陣列系的踩地雷
不過,如果單機自己玩也太無聊了點
所以呢... 這次的目標是「對抗電腦版的踩地雷」!
這個題目有點大,讓我們一步一步慢慢來......

首先是先弄出一個 MineGM 的物件
來負責創造地雷世界、運作規則邏輯
所以 MineGM 必須要有這些 field
public static final int UNKNOW = -1;

private int x;
private int y;
private int total;  //總共幾個地雷
private int remainder;  //剩下幾個地雷
private boolean[][] answer; //地雷分佈圖
private int[][] map;  //玩家看到的地圖
private int[] playerHit = new int[2]; //分別踩了幾個

一開始就用亂數把 answer 準備好
至於 map 的內容一開始都是 UNKNOWN,表示還不知道是啥狀況
map 當中還可能出現:
  • 0~8:九宮格內出現的地雷數
  • 9:玩家踩到的地雷
  • -9:電腦踩到的地雷
而 MineGM 還需要有一個 public 的 method「shoot()」
負責接受玩家的輸入、然後回報是否命中
public boolean shoot(int hitX, int hitY, boolean who){
  map[hitX][hitY] = count(hitX, hitY);

  //踩到空地的連鎖反應
  if(map[hitX][hitY]==0){
    for(int i=-1; i<2; i++){
      if(hitX+i==x || hitX+i<0){continue;}
      for(int j=-1; j<2; j++){
        if(hitY+j==y || hitY+j<0){continue;}
        if(map[hitX+i][hitY+j] != -1){
          continue;
        }else{
          shoot(hitX+i, hitY+j, who);
        }
      }
    }
  }

  //不同人踩到地雷要給不同值
  if(map[hitX][hitY]==9){
    remainder--;
    if(who){
      playerHit[0]++;
    }else{
      map[hitX][hitY]=-9;
      playerHit[1]++;
    }
  }
  
  return Math.abs(map[hitX][hitY])==9;      
}

count() 會計算周圍九宮格有幾個地雷
把回傳質設定到對應的 map 上
另外,因為我採取「answer 的周圍多一格空地」的作法
所以「踩到空地的連鎖反應」那段的迴圈可以比較好看一點

雖然已經有 MineGM 建立、維護地雷世界了
但是,我們還是需要另外一個 GameInfo 來包裝給玩家的資訊
不然如果玩家 or 電腦直接拿 MineGM 的 answer 來作弊怎麼辦? Orz
所以在 MineGM 當中弄了一個 static method 來轉換成 GameInfo
public static GameInfo toGameInfo(MineGM server) {
  GameInfo result = new GameInfo();
  result.setMap(server.getMap());
  result.setRemainder(server.remainder);
  result.setTotal(server.total);
  result.setPlayerHit(server.playerHit);
  return result;
}

至於其他的細節就留給大家慢慢寫了......

接下來處理 UI 的部份
這次使用 GWT 2.0 的 UiBinder 來處理排版
使用方法可以看官方文件痞子版的中文翻譯

預計的遊戲畫面長這樣:

上方是數據區
左右兩側是雙方的名字與分數,包了一個 PlayerInfo 來處理
其實很簡單,剛好適合拿來了解 UiBinder

PlayerInfo.ui.xml:
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
  xmlns:g="urn:import:com.google.gwt.user.client.ui">
  <ui:style>
  .title{
    padding-left: 5px;  
  }
  </ui:style>
  <g:FlowPanel>
    <g:InlineLabel ui:field="title" styleName="{style.title}"></g:InlineLabel>
    <g:InlineLabel ui:field="hitCount"></g:InlineLabel>
  </g:FlowPanel>
</ui:UiBinder> 

PlayerInfo.java
public class PlayerInfo extends Composite {

  private static PlayerInfoUiBinder uiBinder = GWT.create(PlayerInfoUiBinder.class);

  interface PlayerInfoUiBinder extends UiBinder<Widget, PlayerInfo> {
  }

  @UiField Label title;
  @UiField Label hitCount;
  
  public PlayerInfo() {
    initWidget(uiBinder.createAndBindUi(this));
    setHitCount(0);
  }

  public void setHitCount(int i) {
    hitCount.setText(""+i);
  }

  public void setName(String name){
    title.setText(name+":");
  }
}

中間是還剩下多少地雷,用一個 Label 解決
下方的地雷區則是用 FlexTable 處理
這些東西都放在 MineMain 這個 class 當中
所以 MineMain.ui.xml 會長成這樣:
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
  xmlns:g="urn:import:com.google.gwt.user.client.ui" xmlns:m="urn:import:org.psmonkey.product.client.mine">
  <ui:style>
  .playerInfo{
    width: 480px;
  }
  .cpu{
    width: 220px;
    background-color: red;
  }
  .player{
    width: 220px;
    background-color: #64A0C8;
  }
  .remainder{
    width: 40px;
    color: white;
    background-color: gray;
    text-align: center;
  }
  </ui:style>
  <g:VerticalPanel>
    <g:HorizontalPanel styleName="{style.playerInfo}">
      <m:PlayerInfo ui:field="cpu" styleName="{style.cpu}"></m:PlayerInfo>
      <g:Label ui:field="remainder" styleName="{style.remainder}"></g:Label>
      <m:PlayerInfo ui:field="player" styleName="{style.player}"></m:PlayerInfo>
    </g:HorizontalPanel>
    <g:FlexTable ui:field="map"></g:FlexTable>
  </g:VerticalPanel>
</ui:UiBinder> 

很懶惰地用 VerticalPanel 跟 HorizontalPanel 解決 XD

接下來,就要處理跟 web server 之間的溝通了! [待續]

2010-08-12

碎唸 ptt 上的管理

好了,文章開頭就直接自爆來意
以通俗的說法,就是:我是以小組長的身份來官官相護的

後頭的廢話懶得看的人,我可以把結論寫在前面:
除了執法尺度不一致、違反上層法規的申訴外
遇到版務爭議的預設值是支持版主

只要版主有確實執行,版規要定的多奇怪
我會支持,不會干涉

※   ※   ※

我相信這篇大多數人而言,沒啥建設性(破壞性倒是有?)
因為絕大多數是我管 CompScience 這幾年來的 murmur

※   ※   ※

在這個 thread,cleanwind 說了一個很妙的比喻
「CodeJob 像是接案方的家,卻像發案方的動物園。」

如果以 design pattern 的角度 [誤]
管理者跟看板使用者(以下簡稱鄉民)也還蠻適用這個譬喻
管理者像是動物園裡頭的動物
鄉民像是遊客,而且還不用買票進場

管理者的一言一行都要被檢視、要被批評
慘一點的會被批鬥、甚至要被思想改造
當然,這或許是管理者必須要付出的代價 or 義務 or whatever
畢竟權利跟義務通常是對等的
只是,如果以此標準把版主的權利跟義務放到天平上衡量
嗯... 大概版主幹個一年可以增加 2% 上天堂的機率這樣?

但是鄉民呢?

我不是說鄉民不能發表意見
而是,以經驗法則來說,有太高的比率
鄉民因為短暫的不爽,黑特、亂版、申訴、罷免
有付出什麼代價嗎?

保險一點的作法就是負責擾亂一池春水,誰也拿你沒轍
就像 F23ko 的推文,堪稱典範:

吃雞排,看熱鬧,等罷免。
我對這個版的好壞根本沒興趣,也沒心力去付出。
只是看這版主的個性,出狀況、起衝突是遲早的事。

激進一點的,養個帳號出來搞
反正另一個帳號 or 重新註冊又是一條好漢

但是管理者得付出多少代價來處理?
(答:經歷一次事件可以增加 0.5% 上天堂的機率)
版主搞掉幾個?
看板的品質被拖累了多少?

我們真的要理會這種人嗎?

※   ※   ※

回頭看一下版主上任的相關時間表

→2010.07.27 下午,我張貼徵版主的公告
→2010.08.03 凌晨,WolfLord 在 Sub_CS 張貼申請書
    當天,這裡的版標就已經改成
    「新版主政見已貼在 Sub_CS 版」(到現在還沒改是怎樣? [指])
→2010.08.09 半夜,WolfLord 正式就任

徵版主至今,將近兩個禮拜的時間
除了 NotOnSale 那篇不會被受理的文章跟推文
有任何人提出「我要當版主」的申請跟詢問嗎?

從 WolfLord 申請至今,將近一個禮拜的時間
有任何人提出質疑、提出管版理念不妥的地方嗎?

是因為資訊被隱藏起來嗎?
是因為不開放討論嗎?
是因為給的時間不夠多嗎?
是因為 WolfLord 申請書跟實際版規有出入嗎?

我想,這些答案都是「不是」,那麼
為甚麼在正式上任之前都沒有任何(反對)意見
而等到木已成舟之後,才抱怨這個、詰譙那個
還可以批評版主「自我感覺良好」?

有機會發言不發言、有機會掌權不掌權
然後結論確立之後才吵吵嚷嚷要翻案
別人執掌方法不合己意就指責人專制獨裁
這真的是為這個「組織」好而產生的努力嗎?
還是根本就是自我中心、自我感覺良好的作為?

我們真的要理會這種人嗎?

※   ※   ※

我超討厭推文一次超過三行的人
我超討厭連狀聲詞都不能用注音符號的規定
我超討厭案主發案不用寫基本預算

然後咧? 那又怎樣?

你超討厭噓文會被水桶
你超討厭發案之後要回來 update/delete 不然會被劣文
你超討厭水桶解除要寫悔過書

然後咧? 那又怎樣?

版規要怎麼定,那是版主的自由
精準地說,站規賦予版主的權利
從免費入園的遊客變成被觀賞的動物,換來的權利

從另一個角度來說,因為遊客(可能)來來去去
而必須始終都在的,是被觀賞的動物
而必須「面對音樂」,是被觀賞的動物
所以版規要怎麼定,是由版主作決定

需不需要聆聽採納鄉民的意見? 不需要

這就像 Java 跟 .net 哪個好,可以沒完沒了的爭論
你還是得決定這個案子要用哪一個
又或著說,今天說 Java 好的鄉民比較多,所以用 Java
明天說 .net 好的鄉民比較多,所以改寫成 .net
案子這樣搞能不出事嗎?
那為甚麼「管理」這檔事就可以這樣搞?

更進一步講,一個決策必須正反論點都考慮清楚
或是簡稱「要有配套措施」
壓根不是光看哪邊人比較多就能解決的事情

獨裁? 獨裁又怎樣?
這世界上不採取民主制度的組織團體多的是
如果一群腦袋不清楚、說話不用負責的人搞民主
能得到好的結果,只能解釋為神蹟

在 ptt 改站規 or 鄉民講話得付出代價之前
我會贊成、甚至鼓勵版主獨裁

※   ※   ※

日劇《奇蹟餐廳》
來用餐的客人都是國王
但是,不要忘了
國王,也是有被砍頭的
※   ※   ※

原文寫於 2010.08.12 ptt.cc 的 CodeJob 版。由於新版主 WolfLord 的治版理念十分特別,才剛上任就引發不少議論,因此碎念出這篇......

2010-05-20

GWT 2.1 M1 登場!

原文網址:http://googlewebtoolkit.blogspot.com/2010/05/gwt-21-milestone-1-is-now-available.html

GWT 2.1 M1 版當中,你可以更進一步地以雲端方式建立 business application 但卻如以往一樣容易。使用 GWT 新的 data presentation widget 以及 MVP framework,你可以弄出很好看的 web application、可是執行起來卻很快——無論你是要操作 25 筆資料還是 2500 萬筆資料。我們將這個 widget 設計的非常 lightweight 來達到這個目標,不用 compostie 跟 widget 改使用 DIV 跟 HTML;而 framework 讓你可以很容易地僅在需要的時候只抓取你所要的資料。

為了讓你建立 web application 更快一點,現在你可以使用 VMware 的 Spring Roo ,僅僅使用幾個指令就可以建造一個功能性十足的 application。這些 tools 都已經在  SpringSource Tool Suite (STS) 上可以取得了,包括 Google Plugin for Eclipse、App Engine SDK——提供你一個整合性的開發環境。

最後,當你使用 GWT 開發時,做出來的 application 就是一個 HTML5 的 application 了。這表示已經準備好可以開始進入雲端計算的領域了。

想開始行動了嗎? 看一下我們的商業版 GWT,以及 2.1 版將會有哪些東西?兩個 GWT codesite 上的章節吧!

2010-05-04

徵短期 Java Programmer

工作內容
在我的虐待下跟我一起用 Java 寫 web

無須承擔 project 成敗、需求分析等雜事
也無須作任何 layout 以外美術方面的工作

我盡可能做到標準上班時程
但是遇到 trouble 的話就還請共體時艱
(我一定不會比你早下班就是了,這可以保證 XD)

工作地點不確定,有可能在某間公司、我家
或是長期進駐某間咖啡館...... XD

工作時間可能可以不固定,但是 co-work 是一定的
(要不要 pair programming 就再看看)

理論上下禮拜一(05.10)開始
預計執行兩個月、可能會延長不會縮短

工作地點
台北縣市

聯絡方法
pt2club (gmail)

來信只要留下聯絡方式、這幾天方便面談的時間即可
請不要附學歷、證照清單
想要推薦自己的話
請以 project 經驗、blog 網址、ptt 文章代碼... 等方式
實際丟 code 更歡迎(雖然最近沒太多時間看 Orz)


必備能力
  • basic J2SE、OOP skill
    • 至少迴圈要會寫、物件要會包
    • 知道 interface 跟 abstract class 差別
  • Eclipse 操作
  • 中英文盲打
  • 使用 gmail

進階能力(不會也沒關係,但是會用到這些)
  • GWT
  • basic sql
    • 目前不考慮用 or-mapping,所以還是要下 sql
  • Servlet、Filter
加分條件(依重要順序)
  • 社團經驗(登山社尤佳)
  • 龜毛 programmer
  • 文字紀錄控
  • SD 經驗
  • 熟悉 google 相關服務(sites、docs)
待遇
30K/month 以上,無勞健保、不用扣稅
如果Programming 能力很好、能幫我少掉很多煩惱
薪水我是不會吝嗇的

另外,補一下嘴砲層的待遇
因為要作的是一個 prototype
如果反應好會有後續發展......

附註
真正的案主不是我,所以薪水不是我發的
(如果薪水有差額,會用我的薪水補)
需求訪談、需求分析也有人處理
而跟 coding 有關的事情由我負責

我個性不太好,所以
如果連 Java 版的風格都不能接受的話
那請千萬不要浪費時間來應徵
因為實際 work 起來絕對會更龜毛個幾倍

除了 GWT,這個 project 也不會用到什麼高深技術
一來是 prototype 不必要徒增風險
二來是我也不會啥高深技術... [無誤]
不過對程式碼品質、文件的要求卻非常重視
如果只習慣寫出能 run/compile 就好的人,也請不要來應徵

2010-04-02

GwtQuake:將 Web 提昇到下一個境界

原文:http://timepedia.blogspot.com/2010/04/gwtquake-taking-web-to-next-level.html

技術校正、審閱:tkcn

早在 2009 年 11 月的 GWT 高層腦會議上,我和 Joel Webber 就專注於改善 Web 和 UI latency 議題。有人在介紹 WebGL 時,我發現坐在後頭 Joel 看起來完全沒在聽。當我走近他時,他說:「看過這玩意了嗎?」他晃了晃 notebook 展示 Jake2——將 Quake2 用 Java 移植成 Java Web Start application。幾年前我就看過這個 project 了,只是不知道已經進展到這種地步。

就在那時,我不用問也知道他想幹麼——他提議我們用 GWT 改寫 Jake2!


Web 化的方式
「將 Jake2 這類的東西,用 GWT 來 compile 成 JavaScript」的想法,看起來好像很簡單。因為 GWT 本來就是把 Java compile 成 JavaScript。不過要能 compile 仍需要對程式碼作一堆調整、以及將 I/O 對應到合適的 Web API。

你可能已經知道,GWT 其實只有實做 JRE 的一部份,而 Jake2 使用了很多 GWT 當中沒有的 Java class:

  • 3D 的 LWJGL library
  • java.nio.Buffer
  • 網路對戰需要用的 java.net.*
  • AWT 中關於鍵盤的內容
  • 載入資料要用到的檔案系統 API
  • 影音要用的 OpenAL

更重要的是,它是用 synchronous API 作 I/O,但是 JavaScript 是使用 event driven 的 I/O 方式。

I/O 對應
Joel 將 Jake2 的 I/O 系統重新改寫成用 event driven pump 來載入檔案。我們做的第一件事就是處理關卡與 model 的檔案載入,使用 2D <canvas> wireframe renderer 來處理 rendering(沒有用 WebGL)。接著 Stefan Haustein 貢獻了使用 WebGL 的 javascript array 來實做 Java NIO Buffer 的 JRE class,以及用 WebGL 為基礎的 GL renderer。

Joel 很神奇的用 WebSocket 加上了多人對戰的功能,即使測試的人分散在 Atlanta、Mountain View、甚至是 Sydney(雪梨),我們還是廝殺的很爽。

我繼續轉換所有的聲音成 MP3 格式,並且實做了只用 DOM Audio element 的初步 sound renderer。後來,我把 OpenAL 程式碼重構成一個(譯註:GWT 的)base class,並且提供了 OpenAL Inverse Distance Clamped 聲音 model 的實做,用來調整 3D 位置中的音量變化。除了 audio.volumn 之外,沒有用其他東西。

Stefan 使用 LocalStorage 來實做 RandomAccessFile 供遊戲存檔用,以及用 <video> tag 來實做 video 播放。

大部分的工作在今年一月用 part time 的方式完成,但在那之後進度就卡住了。例如 Stefan 就專注在移植整個單人遊戲(無須 server)成靜態的內容。

我會在 Google I/O 上頭作更詳細的解剖報告,請務必參加!

執行速度呢?
我覺得執行速度可能不快,不過說實話,我從來沒想過我們會做到這種程度;但事實證明,正常播放的 framerate 是有達成的。在 MacBook 上的 WebKit/Chrome 有 20~25fps、在 Mac Pro 桌機則有 45fps。Joel 則說在 Linux(notebook)可以到 60fps。

這對 web 的意義是什麼?
多年來,人們總覺得 browser 對這類事情而言,是一個貧乏的平台,所以需要 Flash、Silverlight、JavaFX 或 native 程式碼。儘管你不應該期望在瀏覽器上頭看到像《Far Cry》或《Call of Duty》這種頂級的作品,但這不能解釋為甚麼一堆 casual game 是用 Flash、或是用 Objective-C(在 iPhone/iPad 上)寫的,而我們使用的類似技術卻作不到。

此外,因為是網頁,你要邀請某人進入你的遊戲或分享遊戲,只要傳一個網址就可以了。你可以只接 tweet 你的遊戲網址、點下去就可以加入遊戲當中。而不需要安裝。雖然 Flash 也可以做到,但直接在 browser 當中會覺得比較「自然」。

我希望這個移植可以鼓勵某些人變得更大膽、更瘋狂地嘗試建立各種 Web App,因為如果 Quake2 用 JavaScript 跟 browser API 都辦得到,更令人驚艷的 application 正等你開發出來。所以,開始動手吧!

移植版的 source code 在:http://code.google.com/p/quake2-gwt-port

----
譯註:關於 quake2 的 GWT 移植版,在 GWT 官方 Blog 也有提到,不過該篇文章沒有這篇仔細,所以選擇翻譯這篇。雖然這個時間點實在令人遐想,不過在 Google Code 上頭的確有「非常真實」的程式碼可以觀看,有興趣者不妨試試看。

2010-03-26

App Engine SDK 1.3.2 發佈

今天,我們很高興宣佈 App Engine 1.3.2 版(Java 與 Python 版)正式發佈。1.3.2 版包含數個改變以及 bug 修正。

在這個版本當中,我們全神貫注在解除許多已經影響到開發者的限制:

  • Blobstore API—提供一個新的 method(Python 用 fetch_data,Java 用 fetchData)使你的 application 可以用程式讀取 Blob 的內容。
  • URLFetch API—你可以存取的 port 增加了。現在可以存取的 port 為 80~90、440~450、1024~65535。
  • Mail API—我們放寬了郵件附加檔案的種類,包含了常見的文件種類:.doc、.ppt、.xls。
  • Task Queue API—我們把 Task Queue 的 refill 率增加到每秒 50 次。

我們也很高興地宣佈,根據你們的回饋意見,App Engine 有了一個新的 DoS 阻斷系統。這個系統可以讓你把特定的 IP 存取你的 application 時列入黑名單當中,以防止他們浪費你的錢或是佔用你的 quota。你還可以在 Admin Console 看到最常存取你的 application 的 IP,以幫助你找出你想要阻擋的 IP。 關於這個功能的更多資訊,請參閱下列文件:Python 版、Java 版 。

這個版本還有很多改變跟修正,包含新 Java 版的 Appstat profiling tool。詳細的資料請閱讀版本更新紀錄(Python 版Java 版),並下載新版的 SDK

2010-03-06

外包大學生專題的經驗談

我是跟著鄉民進來看熱鬧的,因為聽人說有人打算搞這行。我只能說......
太有創意啦...... [完全誤]

我靠學生的錢吃飯已經有六七年了,客戶遍及國立、私立、甚至米國的大學。論文實做、專題、作業、連線上代考都幹過。金額從三四萬到三百塊都有、也有一次一個晚上 A 了一萬。

假設你相信我這些「資歷」,那或許我有資格來這個臥虎藏龍的地方,打一下嘴砲。(反正閒著也是閒著,不差多一個人來搶生意)

作這門生意,你必須要想清楚很多事情。當然,不是說作別的生意不用仔細盤算。只是書店裡那些軟體工程教科書 or 創業指南,一點洨用也沒有,沒有人會著書寫如何接學生的案子...(嗯? 有沒有出版社要跟咱家合作啊? [炸])


先把結論寫在前頭:
如果你的目標是練功,那麼這門生意值得作
如果你的目標是賺錢,沒意外的話,你會餓死

好了,如果你打算練功 or 打算餓死,那可以繼續往下看。一般軟體案子會遇到的問題就跳過,這邊只講學生客戶才會出現問題。


首先你要面對的是種種「無法累積」的困擾。這點很可怕,尤其是你有點良心又想長期作這門生意。

你沒有辦法直接展示寫過的 project、也沒有辦法告訴下一個客戶你之前的豐功偉業。更不用說,這些 project 的內容很多可能根本不值一提。

即使你做的很好,客戶通常不會有後續的案子,更不用說客戶不會介紹其他客戶給你(至少我沒遇到過),所以你必須一直一直一~~~直地找尋新案子。偏偏這門生意的客戶跟援交妹一樣:你覺得學校裡頭一定有、但是不知道是誰?不過你倒是可以大聲的喊:「誰有專題論文作業要外包?」不怕警察抓。通常客戶們也怕見光死。


再來你要面對的是「隔山打牛」的困擾,因為絕大多數的時候,跟你談 case 的不是真正的案主,真正的案主是他的指導教授。指導教授普遍都有「朝三暮四」的攻擊靈氣,直接無視對手防禦力,搭配「朝令夕改」技能簡直 imba。

最慘的是,你的客戶天生體質就弱,不然也不會用現金招喚你出來對抗指導教授。表達能力不好、思路不清是常態;根本搞不懂 or 搞不定指導教授表達的內容,會讓你懷疑客戶到底是站在哪邊的?你只能從蛛絲馬跡隻字片語當中找尋線索推敲真相,然後怨歎你爸為甚麼沒有在夏威夷好好訓練你。

當然你會說政府的案子也是常常出現官大學問大、翻臉不認帳。我沒自己接過不知道,我只能說,至少你手上還有合約書之類的文件,可以上法院賭賭看。


如果你撐到這邊還沒有打消念頭,那麼接下來你會遇到進階版「無法累積」的困擾。因為客戶到底要作哪個領域你無法控制,有得作就不錯了,哪能挑?

所以這個案子要搞懂 clustering 跟 classification 的差別、那個案子要用 GA 作生產線的排程規劃、下個案子則要把 SNMP 的訊息塞資料庫然後分析...... 要不是嫌棄高雄太遠案主太正,不然 Network_Sim 的開版版主就會是我了 [誤]

當然,如果你天生喜歡學習新東西倒也無訪。只是不要忘了,你學那些 domain knowledge 的時間也是成本,而且之後用到的機率實在不高。

再更進階版的「無法累積」是你個人的實力。基本上客戶只會指定語言,指定開發環境、指定 framework 很少見,至於軟體品質之類的鬼玩意,一概不管。反正價錢能多低就多低、交件速度能多快就多快、demo 的時候不要當掉就好了。根本不用考慮後續維護——因為有也是學弟妹的問題,更不用說軟體工程層的文件產出,弄出個需求分析書當合約就算不錯了。

當然也不是沒有在大公司上班數年,程度仍然像大一的人存在,只是公司多少會要求、會有 CMMI 之類的鬼東西要面對。

大概是這樣了。

當然,這門生意還是有好處的。風險很低,一般來說,價錢談得成就能順利結案,因為客戶想結案的心情比你還殷切。我從來都不先收錢(包含訂金),至今也沒有失敗案例(當然,這跟我很挑案子也有關吧 XD)

客戶們通常也不要求美術設計,或著說,這方面丟回去給客戶作就可以了(他們通常對「如何做出絢麗的報告封面」很在行)。這對美術沒及格過的單打獨鬥程式設計師而言,很方便。

剩下的是一些零碎的小指南

  • 整體 CP 值最高的是論文實做、最好處理但最少出現的是作業(同學之間的友情,無價!)專題... 最常出現但是變數最大、風險最高
  • 工工系所是值得深耕開拓的客戶群,因為他們常常需要寫程式,但是基本上沒有教程式,所以比較能公開展示、甚至是教授示意的。
  • 不要相信客戶講的任何一句話,尤其是資訊系所出身的客戶跟你說:「你幫我解決大架構,小細節我可以幫忙寫,算我便宜一點」
  • 想靠這個賺錢,還不如去研究大樂透要怎麼簽

以上! [逃]

備註:

  • 原文寫於 ptt.cc 的 Soft_Job 版。其實我很少去、也很討厭那個版的內容跟風氣。只是這個題目實在...... 太熟練了 XD
  • imba 是遊戲術語的台語英譯,接近「主角威能所向無敵」的意思。「夏威夷訓練」則是出自《名偵探科南》。

2010-02-19

I Have a Dream:資訊領域翻譯專版開版理念

原文寫於 ptt.cc 的 Sub_CS 版 2010.02.15

很俗爛的開場白,不過
這個想法掛在 CompScience 的進組畫面很久了
也就借用這老套的標題吧...... [茶]

我有一個夢... 希望能有一個屬於 Computer Science 的翻譯版

這個版會介於 Computer Science 跟英文翻譯這兩個專業領域之間
以 Computer Science 專業為主,英文翻譯專業為輔
內容主要是討論資工、資科領域所遇到的英文問題

是的,英文問題。

英文已經統一電腦的世界了
ASCII 是英文字、導致主流的 program language 也都是英文
即使是非英語系的人們,開發出來的軟體
可能不見得有母語的 doc,但是一定會有英文的 doc

我們在閱讀的時候,困難點未必是自身的英文程度
(ㄜ... 當然,我是很困擾啦,畢竟高中四年英文從來沒及格過 XD)
而是被那些脫離一般用法的詞彙或句型所混淆

舉例來說,當初學者第一次看到 Object-Oriented 的文章
可能會很想死,例如:
A real instance of a class is called an "object"  [1]

instance 是啥? class 又是啥?
萬一英文不太好的人,可能還會順便懷疑
real 是不是「實數」? call 是不是「呼叫」?
明明想搞懂一個名詞,結果跑出來更多名詞
明明每個字都認得(都查得出來),但是放在一起就完全不懂
(我不知道母語是英文或是英文很好的人會不會有這種困擾)

這只是剛開始,如果在資訊領域待久了
另一個麻煩的來源,是很多不同領域會用相同的名詞,但是意思根本不一樣
就假設有一篇教 JSP programmer 如何處理 CSS 相關議題的好了
class 是... 啥? filter 又是... 啥? [2]

如果剛好這篇文章又扯到 Java 中的 class 跟 JSP 的 filter
沒有標示清楚,豈不叫人發狂?


除了面對英文會煩惱,很多時候,
我們面對(廣義的)中文會更煩惱
一個類別的實例稱為物件

或是簡體版的
一個類的實例稱為對象

說真的... 這 到 底 是 什 麼 鬼.... <囧>
徹底的中文翻譯,反而會造成我們哪天要去閱讀原典(?)的極度障礙
那麼... 該翻? 不該翻? that's a question......


最後,是比較敏感的話題,簡體中文

等等,我不想挑起什麼政治問題、誰優誰劣... blahblah
我只想陳述事實:
我用繁體中文、ptt.cc (主要)用繁體中文

當然,繁體簡體,畢竟同是中文
現在用機器作繁簡轉換,似乎可以得到不錯的結果(至少比英翻中好得多)
問題是,如果我們寫文章都(還是)用繁體
為甚麼我們要看從簡體轉換過來第二手(甚至第三手)的資料?

往更源頭、也更嚴肅的方向想:
難道我們已經開始倚賴簡體中文的資料了嗎?

「語言」的使用趨勢,是西瓜效應的經典範例
如果能提供一個園地,讓被英文 or 技術名詞所苦的人能夠交流
在遇到翻譯障礙時,有一個補給後援
是不是能讓繁體中文的西瓜長得大一些?
至少我們不會想仰賴簡體中文,而有讀原文的勇氣

啊? 那為什麼寫這篇? 直接申請開版就好啦?
這說來有些話長...
一是近來開的版,似乎生意都不怎麼興隆 XD
二是自己身兼小組長,總不能自己申請自己審,又不想勞駕群組長
三是自己智寡德薄,手上幾個版也沒有管得很好
加上英文實在很差、資訊領域也讀的不太像樣......

所以,在新年許個願望
希望有勇者能夠嚴謹而盡責地實現

以上...... [遠目]

備註:


  1. 這句不是我掰的,是來自於這個網站 http://searchsoa.techtarget.com/sDefinition/0,,sid26_gci212681,00.html
  2. class 跟 filter 在 JSP 跟 CSS 都是專有名詞。而且實際意思幾乎完全沒有雷同之處(尤其是 filter)

2010-02-18

Buzz?Buts?Bugs? Bums?

Buzz
前些日子,要進 GMail 的時候會跳一個「要不要加入 Buzz 功能」的畫面。Google 正式推出的東西,當然是會想要用用看的。於是,一個早上就栽進去了,除了跟也在摸 Buzz 的人哈拉打屁之外,比較多的時間是在 try 功能與設定......
好東西啊 (y)
很多東西說穿了,其實就是留言板,blog 是個人留言板、Plurk 是多了 timeline 展現方式的個人留言板。Plurk 跟傳統留言板的差別,只在於你知道誰有訂閱你的看板,如此而已。Buzz 大致也依循這個基礎,基本功能就是讓你可以 blahblah 一些有的沒的,然後別人可以在這個主題下回應 comment。如果 Buzz 就只有這樣,除了跟 GMail 整合在一起(有新 Buzz 會有信件通知、可以直接在信件當中增加 comment)、沒有字數限制之外,根本不會覺得它是好東西、也不會想要用(我只用 Plurk 就覺得夠浪費時間啦~ [慘笑])

重點在於整合。Buzz 有一個「connected sites」的設定,可以連接 Google 指定的網站,例如Google Reader、Blogspot(其他沒用過就跳過 XD)。在 Google Reader 對某個 item 設定「分享」,那麼這個 item 就會變成一個 buzz;同理,在 Blogspot 發表新文章時,文章內容也會自動變成一個 buzz。當然,可以直接在 Buzz 的介面看完整內容,也可以回應 comment。

我想,Buzz 的目標就是彙整使用者在 Google 系列產品所產生的資訊,並加以流通。

在 Blogspot 上寫了一篇文章,得手動(至少得自己按個按鈕)轉到 Plurk 上頭才能提醒朋友們去點開來看,Buzz 直接強迫你看 [大笑],還不用在視窗 or tab 之間切換。萬一某個 buzz 導致要私下單挑,轉戰 chat(GTalk)或是 email 的按鈕也都幫你傳便便,而且後續的動作不用在視窗 or tab 之間切換

Buzz 讓平常已經會停留很久的 GMail 變得更久——或著這麼說,如果你的朋友們都是 Google 愛用者(soon or later? :P),那 GMail 或許可以涵蓋所有的溝通需求,因為——還是那句老話——不用在視窗 or tab 之間切換(當然,我不否認,其實我比較喜歡用 Alt+Tab 切換視窗比較快,用滑鼠點來點去實在是很慢.... Orz)。

反過來說,Buzz 又加重了「專一帳號」 的力度。像我自己有兩個 Google 帳號,一個是 PsMonkey(本名?)、另一個是 PT2Club(工作用)。因為工作上的信件往來比較多,導致後來幾乎都用 PT2Club 這個帳號來用 Doc、Reader、譯者工具包、webmaster 等服務。那些也都還好,畢竟各自獨立。但是 Buzz 的出現,就讓我開始猶豫我到底要用哪一個帳號?聊天打屁事小,至少 blog 的文章要能夠自動張貼啊...... 後來解決方法是我的 blog 們現在都有兩個作者 XDXD。

Buts
雖然個人覺得實在是個好東西,不過網路上抨擊 Buzz 的也不少。主要攻擊點在於隱私權政策。這(據說)是因為一開始使用 Buzz 時,它會預設把很多資訊都公開:例如你的某些聯絡人(尤其是在 Google Reader 就有 follow 的)會自動加入成 follower→而 follower 的清單可以在 profile 上頭看到→profile 預設公開...... 如此之類的問題。隱私權的問題我始終...... 搞不太懂,就跳過 XD。

另外,也有這種聲音
忙碌的人不需要另一個社交網絡,他們需要的是一個方便的整合,我們已經做了。Hotmail 用戶自 2008 年起已享受到微軟與 Flickr、 facebook、Twitter 及另外 75 個伙伴合作所帶來的益處。
為此我還特地登入半個月才上去一次的 www.hotmail.com,奇怪... 就跟過去一年多的印象一樣——沒有啊?難道登入之後下面的「好友動向」就是上面說的東西?平常只會看到誰又改了暱稱或是換了照片啊?好吧,就算是好了,那我要怎麼增加新的資訊?至少 blog 的新文章要能掛進去吧?難道一定要跳到 Windows Live 的首頁? (中略數分鐘)還真的是在 Windows Live 的首頁找到了,叫做「新增網路活動」,這需要一點聯想才能從「好友動向」想到這來。而這幾個字有多麼顯眼呢?為了怕大家找不到,我用紅框圈起來了。
點進去則是洋洋灑灑、二字排開、完全沒有分類的項目。有趣的事情來了... M$ 自己提供的 blog service 在裡頭找不到,得在「個人檔案」裡頭的「好友動向」下面的「選項」才有辦法設定(字一樣小,還會跟另一個也叫「選項」的搞混)。因為那被歸類在「Windows Live」裡頭,跟「網路活動」是分開的...... 至於這玩意到底方不方便、好不好用,我就不知道了....... 因為才弄到這裡 我 就 快 抓 狂 啦... 沒有用 AJAX、排版又花又亂、功能提示不清... 啊啊啊啊... <囧>。M$ 的場面話說 2008 年就有這個功能,我很好奇到底有多少人真正在用?上頭有多少資訊在流通(尤其是撇開 MSN 暱稱與圖片 update 資訊)?

說真的,有時候不是「Google 就是好」、也不是「逢 M$ 必反」,而是無論以使用者經驗來給分、以技術面來評估、或是以行銷手段來衡量,孰好孰壞,一望便知。

Bugs
這幾天用下來,其實現階段的 bug——嚴格來講是功能缺失點——還頗嚴重。

最大條也最明顯的是「未讀」的數字始終不太對。常常有新的 buzz 但是數字卻沒出來,或是有出現數字但是點進去卻沒發現有沒讀過的 buzz。這實在是蠻令人生氣(失望)的一個缺點。如果我得要點進去 Buzz、肉眼掃描才能知道有沒有新東西要看......
那我幹麼還用 Buzz 啊啊啊啊....... [翻桌]
另外一個功能缺失點,是在單個 buzz 的 comment 太多時,系統會自動幫你隱藏中間的 comment,只留下第一個跟最後兩個。問題是,可能這個 buzz 最後五筆 comment 都沒看過,就還得全部顯示之後用肉眼掃描......
那我幹麼還用 Buzz 啊啊啊啊....... [翻桌]
至於「別人回覆 comment 會自動寄信到 mail 當中通知」,我覺得這設計的很粗糙且囉唆(弄好 Buzz 的未讀機制,或是開放 API 還比較實在),還好 GMail 本身有 filter 的功能。其他還有 connected site 在顯示 blog 名稱會錯亂、connected site 的來源種類很少、comment 似乎沒辦法擺圖、Insert Link 在刪除 link 之後不會再出現...... etc

總結來說,Buzz 目前是堪用而不太好用。以 Google 的以往的標準來說,實在是不及格(想當年 GMail 出來的時候,我簡直感動得要落淚啊...)。

這給了我一些想像的空間...

Bums
Google 在泛稱 social network 的部份,似乎沒有做的很成功。GTalk 出來太晚,文字訊息打不過 MSN 跟 Yahoo 即時通、聲音影像又搶不了 Skype(當然,micro blog 某種程度導致標準的 IM 沒落)。很早之前有 Orkut,但是好像沒有推起來,至少沒有變成 Facebook。Blogspot 顯示追蹤訂閱的功能應該沒有出來很久(不太確定)......... 如果上述沒有觀察錯誤,那也就是說,Google 手上幾乎沒有「人跟人之間關係」的資料...... 除了不能說的 GMail [奸笑](是說 email 也不太好 mining... 畢竟正常的 mail 都是一對一),還有不太有用的 Google Group 跟... Google Codes? [大笑]

social network 的 service 會徹底遵守西瓜效應。當你身邊的人都在噗來噗去的時候,即使你不趕流行、你身邊的人也會逼你用——不然我怎麼聯絡你啊? or 你一直 lag 我們在講什麼你都聽不懂。雖然使用者的需求是否滿足很重要(所以我壓根不會去用 Facebook),但是常常扮演關鍵的是「誰比較快」。畢竟網路上沒有春夏秋冬,只要種子沒有太差,早播種通常就是早收成。M$ 主管的說詞並沒有錯「忙碌的人不需要另一個社交網絡」,Twitter、Facebook(以很多 service)都有開放的 API 可以連,也就是說,有心要整合都不是問題。如果我已經用了這一個,那為甚麼我要去用另一個呢(一如我到現在還是很死忠 BBS [大笑])?

如果以這點來看 Google Buzz,或許可以代表 Google 也急了、急到連平常應有的水準都沒有就推出來正式上線了。Wave 雖然複雜到不知道該怎麼用,算是失敗,至少它先進且(理論上)完整。Buzz,坦白說,對 Google 開始失望。

如果 Google 未來會崩壞,我會以 Buzz 作為一個轉折點。這也是為甚麼寫這篇的原因,紀錄有過這麼一件事。

2010-02-12

徹底瞭解 GWT Part 2:JavaScript 的 overlay type

原文:http://googlewebtoolkit.blogspot.com/2008/08/getting-to-really-know-gwt-part-2.html

技術校正、審閱:tkcn

假設你已經在 GWT module 當中,愉快地使用 JSNI 來呼叫某些手寫的 JavaScript。一切運作正常,但是 JSNI 只能在獨立的 method 下運作。某些整合性狀況需要你徹底地把 JavaScript 跟 Java 的 object 綁在一起——寫 DOM 跟 JSON 就是兩個好例子——所以我們十分需要可以從 Java 程式碼直接與 JavaScript object 互動的方法。換句話說,我們想要 JavaScript 的 object 看起來就像我們寫的 Java object。

GWT 1.5 引入了 JavaScript overlay type,這讓 GWT 程式整合各種 JavaScript object 變得容易許多。這個技術有很多好處,像是讓你能用 Java IDE 的 code completion 跟 refactoring 功能,即使你寫的是 untype 的 JavaScript object。

範例:簡單、有效率的 JSON
用一個範例來瞭解 overlay type 是最簡單的方法。假設我們要存取一組「customer」數據,底層是用 JSON object。在 JavaScript 中的資料結構可能像這樣:
void jsonData = [
  { "FirstName" : "Ps", "LastName" : "Monkey" },
  { "FirstName" : "痞子", "LastName" : "猴" },
  { "FirstName" : "Pt2", "LastName" : "Club" },
  { "FirstName" : "STO", "LastName" : "Orz" },
];

要把一個 Java type 加到上述的資料結構,要從建立一個 JavaScriptObject 的 subclass 開始,這在 GWT 表示是一個 JavaScript 的 object。接著增加一些 getter。
// An overlay type
class Customer extends JavaScriptObject {
  // Overlay types always have protected, zero-arg ctors
  protected Customer() { }    

  // Typically, methods on overlay types are JSNI
  public final native String getFirstName() /*-{ return this.FirstName; }-*/
  public final native String getLastName()  /*-{ return this.LastName;  }-*/
       
  // Note, though, that methods aren't required to be JSNI
  public final String getFullName() {
    return getFirstName() + " " + getLastName(); 
  }
}

如此一來,GWT 就會瞭解所有 Customer 的 instance 實際上是來自 GWT module 以外的 JavaScript object。這包含了很多意義。舉例來說,看到 getFirstName()getLastName() 裡頭的 this reference。它實質上是代表一個 JavaScript object,所以你操作這個 this 就像在 JavaScript 裡頭一樣。在這個例子中,我們可以直接存取 JSON 中那些我們已知的 field:this.FirstNamethis.LastName

那麼,你要如何才能真正得到一個被包裝成 Java type 的 JavaScript object 呢?你不能用 new Customer() 來建構它,因為重點是把一個既有的 JavaScript object 包裝成 Java type。因此,我們必須使用 JSNI 來得到這樣一個 object:
class MyModuleEntryPoint implements EntryPoint {
  public void onModuleLoad() {
    Customer c = getFirstCustomer();
    // Yay! Now I have a JS object that appears to be a Customer
    Window.alert("Hello, " + c.getFirstName());
  }

  // Use JSNI to grab the JSON object we care about
  // The JSON object gets its Java type implicitly 
  // based on the method's return type
  private native Customer getFirstCustomer() /*-{
    // Get a reference to the first customer in the JSON array from earlier
    return $wnd.jsonData[0];
  }-*/;
}

現在來搞清楚我們做了啥。我們拿到了一個 plain old JSON object(譯註:源自於 POJO)並且建立一個看起來很正常的 Java type,讓 GWT 程式碼中能夠使用它。於是你就有了 code completion、refactoring、compile 階段的檢查——這些寫 Java 時所擁有的好處。然而,你還是可以靈活地操作任何 JavaScript object,這使得存取 JSON service(使用 RequestBuilder)變得很輕而易舉。

為一些 compiler 強者岔題一下。overlay type 另一個美妙的事情是你可以增加 Java 的 type,但是卻不用影響底層的 JavaScript object。注意到上面例子中,我們加入的 getFullName() 這個 method。它是純粹的 Java 程式碼(並不存在於底層的 JavaScript object),但卻是依照底層 JavaScript object 所寫的。也就是說,處理同一個 JavaScript object,以 Java 的角度會比用 JavaScript 功能豐富得多;而且不用動到底層的 JavaScript object——無論是 instance 或是 prototype

(接續上一段的題外話)在 overlay type 增加 method 這個很酷的怪招是可行的,因為 overlay type 的設計規則是不允許 polymorphic 呼叫,所有的 method 必須是 final 且/或 private。因此,compiler 是靜態地解讀每一個 overlay type 的 method,所以不需要在 runtime 的時候動態 dispatch。這是為甚麼我們不用拘泥在 object 的 function pointer;compiler 可以直接對 method 呼叫,就好像是 global function、獨立於 object 之外。很明顯的,直接呼叫 function 會比間接快得多。更棒的是,因為呼叫 overlay type 的 method 是靜態解讀的,這些動作會嘗試自動 inline;這在為了 script 語言的效率而奮戰時,是非常強大的火力支援。接下來我們會重新來一遍,展示給你看這個方法有多成功。

範例:lightweight collection

我們在上面的例子當中掩蓋了某些事情。getFirstCustomer() 這個 method 是非常不切實際的。你一定會希望存取全部的 customer 陣列。所以,我們需要一個 overlay type 來表示這個 JavaScript 陣列。幸運的是,這很簡單:
//泛型在 overlay type 裡頭也運作正常!
class JsArray<E extends JavaScriptObject> extends JavaScriptObject {
  protected JsArray() { }
  public final native int length() /*-{ return this.length; }-*/;
  public final native E get(int i) /*-{ return this[i];     }-*/;
}

現在我們可以寫出更有趣的程式了:
class MyModuleEntryPoint implements EntryPoint {
  public void onModuleLoad() {
    JsArray<Customer> cs = getCustomers();
    for (int i = 0, n = cs.length(); i < n; ++i) {
      Window.alert("Hello, " + cs.get(i).getFullName());
    }
  }

  // Return the whole JSON array, as is 
private final native JsArray<Customer> getCustomers() /*-{
    return $wnd.jsonData;
  }-*/;
}

這是一個很乾淨的程式碼,尤其是以建立靈活配置的角度來看。正如上頭提到的,compiler 可以作一些十分 fancy 的事情,讓它相當有效率。看一下 onModuleLoad() 這個 method 在沒有 obfuscate 的 compile 結果:
function $onModuleLoad(){
  var cs, i, n;
  cs = $wnd.jsonData;
  for (i = 0, n = cs.length; i < n; ++i) {
    $wnd.alert('Hello, ' + (cs[i].FirstName + ' ' + cs[i].LastName));
  }
}

這個最佳化真的是 xx 的好。即使是 getFullName() 這個 method 的 overhead 也沒了。事實上, 所有 Java method 的呼叫動作都不見了。當我們說:「GWT 給你可負擔的 abstraction」,這就是其中之一。不僅 inline 的程式碼執行速度明顯變快、我們不再需要定義 function 的內容、也因而得以將 script 簡短化(雖然持平而論,inline 的方式也很容易讓 script 量變多,所以我們小心地在速度與程式碼大小之間取得平衡)。現在回顧上頭原始的 Java 程式碼是十分有趣的,而試著推導 compiler 最佳化的步驟就展示到這邊。 不過,我們還是忍不住要 show 一下對應、obfuscate 過的程式碼:
function B(){var a,b,c;a=$wnd.jsonData;for(b=0,c=a.length;b<c;++b){  $wnd.alert(l+(a[b].FirstName+m+a[b].LastName))}}

注意在這個版本當中,唯一沒有 obfuscate 的是 JavaScript 當中的識別字,例如 FirstNameLastNamejsonData 等。這是為甚麼即使 GWT 努力讓大量 JavaScript 交互溝通的操作變得容易,但我們還是努力說服別人盡量用純 Java 來寫程式、而不是混著 JavaScript 寫。希望你聽到我們講這些之後,你會明白我們不是要打擊 JavaScript——只是我們不能對它們最佳化,這會讓我們很沮喪。

摻在一起作撒尿牛丸 
overlay type 是 GWT 1.5 的重要特性。這個技術讓直接與 JavaScript library 互相溝通變得相當容易。希望在讀完這篇文章之後,你可以想像如何以一組 Java type 直接導入任何 JavaScript library 到 GWT 裡頭,進而使用 Java IDE 來進行高效率的開發跟 debug,卻不會因為任何類型的 GWT overhead 而影響程式碼大小或執行速度。同時,overlay type 作為一個強大的 abstraction 工具,提供更優雅的低階 API,例如新的 GWT DOM package

2010-02-05

徹底瞭解 GWT Part 1:JSNI

原文:http://googlewebtoolkit.blogspot.com/2008/07/getting-to-really-know-gwt-part-1-jsni.html

技術校正、審閱:tkcn

[前面略過一段很口語、很難翻譯、但是不太重要的一段 XD]

徹底瞭解 GWT,第一步:JSNI
如果你是 GWT 新手,你可能覺得奇怪:到底有什麼好激動的?GWT 跟其他 framework 有什麼不同?GWT 是一個基礎技術、串起許多工具,而不只是特定 application 的 framework。因此,雖然 GWT「有」很多 library,覺得有用的話,要用多用少都可以。不喜歡 GWT 的 UI?你可以用 DOM 建立自己的 UI。不想用 RPC 而想要用 JSON?那也沒問題。事實上,要用 GWT 重頭開始打造自己的  framework 是絕對沒問題的,也能保留所有 GWT debug 跟 compile 方面的優點。

所以,用一個宏觀的角度來思考:為什麼你要考慮使用 GWT 來開發下一個大的 web application 呢?
  • 相較於傳統 server-centric 的方式,如果 AJAX 設計得好,因為本身的特性,在許多種類的 application 上會提供更好的使用經驗。你都已經在讀這個 blog 了,可能早就知道啦。
  • GWT compiler 產生的 JavaScript 碼通常會比你自己撰寫的程式碼還要小、還要快。
  • 用 GWT 開發可以讓你更有產能、而且產生的 JavaScript 也更加可靠。
  • You get the opportunity to say "Gwit" a lot.

我們擔心你是那種容易被一些主張或是行銷說詞給影響了,所以我們在接下來幾篇 blog 文章中,會看到 GWT 如何實現性能的提昇、以及如何讓 JavaScript 使用起來更容易。

在深入技術細節之前,還有一件事。我們有時會問兩個跟 GWT 本質有關的問題:
  1. 為什麼 GWT 以 Java 語言和工具為核心?
  2. 跟手寫的 JavaScript 比起來,GWT 是不是比較多限制、也比較 heavyweight?

先回答第一個問題,請瞭解我們的目標跟滿腔熱血,是為了徹底改善網頁使用者的使用者經驗,這表示 GWT 產生的 JavaScript 必須有最好的效能跟可靠度。為了做到這一點,我們自然想加入一堆程式碼的最佳化,並且盡可能的提早抓出 bug。這兩個目標都容易在 Java 的 static type、以及既有的 Java IDE 來達成。這是為什麼我們冷靜地選擇了 Java 技術作為 GWT 的核心。就是這樣—沒啥語言聖戰的梗。

再來回答第二個問題,有些開發人員在第一次聽到 GWT 時,會假設它是一個 abstraction 的「Walled Garden」,讓你永遠只能用 Java 寫程式而不讓你使用或是整合手寫的 JavaScript。這實在錯的很離譜......

GWT 中的 JavaScript Native Interface(JSNI)
你可以輕鬆地把手寫的 JavaScript 直接嵌入 GWT 程式碼當中。反正最後都會變成 JavaScript,那為甚麼不提供 GWT 的開發人員一個有用的方法,讓他們能夠混合這兩個東西?這方法就叫做 JSNI 啦。它的名字跟 JNI(Java Native Interface)很像,因為他們的基本想法也一樣:把一個 Java method 宣告成「native」然後用另一個語言來實做內容。在 JSNI 當中,就是用 JavaScript 來實做內容。

用 JavaScript 寫 Java Method
JSNI 在創造功能最底層的 abstraction 是非常有用的,它很自然地使用 JavaScript、而不是 Java 語法。例如 Regular Expression 在 JavaScript 是相當簡潔的,所以你可以透過 JSNI 在 GWT 程式碼當中直接使用它們。假設你想把某人的名字(Ps Monkey)轉過來,讓姓在後面、名字在前面(Monkey, Ps)(當然,這在 I18N 當中會是一場惡夢)。你可以建立一個很簡短的 JSNI method 來做到:
// Java method declaration...
native String flipName(String name) /*-{
  // ...implemented with JavaScript
  var re = /(\w+)\s(\w+)/;
  return name.replace(re, '$2, $1');
}-*/;
請注意,method 的內容整個被特殊標記「/*-」、「-*/」給包圍起來,變成一段 Java 的註解。

在 JSNI 中呼叫 Java method
你也可以反過來,在 JavaScript 呼叫一個 Java 的 method。假設我們修改上面的例子,改成這樣:
package org.example.foo;
public class Flipper {
  public native void flipName(String name) /*-{
    var re = /(\w+)\s(\w+)/;
    var s = name.replace(re, '$2, $1');
    this.@org.example.foo.Flipper::onFlip(Ljava/lang/String;)(s);
  }-*/;

  private void onFlip(String flippedName) {
    // do something useful with the flipped name
  }
}

用 JSNI 存取外部的 JavaScript 程式碼
當然,你可以在 GWT module 存取任何外部的 JavaScript 程式碼。舉例來說,如果你的 HTML 頁面看起來像這樣:
<html>
  <head>
    <SCRIPT>
    function sayHello(name) {
        alert("Hello from JavaScript, " + name);
    }
    </script>    
    <-- Include the GWT module called "Spiffy" -->
    <script src="org.example.yourcode.Spiffy.nocache.js"></script>
  </head>
...
在 Java 程式碼,您可以用 JSNI 去呼叫 JavaScript 的 sayHello() function:
// A Java method using JSNI
native void sayHelloInJava(String name) /*-{
  $wnd.sayHello(name); // $wnd is a JSNI synonym for 'window'
}-*/;
GWT compiler 把外部的 method 呼叫給內嵌進來,所以在 Java 當中呼叫 sayHelloInJava() 的效率並不會比直接在 JavaScript 當中呼叫來的差。

用 GWT 建立 JavaScript library
你甚至可以用 GWT 創造一個 JavaScript 可以呼叫的 library。這是一個非常棒的技巧:
package org.example.yourcode.format.client;
public class DateFormatterLib implements EntryPoint {

  // Expose the following method into JavaScript.
  private static String formatAsCurrency(double x) {
    return NumberFormat.getCurrencyFormat().format(x);
  }
  // Set up the JS-callable signature as a global JS function.
  private native void publish() /*-{
  $ wnd.formatAsCurrency =
    @org.example.yourcode.format.client.DateFormatterLib::formatAsCurrency(D);
}-*/;

  // Auto-publish the method into JS when the GWT module loads.
  public void onModuleLoad() {
    publish();
  }
}

接著,你可以在任何 HTML 或是 JavaScript library 當中存取這個 GWT 做出來的功能:
<html>
  <head>
  
    <-- Include the GWT module that publishes the JS API -->
    <script src="org.example.yourcode.FormatLib.nocache.js"></script>

    <-- Write some JS that uses that GWT code -->
    <SCRIPT>
    function doStuff() {
      alert(formatAsCurrency(1530281));
    }
    </script>
  </head>
...

Ray Cromwell(最有名的是 GWT Extreme!)在他的 GWT Exporter 淋漓盡致地採用了上述的 JavaScript 發佈技術。它用 GWT compile 期的 code 產生器來自動創造所有發佈出的程式碼。(「compile 階段的程式碼產生器」聽起來很酷吧?如果你也這樣覺得,那請期待這個系列的後續文章。)

摻在一起作撒尿牛丸
用 JSNI,你可以用你想要的方式,自由地混和手工撰寫的 JavaScript、外部的 JavaScript library、以及 Java 程式碼。這就是我們說 GWT 並不是一個 abstraction 的「walled garden」,因為你可以逐步地將 GWT 融入到既有的 web application 當中。更棒的是,你可以用你喜歡的 Java debugger 來 debug 所有 Java 程式碼。

想要了解更多的話,請看 GWT Developer Guide 的 JavaScript Native Interface。或著,讓 GWT compiler 的設計師 Scott Blum 解釋給你聽也不錯喔。

2010-01-09

喵的,Google 譯者工具包... <囧>

先來個廣告:堪稱 GWT 2.0 當中最重要新功能:UiBinder,官方文件已經翻譯完畢啦!歡迎到 http://gwt-note.psmonkey.org/official/buildUI/With-UiBinder 參觀選購...我是說批評指教..... \囧/

ㄜ? 廣告跟內容有什麼關係呢?因為敝人英文超爛(高中英文從來沒有及格過 \囧/),所以常常仰賴 Google 翻譯來作初步的斷句、有時候比較口語化 or 片語的解讀也頗好用。然後昨天,就在 UiBinder 快要翻譯完的時候,我瞄到 Google 翻譯旁邊有一個連結:「譯者工具包

點進去、用了三分鐘之後.......
救命阿~ 投降能不能輸一半阿阿阿阿...... Orz

當然,我不是專業的翻譯者,翻譯只是我面對外文書的扎實學習方式。只是面對這樣子的工具軟體,很難不大喊「這就是我要的功能阿!」
  • 原文與自動翻譯的譯文並列、同步捲軸
  • 保留原始格式(雖然說這對我來講不太重要 XD)
  • 以句子為單位編輯,原文高亮度顯示
  • 快速鍵、手指幾乎不用離開鍵盤
  • 含字典、翻譯搜尋結果的工具包
  • 可以編輯 or 評論的共用功能
  • 翻譯進度評比
  • 翻譯字彙庫、詞彙庫功能(還不知道要怎麼用 XD)
翻譯的時候都要開三個視窗(一個原文、一個譯文、一個電子字典,後來進化成兩個),在來回對照到眼花撩亂的時候,為甚麼我沒有想過「要是有這樣的軟體該有多好」?

「譯者工具包」的核心技術當然是 Google 翻譯,然後加上 Google Doc 的功能,再作一些補強就差不多了(以 Google 的技術能力而言 :P)。可是我卻從來沒這樣想過,坦白講,以一個技術人員來說,還蠻羞恥的......
摻在一起作撒尿牛丸阿!笨蛋!

當然,我的羞恥每天都有 \囧/,會讓我想要投降的是這張圖......



==" 請不要把焦點放在「我是豬頭」那句話,而是最下面的「建議更好的譯法」。Google 翻譯一直有提供這樣的功能,當你對程式翻譯出來的東西不滿意時,可以 以自己認為良好的譯文作 feedback。不過,你知道、我知道、獨眼龍也知道......
鬼才會特地去寫建議..... [炸]

又或是換個角度講:
都用 Google 翻譯來解讀原文了,這種人建議的翻譯結果,你敢參考嗎? [核爆]

可是我又希望拿到免費、廣泛來源的 feedback 資料,而且資料的正確性也有一定的可靠度...... hmmm... 決定就是你了!去吧!Google 譯者工具包!

譯者工具包,拆穿了,不過就是把「建議更好的譯法」包裝起來......

但是!

你能有一個不錯的軟體來製作翻譯成品、我可以拿到認真撰寫的原文→譯文資料。Great!讓我們一起邁向美好的未來吧! [撒花]

以我的不負責任解讀,Google 一直靠著這樣子的「互利」角度去推出新的「產品」,GMail、AdSense、Analytics、Google News... 是為了 feedback 回 search engine,而 search engine 又 support 了 AdWord 這個真正負責賺錢的東東。說真的,這叫我怎能不折服...... Orz

[小聲] 嗯... 我還有幾個點子,希望 Google 不要發現......