0%

在我的接案生涯當中,曾經試著尋找類似「榜樣」或說「態度」的東西。

如果有個長期目標、或說工作信條的話,只要長期朝它邁進,就會越來越好,那該是什麼呢?

提供客戶最好的服務、最高的CP值、讓客戶最滿意,如何?

首先我直覺上想到這樣的標語。

對於接案來說,實務上卻相當不可行。這只是在盡全力壓榨自己而已。

因為客戶最想要的就是價格便宜、服務快速、隨傳隨到。

收費打對折、更積極地加班、全年無休 on-call,哪個客戶不想要這種服務?

如果拿這種標語當作接案的態度,那麼只會把自己累死而已,恐怕不能當做長期職涯態度。

最耐心的溝通、最禮貌的態度、永遠讓人感受到友善,如何?

這是另一個我也曾經嘗試過的信念。

很快就發現,在實務上這同樣不可行。

首先,每個人對於價格的理解天差地遠。你提供的服務,收費可能是某些人期待價格的2倍~10倍。

這種情況下,光是說出金額,已經讓有些人「聽到就很有挫折感」、「大吃一驚」了。

甲方乙方,完全沒有必要在花了一整個下午,詳談過專案內容、功能規格、天南地北閒聊之後,才發現兩邊價格沒有交集,這是不可能成交的合作。

為了避免浪費雙方時間,如果你習慣在很一開始就先說明收費價格、建立初步共識才答應碰面開會,那麼難免有人會覺得「你很難搞」、「你很驕傲」、「你冒犯到他」。

除此之外,合作過程中意見分歧難免,即便是就事論事,有些時候為了把事情釐清、表明立場,言語上直來直往,難免讓對話有點緊繃。

為了有效清楚的溝通,這種狀況根本沒什麼大不了的,更無需為了保持友善親切,降低對話的效率。

更不用說,有些人來者不善,從一開始就是來凹免費東西、凹免費情報的。

有些人樂意在談判中吃人不吐骨頭,簽約前最後一刻,告訴你金額要扣東扣西的。

有些人說的比唱的好聽,但是在合作磋商的最後階段,拿一份敵意超強的合約給你簽。

這種時候你保持心平氣和,冷靜但是果決的離開談判桌就行了。

你想要保持禮貌、友善、避免讓對方不開心,那就有點沒必要了。

話雖如此,但總是要有個類似榜樣的東西,讓自己在不同狀況下可以快速判斷「該採取什麼立場」。

該怎麼辦呢?

我是一台飲料販賣機

說來奇怪,我最後找到的東西是…飲料販賣機。

這個比喻是這樣的:

一台飲料販賣機,在營業時間,投入正確的金額,就會拿到正確的東西。

沒有意外,也不會有預期之外的事情發生。

沒了,就這麼簡單。

多少客戶在發案之後,收尾的階段才發現接案公司做錯了?整個專案失敗?對方跟你說「我不知道」、「你一開始沒講清楚」。

別擔心,跟飲料販賣機交易,按下飲料,拿到的不會是餅乾。按下可樂,出來的不會是白開水。

你跟販賣機上次意見分歧,鬧得不太愉快、氣氛緊張。沒關係,下次投幣,預期的金額,依然會在預期的時間,拿到預期的商品。

你張牙舞爪地來跟飲料販賣機談判,想要搞定機器、想要打對折的價格,同樣也是辦不到的。

如何當好一個飲料販賣機呢?

首先要讓價格與各種成本足夠透明,不要在奇奇怪怪的地方突然要求客戶額外的費用。

再來要做到對客戶的「期望管理(expectation management)」。

絕對不要過度承諾、對時程過度樂觀,客戶有誤解、不懂、低估難度/時程/成本的地方,要儘早、清楚地說清楚。

溝通再溝通,最好同時提出不同的可行解決方案,或是在聰明的地方協助客戶節省。

通常對方都是可以理解的!也會讓工作內容越來越合理。

接著是以多個短期程的交付,取代一個長期程的大交付。

專案分階段交付、先提供草稿、系統規劃,或是什麼東西都好。

客戶第一次看過飲料機掉出來的東西,就大概知道後面幾次會掉出什麼了。

收了一大筆錢,工作很長時間,販賣機才掉個大東西出來,最後這東西跟客戶預期的不同、感到失望的機率會增加很多。

最後是保持誠信、絕對要保持誠信、從頭到尾都保持誠信。因為跟販賣機交易就是最單純、最有誠信的商業行為。

雙方態度不佳、價值觀落差、工作方式有摩擦,通通都是次要。只要雙方保持誠信,其餘通通都是小事。

結語

在工作上我非常難搞、對於某些工作方式異常堅持、很少接任何人電話、任何訊息動輒數小時之後才回覆、大部份合作洽詢都是直接拒絕。

自從我以飲料販賣機當成比喻、做為 role model 來工作,很多事情簡單多了。

因為對一台專業的飲料販賣機來說,這些通通都不重要。

重要的是,顧客投幣前就知道價格,也知道投幣之後就是會拿到預期之中的成品。

雖然我大部份的時候都是「時間已售完」、「補貨中」,根本沒辦法投幣。

但對於絕大部份客戶來說,跟販賣機交易還是相對單純的事情。

所以各位不管是不是全職的自由工作者,下次接案時不妨試試看這種心態,說不定會有幫助。

接案有很多種報價方法,不管哪種都需要評估自己收費的「時薪」。

我之前介紹的:軟體工程接案技巧:「週薪式兼職」報價 ,會需要評估時薪。

或是常見的「固定價格式」報價,也是以時薪乘上預計工時來報價。

那麼這個時薪該如何決定呢?今天跟大家簡單聊聊。

上班族的時薪

假設你是一個月薪 40,000 元的工程師,那麼每月工時應該在 8x5x4 = 160 小時左右。

也就是時薪大約 40,000/160 = 250 元

但在接案時,千萬不能直接拿這個時薪出去報價。

上班的時候,公司至少還會額外負擔以下成本:

  • 勞退:雇主提繳率不得低於6%

  • 勞保:勞保費率用11.5%計算,而一般公司行號勞保費用雇主:勞工:政府分擔比例=7:2:1,也就是雇主會負擔 11.5% x 0.7 = 8.05%

  • 健保:健保費率用5.17%計算,健保費用雇主:勞工:政府分擔比例=6:3:1,也就是雇主會負擔 5.17% x 0.6 = 3.102%

所以公司額外幫你出的費用至少就 6% + 8.05% + 3.102% = 17.152%

接案的話這些通通都沒有,要自己處理。

另外,如果上班有1個月年終可以拿,當成上班的預期收入,在接案時沒有年終,於是平均到每個月,時薪就要多加 13/12 - 1 = 8.33333%

(註:這邊是每月結帳一次的情況。如果跟客戶是每4週結帳一次,這個 8.3333% 可以不算。)

17.152% + 8.33333% = 25.4853%

所以拿上班月薪回推的數字至少要加上25%才對。

更別提公司會有的福利、三節獎金、特休、半薪病假、育嬰假、生理假,等等諸如此類的,接案時通通都沒有。

除此之外,上班會預期每個月有穩定收入,公司如果沒事就要你走人,會有資遣的費用。

但是接案的收款變數比較多,有時甚至會有週轉的問題,畢竟接案本就比上班更不穩定。

還有上班是用公司的水、電、網路、冷氣,接案是用家裡的,或是要花錢去咖啡廳。

這些額外的成本加起來至少也該算15%左右。

所以拿月薪回推的數字,應該至少要加上40%,也就是 x1.4 才有意義。

接朋友、親戚的案子、或是很信任的客戶時,可以 x1.4

陌生客戶的話,直接 x1.5 更簡單一些。

如果是急件、技術門檻特高、或是超無聊不太想接的案子,以 x1.6 到 x2.0 去估時薪也很正常。

當然如果真的急需用錢、非接不可、或是毫無信心,那就 x1.25 可能撐一下也還行吧!

如果用低於 x1.25 在接,那麼不覺得找一份工作、好好上班比較划算嗎?

簡單的計算對照表

讓我們簡單計算幾個數字

  • 月薪40,000的工程師 40000/160*1.4 = 350

  • 月薪50,000的工程師 50000/160*1.4 = 437.5

  • 月薪60,000的工程師 60000/160*1.4 = 525

  • 月薪70,000的工程師 60000/160*1.4 = 612.5

  • 月薪80,000的工程師 80000/160*1.4 = 700

所以一個年輕的工程師,如果用時薪300元接案,其實不太划算。

別忘了每次接不同案子都需要跟不同團隊磨合,這過程是很累的。

不如白天專心工作、報答公司,也學習成長得比較快。

所以一個資深的工程師,如果用時薪400元接案,其實相當不划算。

拿那些時間去陪家人、朋友,比較值得。

結語

本文描述的數學架構,重點是提供一個簡易的計算邏輯給大家,也盤點、提醒一下,可能忽略沒注意到的成本。

實際上根據報價方式、收款方式、評估工時的方式,到底是乘以多少倍數,可視情況決定。

很多年輕的專業人士,出於害羞,普遍用錯誤的時薪在報價。

常見的結果就是,你做一陣子會發現:怎麼越做越累?感覺比上班還難賺?

錯誤的接案時薪,根本做不了長久。越做越不爽、最後案件爛尾、跟客戶兩敗俱傷的狀況多的是。

本文所說,常常沒注意到的 x1.4 x1.5 的成本倍數,就是公司額外花在你身上的成本。

所以,在接案的時候,也記得要把這些成本考慮進去。

最近接網站開發的案子,特別是新創團隊的案子,我發現用週工時的兼職方式報價,比報固定價格的傳統接案方式好很多。

報價方式會像這樣:

  • 接案者每週工作 N ~ M 個小時
  • 每週收費為新台幣 X 元
  • 預估案件大概 Y 週會做完
  • 每 Z 週結帳一次

N、M、X、Y、Z 的原理如下:

  • 根據案件的困難程度、急迫程度來決定 N、M、X
  • 要讓案主知道估計的總花費,所以要估一個 Y
  • 接著由雙方的互信與方便程度決定 Z

這種報價方式看似含糊曖昧、對雙方來說都有風險。實際上卻正好相反,它比傳統報價方式符合雙方需求得多,也比較符合現實狀況。

舉例來說,我最近一次替新創團隊做網站的接案對話如下。我先說明報價方式:

接著補充說明一些:

案主很爽快的答應了,後續也進行得非常順利。這種兼職方式解決了傳統報價方式會遇到的3個問題:

  • 需求與規格不必在事前定得非常清楚
  • 消除案主的訂金恐懼、消除接案者的尾款恐懼
  • 以連續多個較小的承諾培養互信,取代一次性的大承諾

需求與規格不必在事前定得非常清楚

軟體工程幾乎不可能準確規劃規格與細節,因此案件總金額、總工時根本估不準。

按照傳統的方式,雙方只能各憑經驗,討論出一個價格。這價格簡直是兩邊各猜一個數字之後妥協的,一點都不準,價格很少是公平的。常常導致接案者覺得自己被凹了,或是是案主覺得自己被當肥羊宰了。

其實,對很多案件來說,特別是新創團隊的案件,這種固定價格式的接案本來就不合理。

現在創業的商業方法論,講求執行、靈敏。趕快先開始,過幾天有新想法、新功能想做,再調整開發的優先順序。需求與規格本來就會跟一開始不同。

接案者要案主一開始就把規格訂得非常清楚,實在是強人所難。

而這種情況下,案主在一開始就想確認一個絕對數字的預算,也是強人所難。

除此之外,光是為了報價,就必須花一堆時間確認規格、需求。這些開會討論的時間成本很高,卻又不可能跟案主說「我給報價是要收費的」。

於是接案者為了保護自己,會演化出一種習慣:防禦式報價。

那就是把情況都往壞的方向預想,一律只給偏高金額的報價。這對整個接案市場都不是什麼健康的事情。

週薪式兼職不會出現防禦式報價。只要大致溝通一下需求,接案方大概了解情況,馬上就可以開始動工。

消除案主的訂金恐懼、消除接案者的尾款恐懼

對案主來說,什麼都沒拿到就要先付訂金,還經常是總額的1/3到1/2,壓力不小。

對工程師來說,一大部份費用卡在尾款,還有可能一拖再拖,結不了案,同樣可怕。

每 Z 週結帳一次,雙方觀察對方的時間多了許多,不用再賭人品。

以連續多個較小承諾培養互信,取代一次性的大承諾

傳統接案方式,雙方可能從頭到尾都彼此怕怕的,因為兩個沒合作過的人,要在一開始就互相綁定一個大承諾,也不知道案件會不會進行順利、對方人品到底怎麼樣。

而週薪式兼職,對案主來說,最糟的情況就是幾週之後工程師擺爛失聯了,損失幾週的錢。就算這樣也比傳統報價方式搞砸時,損失50%總金額的訂金好。

對工程師來說,最糟的情況就是定期結帳時,案主不付錢失聯了,損失幾週的錢。就算這樣也比傳統報價方式搞砸時,損失50%總金額的尾款好。

這種方式不但可以逐步培養互信,也對溝通很有幫助。

因為雙方每 Z 週必須真實面對彼此一次(付錢/要錢的時候,就是真實面對彼此的時候)。有什麼誤會都會被迫儘早說開,不會拖拖拖好幾個月,到要結案才發現雙方距離彼此期待差超多。屆時的誤解跟憤怒都會高到無法溝通了。

結語與經驗分享

本文一開始提到的接案經驗,雖然規模很小(我1人+案主方2人參與),但是執行得非常順利、非常愉快。連一開始的報價我都只看了一份 UI 的 pdf 檔一眼,5分鐘就給出報價。

我們甚至連合約都沒簽,隔天就開工,只花費 4 週就完成了這個案件。每週見面1-3次,搭配頻繁的線上討論,總金額新台幣 48,000 元。一點浪費時間的感覺都沒有,是一次非常理想的商業合作經驗。

下次接案、發案時,碰到傳統接案方式的困境的話,不妨以這種方式接案、發案,根據個別情況調整一下 N、M、X、Y、Z 參數即可。

傳統合作產生的商業糾紛,或許只是 pricing model 的問題而已。

附註:為什麼工時是區間、不是定額?

跟案主見面開會完之後的閒話家常,算不算工時?
工程師通勤去開會的時間,算不算工時?
工程師吃晚餐、出門散步的時候腦中在想演算法,算不算工時?
案主要求的技術工具太有趣,工程師自行額外看了一堆進階文件,算不算工時?

每件小事都要定義清楚算不算工時,有點不切實際,對雙方來說都很煩,而且斤斤計較這些東西有點傷感情。區間讓案主、接案者雙方都更自在。

不如承認這點曖昧性,兩邊都知道會有彈性,彼此守住一些議價的立場就好了。而且是區間的話,你也不用一直拿手錶計時了。每週抓固定幾個時段工作就差不多了。案主也不用每次開會要一直看手錶了。

除此之外,區間工時可以保護接案者的時薪高低的彈性。未來再接案時,你會有立場在 X/N 到 X/M 附近的範圍報價。

(完)

(Photo by Maya Maceka on Unsplash)

「為甚麼要使用前端框架?直接用 jQuery 寫完前端不行嗎?」

很多新手 web developer 會問這個問題。

其實在很多情況下,直接用 jQuery 是沒什麼問題的。

至於什麼情況下可以?什麼情況下不行?

這就需要有 business state 與 UI state 的觀念來幫助判斷。

business state 與 UI state 的定義

不論 html / css 也就是 UI 外觀如何,在任何頁面的背後,都會有類似這樣的物件,可以表達目前頁面「商業邏輯會用到的狀態」

1
2
3
4
5
6
7
8
9
10
11
12
13
{
items: [
{
name: 'product 1',
price: 100,
},
{
name: 'product 2',
price: 300,
},
]
sum: 400
}

這種「狀態」我稱之為 business state。

以上是一個購物網站頁面常常有的 business state。

與之相關的,在這種頁面會有這樣的 html /css

1
2
3
4
5
6
<div>
<div>Product 1: $100</div>
<div>Product 2: $300</div>
<hr />
<div>You total cost: $400</div>
</div>

這種「畫面上看到的狀態」我稱之為 UI state。

business state 與 UI state 的關係

完全新手在寫網頁的時候,可能根本沒寫到 business state 的那種 json object。

但就算你只有寫 html / css,其實背後也有隱含著 business state,只是每次你需要的時候都直接去 DOM 裡面 parse 出來而已。

當一個頁面的 business state 與 UI state 是 1-to-1 關係的時候,其實用 jQuery 快速寫完這頁面就可以了。

前端網頁開發麻煩的地方在於,很多時候他們不是 1-to-1 的關係,而是 1-to-many。

同樣的購物車 business state,在畫面上可能有 navbar 的彈出式購物車,同時還有畫面中央的商品庫存數量要跟著增減。

當這類的關係複雜起來的時候,如果只使用 jQuery,那在每次更新 business state 的時候,還要記得去更新很多地方的 UI state。

互動情況複雜的時候,很容易就會出現 bug,所以在這種情況,就會需要前端框架的幫助。

One way data flow

前端框架至少會提供「One way data flow」的功能,也就是你只要把 business state 整理好、準備好、顧好,再講清楚 business state -> UI state 的對應,框架就會自動幫你把 html 自動更新。

我曾經對此寫過一篇詳細的跨框架分析比較文章:

簡單聊一下 one-way data flow、two-way data binding 與前端框架

結語

業界很多網頁的狀況其實很單純,business state 與 UI state 都是很小、很單純的 1-to-1 對應。

這種情況下,根本不需要用到前端框架,切記不要為了用而用一個工具。

當代前端框架的功能遠不止於此,但是 business state 與 UI state 的觀念卻是每一個前端人必備的觀念。

在判斷一個中小型 web application 需不需要引入前端框架的時候,這個觀念也會有非常大的幫助。

身為一個年輕熱血又有一些專業技能的你,在找新工作的時候,遇到了一個好像很厲害的前輩。

這個前輩感覺資源很多、人脈很廣、很會談判,他對你說:「我很看好你,要不要一起創業?你薪水拿一點就好,股權給你多一點。」

你心動了,但又覺得好像哪裡怪怪的。

偷偷跟你說,這等於是你跟陌生網友約碰面,他跟你說:「我很欣賞你,我喜歡你,要不要結婚?」

意思差不多。

問自己兩個問題

不論今天是考慮跟幾個人一起創業,問自己兩個問題:

一、我有沒有一股難以言喻、理性難以解釋的熱情,就是很想跟這些人一起工作?

二、我有沒有一股難以言喻、理性難以解釋的熱情,就是很想動手解決這個問題?

如果兩個答案都是 yes

恭喜你,可以認真評估、嚴肅考慮,答應這門婚事。

你可以完全不拿薪水,真正試著去當合夥人。

這發生機率大概 5% 左右吧。

如果其中一個答案是 no

95% 的情況下,應該會有其中一個答案是 no。

如果對方是親戚、朋友之類的話,那麼相當高的機率,非常傷感情的事情會在之後發生。

除此之外,在很多案例中,其實就是有人想要免費使用你的專業技能,不想付錢而已。

通常過不了多久就會發現自己被耍了。在業界一些最慘的案例中,後面都是雙方官司纏身結尾。

可能的合作方式

專業合作不是只有接案跟合夥兩種。

如果還是有點心動,覺得拒絕這次合作有點可惜,那麼可以考慮一種合作方式:

就是兼職接案,但是收費打折,半年後再一起認真評估一次。

以下是報價範本:

1
2
3
4
5
6
7
- 平常接案是以時薪 X 元計算
- 這次合作以 X 元直接打四折~七折,抓一個數字
- 每週為了這專案固定工作 N 小時左右
- 每 4 週結帳一次
- 先合作半年,完全不佔股份,先預告半年後,會索取 5% ~ 15% 左右的股份(根據時薪是打幾折)
- 這半年間,如果其中任何一人不開心,那就合作結束,結帳走人
- 半年結束之後,如果雙方合作愉快,重新談一次 deal,相關數字全部可以重談

以上數字參考就好,實務上根據雙方狀況,大家覺得公平就好。

要注意這種合作方式,基本上很難寫合約喔。

所以通常是無合約、雙方有相當互信才這樣做。

其實就是介於接案跟合夥之間,一種雙方折衷風險的合作方式。

如果結婚的前提是先交往一段時間,跟人創業不用先一起相處看看嗎?

結語

某人隨隨便便就來問你要不要一起結婚,這麼瞎的事情,首先你根本就不應該答應。

如果有人真的答應了,可以想想看,這種婚姻,是否可能有好結果。

所以這類情境我一律只給以下建議:

如果有人問你要不要一起創業,那跟他問你要不要一起結婚,意思差不多。

在寫 SEO/SMO 相關程式碼的時候,我發現很多使用 laravel/php 的開發者會在 layout 相關檔案這樣寫:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@if (isset($product))
<title>{{ $product->name }}產品資訊</title>
<meta name="description" content="{{ $product->description }}">

<meta property='og:title' content="{{ $product->name }}產品資訊">
<meta property='og:description' content="{{ $product->description }}">
@elseif (isset($category))
<title>{{ $category->name }}類別資訊</title>
<meta name="description" content="{{ $category->description }}">

<meta property='og:title' content="{{ $category->name }}類別資訊">
<meta property='og:description' content="{{ $category->description }}">
@elseif (isset($user))
<title>{{ $user->name }}用戶資訊</title>
<meta name="description" content="{{ $user->description }}">

<meta property='og:title' content="{{ $user->name }}用戶資訊">
<meta property='og:description' content="{{ $user->description }}">
@else
<title>歡迎來到 blah 購物網站!</title>
<meta name="description" content="亞洲最優質購物網站!">

<meta property='og:title' content="歡迎來到 blah 購物網站!">
<meta property='og:description' content="亞洲最優質購物網站!">
@endif

這種寫法對於小網站還行,網站稍大就會發現幾個明顯的問題

  • layout 檔案打開,想要看個排版,卻要先看上百行的這種 seo/smo 程式碼
  • 有時會希望某個 controller 底下的不同 function 回傳不同的 seo/smo meta tag,希望可以在 controller 內設定
  • 有時會希望某個 controller 底下如果什麼都沒設定,預設也不要傳跟首頁一樣的 meta tag,讓 seo/smo 至少可分出大方向

古典的 singleton pattern 被許多開發者忽視許久。其實在這邊可以派上用場。

以 laravel 為例,以下是我在所有專案都會使用的程式碼

layout.blade.php

1
2
3
4
5
6
7
8
9
10
11
<title>{{ AppCore::getOpenGraphTitle() }}</title>
<meta name="description" content="{{ AppCore::getOpenGraphDescription() }}">

<meta property='og:title' content="{{ AppCore::getOpenGraphTitle() }}">
<meta property='og:description' content="{{ AppCore::getOpenGraphDescription() }}">
<meta property='og:image' content="{{ AppCore::getOpenGraphImage() }}">

<meta name="twitter:card" content="photo" />
<meta name="twitter:title" content="{{ AppCore::getOpenGraphTitle() }}" />
<meta name="twitter:description" content="{{ AppCore::getOpenGraphDescription() }}" />
<meta name="twitter:image" content="{{ AppCore::getOpenGraphImage() }}" />

config/app.php

1
2
3
4
'aliases' => [
...
'AppCore' => App\AppCoreFacade::class,
],

app/AppCoreFacade.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php

namespace App;

use Illuminate\Support\Facades\Facade;
use App\AppCore as RealAppCore;

class AppCoreFacade extends Facade
{
protected static function getFacadeAccessor()
{
return RealAppCore::class;
}
}

app/AppCore.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?php

namespace App;

class AppCore
{
protected $openGraphTitle = null;

protected $openGraphDescription = null;

protected $openGraphImage = null;

function setOpenGraphTitle($title)
{
$this->openGraphTitle = $title;
}

function setOpenGraphDescription($description)
{
$this->openGraphDescription = $description;
}

function setOpenGraphImage($image)
{
$this->openGraphImage = $image;
}

function getOpenGraphTitle()
{
if ($this->openGraphTitle) return $this->openGraphTitle;

return '歡迎來到 blah 購物網站!';
}

function getOpenGraphDescription()
{
if ($this->openGraphDescription) return $this->openGraphDescription;

return '亞洲最優質購物網站!';
}

function getOpenGraphImage()
{
if ($this->openGraphImage) return $this->openGraphImage;

return url('/ms-icon-310x310.png');
}
}

如此一來,seo/smo 的設定就變成在各個 controller 內決定了!

ProductController.php

1
2
3
4
5
6
7
function viewProduct(Request $request)
{
AppCore::setOpenGraphTitle($product->name . '產品資訊');

AppCore::setOpenGraphDescription($product->description);
...
}

可以在不同的 controller 內,寫各種複雜的判斷邏輯都沒關係!

也可以在 constructor 內設定適合的內容,對相關頁面做出初步的 seo/smo,接著一層一層覆蓋、做出更精準的 seo/smo 即可!

CategoryController.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class CategoryController extends Controller
{
function __construct()
{
AppCore::setOpenGraphTitle('分類瀏覽 - blah 購物網');

AppCore::setOpenGraphDescription('更方便的搜尋商品');
}

function viewCategory(Request $request)
{
AppCore::setOpenGraphTitle($category->name);

AppCore::setOpenGraphDescription($category->description);

...
}

function viewCategoryTag(Request $request)
{
// even I didn't setup the seo/smo, I still have better tags
// rather than just return homepage tags!

...
}
}

結語

Singleton Pattern 如果濫用,會出現一些像是 global state 的糟糕情況,讓程式碼難以維護。

但是 Singleton Pattern 在建構網站的時候有很多很棒的使用場景,本文的例子只是一個起點,你一定會找到其他也很適合的使用場景。

近幾年跟不同團隊合作,我發現大家似乎都不再使用 global functions 了。

前人的經驗告訴我們,濫用 global functions 會讓程式碼變得很難維護。

大家有改過 wordpress 的 source code 嗎?

滿滿的 global functions。非常難維護,但是功能強大。

其實,只要稍微注意一些原則,適當的使用,還是非常好用的。

以 Laravel 為例,內建有提供一堆好用的 global functions

https://laravel.com/docs/5.8/helpers

在講求開發速度的現代,這種寫了就用的簡單函式,當然也可以自己多寫幾個。

我在使用 Laravel 開發的時候,所有專案都會在 composer.json 加上這段

1
2
3
4
5
6
"autoload": {
...
"files": [
"app/helpers.php"
]
},

然後在 helpers.php 裡面就可以快速寫點輔助函式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

// NOTE @howtomakeaturn: if you just want to find a place to write
// some functions quickly, this is the place!
// you can always refactor them later!

function testFunction1()
{
return 'test 1';
}

function testFunction2()
{
return 'test 2';
}

在兩種情況下,這個檔案特別好用

1. utility function 輔助函式

字串處理、陣列處理,之類的,目的單純、會常常用到的函式,放這裡非常適合。

或者你就想放一些 global shared 的 value,但是懶得使用 framework 的 config 機制時,放這也很適合。

2. 沒時間做好 abstraction 抽象化,但又想找地方放、才有重用性

有些邏輯,一時想不清楚該放到哪個類別、使用什麼 design pattern。

這種時候,隨便寫個 function 就是了。之後要重構也很方便。

不要小看這種寫法,在古早時代,整個系統都是這樣寫的。稍微寫一些也無妨。

那麼有什麼注意事項呢?

不要有 state,不要有 side-effect

讓函式單純一點,每次傳同樣 input 都會得到同樣 output。

除此之外,不要有 side-effect。也就是 function 內不要去更新到 function 外部的 state。

讓 call 這些 function 成為一件單純的事情。

結語

如果不太確定哪些邏輯適合寫成 global functions,參考 laravel 的 helpers 就對了。

適當的寫點 function 來幫助建構你的系統,還是非常好用的,沒必要完全不寫。

前陣子寫了一則簡單的 PR 改善提案,給合作的其中一個技術團隊。

順手分享一下,或許會有幫助。

當前問題

現在發出 PR 之後,幾乎只有 author 跟 reviewer 會互相討論,然後就 merge 了。

其他人可能也對各種 PR 有興趣,但是沒辦法參與討論。因為:

  1. author 創建 PR 的時候腦中只想著 reviewer
  2. 所以其他人打開 PR 不太確定「正在解決什麼問題」
  3. 其他人打開各種 PR 的時候,需要都 git pull 到本機然後 db migrate 才有辦法知道 PR 內容

改善目標

希望發出 PR 之後,讓更多的 developers 有機會參與討論。

不只是 author 跟 reviewer 兩個人互相討論就結束。

讓發 PR 變成一種「分享我的作品」「歡迎大家來欣賞」「一起給點意見」的感覺。

改善方法

一、要讓看 PR 的人不用 git pull 就有信心可以按下 merge

發 PR 的人請在 PR 內附上簡單幾張「螢幕截圖」或者「螢幕錄影」,稍微證明一下這個 PR 不是亂寫的即可。

二、要讓看 PR 的人不用四處打聽、詢問,就知道這個 PR 的目標

發 PR 的人請在 PR 內寫清楚這個 PR 在解決什麼問題。

跟這個 PR 相關的連結、討論文件會議記錄、手寫筆記拍照,都可以一併附上來。

三、責任的主要歸屬

發 PR 的 author 是需要對 PR 主要負責的人。不管有多少 developer 七嘴八舌地參與討論,在 reviewer 按下 merge 之後,這些七嘴八舌 developer 跟 reviewer 只是給意見而已,主要還是 PR author 確保 PR 可靠。

範例

沒有任何公版或是格式需要套用。發揮創意達成以上效果即可。

以下隨便舉例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Summary

- this pr wants to solve blah blah
- [link to a hackmd note]
- [link to another note]
- [link to external references]
- this closes #18
- this closes #19

# Screenshots

[IMG1]
[IMG2]
[IMG3]

# Notes & Todos

- `blah blah` needs more refactoring in the future
- some technical debts exist in `blah blah`

補充說明

  • 此提案只是大方向,不需要死板執行。
  • 如果是趕時間的 hotfix 或是很小的 tweak,其實照原本方式趕快找個人 review 就 merge 即可。
  • 在 PR 比較大、這 PR 有點像心血結晶、甚至像藝術作品一樣想分享給更多 developer 的時候,再這樣做即可。
  • 實務上通常會配合 issue tracker 執行,就是 PR 內會寫明跟哪些 issue 有關。
  • 預期這會讓創建 PR 的時間增加不少。
  • 創建 PR 跟寫 code 原理類似:花越多時間去寫好,會讓這 PR 變得更 independent。沒有上下文 context 的 dependency。讓看 PR 的人可以獨立直接消化掉。
  • 實務上 open source project 就是這樣執行的。下次逛 github 的時候可以留意一下人家 PR 的寫法。

特別感謝 Bible Tang 對這篇文章的啟發。