2012年7月27日 星期五

jQuery 原始碼解讀 (III) - ready 解析


Ready . Set . Go 
jQuery 原始碼解讀 (I) / (II) 看完了 jQuery 進入點的部分之後,我們來看看使用 jQuery 的時候 99.9% 的人一定會在一開始的時候就呼叫的一個 method:
.ready(handler)  
如果你剛好是那 0.1% 的人沒用,放心 jQuery 自己也會幫你呼叫 :) jQuery 自己會在一開始就先偷偷註冊兩個 onReady handler 去做 browser feature detection。

.ready 的用法有三種:
  1. $(document).ready(handler) 
  2. $().ready(handler)
  3. $(handler) 
前兩個方法,如 jQuery 原始碼解讀 (II) 所講的會去呼叫 jQuery.fn.init 然後以傳入的第一個參數當作 selector,由於 document 本身就是一個 DOMNode 所以直接就是回傳 context 為 document 的 jQuery object instance,而 $() 則是回傳一個空的 context 的 jQuery object instance,但因為 .ready 其實跟 jQuery object 本身綁定的 context 無關,所以其實你傳什麼 selector 進去都可以直接呼叫 .ready 。但是除了 document 或是 null/""/undefined 之外的 selector 在 ready 前其實都不會 work,所以基本上就只能 $(document) 或是 $()。 

至於第三個方法,其實只是 jQuery 故意留一個 shortcut,但感覺好像也沒什麼必要

而且這個 shortcut 的 condition check branch 是在最後一段,所以其實效能是最差的,但是 jQuery 官方文件反而是建議不要使用第二個方法?有點不解。綜合 jQuery 文件跟筆者小小的看法,我們還是用第一個好了 :) 

不過,以筆者的個人看法,第二個方法效能才是上等的建議啊,不然客倌您看:
是不是在第一個 if 的時候就回來了!

接著來看看 .ready 裡面做了什麼事情:

看得出來這邊的重點在於使用了 readyList 來追蹤所有的 onReady handler,而 readyList 是一個 jQuery.Callbacks 回傳的 object instance,傳入的參數是以空格分隔的, once memory 代表的意思是當 fire 過一次就失效。 Callbacks 基本上很類似一個 array 裡面存放 function references,同時加上一些 fire event 的處理。之後當收到 DOMContentLoaded event 的時候就可以透過這個 readyList 去 fire 所有已註冊的 on ready hanlders。

之後接著檢查如果已經是在 complete 的 readyState 下,那就直接觸發 jQuery.ready。
正常情況下則是去註冊 DOMContentLoaded 去觸發 jQuery.ready,然後後面再註冊 load event 以避免在某些瀏覽器沒有 DOMContentLoaded 的 support 的情況下整個程式就不會動了。 DOMContentLoaded 與 load 的差別在於 DOMContentLoaded 會在所有的 DOM nodes 都 ready 的時候觸發,而 load 則是必須在所有的 Node 的內容都 ready 才會觸發。比如說你的網頁中有放 <img>,那麼 DOMContentLoaded 觸發的時候 <img> 的圖片內容一定是還沒觸發,但 load 被觸發的時候一定是在 <img> 圖片已經完全載入的時候。所以如果你預計再圖片載入完之後去計算出新的 layout,那你就不能用 .ready 而要改用 .load。

另外一點值得提的是,也可以對 img element 註冊 load 事件,但是 load 無法處理瀏覽器 cached images,也就是說如果你預期每次都要收到 img 的 onload 事件來做一些運算去重新 layout 的話,一定會遇到很多問題,比如 Pinterest like 的 layout 就必須取得每一張圖片的大小來計算每一個 Pin 的高度,這時候單純依賴 load 就會有問題。因此有了一個對應的 Plugin - imageloaded 來幫忙處理 image 的 load event。

至於前面提到的 jQuery 會自己註冊的 feature detection 的 function 可以參考這個 gist: https://gist.github.com/3183078,裡面會做以下 features 的偵測:


偵測的技巧是值得學習的,原理大概是建立一個 DOM element,並加入 DOM tree,再重這個 DOM node 的屬性或是 computedStyle 去偵測 feature。相關的技巧應用的很完整的是 Modernizr,有興趣的話可以找一下 EricSKModernier 源碼剖析文章。


jQuery 系列文章:

2012年7月19日 星期四

開信率和點擊率以外,你一定要知道的一個指標 - RPE (Revenue Per Email)

Calculating Revenue Per Email budget

在作電子報行銷效益分析時,很多人的注意力都放在提高開信率和提高點擊率,但開信率和點擊率的追蹤對很多公司來說並不是那麼容易,因為它是需要有些程式技巧才有辦法追蹤的到,如果沒有開信率和點擊率這些數字的話,我們該如何評斷電子報的行銷效益呢? 我今天來為大家介紹一個很容易追蹤但常被忽略又很重要的一個指標 - RPE (Revenue Per Email) (每封信的效益)

這算法很簡單

RPE (每封信的效益)  = 產生的效益 / (電子報寄出總數 - 退信數)

這意思是什麼呢?我舉個例子大家就明白了,我朋友的老闆最常說一句話【不要告訴我什麼開信率、點擊率,你只要告訴我寄這些名單,公司的收入會增加多少?】,是的,就是每寄出一封電子報,會帶來多少效益(也許是營收、流量、加入會員數....之類的)。

如何計算產生的效益呢?

如果你是經營網路相關事業,現在網路上有很多免費流量分析的工具,例如 Google Analytics、Yahoo!奇摩站長工具..等,如果你是經營實體相關事業,那你應該把電話行銷、電子報行銷或是其它人工的行銷方式清楚區隔開來。

所以你就可以計算

  1. 每月、季、年的RPE
  2. 每個行銷活動的RPE
  3. 每個產品線的RPE
  4. 每個客群的RPE
一旦你開始計算RPE後,你就多了一個評斷電子報行銷效益的數據,接下來你就能判斷在目前這個階段,你需要提高的是RPE,還是開信率、還是點擊率 ? 你就能判斷你的行銷預算應該下在那裡,下多少了。

(Photo via 401K, CC License)

2012年7月18日 星期三

App Store SEO 可以使用的參考工具

對於 Web 搜尋引擎的 SEO 其實已經有不少資料了,但是在 App Store 上面的 SEO 其實許多如我一樣的初學者都還在探索當中。最近看到了這篇 App Store SEO: The tools we used,順手翻譯出來跟大家分享一下看看別人是怎麼樣利用一些工具來研究 App Store 的 SEO。如果大家有什麼心得,歡迎可以一起討論分享 :) 以下為原文翻譯:

幾個禮拜之前,Apple 通過了我們 1.5 版的 Craigslist app,把新功能丟給我們的使用者用當然很好,不過這個版本的發佈有更重要的理由,這讓我們有機會透過一些 app store SEO 的技巧調整我們在 iTunes app Store 的排名。根據我們在 iTunes connect 所看到的結果,我們成功了。這篇文章會談到我用來評估和改善我們在 app store 排名的工具。

在我們第一版的 app 中,我其實不太確定對於 App Store SEO 的努力有沒有用。App Store 的排名和搜尋結果對於 app 來說是兩個很重要的元素,不過 Apple 幾乎沒說過背後的機制是怎麼樣的。此外,iTunes Connect 只提供了像是每天的下載數之類的數據之外,沒有提供一些其他的訊息來評估在 Apple App Store 的排名。

所以對於我們 app 的第一版,我只能透過一些假設來猜看看哪些事重要的。很明顯的名字是個很重要的部份,幾乎所有搜尋結果排在前面的 Craigslist apps 都使用了 “Craig” 或是 “Craigslist” 的變化型(包含了 “Craigslist Mobile”, “Nationwide Craigslist”, “Craigster” 還有在尾端加上了一些標點符號,像是 “Craigslist`” 跟Craigslist.)我們其實不打算像是這些 app 一樣改變我們的名字,除了這些名字跟我們的長期目標不吻合外,感覺起來這樣做也不太對。在 iTunes Connect 中,有一個欄位叫做 keywords field。這些 keywords 不會顯示出來,不過也扮演了一個很重要的角色,根據 Apple’s Developer Guide:

"關鍵字(Keywords) 可以幫助顧客有效率的在 App Store 做搜尋,你的應用程式會透過應用程式名稱、公司名稱和關鍵字被搜尋到"

其中也有一些警告:

"如果你輸入了一個別間公司的註冊商標、app 名字或是公司名字,那麼你的 app 有可能會被 App Store 移掉"

我在這件事情上面考慮了一下,不過根據之前的一些例子 (像是 “Craigslist” 尾端加上一些標點符號) 還有一堆複製其他有名的 app 的現象,註冊商標好像一向會都被忽略,這些 app 還是會被審核通過。

在 keywords 欄位沒有太多的空間(只有一百個字元,包含了空白跟標點符號)所以在 1.0 版的 app 當中我用了下面的 Keywords:

"classified, ad, yard, sale, buy, sell, inventory, craigslist, mobile, ebay, camera, used, cars, furniture, local"

我對 “ebay” 或是 “Craigslist” 這種 keywords 感覺還是毛毛的,不過我希望人們可以找到我們的 app。我也加了 “inventory” 這個 keyword 以避免使用者打錯我們產品的名字(譯註:本文作者產品名稱為 Invantory)。

之前我們對於 app 敘述有些問題(關於這個問題,可以看看這篇文章)。在網路的世界,一個頁面的 “主要內容” 對 SEO/SERP 的排名很重要,不過對 Apple App Store 也是這樣嗎?我決定我盡量的讓 app 敘述簡單扼要。我在 app 敘述這邊也提到了 “Craigslist",不過加了一個 (™) 的標籤。

1.0 版的 Invantory 在四月的時候發佈,我們在那時衝了一些下載量。這是因為新的 app 會在 “new releases” 的列表呆個一天兩天。我們也用了 MailChimp 做了一些行銷 (關於 MailChimp,本文作者有發布過這篇文章), 還用了部落格、twitter 還有家人和朋友,我們透過這些方式有了幾百個下載量。

不過接下來每天下載量變少了,狀況比較好的時候,我們一天只有大概 20 次左右的下載量,這件事情讓人感到相當挫折,我們有一個很不錯的 app - 根據我們獲得的評價還有我們的分析工具(Flurry)得到的結果,使用者喜歡看到他們所看到的東西。那麼為什麼我們的下載量沒有增加呢?我想那是因為人們沒辦法在 App Store 找到我們的 app,我們真的相信我們有個超棒的 Craigslist app UX,而且希望所有 iOS 的使用者可以試看看我們的 app,但是他們就是不知道我們的存在。

因為 Apple 不讓使用者對已經發布的 app 的 keywords 或是名稱做更改,我們不能對 app store SEO 做實驗,所以我們得等到送審的時候才能做修改,在這個時候,這些是 “editable state",代表我們可以做些像是更改名稱、keywords 等等的動作。

在做出任何變動之前,我對於 app store SEO 做了很多的研究,從線上的討論還有一些個人的建議當中,我發現了這份簡報:

App Store SEO tutorial
View more presentations from AppCod.es

App Store SEO tutorial

這份簡報很簡短,你大概花五分鐘左右就可以看完,不過它相當的有用,也清楚的告訴了我們哪些是重要的(app 名稱第一、keywords 第二),而且也有舉了一些例子告訴我們哪些事情要做、哪些事情不要做。

AppCod.es 也提供了一個簡潔的搜尋引擎,它讓開發者可以看看根據不同的搜尋關鍵字會出現怎樣的 app,也會看看其他的 app 使用了哪些關鍵字。使用了這個工具和從簡報當中學到的知識,你可以某種程度的對 App Store 搜尋引擎做到反向搜尋,了解到為什麼有些 app 會出現在結果當中,而你的 app 不會。 我也找到一些其他有用的工具,App Annie 可以追蹤在特定國家中某一個分類當中的排名,它讓我們知道我們的 app 在 Lifestyle 當中的 app 大概是排名在中間的位子。

在 1.5 版,我們決定對 Invantory 的名字增加了一個敘述的短詞,我也重新編排了 keywords:

  • 我透過注意在 AppCod.es 當中的搜尋結果,我不再只看熱門的關鍵字,不過也會關注一些比較稀奇的關鍵字組合,看看哪些會讓 Invantory 放在搜尋結果的前面,甚至看看哪些組合會讓我們的 app 會成為唯一找到的 app。我透過 Google Spreadsheets 來追蹤這些組合,並且藉此來縮小關鍵字的列表。

  • 我把 “classified” 這個關鍵字移掉了,除了這個關鍵字被開發者過度使用之外,我也懷疑這並不是大多數的使用者會拿來找 app 的關鍵字。

  • “Camera” 這個關鍵字也被移掉了,因為它對我們的 app 也不是一個太重要的關鍵字。

  • 我加入了一些人面在找 Craigslist app 的時候會使用的地理性術語,基本上我透過 city/state 的長度來決定我要選哪些字(由於 100 個字的限制,所以只有縮寫),另外 Google Adwords 的關鍵字工具也是我拿來參考的對象。儘管 App Store 搜尋跟 Google 搜尋不太一樣,不過我相信人們會把一些在 Web 搜尋當中的習慣帶進來 Apple App Store 當中。

  • 我把一個其他 app 的名字加入到關鍵字當中 - Pinterest。我們認為 Invantory 的 grid view 看起來很像 Pinterest,而且我們曾經看過有人在 twitter 上面問有沒有一個有 Pinterest-style UI 的 Craigslist app。

結果看來是很顯著的,很快的我們在下載數看到了一個顯著的成長:

app_store_seo

此外,透過 AppCod.es 的搜尋引擎以及 App Store 的實際結果,我確定了哪些術語的組合是有用的。那樣是最有趣的呢?不管我在 keywords 跟 “Pinterest” 用了怎樣的組合,對於搜尋結果都沒啥影響,Invantory 就是不會出現。所以我又把 “Pinterest” 移掉了,並且又新增了額外的幾個關鍵字到下一版的 Invantory,希望在這個禮拜可以通過審核。

2012年7月16日 星期一

幫你的網頁加上 backbone(.js) 吧(五)!

請訪問 iCoding Blog 主站 

今天要介紹的是 backbone.js 在 AJAX 的應用,也是「幫你的網頁加上 backbone(.js) 吧」 系列的第五集。文章內容跟範例皆取材自 Joe Zimmerman 的 Javascript Blog 影片教學單元看影片學寫程式是一件非常棒的事情,但若想快速複習影片中的重點,文字會是更有效率的方式,這也是這系列文章存在的初衷,翻譯如有不周也懇請不吝指正。

Backbone.JS 支援 REST 的開發風格,可以讓你用比較正確的方式使用 Web 標準來寫 code。例如當遇到一些典型的資料操作 CRUD - Create, Read, Update, Delete 邏輯時,可以很容易地對應到 POST, GET, PUT, DELETE 等 HTTP 方法來達成任務,避免無謂的再造輪子

底下的範例是基於先前介紹過的 ModelCollection 等基礎觀念,再加上讓前端 Model 在 CRUD 操作時能夠直接和後端的資源(URL)做到同步能力。

此範例的環境設定比較複雜,需要準備好一臺後端的 HTTP Server 和資料庫,如果您無法做出這樣的環境,歡迎到這裡來看看DEMO程式(記得開啟JavaScript主控台)。而後端 API 有興趣的話也可以到這裡參考原始碼,但請注意這個後端程式純屬 DEMO 用, Update 和 Create 的功能都沒有實作出來,只是剛好做出示意的環境來說明原理,實際要用還是需做修改及調整。

非常強烈建議閱讀本章的同時要打開 JavaScript 的 Console (或是 FireBug 這類的工具),並且觀察網路的狀況,仔細看送給後端的 HTTP header 資料。

緊接著,讓我們來看看原始碼吧:


一開始的 Backbone.emulateHTTP 和 Backbone.emulateJSON 是為了和舊的 HTTP Server 相容所設的(畢竟我們無法確保所有的 Server 都能完整支援 HTTP 的各種方法)。,emulateHTTP 是利用 POST 來模擬 PUT 和 DELETE 兩個 HTTP 做向下相容,模擬的方式是在 Form Data 的後面加上 _method 屬性來說明這個 POST 是 PUT 還是 DELETE。而 emulateJSON 則可以把要送給出的 JSON 在序列化(Serialize)之後,偽裝成 HTML Form 的 MIME 資料達到相容的效果。

接著,我們利用 Person 的 Model 建構子 initialize 來記錄所有被觸發的 event 用來檢驗整個範例程式流程的正確性。

在過去所學的 Model 範例中,Model 所指定的資料是無法永久保存的,每當該 JavaScript 頁面被重新執行一次,之前的資料就會被重新產生、覆蓋一次。除非,我們能指定一個能永久儲存資料的 URL 給該 Model。在 Person 裡的 url 方法,就是用來指定這個儲存位置給 Model。而 urlRoot 顧名思義是用來定義或回傳 URL 的字根(prefix),因此,從我們定義的 url 方法可以看出,Person 這個 Model 的 url 樣式大概會長這樣:

/examples/backbonejs-ajax/backbone.php?id=3

url 這個的方法會在各種 event 被觸發的時候被使用到,像是 fetch, save, destroy 等,而 url 這方法必須根據 model 本身的狀態來回傳該方法所需的 URL。舉例來說,當 save 這個方法被呼叫的時候,可以透過 isNew 方法先檢查 model 是否已經存到 server 來決定是要回傳 Create(POST)用的 URL 還是 Update(PUT)。

呼叫 fetch() 時會對 server 做 GET 的請求,因此當資料是 {id:1} 的時候,GET 的 URL 將會是

/examples/backbonejs-ajax/backbone.php?id=1

呼叫 save() 將會對 server 做 POST,當 model 的 data 是 {name:"Joe Zim", age:23} 時,由於沒有指定 id,因此將會對後端發出 POST 請求,其 URL 會是

/examples/backbonejs-ajax/backbone.php

如果 model 包含 id ,則呼叫 save() 時相當於要對某筆指定 id 的資料做 Update,此時由於 Backbone.emulateHTTP 的關係,會對後端送出假的 PUT 請求,相當於是一個 POST 並且在 HTTP Form Data 加上 _method: PUT 以來說明這個 POST 是要模擬成 PUT 的行為:


舉一反三,我們不難猜測 destroy() 正是呼叫 DELETE 方法,一樣用 POST 來模擬 DELETE,URL 會像是

/examples/backbonejs-ajax/backbone.php?id=1

也會會自動送出一個 Form Data 內容是 _method: DELETE。

小結上述,由 fetch() 、 save() 或 destroy() 來決定要對後端發送請求的方法是 GET、POST 或 DELETE,而當 model 資料包含某筆 id 時,則表示要送 PUT。

Collection 相當於在 Model 外面再多包一層殼,collection 在 create 資料的時候相當於新建一筆 model(所以兩者的 add event 都會被觸動)並呼叫 model 的 save(),接下來的運作原則跟前面 Model 也非常相似,差別主要在於多一層 People event 的觸發,而主要的 CRUD 操作都還是取決於 Model 上的定義 。

希望以上的內容能幫助您多認識一點 Backbone.JS 無縫使用 AJAX 的小伎倆。

Happy Coding!


延伸閱讀



2012年7月15日 星期日

如何利用RGBA_8888,壓縮浮點RGBA [0.0, 1.0]數值

Dear All:
這各部分是很久以前遇到的問題,問題是:“要將GPU所算出的rgba三原色,存入一張dds圖檔,當成Lightmap使用”,很直覺的問題在如何將[0.0, 1.0]的數值存到8bit中?其實這不算什麼新鮮的問題,翻開HDR的歷史,過去舊時代的GPU(如Ps2、XBox)因沒有支援浮點格式的RenderTarget,所以必須想辦法將浮點數壓縮存在一張整數格式的RenderTarget上,來達到HDR的效果,來看看我找到的方法吧!

//這裡將[0.0, 20.0]的數值壓縮到[0.0, 1.0]之間:
Vector4& color = vTempTexture[ py * pCurTexture->Width() + px ];
color *= (1.0f / 20.0f);

//找出RGB最大者,做為將來的除數:
float fA = max( max( color.r, color.g ), max( color.b, 1e-6 ) );
if( fA >= 1 ) fA = 1; else if( fA <= 0 ) fA = 0;

//將fA存在Alpha channel,並將所有RGB用fA以除之:
Vector4 rgbm;
rgbm.a = fA;
rgbm.a = ceil( rgbm.a * 255.0f ) / 255.0f;
rgbm.r = color.r / rgbm.a;
rgbm.g = color.g / rgbm.a;
rgbm.b = color.b / rgbm.a;

//還原回整數表示:
int iValueA = (int)(rgbm.a * 255.0f);
int iValueR = (int)(rgbm.r * 255.0f);
int iValueG = (int)(rgbm.g * 255.0f);
int iValueB = (int)(rgbm.b * 255.0f);

if( iValueA > 255 ) iValueA = 255;
if( iValueR > 255 ) iValueR = 255;
if( iValueG > 255 ) iValueG = 255;
if( iValueB > 255 ) iValueB = 255;

//存入貼讀中:
d3dPixel = ( iValueA << 24 ) | ( iValueR << 16 ) | ( iValueG << 8 ) | iValueB;
而在Shader Code使用這張貼時,記得還原壓縮RGB數值:

diffuseLightmap = lightmap.rgb * lightmap.a * 20;

其實說穿了,這各方法就是針對每一各Pixel找三各channel中的最大值去做壓縮,當然還有另一種方式為per-texture的,很直覺得它是找出貼圖中所有的Pixel裡channel數值最大者進行壓縮,也不失是依各好方法!!

2012年7月9日 星期一

[UnityIN] 效能...效能...效能!!!




UnityIn這次整理了一些程式人員在UNITY開發上與“效能”相關,需要注意的“關鍵點”:
  1. 暫存常用的Component:
    根據官方文件的描述,每次GetComponent的呼叫,UNITY都會花點時間尋找所要求的object上。要省下這點時間,建議利用Private變數在Object Awake時,將常的用component暫存下來。
     
  2. 不要在任何Update的function內,使用Find:
    GameObject.Find這個函式用來搜尋全域、特定名稱之物件,效能上有一定的損耗,所以請盡量在Start或Awake裡使用,而非Update函式裡。若要每個Frame搜尋請改用GameObject.FindWithTag
     
  3. 多多使用BuildIn Array:
    如果物件數量是固定的,type[]的寫法是速度最快的選擇
     
  4. 運算少用“除”,多用“加、減、乘”, 根據UNITY官方數據 :
    “除法”,需要30 - 40 cycles來完成
    “加、減、乘”,只需要一兩個cycles
    “平方根、Sin、Cos” ,需要60 - 100 cycles來完成
     
  5. 單純比較向量距離,少用Normalize:
    Normalize = vec / sqrt( vec.x^2 +  vec. y^2 +  vec. z^2),所以盡量改用sqrMagnitude
     
  6. 少用Dynamic typing (JavaScript):
    使用static typing可以讓JavaScript的程式碼跑起來跟C#一樣快。依據官方數據,JavaScript在UNITY裡的執行速度是一般C++的50%、比Mozllia JavaScript快20倍。
     
  7. 只Update在畫面上或距離較近的物件,最好在需要時才開啟enable
  8. 多使用Trigger或Event delegeate來觸發或通知狀態的改變,而非每個每個Update作檢查
     
  9. 盡量避免每個Frame作Raycasting:
    對於Mesh物件作Raycasting有一定的Cost, 所以盡量避免每個Frame作Raycasting,可用Culling (Layer) mask先濾掉不必要的物件
     
  10. 別忽視SkinMesh與DrawCall的傷害:(Mobile Platform)
    目前iPhone 3GS與Nexus One以下的機種,對於SkinMesh與DrawCall的數量還是相當敏感。以手邊的測試數據來看,800面數的角色在NEXUS ONE同一畫面上,維持30FPS可撐7~9隻角色
  11. 少用Alpha testing,多用Alpha Blending:(Mobile Platform)
    依據PVR所公佈的數據,在iPhone 3GS以前的機種GPU的設計上,Alpha Testing是比Alpha Blending昂貴的

     
  12. 減少動態光照: (Mobile Platform)
    不管是Vertex或PerPixel Lighting,都會在DrawCall上增加數量,因此對於較低階的機種會有影響。PixleLight的部分,因為是對每個Pixel做光照計算,對GPU的影響會更勝Vertex Lighting。解決方法是將光bake到貼圖上。
     
  13. Mesh compress壓縮比調整到最高以降低容量:
    除非發現壓縮之後的Mesh出現,如:破洞、閃爍...等問題,不然盡量將這個選項設定的越高越好
     
  14. 手動呼叫gabage collection:
    在固定時間間隔下手動呼叫System.GC.Collect(),確保記憶體的回收,如下:

    if( Time.frameCount % m_frameFreq == 0 )
        System.GC.Collect();

    這個用法在Profiler下,可能會發現在CPU Cost上固定間隔的突波,但實際在手機上測試時,對於FPS並沒有顯著的影響。
如果各位先進覺得還有其他可以補中的點,也歡迎告訴我們,讓這篇文章更具指標性喔!!謝謝


作者:Bric Lin, Email: ericlin09@gmail.com
曾任職台灣某遊戲公司研發Game Engine,為書籍“OGRE 入門指南”的譯者之一,專攻Rendering技術與遊戲開發

到底怎麼樣可以同時把std error, std out 記錄到檔案中呢?

錯誤記錄是很重要的

身為一個專業的工程師,當程式出現錯誤時,一定要把錯誤好好的記錄下來,而要能夠把錯誤有彈性的記錄下來,在Java上請使用log4j,在Python上請好好研究logging module, 在C++上有log4j的兄弟: log4cxx。若不好好善用這些logging module, 你所寫出來的服務是絕對沒有辨法在Production環境下生存的。

那吐出 std error 的那些壞孩子呢?


每個人都有不得已的時候,總是有許多的程式是不得不把訊息送往std out, 及 std error. 比如說一些簡短 Shell Script 或是較老舊的程式碼。

那我們只好另外把這些訊息集中處理。


這樣的工作看似不難,工程師跟Shell 一定有點交情,IO Redirection 多少都有交手過幾次,直覺可能會寫出類似下面的指令:
some_command 2>&1 >/var/log/certain_message
字面上直接翻譯: "我想把std error 導到 std out, 再把  std out 導到檔案中"。

但是事情絕對沒有那簡單,上面的指令是不work的。

你可以用下面這個指令來認清事實:

python -c "from sys import *; stderr.write('err'); stdout.write('out')" 2>&1  > /tmp/o.txt

這個指令在沒有IO Redirect前,會送 'err' 這個字串到 std error ,送 'out' 到 std out。而在設定完IO Redirection後,err這串字會竟然還是只會出現在畫面上,而out 會出現在 /tmp/o.txt 這個檔案裡。

唉,事與願違,錢不好賺。

我該怎麼做

如果你是個懶鬼,我就跟你說,換順序就好,本篇文章可以不用看下去了,但記得給個讚之類的東西。

    some_command >/var/log/certain_message 2>&1
     
上面這段 Command 就可以把所有的訊息收到集起來了,把 2>&1 往後擺即可。

但要能夠理解順序的影響,就要記得一個Process 與其所掌握IO Device 之間隔了一層抽象層: File Descriptor. Process 是經由 File Descriptor 才能對 IO Device 有所影響。

我們先以這個簡短的command 來做圖解釋: echo "cnt" 2>&1 > o.txt 。
並且請記得,預設情況下,FD1是指向 std out,  FD2是指向 std error

下圖一是我們還沒有做任何 IO Redirection 以前的樣貌。

圖一: File Descriptor 與 IO Device 的對應

Bash 的man page 有說到,會由左到右分析指令。
當以在圖二中,Shell 會首先分析  2>&1 ,這會讓FD 2 指向  FD 1 所指向的裝置。因此,FD2會指向 std out. 而 std err 就像是個 dangling pointer,不再有機會被使用到了。
圖二: 2 指向了 std out, 而此時, std error 成了孤兒了

下圖三: 下一個IO Redirection:  > o.txt, 使 FD1指向了 o.txt.
這只改變FD1, 不會對 FD 2 造成任何影響, FD2 還是指向 std out。
這樣子的結果,達不到我們想要收集訊息的目地。
圖三: IO Redirection 設定結束了,只有FD1 被收集到了檔案中


那如下圖四的正確順序指令 echo "cnt" > o.txt  2>&1 又會呈現什麼樣貌呢?

圖四: 正確順序的指令又會呈現什麼樣貌呢?
 Shell 首先面對了 > o.txt, 會把 FD1 指向 o.txt.
圖五: FD1 被指向了 o.txt

在下圖六,Shell 遇到了 2>&1, 會把FD2 指向 FD1 所指向的IO Device, 也就是 o.txt。


最後,我們的Process 對FD1及FD2 的output, 都會直接導向了 o.txt.

結論

上面這些圖,是幫助我們理解Shell 背後的運作,只要記住 file descriptor 那一層間接性,我們就可以清楚地推導出 io redirection 所造成的結果而不用去硬記指令的寫法,如果你想寫出更複雜的指令組合,也絕不會出錯。 如果喜歡這類文章,你在這個Blog可以看到加入 icoding 粉絲團的方式,請不吝惜給我們支持與鼓勵!


  

2012年7月7日 星期六

HTML 簡報正夯:html5slides, deck.js, impress.js 使用心得

即使有精練的文字、美麗的圖片、華麗的動畫效果,仍舊覺得自己的投影片沒有新意嗎?那麼來試試看使用 HTML + CSS 製作你的投影片吧!

優點

1. 可以加入 JavaScript 做出可以互動的效果。
2. 可以內嵌 iframe 直接把網頁內容匯入。

缺點

1. 動畫效果受限,無法做太複雜、飛來飛去的動畫。
2. 版面大小會受限,或者根據螢幕解析度而有版面移位的風險。
3. 投影片數量無法太多(超過 50 頁) -> 高橋流風格可能不太適合 XD

聽起來好像缺點比較多?但如果你的投影片需要優點 1 或 2,例如 jQuery UI 或 bootstrap 的教學投影片,那就非常適合。在學習 HTML 簡報時偶然看到這句話:
The best way to teach the web is with the web.
說的真是太好了!
而筆者正好就是在這樣的情況下,嘗試了 html5slides, deck.js, impress.js 這三個不太一樣、卻都能做出 HTML 簡報的工具。


html5slides (官網) 是由 Google 做出來的一個小玩具工具,它很單純,就一個 html 檔案,直接另存新檔或者原始碼複製貼上就可以開始修改了。(如果想要完全離線使用,必須把它的 css 和 js 一起抓下來。)


deck.js (GitHub) 是一個功能頗完整的 HTML 簡報工具,包含了快速鍵可以:總覽所有投影片(m),跳到某張投影片(g),並且提供幾個投影片範本、換頁效果可以選擇。如果這樣還不能滿足你,它也有些別人做好的外掛(extension)可以加上去(例如這裡)。此外它也支援平板電腦的手指滑動換頁。


impress.js (GitHub) 是利用 CSS3 來做呈現,有別於傳統投影片一張張放映的方式,它像是在一張大圖片上遊走,瀏覽各個小區塊。如果看過 prezi,那麼 impress.js 就是 prezi 風格的 HTML 版本。它可以讓畫面旋轉,更提供了一些 3D 的效果,讓觀眾就像在看電影一樣邊聽你講故事。小心!別讓他們暈了! :p

下面筆者將簡單介紹一下如何開始 happy coding 一份 HTML 投影片,但不會詳細描述該如何加上標題、插入圖片及 iframe...等等,其實這些就跟製作網頁一樣簡單!看完他們的 demo 之後就會瞭解了。我們把重點放在比較差異、遇到的一些問題以及使用心得。

必備技能:必須要了解基本 HTML 和 CSS 語法才有辦法使用這些工具。
外掛:開發者工具(Firebug 之類),或者直接「檢視原始碼」。

共同部份

無論是哪一套工具,它們描述一頁頁畫面(投影片)的方式都很固定,都是用一個特定的 HTML tag 來標記一頁的內容。html5slides 用的是 <article>;deck.js 用的是 <section class="slide">;impress.js 用的是 <div class="step">。於是整個 html 檔案內,就是不斷地複製貼上同一個 HTML tag 來增加新的頁面。因此頁數過多的時候, html 檔案會很長很難維護,當然也會讓初始化時繪製畫面的時間增長。

deck.js 跟 impress.js 都可以額外指定 id 屬性來替這張投影片命名,這個名字會成為這頁在網址列上的識別與定位(#id)。如果沒有特別指定的話,三者預設以投影片頁碼命名。

此外,HTML 簡報設計都仰賴 CSS 來調整版面,如果不是一個接一個擺下來,而需要並排或者其他特殊排版,撰寫 CSS 是絕對免不了的。

差異比較

* 固定版面大小 v.s. 動態調整版面大小

這三個工具最明顯的差異在於對版面大小的設定,html5slides 固定是以寬 900 高 700 像素作為一頁的呈現,因此它都不太會有「在我的螢幕看起來好的,在你的螢幕走位」的問題。也就是無論螢幕解析度如何,它就是固定那個大小呈現。(當然還是可以使用瀏覽器的縮放功能等比例放大縮小。)
deck.js 可就不同了,它會自動依照螢幕解析度幫你調整畫面、以及字型大小。看起來好像很聰明,可事實上,這會讓指定字型大小或者調整文字間距變得困難。建議最好直接使用投影時的解析度來編寫投影片,以免發生最後畫面亂成一團的慘劇。(筆者深受其害啊!QQ)
impress.js 完全就是「你自己控制吧」的設計邏輯,特別是一個畫面的寬度如果不指定的話,它就完全算不準該把哪裡當作中心,因此視窗移動到下一個畫面後,就會有偏移的現象。也不要故意把寬度設的比投影螢幕的解析度大,否則內容超出邊界也是理所當然的。XD
除了畫面寬度,位置也要自己指定,就像在製作大型海報一般,好好地為每一段文字在無限延伸的畫面上安排一個區塊。

* 初始化的複雜度

或許是 deck.js 需要製作投影片選單的緣故,當投影片的張數較多時,很明顯地它會呈現出在處理投影片畫面的混亂狀況,直到它處理完才會正常顯示。萬一在這段時間裡有任何 JavaScript 的執行問題(比如 iframe 無法嵌入),就會讓畫面爛在那裡...。這實在是很大的困擾,但也不是毫無解決方式,比如使用瀏覽器的 plugin 讓畫面完全載入處理完之後再呈現出來,也許是可行的。另外兩者在畫面繪製上比較單純,比較沒有遇到問題。

* 動畫方面

html5slides 與 deck.js 目前都只支援『飛入』這樣的動畫,特別是像 bullet 文字要點的飛入,實在是蠻陽春的。而且播放的時候,它只能「按一下出現」,無法指定時間或者接續前動畫。但幸好它是 HTML 投影片,懂 JavaScript 的朋友也可以自己做出其他動畫效果。相較之下 impress.js 提供的動畫效果比較豐富一點,除了可以 360 度旋轉之外,還有文字的放大縮小。但它沒有文字的飛入這種動畫效果,並且所有動畫效果都是自動在該畫面呈現時就一起執行,沒辦法「按一下出現」。(它是利用 CSS class 改變來控制的緣故,直接去修改 CSS 規則或許可以做些調整。)

* 嵌入 iframe

html5slides 與 deck.js 都支援插入 iframe,但 iframe 可能會有 SOP (Same Origin Policy) 的問題,有時候會造成 deck.js 初始化失敗(畫面就會爛掉,如前面所述),有時候卻會安全過關,實在是蠻奇怪的問題。
而 html5slides 比較沒有遇到狀況。
至於 impress.js 則是不支援的。(但其實因為是 HTML,還是可以使用 <iframe> 這個 tag,只是行為有點怪怪的...)

* 程式碼呈現

deck.js 跟 impress.js 不支援 syntax highlight,而 html5slides 則是已經整合了 google-code-prettify。有插入程式碼需求,又希望它漂亮地呈現,有以下方法:
1. 使用 GitHub:gist 或者 jsFiddle 這類服務,用內嵌 iframe 的方式呈現程式碼。這類網站都有幫你處理 SOP 的問題,應該不會出錯。
2. 自己客製化: 推薦 CodeMirror 或者 google-code-prettify、或者 SyntaxHighlighter,都是可以簡單做到這個功能。
3. deck.js 可以找 extension (如上),或者使用它的延伸版 CoderDeck (它直接改寫原本 deck.js 架構,而非作為 extension 添加上去。)

個人心得

上面介紹了這麼多,大家應該都有感受到優缺點了。以下是筆者簡要的心得:
- 喜歡 html5slides 絕對不會讓調整好的位置跑掉;(不管螢幕多大~它就這麼大 XD)
- 喜歡 deck.js 可以很方便的跳頁/選擇頁面;
- 喜歡 impress.js 有「全局」的感覺;
- 不喜歡 html5slides 就只能固定一個樣子,旁邊的頁面還會露出來破梗;
- 不喜歡 deck.js 複雜的繪製流程不小心就會讓畫面亂掉;
- 不喜歡 impress.js 很多細節都要自己指定,特別是畫面寬度;

結論

其實優點必然會造成一些缺點,考慮自己的情況,選擇一個適合的來用吧!
最後來個不負責任比較表 XD

項目html5slidesdeck.jsimpress.js
上手時間10 分鐘看完 demo10 分鐘看完 demo30 分鐘看完 demo + 文件
排版容易可調但不容易非調不可
變化程度頁面內而已範本有些選擇完全可以自己變化
不同機器上還算穩定走位機會高頁面設定過大會有問題
CSS 需求基礎稍高稍高
美感需求沒有也沒關係沒有也沒關係要會把東西排好看
投影片數量50 頁還可以超過 30 頁就拆開吧沒測過,但太多頁排版痛苦
跳頁面手動改網址快速鍵切換點旁邊看得到的或改網址
動畫飛入飛入旋轉、縮放
支援 iframe有,但會出包
插入程式碼有顏色無(可解決)無(可解決)
觀眾驚喜度還可以不錯Wow~ *_*
副作用下一張會破梗畫面爛掉轉轉轉觀眾頭暈
Ending「謝謝」那頁「謝謝」那頁所有畫面全貌

HTML 簡報正夯,你/妳心動了嗎?:p

2012年7月6日 星期五

提升 JavaScript 效能的技巧





筆者最近因為看到一些文章提到了這段2009年的影片,所以看了這段影片好幾次,雖然已經是三年前的資訊,但還是很實際的 JavaScript 知識。所以摘錄一些重點。講者 Nicholas C. Zakas 是 Professional JavaScript 一書的作者,也曾經(聽說離開了?)是 Yahoo! 的 Principal Front End Engineer。

一開始講者先說明了瀏覽器不會為你的 code 做什麼,唯一可以做出什麼改變的是你本人。並且這邊所講的技巧,並沒有要你不管在寫怎樣的 code  都要硬套進去,還是要進行適當的分析。重點應該要放在瞭解這些概念,並看看對於您的工作有沒有幫助。

主要分為四個領域來講,包含 Scope Management資料存取、迴圈DOM

Scope Management

講者一開始先以這一段據說很糟的程式碼(一個全域函式 setup)來當起始範例。要理解這個全域函式怎樣糟之前呢,我們先來看看 JavaScript 的 Scope Chain。
當你定義一個全域函式的時候,那根據 ECMAScript 規格,它就會有一個 [[Scope]]屬性,而這個 [[Scope]] 會指到一個 Scope Chain Table,這個 table 裡面存放一個指到 global variables 的 table。


之後,當一個函式被執行的時候,對應的 execution context 會被生成,而這個 execution context  會有一個屬於自己的 scope chain,這個 scope chain 會被用來作變數解析。這個 execution context 一開始先把函式的 [[scope]] 複製一份,之後再產生一個 activation object 裡面指到所有的 local variables table,並把這個 activation object 放在 scope chain 的一開始,所以當 setup 被執行的時候 scope chain 應該是長這樣:



之後當你在 function scope 裡面做任何變數的存取的時候,第一步就是先從位於 0 的 scope chain 開始找,如果沒找到就會再往下一個位置去找。這也是一般
大家理解的會先取用 local 的,之後再往上一層,最後一直都找不到的話,就會產生錯誤。這邊的重點就是 global 的變數永遠都會在 scope chain 的最後面那一層,所以盡可能地使用 local variables,因為這總是比 global variables快。這邊跟之前我們解讀 jQuery 原始碼裡面提到的將 window 轉換成 local 所提到的效能問題是一樣的。不過這邊可以想像的到由於 jQuery 內部應用了許多 closure 技巧,所以很多時候 global variables 可能已經被往後推了非常多層。jQuery 為了改善這一塊,特別在進入點的地方就做了處理。

講者還對不同瀏覽器作了實驗,不過因為是三年前的資料,所以 Chrome 跟 Firefox 都是很早期的版本。如果先不論 IE 的話,其實以當時的 Chrome / FireFox 在 scope 這邊的最佳化似乎已經做得很好了。難怪現在常有一派說法是說不用太管這個,除非你的站的流量大到跟 google 或 yahoo 一樣,不然這些效能跟你應該是沒什麼關係的。即使是最爛的 IE7,這邊是 20 萬個讀取也還花不到 0.2 秒。所以就參考看看吧,我覺得現在的話了解scope management的知識才是重點,不用硬改這個。

關於 scope management,還有一點很重要的是以前常聽到人家說不要用 with,而 scope 是其中的一個原因。當你用了 with 的時候其實是在 scope chain 裡面硬加了一個暫時的 scope 在最前面的地方,當離開 with scope 的時候,這個 with scope 物件就消失。所以在 with 的範圍內,所有原本的 local variables 的存取都變慢了。 JavaScript 之神 Douglas Crockford 還曾寫過一篇文章來要你不要使用 with statement。另外,try/catch 也一樣有這個問題。

在 closure 的部分,可以想像的是至少會有三個 scope chain,一個是 global,一個是 containing function 的 activation context,還有一個是最前面的 local。可以想見的是 closure 的使用也會影響資料存取的效能,因為存取階層變多的關係。

在 scope management 這邊,講者總結了幾點建議:
  1. 對那些常常會存取的變數,盡量把它放在 local 
  2. 避免使用 with 
  3. 小心使用 try / catch
  4. 沒有必要的話不要用 closure 
  5. 不要忘記在宣告變數時要加上 var,不然你會不小心宣告太多全域變數
根據以上原則,範例函式應該可以這樣修正:

資料存取方式

有四種存取資料的方式分別是:
  1. 數值或字串  (literal value)
  2. 變數
  3. 物件屬性
  4. 陣列
在這四種方式裡面,literal 與區域變數的存取效率都很好,兩者不相上下。而物件跟陣列的存取相對於前者,效能就差很多。講者一樣針對不同瀏覽器作了實驗。

 一樣是三年前的資料,看起來現代瀏覽器對這部分的最佳化也都做得很好了。甚至連很糟的IE其實也只花了0.09秒而已。如果你不是太在乎 IE 上的表現的話,我覺得幾乎可以忽略這部分的影響 XD

除此之外,講者還特別提到物件屬性的深度也會對效能有影響。深度越深的話就自然得會越滿慢。所以在資料結構的設計上要小心。
在資料存取的建議是:
  • 如果有一個物件屬性或陣列元素會被用到超過一次,就用區域變數取代它。
  • 盡量減低物件或是陣列存取的深度。

迴圈

這一部分講者講了幾個技巧,但我覺得跟 JavaScript 的特性無關啊,只是一般的演算法改進。重點應該只在於不要使用 for … in 跟 for each。當然各個 JS framework 提供的 each 也是少用。尤其是每一次 iteration 都要執行一次函式的方式盡量少用。


DOM

在談 JavaScript 的效能問題,就不能不提到 DOM,講者在 present 的時候嘗試講了笑話,但現場沒有任何反應,有點冏 XDD。

首先提到的是邪惡的 HTMLCollection,透過 document.getElementsByTagName之類的函式取得的 HTMLCollection 的存取都很慢。因為每一次的存取都會重新做一次 DOM query。所以要盡量避免在迴圈中存取 HTMLCollection。但這畢竟是不可能的,所以講者建議將 HTMLCollection 轉成陣列後再做處理。不過如果你用 jQuery 的話大概不用擔心這個問題,筆者前一篇解讀 jQuery 原始馬的文章有提到 jQuery  會把 selector query 出來的 collection 轉成陣列,因此大概不會有這個問題。

關於 DOM 的效能問題,還有一個比較麻煩的是 ReFlow,幾乎所有跟 DOM 物件的操作都會引發 ReFlow,新增或是移除 DOM 物件,或是改變 CSS 屬性,甚至是讀取 DOM 物件屬性,都有可能引發 ReFlow。要解決這個問題,必須利用 DocumentFragment,這是一個類似 document 的物件,但是並不在實際的 DOM Tree 裡面,因此在這個 fragment 上做操作不會引發 ReFlow,之後只要將這個 fragment add 到 DOM,所有的 fragment children 都會被加入到實際的 DOM Tree 中。在你其實並不懂 JavaScript 一文中亦有提及一個好的 JavaScript 開發者必須瞭解如何透過 DocumentFragment 來有效率的新增或移除 DOM Nodes。

另外要避免個別的 CSS 屬性修改,因為每改一次就會觸發一次 ReFlow,最好是將要修改的屬性包裝成一個 CSS Class,之後改變對應的 className,這樣會大量地縮減 ReFlow  的次數。


結論

總結來說,筆者認為除了 DOM 的部分之外,其他的再現在的瀏覽器上其實改善的空間有限。也許在 coding 上的習慣可以調整,但應該也是不必去把既有的 code 作對應的調整。調整還是需要建立在對應的 profiling 資訊上。而 DOM 的操作,目前大部分的 framework 可能會 cover 一部分,不過在 ReFlow 的控制上可能還是需要 coding 的人自己多多小心的。最後祝大家 happy coding :) 

2012年7月4日 星期三

iDoneThis 架構剖析

之前為大家介紹了 iDoneThis 這個 service,今天讓我們來看看iDoneThis 的架構還有他們從剛開始到目前的開發心得吧。

一些數據

  • 每天收到 10,000 封 e-mail
  • 每天寄出 40,000 封 e-mail,大多數都是在美國時間的下午六點寄出
  • 每秒鐘數個 web-request
  • 1GB 的使用者資料,5GB 的資料庫
  • 文件有做即時索引和搜尋
  • 所有的 service 只用了一台 xlarge EC2 instance

架構

  • 幾乎所有事情都用 Python 搞定
  • Django 當做 web interface
  • apache + mod_wsgi 來處理 web server
  • postgresql 作為資料庫
  • 收到的 email 經由 sendgrid parse API 跑過後再用自己寫的 email processor 處理
  • 前端: CoffeeScript, backbone.js, jQuery, sass
  • lucene + solr 作為搜尋之用

Email - 從 Gmail 到 Sendgrid

由於直接用 EC2 instance 送信很容易被判定為 spam,所以剛開始 iDoneThis 是用 gmail 的 BCC 來送信的,之後就換成了 SendGrid

iDoneThis 寫了一個 script “sendmail” 來跟 SendGrid API 互動,直到今天這個 script 還是被使用著,不過可靠度方面也大大改善了。剛開始的 sendmail 只是一個簡單的迴圈來處理資料庫當中的東西,現在已經變成了會追蹤寄送 e-mail 當中的不同狀態並且做些相對應的處理。

為了處理收到的 e-mail,剛開始 iDoneThis 寫了一個大概兩百行的 “getmail” script 來透過 IMAP 存取 gmail,起初這個 script 很不穩定,而且當 script 出錯的時候會讓資料庫爛掉,所以在跑 getmail 必須有人在旁邊看著。不只這樣,還必須來處理一些不必要的文字(簽名檔、回文等等)以及 encoding 等等的問題,為了要搞定這件事情,getmail 長到了大約 800 行左右。

持續了一段時間之後,iDonThis 捨棄了 getmail,轉為使用 SendGrid 的 incoming parse API。主要的原因是因為 gmail 對 IMAP 存取有 rate limit,所以沒辦法快速的把 email 處理完。

規模化

起初,iDoneThis 主要面對到的瓶頸不是在技術上面,而是在開發時間上面,就算到今天還是如此,對 iDoneThis 來說,這代表著效能以及規模化的議題主要是圍繞在簡化程式設計上面。

想要做到這件事情,第一步就是盡可能的限制要開發的功能,或是把一些部分外包出去。只有在確定開發出來的功能有人用之後才會做優化。

再來,儘管可能會犧牲效能,還是要盡可能讓程式碼精簡。舉例來說,使用 Django ORM 不一定會產生有效率的 SQL 查詢,但是只有在實際上線而且發現會產生效能問題之後才會做這部份的優化。這麼做除了可以避免過度的優化外,也讓程式碼看起來乾淨許多。

隨著收到和寄出的信件越來越多,iDonThis 考慮到轉換到多台 server 的架構。不過這會讓架構上面複雜不少,也會帶來些許的麻煩。為了可以節省開發者的時間和簡化成是架構,儘管可以用好幾台 small EC2 instance 搞定一樣的事情,iDonThis 還是用一台 xlarge EC2 instance 搞定。

最重要的部份是 iDonThis 有做到 continuous deployment,每個開發者都可以在幾秒之內把程式碼 deploy 到 production server,這代表著開發環境跟 production 會是一致的,這也代表在某個功能上線之後可以很快的做出相對應的反應。

重新架構

剛開始的 iDoneThis 只是一個很簡單的 Django 專案,只有幾個 models、views 和 templates。儘管產品一直在進步,不過 iDoneThis 依舊是在比較舊的 web 應用架構上面 - 所有在上面發生的事情都伴隨著頁面讀取和送出表單。有一段時間這樣運作的還不賴,不過過了一段時間之後使用者希望可以有更好的使用者介面來存取他們的資料。

起初 iDoneThis 用了一堆 jQuery 來完成需要做的工作,雖然可以動,但是 code 變得很混亂而且很難除錯。為了希望盡可能的簡化前端的程式碼,原本的 jQuery code 被 CoffeeScript 和 Backbone.js 取代。隨著時間的演進,iDoneThis 的 backend 變成了 API-based 的架構。Django web service 分為兩個部分,一部分是負責提供 json API,另外一部分是負責吐出前端的程式碼以及 html 的部份。

目前為止,這種模式的程式碼比舊方法簡單許多而且更容易除錯,儘管還是花了不少時間才變成如今的樣子,不過由於程式碼簡潔許多,所以之後也可以更容易的增加新功能以及除錯。

這當中 iDoneThis 學到了什麼

  • 把麻煩的事情外包,而且是盡可能的外包。iDoneThis 來說,把所有 email 相關的事情都交給 SendGrid 處理。當然可能在某些時間點或許把這些事情自己刻些 tool 來處理是有意義的,不過對於新創公司來說,這個時間點可能會是在遙遠的未來。
  • 簡化比效能重要。儘管或許可以用 nginx 跑在一個比較小的 EC2 instance 上面,不過用 apache + mod_wsgi 一切都處理的很好。對 iDoneThis 來說,都是盡可能的用最簡單的方法來完成工作,關於效能的問題之後再考慮。
  • 有一小段時間 iDoneThis 打算把資料庫和 web server 分開跑在不同的機器上面,不過後來發現帶來的問題大過於好處,所以現在都是跑在同一台 EC2 instance 上面。

參考文章: IDoneThis - Scaling An Email-Based App From Scratch

2012年7月1日 星期日

CMake 玩一玩!!PormanGL實例解析 Ver 2.1


Dear All: ( 文章轉自 "B瑞克的Casual Report" )
最近跨平台的專案越來越多,相信大家對於不同開發工具的整合一定很頭大。其實這部分可以用CMake解決,不過老實說CMake的文件真的很...不知讓我從何說起。記的前年小弟在開發自己的Lib時,就遇到不同VC版本的問題,有人只有2005、有人只有2008,搞得我只好兩套都裝,兩套都Commit.............,沒多久2010又出了....

[UnityIN] SONY Xperia Game Pad + UNITY3D



Dear All: ( 文章轉載自UnityIN )
Hello~如果有Xperia Play且常玩遊戲的朋友因該都有發現,目前支援Xperia Game pad的遊戲其實也滿多的,其中UNITY的作品也不少,最近一款讓我比較喜歡的就是“Muffin Knight”,開發商Angry Mob Games在更之前的一款作品“Guerrilla Bob”,早在2011年初就支援了Xperia Play的Game pad。

Related Posts Plugin for WordPress, Blogger...