設計規範
這篇可能要得罪人……
最近不少開發團隊交流,都涉及到設計規範的問題。我個人有個感覺:“對設計規範的態度,很大程度上反映了這個組織或者架構師是否有足夠的設計經驗”。但還是那句話,“唯我道大故不肖”,我把這個觀點表述出來,如果被一個群體接受了,他們就會做出“不是這樣的樣子”,這個特徵就不能被用於觀察了。所以讀者要想解決問題,知道我什麼意思就好了,你要把這個作為“原則”,用得不對不要來找我;)
很多認為設計規範“非常重要”,有兩個常見的論據:
- 很多成功的公司,團隊,專案都有程式設計規範,說明程式設計規範是一種成功的實踐
- 如果團隊裡都是高手,當然用不上設計規範,但我們大部分都是低手啊
我覺得能提出這兩個論點的,如果還是架構師,這些人就可以辭退了,基本上都是神棍。
我們一個個看。
成功的公司表現出來一個特徵,和這個特徵驅動了這個公司成功,這兩者沒有相關性,根本不能用作選擇這樣做的證據。舉出這個證據,說明這個架構師一點邏輯思維能力都沒有,這種人給團隊作技術引路?還不如找個薩滿或者程式員鼓勵師之類的更靠譜。
這個問題我後面進一步細化,現在我們先討論第二個問題。
我練二胡練了十幾年了,三天打魚兩天晒網的,練得不好,但肯定比剛練一年半載的普通玩家好,等以後程式設計序不好混了,去街頭拉個《江河水》、《藍花花》什麼的賣慘還是夠的。好了,你現在告訴我,你讓練了3個月的人也能拉《江河水》,設定什麼樣的《二胡演奏規範》能夠提供幫助?或者說,你要讓我拉出演奏家的水平,又設定什麼樣的《演奏規範》可以達成?
二胡技術是一遍遍的練習中提高的,調式,音階,琶音,短弓,長弓,抖弓,跳弓,滑揉,滾揉,壓揉……這些每個動作都是要單獨練習,重複幾萬次才培養出來的呀。最後你看到的一些特徵,那只是它的表面特徵而已啊,那些一個個單獨的細節才是支撐這些特徵的根本啊,學特徵並不能取代這些細節的呀。
這不是顯而易見的事情嗎?
寫一段程式,重複到什麼程度需要抽象成函式,巨集,模板,又複雜到什麼成什麼程度必須分解為兩個實現,這是要打磨才會知道的啊。一個函式怎麼才能不成為多執行緒程式的瓶頸,使用不同型別的鎖會製造什麼樣的瓶頸模式,你跟我說你看一本《程式設計指導》就會明白?不帶這樣看不起我們程式設計師的專業技能的吧?
你去找一個二胡老師,說“我也不想練習,你給我本祕籍,你讓我回家對著拉個《新婚別》行不?”,二胡老師會覺得你神經病(反正我現在的水平讓老師教,他說“你還是不要毀名曲吧”)。怎麼你就會覺得給“低水平”程式設計師一本《規範》,他就會寫出好程式?
你不要跟我說,雖然不能完全解決問題,但也有一點用吧?——這句話你跟音樂老師說去,我估計大部分音樂老師會跟你說:把你那本《規範》給我扔一邊去,給老子每天拿出8個小時出來練琴。
《設計規範》、《程式設計規範》是給高手(或者reasonable的程式設計師)提供幫助,不是教菜鳥寫程式,你不會寫程式,好好學寫程式。不是學《設計規範》。Linux Kernel上傳的時候都要求checkpatch.pl,那是在你邏輯已經搭得很Solid之後,幫你打磨一下細節,不是讓你一開始就對著這些細節來寫程式。不要搞反了。
那麼設計規範到底是什麼東西呢?其實設計規範和庫,模組,函式本質上沒有任何區別,都是一種“抽象設計”。我們先看看抽象是什麼,比如你要做兩個函式,分別計算算術平均和平方平均:
def arithmetic_mean(a): return sum(a)/len(a) def quadratic_mean(a): return square_root(sum(square(a))/len(a))
這背後有一個自然語義是重複的,就是“把一個數組的內容加起來,然後除以陣列的長度。這就形成這個抽象:
def mean(a): return sum(a)/len(a); def arithmetic_mean(a): return mean(a); def quadratic_mean(a): return square_root(mean(square(a)))
這裡的mean就叫“抽象”,它不具有算術平均和平方平均的其他考量,要拿到一個轉義過的邏輯:“進來的是陣列,我要把陣列中的成員加起來,併除以它們的數量”,其他東西我一概不知。這個“一概不知”,是你抽象的基礎。
其實我們應該早就注意到了,這個例子中的mean和arithmetic_mean的語義是一樣的,所以,這個抽象其實是個樣子貨,正確的抽象應該是這樣的:
def arithmetic_mean(a): return sum(a)/len(a); def quadratic_mean(a): return square_root(arithmetic_mean(square(a)))
但真得一定如此嗎?你以為你看到的程式碼就是全部?後面升級這個系統說不定是這樣的:
def mean(a): return sum(a)/len(a); def arithmetic_mean(a): log(arithmetic, a); return mean(a); def quadratic_mean(a): log(quadratic, a); return square_root(mean(square(a)))
呵呵,你原來的抽象還是得改回來。否則你就會變成這樣的邏輯:
def arithmetic_mean(a): if is_quadraic(a): log(quadratic, a); else log(arithmetic, a); return sum(a)/len(a); def quadratic_mean(a): return square_root(arithmetic_mean(square(a)))
現在明白我前一篇(ofollow,noindex" target="_blank">再談“法自然”的設計思路 )說的“關聯是需求決定的,不是設計決定的”的意思了吧?
所謂抽象,本質是除重和簡化邏輯空間,它是對現實的“總結”,不是對現實的“改變”,這就是“法自然”。是自然在驅動你,不是你在驅動自然。
回到設計規範這個問題,你決定做出一個“抽象”,比如“所有函式必須用動賓結構的語法形式”,這在解決什麼問題呢?這是在解決“溝通”的問題,當我們看一個標識的時候,一看見它是個名詞,我就知道它99%的可能是個變數,看到一個標識是動詞,我知道它大概率是個函式。這降低了我們理解程式碼時的複雜度。
又比如說,我們禁止C語言中使用setjmp和goto,這又帶來另一個抽象:任何一段程式碼,只要裡面沒有break, return,必然在這段程式碼的末尾退出(大概這樣吧,嚴格定義要寫的東西太多,沖淡我的表達了)。
《規範》只是在另一種形式的抽象,和基於庫,DSL,模型的抽象沒有區別,重點不在於這個形式,而是你抽象了什麼。而越高層的抽象就越難,而且越趨向無用。比如你把加減乘除抽象為ArithmeticOp(a, b),這個抽象有啥用?只是把“區分”放到這個“代理”的內部而已。
所以,你能不能做出抽象,取決於你能否找到這個減少介面語義複雜度的模式。如果你找不出來,《規範》不是“有病治病,無病防病”的“補品”,是藥三分毒,用麵粉當保健品賣算是良心企業了,真給你藥,吃死你。
所以,很多挺有經驗的工程師被人推上“提高組織開發效率”這種位置上後,首先想到的是“設計規範”。我總忍不住提醒一句,“你們何德何能進行這種層面的抽象啊?”
我不是看不起你,而是所有的抽象,都基於對全集的有效觀察和抽象。你跳過這些抽象,就想直接設定抽象,這個事情本身就是在賭,我不反對你賭,但你賭得連自己在賭都不知道,你就明白為什麼那麼多人輸得傾家蕩產了。
如果真要提高整體開發水平,你確實水平高,你可以解決一個模組的問題,影響一撥人,然後讓其他模組複製這個成功經驗,當這個滾動過程大到一定程度,後面的事情都不用你管了。如果你僅僅想讓一堆低水平的工程師襯托你的“高水平”……嗯,那就只能當我沒有說過了……