不要相信程序員在加班時間寫的代碼
作為一個最底層的程序員,我先記錄一些只有底層程序員才會知道的事情。如果多年后,我違背自己進入這個行業的初心,走上管理崗位,也能回想起一些禁忌,避免一些錯誤。
其中最重要的就是這條:不要相信一個程序員在加班時間寫出來的代碼。
(軟件工程的學說表明,連正常時間好好寫的代碼,也不要太相信。不過這不是本文的重點,略過不提。)
(不懂代碼的人,看到本文中的Java代碼可以略過,不影響理解。)
創造力的時限
寫代碼,與寫文章、繪畫、思考復雜問題,并沒有本質上的區別,都是創造性的活動。
每個人的創造力,都會隨著身體狀態而波動。廣為人知的是,一個人年老體衰后,相比年富力強時,創造力會急劇下降。其實,人每天的狀態起伏,也同樣會劇烈影響這一點。
如果是擰螺絲,那么在精疲力盡、擰不動以前,身體狀態對結果不會產生太大影響。因為擰螺絲的指標非常簡單——擰緊,要做的事也非常機械化——擰,直到它緊,換下一個。
但如果是寫代碼,有些事,是不能在狀態不好的時候完成的。
比如,在Java里,遍歷一個外部的List
,做一些處理。如果狀態不佳、做事前想的東西少了點,那么很可能直接這么做:
1
2
3
4
5
|
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
;而如果思考得夠充分,考慮到這兩種情況都有可能,那么代碼就會做兼容處理,改成這樣:
1
2
3
4
5
|
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來遍歷,已經是一個共識,是技術素養的一部分。)
失誤率的飆升
程序員在寫代碼的過程中,每天做得最多的應該就是等價變換。
把
1
2
3
|
if (isSthTrue()) {
// Take some actions.
}
|
變換成
1
2
3
|
if (!isSthTrue()) return;
// Take some actions.
|
這只是最簡單的一種邏輯反轉,實際上還有更多、更復雜的形式。通過這類變化,對代碼做出調整后,程序員可以把代碼變得更好,或者做到以前不能做的事。
而在加班時間、大腦不那么清醒的情況下,很可能會寫成這樣:
1
2
3
|
if (isSthTrue()) return;
// Take some actions.
|
區別僅僅只是少了一個符號,而意義則完全走樣。
這個例子比較簡單,出錯后也很容易在調試過程中發現、糾正。但是,請不要懷疑,的確會有程序員為了這么個簡單的問題,調試整整一個晚上!
(
、!
、i
,(字體未配置好時)本就難以區分,眼睛疲勞昏花時,在數百個字符里掃來掃去,難以分辨(!i
中是否少了個符號,也并不奇怪。而如果換成第二天早晨,很可能只需要瞥一眼。
大多數管理者,往往會對熬夜的程序員給出一些肯定,并且允許第二天可以休息一天(有些甚至只給一早上)。但如果他們知道內情,會發現自己其實虧了一天。如果程序員正常下班,第二天花一小時解決這個問題,剩下的七個小時可以繼續開發。
還有很多比這復雜得多的變換,或其它類型的代碼改動,即使在大腦清醒的情況下也需要花費一些時間,認真思考、小心調試。而如果來了一個問題,你說“必須要今天下班前搞定”,那么程序員會很煩躁,并且越來越煩躁。
煩躁的后果
一件需要冷靜思考、謀定后動的事,如果逼迫人們在煩躁的情況下去做,那么往往會得到意想不到的糟糕結果。
我有一位前同事,技術實力且不論,心性也不太穩(實際上,像我這種少年老成、未老先衰、找不到妹子都不急的青年,還真不多)。他是一個可以解決問題的人,但是在煩躁的情況下,也經常做出令我瞠目結舌的事。
比如,有一天,項目組要求某個bug必須解決。他搞到晚上9點還沒搞定,找我幫忙。我當時水平也很差,不然也不會那時還在加班,沒能幫他解決,只是因此而知道這件事。他后來在10點半時采用了一個規避方案,然后下班了事。
具體一點是這樣的:在一個class中,有多個地方調用同一個Method。其它地方沒有問題,唯獨某個位置的結果不正確。他改成這樣:
1
2
3
4
5
6
7
8
9
10
11
|
private boolean isSthTrue(int sth) {
// Implementation A
}
private boolean isSth1True() {
// Implementation B
}
private boolean isSth2True() {
// Implementation C
}
|
本來isSthTrue()
是可以做通用判斷的,他沒有在規定時間內找到根本原因(Root Cause),實際上當時他也根本沒有往發現根本原因的方向去查找代碼,而是一晚上都在做一些無效的調試。最后沒辦法調試出好的結果,于是給出問題的地方一個特殊處理——新增了isSth1True()
和isSth2True()
去那個出錯的地方頂替。結果,那個bug的確是解決了,但是后來帶出來了另外一個bug。
不過他也達到了目的,當天下班了。
而后來,我在代碼里發現了另外一組更早就有的接口。
1
2
3
4
5
6
7
|
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)警覺,但是會客觀地影響產品的品質,讓用戶討厭一個產品,或者讓一個爆款產品最終失敗。
反正埋了雷,領了工資,跳下一家便是——要么給股票、期權,要么充分洗腦,或至少給出足夠的加班費(幾年后的醫療費),否則就是這個后果。
我只能說,就我個人而言,最多辭職,不會故意亂搞。這關乎職業道德,關乎我是否意念通達、心境澄明。(坐等穿越去修真:P)
但是,我不能用自己的道德準繩去要求別人,對吧?
而且,永遠不要指望一個人在承受不道德的對待時,仍然能謹守原來的道德。
結語
作為一個軟件項目的領導者,你在要求某個程序員加班時,其實就已經在冒險;而如果你經常這么干,不要奇怪為什么項目總是延期,或者一到關鍵時候,總有突發事件。
只要試驗次數夠多,可能性再小的事也會發生;而只要試驗次數更多,小概率事件也會連續發生。
所以,最理智、客觀的觀念就是:欲速則不達,不要相信一個程序員在加班時間寫的代碼。
免責聲明:文章來源于網絡,侵權刪
- 贊