Saturday, December 29, 2018

The Unintentional Side Effect of a Bad Concurrency Model

這篇 The Unintentional Side Effect of a Bad Concurrency Model 出自 Joe Armstrong 的 blog 。文章裡提到的 Erlang 世界觀,跟 Robert C. Martin 主張的 Clean Architecture 相左。 Robert C. Martin 認為:「 micro-services 只是一種 deploy 的選項 ,並不是 Architecture 。」然而在 Erlang 的世界觀卻不這麼認為,在 Erlang programmer 的世界觀認為:
(1) micro-services 的設計,才應該是 default 的選項。我們應該要透過不停地在系統中加入新的小型獨立的 communicating objects 來讓系統成長 (micro-services),而不是把「不需要溝通」的程式愈寫愈大 (monolithic)。
(2) 模組與模組之間,應該要透過 communication protocol/message 來溝通,而不是依賴於 API 。

而作者的結論是:『為什麼世界上大部分的程式都不是用 erlang 的世界觀在開發,主要就是因為大部分的其它語言都沒有一個好的 concurrency model ,於是就造成了一種未意料到的副作用。』

Sequential languages are designed to write sequential programs, and the only way for a sequential program to grow in functionality is for it to get larger. It's technically difficult to split it into cooperating processes so this is not usually done. The concurrency in the application cannot be used to structure the application.

We should grow things by adding more small communicating objects, rather than making larger and larger non-communicating objects.

Concentrating on the communication provides a higher level of abstraction than concentrating on the function APIs used within the system. Black-box equivalence says that two systems are equivalent if they cannot be distinguished by observing their communication patterns. Two black-boxes are equivalent if they have identical input/output behavior.

Friday, December 28, 2018

How to build stable systems

 How to build stable systems 算是一篇集大成的文章,內容同時涵蓋了軟體開發的各種層面,並且提出作者認為合理、有效的作法。

在「前置準備」方面,他主張,『如果要試用新的技術,只能做單一賭博』不要一口氣嘗試太多技術,過度增加專案的危險性。
A project usually have a single gamble only. Doing something you’ve never done before or has high risk/reward is a gamble. Picking a new programming language is a gamble. Using a new framework is a gamble. Using some new way to deploy the application is a gamble. Control for risk by knowing where you have gambled and what is the stable part of the software. Be prepared to re-roll (mulligan) should the gamble come out unfavorably.

在「系統規畫」方面:
由於作者來自 erlang 的背景,他的思維是以 micro-services 做為預設值,所以他主張,模組與模組之間要透過 protocol 來溝通。( 注意:傳統的系統規畫通常是建議,系統先做成 monolithic,模組與模組之間先透過 API 來溝通,之後再逐步視需要,將模組變成獨立運作的 service。)
Your system is a flat set of modules with loose coupling. Each module have one responsibility and manages that for the rest of the software. Modules communicate loosely via a protocol, which means any party in a communication can be changed, as long as they still speak the protocol in the same way. Design protocols for future extension. Design each module for independence. Design each module so it could be ripped out and placed in another system and still work.

他主張 end-to-end principle 。只有端點需要有複雜的邏輯。
In a communication chain, the end points have intelligence and the intermediaries just pass data on. That is, exploit parametricity on mulitple levels: build systems in which any opaque blob of data is accepted and passed on. Avoid intermediaries parsing and interpreting on data. Code and data changes over time, so by being parametric over the data simplifies change.

在「設置檔」方面,他主張「除非系統真的超大,不然,並不需要全然動態的『設置檔』」所以不需要太早使用 etcd/Consul 之類的,把設置檔放在 S3 即可。
Avoid the temptation of too early etcd/Consul/chubby setups. Unless you are large, you don’t need fully dynamic configuration systems. A file on S3 downloaded at boot will suffice in many cases.

Tuesday, December 25, 2018

Erlang 的啟發

(1) Remote procedure call 不是一個好的 idea
What seems like a good, simple idea on the surface -- hiding networks and messages behind a more familiar application-development idiom -- often causes far more harm than good.

Request-response is a network-level message exchange pattern, whereas RPC is an application-level abstraction intended.

Equating RPC with synchronous messaging means the later wrongly suffers the same criticisms as RPC.

RPC 主要有兩個問題:
(1) 無法提供合理的抽象層來處理 network failure
(2) 讓工程師沒有注意到透過網路呼叫程序的代價,容易設計出 response time 過長的系統。

Erlang 沒有提供「遠端程序呼叫」。Erlang 只有提供 message passing 和 failure handling 的 primitives ,即 send/receive 和 link 。

(2) Scalable, fault-tolerant, hot-upgradable 的共同點
一個系統同時若要同時滿足上述三件事,其實需要實作下列的 primitives
(a) detect failure
(b) move state from one node to another

When designing a system for fail-over, scalability, dynamic code upgrade we have to think about the following:

What information do I need to recover from a failure?
How can we replicate the information we need to recover from a failure?
How can we mask failures/code_upgrades/scaling operations from the clients

(3) Cooperative multitasking is fragile
協作式多工是「脆弱」的設計。( 但是,cooperative 的效能通常會比 preemptive multitasking 好一些。)

The weakness of the cooperative model is its fragility in server settings. If one of the tasks in the task queue monopolizes the CPU, hangs or blocks, then the impact is worse throughput, higher latency, or a deadlocked server. The fragility has to be avoided in large-scale systems, so the Erlang runtime is built preemptively. The normal code is “instrumented” such that any call which may block automatically puts that process to sleep, and switches in the next one on the CPU.

(4) Stacking Theory for Systems Design
對 service 的可用性,定義出數個不同的 mode of operation ,比方說:
VM 有 run 起來,這就是 level 0 。
database connection 有通,這就是 level 1 。

如果某些條件達成,系統就會轉換到高一級的 operational level 。如果 error 發生,就自動轉換低一級的 operational level 。

By "stacking" service, we can go back to level 0 and start best-effort transitions to level 1 again. This structure is a ratchet-mechanism in Erlang systems: once at a higher level, we continue operating there. Errors lead to the fault-tolerance handling acting upon the system. It moves our operating level down — by resetting and restarting the affected processes — and continues operation at the lower level.

用了這樣子多個 operational mode 的設計,系統較容易得到「容錯」的特性。

A system assuming the presence of other systems has an unwritten dependency chain. You have to boot your production systems in a certain order or things will not work. This is often bounds for trouble.

(5) Processes are the units of error encapsulation --- errors occurring in a process will not affect other processes in the system. We call this property strong isolation.

關於如何將軟體模塊化總是有許多不同的爭議。編譯器設計者通常把硬體想象成完美的,並且主張由通過靜態編譯時型別檢查來提供良好的隔離性。與編譯器設計者們相反,作業系統設計者們則主張運行期檢查,並主張將進程做為保護單位與故障單位

Friday, December 21, 2018

why does 12 factor recommend not to daemonize processes?

最近在研究 Erlang/Elixir ,又發現了以前自己總是不小心忽略的東西 supervisor 。

然後就查了一下 12 factor app。為什麼會想查 12 factor app ?印象中,我好像不知道在哪裡看過一篇文章,提到,許多 Erlang 的設計,後來都被 12 factor app 所採用,意思大概是說,Erlang 真的就是分散式系統設計的先趨。

結果,12 factor app 裡頭也有一條 rule  有提到 process supervisor 的相關概念。但是,我覺得說明最清楚的,反而是 stackoverflow 上,對下列問題的解答:
「為什麼 12 factor app 不推荐我們 daemonize processes?」

答案是:
重點不在於不要使用 daemonize processes 這個解法。重點在於: process management framework 其實 *nix 早就有了,沒事不要自己做。要善用現有的工具,這個才是 DevOps 的精神。



Friday, November 9, 2018

Web scraping notes

最近公司的工作裡有一部分是做 web scraping 。要做的事,大致可以分成 2 個步驟:
a.  login/authentication
b.  extract data from web page

其中,登入的部分,客戶的網站大概可以分成兩大類別:
(1) token based authentication
最新一代的標準是 JSON Web token 。
(2) session based authentication
這種通常都需要去處理 cookie 才有辦法登入

客戶的網站有的是 backend rendering ,有的是 frontend rendering 。如果是前者,通常 backend API 傳回的資料是 JSON ,算是最容易處理。如果是後者,backend API 傳回的資料則是 HTML/XML 的格式

要從 HTML/XML 中提出資料,通常可以考慮三種做法:
(1) Regular expression  => 直接硬做字串的比對
(2) XPath                       => 可以看懂 XML tree ,透過資料的結構關系去定位資料的位置。
(3) CSS selector            => 表達能力比 XPath 略弱一些,但是使用比較廣泛。


Wednesday, November 7, 2018

Effective REPL-driven Clojure

Effective REPL-driven Clojure 這篇文章裡,介紹了一系列 REPL-driven 的好用技巧:

1. Evaluate forms frequently
每次如果修改了某個 forms  ,尤其是它是 def 的 form ,就可以考慮把它重新求值一下。 求值的作用可以想象成是一種存檔。

2. Start stateful things in REPL before you do anythings else
使用 mount 或是 component

3. Use comment forms to document your experiments
在 REPL 要做的求值實驗,可以考慮用 (comment ... ) 記錄在程式裡,方便日後理解。

4. Use def for debugging
REPL 可以對定義好的函數輕易地求值。然而,函數內部的 form 要如何 debug 呢? 函數內部的 form 往往會依賴於函數的 arguments 。很簡單,就是用 def 把函數的 arguments 指定一個固定值,這樣子就可以 debug 了。記得 git commit 之前要把這種 debugging 的 def 移除。範例

5. Unmap namespace during experimentation
ns-unaliasns-unmap 可以視為 def 和 require 的反運算。

6. Re-open libraries for exploration
在 require 某個 library 之後,使用 in-ns 可以進入該 library 的 namespace 裡,並且在這邊重新定義變數,以達成除錯或是取得執行資訊的目的。(monkey-patch)

7. Pretty Print
使用 clojure.pprint 這個函式庫來做輸出

Friday, October 12, 2018

first class

現在一些比較新的 programming language 有時候都會在語言裡加入新的 first class element 。對我來講,這個詞彙 first class ,總是讓我覺得有「理解的困難」,到底什麼才叫 first class 呢?

有一回,我終於在 SICP Distilled 看到定義了。

In general, programming languages impose restrictions on the ways in which computational elements can be manipulated. Elements with the fewest restrictions are said to have first-class status, an idea originally due to Christopher Strachey
Some of the “rights and privileges” of first-class elements are that they may be:
  • Named by variables
  • Passed as arguments to functions
  • Returned as the results of functions
  • Included in data structures
Lisps, unlike other common programming languages, awards functions full first-class status. This poses challenges for efficient implementation, but the resulting gain in expressive power is enormous.


Software Engineering Task Orders


Stage 1
(1) end-to-end test
(3) CI/CD -> cloud formation
(4) linter

Stage 2
(1) unit test
(2) cloudwatch filtering/alarm

Stage 3
(1) integration test
(2) profiling
(3) automatic document generation


開發軟體總是需要做一些軟體工程的事情,不過,通常總是有一些壓力讓我沒有辦法事事做到完美。於是我自己弄了一個排序。如果有時間的話,先做哪些、後做哪些。 stage 1 是我覺得最重要、最該先做的。 stage 2 是我覺得可以拖一下的。stage 3 是行有餘力才做的。

為什麼 stage 2 可以拖一下呢?
(*) 以 unit test 來講,如果已經有 end to end test 並且加上可以用環境變數關閉的 debugging log ,某種程度來講,也可以把該測的程式碼測到乾淨。沒有 unit test 似乎也沒有這麼嚴重了,因為已經有了替代方案了。

(*) cloudwatch 的 filtering/alarm 的話,這個通常是設計用來抓出某些本來預期不會發生的錯誤、或是超大的流量發生的情況。但是,專案剛開始發展的時候,流量有時候也不大,所以可以拖一下。在 Erlang  的設計哲學裡,這部分設計應該算是所謂的「故障曝光性質」 (failure status property) 與「持久存儲性質」 (stable storage property)  ,算是直接要設計在 programming language layer 的東西,而不是要設計在 infrastructure layer 的東西。

為什麼 stage 3 可以拖更晚呢?
(*) 專案在「探索期」的時候,對資料庫的查詢,可能會快速地一直更換,太早加上 integration test ,有時候也太費力了,因為加上去了,總是要維護吧?
(*) 如果專案沒有效能瓶頸,profiling 沒有做也還好吧?
(*) 如果專案還沒有長太大,沒有自動產生的文件,似乎也不太嚴重。

Friday, October 5, 2018

cooperative multitasking

過去初學 event-driven 或是 cooperative multitasking 時,沒有特別想清楚 。等到後來,多寫了一些 Nodejs 的 async/await ,還有 Clojure 的 go-block 之後,才覺得這個 cooperative multitasking 真的大有文章在。

這邊有一個參考資料,比較了兩種 cooperative multitasking 不同的實作方式:

實作一: 讓語言本身的排程器可以被 I/O 觸發,在 I/O blocking 的同時,自動做出 context-swtich 。這是 golang 的實作法。
實作二:讓 I/O 呼叫變成 non-blocking 的。這是 Nodejs, Python, Clojure 的實作法。

原文如下:
For cooperative concurrency to work well with I/O, the language should either make the scheduler aware of the I/O calls (to be able to switch to another context while blocking) or the I/O should be non-blocking. The former requires runtime support in the language, like Go; the latter is what programming environments like Python (with asyncio) and Node.js (with its fully non-blocking standard library) do. The same applies to Clojure, where core.async is just a library without actual runtime support.

在 golang ,如果要應用語言本身提供的同步特性,常見的寫法是:
將有 I/O 呼叫的函數,放在一個被 go 關鍵字修飾的函數裡頭 (即放在一個獨立的 goroutine 裡),並且透過 channel ,將 I/O 呼叫的結果送出來。

Friday, September 7, 2018

MySQL 搬移大量資料 (import/export)

工作上會需要做 MySQL 搬移大量資料,因為 development 環境沒有足夠的測試資料,我必須從 production 的資料庫搬過去。

本來打算用 MySQL Workbench 的 export/import 功能。但是,由於需要搬的資料多達 3 MB 。匯出還可以使用 MySQL Workbench ,匯入的話,就非常非常慢,完全是慢到我受不了。所以我決定改用 LOAD DATA LOCAL INFILE

真的操作指令時,還是遇到了一些問題:
沒有仔細去計較 CSV 檔的 FIELDS TERMINATED BY CHARACTER 或是 FIELDS ENCLOSED BY CHARACTER 。資料就是無法匯入成功。

最後實驗成功的指令記錄如下:

Wednesday, September 5, 2018

The Legacy Code Change Algorithm

  • Identify change point
  • Identify test point
  • Break dependencies
  • Write test
  • Make change and refactor
Notice that the LAST thing you do is write new code. Most of your work with legacy code is understanding and carefully selecting where to make changes so as to minimize risk and minimize the amount of new test code you have to write to make your target change safely.

參考資料

Tuesday, September 4, 2018

Testability/Dependency Injection in Clojure

在 OOP 語言的時候,因為要處理「可測試性」的問題,常常需要去考慮 dependency injection。但是, Clojure 已經有了 with-redefs 這種可以將任意的 function 都變成 software seams 的 language syntax 了,還需要特別寫 DI 嗎?

上網查了一下,各家有各家的說法,我個人比較認同的說法是這樣子:
原則上不太需要用 DI 去處理 external dependency 的問題,用 with-redefs 即可以。然而,如果某個外部資源它有 life cycle management 的問題,最好要用 component 或是 mount 來處理。

要達成 Testability 不一定要用 DI。有一篇文章 Testing the Clojure way 指出了這一點:
如果在 C# 之類的靜態語言,要做出類似 with-redefs 的語意的話,則需要用 preprocessing seams 的技巧來替換 dependency

再從另一個觀點來看,在 let over lambda 一書指出 let over lambda 其實就是 object 。而 lambda over let over lambda 可以看成是 anonymous class 。順著這個邏輯去想, dependency injection 也可以視為是 higher order function 的一種語意應用。

Thursday, August 30, 2018

使用 Node.js 的 Sequelize 如何看到自動產生的 SQL 語句

解法: 在初始化的時候,傳入一個函數給 options.logging

var sequelize = new Sequelize('database', 'username', 'password', {
    logging: console.log
    logging: function (str) {
        // do your own logging
    }
});

如果原本的 legacy code 的深處使用了 Sequelize.js ,但是,又沒有留下接口可以從高階模組去設置 options.logging 。由於 Node.js 是動態語言,在 testing/development  的時候,可以考慮直接去修改 node_modules/ 資料夾裡的內容來啟用 logging 選項。

Tuesday, August 28, 2018

準備 DB integration test

本來我的作法通常是這樣子:

  • CREATE TABLE new_table LIKE old_table;
  • INSERT new_table SELECT * FROM old_table;
先透過上述的指令,將資料表的子集合資料做成新的資料表,然後再透過 MySQL Workbench 將新的資料表匯出。最後再刪掉暫時存在的資料表。

==============
最近改用了新的方式
1. 對原始的資料表,透過 MySQL Workbench 只匯出 schema ,做成一個匯入的 SQL script
2. 透過 MySQL Workbench 下 query ,query result 只能做成 CSV 匯出。但是,可以透過其它的線上工具,將 CSV file 轉檔成為 SQL data file 

integration test / Export Database Data to SQL insert statement

寫 integration test 的時候,有時候會需要透過 SQL query 去抓取部分的 table ,然後再塞入 testing 資料庫裡頭。這件事,透過 MySQL Workbench 來做的話,它可以很快地 export result set as CSV file 。但是,變成了 CSV file 雖然可以透過 LOAD 指令來匯入,更一般的指令,應該還是 mysql insert 。

那要如何將 data CSV file 做成 mysql insert statement 呢?

(1) CodeBeautify 圖形化介面
(2) csvsql command line 指令

Friday, August 24, 2018

OO design for testability (Miško Hevery) --- 心得(續)

這兩天重看了一篇 OO design for testability (Miško Hevery) 的前 15 分鐘,又有新的領悟。

本來我覺得整個 talk 的重點是在於四個常見的錯誤不要犯,重新讀一次之後,我覺得重點是更抽象的設計原則:

設計 OO 的程式的時候,要把 class 分成兩大類:
(1) 一類是 Object Graph Contruction & Lookup ,比方 Factory ,它會有 new  operator。
(2) 一類是 Business Logic ,它會有 conditionals 和 loop 

依照這種方式設計,如果要測試 Business Logic 的時候,就可以透過 test unit ,重新 wire 一些 fake 的物件去測試 Business Logic 。

而如果要測試 Factory object ,也可以獨立於 business logic 來做測試。



可以測試的程式應該要有性質是:你可以單單只依賴整套軟體組裝起來的方式,就控制軟體的控制流 (You can control the path flow of your software purely by the way the application is wired together.)

於是又提到了一個專有名詞 Seam
A seam is a place where you can alter behavior in your program without editing it in that place.

跟 Seam 相關的另一個詞是 enabling point
Every seam has an enabling point, a place where your can make a decision to use one behavior or another.

Object Seam


附帶一提,我後來針對 seam 去查資料,發現 seam 出自一本書 WELC。在 Working Effectively with Legacy Code 裡,除了作者最建議的 Object seam 之外,作者還介紹了其它兩種 seam:
preprocessing seam 和 link seam 。

object seam 的 enabling point 是「我們生成待測物件的位置

link seam 在 Java 可以透過 classpath 環境變數做為 enabling point 。原文對 link seam 的描述:
The enabling point for a link seam is always outside the program text. Sometimes it is in a build or a deployment script. This makes the use of link seams somewhat hard to notice.

preprocessing seam 的 enabling point 則是 preprocessor define 語法

參考資料:
(1) Testing Effectively With Legacy Code
(2) How to use link seams in CommonJS
(3) Seams in Javascript







Friday, August 3, 2018

Conditionals v.s. Rules Engine

在 Rich Hickey 的 Simple Made Easy 這個演說中,他講了幾句有關「條件判斷」的話:

  • Conditionals are complex in interesting ways, and rules can be simpler. 
  • You can get rule systems in libraries, or you can use languages like Prolog.


首先,我本來考慮要直接研究 Prolog ,不過,Prolog 並沒有很容易學,畢竟是個不同 paradigm 的程式語言。但是查了一陣子 Prolog 的資料之後,也有想出一些有趣的東西:

比方說,Prolog 這種語言的核心是 unification,除了可以實現 rules engine 之外,還可以用於 data validation 和 data extraction, data transformation 。 想了一想之後,我想到了一件事:寫 validation 時,使用的 JSON Schema ,其實也可以視為是 rule 。

後來,我從 rules engine 的角度去查資料,這回查到的 library 就很有啟發。 json-rules-engine ,就可以讓 nodejs 可以使用類似 Prolog 的能力。

仔細想想,本來直接寫在 if 裡頭的條件判斷 (conditionals) ,被用 rules engine 這種「增加一層抽象層」的方式改寫,程式就會變得更有彈性 (flexibility) 。這又是再一次地應証了那句老話:

You can solve every problem with another level of indirection

Thursday, August 2, 2018

Clean Micro-service Architecture

Clean Micro-service Architecture 又是 Robert C. Martin 的文章。

大意如下:

Clean Architecture 與 microservices 是兩個正交的概念

  • Clean Architecture 是指軟體系統的設計要可以清楚地區分成 entity, use cases, controller & gateway & presenter 這三個圈圈。之後才可以不斷地配合需求而修改。
  • microservices 是一種 deployment option (布署選項) ,而且這個布署的選項有它的 trade-off 。此外,還有其它的布署選項。使用不同的布署選項,是為了達成不同的 scalability  。

其它相關的布署選項還有:

  • 在同一個 thread 裡的 jar ,彼此透過函數來溝通。 
  • 在同一個 process 裡的 thread ,彼此透過 mailbox 或是 queue 溝通
  • 在同一台 machine 裡的不同 process ,彼此透過 socket 溝通
  • 在 Internet 上不同的 services ,彼此透過 asynchronous API 溝通
引述文章裡的一小段話 --- The deployment model is a detail. Micro-services are deployment option, not an architecture. 作者主張:「開發軟體系統,要視實際的需求,再來決定要用哪一種合適的布署選項。」

注意: jar 也可以達成 independent deployability (可獨立布署性) 和 hot swapping (程式碼熱抽換。) 所以也是相關的 deployment option


Sunday, July 29, 2018

Thinking in Data (Stuart Sierra)

Thinking in Data

(*) Data Oriented Programming
 
總結: 
(a) 寫程式解真實世界的問題,要用 Data 來對真實世界建模。
(b) 如果要驗証程式的正確性,將程式內部的 Data 加以「視覺化」(visualize) ,常常會比 unit test 還管用。
(c) Make your data visible!


1 用 Data 來對真實的世界建模。
  => 容易除錯。
  => 比物件好太多了

2. 物件 (Object) 適合用來建模的東西,往往是完全在電腦內部的東西。
   stack, queue, GUI, widgit 
   註: Clojure 的 vector, list, set, hash-map ,就是使用 Object 來建模。Why? 因為是電腦內部的東西,一方面效能很重要、另一方面需要 Polymorphism

3. Examine Each Step
   對有使用 reduce 函數來除錯的技巧
   (a) 把 reduce 換成 reductions ,如此就可以看到運算過程的每一個 step
   (b) 使用 pprint

4. Everything is a Map
   一種抽象思考:
   Collections
   This ..  | maps from ... | to ...
   ---------------------------------
   Map     | Keys              | Values
   Set       | Values            | Values
   Vector  | Integers         | Values
   List      | first/rest         | Values/List
 
5. Add Keys with Impunity

(assoc stuart :business/role :developer)
 
用 map 來對真實世界的東西建構,隨時可以加入新的東西。
如果要避免 key 的 collision ,可以對 key 加上 namespace

6. 一個函數的 argument 超過三個的時候
   => 用 map 來把 arugment 包裝起來是更好的作法。

   (defn full-name [person]
     (let [{:keys [first-name last-name]} person]
       (str first-name " " last-name)))

7. defrecord, defprotocol 這些通常是用來處理需要高效率或是 Polymorphism 處理的情況、
   然而,十之八九,用 map 來建模就已經很夠用了。

8. 封裝(Encapulation)實作細節這個技巧,被應用的時候,我們會無法看到 data representation
   然而,將 representation 隱藏起來是有代價的。
   (a) 效能
   用同一個介面去輕易地使用不同的物件,會有不同的效能屬性,有時被誤用,很傷效能。
   (b) 實作的難度
   (c) 「網路傳輸/程式之間的傳輸」仍然依賴於 representation
   也因此,任何我們透過隱藏 data representation 得到的好處,往往會被扺消,一旦我們的程式會接觸到外部世界。
 
   What I suggest, instead of encapsulating your representation, let your representation be visible and think about them. Choose the representation that best fit a particular module, a program, or a component that your are dealing with. Then, you establish the boundary between the components, where representation can change. We change representation all the time. That is what computer can do more than anything else.

 
9. Data Literals feature
   (*) Isolate literal representation from what's in memory
   不同的記憶體格式,可以共用同一種「printed data representation」

   (java.util.Date.)
   ;;=> #inst "2012-03-03T22:50:21.656-00:00"
   (java.util.GregorianCalendar)
   ;;=> #inst "2012-03-03T22:50:21.656-00:00"

(*) 應用 data representation 的技巧來撰寫函數

1. Isolate Components
對一個 state 要經過多次的轉換,但是每一次的轉換,只修改它的一部分狀態,可以用下列的 pattern 來實作。

(defn complex-process [state]
  (-> state
    (update-in [:part-a] subprocess-a)
    (update-in [:part-b] subprocess-b)))


2. Isolate side effects
下列的程式很難測試
;; Bad
(defn complex-process [state]
  (let [result (computation state)]
    (if (condition? result)
      (launch-missile)
      (erase-hard-drive))))


相對比較容易測試的版本
;; Better
(defn complex-process [state]
  (assoc state :analysis (computation state)))

(defn decision [state]
  (assoc state :response
         (if (condition? (:analysis state))
            :launch-missile
            :erase-hard-drive)))

(defn defend-nation [state]
  (case (:response state)
    :launch-missile (launch-missile)
    :erase-hard-drive (erase-hard-drive)))


3. 由 data 思維,推導出 naming convention
A function which...       |  has a name that is...   |  return...
-----------------------------------------------------------------
Computes a value         |  noun: what it returns  |  value
-----------------------------------------------------------------
Create a new thing        |  noun: what it creates  |  value
-----------------------------------------------------------------
Gets input from ouside |  "get-noun"                  |   value
-----------------------------------------------------------------
Affects outside              |  verb: what it does       |  nil
-------------------------------------------------------------------

(*) Summary
(a) Make data visible
(b) Make functions composable
(c) Isolate computation decisions from side effects
(d) Establish boundaries
    - from computation world to side effect world
    - from one data representation to different data representation

Saturday, July 28, 2018

OO design for testability (Miško Hevery)

偶然看到的 youtube 影片--- OO design for testability。對我的幫助滿大的,看完之後,有點讓我豁然開朗,發現:「喔,原來物件導向的程式要這樣子設計!」「原來 dependency injection 的用法是這樣子用!」

影片一開頭不久,就指出了有四大常見的錯誤,一旦犯了,就可以寫出很難測試的程式:
1. Location of the new operator
2. Work in constructor
3. Global State
4. Law of Demeter violation

在明確的去說明四大常見錯誤之前,講者做了一段基本的測試概述:你寫了一個物件,然後你要測試它。測試它,於是就遇上了難題:因為它有許多的「依賴」,而這些「依賴」產生的方式有許多不同的方式:
(a) 依賴可能是 global 物件
(b) 依賴可能是在 class 內 new 出來的物件
(c) 依賴可能是注入的物件
只有 (c) ,不會導致嚴重的測試問題。

錯誤 1 & 錯誤 2 
不容易測試的範例:
class Car {
  Engine engine;
  Car(File file) {
    String model = readEngineModel(file);
    engine = new EngineFactory.create(model);
  }

  // ...
}

=============================================
容易測試的範例:
class Car {
  Engine engine;
  @Inject
  Car(Engine engine) {
    this.egine = engine;
  }
}

@Provides
Engine getEngine(
    EngineFactory engineFactory,
    @EngineModel String model) {
  return engineFactor.create(model);
}
=============================================

由上述的反思可以推論出:
(*) 在 constructor 內部,應該要儘量少做事、唯一應該寫在 constructor 裡的程式碼,就是把 dependencies 指派給 class 內部的 field。 (In constructor, you should do as little work in constructor as possible. You should do nothing else but assign your dependencies to your fields.)

(*) 檢驗物件導向程式有沒有設計好 testability 的方式:
Look at the constructor and make sure there is no work in there. Look at the constructor to make sure that all it's doing is assigning its dependencies to the fields. Check to make sure that the dependencies that your actually save into your fields are actually the dependencies that you truly need. This is where we are going to get into law of Demeter violation.

遵循上述的 Guideline 的話,程式應該長成什麼樣子呢?
(*) 好的測試程式碼 (test code) 應該充滿 new operator 和 null
(*) 好的線上程式碼 (production code) 則應該「沒有」new  和 null

================================================
錯誤 3 (Global State) 的例子

不清不楚的 API 介面。 Deceptive API

testCharge() {
  Database.connect(...);
  OfflineQueue.start(...);
  CreditCardProcessor.init(...);
  CreditCard cc;
  cc = new CreditCar("12..34", ccProc);
  cc.charge(100);
}

================================================
明確說明依賴關系的 API 介面。 Better API

testCharge() {
  db = new Database();
  queue = new OfflineQueue(db);
  ccProc = new CCProcessor(queue);
  CreditCard cc;
  cc = new CreditCar("12..34", ccProc);
  cc.charge(100);
}
================================================
錯誤 4 (Law of Demeter violation) 

不良的例子: 使用超過一個 dot 符號
class LoginPage {
  RPCClient client;
  HttpRequest request;

  LoginPage(RPCClient client,
      HttpServletRequest request) {
    this.client = client;
    this.request = request;
  }

  boolean login() {
    String cookie = request.getCookie();
    return client.getAuthenticator().authenticate(cookie);
  }
}

==========================================
改進的範例
class LoginPage {
  ...
 
  LoginPage(@Cookie String cookie,
            Authenticator authenticaor) {
    this.cookie = cookie;
    this.authenticaor = authenticator;
  }

  boolean login() {
    return authenticator.authenticate(cookie);
  }
}

Wednesday, July 25, 2018

Linux command line 技巧總整理

有一些 linux command line 的技巧,我到了年紀很大才學會,大概比較不流行吧?

(1) autojump
這個是用來取代 cd 。切換資料夾,只需要填入「關鍵字」即可。

(2) ack 或是 git grep
這個是用來取代 grep 。在 nodejs 的開發環境,如果用 grep 來做搜尋,沒有指定複雜的子選項, grep 總是把 node_modules 裡的一堆源碼也做比對。ack 的 default 選項就會忽略 node_modules 這個資料夾。相對省事不費心許多。

git grep 是對已經存進 git repository 的資料做 grep 。速度比 grep 快了十倍左右。而且可以查出所有在 git repo history 裡的資料。

(3) cheat
用來取代 man 。會直接給許多指令的最常見、最好用的用法。

Monday, July 16, 2018

python 依賴宣告、依賴隔離、程式碼排版

開發 python 的程式,要遵循好的開發紀律,避免日後的可移植性問題,最好還是在最初就做好「依賴宣告」和「依賴隔離」、「程式碼排版」

依賴隔離  (dependency isolation) 
cd my_project_folder             // 先進入到 git 管理的 python 開發資料夾
virtualenv venv                      // 建立一個類似 node_modules 的 venv 資料夾
                                               //  virtualenv 也可以在啟動時,用 -p 參數指定要用的 python 版本。
source venv/bin/activate        //  啟動 python 的虛擬環境
pip install autopep8                //  在虛擬環境中安裝套件
deactivate                               //  離開虛擬環境

依賴宣告 (dependency declaration)
啟動虛擬環境之後…
pip install -r requirements.txt          // 根據依賴宣告檔來安裝套件
pip freeze > requirements.txt          // 重新生成依賴宣告

程式碼排版 (linter)
autopep8 --in-place --aggressive

Nodejs 的 unit test 經驗

今年 6 月在荷蘭旅遊時,偶爾看了 clean coder blog 之後,覺得自己還是來認真寫一些測試。畢竟 Uncle Bob 的文字傳達了一種觀點:「寫程式的紀律才是一切的基石。」

在 intowow 開發主要是用 Nodejs 。本來公司就有在用的 library 是 mocha 和 chai 。 BDD 風格的測試,確實比較容易看懂。由於我在 cepave 的工作也有用過 BDD ,我就不假思索地接受了。

最近愈是寫測試,愈是覺得真的很需要用好用的測試 library :
(1) sinon
功能很強,主要的功能是 stub 和 spy 。 stub 可以重新定義函數的行為,讓函數傳回固定的輸出。 spy 可以對函數做出監控,透過 spy ,可以輕易地驗証某個函數是否被呼叫、被怎樣的引數呼叫。

(2) nock
這個很適合來測 web API 。

(3) rewire
這個可以測模組的「私有函數」。

Wednesday, July 11, 2018

使用 localstack 來開發 aws service 的整合測試

要使用這個測試,需要先啟動 localstack 的 docker
docker run -d -p 4567-4583:4567-4583 localstack/localstack

Sunday, July 8, 2018

queue, channel, asynchronous API

以前讀 Rich Hickey 的 Simple Made Easy 時,有一回他講了 If you're not using queues extensively, you should be. You should start right away, like right after this talk. (如果你沒有常常使用 queue 的話,你應該要多用一些。你應該立刻開始…聽完現在的 talk 就開始吧。)

紅色那段英文的上下文, Rich Hickey 的論點是: Queue 可以讓 when 和 where 這兩件事被分開來。這個說法太抽象了,我一直無法理解它。此外,看完那段話之後,也還是想不出來,我寫程式的時候,哪些地方有可能用 queue 去改善?

而公司的同事最近在設計架構時,用了 aws  的 SQS 服務。我隨口問了他,為什麼要用使用 queue ?他回答: producer 和 consumer 的速度根據估計會有很明顯的差距,所以使用 queue 來做解耦,就可以大幅改善效率。

同事的理由,讓我忍不住回頭去看了一下 Rich Hickey 曾經寫過的關於 channel 的設計理由:
There comes a time in all good programs when components or subsystems must stop communicating directly with one another. This is often achieved via the introduction of queues between the producers of data and the consumers/processors of that data. This architectural indirection ensures that important decisions can be made with some degree of independence, and leads to systems that are easier to understand, manage, monitor and change, and make better use of computational resources, etc.

嗯,這一句理由總算是把問題講得清楚了一些,至少也提到了計算資源的消耗問題

那還有什麼情況也是適合用 queue 來處理問題呢? 為什麼 Rich Hickey 說,要多用一些?那如果卯起來用 queue 的話,還可以用到什麼程度?

我在 Martin Trojer 的文章裡,終於也找到了部分的答案: 像 Nodejs ,只要一遇到 I/O 就是用 callback/promise 來處理,以避免 I/O wait 。實際上, callback/promise 也可以用 queue 去取代。(Martin Trojer 的原文內容要旨為:如果要使用 clojure 的 core.async library 的話,正確的使用方式,必須搭配 asynchronous I/O API )

另外,如果不是只考慮計算資源消耗,而是考慮系統的單純性 (simple system) 的話,也有人用 event driven programming 來描述這個概念:

He's really just giving a fuzzy description for the concept of job pools and event driven programming. So you pack a function call into a generic Job object, push that into a queue, and have one or more worker threads work on that queue. The Job then dispatches more Jobs into the queue upon completion.

Wednesday, June 27, 2018

vagrant resize disk

在 windows 上用 virtualbox 來開發,因為我總是使用 default Vagrantfile ,沒有多久,就遇到了預設的 10G disk space 不夠用的情況。

查了一些資料,發現需要下一堆很複雜的指令,我實在很擔心自己會搞不定。

仔細找了一下,結果有一個 vagrant plugin 可以達成這個功能! vagrant-disksize


Tuesday, June 26, 2018

integration test with mysql

重構公司的 legacy code 時,發覺沒有 integration test ,一遇到資料庫相關的程式碼,心裡就覺得很棘手,不知該如何是好?

於是就動手做了一小段 integration test 的準備。
1. 首先是準備資料庫
2. 再來是準備初始資料
3. 將初始資料塞進資料庫

準備資料庫的部分,用 docker 好了。

準備初始資料的部分,想了一下,就用 mysql workbench 。
mysql workbench 要 export 資料,如果是用 csv 格式的話,可以 export 查詢的 result set 。但是如果要 export sql 格式的話,只能是 table 或是 database 。後來,我選擇先用 query  來做出一張新的 table ,再用 mysql workbench 來 export table

將初始資料塞進資料庫的部分,我本來是想說,我開發的程式語言是 nodejs ,我就用 nodejs 來做檔案的讀取,與資料庫的寫入吧? 結果,試了一些專門做資料庫 import 的 library 之後,卻出乎意料之外的…很難用。

重新考慮之後,如果考慮 dependency declaration 和 dependency isolation ,最好還是把測試用 nodejs 來做。不過,既然只是 integration test ,前兩者似乎也可以做一些妥協,那我還是用單純的老方法 mysql client 來做好了。

#!/bin/bash

docker run --name mysql-container -p 3306:3306 -e MYSQL_ROOT_PASSWORD=password -d mysql:5.6

# wait docker initialized
sleep 30

# import ce.item-report-hourly
mysql -h 127.0.0.1 -uroot -ppassword < ./ce_item_report_hourly.sql

Thursday, June 14, 2018

AWS 服務的除錯

現在任職的公司對 backend service 的要求是要設計成 horizontal scalable 的設計,所以其實用了滿多的 AWS 的技術。一個新的 nodejs 服務剛做出來的時候,有時候功能還不完整,也沒有設計複雜的 log 機制,那這樣子的話,該怎麼在生產環境中除錯呢?

 AWS 的 Elastic Load Balancer 是有 access logs 的機制的,所以把這個部分設定好,之後就可以把這個 ELB 的 access logs 當做 apache access logs 來除錯了。

Friday, May 25, 2018

AWS 使用心得

在 intowow 上班,不知不覺過了 5 個月,本來對 AWS 的理解就是只會用 EC2 來布署虛擬機,玩了 5 個月之後,也或多或少多學了一些東西。

對 AWS 的理解大概是這樣子,本來在非雲端的世界裡,可以用的東西,幾 AWS 的雲端環境,幾乎都會有至少同一個等級,或是更好的解決方案。比方說:

  • 檔案 (file)             => S3
  • 排程 (cronjob)      => cloudwatch schedule event 
  • 防火牆 (iptables)  => security groups
  • 路由器 (router)     => VPC & subnet
  • 自動擴展的服務  => serverless lambda

在 intowow 比較新的專案幾乎都會做的 devops 工作,是 continuous integration 和 continuous deploy
continuous integration 用 codebuild 來做
continuous deploy 用 cloudformation 來做
整套 CI/CD 則是用 codepipeline 來做


Tuesday, March 27, 2018

javascript regular expression 常見的用法

(1) 全部取代「特定字串」
範例: 刪除所有的 a 字元
const s = 'abcdeabcdeabcde';
const r = s.replace(/(?:a)/g, '');

使用 non-capturing group 符號  ?: 

(2) 計算「特定字串」的所有出現次數
範例: 計算所有的 a 字元出現次數
const s = 'abcdeabcdeabcde';
const times = (s.match(/a/g) || []).length

(3) 比對所有符合的特定字串
範例: 找出所有的 abcdeabcde
const s = 'abcdeabcdeabcde';
const rr = s.match(/(abcde)\1/g);

使用 capturing group 和 back reference



Sunday, February 18, 2018

讀 kibit static code analyzer

Clojure 的 kibit static code analyzer 可以掃瞄你寫的程式碼,並且做比對,如果發現有更好的寫法,它就會提供你做為參考。很多初學者容易寫的「冗餘表達法」,都會被 kibit 抓出來。

比方說:
(= 0 x)  就會被 kibit 建議改成  (zero? x)

我一時興起,下載了 kibit 的 source code 來研究之後,腦子裡立刻充滿了一大堆問號。經過歸納之後,我把這些自己問自己的問題,歸納了一下。首要的問題,當然是「kibit 是如何實現的?」

這個問題可以拆解成下列的幾個問題:
(1) 我們寫的程式碼,是如何被 kibit 讀入,以什麼樣的資料型態儲存?
     clojure 的 source code 被 kibit 讀入之後,刻意以 unevaluated form 的形式,視為是 list 的 data structure ,而不是字串。之後,就用 tree-seq 將 list expression 拆解成「子樹」,即 sub form 。

(2) kibit 裡的 rules 是怎麼呈現的?
      kibit 裡的 rules 可以寫成 vector 的形式。比如: [(= 0 ?x) (zero? ?x)]
      其中, ?x 的部分,是可以用來做 binding 的部分。   

(3) 用什麼 library 來做「模式比對」?
      使用 core.logic 這個 library 來做 unification
      就我個人的看法,其實沒有使用 core.logic 大部分的功能,所以如果改寫的話,說不定可以用 core.unify 來替代掉。

這邊一句話簡單介紹一下 core.unify 這個 library 主要的功能: unification 
Unification is the process of taking two expressions and substituting all of the variables with matching concrete expressions.

範例:
(unify/unify '(= 0 aa) '(= 0 ?x))
;; => {?x aa}

Friday, February 9, 2018

Http API 使用 query string / header 來傳送 json

公司的一隻 http API 的設計是這樣子:
(1) 會接受 http POST
(2) http body 是 xml
(3) 其它的 meta data 是透過 query string 來傳遞。

meta data 的參數有一些是字串、有一些是整數。然而,API 端將這些 query string 解析出來的時候,資料型別的資訊卻早已不復存在,全都變成了字串。這是因為 http query string 並沒有支援資料型別的表示法。

那…是否可以把資料先用 json 來表示,再將 json 塞進其中一個 query string 又或是 http header 呢?如此發送端就可以把資料的型別也一併送到接收端了。

查了 stackoverflow 之後,這些問題也有人討論了,建議的解法如下:
(a) 如果 json 要放入 http header 的話,json 先做 base64 編碼,再放入 http header 。JSON web token 就是使用 base64
(b) 如果 json 要放入 query string 的話,json 先做 URLencode ,再收入 query string 。但是,必須要注意 URL 長度限制的問題。

Monday, January 22, 2018

Simple Made Easy

話說我長久以來一直有個問題:『看不懂別人寫的 OOP style 程式碼。』學了 Clojure 之後,這個「看不懂」似乎有理由了。

OOP style 的東西,當一組 class/object 被定義生成時,有太多可能的語意 (semantic) ,要猜的可能性太多。

可能是:
1 values object , 在  clojure 會用單純的 hashmap 或是 records 來處理。
2 entity object, 在 clojure 會用 atom 處理。
3 function with state, 在 clojure 會用 lexical closure 來處理。
4 純脆只是 namespace 功能,為封裝而封裝
5 multimethod 的語意, 在 clojure 會用 defmulti 又或是  protocol + reify 來實現。

最近偶然又重看了一遍 Rich Hickey 的 Simple Made Easy 。發現類似的概念,他已經討論過了。在 Simple Made Easy 這個 talk 裡。object 被 Rich 歸類為 complex construct ,難以做出 simple artifact 。而太 complex 的東西會讓人腦的認知能力超載,因為要同時考慮太多元件。

該 talk 中有許多深具啟發的句子和概念:
1. All too often we do what is easy, at the expense of what is simple.
2. The benefits of simplicity are: ease of understanding, ease of change, ease of debugging, flexibility.
3. Reliability tools: testing, refactoring, type systems are good but secondary. They do not enforce simplicity. They are just safe net.
4. Build simple system by choosing construct that generate simple artifacts and by abstracting: design by answering questions related to what, who, how, where, when, and why.

Friday, January 19, 2018

Never build an Application

最近看了一篇談軟體設計的文章,文章是 Never build an Application 。主要的概念大概有幾項:
1. Never build an application. Build a library of reusable functions specific to the problem domain instead.  不要針對「需求」設計一體成形的應用程式做為解決方案。取而代之的是,要對「需求」設計一套可復用的函式庫,透過函式庫來做出解決方案。

2. 這套針對「需求」而產生的函式庫,可以視為是一種 domain specific language 。而它也可以是各種的形式: 比方說,一堆 class 可以提供的服務、一些 meta data 、 XML config 檔等。換言之,並不是一定要有特殊的語法,才可以看成是 DSL 。重點是在於,要做出一個「抽象層」,這個抽象層可以很好地對 problem domain 做很好的塑模。於是 application 就會變得很小,因為 application 是利用抽象層來做出的。

3. UML 在設計的應用上,有一個明顯的問題。它的塑模是針對 solution domain ,而不是針對 problem domain 。然而,DSL 的解法之所以有用的原因,是因為 DSL 是針對 problem domain 的 modeling 。

真的要講的話,其實這篇文章講的核心概念,和 Paul Graham 在 On Lisp 的序言中提到的 programming bottom-up 概念近乎一致。然而,我自己在實際的 clojure 程式設計經驗中,得到的心得啟發則是:

(*) REPL-driven development  是促成 programming bottom-up 的首要條件。

用不同的程式語言來開發,一方面是語言的表達能力不同。但是,更關鍵的一點是:語言對於「單元測試」的表達能力也有差異。使用 OOP 來開發時,單元測試的最小單位,往往就已經是物件,所以最小的抽象層,也往往要用 object 和 interface 來表達。 而使用 Lisp 語言的話,因為最小的測試單位是 REPL-driven development ,所以往往可以做出理論上最小的抽象層組件。

換言之,使用 Lisp 之所以可以導致 elegant design ,主要的原因在於 REPL-driven development  。跟 Lisp 擁有最強大的 macro system 的關系則比較小一些。使用其它的程式語言來開發時,只要貫徹「讓被測試的組件最小化」的原則,也一樣可以做出高品質的抽象層。

Thursday, January 18, 2018

Nodejs 開發環境設置 --- navigation

快速查詢變數 (navigation)
tern_for_vim 可以幫 vim 整合 ternjs 的 javascript 靜態語言分析器。

使用方式:
:TernDef                   查詢變數的定義
:TernRefs                  查詢所有出現的位置 / (會開啟 location list ,需要用 :lcl 指令關閉。)
:TernRename            重新命名變數,適合重構時大量修改使用。

安裝:
(a)  cd ~/.vim/bundle/   && git clone https://github.com/ternjs/tern_for_vim.git
(b)  cd ~/.vim/bundle/tern_for_vim  && npm install
(c)  在要做變數查詢的 project 根目錄,增加一個 .tern-project 檔
{
  "plugins": {
    "node": {},
    "modules": {},
    "es_modules": {},
    "requirejs": {
      "baseURL": "./",
      "paths": {}
    }
  }
}

Sunday, January 14, 2018

vim 開發環境設置

用 vim 做為主要的開發環境、也陸陸續續開發了三、四種不同的語言: javascript, python, clojure, golang 。這邊總整理一下,我覺得最常用的幾種功能。

1. Formatting/Linter
2. Evaluating
3. Navigating
4. Autocomplete

在 golang ,因為語言的特性,無法達成 2 。裝 vim-go 就可以達成 1, 3 。安裝 gocode 可以達成 4 。

在 clojure 的話:
1. Formatting/Linter  -> cljfmt 和 clj-kondo
2. Evaluating -> Conjure
3. Navigating  -> clojure-lsp 可以查找「函數引用」
4. Autocomplete -> Conjure 也有基本的支援

Nodejs 開發環境設置 --- eslint

設置 eslint
1. npm install -g eslint-config-airbnb-base eslint-plugin-import eslint
2. eslint --init

在 vim 安裝 vim-syntastic 
cd ~/.vim/bundle && git clone --depth=1 https://github.com/vim-syntastic/syntastic.git

設置 vim 的設置檔  .vimrc
set statusline+=%#warningmsg#
set statusline+=%{SyntasticStatuslineFlag()}
set statusline+=%*
let g:syntastic_always_populate_loc_list = 1
let g:syntastic_auto_loc_list = 1
let g:syntastic_check_on_open = 0
let g:syntastic_check_on_wq = 1
let g:syntastic_javascript_checkers = ['standard']
let g:syntastic_javascript_standard_generic = 1
let g:syntastic_javascript_checkers = ['eslint']
" let g:syntastic_javascript_eslint_exec = 'eslint'
let g:syntastic_mode_map = { 'mode': 'passive', 'active_filetypes': [],'passive_filetypes': [] }

在 vim 啟動 linter
:SyntasticCheck

在 vim 檢查 linter 是否成功設置
:SyntasticInfo

關閉顯示 lint Error 的 Location List
:lclose

讓本地端資料夾的 eslint 被 syntastic 優先採用
cd ~/.vim/bundle && git clone https://github.com/mtscout6/syntastic-local-eslint.vim.git

在已經有安裝 local eslint 的資料夾下,啟動 eslint 來做 autofix
node_modules/.bin/eslint   --fix   FILENAME

Saturday, January 13, 2018

API v.s. DSL

API 與 DSL 是不同的概念,偏偏有一句電腦科學的諺語: library design is language design.

查一下 stackoverflow ,也查到了還不錯的解釋與比較。

DSL (domain specific language) 是「程式語言」。它們可以是編譯的、直譯的、資料查詢用的 (如 SQL)、標記語言 (如 JSON, XML)。總之,DSL 會是個語言。 DSL 這個詞描述的是它們的本質 (nature),而不是它們的用途 (purpose)。

API (application programming interface) 是「讓軟體元件可以被其它元件使用的溝通介面」。這個詞彙描述的是它們的用途 (purpose) ,而不是它們的本質 (nature) 。所以 API 可以是各種不同的形式: 物件方法、 C 語言的函式等等。

在 Martin Fowler 的 DslBoundary 一文中則是特別討論了算是一種特例的 embedded DSL :
1. API 強調提供新的「功能」
2. DSL 的重點則是在於:以一種『新的語法、編程方式』來提供新的「功能」

Tuesday, January 9, 2018

12 factors 心得

I. Codebase
一個 app 對應一個 git repo ,而不是一個 distributed system 對應一個 git repo 。

II. Dependencies
python 可以用 pip 和 virtualenv 來做依賴聲明 (dependency declaration) 與依賴隔離 (dependency isolation)

V. Build, release, run
應該要使用 release management tools,以便用於管理 release 的版本。必要時可以一個命令 rollback 退版。

VII. Port binding
語言有提供 http server 的 library 是一大優點。app 可以直接做 port binding

IX. Disposability
要讓 app 可以 graceful shutdown ,主要需要考慮兩個問題:
(1) network app 要拒絕所有新的請求,並且繼續執行當前已接收的請求,然後退出。
(2) worker process 終止之前,要把當前的任務退回 Queue 。比方說,對 RabbitMQ 發送 NACK

XII. Admin processes
語言有提供 REPL 是一大優點,可以直接使用該種語言寫成的 script 來做 admin/management process。如此就可以與 app process 使用相同的依賴聲明、依賴隔離。

Saturday, January 6, 2018

[jQuery語法] 找出 DOM 裡的 leaf node

用 JQuery 找出 Dom 裡頭的所有 leaf node ,是做前端時有可能遇到的有趣問題。
整理一下暴力但是好用的解法:

找出所有在 body 裡的 leaf node
$('body *:not(:has(*))')

找出所有本身是 div 的 leaf node
$('div:not(:has(*))')

找出所有最內層的 li 。注意:li 總是存在於 ul 和 ol 裡頭。
$('li:not(:has(ul, ol))')
============================================
Extract value from XML format

仔細想想的話,上述的問題可以歸類於「從 xml 提取值」。
解法大概可以分成兩種:

1. 命令式編程的解法:對 xml 做樹的走訪 (tree walk),邊走邊取值。
    這種設計如果來源資料的 xml 有局部變化,通常就會朝 polymorphism 的方向演化。每一種局部變化會對應獨立的模組 (module)。

2. 宣告式編程的解法:找一個支援 XPath 的 xml parsing library 。用 XPath 做查詢 (query) 取值。
    這種設計如果來源資料的 xml 有局部變化,每一種局部的變化會對應不同的 XPath 設定檔 (config)。

Friday, January 5, 2018

git log analysis --- find out the hot spot

Clojure 寫的 https://codescene.io/
它是一個 on-line 的 data analyzing tool 。分析的目標是 git commit  log 。

它可以回答下列幾個我覺得對於 technical manager 會有意義的問題:
1  How shall we prioritize improvements to our codebase?
2  How can we follow-up on the effects of the improvements we do? Did
we really get an effect out of that design improvement?

它分析 git log 的方式,是透過一種 hot spots 的分析,找出 git repo 裡變動頻率最高、相對的行數也特別多的檔案,這些檔案是 git repo 裡的 hot spots 。

同時,hot spots 往往也是
(1) 最容易產生 bug
(2) 最有可能需要、或是值得重構、
(3) business value 最高、
(4) 最容易造成 delivery delay
(5) 最值得做 code review 的區塊。

hot spots 可以如何使用呢?
  • Developers use hotspots to identify maintenance problems. Complicated code that we have to work with often is no fun. The hotspots give you information on where those parts are. Use that information to prioritize re-designs.
  • Hotspots points to code review candidates. At Empear we’re big fans of code reviews. Code reviews are also an expensive and manual process so we want to make sure it’s time well invested. In this case, use the hotspots map to identify your code review candidates.
  • Hotspots are input to exploratory tests. A Hotspot Map is an excellent way for a skilled tester to identify parts of the codebase that seem unstable with lots of development activity. Use that information to select your starting points and focus areas for exploratory tests.

-------------
針對這個透過 git log 來找出 hot spot 的工具,再三思考後,我覺得它本質上還是有點像 unit test, static type system 這樣子的東西,它基本上還是輔助用的東西,可以提供「提示」卻無法提供「洞察」。

故意舉一個極端的案例:如果我寫 web application 使用了 ORM 。ORM 也許不會要常常修改,要手動改的程式碼也不多。所以 ORM 相關的區塊就不會被 git log analysis 標定為 hot spot 。然而,ORM 的本質卻是極致的 complex ,因為它同時把物件與 SQL 纏繞在一起,絕對是極度複雜的區塊。