由 howtomakeaturn 發表的所有文章

我2月12號到5月8號學到了什麼?(管理篇)

工作幾個月之後我開始想:

大家都希望工作經驗越久、薪水越高,憑什麼?

年紀越大,實力一定越強嗎?

所以我比上個月強?我比昨天強?

到底強在哪?難道只能空泛的回答:我經驗更豐富了。

於是我就開始筆記,明確的記下來,為什麼我每天都比前一天強。

寫到今天約莫三個月了,決定打成「軟體篇」、「管理篇」兩篇文章。

跟各位分享這本流水帳。

2月13號

* 不要對自己的個性或人格太滿意而沾沾自喜。其他人可能根本不喜歡你。

2月20號

* 每天起床前有兩個選擇:繼續躺著做夢,或是起來完成你的夢。

3月5號

* 想要別人付出超乎標準的能力,你需要讓他「一點不爽的感覺」都沒有才行。

可以讓他有壓力,但不能讓他有一點不爽的感覺。要讓他在工作環境感到絕對的自在,這也是主管能對屬下付出的地方,也就是管理者該做的「託付」。

因為人是不可能在不自在的地方久待、全力付出的。

況且沒人是笨蛋、會在沒被託付的情況下,將自已託付給別人。

3月8號

* 別只想著找到能「做你喜歡的事」的工作。也許能「用你喜歡的方式做事」的工作也很棒。

3月10號

* 更了解消費者行為一些了

3月12號

* 37signals公司在暑假甚至一週工作4天。

* 37signals下面這個政策就叫做他媽的行銷。又有多少人能理解。

「公司出錢讓員工去學業餘興趣有一個條件:你必須跟所有人分享你學到了什麼。不只是跟其他同事,要跟所有37signals部落格的觀眾分享。」

3月13號

* 有限的時間會鼓勵人去有效率的運用時間。

* 生產力怎麼衡量?不是一天寫幾行程式碼吧?

「我能斷言,任何軟體開發的真正生產力,只能用它帶來的商業價值去衡量。」

3月21號

* 是否只能用「激勵他的內心」去增加生產力、很難用「限制他能做的事情」去增加生產力?

* 這樣去理解Jason Fried如何當一個老闆:他就像很保護其他人的一個老大哥。

* 沒什麼急事,就放人慢慢做。永遠都是「很急」,那叫壓榨。

3月22號

* 「被禁止上網閒逛的員工,因為必須消耗意志力去克制自己,所以生產力也下降了。」

* 「很多大企業會提供健身房、遊戲室、休息室給員工。這只解決一部分的問題。遠程工作(允許在家工作)是最佳解。但真正的問題是管理者需要去信任員工,別像管小孩一樣管人。理解有時候看著窗外發呆比較有生產力、有時候跟可愛女孩調情比較有生產力、有時候傳傳簡訊比較有生產力,而不是強迫他在電腦前面敲鍵盤。改變文化、擁抱新工作方式吧。」

4月10號

* 壓榨員工,公司比較賺錢?讓員工快樂、放任,公司比較賺錢?

公司已經「知道如何賺錢」、有方向,壓榨員工的確可以擠出生產力、也就是賺更多。

公司還「不知道如何賺錢」就讓員工準時下班、取悅他、期待他快樂的工作、期待他佛心去幫老闆找到方向、找到賺錢方法。壓榨他出來的東西,大概還是無法幫公司賺錢。

4月19號

* 來了幾個實習生,讓我想幾個步驟。不如這樣:

1. 凡事回答「都好都好、OKOKOKOKOK」

2. 表明你不會管他平常在幹嘛,一切看成果

3. 要求成果、要求成果,視情況協助安排進度

4. 檢討成效、協助下次能完成目標。

不可覺得手下沒做好、下次會更好,因為你這個廢物沒資格這樣想。你自己也沒做好,失敗你負一半以上責任。(你當人手下的時候,目標失敗不也覺得主管是廢物、老闆是廢物?)你帶的人fail,你就是廢物。只覺得問題出在他不夠強?是你不會用人吧。

為什麼中途沒發現會失敗?為什麼沒看出此人實力能放任到哪?目標導向、成果導向是否只是讓人無所適從?為什麼沒給人可以出力的方向?成就感、成長感沒人不喜歡,所以奮鬥是快樂的,結果他沒快樂奮鬥,問題會不在你身上?

把人當小孩子帶、出問題責任都推給別人,絕對不會有人用全力奮鬥去回報你。

4月24號

* 「信任是這樣的:我們一開始多少都會信任別人,當別人沒有善意回報,我們的信任便會少一些。」 

4月25號

* 看看這個創辦人如何以身作則:

—  他認為介面不夠自然,於是就自己寫了程式改善。此舉讓這名比利時藉的設計師嚇呆了。「像他這樣的行為真的很容易就像病毒一樣感染員工。」

5月8號

* 「你工作要主動一點」

不要這樣講話。像在指導小孩子。

他「怎麼做」不是重點。他厲害的話,一個人毫不溝通就默默全部搞定也可以。

直接點出「績效」是如何被判斷為「不佳」。讓他理解到「不主動好像真的做不出公司要的東西」

身為管理者,一定要嚴格看待自身言行。當你發現自己似乎在對小朋友講話的時候,小心了,你正在羞辱人。

* 他領日薪,你就每天跟他計較生產力。他領月薪,你就去在乎他每個月的績效,不要每天在那邊靠北靠目。

(Photo via   Sebastiaan ter Burg, CC license)

離開JavaScript新手村:Module Pattern

最近在開發「按讚」功能,也就是類似對商品列表「按讚」或是「加入我的最愛」之類的功能。

(本篇不討論server side與database實作,僅討論client side的JavaScript)

按讚的對象是衣服,於是我先開份js檔:

// clothing.js

// 我知道直接寫整串程式邏輯不好,一大團難以理解
// 那我用function把各小功能包起來好了!

// 先替按鈕加上click event,點了之後就送出ajax,
// 把這個衣服加入我的最愛
function setupToLikeButton(){
    // some codes
    $('.to_like').click(function(e){
        // some codes
    });
}

// 再來替已經按讚過的東西加上click event,
// 點下去就送出ajax,從我的最愛移除
function setupLikedButton(){
    // some codes
    $('.liked').click(function(e){
        // some codes
    });
}

// 大功告成,寫個「初始化」功能
// 正式執行clothing.js這個偉大的模組
function registerLikeButtonEvent(){
    setupToLikeButton();
    setupLikedButton();
}

然後在所有需要「按讚」功能的頁面,加入這些程式碼:

<script src="/assets/js/jquery.js"></script>
<script src="/assets/js/clothing.js"></script>

<script type="text/javascript">
    $(document).ready(function(){
        registerLikeButtonEvent();
    });
</script>

寫完之後,再看了一眼,一股涼意上心頭,覺得這些程式碼真的很可悲。

  • 這只是把一串js code,用function隨便包一包、把功能隨便分開而已
  • 在所有需要「按讚功能」的頁面加上一行registerLikeButtonEvent(),感覺不是模組化(一點也不物件導向),依然停留在procedural programming(這邊執行一串code、再跳過去執行那邊一串code,有bug的時候光tracing code就累死人)
  • setupToLikeButton跟setupLikedButton可能會被誤用。應該禁止在registerLikeButtonEvent以外的地方執行(有點像private method)
  • 多了setupToLikeButton、setupLikedButton、registerLikeButtonEvent三個global function,感覺就是很不爽

我對這串code很不滿意、覺得應該有更好的pattern。

立馬上網買三本書

  • JavaScript 設計模式
  • JavaScript大全(第六版)
  • JavaScript:優良部分

其中「JavaScript 設計模式」果然是開卷有益。下面就是應用Module Pattern之後的寫法。

// clothing.js

// 先做出一個命名空間,讓變數名稱留在local
var LikeButtonModule;

LikeButtonModule = (function(){

    // 提示使用者這個模組跟jQuery有相依性
    if (!window.jQuery) { throw new Error("LikeButtonModule requires jQuery") }

    // this line declares the module dependency and
    // gives the global variable a local reference.
    // 這行可以增進效能
    var $ = window.jQuery;
    var _setupToLikeButton = function(){
        // some codes
        $('.to_like').click(function(e){
            // some codes
        });
    }// end _setupToLikeButton

    var _setupLikedButton = function(){
        // some codes
        $('.liked').click(function(e){
            // some codes
        });
    }// end _setupLikedButton

    // the public API interface
    return {
        initialize: function(){
            // initialization
            _setupToLikeButton();
            _setupLikedButton();
        }
    };
}());

然後在所有需要「按讚」功能的頁面,加入這些程式碼:

<script src="/assets/js/jquery.js"></script>
<script src="/assets/js/clothing.js"></script>

<script type="text/javascript">
    $(document).ready(function(){
        LikeButtonModule.initialize();
    });
</script>

各位覺得,有沒有比較模組化的感覺呢?

Q&A

Q1: 為什麼 setupToLikeButton函式前面多了底線?

只是命名慣例,提醒developer它有private性質。

Q2: 那setupToLikeButton為何會得到private性質?

因為它是function內的一個var,被限定在function的closure內,所以function以外的地方不能執行它。

Q3: LikeButtonModule只有提供一個initialize函式給別人使用?這什麼爛模組?

等到「按讚系統」需要的功能更複雜、更豐富時,可以在最後return的物件內定義模組公開API,到時這個pattern會看起來很完整很漂亮。

Q4: 我看到一個很怪的文法 (function(){}()); 請問它是什麼?

那是JavaScript的立即函式。簡單來說就是定義一個function然後馬上執行它。注意這個函式最後return一個物件,而這個物件也就是這個模組的公開API介面。

(Photo via  Alison Christine, CC licensed)

如何寫好code:用Guard Clauses與EAFP打造你的城堡。

以前寫project的時候,常常越寫越不安。

心裡頭總是有兩個聲音:

  1. 為了更能應付不同狀況,程式碼要處理的情況越來越多、越來越複雜。if/else跟switch/case越寫越多層、一層又在一層又在一層裡面,越寫越像在挑戰自己智商的極限。
  2. 這個project再繼續維護(擴充功能、修bug)三個月沒問題、半年還撐得住,之後一定越寫越慢。不到一年,程式碼將龐雜到難以挽救。

總覺得哪裡出了問題:如果之後寫商用軟體、有持續擴充的野心,這樣寫code下去一定會遇到大麻煩。莫非要跟客戶或是老闆說:這project我無法繼續維護了,因為我能力不夠、您可能要另請高明。

難道我真的智商不足,沒能力寫出規模比較大的軟體?

終於,有一天,我學到了兩招,從此不再有這種擔憂。

今天,就來跟各位分享我的祕密。

(天阿,口氣真誇張,好像要推銷產品還是賣保險,哈)

這兩招是Guard Clauses與EAFP原則(It’s Easier to Ask Forgiveness than Permission)。下面跟各位分享,有寫錯的部份也期待各位前輩指教

Guard Clauses

與Guard Clauses相對的是Nested Structure。

下面這種code是典型的Nested Structure,相信也曾出現在你的project。災難一場。

function the_method($some_variable){
    if (some_expression){
        // some code
        // ...
        if (another_expression){
            // some more code
            // ...
            if (another_expression){
                // 這裡是理想情況
                // 終於可以在這裡做想做的事了
                // 不過我相信這串code過了一個月連你自己都看不懂
            }
        }
        // ... maybe some 'else' block to handle here
    }
    // ... and here
}

遇到這種情況,要怎麼應用guard clauses?請看下面的範例。

以電子商務網站為例,下面是某class建立一張購物訂單的method。要輸入單品的id陣列、買家id、付款方式與折扣金額:

function create_one($ids, $user_id, $payment_method, 
  $deduction_amount)
{
    // 抵扣金額不是整數
    // 呼叫這個method的工程師一定哪邊搞錯了吧
    if ( !is_integer( $deduction_amount) ){
        throw new Exception("invalid 'deduction_amount': $deduction_amount. it's not integer.");
    }

    // 之前決定資料庫內用負數代表折扣金額
    // 傳正數進來的工程師你昨天很晚睡吼
    if ($deduction_amount > 0){
        throw new Exception("invalid deduction amount: $deduction_amount. it can't be position");
    }

    // 資料庫內找不到這個$user_id
    // 呼叫這個method的工程師你要不要再檢查一下程式碼
    $query = $this->db
                               ->where('id',$user_id)
                               ->get('users');
    if ( $query->num_rows() == 0 ){
        throw new Exception("invalid user id: $user_id. the user doesn't exist");
    }

    // 公司只有接受三種付款方式
    $acceptable_payment_method = array( 'ATM', 'at_home', 'store');
    if ( !in_array( $payment_method, $acceptable_payment_method ) )
    {
        throw new Exception("invalid payment_method: $payment_method .");
    }

    // 繼續一串要檢查的項目

    // 這邊終於可以把資料整理一下然後存進資料庫了
    // 可能還是會在這裡噴出不知道什麼鬼的exception
    // 但整段code真的好讀、好維護、又robust很多了

    return true;

}// end function create_one

後者是不是清楚非常多?

EAFP原則(It’s Easier to Ask Forgiveness than Permission)

*註1

與EAFP相對的是LBYL原則(Look Before You Leap)

想像下面這個用LBYL寫的除法函式:

function safe_divide_1($x, $y){
    if ($y == 0){
        echo "分母為零";
        return null;
    }
    else{
        return $x/$y;
    }
}

接著是用EAFP寫的除法函式:

function safe_divide_2($x, $y){
    try{
        return x/y
    }
    // 如果你有定義這樣一個ZeroDivisionException類別的話才行
    catch (ZeroDivisionException $e){ 
        echo "分母為零";
        return null; // 或是再throw $e給上面的stack處理也行
    }
     // 就算你上面沒定義ZeroDivisionException
    // 這邊還是會抓得到錯誤然後顯示訊息啦
    catch (Exception $e){ 
        echo ($e->getMessage());
        return null;
    }
}

後者有沒有比較乾淨俐落?

(好吧,這邊sample code看起來好像沒有,哈哈)

LBYL代表你小心處理各種情況;EAFP代表你直接執行最重要的程式碼,然後碰到問題(出現exception)再面對。

在MVC web開發領域,通常就是controller寫try/catch去執行model的某些method(EAFP),而不要在controller內自己寫一大堆判斷式檢查(LBYL)。
對應到剛剛電子商務訂單的例子,某個成立訂單的controller根據EAFP會寫出這樣的code:

try{
    $this->Order_model->create_one($ids, $user_id, $payment_method, $deduction_amount);
} catch (Exception $e){
    // 這樣寫其實很混,使用者會看到一個很醜的error頁面
    // 但是work就是了
    exit($e->getMessage());
}
// some code ...
// 顯示恭喜使用者的頁面/請他拿錢包準備付款的頁面之類的

若是根據LBYL原則去寫controller,則會寫出一大串if/else if/else去檢查$ids, $user_id, $payment_method, $deduction_amount這幾個變數、小心處理完各種情況才去呼叫create_one這個method。程式碼會又長又醜,看不懂整段code重點在哪。

以上是個人學習寫code學到最重要的其中兩招。其中有寫錯的部份也期待各位前輩指教。

Q&A

Q1: guard clauses看起來超直覺的啊,為什麼我到今天才知道可以這樣寫code?

這跟學校教育有關。正統教育有個名詞叫做single entry/exit point,說是為了維持code的高品質,叫你應該這麼做。

Q2: 有時候我還是覺得寫一串if/else比guard clause直覺啊?

程式碼的外觀其實直接說明了某些事情。guard clauses的code看起來就像在說:如果發生這件事,趕快處理完、然後滾吧。一串if/else if/else像是在說:你看,各個情況都一樣重要、所以大家都在一樣深層的巢狀結構裡面,真正重要的code跟某些狗屁情況一樣重要,所以他們一樣在這恐怖程式碼的內部第N層!

到底該怎麼寫還是看實際情況。不妨都試試,看當下哪個適合?

Q3: 我就老實告訴你吧!我根本不想學exception或是try/catch是什麼!因為我的code用if/else或是switch/case/default就把全部情況都處理好了!

會這樣想是因為你以前學C語言對嗎?C語言沒有exception機制,工程師必須寫出完整處理各種情況的程式碼。畢竟LBYL(Look Before You Leap )也是一種撰寫風格。那就寫到你覺得痛苦的那天再回來學這幾招吧。
丟exception不但code較易讀,還能在原本function之外去處理相關錯誤(把問題丟給「其他層級」去處理,像是model丟給其他model、model再丟給controller之類的)、追蹤exception在各stack內的流動。

Q4: 好吧,我剛是騙你的。其實我對正在用的語言的exception機制不熟。我還能用guard clauses嗎?

可以!上面sample code的throw new Exception全部寫成return false即可。然後EAFP就不會寫成try/catch了。

以同一個建立訂單的例子來說,如果你覺得成功跟失敗都是人之常情,請寫成這樣:

$success =$this->Order_model->create_one($ids, $user_id, $payment_method, $deduction_amount);
if ($success){
    // some code ...
    // 顯示恭喜使用者的頁面/請他拿錢包準備付款的頁面之類的
}
else{
    echo "親愛的使用者,因為我還沒學exception所以我不能告訴你哪邊出錯,反正出錯了。"
}

如果你覺得在你系統內應該不太會失敗才對,請寫成這樣:

$success =$this->Order_model->create_one($ids, $user_id, $payment_method, $deduction_amount);
if ( !$success){
    echo "親愛的使用者,因為我還沒學exception所以我不能告訴你哪邊出錯,反正出錯了。"
}
// some code ...
// 顯示恭喜使用者的頁面/請他拿錢包準備付款的頁面之類的

Q5: 你Q2、Q3好像在說有時候的確需要寫一串醜醜if/else if?可以再做一點說明?

if/else if 可以想成在處理「合理的各種情況」。它們都一樣重要,所以code在同層級各佔了一整串。
throw exception則是在處理「不合理但確實可能面對的情況」。這種情況隨便丟個exception還是秀error之類的,叫它快滾就好了。
如果你很在意user experience,那就去客製化(宣告class去繼承原生exception)各個exception、幫助其他developer(通常就是你自己)在各種exception之下處理各種情況吧。
不過,在Web開發領域,噴exception常是在controller呼叫model函式之時發生。我通常直接在view內用JavaScript提示使用者。他若是進行什麼詭異操作跳過了我的JavaScript協助,那我就用瀏覽器大方的噴個超醜的error message頁面給他。

Q6: 為什麼Q4你要示範兩種寫法?你讓我好困擾、我到底要挑哪種?

把Q2再看一遍。

 

————————————————————————————–

註1:EAFP與LBYL這兩個詞我是在Python社群看到的。這篇用字遣詞可能不甚精確。

註2:文章裡建議的某些處理方式非常隨便。以上僅是我目前的理解。請不斷追尋自己最喜歡的best practice。

(Photo via Elescir, CC License)

問題不是服貿好不好,是根本不相信政府說的話。

馬英九總統在今天早上發表了最新的聲明,再次強調服貿的好處、重要性與迫切性。

然後學生團體完全不接受。不管服貿聽起來多有道理、多有好處、多重要又多迫切,大家都聽不進去,因為現在根本沒人相信政府說的話。

這有點像是組織(政府)想推銷一個它認為很棒的產品(服貿),卻怎樣都沒人買帳。

這讓我想到Seth Golding(前Yahoo行銷副總裁)的一篇文章:

當公司產品賣不出去時,最重要的問題何在?

最重要的問題其實不是產品價格是否夠低、不是它是否耐用、不是它的功能是否豐富、不是打廣告的管道是否正確、不是官方網站是否夠酷,也不是公司的產品承諾是否完整。

這些都不是最重要的問題。

把產品賣給從沒買過它的人之前,最重要的問題是:他們是否相信你。

如果他們不相信你,那做再多承諾都沒用。

如果你很清楚自己的產品在幹嘛,但人們之前都沒買過你產品的話,他們很可能根本不相信你說的一切。

如果公司跨足新的領域、做一門新的生意,也會遇到同樣問題。

如果你非常確定你的產品很有價值,但卻怎樣都賣不掉,請先想想「信任」的問題。

取得信任、取得信任、取得信任。然後再去想剩下的事。

現在不管政府講什麼經濟分析、舉什麼競爭力的說明都沒有意義。大家根本不相信政府說的話。

原文出處:
http://sethgodin.typepad.com/seths_blog/2014/02/the-most-important-question.html

(Photo via Wikipedia)

當代領袖怎麼想:員工上班時間摸魚、逛Facebook!

台灣這幾年似乎沒有年輕領袖。檯面上都是郭台銘、張忠謀、施崇棠一類上一輩的企業家。

上一代的企業家持續影響著這一代的所有人:教導大家工作的方式、管理的方式、生活的方式。連創新的方法、網路時代的野心都是他們在談。

我想分享一些國外當代領袖對事情的看法,希望能給大家一些靈感。

————————————————————————-

今天要討論的問題就是「當代領袖怎麼想:員工上班時間摸魚、逛Facebook」,回答者是37Signals、Basecamp、「工作大解放」的作者Jason Fried。

下文摘錄自他在接受Inc網路媒體專訪時,分享他一天如何工作,以及對工作的看法。

Jason Fried:

午餐過後,我在1點到3點之間會覺得很慵懶。我覺得就算工作也沒生產力,所以我通常會上網隨便逛逛。我覺得這很重要。每個人都應該了解一下網路上最近什麼蠢東西很紅、或是看看有什麼新鮮事。

我痛恨把員工當小孩子的公司。他們封鎖Facebook或Youtube,因為他們希望員工一天工作8小時。員工不會因此更有生產力,他們只會很失望。管這種事到底能幹嘛?只要工作能做完,我不在乎大家一整天都做些什麼。

(以上只節錄訪談的一小段,建議有興趣的各位可以去翻閱原文。

順帶一提,Jason Fried的Basecamp是一間獲利「非常驚人」的小型網路公司。)

—————

原文出處

http://www.inc.com/magazine/20091101/the-way-i-work-jason-fried-of-37signals.html

(Photo via Inc)

當代領袖怎麼想:當公司快倒了,員工卻不在乎業績,還每天準時下班!

台灣這幾年似乎沒有年輕領袖。檯面上都是郭台銘、張忠謀、施崇棠一類上一輩的企業家。

上一代的企業家持續影響著這一代的所有人:教導大家工作的方式、管理的方式、生活的方式。連創新的方法、網路時代的野心都是他們在談。

我不認為這些人懂當代的管理、當代的經營。我認為他們不懂。不然去這些人手下做事的年輕人,怎麼好像都沒有很滿意?為什麼當國家整體實力低下的時候,是年輕員工的競爭力不足,而不是上一輩企業家整體管理/領導能力的低落?

為什麼勞資關係惡化的時候,都是老闆對員工說「我認為你還沒有準備好當一個員工」,而沒有員工對老闆說「我認為你還沒有準備好當一個老闆」。

這跟學校教育、家庭教育有點關係。大家在唸書的時候,連上課舉手發言都覺得不好意思,離開學校之後又怎麼敢表達自己的意見。

除此之外,我還認為大家普遍對領導人的要求太低了。沒人要求自己的領導人格局要夠大、胸懷要夠寬廣。大家覺得這種事情可遇不可求、下面的人沒有資格要求上面的人。

我想從今天開始,分享一些國外領袖對事情的看法,希望能給大家靈感。

———————————————————————————–

今天要討論的問題就是本篇標題,回答者是阿里巴巴、淘寶網的馬雲。

大家看完問題可以先想想,故事發生在台灣,老闆會怎麼想?當事人員工會怎麼想?台灣員工敢這樣準時走嗎?那準時走是錯的還是對的?事不關己的其他人又會怎麼想?

原文出自阿里巴巴的生意經,是阿里巴巴旗下的一個社群平台。

網友sxf50000提問:

史上最牛業務員,一單不出準點下班。 80後女老闆抓狂了,我這是為員工在打工嗎?

我是一個80後女孩,畢業後工作一年,就自己出來創業了。做時尚時裝的外貿,是專做貿易,利用網絡平台在做。做了半年多了,毫無進展。現在是我和2個外貿業務員在做,自己每個月能出幾個小單,可是業務員一個單都出不了。即使能出小單,也入不敷出。我每天晚上都工作到11點,感覺很辛苦,我的業務員比我還輕鬆,他們每天下午6點準時下班,也不知道如何管理他們,現在是老闆比員工還累,壓力好大,再這樣我覺得我快堅持不下去了。我該怎麼辦?

馬雲:

我覺得這問題出在老闆身上。

第一,是不是招錯人了。

第二,是不是沒有激勵機制。

第三,總共三個人的公司,你要學會替別人多想想,別人就會替你多想想。你做10個小時是應該的,別人做10個小時是不應該的。因為你沒有讓​​他覺得是你的一份子,他憑什麼這麼做?

原文出處

http://baike.1688.com/doc/view-d20798915.html

(photo via wikipedia)

MVC,令人搞不懂的Model。

自學web開發兩年,對MVC一直一知半解,常在想怎樣設計系統會最漂亮。整理了一下目前的理解,以Model出發跟各位分享。其中有寫錯的部份也期待各位前輩指教。

Q1: 一個model對應到資料庫一張table對嗎?

不對。雖然大部分framework實作都會將一張table對應到model內一個class,但「一個model對應一張table」比較像是在描述ORM之類幫助簡化資料庫SQL操作的class,而model是「處理跟某張table相關任務的class」。

差別何在?一個model可能會處理到多張table的資料,也可能一張table都不碰,純粹處理一串商業邏輯。舉例來說,可能會有個負責整理行銷資料的model叫Marketing,從多張table撈資料出來,整理之後丟給controller。而資料庫中可能有,也可能沒有「marketing」這張table。

Q2: 可是我寫專案幾乎一個model對應到一張table啊?

因為實務上一個服務常會對應到一個model,而提供這model所需的資料常常就只是一張table。所以許多情況下的確是一張model對應到一張table沒錯。

但也不能說是「一張」table。model內常會去join其他相關的table對吧?所以說「model對應到一張table」或是「model就是一種ORM」都是不精確而誤導的說法。雖然寫起來常會有這種錯覺。

Q3: 那你用一個不會誤導又精確的說法描述model給我看?

model是layer」。不要把MVC中的M跟VC想成平行的分工關係,把VC跟M想成上往下的幾層layer。VC最靠近使用者、下來是M、再下來是database。所以VC要取得資料庫的資料都要透過model,而不同任務有不同model。

Q4: 聽不太懂,談點別的好了。從Rails社群流出一句很流行的MVC原則「fat model, skinny controller」,你對此有什麼想法?

這句話對初學者有幫助,對進階者有害。「fat model, skinny controller」這句話可以提醒初學者盡量把code丟進model別丟進controller。你再看一次我Q3的回答,一個應用程式的最重要部份(商業邏輯、安全性、效能)本來就是跟資料庫互動的那層layer,也就是model,所以把code丟進model別丟進controller可以暗示初學者寫出reusable的code。

但是把太多code寫進model會形成god pattern,也就是某個model跟神一樣負責絕大部分的任務,導致維護起來有困難(想像你初學PHP把全部code寫在同一份檔案的恐怖情況)。

Q5: 又說code要盡量丟進model,又說model內code太多很不好,不然code到底要丟哪?

library以及helper。現在大部分的framework都預設有放library以及helper的地方。不使用framework的話你也可以自己開這兩個資料夾。

多個model常做的同幾件事可以包起來寫成library,而單純的資料格式轉換(甚至是產生相關html/css)之類的任務可以寫成helper。讓model主要專注在商業邏輯(這是最花功夫的地方),其餘的搬到library、helper、或是有些framework稱之為third_party或package的什麼地方都好。別丟進controller!更別丟進view。

Q6: 好吧。可是我開發時在controller跟model內真的會不小心就寫掉大部分任務,這樣寫真的比較快阿。你開發時,上面說的你自己有沒有都做到?

沒有。原因有兩個。一,實務上的確很難在一開始就把架構想清楚,所以一堆任務會丟給model。二,常常越往下寫才發現某些任務是共通的、某些任務一開始只是「簡單」處理資料轉換,寫到後來變成「複雜的」處理資料轉換,我認為這時再把它拉出來成為library或helper即可。重構本來就是不斷在做的事情,時機到了就做*(註1)。

Q7: 我知道library跟helper的地位何在了。但,library跟helper的差別又何在?

軟體開發時,有時會寫幾個function或method解決某些任務,有時候覺得這樣太鬆散,會包成class。library常常是class、helper常常是一組function。以我自己來說,常在controller或view使用helper,在model使用library。

Q8: 關於model,最後有沒有要提醒的?

不要在model裡面用session variable。用下去,那model根本不再reusable(其他controller用之前還要先設定好相關session)。連unit testing都毀了。解決之道是dependency injection。session variable某種程度來說根本就是global variable,濫用global variable是很不好的。

註1:這段話可能很有問題。我在startup工作,開發上極度注重「速度」,常為了拼上線時間而捨棄其他事情。

(Photo via  Alaskan Dude, CC License)