2015年12月13日 星期日

從 Maker 出發並反思:於是我們成立了 MakerCup!

Standard

Maker 一詞近年來翻紅,有人稱「自造者」,有人稱「創客」,以代工起家的國內產業,覺得 Maker 風潮是一個維持舊有工業地位的方法和機會,更將其引伸成軟硬整合、創業模式,無一不紛紛出來插手,想佔一塊地,分一杯羹。有些媒體將 Maker 塑造成有專業技術能力的人們,彷彿與一般人有很大的鴻溝。種種因素,自然越來越多人不了解 Maker 是什麼了。

但我們認為真正的 Maker 並不是擁有厲害能力的人,而是願意動手落實的人。

為什麼我們要成立 MakerCup?

我們想聚集純粹想動手、交流的朋友,並讓更多人參與並體驗 Maker 的世界。

事實上,Maker 的定義很簡單,凡是能打造、做東西的人,都能稱為 Maker。做菜的廚師,是個 Maker;編織衣服的人,是個 Maker;畫家,也是個 Maker。當然,寫軟體、做電子電路的人,以及各種設計師,通通都算是 Maker。無論在什麼領域,Maker 精神強調的是動手去實現、完成,去參與過程、瞭解過程,進而讓自己更有能力去打造出更多創意十足的東西。更重要的是,在這種不設限的旅程,能讓我們都具備著跨領域思考的能力。

既然過程才是最重要的,我們便開始思考怎麼樣讓更多人交流,交流技術、能力,共同發展和探討更多的知識。我們不應該只是追求一時且短暫的成果,滿足政府或代工產業想要立即成果的 KPI,更或是不應該鑽牛角尖盲目追求頂尖技能,而是讓更多人參與、動手,普遍瞭解更多不同的事物和技能。

於是, MakerCup 這個社群出現了,每週四都會舉辦一場分享交流活動或是小聚會,讓 Maker 平日下班或閒暇時,可以來走走坐坐,輕鬆喝點小飲料,或是現場做點東西:
https://www.facebook.com/groups/MakerCup/

我們希望,這個社群將如一碗太古時代的生命濃湯一般,熬煮出真正的 Maker 生命。

延續黑客松台灣的精神

還記得這一年,我們籌辦了整個年度的「黑客松台灣(Hackathon Taiwan)」,每個月都有 300 至 500 人的大型創作活動,讓不敢踏出來的年輕學子、上班感到無聊的人、及很少離開自己專業領域的朋友,走出來到活動上以「能力會友」。這一年的過程,讓大家的成果,從簡陋成長到真正的創意或產品,從簡單的技術到複雜的應用,從小設計到跨領域的整合。

想當初,很多人剛開始嘲笑我們的成果都像玩具,勸我們不要再辦下去,請大家白吃白喝沒有意義。但一年以後的今天,事實證明我們是對的,黑客松台灣的參加者們,有最堅實的創意、能力和執行力,能解決各式各樣的問題,就算去號稱 Maker 聖地的中國深圳,也絲毫不遜色。

更重要的是所有人都樂在其中,並把這份能力和喜悅,帶回原本的工作崗位上。

同樣的精神,同樣的想法,我們一樣將在 MakerCup 落實。我們希望讓更多人來交流,讓更多人來學習動手,共同成長,而不將只是各式各樣的發表會而已。

我們所見、期待的未來?

從商業角度,許多傳統代工廠,在初面對 Maker 時,都誤將 Maker 當作了新的客人,期望 Maker 能產出點子、找到客人,然後下單。事實上,Maker 並不應該是代工廠下的消費者,而是橫向整合者,將不同領域、需求及客群,重新整頓和安排設計,然後創造出各種新的產業型態。而對於代工廠,精緻化並不再是唯一選擇,成為各行各業的技術供應者亦是一種出路。

所以我們相信,新的世代和市場潮流,不是築一道高牆,將 Maker 拒於專業的工廠門外,而是讓 Maker 視野做廣、扎根,讓大家愛上來台灣當一個 Maker,做出許多前所未有的成果或產品。未來,肯定會有更多的企業投入、民間組織投入,技術上也會有更多模組化解決方案,或是各類的知識交流,甚至是文化交流,來支撐這樣的整合性變革。

不可否認,在 Maker 的年代,什麼產業都將會是科技業,也都會是混血兒產業,誰能迎合這樣多族群共榮,誰就能在這時代中發光發熱。

歡迎加入我們!

MakerCup 社群是由黑客松台灣(Hackathon Taiwan)的部分成員共同推動的,感謝背後有更多合作單位或是朋友的陸續協助和資助,如黑客松台灣講師發起的創作學校「LetSchool」、「聯發科 MediaTek」、「Seeed Studio」、「緯創 Wistron」、「台灣品牌協會」、「台灣土地開發」及「卡市達創業加油站」。不久的將來,還有「Node.js Party」、「IoT Taiwan 社群」、「MakerBot」或是「品酒社群」在這一望無際的場地裡當鄰居。

更多需要感謝的朋友們,不勝枚舉,也歡迎更多人共襄盛舉這樣具有台灣風味的「圓山社群觀光夜市」。

不多說了,先來一杯 Maker 吧!

2015年11月1日 星期日

Lantern 專案:快速打造屬於自己的 Isomorphic 網站服務

Standard

話說,Isomorphic 一直是 Node.js 開發者的夢想,期望同一套程式碼前後端都可以使用,大幅簡化程式碼和加速開發。此外,動態網頁的 SEO 問題也可以同時獲得解決,許多效能問題也可以得到改善。但是,要實現 Isomorphic 的架構,有很多的問題得先解決,會花大量時間在前期工作上,往往讓許多開發者頭痛。

儘管頭痛,仍然阻止不了大家往 Isomorphic 的世界前進,我也因此建立了一個專案「Lantern」,希望能讓更多人能以 Isomorphic 架構,快速建構出自己的網站服務,省去許多前期工作的時間。該專案是一個網站服務的樣板,實作了會員系統、權限管理、第三方登入、多國語系和送信機制等功能,在使用者介面上也做了一個還算美觀的介面。基本上,開發者只要 clone 下來,然後修改設定檔或改改介面、增加點功能,就可以快速完成一個屬於自己的全新網站服務。

最特別的是,「Lantern」整合了現今所有最新的技術和概念,包括了 Koa、React、FLUX、ES6/7+、Webpack 以及 Semantic UI,大量運用了 Generator、class 及 decorator 等最新 JavaScript 語言特性來簡化設計。所以,如果你想要接觸最新的技術,完全可以透過修改「Lantern」專案來學習和熟悉。

目前「Lantern」支援 Facebook 剛發佈的最新 React v0.14+ 和 react-router 1.0.0+,也避免使用像 redux 這類反 FLUX 原始設計的框架,讓原本熟悉 React 和 FLUX 架構的開發者,可以快速上手。也提供一些常見的 Extension,方便開發者寫出前後端通用的程式碼,大多數情況下,開發者不需思考程式碼運行在前端還是後端。

快速安裝使用

若想要使用「Lantern」,方式很簡單,先從 Github 取得程式碼:
git clone git@github.com:cfsghost/lantern.git

安裝必要之 NPM 模組:
npm install

使用 webpack 編譯專案(若要正式上線,可加上 -p 選項來編譯):
webpack

運行網站服務:
node app.js

最後可以使用瀏覽器開啟網址,確認是否成功:
http://localhost:3001/

修改設定檔

一般情況,你無需做任何設定就可以把服務跑起來,但如果你需要修改網站名稱、使用自己的第三方登入設定以及電子郵件伺服器,可以修改 Lantern 的設定檔。設定檔是 JSON 的格式,相當容易修改。


  1. 只要進入到「configs」目錄
  2. 把「general.json.default」複製一份並更名為「general.json」
  3. 修改「general.json」內的設定
  4. 重啟服務

目錄架構

如果你想要開始客製化網站服務,需要先簡單理解「Lantern」的目錄架構。
  • src - 主要程式
    • js - 頁面部分的程式
    • img - 存放圖片
    • less - CSS 原始碼
    • translations - 存放多國語言的對應表
  • routes - 主要為 Restful API
  • lib - 後端的相關函式庫(資料庫、第三方認證、發送電子郵件等功能)
  • models - 資料庫 Schema

快速上手開發

首先記得,只要你修改了「src」底下的任何檔案,你必須重新執行「webpack」來進行編譯。或是可以跑一個「webpack -w」在背景,讓 webpack 在檔案有變更的時候自動重新編譯程式碼:
webpack -w

一般來說,我們會從頁面修改和增減開始進行客製化工作。由於「Lantern」是採用 React 來繪製頁面,所有的頁面程式都將放在「src/js/components」底下,只要看到副檔名為「.jsx」的檔案,就分別是各種畫面上的元件。

建立新的頁面

建立頁面需要修改「src/js/routes.js」,加入一個網址及對應的頁面元件(以 Chatroom.jsx 為例):
module.exports = [
    // 省略 ...
    {
        path: '/chatroom',
        handler: require('./components/Chatroom.jsx')
    }
];

接著可以建立「src/js/components/Chatroom.jsx」檔案,開始設計你的頁面。如果需要使用 FLUX 的機制,可以載入並引入「Lantern」所提供之 decorator 到你的 React 元件上:
import React from 'react';
import { flux } from 'Decorator';

@flux
class MyComponent extends React.Component {
    constructor() {
        super();

        this.state = {
            messages: []
        };
    }

    componentWillMount() {
        this.flux.on('state.Chatroom', this.flux.bindListener(this.onChange));
    }

    componentWillUnmount() {
        this.flux.off('state.Chatroom', this.onChange);
    }

    onChange = () => {
        var store = this.flux.getState('Chatroom');

        this.setState({
            messages: store.messages
        });
    }

    render() {
        return <div>{this.state.messages}</div>;
    }
}

export default MyComponent;

開發自己的 Actions 和 Stores

假設你已經很了解 FLUX 的開發模式,你可以直接開始設計 Action 和 Store。對「Lantern」而言,無論是 Action 和 Store 都是一樣的東西,只不過執行的順序不一樣。

建立 Action(放在 src/js/actions/chatroom.js):
export default function *() {
    this.on('action.Chatroom.say', function *(name, message) {
        this.dispatch('store.Chatroom.addMessage', name + ':' + message);
    });
}; 

建立 Store(放在 src/js/stores/chatroom.js):
export default function *() {
    // 初始化一個 state 用來存放 store 的資料
    var store = this.getState('Chatroom', {
        messages: []
    });

    this.on('store.Chatroom.say', function *(msg) {

        // 加入新訊息到 store
        store.messages.push(msg);

        // State(Store) 已經更新,React 元件會被觸發更新
        this.dispatch('state.Chatroom');
    });
}; 

最後在「actions/index.js」和「stores/index.js」分別載入新建立的 Action 和 Store:
export default {
    // ...省略
    chatroom: require('./chatroom')
}; 

存取 Restful API

「Lantern」提供了統一的方法呼叫 Restful API,無論前端還是後端都可以使用(在 Store 或 Action 中),此外,如果在後端使用呼叫,該方法會自動接續使用者的 Session (登入)狀態,進行 Restful API 存取。使某些使用者登入後才可存取的 API,更為容易被存取。
export default function *() {
    this.on('store.Chatroom.getMessages', function *() {
        var store = this.getState('Chatroom');

        try {
            var res = yield this.request
                .get('/apis/messages')
                .query();

            // 取得聊天室訊息,並更新到 store
            store.messages = res.body;

            // State(Store) 已經更新,React 元件會被觸發更新
            this.dispatch('state.Chatroom');
        } catch(e) {
            switch(e.status) {
            case 500:
            case 400:
                console.log('Something\' wrong');
                break;
            }
        }
    });
};

在畫 React 元件前先預載資料

後端要把畫面送到瀏覽器前,有時需要先資料庫的資料載入,預先植入畫面之中,前端有時也需要預先載入一些資料,以便畫面宣染時有實質內容。我們可以透過載入「@preAction」這個 decorator 來達成這個需求。「@preAction」會在元件初始化前,先去執行一些工作。

底下範例是利用「@preAction」去跑 FLUX 裡的 Action - 「Chatroom.fetchMessages」:
import { preAction } from 'Decorator';

// 相當於 this.flux.dispatch('action.Chatroom.fetchMessages')
@preAction('Chatroom.fetchMessages')
class MyComponent extends React.Component {
    // ...
}

當然可能要預先做的工作不只一項,而且可能要帶入 React 元件的 props 或更多資訊到 Action 中。「@preAction」可以被帶入函數,作更複雜的設計:
@preAction((handle) => {
    handle.doAction('Chatroom.fetchMessages');
    handle.doAction('Chatroom.doSomething', handle.props.blah, 123);
})

因為 Store 會因為「@preAction」而被更新、有資料,這時就可以理所當然地在元件初始化時直接取用 State(Store)的內容。
class MyComponent extends React.Component {
    constructor(props, context) {
        super();

        this.state = {
            messages: context.flux.getState('Chatroom').messages;
        };
    }
    // 省略 ...
}

動態載入 JavaScript 或 CSS 檔案


很多 JavaScript 或 CSS 檔案是隨著 React Component 的載入,才會被動態載入,有時甚至需要照順序載入。此外,通常這樣的機制比較多會被使用在前端瀏覽器的頁面上,同樣的載入程式碼工作,在後端 Rendering 時往往會壞掉而無法通用,這在 Isomorphic 的架構中往往需要特別處理,像是判斷執行期是在前端還是後端,相當麻煩。

為此,「Lantern」提供了「@loader」這個 Decorator,使開發者可以容易引入動態載入的機制,而且不用思考前後端的問題,也可以控制載入順序,或是等待檔案載入完成。

以下範例就是一個載入地圖 API 的範例,載入工作只會在前端執行,不會在後端執行:
import { loader } from 'Decorator';

@loader
class MyMap extends React.Component {

    componentWillMount() {
        // Loader 在後端不會有任何作用
        this.loader.css('https://example.com/css/test.css');
    }

    // componentDidMount 只會在前端觸發
    componentDidMount() {
        this.loader.css('https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.5/leaflet.css');
        this.loader.css('https://api.tiles.mapbox.com/mapbox.js/plugins/leaflet-minimap/v1.0.0/Control.MiniMap.css');

        this.loader.script([
            'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.5/leaflet.js',
            'https://api.tiles.mapbox.com/mapbox.js/plugins/leaflet-minimap/v1.0.0/Control.MiniMap.js'
        ], function() {
            // 初始化地圖 ...
        });
    }

    render() {
        return 
; } }

取得和監聽視窗資訊


為了更方便前端排版,尤其是需要滿版的設計時,我們往往需要得知或監控瀏覽器視窗的大小,通常做法是存取瀏覽器中的「window」物件,並監聽事件來達成。但「window」物件只在瀏覽器上存在,在後端如果存取該物件,會失敗而且有錯誤發生,在以往 Isomorphic 架構中,每次都要特別處理,相當麻煩。因此「Lantern」預設提供了一個名為「Window」的 Store,將這類資訊包裝起來,使 React Component 能輕易存取又不會因在後端或前端而出現問題。

下面範例就是存取 Window 的例子,以及監聽視窗大小改變的事件。
@flux
class MyPage extends React.Component {
    constructor(props, context) {
        super();

        var win = context.flux.getState('Window');
        this.state = {
            winWidth: win.width,
            winHeight: win.height
        };
    }

    componentWillMount() {
        this.flux.on('state.Window', this.flux.bindListener(this.updateDimensions));
    }

    componentWillUnmount() {
        this.flux.off('state.Window', this.updateDimensions);
    }

    updateDimensions = () => {
        var win = this.flux.getState('Window');
        this.setState({
            winWidth: win.width,
            winHeight: win.height
        });
    }

    render() {
        return 
{this.state.winWidth}x{this.state.winHeight}
; } }

看不懂很多 ES6 和 ES7 的東西?

這邊已經整理了一些常用的對應表「ES6 and ES7」,方便開發者理解其中的語法。

更多文件和說明

更多資訊可以參考 Github 上的 Wiki:

後記

其實「Lantern」已經改版了幾次,因為之前在好幾個要上線的專案上,每次都發現有些許不足之處,所以就不斷翻新架構和改進,甚至是優化效能。到目前為止,大致已經算是穩定的狀態,未來的開發方向不外乎是繼續寫 Isomorphic 的 Extension,以及效能優化。

如果你有興趣,歡迎加入並共同改善這個專案。:-)

2015年9月30日 星期三

Git 大哉問:如何為 Fork 出來的專案,同步上游的更新?

Standard


搭配使用 Git 進行開發工作,時常會碰到一個狀況,就是我們 fork 一個專案出來修改,但在我們在修改的同時上游有了更新,這時我們會想要把上游的更新同步下來。這是一個常見的問題,許多人不時會提出來詢問,事實上如果你去 Google ,多半能找到這樣一篇名為「Syncing a fork」的 Github 文件。雖然這篇文章已經把程序詳細列出來了,但還是有人看不太懂,原因是要搭配「Configuring a remote for a fork」這一篇文件一起看才知道來龍去脈。

簡單來說,我們要先把「上游(upstream)」的 repository 加入我們眼前正在修改的專案,然後把上游更新拉回來,最後再與我們現有程式碼合併。

首先,加入上游的 repository 並命名為「upstream」:
git remote add upstream https://github.com/YOUR_USERNAME/YOUR_FORK.git

未來想要更新前,可以使用 fetch 去拉上游的更新回來:
git fetch upstream

最後再把 upstream 的內容,與現有的正在修改的進行「合併」:
git merge upstream/master

2015年9月20日 星期日

Fluky - 打造 Isomorphic App 的副產品:一個基於事件驅動的 Flux 框架

Standard

要講到 Fluky 這一個 Flux 框架,這要從我的一個新計畫說起。因為最近又重新興起了一波 Isomorphic App 的熱潮,許多人開始打造了自己的 Isomorphic 網站,自己也做了一個。而什麼是 Isomorphic 呢?簡單來說,就是寫一次程式,然後前後端都可以使用的機制,也是一個網站服務工程師的夢想。還記得過去自己曾實作了 frex.js 試圖達成 API 層面的 Isomorphic,現在 React 這樣的前端框架,更提供了一個打造前後端 Rendering 的 Isomorphic,使得原本在前端動態產生的畫面,可以在後端產生,更一舉解決了 SEO 的問題。

話說,搭上了這波熱潮,最近開始土炮自己的 Isomorphic App,Github 上開啟了一個「Lantern 燈籠」專案,希望做一個標準的專案架構,讓自己以後開發新專案時,可以不需要重新再來一遍。痛苦的是,與很多人一樣,踩到了很多地雷,在專案架構設計上,也一直有很多許要調整的地方,這也難怪,畢竟這是一個興新的開發概念。

於是從零到有的過程中,也有許多副產品,其中包括了一個新的 Flux 框架「Fluky」。很多人問我為什麼不採用當今紅遍半天邊的「redux」,原因其實很簡單,我不想脫離傳統 Flux 模式和 React 開發的概念太遠,然後同時想要用試著更精簡的方式描述這些流程。另外一點是,受到過去 X11 這世界最先進的網路圖形化視窗系統的設計所啟發,打算試著全面使用「事件」來管理資料流和程式上任何的溝通。

如果你有興趣,可以直接以 NPM 來安裝這個 Flux 框架:
npm install fluky

Fluky 的設計


基本上, Fluky 本身的概念很簡單,幾乎所有的行為都是透過 Fluky.dispatch() 這個 API 來進行,包括呼叫 Action、Store,然後所有的行為都可以使用 Fluky.on() 所監聽。也就是說,只需要這兩個 API,幾乎就已經足夠。對於 View 的工作而言,永遠就是呼叫 Fluky.dispatch('action.*') 和監聽 Fluky.on('store.*') 。

這樣設計有什麼好處呢?因為所有的訊息和命令傳遞,都有統一的事件機制和命名規則,理論上來說,事件可以很容易被提到前端或是放在遠端被處理,這就有點像 X11 的設計,可遠端也可本地端進行圖形繪製處理。不過對 Fluky 來說,這目前還算太遠了,目前 Fluky 還沒有真正處理太過複雜的狀況,純粹就是以完全的事件化來處理資料流。

也因為一切都事件化,就可以良好支援 Isomorphic 的設計,例如很多的 Action、Store 工作,可以有個前後端統一的命名和呼叫方法,在 Server 預處理,便於 Server Rendering 的使用,甚至是一部份在前端做,一部份在後端做都有可能。最重要的是,在 Isomorphic 上會碰到的前後端 state 不一致的狀況,也可以很容易使用事件、或是在事件分配中的空擋,進行 state 同步而獲得解決。

此外,為了嘗試新技術,Fluky 也在前端引入了 Generator ,所以如果你想要使用 Fluky,要確保前端瀏覽器能使用 ECMAScript 6+ 最新的標準,或是你必須安裝 babel 模組來打包並轉換你的程式碼為 ES5。

講了這麼多,怎麼使用 Fluky 呢?下面將以實作一個簡單的 TODO 清單為例。

建立 Action

import Fluky from 'fluky';

Fluky.on('action.Todo.toggle', function *(todo) {
  // 用不同的 store 方法處理
  if (todo.completed)
    Fluky.dispatch('store.Todo.unmark', todo.id);
  else
    Fluky.dispatch('store.Todo.mark', todo.id);
});

建立 Store

var todoStore = Fluky.getState('Todo', {
  todos: [
    {
      id: 1,
      name: '寫一篇文章',
      completed: false
    }
  ]
});

Fluky.on('store.Todo.unmark', function *(id) {

  // 找到指定的 TODO 項目
  for (var index in todoStore.todos) {
    var todo = todoStore.todos[index];

    if (todo.id == id) {
      // 改為未完成
      todo.completed = false;

      // 發出 store 已改變的事件
      Fluky.dispatch('store.Todo', 'change');
      break;
    }
  }
});

Fluky.on('store.Todo.mark', function *(id) {

  // 找到指定的 TODO 項目
  for (var index in todoStore.todos) {
    var todo = todoStore.todos[index];

    if (todo.id == id) {
      // 改為完成
      todo.completed = true;

      // 發出 store 已改變的事件
      Fluky.dispatch('store.Todo', 'change');
      break;
    }
  }
});

在 React 元件內的使用

import React from 'react';
import Fluky from 'fluky';

class TodoList extends React.Component {

  constructor() {

    // 取得 Todo 的 Store,從 Fluky 的 state 資料暫存區
    this.state = {
        todos: Fluky.getState('Todo').todos;
    };
  }

  componentDidMount() {
    // 監聽 store 的改變事件
    Fluky.on('store.Todo', Fluky.bindListener(this.onChange));
  }

  componentWillUnmount() {
    // 停止監聽 store
    Fluky.off('store.Todo', this.onChange);
  }

  onChange = () => {

    // 當 store 有改變時更新元件的 state
    this.setState({
      todos: Fluky.getState('Todo').todos;
    });
  }

  toggle = (todo) => {
    // 呼叫 Action 去切換工作項目狀態
    Fluky.dispatch('action.Todo.toggle', todo);
  }

  render: function() {
    var todoList = [];

    // 印出所有的工作項目
    this.state.todos.forEach((todo) => {
      todoList.push(
{todo.text}
); }); return (
{todoList}
); } }

State 資料暫存區的設計

傳統的 Flux 做法,不外乎是載入所要的 Store 檔案,來取得 Store 資料,這樣做不但麻煩且囉唆。既然事件分配器(Event Dispatcher)是 Singleton(只存在一個實例,所有人共用),將 Store 的資料共同管理顯然是比較簡單的做法,然後只需要統一使用 Fluky.getState() 就可以取得所需要的 Store 資料。

如果從前述範例來看,Fluky.getState() 可以帶兩個參數,第一個是 State 的名稱,第二個是當 State 不存在時,其預設值。

當然,這個暫存區是可以整個取出來的,也可以使用 Fluky.setInitialState() 或是藉由 window.Fluky.state 在第一時間載入時整個放回去,這可以應用在解決 Isomorphic App 的前後端 Store 不同步的問題。

後記


新專案「Lantern 燈籠」目標就是嘗試開發一個 Isomorphic 的網站服務,並使用最新的技術,此外,也希望開發一些基本功能(如:會員系統、第三方登入、權限管理等),方便日後開發新網站服務時,可以避免早期的苦工和踩地雷。這是一個 Open Source 專案,如果你有興趣,可以一同開發。:-)

2015年8月28日 星期五

Node.js 的單執行緒(Single Thread)設計,到底有什麼優點?

Standard

這是個總有人一問再問的問題,到底 Node.js 這樣單執行緒(Single Thread)的設計,到底有什麼優點?為什麼總是有人說,它比傳統多執行緒的設計來得有效率?以往,一旦開始討論這個問題,總是會有人開始提到 Context Switch、Asynchronous 等機制,越講越玄也越講越複雜化。

其實我們可以用簡單的餐廳比喻,就能理解 Single Thread 加上事件驅動(Event-driven)的機制,如何與傳統設計不一樣。

場景想像

試想一個場景:一間餐廳有 100 個座位,然後來了100個客人。

處理方法

身為老闆的你,你會選擇哪種方式服務這些客人:

  1. 請100個服務生一對一服務這些客人。
  2. 請 25 個服務生,看當下狀況服務這些客人。
一般來說,傳統的多 Multi-thread 的設計就類似方法 1,而 Single-thread 且 Event-driven 的設計就是方法 2。

通常大多數情況我們會選擇方法 2,因為客人通常都是處於等待(看菜單、等上菜、吃自己)的情況,並不需要服務生貼身服務。所以即便請 100 個服務生,這些服務生大多數時間也只是等在那也佔地方,而服務生眾多也導致服務生之間的溝通和互動其實不易,更不容易交換資源,回報和協同工作難以進行。反而安排一個小型的外場班,讓裡面的人合作見機行事,會比派出 100 個各自獨立的人來得好。

併發數高的原因

併發數(concurrent request)指的是單位時間內可以處理的要求量,一般用來評估一個網路應用程式的效能。而通常在網路服務裡,併發數也相當於單一時間內能服務的連線數量。

所以,以前面餐廳外場班的模型來說,如果你有 100 個服務生,就可以服務 400 個客人。換句話說,同樣的資源,能處理的併發數(concurrent requests)也就比較高。

後記

不過如果你開的是酒店或按摩店,那可能就要請一百位服務生了。:-)

2015年7月13日 星期一

Geek?技客?是什麼?我不宅,我用動手代替說話!

Standard

因為我對外都自稱一個 Geek,所以時常有人問我,Geek 是什麼?一直以來,「技客(Geek)」個名詞讓許多人感到陌生,甚至對這詞彙一知半解的人,都以為 Geek 與普通宅宅無異,更甚至是覺得這只是另一種宅宅的說法。的確,Geek 某些地方與普通宅宅很相近,指的是醉心於特定專業或領域的人,但這些人擁有絕佳才能,只是為了鑽研知識或研究新事物可以幾乎荒廢其他事,廢寢忘食對他們來說只是小意思,甚至可以犧牲人際關係等一般正常社會交際行為。由此可以看出 Geek 對於自有興趣的事物,將會多努力去鑽研。

不可否認,「技客(Geek)」這個詞在早期帶有貶意,但在現今社會中,人們交流的手段和方法已經透過科技有很大的改變,Geek 可以透過網路或各種新方法,找到能分享交流的同好朋友。透過網路串連,他們不再只是群體中被傳統主流排除在外的人,反而搖身一變,變成強力促使世界進步的主要群體。如今,大家眼中的 Geek ,代表著才能與努力,更代表能「動手完成任務」的傑出人們。開玩笑的說,專心致志(宅宅)加上真正動手實做的能力,就是「技客(Geek)」。

想要成為一個 Geek 嗎?只要你符合這樣的條件,無論你原本是個技術 Hacker、Maker 還是個設計師,更甚至是任何領域的人,都可以稱自己是一個 Geek,甚至可以以此為榮!也許,你也有自己沒發現,但是能堪稱 Geek 的一面也說不定!

後記

為了讓更多 Geek 能聚集在一起交流,共同迸出創新的火花,我們 Hackathon Taiwan 開始了一個新計畫 GeekBar I/O,將嘗試透過各種活動和方式,讓 Geek 們共同發光發熱!歡迎大家共襄盛舉!

2015年7月8日 星期三

快樂玩 ES6 Generator,從 co 起手式開始

Standard

自從 Node.js 0.12 版和 io.js 之後,大量的開發者開始了各自的 ECMAScript 6 大冒險,許多人對 Generator 的使用仍跌跌撞撞,對於這種看似「同步(Synchronous)」的「異步(Asynchronous)」機制,有許多人腦袋遲遲無法轉過來。雖然在小弟的書(參閱連結:新書報到!Node.js 模組參考手冊!)已經有清楚的說明 Generator 使用方法,但就許多讀者回函來看,對於 JavaScript 越是熟悉的人,越無法直觀理解 Generator 的思維,甚至是老是抓不准使用的時機點。

尤其是過去我們已經有 Promise、async、Q 和 bluebird 等處理非同步程式流程的模組和工具,很多人就是覺得沒有使用 Generator 的必要。不過,如果你會使用 co 模組,你會突然發現若是將過去的流程機制與 Generator 相搭配,程式開發將變得更為流暢。

若想要安裝 co 模組,可以直接以 NPM 下載:
npm install co

本文接下來會說明一些 co 的基本使用,破除一些 Generator 難以使用的地方,讓開發者們更容易開始 Generator 的旅程。

讓原生 Generator 更好用

這是很多人不喜歡使用 Generator 的主因,以往為了使用 Generator,我們還要先建立一個 Generator 的函數,然後不時的去處理 Generator 所返回的資訊,一遍又一遍進入 Generator 之中。因此,無論 Generator 再好用,這些麻煩的動作也完全抵銷他的優勢,大多數人還不如回到舊的流程控制方法,以免徒增自己的麻煩。

而如果使用 co,可以直接將 Generator 函數當作「立即函數使用」,其餘的部份我們可以不需要擔心:
var co = require('co');

co(function *() {
    console.log('Inside'); 
}) 

再也不用煩惱 yield!

以前光是 Promise,就已經讓很多人詬病,覺得每次使用 Promise 都要花許多時間對函數進行包裝,而 Generator 也有類似的問題,若是要使用 yield,更是一件大工程。於是,co 幫開發者做了些設計,讓 Generator 可以直接利用 yield 去跑 Promise 類型的函數、陣列等物件,因為是 yield,其執行模式是「異步」,不會阻塞事件引擎。

co 支援 Trunks,也就是回傳一個簡單函數進行異步工作,範例如下:
var co = require('co');

function updateInfo() {

    // 返回一個將被執行的函數 
    return function(done) { 

        // 可以執行各種異步工作
        setTimeout(function() {

            // 完成工作後執行 callback,參數一為錯誤訊息,參數二為回傳值
            done(null, 'Done'); 
        }, 1000);
    };
}

co(function *() {
    // 執行並等待回傳值 
    var ret = yield updateInfo();

    // 一秒後收到回傳值並印出來
    console.log(ret); 
});

支援 Promise 的 yield

如果你熟悉 Promise,這是個好消息, co 讓 yield 可以直接吃 Promise:
co(function *() {
    var ret = yield Promise.resolve(true);
});

直接使用 yield 跑一整個陣列的工作

前面提到 co 讓 yield 也支援陣列,所以我們可以準備一系列的工作,放在陣列中讓 yield 去處理:
co(function *() {
    var a = Promise.resolve(true);
    var b = Promise.resolve(true);
    var c = Promise.resolve(true); 
    var ret = yield [ a, b, c ];
});

異步處理一個陣列裡的所有元素

這大概是以往 JavaScript 最頭痛的工作,後來有了 async 這類模組後,才比較容易處理,不然光使用 forEach 或是 for 迴圈來做,碰到較大的陣列,往往使程式阻塞卡死。如果使用 co 前面的起手式,我們可以試著這樣做(如果不使用 Promise):
var arr = [ 1, 2, 3, 4, 5 ];

function handle(el) {

    return function() {
        // 處理傳入值 
        console.log(el); 
    };
} 

co(function *() {

    // 在 Generator 裡,使用 yield 不會使 JavaScript 事件引擎阻塞,
    // 但因為程式流程會等 yield 結束,for 迴圈也不會造成阻塞
    for (var index in arr) {
        var el = arr[index];
         yield handle(el);
    }
});

後記

經過 co 包裝後的 Generator 非常好用,無論你過去習慣用哪一種流程控制的方式,都可以很好的轉換並整合過來。試試看吧!:-)

2015年5月24日 星期日

黑客松式的學習活動:NodeSchool International Day 精彩紀錄

Standard
 『是不是要很厲害才能參加黑客松呢?』這是一個永遠都會有人問的問題。

事實上,黑客松從一開始的出發點,就是三五好友聚在一起進行研究及開發的活動,帶有很純粹的動機。參加這樣的活動,你不必真的很厲害到極點,而最重要的是動手和夥伴合作,過程中可以順便認識朋友與他人交流,讓投入的過程中更加豐富有趣。

從這樣的角度來看,『黑客松式的學習』是不是有可能的呢?如今看來是肯定的,5/23 於台北剛結束的 NodeSchool 聚會就是一場黑客松式學習活動,吸引了近兩百位參加者,共同參與了這場具有黑客精神的學習活動!

順帶一提,這此使用的場地,也是 黑客松台灣(Hackathon Taiwan) 每個月辦活動的主場地,無論如何,也記得來報名下次 6/13 - 6/14 (六、日)的黑客松活動!

什麼是 NodeSchool?

NodeSchool 是一個線上學校,目標是運用線上工作坊課程,讓所有人都可以在這學到各種技術。而恰逢 NodeSchool International Day 國際日活動,全球各地都紛紛各自舉行一場 NodeSchool 盛會,因此身在台灣的我們也理所當然響應了這樣的活動。 

這個活動的形式相當特別,沒有講師在台上講課,而是採用線上工作坊教材與題目的形式,讓參加者在自己的電腦上挑戰通關。NodeScool 擁有許多工作坊教材,從基礎課程到進階的選修課程都有,除了 JavaScript、Node.js 之外,也有 ES6、WebGL、Three.js、Functional Programing 和 React.js 等課程。詳細的課程資訊,可以參考 NodeSchool 官方網站


由於每一份教材都是經過設計,而且每一個主題和關卡都擁有提示和說明,參加者在一題題解答的過程中,可以靠自己慢慢學會一門技術。如果真的遭遇到了自己難以突破的困難,可以跟左右鄰居討論研究,若真的也沒有辦法了,可以找在現場自願的指導員(Mentor)幫忙排解問題。

黑客松式的學習活動

回想起過去從小到大,我們的學習模式,總是有個老師在台上教學,枯燥而乏味,而為了保證教學進度,不免伴隨著填鴨式的教學方法。這樣的模式我們早就習慣,雖不見得喜歡,但也無可奈何,逐漸的,我們的思考和學習,慢慢僵化了。

具有一定寫程式經驗的人都知道,如果想要把程式技術練好,動手研究是不可缺少的工作,而持續鑽研的熱情更是不可以缺少,學習並習慣自己動手解決一個個問題更是必要的訓練。此外,比起學習到一門技術,我們如何在學習和訓練的過程,培養自己自我學習和與其他人間的溝通交流,更為重要。與他人交換想法,或是藉由分享知識將自己所學內化,更是一種提昇自己的方式。

因此,NodeSchool 活動與過去的學習方法最大不同,就是提供一個讓大家共同參與的形式,目標是要大家能在此透過自己動手學習到技術,也參與交流討論,當大家在活動結束後走出會場時,能帶著貨真價值的學習成果回家。

沒有大神,只有迫切突破自我的歡樂參加者

別以為這樣標榜學習的活動只有初學者前來,其實會眾裡臥虎藏龍,不乏有大神級別的高手參與。只不過,在這樣的活動中,即便是高手,也正煩惱自己手上的問題和課程,也需要更多高手一同討論和解決問題。

由於 NodeScool 工作坊的主題相當廣泛,無論是初學者還是專家,都可以前來,然後挑戰一些自己感興趣的技術,然後號招在現場的高手們,一起討論並學習。我們可以說,NodeSchool 的活動,是一個兼具動手、討論交流和突破自我的學習活動。

後記

如果有人完成了課程,日後可以在參加 NodeSchool 時,自願擔任該課程的指導員(Mentor),如果還沒完成的人,可以回家繼續完成,若碰到問題,可以至 Github Issues 直接發問,或到每週四的 Node.js Party 及讀書會討論。日後,社群應該也會不定期的舉辦中小型的 NodeSchool 活動,讓大家共同參與。

另外有趣的是,這次活動也有碰到一些企業派人來參與或獵才,像我就碰到 Foxconn 等公司的主管級人物前來參加活動,也一起下去研究程式。誰說主管只會動一張嘴呢?至少會來黑客松的主管也會動手!:-D

2015年5月21日 星期四

簡報釋出!超酷炫科幻 UI:QML 入門

Standard

這次五月的 Hackathon Taiwan, 因為原本的講師沒空,所以我就來救援了一場 QML 工作坊,這堂課以活動的倒數計時器為開頭,討論怎麼使用 QML 進行超酷炫科幻 UI 的設計,是一堂初學入門課程。

有鑑於過去聽到不少人反應,要使用 QML 必須與 C/C++ 打交道,門檻相當高,於是,這個工作坊的目標,就是讓學員可以在純 QML 的環境下,以 QML 來開發自己的 UI 界面。即便是不熟悉程式語言的人,都可以輕易使用 QML 做出絢麗的 UI。

活動簡報


後記

這次 QML 工作坊事後反應還算不錯,甚至有人以 QML 來開發黑客松活動的作品,未來如果有機會可以再次開課。:-)

2015年5月7日 星期四

JavaScript 的各種迷霧

Standard
 在 Facebook 看到很多人都在整理自己的程式筆記,我也整理關於 JavaScript 語言常被誤解部份的筆記,然後分享出來供各位同好參考。

講實話,從 1995 年開始的 JavaScript,直到前幾年,都讓我恨的牙癢癢,甚至覺得他是個垃圾,因為當時的他太沒有規範,效能極差。直到近年來才逐步改善,我也才慢慢擺脫對他的排斥及恨意。

但就算已經有標準規範,JavaScript 語言對很多人來說依然很頭痛,除了其 Asynchronous 非同步的機制外,更要面對其極為動態(Dynamic)的特性,而從 OOP 的角度來看,其 classless 的設計更讓人不知所措(ES6 之後開始支援 Class),與傳統的 OOP 相去甚遠。所以,JavaScript 處處與其他語言完全不一樣的概念及思維,讓很多人在裡面處處誤解。

所以若是想學習 JavaScript 深入的概念,最好的方式就是放下其他語言的既有概念,直接了解 JavaScript 的核心機制,雖然會讓人手足無措,但卻是最好的手段。否則,很容易練就或死背很多怪招,寫出難以除錯或過於複雜的程式碼,當然,更嚴重的是誤導他人。

要深入或精通 JavaScript,務必了解並思考其執行程式時發生什麼事,每一行程式碼當下被執行時的狀態,遠遠比程式碼排列來得重要。只要掌握每一行程式碼的『執 行當下』,所有 JavaScript 的疑難雜症,都能迎刃而解。關於這一點,有興趣閱讀硬梆梆定義文件的,可以參考 ECMA-626 的『10.3 Execution Contexts』一節。(參考網址:http://www.ecma-international.org/ecma-262/5.1/#sec-10.3

關於 this 關鍵字的機制

很多人會搞糊塗 this 關鍵字,就因為他是一個會被 Execution Contexts 大量影響的東西。JavaScript 在 Function 或 Context 被執行時,才會依當下情況去決定 this 為何。這代表,使用 this 時在哪一個 function 或地方不重要,而是當進入這個使用 this 的 context 時(如:Function 被執行時)才會決定 this 是什麼,this 是什麼只跟你如何去呼叫 this 所在 Function,及 Context 內狀態的改變有關。關於 Function Call 怎麼決定 this 值,可參考:http://www.ecma-international.org/ecma-262/5.1/#sec-11.2.3

當然,純講理論很難懂,可以大概歸納出 this 的使用方式與情景,有開發者已經整理了很清楚的文件,以及常誤觸的陷阱,可參考:https://software.intel.com/…/blo…/2013/10/09/javascript-this

若實在懶得看懂硬梆梆的文件,從實作面來看,就是記住簡單兩種規則而已。

一般來說,決定 Function 內的 this 為何,說明如下:
  1. 只要直接呼叫 Function ,就會代 global 進去當 this,這也是為什麼 Browser 裡面會是 window,因為在 Browser 裡的 global 就是 window。
  2. 如果一個 Function 處於一個 Object 上,像是 a.b() 這樣執行後,b 函數的內部會視 a 為 this。因為當 JavaScript 在透過 a 物件存取底下的 Function 並呼叫他時,會自動把依附的 a 物件(Caller)帶入當 this。

總歸來說,所有的 this 都是在執行期、呼叫當下所決定的,除非你使用 bind() 這類的做法事先去強制寫死他的 caller。這就能解釋,為什麼 this 總是不能往內部一層層自動傳進去,因為所有的 Function Call 都遵循著同樣的規則。

註:在嚴格模式下 Function 裡的 this 會是 undefined,不是他預設是 undefined,而是嚴格模式下呼叫一個 Normal function 時,他不會自動代入 global,所以才是 undefined。在嚴格模式下不能取得 base value(即 global),這點可參閱:http://www.ecma-international.org/ecma-262/5.1/#sec-8.7

Function 的存在

因為所有的型別都是 Object 的衍生物,甚至是 Function 也不例外,所以 JavaScript 裡的 Function 物件可以隨機被動態生成和定義,與其他大多數語言完全不一樣。其他語言多半將 Function 看做是一個寫死程式碼的區塊,放在一個固定的記憶體位置上,然後跳過去執行。而這樣可以動態定義 Function 的特性,導致 JavaScript 天生就支援 Lambda 、匿名函數等特色,因為可以依照任何情況,重新排列組合,甚至生成 Function 的執行流程。

關於記憶體及物件管理機制

JavaScript 的設計是以可動態擴充性為考量,所以也衍生出一套記憶體管理機制,以 Object 為基本記憶體管理單位。所有存取 Object,都透過一套 『Identifier -> Value』 的機制,而在執行上 Value 其實就是 Object。

與其他語言不太一樣的是,JavaScript 在利用 var 宣告 variable 時,實際上是建立一個 Identifier,並不是實體配置一塊物件記憶體。所以在 Identifier 建立的剛開始,並未對應到實際有意義的參考物件。直到我們額外去建立物件後,才讓 Identifier 去對應。可以說,這就是 key-value 的形式,而 Identifier 就是個 key。這部份可以參閱:http://www.ecma-international.org/ecma-262/5.1/#sec-12.2

由此可知, Identifier 本身不是 Object 的實體,因為他只是個 key 而已,就像 C/C++ 語言中的指標。

有趣的是,這樣 Reference 的機制,與 JavaScript object 中的 key-value 機制是一樣的,這也是為什麼 Object 可以隨意掛勾到任何地方。因為 JavaScript 程式中,我們一定是透過 Identifier/key 去取得並使用 Object,而且可能有很多不同的 Identifier/key 都指向同一個 Object。

記憶體回收

簡單來說,JavaScript 記憶體回收,就是當 Object 沒有被任何 Identifier 指向時,就被視為垃圾而回收釋放記憶體。

new 運算子如何建立一個物件

在定義上,用 new 建立物件要經歷過一些程序,甚至要檢查 Type 等工作,這部份的細節可參考原始定義:http://www.ecma-international.org/ecma-262/5.1/#sec-11.2.2

不過,對於大多數開發者來說,不需了解太多細節,較簡化的流程可參考 Mozilla 提供的文件:https://developer.mozilla.org/…/Jav…/Reference/Operators/new
簡而言之,new operator 會去做到幾件事:

  1. 用 Object.create() 等方法去參考 Function.prototype,建立一個物件
  2. 跑 Constructor(也就是開發者定義的 Function)
  3. 如果 Constructor 內沒有回傳一個物件,就以步驟 1 建立的物件回傳出來。
這一切工作是在『內部(internal)』完成,完成後回傳物件出來。

如果你很清楚這些行為,可以把這些工作往外移,自己實作,並完全不用到『new 關鍵字』,但是通常開發者不會這麼做惡整自己。用 new 以後,等他回傳配置完成的物件,才是一般性的做法。