2012年10月4日 星期四

快速認識 Passbook

請訪問 iCoding Blog 主站 

在很多人還沒搞清楚 iOS 6 的 Passbook 怎麼用的時候,已經有不少公司開始賣 Passbook 票券了,發展的速度之快實在讓人感到不可思議,看起來案情並不單純,究竟 Passbook 是一個怎麼樣的應用呢?讓我們看下去!

蘋果的電子票券(Pass)

在過去,我們常使用的票券,像機票、折價券、活動票券(門票)、會員卡等,通常都是一些紙本印刷、卡片或晶片卡等實體票券。

Apple 推出的電子票券(Pass)和 Passbook 簡單說就是一套取代傳統票券的電子票券協定(Protocol)與規範,讓想販售票券的單位可以很容易的發行自家的電子票券,而使用者也可以很簡單地取得票券,並直接利用 Passbook 管理票券,以便隨時拿出來查看或使用。

電子票券(Pass)是一個副檔名為 pkpass 的特殊檔案包,有幾種主要的格式,包括:
  • 登機證(Boarding Pass)
  • 折價券(Coupon)
  • 門票(Event Tickets)
  • 商家卡(Store Card)
  • 通用券(Generic),上述四種外的票券可基於此類衍生。

每張 Pass 的正面都被要求要有清楚簡要的資訊、票券圖示、票券條碼(支援一維或二維等數種條碼格式,包括時下極為流行的QR Code,作為核銷驗證使用)、甚至於地圖、坐標等資訊:

 

票券背面也會有一些選項和詳細資訊(例如使用條款等),使用者可以決定是否接受票券更新通知或是允許在靠近票券使用地點時通知使用者(在鎖定時的螢幕顯示相關提示)等,每張票券都十分簡單明確。

Passbook

Passbook 是 iOS 6 上一個用來管理電子票券的 App,可以讓使用者存放票券資訊,並獲得票券的相關通知。例如,Passbook 可以讓使用者在靠近使用票券的時間、地點時,在鎖定的螢幕畫面上跳出提示,或是在票券發行單位有資訊更新時(例如航班誤點)Passbook 可以收到推播通知,讓使用者更新手上的票券資訊。而用掉的票券也會用很生動的方式顯現,就像撕過的實體票券一樣助於分辨。

取得電子票券

對消費者來說,取得票券的方法十分簡單,可以透過發行票券的網站、 App 或是從電子郵件等管道取得電子票券的下載連結,取得的方式有點像下載 App 一樣,例如底下的新增票券連結:


點擊之後票券就會被加入到自己的 Passbook 中(上圖沒得點擊)。例如,在網路上訂完機票之後,航空公司寄出一個 Pass(電子票券) 的連結到日前註冊的信箱,只要用 iPhone/iPod Touch 打開這個連結,就可取得機票,機票會自動加到 Passbook 裡面。

使用或核銷票券

只要帶著 iPhone/iPod Touch 到商家開啟 Passbook 秀出票券,商家可以選擇適合的方式,例如傳統的條碼掃描器或是光學條碼掃瞄器(甚至是發行電子票券的 App 等)進行核銷,可以不必有特殊的硬體設施(例如 NFC)就能夠完成票券使用。

產生票券

除了透過蘋果提供的 Pass 相關的一系列工具(需要 iOS 開發者帳號)產生、認證、發行票券之外,PassSource 也是一個不錯的選擇,在這個網站上,只需選擇卡片類型,並填寫一些基本的票券資訊就可以自動產生一個簡單的票券。

台灣可以享受到 Pass 嗎?

目前為止,城邦書店、日本料理餐廳「新都里」、賽博數碼3C賣場還有HONMA GOLF等單位,都已經推出了優惠券下載活動,不過都還沒有包含條碼掃描的機制,只有提供優惠訊息等簡單功能而已,還沒充分利用到完整的概念。


小結

Passbook 的方便程度,可說是足以取代傳統票券,雖然目前 只限定在 iOS 6 上的 iPhone 和 iPod Touch 使用,但就整體的設計架構來看,是非常容易發行與使用的,相信逐漸普及是指日可待的事情。

2012年9月24日 星期一

為你的 App 網頁加上 Smart App Banner

請訪問 iCoding Blog 主站 

在 iOS 6 當中,Safari 加入了 Smart App Banner 的支援。如果你的 iOS App 有一個專屬的網頁,那麼你一定要加上 Smart App Banner 的支援,讓你可以提高你網頁到 App 的轉換率。
Smart App Banners for Taiwan B&B
上面是一個 Smart App Banners 的例子,我以我們的好客民宿 App 為例子。在這邊,我們可以看到在 App 專屬頁面的最上面出現了一個 Banner,有 App 的名字、圖示以及評價。如果是在 iPad 的 Safari 開啟,甚至還會看到 App 的截圖。在 Banner 的右方,有個連結可以開啟 App Store 的連結,如果在使用者的 device 當中已經安裝了這個 App,那麼會有個可以開啟 App 的連結。
要加入 Smart App Banners 到你的網頁相當容易,只要在 head 區域當中加入 meta tag:
<meta name="apple-itunes-app" content="app-id=542383077"/>
其中,app-id 的部份就是你 App 所屬的 ID。
前面有提過,當使用者已經安裝了你的 App 的時候,Smart App Banners 會直接讓使用者開啟你的 App。因此你也可以在頁面的 meta tag 當中加入一個叫做 app-argument 的參數,透過這個,可以控制你傳到 App 的參數。當你同時有 Web 和 App 的服務的時候,這個將會相當的有用。舉例來說,如果你有個訂房的 App,當使用者透過瀏覽器來到了某間飯店的頁面,你可以透過 app-argument 讓使用者直接開啟 App 到相對應的飯店做訂房。下面是個使用 app-argument 的例子:
<meta name="apple-itunes-app" content="app-id=542383077, app-argument=http://hotel/3"/>
app-argument 是以 URL 的形式存在,然後會傳到你的 App 當中做處理。會以 url 參數傳入你 UIApplication delegate method 當中。
-(BOOL)application:(UIApplication*)application
        openURL:(NSURL*)url
        sourceApplication:(NSString*)sourceApplication
        annotation:(id)annotation
接著你只要根據你的 url 參數,做些對應的處理就好。
以上就是讓你的 App 網頁加入 Smart App Banners 的方法,花點時間把 Smart App Banner 加進你的網頁吧,可能會讓你的 App 更多人下載喔!
Reference: Implementing Smart App Banners

2012年9月17日 星期一

用 Sublime Text 2 打造你的 Django IDE



前一段時間我的 MacBook Air 送修,跟某好人借了一台 notebook,灌了 ubuntu 之後環境設定一下一般工作倒是還算順手。不過之前在開發 Django 相關應用的時候我是透過 PyCharm 做為我的 IDE。對於開發 Django 來說,PyCharm 是個很棒的 IDE,有方便的 autocomplete 還有跟 virtualenv 的整合,以及支援 Django template、manage.py 等等的功能,不過功能強大的背後就是很耗費資源,在借來的這台電腦上面跑起來實在是不太讓人開心,所以這次剛好來看一下最近很潮的 Sublime Text 2
關於 Sublime Text 2 的文章 Google 一下可以找到一大堆,這邊我只介紹在開發的時候跟 Django 比較相關的 Package。這些除了自己抓下來之外,還可以透過 Sublime Text 2 的 Package Control 來裝。

Djaneiro

這是我在 Sublime Text 2 上面最喜歡的 Package,它提供了很多 snippet 讓你 coding 的時候可以不用記一堆討厭的名稱。舉例來說,在寫 Django model 的時候常常會有一堆 field 要寫,你可以打入 mchar 再按一下 tab,就會自動幫你產生一個 models.CharField()。試試看這個,你一定會愛上的!

SublimeLinter

無論對於哪個語言來說,有個 Linter 總是會省掉你很多麻煩。這個 Package 支援了許多的 Linter 讓你用。

SublimeRope

Rope 是個很不錯的 Python IDE,在我還在用 emacs 開發 Python 程式的時候 Rope 幫了我不少。SublimeRope 則是把 Rope 當中一些功能移到 Sublime Text 2 上面,讓你作一些 Refactor 等等的動作可以更方便。

Git

最潮的 version control,裝了之後讓你不用切到 command line 就能夠快樂 git

JSFormat

開發 Django 一定多少也會寫一些 JavaScript 的 code,JSFormat 把亂七八糟的 code 變得好看很多。
這些只是一些很基本的 Packages,Sublime Text 2 能作的不僅僅於此,透過 build system 還可以作些跟 unit test 相關的整合。如果你正在找一個順手的 IDE,或許可以試試看用 Sublime Text 2 打造適合自己的開發環境。

給非 JavaScript 專家的小技巧

請訪問 iCoding Blog 主站 



JavaScript 越來越火紅,但如果你並不是 JavaScript 的使用者,那你一定常常覺得這是個難懂的東西。這篇文章結合了網路上一些 JavaScript 教材來提供一些實務上對偶爾需要寫一些JavaScript 的人可以有些幫助。 

希望這一篇文章可以讓業餘的程式設計師可以寫出更有效率,更好維護的 JavaScript。 如果你是一個 JavaScript/Front-end 開發者,那這邊文章不適合你。如果你不是,那這邊文章就是寫給你的 :) 

註:由於 jQuery 被廣泛的使用並且可以降低跨瀏覽器開發的差異,所以這篇文章會有一部分提及 jQuery。

1. 了解變數 scope 以及初始化


大部分有其他程式語言背景的工程師一開始都覺得 JavaScript 的變數 scope 很難理解。JavaScript 的變數 scope 看起來很難處理只是因為它跟一般語言的 scope 不同。 但只需要記得兩個原則:

  1. 唯一的變數 scope 就是你所在的函式。
    當然還有全域的 scope,但並沒有區塊的 scope。
  2. 每一個函式都有一個 scope chain,而這個 chain 中會指向上一層函式的 scope。
    如果一個變數或是函式被存取但是在當下的 scope 中找不到,這時候就會嘗試往上一層的 scope 去找一直到有一個對應個宣告被找到為止。如果是嘗試賦予一個變數的值,但往上找卻找不到對應宣告的時候,那麼就會直接在全域 scope 設定一個這個變數名字的屬性。 (這邊可以參考之前一篇提升 JavaScript 的效能)


那麼現在來看一下函式是如何被初始化:

  1. 先宣告函式參數,並設定參數的值
  2. 所有函式內部的函式被宣告
  3. 所有在這函式 scope 中有宣告的變數被生成 (所以即使變數被宣告在最後一行,但卻可已在函式中的任何地方被存取)
  4. 賦予變數值


希望這些簡化的解釋可以幫助理解 JavaScript 的變數行為,如果想要更深入瞭解,可以參考這篇深入 Spec 的文章 (http://dmitrysoshnikov.com/tag/ecma-262-3/)。

2. 避免寫與 HTML 混在一起(inline)的 JavaScript 


這一點很簡單,但因為這實在很常發生,所以我把它包含在這。行為分離 (Behavioral Separation) 對前端開發者而言是一個基本原則,而這也應該是所有寫 JavaScrpt 的人該信奉的原則。 JavaScript 應該是屬於行為層所以不應該與 HTML 內容層混在一起。因此你不開寫這樣的 code:

<button id="my_btn" onclick="doThis();">Submit</button>

把 JavaScript 跟 HTML 混在一起這樣寫會導致 JavaScript 異常的難以維護。一個新的開發者或需要更長的時間來找出散亂在 HTML 中的 script 並且很難將整個功能的情境拼湊出來。

如果一個頁面需要做頁面事件相關的處理,那麼可能需要一個初始畫的函式在頁面最後被呼叫。這麼一來就只需要看一下這個初始化函式就可以知道整個頁面的動作以及事件的 binding。這邊是一個實例:

<button type="button" id="my_btn">Submit</button>

<!-- These scripts go before the </body> tag. -->
<script type="text/javascript" src="/s/init.js"></script>
<script type="text/javascript">
    initializePage();
    // pretend that these functions are in /s/init.js
    function initializePage() {
        document.getElementById("my_btn")
            .addEventListener("click", doThis);
    }
    function doThis(e) {
        // do cool stuff here
    }
</script>

註:addEventListener() 在 IE9 之前並不支援,建議使用 jQuery 可以處理跨瀏覽器的 event binding。

3. 找到對的 Event 去做 binding 


在 bind 任何事件處理之前,先想一想你想要處理的是什麼事情。我看過太多在表單的提送按鈕上 bind "click" 事件處理的案例,但實際上卻不是真的要處理 click ,而是要處理表單送出的事件。那麼這時候你應該 bind 的應該是表單的 "submit",因為 enter 也會引發表單送出。 

4. 使用快速的 selectors  


如果你不是使用 jQuery 的話。常見的選取 DOM elements 的方法有: getElementById,getElementsByClassName, getElementsByTagName,querySelector 以及querySelectorAll。如果你只是處理很小部份的 DOM elements,那麼你用什麼方法其實沒有那麼重要,隨便你要使用 querySelector 或是 querySelectorAll。但如果你要處理的 DOM element 個數很大的話,記得使用比較快的方式(通常是最老的那個方法)。我建議使用 getElementById,所有的瀏覽器都支援而且是目前最快的方式。

如果你用 jQuery 的話,最好還是使用 IDs 或是 classes 可以得到比較好的效能。

5. 非必要的時候不要去動 Doc 


這應該是關於最佳化 JavaScript 效能最重要的技巧:沒事不要去動 document ,除非你百分之百確定你必須這麼做。這通常是大量使用 JavaScript 的網站的瓶頸。

盡量嘗試把 elements 的 reference 記下來,避免下次還要重新 query document 一次。

最後一個:等值檢查與真值

當處理等值測試的時候,你必須瞭解語言是如何測試變數,並且知道如何利用語言特性。JavaScript 除了有一般的等值運算元 (==, !=)之外還有必較嚴格的等值運算元 (===, !==)。 用一些實例來看看有什麼差異:

  • "5" == 5 but "5" !== 5
  • 0 == false but 0 !== false
  • null == undefined but null !== undefined

換句話說,=== 與 != 在比較的時候不會作自動的真值推導以及型別轉換,必須在兩邊的原始型別相同並且值也相同的情況下才為真。

結論


即使你偶爾才寫 JavaScript,能夠讓寫出來的 code 不會成為維護上的負擔並且執行的必較好應該還是一件比較好的事。希望這些技巧可以幫助你瞭解其他人寫的 JavaScript,也可以讓你在需要 debug 的時候是有一些基礎準備的。你可能不一定要成為黑帶 JavaScript 忍者,但至少必須對你寫的 code 有信心。



2012年8月31日 星期五

「資料探勘」幼幼班

請訪問 iCoding Blog 主站 

Mine Cart

儘管「資料探勘」已經被研究數十年了,到現在仍然被許多人所誤解,因此我決定整理一篇「資料探勘」的基礎簡介,希望能幫助大家對資料探勘這個領域有多一點的初步認識。過程會牽涉到一些專有名詞,已盡量附上連結,若仍無法理解還請多多包涵。如有錯誤,也歡迎不吝指正。

什麼是資料探勘

「資料探勘」是一個「嘗試從一大組資料當中找出一些特徵樣式(pattern)的過程」。

「資料探勘」結合了「資訊科學」和「統計學」等跨領知識,除了對原始資料的分析和資料的技術之外,統計模型與統計推論、結構化的後置處理、資料視覺化等技術都是資料探勘會牽涉到的領域。

「資料探勘」的英文原文是 Data Mining,源於1990年的資料庫社群,曾經成為 HNC 這間公司的註冊商標,在人工智慧和機器學習的領域中,Data Mining(資料探勘) 和 Knowledge Discovery(知識探索)經常互為同義詞。

為什麼要做資料探勘

人們希望能從一堆資料當中,萃取出一些有意義的資訊,並且把這些資訊轉換成比較容易理解的形式,以便對這些資訊再利用。而這也是「資料探勘」存在的目的與目標。

舉例來說,人們會想透過某種從地層資料當中去分析歸納出一些含礦量高的地形特徵樣式(pattern),並建出合乎真實的地底模型(model),再利用這些特徵或模型找出建造優異礦坑或油田的過程與方法。

常見的資料探勘方法

粗略來說,資料探勘可以分成底下三個步驟:
  1. 資料預先處理 (pre-processing)
  2. 資料探勘 (data mining)
  3. 結果的評估與驗證 (results validation)
資料預先處理(Pre-processing)是一個必要的步驟,不但要確保資料的數量足夠,讓探勘的結果具有意義,也要避免資料無法在合理的時間內可以被分析完畢,此外,也必須確保資料的乾淨(data cleaning),例如過濾掉一些雜訊或錯誤的資料,以免探勘出奇怪的結果。

資料處理完之後,就可以開始探勘了:
  • 異常偵測
    • 用來分辨一些看起來不尋常的資料,這些東西可能會是有意義的資訊或只是單純的錯誤。
  • 關聯規則的學習
    • 資料當中有許多變因或是變數之間可能會有關聯性(例如天氣和銷售量的關係),這個步驟的目的專門用來找尋這種關聯性。
  • 分群
    • 在面對一群未知類型的資料時,把看起來相似的資料聚集成同一個組別的方法。
  • 分類
    • 把新發現的資料歸類到預先定好的類別當中(例如把郵件分成重要、普通、和垃圾)。
  • 回歸分析
    • 用來找出資料之間是否有依賴或獨立的關係。
  • 結果摘要
    • 提供一組最具代表性(代表原始資料或某件知識或事情)的簡化資料,並且包括視覺化以及結果報告。
由於探勘完的結果或是找到的特徵樣式(pattern)很可能有不合理的結果(例如:過度最佳化 - overfitting),所以在做完資料探勘之後需要做驗證(Result validation),以免被錯誤的結果所誤導。一般來說,我們會在探勘前把資料分成訓練組(training set)和測試組(test set),我們會把訓練組餵給探勘演算法進行探勘,再用測試組檢測從訓練組當中找到的特徵樣式是否也存在於測試組。ROC 曲線是常拿來評估驗證結果的方式。

常見的誤解

資料探勘強調的是「探勘」或是「探索未知(detecting something new)」這件事。

很多人誤以為資料探勘包括任何形式的大規模(尤其是強調巨量,例如大資料 - big data)的資料或資訊的處理過程,例如一些資料收集、擷取、放置、分析與統計分析,甚至人工智慧、機器學習Business Intelligence電腦輔助決策系統等,都不是資料探勘。

市面上有些暢銷書用了行銷手段,在書名前面加上 Data Mining 幾個字來提高賣點,這也是不對的。

另外,資料探勘和「應用」資料探勘技術其實是兩件事情,一個是對未知模型的探索,另一個是拿已知的模型進行應用,一個是模型創造者,一個是模型使用者,如果沒有切割十分清楚很容易被誤解。

資料探勘的應用

前面提到「資料探勘」像是找礦脈、油田的過程,而在這過程下有許多的多輔助工具、已知特徵樣式或模型等技術因應而生,這些的資料探勘的「衍生應用」在許多領域都可以看見,例如:
  • 商業:
    • 客戶關係管理(利用分析結果來找出精準客群)、購物籃分析、決策系統等。
  • 科學與工程:
    • 生物資訊、醫學、基因、藥學、電力工程等,都是「資料探勘」的重度使用者。
  • 人權組織:
    • 可以透過探勘政府的資料,去探索迫害人權的政府機構或文獻的存在。
  • 時空資料探勘:
    • 資料探勘技術也被廣泛的應用在地理資訊系統(GIS)的研究上,這些資料量非常驚人龐大,存在著許多巨大挑戰以及潛在應用在商業的機會。
  • 視覺、音樂資料分析
  • 監控:
    • 分析防堵恐怖份子的應用也透過資料探勘歸納出來的結果作依據。
  • 樣式探勘(pattern mining)
    • 從資料中比對一些已知的樣式,像是所謂的「關聯規則」,可以用來幫助改善商業銷售狀況,例如買啤酒的人,80%的機會會買洋芋片。
提了這麼多相關應用,是不是有種眼花繚亂的感覺呢?
其實,這些廣大的資料探勘的相關應用正一點一滴地在改變你我的日常生活呢:)

希望這篇文章能讓您對「資料探勘」有多一點感覺。

Reference: http://en.wikipedia.org/wiki/Data_mining
Photo by Caitlin Childs, CC licensed

2012年8月27日 星期一

決定你公司名稱的五種方法

決定一個名字的因素有哪些?在 google 裡面搜尋 “naming your company” 得到的一長串列表當中,我們可以知道有一堆的因素可以決定。

當然有很多不同的策略可以幫你的公司取名字,不過你也可以簡單的從報紙上面隨便翻翻來得到其他創業的人都是怎麼取名的。

不過也不要太輕率的亂做決定,公司名稱除了會是一個辨識你個人的重要部分之外,它也會被用在行銷、媒體關係、部落格、網站甚至是印刷品上面。

這會是個很重大的決策!

下面提供了五種不同的企業命名方式可以參考看看。

真實的字 (Real words)

Yahoo、Apple、Amazon 和 Twitter 都是這種類型的命名方式,他們都是一些有意義的字,但是跟你的事業並沒有直接的關係。

採用這種方式的理由:有趣而且相當容易有記憶點。畢竟創造 Yahoo(之前叫做 Jerry’s Guide to the World Wide Web)的人不只找了一個唸起來很有趣的名字,而且他們也建立了一個獨特的廣告口號。

不採用這種方式的理由:名字可能會太抽象而且讓人有點困擾。如果這個字選的不好,那可能不會對你的事業有啥幫助,另外其實你也很難找到沒被用掉的網域。不信的話可以馬上去試試看!

拼錯的字 (Misspelled words)

你當然知道怎麼拼字!不過用些拼錯字的同音字可能會讓你的公司感覺起來很酷。想想看下面這些例子:Tumblr(Tumbler), del.icio.us (delicious), Digg (dig), flickr (flicker) 還有 Google (Googol)。

採用這種方式的理由:除了拼錯的字會特別突出外(不信問看看你的英文老師),而且這也可以幫你克服那些討厭的網域註冊問題。

不採用這種方式的理由:用拼錯的字可能會讓人覺得有點困擾,而且會讓人家在網路上比較難找到你的公司。

兩個看起來有意義的複合字 (Two syllable, compound words)

很多新創公司都朝這個方向前進,像是 Birchbox, SkillShare, Crowdtilt 還有 JackThreads,或許 Facebook 的成功也讓這個風潮有點原因?

採用這種方式的理由:似乎有無限種可能可以把兩個字湊起來表示你的事業,而且相較之下 URL 也會好找很多。

不採用這種方式的理由:感覺起來好像很難想像到這樣做會有啥壞處,不過有的時候這種方法會被玩的太過火,要確認你不會僅僅因為好玩就隨便把兩個字湊起來。

每個字開頭的縮寫 (Initials and acronyms)

想想比較老派的公司像是 IBM (International Business Machines), AOL (America Online) 還有 TBS (Turner Broadcast System),

採用這種方式的理由:如果數個單詞加在一起可以精準的描述你的事業,用縮寫的方式似乎是一個很合邏輯的答案。這樣也會更容易讓你的客戶和事業夥伴記得你的名字。

不採用這種方式的理由:很無聊。而且大多數的公司都用三個字母的縮寫,不過所有三個字母縮寫的 .com 網域都被註冊走了。所以你可能要花上一大筆錢才能獲得這個網域。

假的字 (Made-up words)

Skype, Hulu, Zynga… 儘管一點意義都沒有,不過感覺起來很好玩。

採用這種方式的理由:無中生有你的品牌名稱讓你可以有很多發揮創意的空間,如果你做的好的話,一個虛擬的名字可以弄的很引人注目而且很好記。

不採用這種方式的理由:創造一個名字可能會有幾個缺點,包含了可能會導致其他人對你品牌的誤解和困擾。如果名字太虛無飄渺,那可能會很容易被人家忘記,或是根本就是完全被忽略。

Reference: 5 Trends Entrepreneurs Should Consider When Naming a Startup

Photo by Le ciel azuré, cc licensed.

2012年8月26日 星期日

你其實並不懂 JavaScript



筆者之前曾在 Ideasnow! 上面發表過這篇翻譯文章,最近重看有許多不同的感覺,再加上有些部分之前研究 jQuery 原始碼以及 JavaScript 效能的文章有看過,所以稍微編修了文章再加上了一些個人見解。

偶然在網路上看到這篇文章,覺得很有趣,必須說這篇文章有幾點我不太了解的地方,所以有稍微查了一下,想說乾脆用我自己理解的方式把它翻譯一遍。如果你也恰巧很了解 JavaScript,然後發現我理解錯誤,那請一定要糾正我:)

你其實並不懂 JavaScript

本文翻譯自:http://www.w2lessons.com/2011/04/you-dont-know-javascript.html
This article is translated from: http://www.w2lessons.com/2011/04/you-dont-know-javascript.html
過去一年多來,我發現到一個正在發生的問題,一個令人不是那麼開心的現象。我一再的看到程式設計師在履歷裡面填入他們實際上不是這麼熟悉而只是稍微接觸過的技術。而在那麼多的程式語言中,最常被用來填充在履歷上的正是 JavaScript。

你甚至沒有意識到你不了解 JavaScript

之所以會這樣,大概是因為幾乎每一個 Web 工程師或多或少都會有需要用到 JavaScript 的時候。在沒有完全了解的狀況下,最常看到的學習 JavaScript 的方法是依照需求從網路上隨便找一段 JavaScript 程式碼片段,配合一些間單的編輯之後再進行「剪下與貼上」。這麼一來,工程師從未真正的學習語言可是卻往往會有一種錯覺,自以為自己懂。在經過了一些學習的課程以及 JavaScript 工作了幾年下來之後,我發現在你真正的了解它之前,你並沒有意識到你其實並不懂 JavaScript。看起來這是個惡性循環的問題。而其實你需要的只是有個人來告訴你你並不懂 JS,並且你需要一些真正的學習來了解 JS。時常面試到一些很驕傲的把 JS 列在履歷上的人,但其實僅僅只是做過一些從網路上來的程式碼片段擷取而成的 onClick 的處理函式或是表單驗證的簡單工作。如果有用過或是有一些 JS 框架(例如 JQuery 或 Dojo )的知識的話很好,但是在沒有完全的了解實作這些框架的 JS 的情況下,其實並無法真正的熟悉了解這些 JS 框架。為了展現 JS 的各種組成原理,我把這些概念區分為基本,中等,進階三個等級:

基礎等級包含:

  • 知道基本的 JS 語法,例如迴圈,if 判斷式,try/catch 等。
  • 了解各式函式定義的方法以及指派的方法,包含了解匿名函式的使用。
  • 了解基本的 scope 原則,包含全域 scope (window) 以及物件 scope (不包含 closures)
  • 了解執行 context scope 以及 this 的使用
  • 了解各種生成物件以及宣告物件的方法,包含函式本身也是物件的概念。
  • 了解 JavaScript 的比較運算子的意義,例如 ‘>’,‘<', '==', '===' 
    • == 與 === 可以稍微簡單解釋一下
      • == 會對運算元做完物件轉換(可能是隱性)之後再來做比較
      • === 則不會做物件轉換,並且必須連同 type 也要相同才會被認為是 equal 
  • 物件與陣列生成方式的不同

中階等級包含:

  • 了解 timer 的使用,以及使用的時機以及使用的方式,比如用來當做非同步執行的方法。
    • 譯者之前寫過一篇文章有稍微提到使用 timer 來達到非同步執行的一些技巧
  • 深度了解 callback 以及 function 的使用,例如透過 call 以及 apply 兩個 function 函式來改變 context 以及 function 參數傳遞。
  • 了解 JSON 表示法以及eval函式。
  • 了解 closures,包含對於效率的影響,以及如何透過 (function(){}()) 達成 private變數
  • 了解 AJAX 以及 物件字串化

進階等級包含:

  • 了解  ‘arguments’ 以及如何利用 ‘arguments’ 以及 ‘arguments.length’ 達成function overloading。以及利用 arguments.callee 產生遞迴呼叫。特別要注意的是 arguments.callee 可能會有點問題因為 ECMAScript 5 Strict Mode 並沒有支援 callee。不過 jQuery以 及 Dojo 都有用到就是了。
  • 進階的 closures 應用,例如 self-memorizing functionscurrying,以及 partially applied functions
  • Function 與 html prototyping(prototype chain),並且知道如何 reuse 既有的 JS Objects 與函式 (例如array)
  • 物件型別,以 及 instanceof 與 typeof 的使用
  • Regular expression
  • with 敘述式,以及為什麼不應該用 with
  • 最難的在於把所有這些串接在一起組成乾淨的強大的快速的好維護並且跨瀏覽器的程式碼
最後這一點尤其重要,並且也最難做到。在JS允許非常自由寬鬆的寫法的狀況下,你的code很容易就會變成一團混亂的無法維護的 ”spaghetti code” (譯註:大概是說你的code邏輯扭扭曲曲而且互相糾纏的意思)。一旦你真正的去學習JS,並且能夠結構化它並且把他們組合成一大型的 Web Application,那你就真的精通 JS 了。而這需要數年的練習,以及可能需要從『錯』中學習,並無法僅僅從書上學到。我自己本身每天使用 JavaScript 好幾個小時已經好幾年了,並且持續的找到一些更好的方法來結構化我的 code。貿然的直接去看一個 JS framework 是很危險的,因為 jQuery code 有點慢慢傾向難以維護的態勢 (譯者:最近有在研究 jQuery 原始碼,的確是很容易在裡面迷失,而且架構稍微不好懂,但至於是不是難以維護就見仁見智)。Dojo 則是提供了些支援透過他的 Class 與 Package 來幫忙結構化 JavaScript 程式碼。
在 Node.js 之後,JavaScript 已經從前端滲透到後端了,所以我決定把web相關的知識從以上的需求區隔開來。就是在 Web (也就是 DOM 與 IE) 這一塊給了 JS 一個不好的名聲,並且嚇到所有的Programmer。儘管如此。如果你正在學習 Web 方面的 JavaScript,身為一個好的開發者,有一些其他的事情你必須知道:
  • 有效率的新增,移除,改變 DOM nodes。包含透過一些工具(例如,document fragments)來盡可能減少瀏覽器的 re-flows。
  • 以跨瀏覽器的方式去存取DOM元件的資訊。最後是透過一些既有的 jQuery/Dojo 框架。很重要的是必須了解來自 CSS 或是 style tag 或是運算過後的屬性之中的差異性。
  • 跨瀏覽器的事件處理,繫結,反繫結,事件傳遞 (bubbling),以及如何達到想要的 callback context。一樣,這些最後是透過既有的 framework 來做,但是開發者還是必須瞭解背後的原理。
    • 譯者認為 event bubbling 是ㄧ個很重要的觀念,尤其如果一開始就很習慣使用 jQuery (或其他 framework) 在做 event binding 的時候很容易就忽略掉這一塊,但如果對 event bubbling 不了解的話很容易在 DOM element 有重疊的時候會出現一些自己無法預期,或是明明原本做得到,但卻因為不了解特性而繞路的做法。
  • 擴充物件屬性 (expando) 與屬性設定 (attribute setting) 的差異,包含效能的差異以及既有同名屬性的差異。
    • expando 的效率遠勝於 attribute setting ( 尤其在 IE 上)
  • 用來抽取 DOM node 的 regular expression
    • 其實譯者自己也對 regular expression 不熟 (慚愧)
  • 有效的瀏覽器功能偵測以及適當的防錯機制
從以上的各點看來,JavaScript 除了 alert(myVal 以及 myBtn.onclick=… 之外還有非常多東西。在你的 copy/paste 的來源之外還有許多跟 JavaScript 相關的東西可以學習,而你必須透過閱讀以及練習來成為一個真正的 JavaScript programmer。有兩本很棒的書包含了以上所提到的你必須知道的JavaScript,一本是 Douglas Crockford 大師的著作:The Good Parts,另一本是jQuery作者  John Resign 的著作:Secrets of the JavaScript Ninja (譯者個人很推這本)。必須說由於每個人都有一個瀏覽器,JavaScript 大概是一個最容易可以取得(使用)的一個語言。建立一個簡單的 HTML 頁面,並且開始測試以上所提到的概念吧!至於履歷上的列表,我會說如果你已經瞭解基本等級的知識並且正往中階等級學習,那你就有資格把 JavaScript 列在你的履歷表上。一旦你開始開發你自己的 JavaScript 函式而不是只是 copy/paste,那麼你大概可以宣稱你懂 JavaScript。不過在那之前,請停止說你懂 JavaScript。
如果我有漏掉任何一個關於 JavaScript 的面向,請回覆讓我知道。並且請分享你的經驗關於任何你遇過宣稱懂得 JS 或是任何語言的人。
請注意到我並不是一個前端開發者,我其實是一個後端的開發者,並且也是 full-stack 開發者。現今,每一個後端開發者都需要學習 JavaScript,而這也正是我這篇文章想要鼓吹的事情。我並沒有想要顯示我似乎很厲害,因為我很難說我知道關於 JS 的任何一件事情。我想要的只是更多的人可以了解 JavaScript 是一個巨大並且強大的語言。
(Image source: http://ejohn.org/blog/javascript-performance-stack/)

2012年8月6日 星期一

PHP:一個亂七八糟的爛設計 - (III)

續前

上ㄧ篇陳述了對於 PHP 的立場。( 原本 PHP 這篇文章的 priority 比較低,但因為有讀者留言問有沒有續集,因此特地把 priority 先拉高 :) )

本篇文章翻譯自:PHP: a fractal of bad design


不要告訴我這些事情

我太常爭論 PHP 了,以至於我常常得到一些固定論點的反駁。而這些反駁大部分都是刻意用來想要終止對話用的。所以拜託,請不要再把這些論點丟給我,拜託 :(


  • 不要告訴我好的程式設計師不管用什麼語言都可以寫出好的程式碼。這無法代表什麼。一個好的工匠也可以用石頭來敲釘子,但是有多少工匠會拿石頭來敲釘子?挑選好的工具也是一個好的程式設計師應該具備的技能。

  • 不要告訴我記得一個程式語言的上千種例外狀況是一個程式設計師該做的事,我承認在任何系統上,這件事情都是對的,因為電腦很笨。但是這不代表一個程式語言的例外狀況可以無上限般的可笑。PHP 充斥着例外狀況,以至於花在語言本身的時間比寫出你要的程式的時間還多,這樣是不對的!

  • 別告訴我 C 語言不就是這樣,那就去寫 C 吧。

  • 不要告訴我是因為我把奇怪的東西放在一起而搞爛的。如果有兩個功能存在,一定有一天這兩個功能會被某個人放在一起。

  • 不要告訴我 Facebook 跟 Wikipedia 的 backend 也是 PHP。他們也可以用其他狗屁語言寫,只要他們有夠聰明的人可以解決那些問題。

  • 總之,不要告訴我任何事情。如果這個列表無法傷害你心中 PHP 的地位,那再也不會有任何事情可以。所以不要跟我爭了,把時間拿去寫一個超酷的網站,並再破紀錄的時間裡面完成,儘管證明我是錯的吧 :)
我愛 Python,如果你願意的話,我可以無止盡地告訴你我多愛 Python。但我並不是說 Python 是完美的,但 Python 的好處遠勝過它不好的地方。因此我認為 Python 是最適合採用在我目前想做的事情上。


PHP 系列文章:

2012年8月3日 星期五

python logging 做得好,維護沒煩惱


跟 print 大法說再見


每次在寫程式的時候,我們一定會把程式運行中的訊息記錄下來。在 Python 中,我們很順手地,就會做下面這件事:

    print( "The program meets error %d" % error_code )

比如說,當我們遇到讓我們害怕的bug, 慌張的程師設計師一定會用 print 大法,開始到處印內部的變數。 當你開心地找出問題以後,你一定會很無力地看到不少無用的訊息噴出來....

懶惰的天性使然,一定會等到上線再來整理這些一團亂。

另外,程式中有許多有意義的指標,可以讓我們知道程式運行的狀態,你不會希望它們只是被  print 到螢幕上而已,資訊一定要好好地保存好,甚至是很專業地把log 傳送到 splunk 這種log 分析系統裡,程式才可以被清楚地掌握與管理。

說了這麼多,就是要你除了Business Logic 之外,程式裡面不要有無義意的 print。

請開始用 logging 模組

我們現在就開始用使用python logging 來印出資訊。

首先,用簡單一點的寫法,在你的程式的開頭,可以寫這段code.

import logging

logging.basicConfig(level=logging.DEBUG)

logger = logging.getLogger( __name__ )

這樣就好,沒有別的事情了。

然後,在你的程式中,依照你的需要,你可以寫出如下的 code

logger.debug( "enter the XXX function" )
logger.debug(" user exists 10000 ")

從此以後,你需要做的事就是把資訊,依照 debug, info, warning, error, critical 這些分類來把資訊印出來,像是前面的例子,你只要把第二行的 logging.basicConfig(level=logging.DEBUG) 中的 level 換成別的level: logging.basicConfig(level=logging.INFO)。這樣子的話,程式中為了除錯所加的 debug 訊息就不會再出現了。


不過,上面講的簡略的做法,只是讓你了解logging該怎麼使用,並且只是把 logger.debug 當做可依情況關閉的 print 來用而已,我們需要如下更有彈性的寫法。

這個寫法的原則很重要,請都用下面所說的想法來設計 logging 相關的程式:

  1. 每個 module, class, 都用自已的logger, 當你寫的code, 是一個被人使用的module, 請不要去做多餘的設定。
  2. logger 的名字為了要能方便找出問題,所以不要亂取,通常都是以 module 或是 class 的名稱來命名。
  3. logging 的設定,由整個應用程式的main 函式來決定, 比如說決定該把log 寫到檔案或是將log 傳送到syslogd .我們目前是為了簡單好用才以 logging.basicConfig 當作例子。更實際的例子會用 logging.config.fileConfig. 

所以,當你寫一個 Utility 型式的程式的時候,你只要記好下面這三行code. 其它都不要多想。

import logging
logger = logging.getLogger( __name__ )
logger.debug( "XXX" )


 logging.basicConfig

講完了 Utility 該注意的事情了。你免不了要寫程式進入點。在這裡就是設定logger的地方, 但是python 的logger 由於很有彈性,所以有點複雜。看完了Python logging 的 Reference 以後,我們可要寫像下面落落長的設定才可以把log 寫到 standard error 


恩,要先弄懂 formater, handler。能先弄懂是很好,但若是第一時間弄不懂,你可以把那些設定換成 logging.basicConfig(level=logging.DEBUG) , 就像更前面的例子一樣。若是你想把log 導到檔案中的話,那就只要做一點修改: logging.basicConfig(filename='/tmp/logs', level=logging.DEBUG), 加上 filename 這個 argument就好。 記好 basicConfig 這個捷徑,logging 更快樂~

logging.config.fileConfig

為了更有彈性,更完整的功能,logging module 提供了 ini 檔的格式的設定檔。

有的時候我在想,連logging這件事情都要寫一個config 檔案,就覺得程式設計師好辛苦啊~ 不過苦歸苦,生活還是要過.....

在這裡不講logging 的config 檔怎麼寫,就只講 logging.config.fileConfig 這個function 該怎麼用。

這個function call 做的事還是設定logger, 所以它該出現的地方,跟 logging.basicConfig該出現的地方是一樣的。

不過,在 python 2.6 後,這個 function call 多了一個參數,叫做disable_existing_loggers,使用上,請把它設定成False, 但python 為了向後相容2.5版以前的python,把它的預設值設成了 True. 

如果照它的預設值 disable_existing_loggers=True 來執行的話,在這個function call 之前所get出來的logger,  如果沒有在 config file裡面設定過的話,就會被disable。也就是說,極容易發生你的 module 寫的log 都寫到空氣裡了。

所以,請這樣子用 fileConfig:

    logging.config.fileConfig('log_config_file", disable_existing_loggers=False)

如此,你才有機會看到各個 module 所寫出來的log。

最後

logging 有點複雜,但請熟悉一下這篇文章整理的做法,這樣才能在短時間寫出好維護的好Code!! 當然,還有很多不同的Style 來設定 logger, 如果有不同的Style, 也可以一起討論!

然後,幫強者我同學 Scott 廣告一下他的手機APP (真是不搭) http://blog.walkthisway.tw/post/27802609152/taiwan-host-bnb


2012年8月2日 星期四

人比人氣死人的開信率

日近長安遠
晉明帝才幾歲時,坐在父親元帝膝上,有人從長安來,元帝問他洛陽那裡的消息,聽後流下了眼淚。明帝問父親因為什麼哭泣,元帝把晉朝東渡長江的意思詳細告訴了他,於是問他:「你認為長安比起太陽來哪一個遠?」明帝回答說:「太陽遠。沒聽說有人從太陽那邊來,顯然可知太陽遠。」元帝對他的回答感到驚異。第二天元帝召集許多臣僚舉行宴會,告訴他們上面這些意思,再次重新問明帝。明帝竟回答說:「太陽近。」元帝變了臉色,說:「你為什麼不同於昨天說的話呢?」明帝回答說:「抬頭只見太陽,不見長安。」《世說新語·夙惠》
常有朋友在問我開信率高低的問題,想要知道一般開信率大約是多少 ? 有人說開信率大約在 10% 左右就不容易了,也有人說至少要有20%以上吧 ? 那到底多少 %才算合理呢 ?

真正的問題在於開信率本來就不該和別人比

如果你知道影響開信率的原因,你就知道和別人比開信率真是一點也不理智,讓我來為大家介紹以下幾點會影響開信率的因素:

名單的取得方式

如果是透過買來的,有絕大部份是無效的名單,就算是有效的,也不一定喜歡你的產品或服務,想當然的開信率就會比較低。但如果是會員自行提供的,他本來就對你的產品或服務有興趣也有信任感,所以開信率就會比較高。

名單取得的時間

你說名單都是會員自行提供的,那取得的時間也有差,如果你的名單是半年內收集來的,開信率就會比較高,但如果是累積一、二年以上,其實已經有很多的名單已經失效了,所以開信率就會低很多。

寄進去收件匣

當然啦,如果你的電子報跑到會員的垃圾郵件,那看都看不到,更別說開信了。

電子報的主旨及"第一段"內容

當你好不容易把電子報寄到會員的收件匣中,卻淹沒在滿滿的電子郵件裡,該如何脫引而出呢? 當然第一眼看到的就是主旨,所以主旨寫的好很重要。而現在很多收信軟體、手機、平板電腦都有提供郵件預覽的功能,所以除了主旨以外,你也可以加強"第一段"的內容。

寄送的時間

你的會員通常是早上開信呢? 還是中午開信? 還是晚上開信? 到底是平常日開信比較多? 還是假日開信比較多? 不同的客群都有不同的習慣,說寄信也要看時辰真是一點也不為過。

這樣大家明白了吧,當比較的基準點不同時,開信率是很難和別人比較的,如果你真的要比就是和自己比,然後根據以上各點再來作調整,慢慢提高自己的開信率,進而提高自己的 RPE 囉。

(Photo via marcelleitner bilderleben.at, CC License)


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 :) 
Related Posts Plugin for WordPress, Blogger...