1. 程式人生 > >其實我是想說程式設計師真的很辛苦,別總讓他們加班

其實我是想說程式設計師真的很辛苦,別總讓他們加班

作為一個最底層的程式設計師,我先記錄一些只有底層程式設計師才會知道的事情。如果多年後,我違背自己進入這個行業的初心,走上管理崗位,也能回想起一些禁忌,避免一些錯誤。

其中最重要的就是這條:不要相信一個程式設計師在加班時間寫出來的程式碼。

(軟體工程的學說表明,連正常時間好好寫的程式碼,也不要太相信。不過這不是本文的重點,略過不提。)

(不懂程式碼的人,看到本文中的Java程式碼可以略過,不影響理解。)

創造力的時限

寫程式碼,與寫文章、繪畫、思考複雜問題,並沒有本質上的區別,都是創造性的活動。

每個人的創造力,都會隨著身體狀態而波動。廣為人知的是,一個人年老體衰後,相比年富力強時,創造力會急劇下降。其實,人每天的狀態起伏,也同樣會劇烈影響這一點。

如果是擰螺絲,那麼在精疲力盡、擰不動以前,身體狀態對結果不會產生太大影響。因為擰螺絲的指標非常簡單——擰緊,要做的事也非常機械化——擰,直到它緊,換下一個。

但如果是寫程式碼,有些事,是不能在狀態不好的時候完成的。

比如,在Java裡,遍歷一個外部的List,做一些處理。如果狀態不佳、做事前想的東西少了點,那麼很可能直接這麼做:

public void handleAList(List<Integer> aList) {
        for (int i = 0; i < aList.size(); ++i) {
                // Do sth with List#get(int)
        }
}
這樣做是從C/C++帶來的一種很直觀的做法。有什麼問題嗎?

假如外面傳入的aList是一個ArrayList,那麼List.get(int)的時間複雜度是O(1),算上外面那重迴圈則是O(n);而假如aList是一個LinkedList,那麼List.get(int)的時間複雜度是O(n),算上外面那重迴圈則是O(n2)!

(為不懂演算法時間複雜度評估的人解釋下:在這個場景下,O(n)代表最優、最快,而O(n2)代表不可接受地慢。)

如果時間充分,那麼可以去檢視handleAList()的呼叫位置,看看它傳遞的是哪種List;而如果思考得夠充分,考慮到這兩種情況都有可能,那麼程式碼就會做相容處理,改成這樣:

public void handleAList(List<Integer> aList) {
        for (int i : aList) {
                // Do sth with i
        }
}
這使用了for-each語法,實際上是用Iterator來做遍歷,無論對哪種List都是總共是O(n)的開銷。

注意,這通常不被看做一個bug,普通的黑盒與白盒測試都是無法發現的。只是你的App會比較卡,或者後臺會比較慢。當需要解決這種效能問題時,可能需要非常經驗豐富的程式設計師,在海量程式碼裡找數週時間——而這一切,在開發之初,只要那個程式設計師狀態好一點,就可以避免。

一個人,每天的創造力是有時限的。在時限外,他不再是一個優秀的創造者,而是一個笨蛋。

(為了便於理解,這個例子非常簡單,以至於不夠貼切。對Java來說,優先使用for-each或Iterator來遍歷,已經是一個共識,是技術素養的一部分。)

失誤率的飆升

程式設計師在寫程式碼的過程中,每天做得最多的應該就是等價變換。



if (isSthTrue()) {
        // Take some actions.
}
變換成

if (!isSthTrue()) return;

// Take some actions.
這只是最簡單的一種邏輯反轉,實際上還有更多、更復雜的形式。通過這類變化,對程式碼做出調整後,程式設計師可以把程式碼變得更好,或者做到以前不能做的事。

而在加班時間、大腦不那麼清醒的情況下,很可能會寫成這樣:

if (isSthTrue()) return;

// Take some actions.
區別僅僅只是少了一個符號,而意義則完全走樣。

這個例子比較簡單,出錯後也很容易在除錯過程中發現、糾正。但是,請不要懷疑,的確會有程式設計師為了這麼個簡單的問題,除錯整整一個晚上!

(、!、i,(字型未配置好時)本就難以區分,眼睛疲勞昏花時,在數百個字元裡掃來掃去,難以分辨(!i中是否少了個符號,也並不奇怪。而如果換成第二天早晨,很可能只需要瞥一眼。

大多數管理者,往往會對熬夜的程式設計師給出一些肯定,並且允許第二天可以休息一天(有些甚至只給一早上)。但如果他們知道內情,會發現自己其實虧了一天。如果程式設計師正常下班,第二天花一小時解決這個問題,剩下的七個小時可以繼續開發。

還有很多比這複雜得多的變換,或其它型別的程式碼改動,即使在大腦清醒的情況下也需要花費一些時間,認真思考、小心除錯。而如果來了一個問題,你說“必須要今天下班前搞定”,那麼程式設計師會很煩躁,並且越來越煩躁。

煩躁的後果

一件需要冷靜思考、謀定後動的事,如果逼迫人們在煩躁的情況下去做,那麼往往會得到意想不到的糟糕結果。

我有一位前同事,技術實力且不論,心性也不太穩(實際上,像我這種少年老成、未老先衰、找不到妹子都不急的青年,還真不多)。他是一個可以解決問題的人,但是在煩躁的情況下,也經常做出令我瞠目結舌的事。

比如,有一天,專案組要求某個bug必須解決。他搞到晚上9點還沒搞定,找我幫忙。我當時水平也很差,不然也不會那時還在加班,沒能幫他解決,只是因此而知道這件事。他後來在10點半時採用了一個規避方案,然後下班了事。

具體一點是這樣的:在一個class中,有多個地方呼叫同一個Method。其它地方沒有問題,唯獨某個位置的結果不正確。他改成這樣:

private boolean isSthTrue(int sth) {
        // Implementation A
}

private boolean isSth1True() {
        // Implementation B
}

private boolean isSth2True() {
        // Implementation C
}
本來isSthTrue()是可以做通用判斷的,他沒有在規定時間內找到根本原因(Root Cause),實際上當時他也根本沒有往發現根本原因的方向去查詢程式碼,而是一晚上都在做一些無效的除錯。最後沒辦法調試出好的結果,於是給出問題的地方一個特殊處理——新增了isSth1True()和isSth2True()去那個出錯的地方頂替。結果,那個bug的確是解決了,但是後來帶出來了另外一個bug。

不過他也達到了目的,當天下班了。

而後來,我在程式碼裡發現了另外一組更早就有的介面。

private boolean isTrueSth1() {
        // Implemented like B
}

private boolean isTrueSth2() {
        // Implemented like C
}
我問了一下這兩個Method的作者(另一位同事),他根本沒有看到有isSthTrue()。

這件事的最終結果是,解決了一個bug,後來又引起了多個bug,連我也跟著一起焦頭爛額。

藉著這個例子,回頭再說一下創造力的時限。

這位同事,之所以不去找Root Cause,是因為專案組的催逼和自身的煩躁,他平時是可以解決問題的。但是為什麼一個簡單問題會這麼難解決,為什麼程式碼裡之前就有一套他要的Method,他卻新寫一個?

外部程式碼環境就不說了,這個class共有2000行。2000行可能並不是特別直觀的數目,既不能說多,也不能說少,取決於這個class幹什麼事。

後來,另一個比較老道的同事,重構(refactor)了這個class,只用了不到500行——這就說明了一個問題,這個class之前就太過冗餘。

約半年後,我水平也提高了些,總體的專案時間也鬆散了些,我花了六週重寫(rewrite)了這個不大的程式碼庫。這個class最終只用了100行,部分功能都獨立封裝到了其它class中。

如果之前,在這個程式碼庫寫就之初,就能有一個充分的時間做一個好的架構設計,不需要rewrite就可以只有100行;而如果時間不太充分,卻能給應有的時間好好寫,也起碼能有refactor後的水平,也就是500行。無論是100行,還是500行,後面出的一大堆問題,都不會出現,或者更容易解決。

這個程式碼庫是怎麼來的?

當初某領導,交給了一個比較厲害的同事,只給一週時間。這位同事加班加點,一週當成兩週用,從別的程式碼裡剝離、拼湊出來了一個編譯能通過的東西——這就是交給我們維護的程式碼庫。

來自專案最底層的復仇

前面說的,無論是寫出隱蔽的bug,還是解決一個帶出倆,其實都是這類事情的陽光面。你沒看錯,這是陽光的一面。

還有我不想多說的陰暗面。

前面說的事情,沒有一類是故意的。無論出事的原因是程式設計師的技術素養不足、加班情況下大失水準、還是原先的程式碼就非常容易誘導失誤,都是程式設計師在認真努力的情況下,不可自控地犯錯。

還有一類是故意的。

比如,去年(2015)攜程那小哥兒,就是怒刪資料庫。當然,他不是為了加班嚴重而如何如何,而是心愛的運營妹子被公司某高層給……(另有一說,雖然有什麼內部的QQ、微信截圖,但這仍然是謠言,實際上是黑客攻擊。)

什麼程度的壓迫,就會得到什麼程度的反抗。

要知道,即使是很努力地去做,也仍然可以出各種問題。而如果要故意搗亂,很多手段,雖然不會引起老闆的注意,甚至可以不被認真的程式碼審查者(reviewer)警覺,但是會客觀地影響產品的品質,讓使用者討厭一個產品,或者讓一個爆款產品最終失敗。

反正埋了雷,領了工資,跳下一家便是——要麼給股票、期權,要麼充分洗腦,或至少給出足夠的加班費(幾年後的醫療費),否則就是這個後果。

我只能說,就我個人而言,最多辭職,不會故意亂搞。這關乎職業道德,關乎我是否意念通達、心境澄明。(坐等穿越去修真


但是,我不能用自己的道德準繩去要求別人,對吧?

而且,永遠不要指望一個人在承受不道德的對待時,仍然能謹守原來的道德。

結語

作為一個軟體專案的領導者,你在要求某個程式設計師加班時,其實就已經在冒險;而如果你經常這麼幹,不要奇怪為什麼專案總是延期,或者一到關鍵時候,總有突發事件。

只要試驗次數夠多,可能性再小的事也會發生;而只要試驗次數更多,小概率事件也會連續發生。

所以,最理智、客觀的觀念就是:欲速則不達,不要相信一個程式設計師在加班時間寫的程式碼。