發表文章

從 IT 轉型看微服務

從電腦、資訊科技開始普及後,每過一段時間,企業界就會開始出現新的 IT 轉型計畫,以整頓整個資訊系統、商業運行模式,然後試圖找出下一個世代的新方向。每一個具規模的企業,都生怕與新世代的技術和商業模式脫鉤,於是砸重金、拚人力,說什麼也要追上世界的腳步。當然,中間的技術供應鏈也不斷在改變,上一世代的王者或是贏家,在下一個世代可能不一定跟得上,因此身為終端用戶的企業,每當面臨新的 IT 轉型計畫,往往也跟無頭蒼蠅般, 不知所措甚至不小心會做錯了決定。 回到十幾二十年前,你也許可以有機會一次性把一個大系統整個替換掉。但在今天資訊系統高度介入管理的時代,版本更迭無數次的平台,沒有一個系統能在一夕之間被整個換掉,哪怕你的品管和測試做得非常足夠,甚至是超標達成,也哪怕你事先的需求訪談談到祖宗十八代都瞭然於心。 這常常是系統設計者和規劃者的誤區,看見許多表面問題,於是設計了一套全新、看起來完美的解決方案。而剩下沒照顧到的部分,都覺得只要能配合測試就可以邊走邊補足。事實上,現代資訊系統的維度和複雜度,已經超出人類的想像,太多的例外、不可控、溝通執行問題,遠遠超過理論上的執行方法。 所以從系統規劃設計的角度來看,我們總是要慢慢來,一段段改善、替換,最終達成全面換血。而且越是龐大複雜且重要的系統,越要切割得越細,透過降維度的方法,來慢慢替換。所以我們在討論IT轉型,從一個肥大的系統換到另一個肥大的系統,已經不會是選項,系統架構的去耦合、高擴展性甚至是服務不中斷才是考量的重點。 這也是為什麼「微服務架構」在近年來特別熱門,微服務真正在討論的不是容器化,而是在討論的是怎麼讓一個龐大的系統,更容易受到管控、維護和擴充,除了可以更容易實現災難隔離、例外狀況、整體高可用性,也可以更有彈性做各種變化。 更重要的是,微服務架構下,其系統風險管理上,從在開發階段開始,每個部分就已經受到隔離和管控。也由於每個被拆解後的系統都非常小,遵循 SRP 原則,在開發維護上相對容易,也可以保證每個單元有更低的問題發生率。至少,你不會因為一顆螺絲或換一顆螺絲,導致整個塔倒塌。 不過微服務架構卻也不是說要做就能做,對於規劃設計者來說,他必須很了解各種新舊技術議題、有能力做實務執行面的評估,也需要耐心做各種服務拆解梳理和引入正確的 Pattern 及技術,甚至很多時候要有走一步是一步且可以快速反應...

GitLab DevOps:Kubernetes 整合的 Applications 裝不起來怎麼辦?

圖片
我們的平民化旅遊黑卡秘書服務 LiMaGo,系統已經開發了兩年,有一定的複雜度,一直以來在維護上其實都有一定困難。此外,不知道是好消息還是壞消息,由於我們開始拓展日本等其他國家的業務,也加強了行銷通路,所以業務數字持續增加,導致各種功能和後勤機制也持續在擴張。還有,許多隱藏尚未公開的新計畫,整個系統複雜度不斷的再增加,慢慢的一切都開始混亂。 所以,為了重整系統,以及重建開發團隊的各種資源和管理流程,開始試圖引入了 Kubernetes 和 GitLab,並試著將 DevOps 流程給搭建起來。畢竟,自己的另外一家公司,是微服務和容器化的解決方案供應商,自己手上的東西要是沒有導入,也太講不過去。還好轉換的問題不太大,LiMaGo 的系統從第一天設計,就是採用微服務架構(Micro-service Architecture),甚至當時還設計了一套框架「Engined」,可以在 Node.js 上更容易開發微服務架構,只要專心在容器化和 DevOps 的工作上即可。 於是動手把 Kubernetes 架好,也安裝好 GitLab CE。下一步,就是把 Kubernetes 的設定加入 GitLab,讓 GitLab 可以把 GitLab Runner 和 DevOps 相關機制跟手上的 Kubernetes 整合起來。 糟糕!Applictions 裝不起來! 原本一切很順利,但在 GitLab 設定頁裡面的最後一個步驟「在 Kubernetes 上安裝 Applications」遭遇到莫名失敗,一開始是 Ingress 裝不起來,後來發現 Prometheus 也裝不起來,如下圖: 透過 kubectl 直接去 Kubernetes 裡面挖 logs ,得到了錯誤訊息: $ kubectl logs install-prometheus -n gitlab-managed-apps -f + helm init --upgrade Creating /root/.helm Creating /root/.helm/repository Creating /root/.helm/repository/cache Creating /root/.helm/repository/local Creating /root/.helm/plugi...

有趣的洗牌演算法

圖片
最近因為一些專案,所以需要實做一些撲克牌的洗牌機制。雖然這個動作看起來簡單,但其實對於開發者來說相當有趣,因為真的除了做這種牌類遊戲之外,平常很少用到這樣演算法,也由於有太多種做法,不免著迷於其中。 洗牌目的就是讓結果隨機、不能預期,只不過雖然很多遊戲同樣都是圍繞在亂數產生上面,但撲克牌遊戲(或麻將遊戲)最大的不同,就是同一排組每次發出來的牌,一但發過了就不會再出現一次。這一點,和每次都可以出一到六點數的骰子遊戲,就完全不一樣,不是隨機出一個亂數就可以搞定。 準備工作:先準備個牌組 開始前,先準備四種花色、A 到 K 的牌組,使我們可以以 0 至 51 的號碼去取得任意一張牌。 const suits = [ 'S', 'H', 'D', 'C' ]; const points = [ 'A', 2, 3, 4, 5, 6, 7, 8, 9, 10, 'J', 'Q', 'K' ]; const cards = []; for (let i = 0; i < 4; i++) { for (let j = 0; j < 13; j++) { cards.push(points[j] + suits[i]); } } console.log(cards[10]); // 10S(黑桃10) 方法一:硬幹 最直覺的方法,不外乎就是不斷產生 52 張牌的亂數(0 ~ 51),然後檢查這張牌發過沒,如果牌發過了就重新產生一個新的亂數,持續這個步驟。 let shuffledCards = []; while(shuffledCards.length != 52) { // 取得 0 ~ 51 的亂數 let idx = Math.floor(Math.random() * 51); let card = cards[idx]; // 檢查這張牌是否已經出現過 if (shuffledCards.indexOf(card) !== -1) continue; // 沒出現過則放入陣列 sh...

打造自己的 Node.js Transform Stream

圖片
熟悉並學習實作 Node.js Stream,在 Node.js 開發者生涯裡是一件很重要的事,尤其在資料處理的工作上更是需要運用 Stream。在這些應該用的情境下,若不懂得使用 Stream,我們所開發出來的程式其執行效能及穩定性會相當令人擔心。 而如果你從未自己實作過 Stream,從 Transform Stream 開始入手是一個好選擇,也是一個非常實用的開發技巧。 更多關於 Stream 的說明,可以參閱 Node.js 官網上的文件: https://nodejs.org/api/stream.html 什麼是 Transform Stream? 你可能知道 Node.js 裡有多種 Stream 的機制,但其實主要是 ReadableStream 和 WritableStream 兩種基本 Stream 的組成和變化。而對一般開發者來說,最常自己實作的是 Transform Stream,你可以想像這是一個產品生產線上的加工器,進入 Transform Stream 的資料會被加工後輸出。 而以一個 Stream 而言,Transform Stream 同時具有 ReadableStream(讀入)和 WritableStream(輸出)的特性,俗話說「左耳進右耳出」就是其最佳的寫照。 舉一個 Node.js 官方的例子,利用 Gzip 的 Transform Stream 將通過的資料流進行壓縮: const zlib = require('zlib'); const gzip = zlib.createGzip(); const fs = require('fs'); const inp = fs.createReadStream('input.txt'); const out = fs.createWriteStream('input.txt.gz'); inp.pipe(gzip).pipe(out); 實作第一個 Transform Stream 先不必暸解太多 Stream 的專有名詞和機制,若想要實作一個標準的「耳邊風」Stream,程式碼如下: const Transform = require('stream...

救火奇兵之 Android USB Host API 反應遲緩

圖片
話說,Android 在某個版本後,開始提供了 USB Host API,這代表開發者可以不必再用 NDK 和硬梆梆的 C 語言去開發 USB 裝置的驅動程式,而可以完全用 Java 來開發。但是,現實往往沒有這麼美好。 日前,就協助了一個案子,解決了一個 USB 裝置驅動程式的問題,起因就是客戶用了 Android USB Host API 去控制 USB 裝置,但發現 USB 裝置的回應一直不如預期,有時像是掉資料,有時像是沒反應。而同樣的控制邏輯,用純 C 開發的驅動程式配上 libusb 就完全正常,所以我們相信肯定不是控制邏輯上的問題。 剛開始,大家都懷疑是 Java 本身的問題,懷疑是不是 JVM 執行驅動程式太慢,而造成接收 USB 裝置的資料時來不及。但我一直保持著懷疑,因為 USB 裝置回傳的資料並不多,如果 JVM 本身的效能連處理這幾 KB 的資料量都如此差,就實在是太可笑了,我無論如何不相信。 還好最後還是解決了,雖然過程曲折。 USB Request Block 的 16KB 限制 事實上,每次最多傳送 16KB 資料,是一個 bulk transfer 的 URB 限制,使用 Android USB Host API 就會直接遭遇到這個問題,所以不管用什麼方法,怎麼收資料,只要資料太大,你最多一次就只能收到 16KB。 多次收資料所發現的延遲問題 當然,既然一次最多只能收 16KB,我們可以分多次向 USB 裝置要求收資料,但就會發現會莫名掉資料。從 USB 的分析器上來看,該有的命令都有,但就是有掉,後續的資料不管怎麼取都是 0。 後續資料為 0,在這個案子的 USB 裝置設計上是可以理解的狀況,因為該 USB 裝置只會保留資料一小段時間,然後就會清空,所以若之後跟它要任何資訊,他都會回傳空的東西回來。這很明顯,就是我們要資料的過程時間,已經超過了該 USB 裝置正常的情況。 而從收到的資料來看,有收到的資料,經驗證過後發現是斷斷續續的,中間有漏資料。經過測試,發現是每個命令之間的間距時間太長,因為該 USB 裝置會不斷復寫一段緩衝區,如果我們太慢去要資料,那段緩衝區就會被新的資料蓋掉,理所當然的,我們就會漏掉一些資料。 經過各種測試紀錄,很明顯的,Android USB Host API 並沒...

JavaScript async/await 的奇淫技巧

圖片
JavaScript async/await 的奇淫技巧 話說,最新的 ECMAScript 已經引入了 async/await 語法,讓開發者可以更容易控制非同步的程式邏輯,換言之,我們可以減少許多 callback 的使用,讓 JavaScript 這種單線程、事件驅動的程式語言更易讀、好寫。 關於 async/await 的基礎使用,有興趣的人可以參考舊文「 JavaScript 好用的 async 異步函數! 」,而本文將探討更多實際使用上的小技巧。 另外,瀏覽器不一定有支援 async/await,你可以在新版的 Node.js 上面測試本文的內容。 呼叫 async 函數與一般的函數沒有差別 想像一下,async 函數就是一個在執行後會回傳 Promise 物件的「普通函數」,和一般常見的函數的使用差異,僅僅只是 async 函數在執行後「不是回傳函數執行結果」。這代表我們可以把 async 函數當作一般函數來呼叫使用,用法一模一樣。 async/await 與 Promise 是可以共通的 非常有趣,async 函數與 Promise 其實能夠共通,這代表我們可以玩一些特別的組合技。所以,若要把 async/await 玩得通透,建議你盡量熟悉 Promise 的各種用法。 實現 delay 函數 過去因為單線程和事件驅動的關係,JavaScript 不可能實現一個沒有嚴重副作用的 delay 函數,所以取而代之的是使用 setTimeout() 加上 callback 來實現「一定時間後執行什麼工作」的需要。 不過來到 async/await 的世界後,我們可以一行行描述程式邏輯,無論是不是同步(Synchronous)的程式碼,所以我們可以用 Promise 來包裝 setTimeout() ,以實現一個在 async 函數裡可以跑的 delay 函數: // 實現一個等待函數 const delay = (interval) => { return new Promise((resolve) => { setTimeout(resolve, interval); }); }; const main = async () => { ...

自幹 JavaScript 的 Tail Call Optimization

圖片
ECMAScript 6 開始,規範中出現了一項被稱為「尾呼叫優化(Tail Call Optimization, TCO)」的優化技術,這讓開發者可以在函數的執行過程中,減少 Stack Frame 的數量,進而提升效能。TCO 尤其是在遞迴這種不停呼叫自己或新函數的工作上,能得到最大的優化效益,能提升遞迴的執行效能如同迴圈一樣。 只不過很可惜的是,截至本文發稿前,大多數瀏覽器及 JavaScript 引擎尚未支援這項技術。但我們還是可以自幹並模擬一個 TCO 的行為,雖然比起語言本身、編譯器(Compiler)及虛擬機(VM)層面的實現,效果差了些,但仍然可以減少 Stack Frame 的數量避免達到 Stack Frame 的數量上限。 什麼時候會啟用尾呼叫優化機制? 如果 JavaScript 引擎有支援,通常一個函數執行到最後一行 return 時,是回傳另一個函數的執行結果,就會啟用 TCO 機制,如: const f = () => { return 999; }; const g = () => { // 執行並直接回傳 f 函數的執行結果:會啟用尾呼叫優化機制 return f(); }; g(); 但要注意的是,回傳的「必定為函數的直接回傳值」,所以下面這些寫法不會啟用 TCO 機制: // 不會啟用 TCO 機制的設計 const g = () => { return f() + 1; }; // 不會啟用 TCO 機制的設計 const g = () => { let ret = f(); return ret; }; 創造一個跑不完的函數 首先我們先創造一個肯定跑不完的遞迴,然後改善它: const func = (x) => { // 讓他跑 10000000 次 if (x === 10000000) return x; return func(x + 1); }; let ret = func(0); 理論上,如果你直接執行上述程式碼,會得到 stack size 超過上限的錯誤訊息: RangeError: Maximum call stack size exce...

實現 JavaScript 的 Memoization

圖片
函數式程式設計(Functional Programming)是近年來越來越被軟體開發者常提及的話題,許多人討論它時,不外乎說其就是在程式設計中引入了數學方法,彷彿有神奇又高深的理論加持一般。事實上,對於一般開發者而言,函數式程式設計比較通俗且直接的好處,就是讓開發者可以在「函數」的層面和維度,進行邏輯或是效能上的優化。所以說,比起命令化的執行程式、管理物件,怎麼去設計和管理函數這件事,就是函數式程式設計所關心的重點。 JavaScript 這語言在設計上,天生就支援 first-class function,這代表函數在 JavaScript 是一種資料型態,可以被當成普通物件傳遞、處理,這讓開發者在使用 JavaScript 時可以時不時引入 Functional Programming 的技巧和概念。 本文將介紹 Functional Programming 中大量被使用的 Memoization 機制,然後我們如何在 JavaScript 中引入並實地使用它,無論你會不會 Functional Programming,這都是一個可以常用於日常開發中的優化技巧。 Memoization 是什麼? 用一般程式開發的說法就是快取(Cache)機制,只不過 Memoization 是針對「函數」進行快取。快取的好處在於我們只要執行過一次工作後,之後在執行相同工作前,就能提前知道執行結果為何,所以我們可以不用「真正的」去執行工作,而直接取用執行結果就好,可大量提升程式執行的效能。 同樣快取概念套用在函數上,若我們給予特定「輸入(Input)」到一個函數中,而該函數會回傳一個特定的「輸出(Output)」,理論上函數執行一次後,下次再使用這個函數時,只要「輸入」和過去一樣,我們就能提前知道結果。 而這樣對函數進行的快取機制,就是所謂的 Memoization。 只能使用在純函數 你可能會聽一些人說,只有「純函數」才能引入快取機制,然後開始討論數學上所謂函數的定義,然後你就聽到昏了,後面在講什麼你就都聽不進去了。 但如果撇除函數的數學定義,若白話來說,能被快取的東西,就是能被預測的東西,這代表函數的執行結果也要能被預測,也就是一樣的輸入值,就會有一樣的輸出結果。 所以,如果一個函數每次執行,代入的輸入值一樣,但回傳結果卻是可能不一樣,這...

Koa 2 起手式!

圖片
在 Node.js 的世界裡,說到今天最潮的 Web Framework,肯定就是 Koa!其用了最新的 JavaScript 語法和特性,來改善 Web Framework 的設計。只不過,Koa 雖然相對於其他舊的 Web Framework 來說有相當多的進步,但很多人卻相當討厭 Koa 的 Generator 設計,尤其是那些「*」符號,那不知所謂的 yield 也讓很多人不舒服。所以至今仍然有許多人在使用 express 來當作自己的 Web Framework,寧可繼續使用那老派的 callback 設計,而不肯嘗試 Koa。 隨著 ECMAScript 標準的進步,Koa 才剛被開發出來沒多久,原本的開發團隊就立即著手打造 Koa 2 ,開始更進一步採用更新的 JavaScript 特性,以 async/await 語法重新打造了全新且更簡潔的框架。可惜的是,由於 async/await 語法一直遲遲沒有被 JavaScript 引擎原生支援,因此總需要靠 babel 編譯打包程式碼後,才能正常跑在 Node.js 之上。這讓 Koa 2 一直無限期處於非穩定版,讓原開發者從開發的一開始,就打算等到 V8 和 Node.js 開始原生支援 async/await 後,才會被以穩定版(stable)的姿態釋出。 所以,即使 Koa 2 到了今天已經相當穩定,也開始有不少人使用在自己的線上服務,卻一直無限期處於非穩定版的狀態。 另外,由於 Koa 2 大量使用 Async/Await,如果你還對 Async/Await 的使用還不熟悉,建議在閱讀本文之前,先閱讀舊文「 JavaScript 好用的 async 異步函數! 」來學習如何使用。 學習 Koa 的好時機來囉 總算,日前 Node.js v7.6.0 釋出後已經正式宣布原生支援了 async/await 語法,而且不需要額外的參數選項。伴隨著這個消息,Koa 2.0 也隨即正式釋出了! Node.js 內建支援 ES7 的 async/await 真的是非常棒的消息!過去我們使用 async/await,都還需要 babel 的協助才能正常跑在舊版的 Node.js,不但開發上相當麻煩,非原生的各種 ES7 特性也浪費不少額外的記憶體和效能,這樣的問題在斤斤計較效...

Node.js 也可以使用 Protocol Buffers!

圖片
「 Protocol Buffers (protobuf) 」是一套 Google 所提出的結構化資料的包裝技術,讓資料便於網路傳輸或交換,如同常見的 JSON 和 XML 等技術一般。但相對於其他常見技術,protobuf 設計上更易於用來包裝二進位資料,應用在串流(Streaming)技術上,在資料包裝上也更為節省空間,在包裝或解析上也更有效率。 註一:若採用 JSON,由於原本的設計上並無法處理二進位資料,所以如果要包裝二進位資料,傳統做法會將資料轉換成 base64 的格式,再以字串(String)的格式儲存。因為這等於二次包裝資料,導致處理上非常沒有效率。 註二:與 Google Protocol Buffers 類似的技術還有 MessagePack 及 Facebook 採用的 Apache Thrift,有興趣的人可以自行參考比較。 跨語言的優點 另外,Protocol Buffers 最大的優點,就是擁有跨程式語言的設計,提供了一個標準通用的 .proto 定義方法,讓我們定義資料結構和格式。只需要載入這些我們事先準備好的資料定義,就可以輕易生成給不同語言(如:C++、C#、Go、Java、Objective-C 或 Python)用的資料解析器、包裝方法,讓我們可以在不同的語言之間,解析或包裝相同的結構資料。 Protocol Buffers 的使用場景? 若在純粹的 Web 應用下,大多數情況,我們不需要處理二進位資料,或是需要非常精準的資料格式,也不會進行單筆高流量的資料交換,所以使用 JSON 或 XML 已經足以。但若你的應用有串流、二進位資料的需求,Protocol Buffers 就是你可以考慮的選擇。 像是筆者在一些公司專案中,會運用 Message Queuing 進行各種訊息資料傳遞,以達成各種資料處理需求。但由於訊息資料內可能有大大小小等各種資料形式和資料型態需求,導致 JSON 包裝已經完全不敷使用,甚至有效能上的疑慮,這時就會採用 Prorocol Buffers 來打包資料。 安裝 ProtoBuf.js Google 官方其實並沒有實作 JavaScript 版本的 Protocol Buffers 支援,但還好廣大的社群已經有高手開發出 JavaScript 的模組「...

上手使用 JavaScript 的 Map、Reduce 吧!

圖片
雖然有些概念類似甚至可以相通,但這裡並不是指常聽到的「MapReduce」,本文目的不是要討論如何運用 MapReduce 這樣的架構去處理大資料庫。這裡真正要討論的是,如何使用 JavaScript 裡陣列(Array)中的 .map() 和 .reduce() 方法,並把一些常見的使用方法和情境描述出來大家進行參考。 很多人對這兩個方法不習慣,原因不外乎是這兩種方法本來就不是一個非常直覺的東西,在大多數 JavaScript 語言的開發情境中,其實也沒有非得使用的理由。但不得不說,習慣了這兩個對陣列操作的方法,程式碼會變得簡潔,也更容易能處理一整批的資料。有時也能順便學習到一些「Functional Programming」會用到的概念,無論是在改善程式品質,還是投資自己的角度上,都有相當好處。 從最簡單的遍歷陣列開始 面對一個陣列裡的一堆資料,我們一定是從遍歷開始,一一處理裡面的每一筆資料。你也許已經非常熟悉如何遍歷陣列,最常見的不外乎就是兩種做法。 使用 for-loop: var myArr = [ 1, 2, 3 ]; for (var index in myArr) { console.log(myArr[index]); } 使用陣列內建的 forEach 方法: var myArr = [ 1, 2, 3 ]; myArr.forEach(function(element) { console.log(element); }); 使用 .map() 對每個陣列元素加工 有些時候,我們想對每個陣列元素(Element)進行加工處理,於是最土法煉鋼的方法大概就是這樣: 幫每個元素加一: var myArr = [ 1, 2, 3 ]; for (var index in myArr) { myArr[index] = myArr[index] + 1; } // [ 2, 3, 4 ] console.log(myArr); 這時你可以使用 .map() 方法來達成同樣目的: var myArr = [ 1, 2, 3 ]; var newArr = myArr.map(function(element) { return element + 1; }); // [...

Node.js 小密技:以 Readline 核心模組一行行讀取檔案內容

圖片
最近參與了一些關於資料處理的專案,處理了很多各式各樣的原始資料(Raw Data)或各種不同格式的資料,於是使用到了 Node.js 上的一些小技巧。像是一行行讀取檔案內容這件事,就隱藏了一些技巧。 對很多人來說,處理的檔案內容都不大,如果用 Node.js 來一行行讀取檔案內容,不外乎就是將整個檔案讀出後再進行切割,做法大致上如下: var fs = require('fs'); fs.readFile('example.txt', function(err, data) { // 以換行字元作為切割點,將內容切成一個大陣列 var lines = data.split('\n'); lines.forEach(function(line) { // 一行行處理 }); }); 但有些時候,由於檔案並不小,若又牽涉到運算,不可能整個檔案都讀出到記憶體上才進行切割,這時就得用到 Stream(資料流)機制,將檔案一段段讀出來進行處理。然後,為了進行一行行的切割,我們會自己做這樣的機制,先將一段段讀取出來的檔案內容放到緩衝區(Buffer),然後找到換行字元進行切斷取出,然後再繼續讀取檔案,重複這樣的過程直到檔案結尾。 的確,實做這樣的機制有點麻煩,所以其實能利用 Node.js 現成內建的核心模組 Readline 來做到切割資料流中一行字串的工作。因為常見的 Readline 用法都是拿來做終端機字元模式下的命令列操作,所以許多人沒有想到可以這樣使用 Readline。作法其實很簡單,就把 Readline 的 input 從標準輸入(Standard Input)換成我們的檔案讀取資料流就可以。 完整做法如下: var fs = require('fs'); var readline = require('readline'); // 建立檔案讀取資料流 var inputStream = fs.createReadStream('example.txt'); // 將讀取資料流導入 Readline 進行處理 var lineReader = readline.createInterfac...

產品開發玩技術很過癮!實作 QML 動畫背景!

圖片
由於最近在開發自己的產品,又開始重操舊業,開發起 Linux 系統的相關應用和嵌入式技術。為了這個產品,精心開發了一個使用者介面,除了動手把驅動程式搞定、圖形化介面搞定,也調教效能、改善系統架構。 開發自己的產品很過癮,愛怎麼搞就怎麼搞!於是,看到死板的背景覺得很不舒服,就在思考是否可以跑個動畫背景呢? 因為使用的是 QML 技術來開發 UI,最直接的想法,就是用 QtMultimedia 的 MediaPlayer 無限循環播放一個影片,當作動畫背景: MediaPlayer { id: bg; source: 'bg.mov'; loops: MediaPlayer.Infinite; autoPlay: true; } VideoOutput { anchors.fill: parent; source: bg; } 當然,我們選擇的背景影片,是一個開頭跟結尾一樣的影片,如果正確循環播放,會無縫接軌的變成一個順暢的動畫背景。 然而,結果不如預期,碰到了一個問題,那就是每當背景影片播放到最後時,會畫面變成全黑,然後才再一次重新開始播放,沒辦法「無縫接軌」。仔細暸解以後,發現 MediaPlayer 元件是 QMediaPlayer 的 QML Type 實作,所有秘密都藏在 QMediaPlayer 之中。因為 QMediaPlayer 預設所有的通知事件,都是固定以 1000ms(1秒)的頻率來觸發,這代表,當 QML 元件發現影片播完時,通常已經是播完以後的事了,所以畫面一定會因為影片結束而變黑,然後 QML 元件才發現影片結束,重新進行播放。 知道緣由後,我們可以從事件更新頻率下手,讓 QML 元件發現影片播完的時間更接近實際影片結束的時間,但這必須動用到 C/C++ 的實作,因為 QMediaPlayer 的事件更新頻率無法以純 QML 的方法修改。 C/C++ 完整應用程式的實作如下,我們把更新頻率調高為每 100ms 一次: #include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQuickWindow> #include ...

JavaScript 好用的 async 異步函數!

圖片
先聲明,async 異步函數是 ECMAScript 第七版(ES7)才被支援的語法和特性,目前 ES7 還沒有被大多數的 JavaScript Engine 所實作,如果你要使用,需要用到 babel 這類工具,先把此程式編譯轉換,讓其可在舊版本 JavaScript Engine 上執行。 如果你覺得以 co 模組來操作 Generator 很好用,你可以想像 async 異步函數就是原生的 co,幾乎是同樣的使用方式,同樣的使用概念,只不過不再需要使用 generator 和 yield 這類語法。如果你是個過不了在函數上有個醜陋「*」符號這一關的人,async 異步函數的使用方式應該會讓你感覺到舒服許多。 什麼是 async 異步函數(async functions)? 異步函數使用方式其實和一般的函數一樣,只不過在這函數之內的程式,可以用 await 的語法去執行並等待異步工作(如:Promise)而不需要使用到骯髒的 callback function。宣告並使用一個 async 異步函數,就是在定義函數時加上「async」,然後直接執行這個函數即可,簡單的範例如下: async function myAsyncFunc() { console.log('Hello async functions!'); } myAsyncFunc(); 搭配 Promise 的使用 Promise 通常被大量用來管理非同步的工作,並讓開發者容易管理錯誤拋出等機制,一個典型的 Promise 使用如下: var task = new Promise(function(resolve, reject) { // 執行一個非同步的工作,完成後呼叫帶入的 callback doAsyncTask(function(err) { // 有問題呼叫 reject,並帶入錯誤值 if (err) return reject(err); // 成功呼叫 resolve 並帶入回傳值 resolve('VALUE'); }); }); // 使用 then 去執行並等待工作完成,成功會呼叫 callba...

下一代的框架:Koa 1.0 起手式

圖片
身為 Node.js 使用者的你,還在使用 Express 嗎?快來使用下一代的 Web Framework 吧!Koa 是由 Express 的開發者們出來所開發的新網站框架,嘗試採用了最新的 ECMAScript 6 語法,讓開發者可以用更簡約的方式,開發網站應用程式,讓程式碼更好維護之外,也能受益於最新的語言特性。 穩定版與不穩定版 現在的 Koa 分為 1.0 和 2.0 兩個版本,1.0 使用 ES6 的 Generator 特性,也是目前的 stable 版本,而 2.0 採用 ES7+ 的 async/await,據 Koa 官方說法,2.0 穩定度可以用於實際產品,只是在 ECMAScript 7 規格正式敲定,且 JavaScript V8 Engine 推出原生的實作之前,將無限期處於 unstable 的狀態。也就是說,若你想要在你自己的專案上使用 2.0,你必須使用 babel 這一類的編譯器,因為裡面用到了 ES7 的語法。 本篇文章的重點將放在 Koa 1.0 之上,畢竟在 ECMAScript 7 還處於草案階段的現在,很難說未來會不會有什麼改變。 安裝 Koa 由於 Koa 需要用到 ECMAScript 6 的語言特性,請先檢查你的 Node.js 版本,最少為 0.12 以上,如果你已經使用了 Node.js 4.0 或更高版本,請不用擔心這個問題。 然後透過 NPM 即可安裝模組: npm install koa 開發第一個應用程式 開發 Koa 應用程式非常容易,下面是程式碼範例: var koa = require('koa'); var app = koa(); app.use(function *() { this.body = 'Hello World'; }); app.listen(3001); 跑起來後,用瀏覽器連入 3001 埠即可看到「Hello World」的字樣,因為 this.body 的內容,將會被輸出到前端瀏覽器上。 使用 Generator 打造的 Middleware koa.use() 將用來載入 Middleware,所有連線工作都會經過 Middleware 處理。所以前一個例子裡,我們...

MakerBoard: 自幹 MT7688 模擬器!簡報釋出!

圖片
使用 MTK LinkIt Smart 7688 這類開發板時,總是很痛苦,由於儲存空間不大,記憶體也不大,常常在開發的過程中飽受折磨。於是我們開始思考如何可以在自己的電腦上,模擬一個 MT7688 的環境,在有充沛資源的機器上進行開發。就這樣,前陣子開發了一個小小的 Open Source 工具專案「MakerBoard」,並在 MakerCup 的共筆網站發表「 沒有板子也可以玩的 7688 模擬器! 」。 雖然這樣一個小小的模擬器運用了 VM 和 Container 相關技術。但其實主要概念並不難,這次 5/10 在台大的開放原始碼課程中,就簡單從 MakerBoard 這專案出發,然後說明了一下怎麼樣自己打造一個簡單的 Container,並利用 QEMU 來進行 Binary Translation 的工作。 簡報釋出,請自行服用:

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

圖片
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 人的大型創作活動,讓不敢踏出來的年輕學子、上班感到無聊的人、及很少離開自己專業領域的朋友,走出來到活動上以「能力會友」。這一年的過程,讓大家的成果,從簡陋成長到真正的創意或產品,從簡單的技術到複雜的應用,從小設計到跨領域的整合。 ...

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

圖片
話說,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://...

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

圖片
搭配使用 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

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

圖片
要講到 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.*') 。 這樣設計有什麼好處呢?因為所有的訊息和命令傳遞,都有統一的事件機制和命名規則,理論上來說,事件...