我见过的最糟糕代码
作者 | Mehdi Zed
在本文,將向讀者展示一些作者見過的一些最糟糕的代碼。除非你希望被同事和用戶討厭,否則這些“魔鬼”永遠都不應該被放到人間。我們會發現,通過一些好的實踐,其實很容易避免它們。
**
01”魔鬼代碼“
需要改進的代碼與所謂的“魔鬼代碼”是不一樣的。
不管使用的是哪種語言,“魔鬼代碼”都是糟糕的代碼,因為它會危及項目的穩定性和可維護性。告訴你,我見過很多“魔鬼代碼”。
當它堆積如山時,你的項目很快就會變成十八層地獄的樣貌。如果你喜歡到處捅婁子,那么團隊領導看你的眼光也會越來越不一樣。
**
02模棱兩可和前后矛盾
很久很久以前,在一個遙遠的星系中,我在一個清晨醒來,被世界末日般的景象嚇得跳了一跳。
生產環境出了一個了不得的錯誤。生產中的所有系統票證莫名其妙地返回“null”。到處都亂成一團。所有人都像無頭蒼蠅一樣到處亂跑。
我百米沖刺到我的工作站,第一個條件反射就是看 Kibana。沒有日志,什么都沒有。完蛋了,這可不是什么好的開始。
因此,我決定追溯票證的創建路徑。
為此,我必須深入研究系統自盤古開天辟地以來創建的那些內部庫。考古工作結束的時候,我找到了問題來源之一的一堆文件。
然后,我看到了這樣的景象:
// use id and expire to get ticket async function get_ticket(i, expire) {return CheckisNotExp(expire).then(async function() {var t = await GetTicketModel(i)if (t) {return t} else {logger.error(JSON.stringify(t))return null}}).catch(async function(e) {logger.error(JSON.stringify(e))return null })}真的有一個“i”變量嗎?我們現在在哪?這是一個 id,對不對?它是整數還是 UUID?到底是什么到期了?是日期還是時間戳?為什么會有 camelCase、PascalCase 和 snake_case?帶有 promise 的異步注解和又一個異步注解?如果失敗,我們會返回 null?簡直是魔鬼啊!
那時,每隔 5 分鐘就有一半的公司同事向我發 Skype 消息,索取 ETA 修復。
所以首先,有人需要知道這里究竟發生了什么。
我很快意識到,該為此負責的不是一個人,而是三個人。很久以前,其中兩個人離開了公司,而第三個人今天早上還沒來。這真是經典場景。
根據 Git 的記錄,這三個人碰這個文件的時間各自差了很久。因此他們留下了不一致的代碼、不同的樣式、不一樣的 ECMAScript 版本和不同的 promise 處理方式。
不管怎樣,在這段代碼中,一切都是模棱兩可的,一切都是不一致的。這是一個絕佳的反面案例,你應該盡一切可能避免這種情況。不用說,代碼審查并沒有覆蓋到這里。
現在好了,我們必須快速重寫它。
我得更改那些變量和函數的名稱。不能再有歧義了。各處的 Async/Await 都要做成相同的方式。
我還要確保自己不會漏掉任何錯誤,結果再返回一個 null。如果出現了什么問題,這些錯誤肯定要破壞函數。異常應由上面的層來處理。
// hotfix // @todo rewrite the ticket module entirely async function getTicket(ticketUuid, ticketExpirationTimestamp) {await validTicketExpiration(ticketUuid, ticketExpirationTimestamp)const ticket = await getTicketByUuid(ticketUuid)return ticket }最好的解決方案是重寫這個模塊的一部分。這里的票證驗證邏輯很糟糕。但這并不是最要緊的事情。
當務之急是找出并修復錯誤。
在更新代碼后,真正的錯誤開始浮出水面。前一天所做的一個配置更改改變了票證創建行為。返回到先前的配置可以立即解決這個問題。
接下來的一周時間里,有問題的模塊被完全重寫。
**
03肉醬意面
只有一個依賴項
很久很久以前,在一個遙遠的星系中,我正在做一個代碼干凈整齊的產品。
作為優質產品,一切都在內部做好了優化。功能是用盡可能少的代碼開發的。代碼高度重視可讀性。由注重整潔代碼的工程師管理的代碼審查流程確保了產品嚴格遵循所有最佳實踐。
SOLID、DRY、KISS、YAGNI 和你可以想到的其他首字母縮寫詞,這里都能見得到。
即使做到這個地步,這個產品的某個特殊部分也會間歇性地崩潰。在一個沖刺期間,我終于設法安排出了時間來調查這件事。
很快,我意識到問題不在于產品。那些錯誤只有一個共同點:一個依賴項。那是一個通過內部工件處理的內部依賴項。
它由另一個團隊管理,而且——令人驚訝的是——這段代碼不是免費提供的。你必須先獲得許可才能看到它。因此,我請求了訪問權限來了解到底發生了什么事情。
然后我收到了一條 slack 消息,問我為什么要訪問源碼。
“你好!為什么你需要訪問這個存儲庫?”
“你是什么意思?你知道我在這里工作嗎?等等,我在路上。”
我突然出現在他面前后,終于拿到了訪問該項目的權限。
我在其中看到一個文件,大小為 300KB。300KB 的文本,竟然有那么大。它已經有好幾年沒人碰過了。上次碰過它的那個人,我完全不認識。
簡直是最可怕的魔鬼。
那是我一生中見過的規模最大的意大利面條代碼。篇幅所限,我并沒有把所有代碼都放在這里。下面的代碼只是我看到的那一坨東西的冰山一角。
小心閱讀,它讀起來扎眼。
// Thousands of lines of spaghetti codesif (global.Builder) {module.exports.buildPgs = function(pgs, options, limitNodes = 0) {var config = options.config || {};var builded = [];pgs.each(function(pg) {var supported = pg.prop('tagName') == "INPUT"&& pr.attr['name'] == "file"&& options.pgs.rel.active == ""&& global.FileReader;if (!supported || !pg.f || pg.f.length == 0)return;for (var i = 0; i < pg.f.length; i++){builded.push({file: pg.f[i],instanceConfig: _.extend({}, config)});if (isFunction(options.before)){var returned = options.before(pg.f.path);if (typeof returned === 'object' && global.status.in_progress){if (returned.action == "skip"){var needsSkip = (typeof global.status.in_progress === 'boolean' && global.status.in_progress)|| _.hasAny("cancel", Global._quotes.BAD_DELIMITERS)|| str.indexOf(Global._delimiter) > -1;if(needsSkip) return;}else if (typeof returned.config === 'object'){var LOCAL_BUILDER = new global.Builder("/builder/" + options.module + "/" + options.module + );for(var s=p,a=p.matchIndex(o),shift=0,i=0;i<a.length;i++){var deepcopyfile = JSON.parse(JSON.stringify(pg.f[i].getRawValue()));LOCAL_BUILDER.build(deepcopyfile)LOCAL_BUILDER.onmessage = global.Notification("buildPg", deepcopyfile);}}}}}});} }// Thousands of lines of spaghetti codes我甚至都沒有嘗試去碰這個惡魔之子。
在這類情況下,解決方案不是從代碼中找出來的。我召開了一次小組會議,向他們解釋具體情況。我的計劃也很簡單。
我們用一個已經可用的開源模塊替掉了這個撒旦般的依賴項。與往常一樣,這是一個大問題。必須做一些準備工作才能正確插入新的依賴項。
一開始的快速調查已經演變成持續幾天的一項艱巨任務。
在會議桌那頭,Scrum 主管很生氣。討論得越多,我越覺得想要不碰到該死的東西會非常困難。當我展示我們的處境后,討論結束了,答案是不行。
“你只需要稍微動一動這個模塊,把它修好就行了,然后我們會繼續原本的工作。”
于是乎,我做了開發人員為代碼質量和項目的可持續性應該做的額外工作。我說不行。我甚至走得更遠。這是我職業生涯中的第一次,也是唯一一次,如果我被迫要辭職,我已經做好辭職的準備。
他們顯然問了其他開發人員。大家都拒絕了。
由于這個問題的嚴重性,我爭取到替換這個模塊所需的時間。我為開源依賴項開發了一個小型適配器。然后我擺脫了那個被詛咒的依賴項。
此后,那個產品一切順利,運行正常。
開發人員經常會抱怨意大利面條代碼,這是有充分理由的。這是你能見過的最糟糕的代碼。但無需大量投資即可確保你避免這種情況。
**
04驅魔
一開始,本文想寫的是一個最佳實踐的列表。
“作為開發人員,為什么以及如何應用最佳實踐。”
不過上面這個標題很容易像大劑量安眠藥一般令人昏昏欲睡,此外我出于兩個原因更改了計劃。
首先,對于我,特別是對你來說,先談論后果會有趣很多。對開發人員來說,這很重要,因為這就是魔鬼代碼的起源。此外,如果你可以為我的遭遇會心一笑,那也很好。
其次,互聯網上已經有很多關于這個主題的文章。它們都有一個共同點,就是它們的內容都是從兩本書中摘出來的。這兩本書培養了幾代開發人員。——羅伯特·馬丁的《代碼整潔之道》、史蒂夫·麥康奈爾的《代碼大全》
你是否真的要縮短代碼審查時間,并且再也不想搞出什么魔鬼代碼?直接看原始資料就行,花點時間好好看完這兩本書。
我發現《代碼大全》的方法更易讀、更實用。但是,盡管《代碼整潔之道》非常復雜,但它教給我的知識不亞于甚至超過了《代碼大全》。前者里面使用的代碼是 Java 和 C++,但是誰在乎具體的語言呢?你在這本書里學到的是規則和編程理念。
用代碼審查來驗證代碼是好事情。但是,如果你不確定為什么它是好的代碼,那么到頭來還是會出來你經歷過的魔鬼代碼。
—END—更多精彩內容歡迎關注百度開發者中心公眾號。
總結
- 上一篇: 可申请试用!GN4系列GPU云服务器重磅
- 下一篇: Kubernetes入门——深入浅出讲D