分類彙整:工作

搞懂為何設定 React、JSX、ES2015、Babel、Webpack 的學習筆記

學習 React、JSX、ES2015、Babel、Webpack 等等現代化的前端開發觀念時,會需要安裝很多工具、設定多個檔案、用到許多指令。

大部份教學文章只講「怎麼設定這些」而沒說明「為何要設定這些」。照著做完感覺很不放心。

最近我試著搞懂「為何要設定這些」,跟大家分享一下這個逐步搞懂的過程。

前言

這篇文章假設你想要開始用 React 開發,希望搞懂相關的前端環境與設定。

如果你想知道用 React 到底有什麼好處,可以參閱以下連結:

Introducing JSXThinking in React簡單聊一下 ONE-WAY DATA FLOW、TWO-WAY DATA BINDING 與前端框架

接下來,這篇文章會以試著寫一個陽春的 App 元件作為範例,內含 ChildA 跟 ChildB 兩個元件。

讓我們開始吧!

第一步:直接在 html 檔內開發 React 給瀏覽器執行

我們先用原始的開發方法硬搞看看!

用 script 標籤直接從 CDN 引入 react.js 跟 react-dom.js 檔案。

然後試著寫一個陽春的 App 元件,內含 ChildA 跟 ChildB 兩個元件。

這個 html 檔內容會像這樣:






Should Render Here

source code

結果瀏覽器無法執行 React 相關的程式碼!

原因有兩個:

1. 瀏覽器看不懂 ES2015 的語法(class、extends)

2. 瀏覽器看不懂那個像是把 html 寫在 js 裡面的 JSX 語法

該怎麼辦呢?

第二步:用 Babel 讓瀏覽器能看懂 ES2015 與 JSX 語法

我們可以使用 Babel 套件來來編譯 ES2015 與 JSX 的程式碼,讓瀏覽器能夠看懂。

只要在 html 內加上這行:


然後在要編譯的 script 加上 type=’text/babel’ 即可:








Should Render Here

source code

然而,這樣做卻又產生3個新的問題:

1. 有N個檔案,就要打編譯指令N次,太累了

2. 透過 script 標籤引入N個檔案,會增加伺服器負擔、也會增加用戶端等待時間

3. babel 相關的程式碼都移出瀏覽器了,react.js 跟 react-dom.js 卻還是在瀏覽器內引入

第一個問題還算簡單,可以改成輸入指定資料夾的指令,就可一次編譯:

./node_modules/.bin/babel src/ -d dist/ --presets es2015,react

但是第二三個問題就棘手得多。

有鑑於此,需要找方法一次解決這三個問題:設法一口氣全部編譯、封裝成單一檔案、並且函式庫統一用NPM在伺服器端安裝與管理吧!

第四步:使用 webpack 來解決這些整合問題

webpack 會利用 babel-loader 套件來呼叫核心的 babel-core,所以先分別安裝它們:

npm install --save-dev babel-core
npm install --save-dev babel-loader
npm install --save-dev webpack

我們不會透過命令列去使用 babel 了,既然用不到就先把它移除吧:

npm uninstall babel-cli

我們也不會用 script 標籤引入 react.js 跟 react-dom.js 檔案了。一口氣全部編譯時會用到,所以用NPM安裝它吧:

npm install --save-dev react
npm install --save-dev react-dom

前面說過 babel 版本 6 以後預設不支援 ES2015 與 JSX,現在不透過命令列設定 presets 的話,就需要建立 .babelrc 檔案去開啟支援:

{
  "presets": ["es2015", "react"]
}

然後為了讓 webpack 在一口氣全部編譯成一個檔案時,能看懂檔案跟套件之間的相依性,因此需要按照 ES2015 的 import/export 語法寫清楚。

例如原本的 App.jsx 就要改寫成這樣:

import React from 'react';
import ChildA from './ChildA.jsx';
import ChildB from './ChildB.jsx';

export default class App extends React.Component {
  render() {
    return (
      

Hello App!

); } }

src 資料夾內的檔案都要進行改寫。

最後建立 webpack.config.js 來設定檔案路徑,並指定使用 babel-loader 來幫忙編譯:

var path = require('path');

module.exports = {
  module: {
    loaders: [{
      loader: 'babel-loader',
    }]
  },
  entry: './src/index.jsx',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, './dist/')
  }
}

就可以透過命令列執行 webpack了:

./node_modules/.bin/webpack 

這段指令有點長,可以把它放進 package.json 的 scripts 欄位:

  "scripts": {
    "build": "webpack"
  },

之後就可以用下列指令來進行編譯工作了:

npm run build

會得到我們要的結果:一個檔案搞定一切!

Should Render Here

source code

大功告成!終於搞定我們想要的所有效果!

結語

本篇文章提到的內容,根據不同的套件版本,實務上有多種方式去安裝、設定達到同樣效果。

但是核心觀念是共通的!

可以此為基礎,探索更多的現代前端開發方法,學習更多工具的功能與設定!

(完)

(Photo via 8 Kome, CC licensed.)

Cafe Nomad 蒐集 1,700 間咖啡廳的理由

我是一個很常去咖啡廳用筆電的軟體工程師。

每次在巷子內找到新的獨立小店時,我心中都會浮現3個念頭:

1. 又是一間精緻用心的獨立咖啡廳。這種店台北應該有幾百家,如果每間都去過,一定很有成就感

2. 這間店的位置真是太低調了,就算開幕滿一年,恐怕連附近住戶都不知道它的存在

3. 根據以上兩點,好像可以推論:巷子內的這些咖啡廳,普遍被埋沒了

2016年11月,趁著離職之後時間多,我決定驗證一下這些直覺。

我把過去幾年去過的20多間咖啡廳資料整理出來,根據自己最在乎的7個指標做了評分,接著上網再找出台北60間咖啡廳的店名,作成 Google 試算表,等待網友幫忙評分。

把這個試算表貼到批踢踢的軟體工程師看板之後:

[討論] 台北適合工作的咖啡廳

反應很不錯,隔天下午時,大部份咖啡廳都得到網友評分了,資料也被新增到100多間。

然後,我寫了程式把試算表的內容轉成網頁版的表格,接著訂購了一個網址:cafenomad.tw

稍微做了SEO與SMO之後,我把這張表格分享到我的一個粉絲專頁:

https://www.facebook.com/permalink.php?story_fbid=982000048579054&id=650279948417734

這則貼文隔天被分享了1,200次,再被轉分享之後,最後共計被分享了30,000次,當天有90,000人來看 Cafe Nomad:

1

這些流量也帶來更多人幫忙整理咖啡廳資料,最後台北收錄了200多間,其他地區也共計有200多間。

網友們的熱情參與,幾乎證明了巷子內的這些咖啡廳果然被埋沒了。

換句話說,台灣獨立咖啡廳產業的整體產值被低估了。

那麼,該如何提昇台灣獨立咖啡廳產業的整體產值呢?

我想到三個辦法:

1. 針對原本就會去獨立咖啡廳的族群:鼓勵他們更頻繁地消費
2. 針對只去連鎖咖啡廳的族群:邀請他們試試看獨立咖啡廳
3. 針對近年來與日俱增的國際觀光客:鼓勵他們去咖啡廳喝一杯

針對原本就會去獨立咖啡廳的族群

這是其中最簡單的方向。只要把這份清單給他們看就行了。

然後不斷開發更好用的找店功能,然後定期告訴他們有新功能就可以了。

這個族群也容易觸及:只要去批踢踢、Facebook社團貼文就可以了,大概像這樣:

[閒聊] 我做了一個蒐集956間咖啡廳資料的網站

[閒聊] 我做了一張台中咖啡廳地圖

[閒聊] 一口氣逛高雄全部咖啡廳的FB粉專的方法

[閒聊] 台南咖啡廳地圖:150間店 + 1,000張照片

https://www.facebook.com/groups/1507207486163325/permalink/1793163560901048/

這些貼文的反應都很不錯,也讓更多人知道巷弄小店要去哪裡找。

針對只去連鎖咖啡廳的族群

這是一個十分困難的方向,但還是有出力空間。

時不時會有咖啡產業的新聞出現,這種時候直接去人多的地方,然後跟大家談談自己對獨立咖啡廳的觀察跟心得就可以了。例如八卦版就很適合去po文:

Re: [問卦] 為什麼台北都很少小資本額的咖啡館?

把握住這種機會的話,可以吸引到至少50,000人開始想試試巷弄小店。

2

近年來與日俱增的國際觀光客

這是最困難的方向,光是要觸及這些旅客就很難,但還是有出力空間。

我把網站介面翻譯成了英文版:

https://cafenomad.tw/en

然後貼到幾個在台灣的英文社群網站:

https://www.reddit.com/r/taiwan/comments/5uw792/cafe_nomad_700_best_cafes_to_work_in_taiwan/

https://www.facebook.com/groups/357339197762790/permalink/723135624516477/

接著找 Cafe Nomad 社群的人幫忙翻譯成日文韓文版

https://cafenomad.tw/ja/

https://cafenomad.tw/ko/

然後再貼到相關的台日交流社團、台韓交流社團:

https://www.facebook.com/groups/japantaiwan/permalink/1392935974078433/

https://www.facebook.com/groups/partyintaiwan2/permalink/1453874974920063/

反應普遍很正面,雖然比較間接,但還是有點效果。

結語

網站上線5個月之後,我開始會在FB收到店家這樣的訊息:

3

Cafe Nomad 現在已經蒐集超過1,700間店、超過3,500筆評分、500則留言。每天都有超過1,000人主動上站尋找他下一間要去的巷弄小店。

最近替 Cafe Nomad 新增了店家贊助的功能,也找到了幾間贊助店家,所以我也得到了一些報酬。

在過去三年,我去這種巷弄小店消費了幾百次。整體說起來,在台灣,你很難找到一間會讓你失望的獨立咖啡廳。

花了很多時間做這網站,其實我也不太確定自己在幹嘛,但因為是滿有趣的事情,所以就算完全行不通,好像也沒關係。

我唯一確定的是,台灣這些巷弄小店,確實普遍被埋沒了。


附註:

店家贊助」功能是我替 Cafe Nomad 最新加上去的廣告功能。

它會將咖啡廳的名稱與照片獨立顯示出來,並且在清單與地圖內特別呈現。

歡迎有興趣的店家點這裡看看這個贊助功能的細節

(Photo via unsplash.com, licensed under Creative Commons Zero)

不要浪費開發時間:給新創公司的6個軟體開發建議

新創公司因為產品定位還不明確,常常花一堆時間做好功能,卻發現沒人要用,浪費了寶貴時間。

我在2014年底加入一元翻譯,一個已有穩定客源與翻譯師的翻譯團隊,負責開發系統來協助公司處理與日俱增的文件。

原以為這套系統的定位明確、連使用者都已經有了,因此容易開發,結果初版的系統開發還是犯了一些錯誤。

踩雷幾次之後,我們慢慢整理出一些比較有效的開發方法。

這些方法幫我們省下很多開發時間,今天跟大家分享一下!

一、別在一開始就想要流程全自動

常常聽到正要創業的團隊擔心「要是我們太受歡迎怎麼辦?」、「要是我們太紅怎麼辦?」

直覺的想法會是「當然要先做好準備再上線!」、「把系統做到全自動,讓工作人員不需要介入,整個流程就能順利跑完!」

我們也犯過類似錯誤:既然都已經有穩定客源了,請他們改用更方便的全自動系統應該不難吧?

結果系統上線之後,舊有客戶大多繼續用 Email 和電話與我們來往。即使我們提供額外折扣,他們一時之間還是不習慣新的下單方法。

所以我們後來設計新功能、新產品的時候,便不再一開始就把所有流程自動化。

客戶習慣的部份,不要輕易做大幅度調整。先針對很花時間、重複性很高的部份做自動化,比較有效率。

其餘的部份,快速寫出一個「半自動」的系統,然後直接上線。

收集Feedback系統?用個Google表單嵌入在網頁裡面就很像了。

自動寄出電子發票系統?做個「寄出發票」按鈕,給客服人員自己找時間去按就好了。

合作夥伴註冊系統?用個Google表單嵌入蒐集資料,然後自己用 Email 一個一個聯絡就好了。

商品具有多種狀態的全自動物流系統?其實在資料庫用不同整數代表各種狀態,給物流人員手動調整所有商品的所有狀態就夠用了。

做出幾個功能按鈕,讓負責人員自己判斷什麼時候去按那些按鈕就好了。

情境判斷與自動化的程式碼都先別寫了。需求量大到負責人員忙不過來再寫就可以了。

二、信任你的內部人員

不信任的成本是很高的。

公司常常需要針對管理員、工程師、合作廠商、實習生等等不同角色,開發權限系統。

真的要針對每種角色,在後台管理面板寫一套大企業等級、能夠做完整內控的權限管理系統嗎?

底層不作嚴格檢查、只針對各角色顯示不同資訊,真的不夠用嗎?

相關的內部人員真的會惡意到去測試系統漏洞、亂改不屬於他們的資料嗎?

真的發生這種事,在算帳時會發現不了嗎?被影響到的人不會來反應嗎?

我們也曾因為預計之後會有獨立的PM權限,於是在目前的ADMIN權限之外多寫了PM權限。

結果因此浪費很多開發時間,而且根本沒那麼快招募專門的PM人員。

完整的權限系統會需要在controller或是更底層的layer寫一堆檢查的程式碼。

更糟糕的是,各個角色的職責跟功能也變動得很快,複雜的權限系統會讓各種功能改起來更慢。

先實作一套簡單陽春的後台權限檢查,通常會夠用好一陣子。

 三、把功能拆分成階段上線

這是所有建議裡面,我們付出最多代價,才學到的教訓。

不要在一開始就把功能做到豐富完整。

大部份的功能,都可以在討論過後,拆分成階段上線。

討論出功能最基本的長相(Phase 1),試著在幾天到幾週內開發然後上線。

Phase 1上線後會碰到各式各樣的問題,像是使用者不想用、不會用、用了卻不爽…等等。

不如讓 phase 1 先上線,再根據結果,思考phase 2要做哪些事,或是乾脆擱置這個功能不做下去。

這種作法能讓服務快速上線、團隊的下一步明確、省下不必要的開發時間。

以我們一元翻譯為例,公司的營運有兩段流程:客戶送文件給我們、我們送文件給翻譯師。

這兩段流程原本都是在Email上進行,也就是很傳統的作法。

為了讓這個流程自動化,需要開發一個類似購物網站的電子商務系統,讓客戶透過網站下單、翻譯師透過網站收到文件。

直覺上會從客戶下單介面開始,接著做內部管理面板、翻譯師工作面板,把整個系統寫完。

但如果真的一口氣把系統寫完,風險會非常巨大。

要是客戶都拒絕透過網站下單、堅持繼續用Email寄檔案給我們怎麼辦?

要是翻譯師不喜歡用工作面板接案,習慣用Email跟我們互動怎麼辦?

那原本預期的流程就完全行不通了。

為了避免這種「寫出一整套solution,上線後卻行不通」的慘劇,我們  phase 1 只開發「送文件給翻譯師」系統。

也就是客戶繼續用 Email 給我們檔案,我們的PM手動把文件上傳到系統,接著系統通知翻譯師,翻譯師透過網站接案與交稿,PM最後再把成品用Email寄給客戶。

對客戶來說,流程跟原本一模一樣。

upload

(圖一、PM用這個面板把檔案丟進系統)

projects

(圖二、PM跟翻譯師在這個面板瀏覽文件、翻譯文件)

Phase 1 上線後,我們蒐集翻譯師的意見、不斷改善系統、改到翻譯師覺得系統好用為止。

然後才去開發「客戶送文件給我們」的系統。

但因為怕客戶「堅持只用Email與我們互動」、「操作網站會很沒安全感」,我們的 phase 2 從提供一個確認報價的連結開始:

email

(圖三、收件後繼續由PM寄Email給對方。但要求他至少打開一個網頁。)

confirm

(圖四、報價資訊只寫在網頁內,不寫在Email內,鼓勵客戶去按那個大按鈕。如果客戶拒絕去按,而寧願寫Email回信,那這個 phase 2 就算是失敗。)

結果大部份的客戶都願意去按「同意報價」按鈕!只有少數客戶選擇用Email回信。

確定客戶們至少「願意在Email以外與我們互動」,我們才動手把圖一的PM下單面板做成公開版本的 phase 3,讓客戶能夠自行下單。

如果沒有分階段開發,這個系統可能會開發非常久才上線。

四、盡量去追蹤網站的使用情況

網站到底哪些功能常被使用?哪些地方客戶逛了但是沒去用?哪些地方客戶根本逛都不逛?

除非你站在客戶旁邊看他操作系統,否則很難得到答案。

網站的下一步要怎麼改善,團隊裡的每個人都有不同想法,這種時候,最好能用數據來說話。

因此,應該要盡可能地去紀錄每個按鈕、每個連結、每個頁面的使用數據。

安裝Google Analytics是基本的。

除此之外,記得替幾個你覺得重要的按鈕加上Google Analytics的Event Tracking功能;

有在網路上公開的文章、貼文,可以用Bitly之類的工具紀錄網址使用情況。

再不行的話,就在系統本身建幾張資料表,直接用程式碼去紀錄某些功能的使用數據吧。

五、工程師做做看客服,做做看業務

這點跟開發沒有直接相關,但還是會影響開發速度。

負責客服的同事可能多次向工程師反應過某個頁面很難懂、按鈕很難用。

工程師常常會去忽略這些反應,心想「那個頁面最好是有這麼複雜」、「你跟那些客戶多解釋一下就好了」。

然後就會導致每個人對於接下來什麼事最重要有不同看法。

這種時候,如果工程師花點時間去做客服,通常會有很大幫助。

他可能會驚覺「居然這麼多客戶反應同樣問題」,於是充滿鬥志的把功能改好。

也可能會發現「同事沒講清楚,但其實加點字、改改按鈕顏色就解決了」。

同樣的,如果工程師花一些時間做業務,可能會突然理解為何負責業務的同事會一直要求某個功能。

工程師偶爾換一下角色,做點其他事情,不但能讓團隊溝通更有效率,開發起來也會更有士氣。

六、先讓介面簡單易用

我們在設計第一版網站的時候,想把網頁弄得漂漂亮亮、希望它很有質感。

後來發現如果它的功能本身不受歡迎,那再漂亮也沒用。

在不確定會多受歡迎的情況下,不如先做到使用者知道系統怎麼用就好。

所以JavaScript預設的alert、confirm、prompt函數,其實依然很好用。

一元翻譯來說,我們把報價頁面做好之後,客戶卻多次反應「不知道怎麼確認報價」。

因為不想花時間去大幅調整設計,我們於是直接加上又大又紅的提示文字,再搭配一個大大的綠色按鈕:

red

(又大又紅的提示文字)

green

(48px 超巨大綠色確認按鈕)

雖然不太好看,但這個介面開發時間很短,而且上線之後,再也沒有客戶抱怨找不到確認按鈕了。


以上六點就是我們花了許多開發時間後,整理出來的一些建議。

說穿了其實就是:盡可能地將開發時間花在確定有價值的事情上面。

但也不需要太怕犯錯,初次開發產品多少都會遇到類似問題。

最重要的是有明確方向、小步驟地實驗與驗證,同時又保持足夠彈性來根據實際使用狀況做調整。

如果您有任何想法或是其他開發建議和大家分享,歡迎在下方留言!

 

工商服務時間:

本篇文章的內容,大多是來自我在一元翻譯開發系統的心得。

需要專業翻譯服務的朋友,歡迎來一元翻譯的官網逛一逛!

文中提到的客戶下單系統,可以在這裡看到它的真面目!

也歡迎到一元快報看看我們翻譯的各類優質文章!

(Photo via Sano Rin, CC licensed.)

[翻譯] Basecamp共同創辦人:不必擇你所愛,也不必愛你所選

如果你常參加創業聚會,或是常去聽那種企業家的勵志演講,你會很常聽到這種論調:你必須擇你所愛,愛你所選!如果你沒有的話,那你乾脆別出來混了。最著名的例子是Steve Jobs 2005年在Standford畢業典禮上的演講:「想要有出色成就,唯一方法就是做你熱愛的事。如果你還沒找到,那就繼續找下去,不要停下來。」

我完全不信這套。

熱愛自己的工作當然沒有錯。但我不覺得這是創業或實現抱負的先決條件,也跟什麼出色成就無關。說真的,事業有成的人吹噓自己對工作的熱愛實在很虛偽。就跟富有的人說自己不在乎財富一樣虛偽。人們常常會浪漫化自己的動機跟過去。他們會把自己現在所在乎的東西想得很重要,而忘記他們一開始在乎哪些東西。人類天性如此,很容易就會這樣。

根據我的觀察,許多出色企業跟重要創新,根本是源自於挫折,甚至是厭惡。Uber的共同創辦人 Travis Kalanick 和 Garrett Camp哪是因為熱愛運輸與物流才創業的。他們創業是因為舊金山很難叫計程車,搞得他們一肚子火。Kalanick現在大概滿喜歡經營Uber的,但他之前真的超痛恨叫不到車回家。巴黎一個腦力激盪的夜晚,讓那份挫折轉變為一棵幼苗,孕育出一間市值數十億美金的公司。

我常常跟其他企業家聊天,很多人開公司都是基於類似的理由:他們想要的東西市面上沒有,或是想用更好的方法去改變舊的做事方式。至於是否熱愛則未必重要。但是對於現有選項的厭惡、對於事物運作方法的強烈意見則影響極大。能否成功也和它比較相關。

我的職業生涯也是這樣。大概在90年代的時候,我想找一個能幫我紀錄音樂播放清單的小工具,但市面上的軟體都很肥大,而且過度複雜。這兩件事都讓我痛恨。所以我就自己想辦法做了一個工具,最後命名為 Audiofile丟到市面上。我並不熱愛音樂蒐集,也不熱愛開發軟體(當時才剛學而已)。我也沒有開一間軟體公司的願望。我就只是看到一個需求,然後設法滿足它,謹此而已。這沒什麼不對。之後也基於類似的情況,開了現在這間公司Basecamp。

老實說,就算到了今天,我也沒說總是很愛我的工作。那些文書工作,那些報告,伴隨公司成長所帶來與日俱增的責任與瑣事。這些事都讓我覺得很煩。不過經營Basecamp對我來說還是比做其他事好。我覺得我做得不錯。每天都要作一些需要創意、很有挑戰性的工作。我一直覺得讓專案管理工具越變越好,是一個值得、能有所回報的理由。每天都能跟這些厲害的同事一起工作也真的很棒。

如果要我上講台發表什麼勵志演說,我會說,如果你想成功、想對世界有貢獻,你需要對你在做的事情有某種內在動機。你必須樂於把時間花在那上面。對於它的喜愛有可能在日後增加。如果真的那樣,那很棒。但不需要一開始就熱愛它。光是渴望一個還不存在的東西,就足以讓你成功。

(本文翻譯自 Do you have to love what you do?,得到 Jason Fried 親自授權。)

[推薦]學寫程式的幾本書籍

常常被朋友問到,說他工作上有某某需求,想學某某語言,有沒有適合他的書本?

有的,我的確有推薦的入門書籍。

這裡寫文章一次回答這些問題。

Q: 我沒有任何基礎,想入門程式設計,希望能叫電腦替我做些事情?

我推薦「深入淺出程式設計」。
programming
Q: 我沒有任何基礎,想入門程式設計,學架blog、網站、個人網頁?

我推薦「深入淺出 PHP 與 MySQL」。
php-mysql
Q: 我不想學寫程式,只想學設計相關的HTML&CSS?

我推薦「深入淺出HTML&CSS」。
html-css
Q: 我有一點基礎,但想更了解Python的初級、中級觀念?

我推薦「深入淺出 Python」。
python
Q: 我有一點基礎,但想更了解Java的初級、中級觀念?

我推薦「深入淺出 Java 程式設計」。
java
Q: 我有一點基礎,但想更了解JavaScript的初級、中級觀念?

我推薦「深入淺出 JavaScript」。
javascript

你可能會覺得奇怪,怎麼全是同一套系列的書?

這也沒辦法,因為深入淺出系列就是如此出色。

整套系列,我買入手的、曾經去圖書館借來看的,就在10本以上。

歐萊禮的這系列書籍,跟一般的電腦書籍非常不同,

它會兼顧你學習、應用、樂趣、知識,多個入門最需要的面向。

試了很多本電腦書籍,卻依然一頭霧水的朋友,不妨試試看深入淺出系列。

寫程式不需要天份,也不需要熱情

從來沒有一個技能,曾經被神化到這個程度:

「你不但要有天份,還要有熱情,才適合寫程式。」

那些寫程式的人,好像「從小就立定志向,決定未來要寫程式了」。

缺乏其一的話,你要嘛是個假貨,要嘛走不遠,總之就是不適合。

這種深植人心的刻板印象不但大錯特錯,同時還是有害的。

隨便找幾個工程師都能證明這點。

Jacob Kaplan-Moss(Django創造者)

Jacob Kaplan-Moss的這份簡報提到:

一個平庸工程師的自白

這種關於「程式天才」的神話非常有害,一方面它把行業門檻設置得特別高,令很多人望而卻步,另一方面它也在折磨產業內的人,因為你如果不能 rocks ,就會變成 sucks ,所以不得不用一切時間來努力學習和工作,導致影響生活。…(略)…我們應該改變這種態度,寫程式只是一些技能,並不需要太多天分,它是可以學習的,而且做一個平庸的工程師不丟人,

他本人在Twitter的自介直接寫「不是真的程式設計師(not a real programmer)」,

透漏著他對這種迷思的不耐煩。

Jacob Thornton(Bootstrap作者)

在Github擁有八萬顆星的Bootstrap作者,

前Twitter、現任Medium工程師Jacob Thornton的一篇採訪也是這種迷思的反例:

Jacob Thornton痛恨電腦(Jacob Thornton Hates Computers)

當他說「我痛恨電腦」的時候,並不完全在開玩笑。…(略)…他說「我本來要去唸社會學的」

接著描述了他第一份工作的情況:

我拿到了一個遠超我能力的工作。每一天都可能被開除。所以我非常努力工作,想搞懂JavaScript,因為我不懂它到底在幹嘛。

我一生中最現實的一刻到了。整間公司的人圍在我身邊,要我做一個XHR request。我根本沒做過,我只稍微聽過而已。於是我開始打字、重新整理瀏覽器,然後什麼都沒出來。我反覆做了幾次,知道自己完蛋了,他們發現我是假貨了。接著我突然發現自己忘記加「.send()」。我加了之後再次重新整理瀏覽器,畫面成功顯示。整個團隊感覺像在說「喔,酷。」然後就各自回辦公桌了。

我在那裡坐了15分鐘。心想,就這樣。我搞定了。我不會被開除了。

這段描述一點也不像「程式天才」在職場的表現。

至於支持他一路走來的動機是什麼呢?他說:

我是一個高度在乎同儕的人,我做前端的朋友總是會告訴我哪個地方做很醜或是在哪個瀏覽器上壞掉。感覺真的很棒。我真的只想跟朋友一起寫程式,一起工作。

他本人的Twitter自介寫「computer loser」,

置頂推文是「公司裡第一爛的工程師,但是第三酷」。

這種態度跟刻板印象完全相反。

Rasmus Lerdorf(PHP之父)

Rasmus Lerdorf的言論常常引起廣泛爭議:

  • 我其實很討厭寫程式,不過我喜歡解決問題。
  • 有些人熱愛寫程式。我不懂他們為何會這樣。
  • 我不是一個真的工程師。我把東西弄一弄,弄到能跑之後就不管了。真的工程師會說「這段程式能跑,但記憶體沒管理好,我們來修好它」。我只會說,一直重新開機不就好了。

從他的言論,很難看出他對電腦本身有多少熱情。

他也跟Jacob Kaplan-Moss以及Jacob Thornton一樣,懶得對寫程式的迷思多做解釋,

乾脆直接說自己是loser、假工程師了。

David Heinemeier Hansson(Rails之父)

DHH在接受Big Think訪問時提到:

說來有點好笑。我以前寫PHP跟Java的時候,常常花時間去摸其他程式語言。到處摸看看其他程式語言…隨便什麼都好。寫PHP跟Java實在太悶了,我需要用這種方式讓自己暫時抽離。

我以前寫PHP跟Java的時候,完全不覺得自己之後會當程式設計師。

整段看起來都不像是一個「電腦天才」的自我介紹。

最後讓他愛上的不是電腦本身,而是Ruby程式語言的優雅性。

如果Ruby沒有被發明,DHH現在也許會做完全不同的事情。


這一類可以說明刻板印象大錯特錯的文章實在太多了,

看看工程師們最愛的幾個玩笑:關於工程師 59 條搞笑但卻真實無比的語錄

  • 一個人寫的爛軟體將會給另一個人帶來一份全職工作。
  • 傻瓜都能寫出電腦能理解的程式,優秀的工程師寫出的是人類能讀懂的程式。
  • 開發軟體和建造教堂非常相似——完工之後我們就開始祈禱。

如果工程師都很有天份跟熱情,這些笑話又怎會受歡迎呢。

再看看Medium上很受歡迎的學習系列文章:資深開發者給後輩的七個 Coding 學習心得

其中的幾個建議

  • 也許常常有人說你是錯的
  • 也許常常會有人跟你說「你並不是個 Coder」
  • 不要在意外表,能力才是一切

無非就是想打破這類寫程式的迷思、無意義的資格論神話。

下次又有人學到一半,開始反省自己適不適合、夠不夠資格的時候,

我只想跟他說:你就多找幾種方式學學看吧,不要抱持那種奇怪的資格論。

很多時候其實只是搞錯方法搞錯心態而已。

真的完全學不懂再放棄吧。

寫程式不需要天份,也不需要熱情。


如果您喜歡我的文章,可以在這裡訂閱。我有新想法的時候,很樂意跟你分享。

(Photo via Sano Rin, CC licensed.)

ARCA架構:以 Active Record 為軸心的架構

越來越多的framework本身提供Active Record實作。

不管是Rails的Active Record,還是Laravel的Eloquent,

這些Active Record實作的功能越來越豐富而強大。

它讓開發速度更快了,但也因為太多功能塞在一起,容易寫出極度肥胖的萬能類別。

該怎麼使用這種萬能類別,又不至於讓它胖到難以維護呢?

我從工作中整理出了稱之為ARCA的架構。今天來跟各位分享。

註:本文程式碼多以Laravel框架舉例。但是相關概念通用在任何擁有Active Record的語言/框架。

簡介

ARCA全名「Active Record Centered Architecture」,

也就是「Active Record中心架構」,以Active Record為主軸的軟體架構。

ARCA試圖在享受Active Record帶來的開發速度的同時,拉出一定的架構。

這套架構適用於中型以上(連續開發3個月以上)專案,不適用於小型專案(像是餐廳官網)。

ARCA架構

ARCA架構由幾個元素組成,檔案結構看起來如下(以MVC舉例):

一個元素會是一個資料夾,或是一個檔案(類別)。

/View資料夾
/Controller資料夾
/Model資料夾
…./Entity Model資料夾
……../Entity類別
……../Repository類別
……../Presenter類別
……../Form類別
…./Service資料夾
……../Service類別
…./Operation Model資料夾

Entity Model資料夾

擺放與一種entity相關概念的程式碼。

將一種entity相關的程式碼放在同一個資料夾。

Entity類別

entity是公司在乎的商業領域(domain)中,代表現實世界的一種事物。

常見的entity包含訂單、商品、顧客、禮券、文章。

通常會代表現實生活中的一種事物、需要具備ID。

在ARCA中負責直接繼承Active Record的類別。

這是ARCA架構的重點所在。遵照少數的類別命名、資料表命名慣例(convention),

直接賦予這些entity類別多種能力:

  • 物件property存進資料庫的相關操作(CRUD)
  • entity間的relationship
  • 對資料庫取出的值做適當轉換

而開發者所要做的,只是寫好類別定義與幾個函式而已。

class User extends Eloquent {

    public function phone()
    {
        return $this->hasOne('Phone');
    }

    public function comments()
    {
        return $this->hasMany('Comment');
    }

}

也正是Active Record的多功能,讓entity的建立如此迅速。

公司domain所需的商業行為,與entity相關的動作都是寫在此處。

class User extends Eloquent {
    // ...
    public function addComment()
    {
        // ...
    }
}

Repository類別

封裝query logic,提供取得entity(一或多個)的不同方法。

不論是Rails的Active Record還是Laravel的Eloquent,都有提供查詢(query)的功能。

舉例來說,想要拿出新舊排序過的精選文章,可以這樣寫:

Article::where('featured', true)
            ->where('status', '=', Article::STATUS_PUBLISHED)
            ->orderBy('published_at', 'desc')
            ->get();

但是這幾行程式碼很可能常常用到,會到處重複。

這時就可以用repository封裝這段code:


class ArticleRepository
{
    public function getFeaturedArticles($count = 3)
    {
        return Article::where('featured', true)
                           ->where('status', '=', Article::STATUS_PUBLISHED)
                           ->orderBy('published_at', 'desc')
                           ->take($count)
                           ->get();
    }
}

以這段封裝來講,不但讓controller內的code可讀性提昇,同時還提供取出不同數量的參數功能。

// in controller
$repository = new ArticleRepository();

$articles = $repository->getFeaturedArticles(10);

ARCA內的repository都依賴Active Record提供的query實作,

所以建立一個abstract class再讓所有repository都繼承它是一個好主意。

參考這份EloquentRepositoryArticleRepository實作。

Presenter類別

封裝與business logic無關,只與呈現(presentation)相關的logic。

entity相關的名稱、金額、日期,常常在不同地方有不同呈現格式。

將這些寫成entity的function可不是好主意。

這類presentation logic可以獨立成presenter類別。

(或稱View Objects…。注意:軟體名詞在不同社群的用法極度混亂。)

概念上只是將entity塞進presenter,也就是decorator pattern

實作上可以依不同語言、框架的特色發揮創意。

像是這套善用了PHP特性,讓presentation 相關的code明確分離。

Presenter長這樣:

class UserPresenter extends Presenter {
    public function fullName()
    {
        return $this->first . ' ' . $this->last;
    }
}

接著可以從entity直接使用presenter實體,呼叫presentation logic。

$user->present()->fullName

Form類別

處理使用者從HTTP表單填入的資料,封裝驗證(validation) logic。

web application少不了大量的運用html表單與使用者互動。

針對user input的驗證 logic(例如字串長度、日期、金額大小)可以抽出來成為Form類別。

(在Laravel 5稱為Form Request,在Rails社群稱為Form object

原理上來說,就是將表單傳來的參數傳給Form類別,在裡面驗證過後才決定是否執行下一步驟。

舉例來說,這件事若在controller內進行,看起來就像:

$form = new ArticleForm( Input::all() );

if ( ! $form->isValid() ){
    return Redirect::back()->with( [ 'errors' => $form->getErrors() ] );    
}

$article = new Article( Input::only('title', 'content', 'status') );

$article->save();

而Form類別的實作則依照語言/框架的不同各自發揮。

舉例:Rails可用VirtusLaravel可應用Validator

Service資料夾

擺放跨domain概念的程式碼。

多個service類別會放在此資料夾。

Service類別

概念上不屬於任何entity的business logic可以獨立成service。

通常是施加在多個entity上(來自不同domain)的複雜行為。

舉例來說,計算訂單的折扣與售價,通常會涉及訂單與禮券,並且運算邏輯較為複雜:

class PriceService
{
    public function calculate(Order $order, Coupon $coupon)
    {
        // ...
    }
}

使用時,將entity傳進去即可:

// in somewhere, maybe a controller

$order = /* get the order */;

$coupon = /* get the coupon */;

$service = new PriceService();

$service->calculate($order, $coupon);

Operation Model資料夾

圍繞某種行為概念的程式碼可以整理起來、放在一起。

當service開始依賴其他service時,代表這項行為開始變得複雜、開始造成理解的困難。

試著看清是哪個概念(這很不容易)、以它命名(也不容易),建立出Operation model。

舉例來說,電子商務公司提供報價會同時涉及「禮券折扣」、「出貨日期」、「總金額」的計算。

也就是QuotationService(報價)會用到DiscountService(計算折扣金額),

DueDateService(計算出貨日期), PriceService(計算總金額)。

這時可以將它們從Service資料夾移出、建立一個Quotation資料夾、再全部丟進去。

丟進去之後可以進行適當的refactoring,增加這幾個類別間的cohesion,降低之後理解的困難。

也可以進一步使用Facade Pattern封裝這些類別,建立一個統一對外的接口,

降低其他地方的程式碼與這個Operation Model的coupling。

以上面報價的例子來說:

class QuotationManager // 我決定將它直接視為某種管理員,不再視為單純的service
{
    
    protected $due;
    
    protected $price;

    protected $discount;
    
    public function __construct(DueDateService $due, PriceService $price, DiscountService $discount)
    {
        $this->due = $due;
        
        $this->price = $price;
        
        $this->discount = $discount;
    }
    
    public function calculate(Order $order)
    {
        // 可能依序執行某些business logic,也可能只是直接呼叫底下的service
        /*
        $this->due->calculate($order);
        
        $this->price->calculate($order);
        */
    }
    
    public function discount(Order $order, Coupon $coupon)
    {
        // 可能依序執行某些business logic,也可能只是直接呼叫底下的service
        // $this->discount->calculate($order, $coupon);
    }
   
}


結語

討論架構時,通常只討論概念,不將檔案結構列入討論。

我認為那樣的討論讓很多人有看沒有懂,所以將檔案結構一併納入ARCA說明了。

ARCA不是一種理論或是無敵的架構,只是一種用來搭配Active Record,

將常用元素(或說是Design Pattern)組合起來的一種方式而已。

可以將ARCA架構視為一種適合新創公司快速開發的、通用的中型基本架構。

隨著情況不同,做出調整、增減元素(例如好用的Factory類別)即可。

有任何疑問,或是有推薦的好用Design Pattern,覺得適合加進ARCA嗎?

歡迎在下方留言,或是在 Twitter @howtomakeaturn 和我討論。


Q&A

Q1: Service類別的例子怪怪的…明明可以寫進其中一種entity啊!像是這樣就可寫進訂單entity:

class Order extends Eloquent
{
    public function calculatePrice(Coupon $coupon)
    {
        // ...
    }
}

何時放在entity,何時獨立成service呢?

如你所說,一項行為究竟是否足夠「複雜」到需要獨立出來,的確屬於開發者的主觀判斷。

但是認定所有行為都不「複雜」的話,entity很容易變得極度肥胖喔。

Q2: 我覺得用Active Record會讓測試很難寫,有解法嗎?

這確實是Active Record的一大缺點:在測試時很難不去碰到database。

使用快速建立測資的套件,似乎是唯一能勉強接受的方法。

Ruby有factory_girl,PHP有Factory Muffin

參考看看吧。

(Photo via SuperCar-RoadTrip.fr, CC licensed)