不要相信程序員在加班時間寫的代碼
作為一個最底層的程序員,我先記錄一些只有底層程序員才會知道的事情。如果多年后,我違背自己進入這個行業(yè)的初心,走上管理崗位,也能回想起一些禁忌,避免一些錯誤。
其中最重要的就是這條:不要相信一個程序員在加班時間寫出來的代碼。
(軟件工程的學(xué)說表明,連正常時間好好寫的代碼,也不要太相信。不過這不是本文的重點,略過不提。)
(不懂代碼的人,看到本文中的Java代碼可以略過,不影響理解。)
創(chuàng)造力的時限
寫代碼,與寫文章、繪畫、思考復(fù)雜問題,并沒有本質(zhì)上的區(qū)別,都是創(chuàng)造性的活動。
每個人的創(chuàng)造力,都會隨著身體狀態(tài)而波動。廣為人知的是,一個人年老體衰后,相比年富力強時,創(chuàng)造力會急劇下降。其實,人每天的狀態(tài)起伏,也同樣會劇烈影響這一點。
如果是擰螺絲,那么在精疲力盡、擰不動以前,身體狀態(tài)對結(jié)果不會產(chǎn)生太大影響。因為擰螺絲的指標非常簡單——擰緊,要做的事也非常機械化——擰,直到它緊,換下一個。
但如果是寫代碼,有些事,是不能在狀態(tài)不好的時候完成的。
比如,在Java里,遍歷一個外部的List
,做一些處理。如果狀態(tài)不佳、做事前想的東西少了點,那么很可能直接這么做:
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++帶來的一種很直觀的做法。有什么問題嗎?
假如外面?zhèn)魅氲?code style="border:0px;margin:0px;padding:0px;font-family:Monaco, Consolas, "">aList是一個ArrayList
,那么List.get(int)
的時間復(fù)雜度是O(1),算上外面那重循環(huán)則是O(n);而假如aList
是一個LinkedList
,那么List.get(int)
的時間復(fù)雜度是O(n),算上外面那重循環(huán)則是O(n2)!
(為不懂算法時間復(fù)雜度評估的人解釋下:在這個場景下,O(n)代表最優(yōu)、最快,而O(n2)代表不可接受地慢。)
如果時間充分,那么可以去查看handleAList()
的調(diào)用位置,看看它傳遞的是哪種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,普通的黑盒與白盒測試都是無法發(fā)現(xiàn)的。只是你的App會比較卡,或者后臺會比較慢。當(dāng)需要解決這種性能問題時,可能需要非常經(jīng)驗豐富的程序員,在海量代碼里找數(shù)周時間——而這一切,在開發(fā)之初,只要那個程序員狀態(tài)好一點,就可以避免。
一個人,每天的創(chuàng)造力是有時限的。在時限外,他不再是一個優(yōu)秀的創(chuàng)造者,而是一個笨蛋。
(為了便于理解,這個例子非常簡單,以至于不夠貼切。對Java來說,優(yōu)先使用for-each或Iterator來遍歷,已經(jīng)是一個共識,是技術(shù)素養(yǎng)的一部分。)
失誤率的飆升
程序員在寫代碼的過程中,每天做得最多的應(yīng)該就是等價變換。
把
1
2
3
|
if (isSthTrue()) {
// Take some actions.
}
|
變換成
1
2
3
|
if (!isSthTrue()) return;
// Take some actions.
|
這只是最簡單的一種邏輯反轉(zhuǎn),實際上還有更多、更復(fù)雜的形式。通過這類變化,對代碼做出調(diào)整后,程序員可以把代碼變得更好,或者做到以前不能做的事。
而在加班時間、大腦不那么清醒的情況下,很可能會寫成這樣:
1
2
3
|
if (isSthTrue()) return;
// Take some actions.
|
區(qū)別僅僅只是少了一個符號,而意義則完全走樣。
這個例子比較簡單,出錯后也很容易在調(diào)試過程中發(fā)現(xiàn)、糾正。但是,請不要懷疑,的確會有程序員為了這么個簡單的問題,調(diào)試整整一個晚上!
(
、!
、i
,(字體未配置好時)本就難以區(qū)分,眼睛疲勞昏花時,在數(shù)百個字符里掃來掃去,難以分辨(!i
中是否少了個符號,也并不奇怪。而如果換成第二天早晨,很可能只需要瞥一眼。
大多數(shù)管理者,往往會對熬夜的程序員給出一些肯定,并且允許第二天可以休息一天(有些甚至只給一早上)。但如果他們知道內(nèi)情,會發(fā)現(xiàn)自己其實虧了一天。如果程序員正常下班,第二天花一小時解決這個問題,剩下的七個小時可以繼續(xù)開發(fā)。
還有很多比這復(fù)雜得多的變換,或其它類型的代碼改動,即使在大腦清醒的情況下也需要花費一些時間,認真思考、小心調(diào)試。而如果來了一個問題,你說“必須要今天下班前搞定”,那么程序員會很煩躁,并且越來越煩躁。
煩躁的后果
一件需要冷靜思考、謀定后動的事,如果逼迫人們在煩躁的情況下去做,那么往往會得到意想不到的糟糕結(jié)果。
我有一位前同事,技術(shù)實力且不論,心性也不太穩(wěn)(實際上,像我這種少年老成、未老先衰、找不到妹子都不急的青年,還真不多)。他是一個可以解決問題的人,但是在煩躁的情況下,也經(jīng)常做出令我瞠目結(jié)舌的事。
比如,有一天,項目組要求某個bug必須解決。他搞到晚上9點還沒搞定,找我?guī)兔ΑN耶?dāng)時水平也很差,不然也不會那時還在加班,沒能幫他解決,只是因此而知道這件事。他后來在10點半時采用了一個規(guī)避方案,然后下班了事。
具體一點是這樣的:在一個class中,有多個地方調(diào)用同一個Method。其它地方?jīng)]有問題,唯獨某個位置的結(jié)果不正確。他改成這樣:
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()
是可以做通用判斷的,他沒有在規(guī)定時間內(nèi)找到根本原因(Root Cause),實際上當(dāng)時他也根本沒有往發(fā)現(xiàn)根本原因的方向去查找代碼,而是一晚上都在做一些無效的調(diào)試。最后沒辦法調(diào)試出好的結(jié)果,于是給出問題的地方一個特殊處理——新增了isSth1True()
和isSth2True()
去那個出錯的地方頂替。結(jié)果,那個bug的確是解決了,但是后來帶出來了另外一個bug。
不過他也達到了目的,當(dāng)天下班了。
而后來,我在代碼里發(fā)現(xiàn)了另外一組更早就有的接口。
1
2
3
4
5
6
7
|
private boolean isTrueSth1() {
// Implemented like B
}
private boolean isTrueSth2() {
// Implemented like C
}
|
我問了一下這兩個Method的作者(另一位同事),他根本沒有看到有isSthTrue()
。
這件事的最終結(jié)果是,解決了一個bug,后來又引起了多個bug,連我也跟著一起焦頭爛額。
借著這個例子,回頭再說一下創(chuàng)造力的時限。
這位同事,之所以不去找Root Cause,是因為項目組的催逼和自身的煩躁,他平時是可以解決問題的。但是為什么一個簡單問題會這么難解決,為什么代碼里之前就有一套他要的Method,他卻新寫一個?
外部代碼環(huán)境就不說了,這個class共有2000行。2000行可能并不是特別直觀的數(shù)目,既不能說多,也不能說少,取決于這個class干什么事。
后來,另一個比較老道的同事,重構(gòu)(refactor)了這個class,只用了不到500行——這就說明了一個問題,這個class之前就太過冗余。
約半年后,我水平也提高了些,總體的項目時間也松散了些,我花了六周重寫(rewrite)了這個不大的代碼庫。這個class最終只用了100行,部分功能都獨立封裝到了其它class中。
如果之前,在這個代碼庫寫就之初,就能有一個充分的時間做一個好的架構(gòu)設(shè)計,不需要rewrite就可以只有100行;而如果時間不太充分,卻能給應(yīng)有的時間好好寫,也起碼能有refactor后的水平,也就是500行。無論是100行,還是500行,后面出的一大堆問題,都不會出現(xiàn),或者更容易解決。
這個代碼庫是怎么來的?
當(dāng)初某領(lǐng)導(dǎo),交給了一個比較厲害的同事,只給一周時間。這位同事加班加點,一周當(dāng)成兩周用,從別的代碼里剝離、拼湊出來了一個編譯能通過的東西——這就是交給我們維護的代碼庫。
來自項目最底層的復(fù)仇
前面說的,無論是寫出隱蔽的bug,還是解決一個帶出倆,其實都是這類事情的陽光面。你沒看錯,這是陽光的一面。
還有我不想多說的陰暗面。
前面說的事情,沒有一類是故意的。無論出事的原因是程序員的技術(shù)素養(yǎng)不足、加班情況下大失水準、還是原先的代碼就非常容易誘導(dǎo)失誤,都是程序員在認真努力的情況下,不可自控地犯錯。
還有一類是故意的。
比如,去年(2015)攜程那小哥兒,就是怒刪數(shù)據(jù)庫。當(dāng)然,他不是為了加班嚴重而如何如何,而是心愛的運營妹子被公司某高層給……(另有一說,雖然有什么內(nèi)部的QQ、微信截圖,但這仍然是謠言,實際上是黑客攻擊。)
什么程度的壓迫,就會得到什么程度的反抗。
要知道,即使是很努力地去做,也仍然可以出各種問題。而如果要故意搗亂,很多手段,雖然不會引起老板的注意,甚至可以不被認真的代碼審查者(reviewer)警覺,但是會客觀地影響產(chǎn)品的品質(zhì),讓用戶討厭一個產(chǎn)品,或者讓一個爆款產(chǎn)品最終失敗。
反正埋了雷,領(lǐng)了工資,跳下一家便是——要么給股票、期權(quán),要么充分洗腦,或至少給出足夠的加班費(幾年后的醫(yī)療費),否則就是這個后果。
我只能說,就我個人而言,最多辭職,不會故意亂搞。這關(guān)乎職業(yè)道德,關(guān)乎我是否意念通達、心境澄明。(坐等穿越去修真:P)
但是,我不能用自己的道德準繩去要求別人,對吧?
而且,永遠不要指望一個人在承受不道德的對待時,仍然能謹守原來的道德。
結(jié)語
作為一個軟件項目的領(lǐng)導(dǎo)者,你在要求某個程序員加班時,其實就已經(jīng)在冒險;而如果你經(jīng)常這么干,不要奇怪為什么項目總是延期,或者一到關(guān)鍵時候,總有突發(fā)事件。
只要試驗次數(shù)夠多,可能性再小的事也會發(fā)生;而只要試驗次數(shù)更多,小概率事件也會連續(xù)發(fā)生。
所以,最理智、客觀的觀念就是:欲速則不達,不要相信一個程序員在加班時間寫的代碼。
免責(zé)聲明:文章來源于網(wǎng)絡(luò),侵權(quán)刪
- 贊