作為一個(gè)最底層的程序員,我先記錄一些只有底層程序員才會(huì)知道的事情。如果多年后越锈,我違背自己進(jìn)入這個(gè)行業(yè)的初心仗嗦,走上管理崗位,也能回想起一些禁忌甘凭,避免一些錯(cuò)誤稀拐。
其中最重要的就是這條:不要相信一個(gè)程序員在加班時(shí)間寫出來的代碼。
(軟件工程的學(xué)說表明丹弱,連正常時(shí)間好好寫的代碼德撬,也不要太相信。不過這不是本文的重點(diǎn)躲胳,略過不提蜓洪。)
(不懂代碼的人,看到本文中的Java代碼可以略過坯苹,不影響理解隆檀。)
創(chuàng)造力的時(shí)限
寫代碼,與寫文章北滥、繪畫刚操、思考復(fù)雜問題,并沒有本質(zhì)上的區(qū)別再芋,都是創(chuàng)造性的活動(dòng)菊霜。
每個(gè)人的創(chuàng)造力,都會(huì)隨著身體狀態(tài)而波動(dòng)济赎。廣為人知的是鉴逞,一個(gè)人年老體衰后,相比年富力強(qiáng)時(shí)司训,創(chuàng)造力會(huì)急劇下降构捡。其實(shí),人每天的狀態(tài)起伏壳猜,也同樣會(huì)劇烈影響這一點(diǎn)勾徽。
如果是擰螺絲,那么在精疲力盡统扳、擰不動(dòng)以前喘帚,身體狀態(tài)對(duì)結(jié)果不會(huì)產(chǎn)生太大影響。因?yàn)閿Q螺絲的指標(biāo)非常簡單——擰緊咒钟,要做的事也非常機(jī)械化——擰吹由,直到它緊,換下一個(gè)朱嘴。
但如果是寫代碼倾鲫,有些事,是不能在狀態(tài)不好的時(shí)候完成的。
比如乌昔,在Java里隙疚,遍歷一個(gè)外部的List
,做一些處理磕道。如果狀態(tài)不佳甚淡、做事前想的東西少了點(diǎn),那么很可能直接這么做:
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>aList是一個(gè)ArrayList
资柔,那么List.get(int)
的時(shí)間復(fù)雜度是O(1)焙贷,算上外面那重循環(huán)則是O(n);而假如aList
是一個(gè)LinkedList
贿堰,那么List.get(int)
的時(shí)間復(fù)雜度是O(n)辙芍,算上外面那重循環(huán)則是O(n2)!
(為不懂算法時(shí)間復(fù)雜度評(píng)估的人解釋下:在這個(gè)場景下羹与,O(n)代表最優(yōu)故硅、最快,而O(n2)代表不可接受地慢纵搁。)
如果時(shí)間充分吃衅,那么可以去查看handleAList()
的調(diào)用位置,看看它傳遞的是哪種List
腾誉;而如果思考得夠充分徘层,考慮到這兩種情況都有可能,那么代碼就會(huì)做兼容處理利职,改成這樣:
public void handleAList(List<Integer> aList) {
for (int i : aList) {
// Do sth with i
}
}
這使用了for-each語法趣效,實(shí)際上是用Iterator來做遍歷,無論對(duì)哪種List都是總共是O(n)的開銷猪贪。
注意跷敬,這通常不被看做一個(gè)bug,普通的黑盒與白盒測試都是無法發(fā)現(xiàn)的热押。只是你的App會(huì)比較卡西傀,或者后臺(tái)會(huì)比較慢。當(dāng)需要解決這種性能問題時(shí)楞黄,可能需要非常經(jīng)驗(yàn)豐富的程序員池凄,在海量代碼里找數(shù)周時(shí)間——而這一切,在開發(fā)之初鬼廓,只要那個(gè)程序員狀態(tài)好一點(diǎn)肿仑,就可以避免。
一個(gè)人,每天的創(chuàng)造力是有時(shí)限的尤慰。在時(shí)限外馏锡,他不再是一個(gè)優(yōu)秀的創(chuàng)造者,而是一個(gè)笨蛋伟端。
(為了便于理解杯道,這個(gè)例子非常簡單,以至于不夠貼切责蝠。對(duì)Java來說党巾,優(yōu)先使用for-each或Iterator來遍歷,已經(jīng)是一個(gè)共識(shí)霜医,是技術(shù)素養(yǎng)的一部分齿拂。)
失誤率的飆升
程序員在寫代碼的過程中,每天做得最多的應(yīng)該就是等價(jià)變換肴敛。
把
if (isSthTrue()) {
// Take some actions.
}
變換成
if (!isSthTrue()) return;
// Take some actions.
這只是最簡單的一種邏輯反轉(zhuǎn)署海,實(shí)際上還有更多、更復(fù)雜的形式医男。通過這類變化砸狞,對(duì)代碼做出調(diào)整后,程序員可以把代碼變得更好镀梭,或者做到以前不能做的事刀森。
而在加班時(shí)間、大腦不那么清醒的情況下丰辣,很可能會(huì)寫成這樣:
if (isSthTrue()) return;
// Take some actions.
區(qū)別僅僅只是少了一個(gè)符號(hào)撒强,而意義則完全走樣。
這個(gè)例子比較簡單笙什,出錯(cuò)后也很容易在調(diào)試過程中發(fā)現(xiàn)飘哨、糾正。但是琐凭,請(qǐng)不要懷疑芽隆,的確會(huì)有程序員為了這么個(gè)簡單的問題,調(diào)試整整一個(gè)晚上统屈!
(
胚吁、!
、i
愁憔,(字體未配置好時(shí))本就難以區(qū)分腕扶,眼睛疲勞昏花時(shí),在數(shù)百個(gè)字符里掃來掃去吨掌,難以分辨(!i
中是否少了個(gè)符號(hào)半抱,也并不奇怪脓恕。而如果換成第二天早晨,很可能只需要瞥一眼窿侈。
大多數(shù)管理者炼幔,往往會(huì)對(duì)熬夜的程序員給出一些肯定首繁,并且允許第二天可以休息一天(有些甚至只給一早上)卷胯。但如果他們知道內(nèi)情,會(huì)發(fā)現(xiàn)自己其實(shí)虧了一天藤肢。如果程序員正常下班圆兵,第二天花一小時(shí)解決這個(gè)問題跺讯,剩下的七個(gè)小時(shí)可以繼續(xù)開發(fā)。
還有很多比這復(fù)雜得多的變換殉农,或其它類型的代碼改動(dòng)抬吟,即使在大腦清醒的情況下也需要花費(fèi)一些時(shí)間,認(rèn)真思考统抬、小心調(diào)試。而如果來了一個(gè)問題危队,你說“必須要今天下班前搞定”聪建,那么程序員會(huì)很煩躁,并且越來越煩躁茫陆。
煩躁的后果
一件需要冷靜思考金麸、謀定后動(dòng)的事,如果逼迫人們?cè)跓┰甑那闆r下去做簿盅,那么往往會(huì)得到意想不到的糟糕結(jié)果挥下。
我有一位前同事,技術(shù)實(shí)力且不論桨醋,心性也不太穩(wěn)(實(shí)際上棚瘟,像我這種少年老成、未老先衰喜最、找不到妹子都不急的青年偎蘸,還真不多)。他是一個(gè)可以解決問題的人瞬内,但是在煩躁的情況下迷雪,也經(jīng)常做出令我瞠目結(jié)舌的事。
比如虫蝶,有一天章咧,項(xiàng)目組要求某個(gè)bug必須解決。他搞到晚上9點(diǎn)還沒搞定能真,找我?guī)兔α扪稀N耶?dāng)時(shí)水平也很差扰柠,不然也不會(huì)那時(shí)還在加班,沒能幫他解決误澳,只是因此而知道這件事耻矮。他后來在10點(diǎn)半時(shí)采用了一個(gè)規(guī)避方案,然后下班了事忆谓。
具體一點(diǎn)是這樣的:在一個(gè)class中裆装,有多個(gè)地方調(diào)用同一個(gè)Method。其它地方?jīng)]有問題倡缠,唯獨(dú)某個(gè)位置的結(jié)果不正確哨免。他改成這樣:
private boolean isSthTrue(int sth) {
// Implementation A
}
private boolean isSth1True() {
// Implementation B
}
private boolean isSth2True() {
// Implementation C
}
本來isSthTrue()
是可以做通用判斷的,他沒有在規(guī)定時(shí)間內(nèi)找到根本原因(Root Cause)昙沦,實(shí)際上當(dāng)時(shí)他也根本沒有往發(fā)現(xiàn)根本原因的方向去查找代碼琢唾,而是一晚上都在做一些無效的調(diào)試。最后沒辦法調(diào)試出好的結(jié)果盾饮,于是給出問題的地方一個(gè)特殊處理——新增了isSth1True()
和isSth2True()
去那個(gè)出錯(cuò)的地方頂替采桃。結(jié)果,那個(gè)bug的確是解決了丘损,但是后來帶出來了另外一個(gè)bug普办。
不過他也達(dá)到了目的,當(dāng)天下班了徘钥。
而后來衔蹲,我在代碼里發(fā)現(xiàn)了另外一組更早就有的接口。
private boolean isTrueSth1() {
// Implemented like B
}
private boolean isTrueSth2() {
// Implemented like C
}
我問了一下這兩個(gè)Method的作者(另一位同事)呈础,他根本沒有看到有isSthTrue()
舆驶。
這件事的最終結(jié)果是,解決了一個(gè)bug而钞,后來又引起了多個(gè)bug沙廉,連我也跟著一起焦頭爛額。
借著這個(gè)例子臼节,回頭再說一下創(chuàng)造力的時(shí)限蓝仲。
這位同事,之所以不去找Root Cause官疲,是因?yàn)轫?xiàng)目組的催逼和自身的煩躁袱结,他平時(shí)是可以解決問題的。但是為什么一個(gè)簡單問題會(huì)這么難解決途凫,為什么代碼里之前就有一套他要的Method垢夹,他卻新寫一個(gè)?
外部代碼環(huán)境就不說了维费,這個(gè)class共有2000行果元。2000行可能并不是特別直觀的數(shù)目促王,既不能說多,也不能說少而晒,取決于這個(gè)class干什么事蝇狼。
后來,另一個(gè)比較老道的同事倡怎,重構(gòu)(refactor)了這個(gè)class迅耘,只用了不到500行——這就說明了一個(gè)問題,這個(gè)class之前就太過冗余监署。
約半年后颤专,我水平也提高了些,總體的項(xiàng)目時(shí)間也松散了些钠乏,我花了六周重寫(rewrite)了這個(gè)不大的代碼庫栖秕。這個(gè)class最終只用了100行,部分功能都獨(dú)立封裝到了其它c(diǎn)lass中晓避。
如果之前簇捍,在這個(gè)代碼庫寫就之初,就能有一個(gè)充分的時(shí)間做一個(gè)好的架構(gòu)設(shè)計(jì)俏拱,不需要rewrite就可以只有100行垦写;而如果時(shí)間不太充分,卻能給應(yīng)有的時(shí)間好好寫彰触,也起碼能有refactor后的水平,也就是500行命辖。無論是100行况毅,還是500行,后面出的一大堆問題尔艇,都不會(huì)出現(xiàn)尔许,或者更容易解決。
這個(gè)代碼庫是怎么來的终娃?
當(dāng)初某領(lǐng)導(dǎo)味廊,交給了一個(gè)比較厲害的同事,只給一周時(shí)間棠耕。這位同事加班加點(diǎn)余佛,一周當(dāng)成兩周用,從別的代碼里剝離窍荧、拼湊出來了一個(gè)編譯能通過的東西——這就是交給我們維護(hù)的代碼庫辉巡。
來自項(xiàng)目最底層的復(fù)仇
前面說的,無論是寫出隱蔽的bug蕊退,還是解決一個(gè)帶出倆郊楣,其實(shí)都是這類事情的陽光面憔恳。你沒看錯(cuò),這是陽光的一面净蚤。
還有我不想多說的陰暗面钥组。
前面說的事情,沒有一類是故意的今瀑。無論出事的原因是程序員的技術(shù)素養(yǎng)不足程梦、加班情況下大失水準(zhǔn)、還是原先的代碼就非常容易誘導(dǎo)失誤放椰,都是程序員在認(rèn)真努力的情況下作烟,不可自控地犯錯(cuò)。
還有一類是故意的砾医。
比如拿撩,去年(2015)攜程那小哥兒,就是怒刪數(shù)據(jù)庫如蚜。當(dāng)然压恒,他不是為了加班嚴(yán)重而如何如何,而是心愛的運(yùn)營妹子被公司某高層給……(另有一說错邦,雖然有什么內(nèi)部的QQ探赫、微信截圖,但這仍然是謠言撬呢,實(shí)際上是黑客攻擊伦吠。)
什么程度的壓迫,就會(huì)得到什么程度的反抗魂拦。
要知道毛仪,即使是很努力地去做,也仍然可以出各種問題芯勘。而如果要故意搗亂箱靴,很多手段,雖然不會(huì)引起老板的注意荷愕,甚至可以不被認(rèn)真的代碼審查者(reviewer)警覺衡怀,但是會(huì)客觀地影響產(chǎn)品的品質(zhì),讓用戶討厭一個(gè)產(chǎn)品安疗,或者讓一個(gè)爆款產(chǎn)品最終失敗抛杨。
反正埋了雷,領(lǐng)了工資荐类,跳下一家便是——要么給股票蝶桶、期權(quán),要么充分洗腦掉冶,或至少給出足夠的加班費(fèi)(幾年后的醫(yī)療費(fèi))真竖,否則就是這個(gè)后果脐雪。
我只能說,就我個(gè)人而言恢共,最多辭職战秋,不會(huì)故意亂搞。這關(guān)乎職業(yè)道德讨韭,關(guān)乎我是否意念通達(dá)脂信、心境澄明。(坐等穿越去修真:P)
但是透硝,我不能用自己的道德準(zhǔn)繩去要求別人狰闪,對(duì)吧?
而且濒生,永遠(yuǎn)不要指望一個(gè)人在承受不道德的對(duì)待時(shí)埋泵,仍然能謹(jǐn)守原來的道德。
結(jié)語
作為一個(gè)軟件項(xiàng)目的領(lǐng)導(dǎo)者罪治,你在要求某個(gè)程序員加班時(shí)丽声,其實(shí)就已經(jīng)在冒險(xiǎn);而如果你經(jīng)常這么干觉义,不要奇怪為什么項(xiàng)目總是延期雁社,或者一到關(guān)鍵時(shí)候,總有突發(fā)事件晒骇。
只要試驗(yàn)次數(shù)夠多霉撵,可能性再小的事也會(huì)發(fā)生;而只要試驗(yàn)次數(shù)更多洪囤,小概率事件也會(huì)連續(xù)發(fā)生徒坡。
所以,最理智箍鼓、客觀的觀念就是:欲速則不達(dá),不要相信一個(gè)程序員在加班時(shí)間寫的代碼呵曹。