coding crops

[UK] 英國找軟體工作心得

| Comments

在英國工作快要半年,一切都比原本預期的順利許多。這篇紀錄一些我在英國找工作的歷程也給有意願試試的人參考,之後有空會再寫些工作的經驗。

網頁開發效能筆記

| Comments

這篇文章用來記錄一些網頁開發的效能觀念與心得,主要是基於HTTP/1.x下。

成本問題

大家都知道從 Client 端發一個 AJAX 出去,中間經過的路簡單來講會是 Request -> [Server -> DB I/O] -> Response。而在這樣的一條路裡,發一個 Request 是最貴的成本,是 DB I/O 遠所不及的,這是最重要的一個概念。這個概念在行動網路下更為重要,因此建議要盡量減少發出 Request 的數量,但也不要因此把許多不同的事情都塞在一個 Request 裡做,在減少 Request 成本中與單一權責區分或可維護性下的設計概念下還是需要有所區別的。

結構與渲染 (Render) 流程

在網頁開發中,除了我們自己寫的AJAX 會送 Request 之外,import 的外部 script 也各會發一個 Request。意思就是不考慮 Cache,當你在 HTML 中 import 了十個外部的 script tag,那就是送出十個 Request。

而在 HTML 的渲染機制下,基本上是從檔案的上跑到下。在以前的寫法會把 CSS 與 JS 寫在 head 裡,而如果在 head 裡 import 了 10 個 js 檔,則瀏覽器會等這十個 js 都 response 回來之後才繼續往下 render DOM。

這樣的流程會導致網頁 hang 在那一段時間造成使用者體驗 (UX) 不好,並且通常 JS 會做的事主要會是等 DOM ready 才需要做,因此近代的寫法會建議將 import js 寫在 body 的最底部,如此可以不阻塞 DOM 的渲染而提升 UX。但是這部分不是說大家搬到下面放都會比較好,還是要視各位的 JS 做的事而定。

Stalled Time

所謂的 Stalled Time 指的是Client 端需要的資源在「被排進 Queue 後等著發出 Request 去向 Server 取得靜態資源前的等待時間」,主要意思就是瀏覽器是需要排隊來發 Request 的。

在每個瀏覽器的實作下會有些許不同,以 Chrome 來說,Chrome 允許對同一網域同時能存在的 Connection Request 是六個。而當需要的靜態資源過多時,可以想見的就是會有很多人在排隊等這六個 connections,因此後面資源(同優先權下)的 stalled time 就容易拉的比較長。然而我認為 Stalled Time 所代表的意義不是直接表示著網頁效能的優劣,他表示的意義我覺得主要是「可供改善的空間」。

下面這張圖是我們專案裡面某頁面資源的 Request 資訊,可以看到他光等著送出 Request 就花了 1.26 秒,而真正送出 Request 到拿到 Response 只需要 24.26 ms。這有種苦守寒窯十八年的感覺,不過這不是重點。這張圖所代表的意義是他花了 1.26 秒在等前面的人,那意味著他前面被要求的資源不是太多就是太久,而在我們專案裡面主要的問題是太多。這部分能改善的方法有很多,bundle js 與 css 已經能改善一部分,剩下且比較容易做的應該就是讓 icon 交給 css 與字型來處理或是組合成一張透過座標來存取等。

非同步做法

AJAX 主要的用意就是希望透過非同步的做法來增加 UX,因此在發出 AJAX 後理論上應該要繼續去做其他事情並且持續更新頁面會是比較好的做法。而基於這個目標下,有些瀏覽器會開始對同步 Request 慢慢做些限制然後進一步準備淘汰,畢竟如果同步的話就不叫 AJAX 了嘛。
如果大家有打開 Chrome 的 DevTool 應該會對下面這張圖蠻熟悉。

他主要說的就是:『別再用同步的 AJAX 了,那會讓使用者不爽而且我們已經在 Main Thread 裡準備淘汰。』
這宣告其實蠻久的了,因此建議大家在新的功能實作上不要再使用同步 AJAX。如果擔心一些 function 需要排隊來做才不會歪掉的話,那建議可以使用 defer 或是 Promise 的寫法,他們不僅能強制使 AJAX response 回來再做下一件事,並且能減少過度縮排的程度以讓程式碼好看,避免 callback hell。

ref:
Evaluating network performance
Web Fundamentals Performance

[PY] Integrate Vagrant with PyCharm

| Comments

之前開發 Django Project 時都是直接在 Mac 的 local 環境中加個 venv 做簡單隔離而已
但從之前參加 DevOps meetups 時聽到 packer 後,就一直想讓 dev 跟 production 的環境整成一樣
然後 deploy 時直接整個 box deploy 上去

而最近終於有些時間,所以準備來把之前的專案來調整一下,順便做些重構

而由於我開發專案其實蠻依賴 PyCharm 的 debugger
所以第一步就是要來把 Vagrant 跟 PyCharm 做個整合,也因此才有這一篇

之所以要整合 Vagrant 跟 PyCharm 的原因是我的 box 必須是個 ubuntu,
而 PyCharm 則是在我的 OSX 裡
因此我勢必要把 PyCharm 的 python interpreter 用 box 裡的做替換,如此才能透過 PyCharm 來 debug
(而如果單純習慣用 pdb 的使用者們,基本上你就直接進 Vagrant debug 就好,可以直接跳過這篇)

接著開始為這樣的環境做記錄,基本上這篇只是我用來以備下一次忘記怎麼設定的筆記,所以懶得截圖了

首先 PyCharm 必須有 Vagrant 的 plugin,這在 Pro 4.5 中已經有內建
只要在專案中的 tools\vagrant\init 就好,
init 的時候會問要用哪個 image,這邊如果 local repo 有之前下載過的 image 就可以直接用
如果沒有的話,他會連到 Atlas 去當場下載,所以這部分的網路要注意一下

init 後基本上就會產生 Vagrantfile,而這就是我們的 box 設定
此外,相關的專案程式碼檔案預設會同步在 box 裡的 /vagrant 下
要更改可以在 projectPath/.vagrant/machines/default/virtualbox/synced_folders 做修改
又或者是直接到 Vagrantfile 裡去改也是可以

接著由於他就是個 virtual box,所以要針對 port 做一些 mapping,以便我們能從 host 連到 guest box 裡去
這部分就是修改 Vagrantfile 的 forwarded_port
例如:
config.vm.network "forwarded_port", guest: 8000, host: 8080

基本上到這邊已經可以 Vagrant up 然後直接起 PyCharm 的 debugger 來 debug 我們的 django project 了
但是應該會遇到一個小問題,就是你在 box 裡面會連得到頁面,但是到 host 來卻連不進去
可是明明 port binding 已經做了,那怎麼還是連不進去呢?

這讓我重複檢查了一陣子...
基本上這篇其實也是為了記錄這部分,以避免下次又踩在這個坑裡浪費時間
這部分在 stackoverflow 找到了答案,原來是要把 IP binding 成 0.0.0.0
所以接下來就可以輕鬆地在 host 去 debug box 裡的 code 了!

ref: http://stackoverflow.com/questions/15255203/vagrant-is-not-forwarding-when-i-run-django-runserver-on-ssh

[JS] From Classic Async Function to Promise

| Comments

以常見傳統的 JavaScript async function 來說,其形式大概會類似:

function myLoadFile(path, callback) {
     fs.readFile(path, function(err, res){
          if (err) { callback(err); }

          callback(null, res);
     });
}

這是一種 err first callback 的模式,就是有 err 會帶在第一個參數用來讓帶回去的 callback 來確認到底是成功還錯誤。

那這種傳統的 async function 要怎麼改寫才能讓我們用 Promise 的方式串起來循序的做呢?
以下用 Q 來當作範例的寫法,其實就是用個 Q.Promise 把它包起來而已:

function myLoadFilePromise(path) {
     return Q.Promise(function(resolve, reject) {
          fs.readFile(path, function(err, res){
               if (err) {
                    reject(new Error(err));
               }
               else{
                    resolve(res);
               }
          });
     });
}

但是如果我們已經是一個很大的系統,且裡面有很多這種 async function。如果全部都要這樣包起來做的話,那真的是一件很大的工程。
所以大多數的 Promise lib 都有提供簡單的 wrapper 來幫助我們包這種 err first 的 async function。
像是 Q 就可以用 Q.denodeify 來將原本的 async function 當作參數丟進去,這樣他就能夠回傳一個包裝過後的 Promise 回來給我們用

例如:

var myLoadFilePromiseWrapper = Q.denodeify(myLoadFile);

其餘像是 bluebird 也有提供 promisify() 來當作 promise wrapper function 等。

到這邊為止應該解決了 lagacy async function 轉成 promise 的問題,
但如果有的 function 你想要讓他可以用給 callback 的方法用又想要讓他可以用 promise 的方法來串呢?

那可以用 deferred 來改寫:

function myLoadFileBothCompatible(path, callback) {
     var deferred = Q.defer()
     fs.readFile(path, function(err, res){
          if (err) {
               deferred.reject(new Error(err));
          }
          else{
               deferred.resolve(res);
          }
          return deferred.promise.nodeify(callback);
     });
}

包裝成這樣的 myLoadFileBothCompatible 就可以給 callback 或用 promise 的方式來使用了!
不過我覺得這比較偏向一種過渡期的用法,統一 API 的使用方法應該還是比較對的一條路,不然有的要用 callback 有的要用 promise 可能會增加使用者的混亂程度。

[JS] JavaScript Callback Hell with Promise

| Comments

寫 JavaScript 免不了會寫到許多 asynchronous 的 function,
而當 async function 做完事的下一步通常就是去執行丟進去的 callback function 。

由於這樣的特性,有些我們想要等到 async function 做完才做的事就會寫在 callback function 裡。
如果只是一些簡單的處理那還好,但如果是又包一個 async function 進去呢?

最後會發現程式碼越寫越右邊了,可能會長類似:

var doSomething = function(){
     myReceiveService.getToken(auth, function(err, result){
          if (err) { throw err; }
          if (result) {
               myReceiveService.getDeviceValue(result, function(err, retValue){
                    if (err) { throw err; }
                    myResponseService.sendDeviceValue(retvalue, function(err, status){
                         if (err) { throw err; }
                         ...
                    });
               });
          }         
     });
};

這種太多層巢狀且越來越往右邊的結構有人說叫 pyramid of doom,也有一些人叫他 callback hell。
而為什麼會寫出這種結構呢?

探究原因就是我們想要他循序的去執行這些 function,譬如說 a 做完再做 b,b 做完再做 c;並且,如果前面的 a 已經 error 了,那後面的都不要再做了

如果認可與需要這樣的邏輯,那你需要的東西可以用 Promise 來做,Promise 大概是這樣的概念:

  • 循序的作
  • 結果只有會成功或失敗
  • 前面的錯了,那後面就 abort

有興趣可以參考 Html5Rock 的這篇文章

而這樣的概念想實現又不想寫出上面那種很醜的 pattern 的話,
那可以找找以 Promise/A+ 這種標準的 lib 來用,
Node.js 比較常用的大概有 Q 與 when

下面就是用 Promise 改寫上面的 snippet 後的長相,相對來說漂亮很多了:

var doSomething = function(){
     myReceiveService.getToken(auth)
          .then(myReceiveService.getDeviceValue)
          .then(myResponseService.sendDeviceValue)
          .fail(_promiseErrorHandle);
}

但這樣又有個問題,
我們原本就有的 async function 基本上是無法這樣直接串起來的,
因為要這樣串起來必須要 return 的是個 Promise 的 object
所以我們的 function 就需要稍微改寫一下

下一篇就來講講怎麼改寫我們舊有的 function,
讓他能夠同時用 promise 的方法去串又能同時用舊有丟個 callback function 的方法去 call

[JS] JavaScript Factory Pattern

| Comments

首先來看常見的工廠模式,下面是一個簡單的飲料工廠與飲料類別的 snippets:

function BeverageFactory(){}
BeverageFactory.prototype.createBeverage = function(type) {
    var Beverage;
    if (type === 'GreenTea') {
        Beverage = require('./GreenTea.js');
    }
    else if (type === 'BlackTea') {
        Beverage = require('./BlackTea.js');
    }
    else{
        Beverage = require('./SpecialTea.js');
    }
    return new Beverage();
};

module.exports = BeverageFactory;
function GreenTea() {
  this.name = '';
  this.id = '';
  ...
}
GreenTea.prototype.type = 'GreenTea';
module.exports = GreenTea;

當想要生產新的飲料的時候就是 var beverage = new BeverageFactory().createBeverage('GreenTea');

但是上面這樣的工廠有一個問題,假設今天飲料有五十種的話,那就要五十個 if else 或是 switch case,
這真的很不好看,而且對於效能魔人的話會覺得每次都要這樣去比會有效能問題

因此,以下是一個用 map 來改寫的簡單範例
而且把這個 map 放在 prototype 裡面也就不會每 new 一次就來一個 map 佔空間

function BeverageFactory(){}

BeverageFactory.prototype.beverageMap = {}
BeverageFactory.prototype.beverageMap['GreenTea'] = function() {
    return require('./GreenTea.js');
};
BeverageFactory.prototype.beverageMap['BlackTea'] = function() {
    return require('./BlackTea.js');
};

BeverageFactory.prototype.createBeverage = function(type) {
    if (typeof this.beverageMap[type] !== 'function') {
        throw new Error(type + ' does not exist.');
    }
    return new this.beverageMap[type]();
};

module.exports = BeverageFactory;

[Note] Cached QuerySet in Django

| Comments

Django 裡的 QuerySet 是否 cache 的準則似乎是 depends on 你接下來要拿多少資料
像是下面這樣兩次都去做 loop all,那第二次用的就會是 cached 起來的 data 而不是再去 db 撈一遍

>>> queryset = Entry.objects.all()
>>> print([p.headline for p in queryset]) # Evaluate the query set.

>>> print([p.pub_date for p in queryset]) # Re-use the cache from the evaluation.

而如果只是要拿少量的資料,像是下面都只去拿其中一筆,那即使都拿同一筆,但是 Django 還是會去做兩次的 db io

>>> queryset = Entry.objects.all()
>>> print queryset[5] # Queries the database

>>> print queryset[5] # Queries the database again

所以如果是每次拿少量但是又不想一直 db io 的話,那可以先 loop through 一遍,這樣之後用的都會是 cached 起來的 data 了

>>> queryset = Entry.objects.all()
>>> [entry for entry in queryset] # Queries the database

>>> print queryset[5] # Uses cache

>>> print queryset[5] # Uses cache

p.s. code snippets from DjangoDoc

[JS] Interface in JavaScript ?

| Comments

OOP 一句很著名的話:針對介面做事。

然而,在 JavaScript 裡面我們無法這麼做,因為 JavaScript 沒有介面而且他是 weak tying
當然也是可以模擬的出來,大概長的會像:

var widgetInterface = function(){};

widgetInterface.prototype.setStyle = function(){
     throw new Error('Not Implemented');
};
widgetInterface.prototype.appendValue = function(){
     throw new Error('Not Implemented');
};

module.exports = widgetInterface;

而要怎麼去 implements 呢?

var lineChartWidget = function(){
};

var util = require('util');
var widgetInterface = require('./widgetInterface');
util.inherits(lineChartWidget, widgetInterface);

lineChartWidget.prototype.appendValue = function appendValue(){
     this.value.test = true;
     return this.value;
};

這邊故意不實作 setStyle ,如果是一般 OO language 的話,在 compile 的時候應該就報錯了
但是 js 要到你真正去 call 了 setStyle 的時候才會報錯,所以如果你不實作也不 call 的話,那大家和平共處相安無事
而這也算是 js 的特性,有好有壞

因此針對 interface 我是這麼理解:

模擬出來的 interface,他所能做到的只是個類似 spec doc 的功能
告訴你應該實作那些 function,而之後沒有真正實作這些 function 也不會怎樣

既然沒辦法對介面做事,也沒辦法規範實作的類別,那我覺得在 JavaScript 裡去模擬 interface 真是有點多餘了
畢竟 duck typing 何必真的硬要把所有 OOP 的概念都實作出來呢?
只要 module 跟架構切的夠清楚並且容易維護就好了吧!

[JS] JavaScript Static Method

| Comments

靜態成員在 OOP 當中是 class 層級的東西,亦即他是跟著 class 的生命週期而不是 instance,因此他在取用上不需要透過實體來做存取。

在 JavaScript 裡我們要怎麼來做到這一點?
要使用靜態方法,我們首先透過建構式來模擬 class:

//建構式

var Device = function(){
};
//靜態方法

Device.madeBy = function(place){
     return place;
};

透過上面的程式碼,madeBy 就是 Device 的靜態方法
要使用它只需要 Device.madeBy('Taiwan'); 即可

而在這樣的模式下,要怎麼建立實體或一般方法?這時候就要透過 prototype 了
基本上以我的認知,prototype 是實體層級 (實體層級並不是一個通用說法,但是我是這麼理解)
prototype 在實體尚未建立前,你直接透過建構式是沒有辦法去 access 到的

延續上面的例子,我們加一個實體方法進去

//實體方法(一般方法)

Device.prototype.setType = function(type){
     this.type = type;
};

在這時候,如果我們沒有去 new 一個實體而直接透過建構式取用

Device.setType('3C');     //error

這時候會發現它會找不到這個 function,因為它的生命周期是從 new 之後才開始

因此如果想用取用這樣的實體方法,我們就要像下面這樣調用

var myDevice = new Device();
myDevice.setType('3C');

然而,這時候你是無法透過 myDevice 去拿到靜態成員的,要想拿到靜態成員的話
我們只能透過實體成員指向原本的靜態成員來使用

Device.prototype.madeBy = function(place){
     return Device.madeBy.call(this, place);
};

其實不知道靜態方法也無訪,或許你早就常常在使用而只是不知道而已
但當需要用到 singleton pattern 的時候,靜態方法/成員就是不可或缺的了!

[JS] JavaScript Module Pattern

| Comments

在最一開始寫 JS 的時候雖然有一些模組化的概念,不過那時候的做法就是很單純的切檔案硬幹
一個模組一個 JS,然後檔案打開就是一大堆的 var 跟 function
完全沒有 global pollution 的概念,最後的痛點就是 global 無止盡地長,然後無止盡的蓋來蓋去...

而後來在學到了立即執行函式(IIFE)之後,開始會把 code 包進去,至此有看到乾淨一點的 global 環境了
這時候的 JS 大概是長這樣:

(function(global){
    ...  
}(this));

而今天在書上(Programming JavaScript Applications)看到了一個更好的 pattern:

(function(exports){
  var api = {
    moduleExists: function moduleExists(){
      return true;
    }
  };

  $.extend(exports, api);
}((typeof exports === 'undefined') ?
  window : exports));

這樣的寫法可以把所有 module 都 append 到 exports 上
但是這樣又有另一個問題,這個寫法是把所有的 module 掛到 exports 下面,就像一串粽子一樣
那當 module 越來越多的時候蓋來蓋去的問題可能又會發生了,然後變成要回過頭來改 module 裡的 code
這聽起來就不是一個好的行為!

因此我們可以透過 namespace 再往上提一層,透過 namespace 來做 module 的管理會是更漂亮的 pattern:

var app = {};

(function(exports){
  (function(exports){
    var api = {
      moduleExists: function moduleExists(){
        return true;
      }
    };

    $.extend(exports, api);
  }((typeof exports === 'undefined') ?
    window : exports));
}(app));

如此就完成一個漂亮的 module pattern,以後寫 module 以這種 pattern 作為起手式會是個比較好的 solution