1. 程式人生 > >關於爛程式碼的那些事(上)

關於爛程式碼的那些事(上)

1.摘要

最近寫了不少程式碼,review了不少程式碼,也做了不少重構,總之是對著爛程式碼工作了幾周。為了抒發一下這幾周裡好幾次到達崩潰邊緣的情緒,我決定寫一篇文章談一談爛程式碼的那些事。 這裡是上篇,談一談爛程式碼產生的原因和現象。

2.寫爛程式碼很容易

剛入程式設計師這行的時候經常聽到一個觀點:你要把精力放在ABCD(需求文件/功能設計/架構設計/理解原理)上,寫程式碼只是把想法翻譯成程式語言而已,是一個沒什麼技術含量的事情。

當時的我在聽到這種觀點時會有一種近似於高冷的不屑:你們就是一群傻X,根本不懂程式碼質量的重要性,這麼下去遲早有一天會踩坑,呸。

可是幾個月之後,他們似乎也沒怎麼踩坑。而隨著程式設計技術一直在不斷髮展,帶來了更多的我以前認為是傻X的人加入到程式設計師這個行業中來。

語言越來越高階、封裝越來越完善,各種技術都在幫助程式設計師提高生產程式碼的效率,依靠層層封裝,程式設計師真的不需要了解一丁點技術細節,只要把需求裡的內容逐行翻譯出來就可以了。

很多程式設計師不知道要怎麼組織程式碼、怎麼提升執行效率、底層是基於什麼原理,他們寫出來的是在我心目中爛成一坨翔一樣的程式碼。

但是那一坨翔一樣程式碼竟然他媽的能正常工作。

即使我認為他們寫的程式碼是坨翔,但是從不接觸程式碼的人的視角來看(比如說你的boss),程式碼編譯過了,測試過了,上線運行了一個月都沒出問題,你還想要奢求什麼?

所以,即使不情願,也必須承認,時至今日,寫程式碼這件事本身沒有那麼難了。

3.爛程式碼終究是爛程式碼

但是偶爾有那麼幾次,寫爛程式碼的人離職了之後,事情似乎又變得不一樣了。

想要修改功能時卻發現程式裡充斥著各種無法理解的邏輯、改完之後莫名其妙的bug一個接一個,接手這個專案的人開始漫無目的的加班,並且原本一個挺樂觀開朗的人漸漸的開始喜歡問候別人祖宗了。

我總結了幾類經常被艹祖宗的爛程式碼:

3.1.意義不明

能力差的程式設計師容易寫出意義不明的程式碼,他們不知道自己究竟在做什麼.

就像這樣:

Java
123456 publicvoidsave(){for(inti=0;i<100;i++){//防止儲存失敗,重試100次document.save();}}

對於這類程式設計師,我一般建議他們轉行。

3.2.不說人話

不說人話是新手最經常出現的問題,直接的表現就是寫了一段很簡單的程式碼,其他人卻看不懂。

比如下面這段:

Java
1234567891011121314 publicbooleangetUrl(Longid){UserProfile up=us.getUser(ms.get(id).getMessage().aid);if(up==null){returnfalse;}if(up.type==4||((up.id>>2)&1)==1){returnfalse;}if(Util.getUrl(up.description)){returntrue;}else{returnfalse;}}

很多程式設計師喜歡簡單的東西:簡單的函式名、簡單的變數名、程式碼裡翻來覆去只用那麼幾個單詞命名;能縮寫就縮寫、能省略就省略、能合併就合併。這類人寫出來的程式碼裡充斥著各種g/s/gos/of/mss之類的全世界沒人懂的縮寫,或者一長串不知道在做什麼的連續呼叫。

還有很多程式設計師喜歡複雜,各種巨集定義、位運算之類寫的天花亂墜,生怕程式碼讓別人一下子看懂了會顯得自己水平不夠。

簡單的說,他們的程式碼是寫給機器的,不是給人看的。

3.3.不恰當的組織

不恰當的組織是高階一些的爛程式碼,程式設計師在寫過一些程式碼之後,有了基本的程式碼風格,但是對於規模大一些的工程的掌控能力不夠,不知道程式碼應該如何解耦、分層和組織。

這種反模式的現象是經常會看到一段程式碼在工程裡拷來拷去;某個檔案裡放了一大坨堆砌起來的程式碼;一個函式堆了幾百上千行;或者一個簡單的功能七拐八繞的調了幾十個函式,在某個難以發現的猥瑣的小角落裡默默的呼叫了某些關鍵邏輯。

這類程式碼大多複雜度高,難以修改,經常一改就崩;而另一方面,創造了這些程式碼的人傾向於修改程式碼,畏懼創造程式碼,他們寧願讓原本複雜的程式碼一步步變得更復雜,也不願意重新組織程式碼。當你面對一個幾千行的類,問為什麼不把某某邏輯提取出來的時候,他們會說:

“但是,那樣就多了一個類了呀。”

3.4.假設和缺少抽象

相對於前面的例子,假設這種反模式出現的場景更頻繁,花樣更多,始作俑者也更難以自己意識到問題。比如:

Java
1234 publicStringloadString(){File file=newFile("c:/config.txt");// read something}

檔案路徑變更的時候,會把程式碼改成這樣:

Java
1234 publicStringloadString(Stringname){File file=newFile(name);// read something}

需要載入的內容更豐富的時候,會再變成這樣:

Java
12345678 publicStringloadString(Stringname){File file=newFile(name);// read something}publicIntegerloadInt(Stringname){File file=newFile(name);// read something}

之後可能會再變成這樣:

Java
123456789101112131415161718 publicStringloadString(Stringname){File file=newFile(name);// read something}publicStringloadStringUtf8(Stringname){File file=newFile(name);// read something}publicIntegerloadInt(Stringname){File file=newFile(name);// read something}publicStringloadStringFromNet(Stringurl){HttpClient...}publicIntegerloadIntFromNet(Stringurl){HttpClient...}

這類程式設計師往往是專案組裡開發效率比較高的人,但是大量的業務開發工作導致他們不會做多餘的思考,他們的口頭禪是:“我每天要做XX個需求”或者“先做完需求再考慮其他的吧”。

這種反模式表現出來的後果往往是程式碼很難複用,面對deadline的時候,程式設計師迫切的想要把需求落實成程式碼,而這往往也會是個迴圈:寫程式碼的時候來不及考慮複用,程式碼難複用導致之後的需求還要繼續寫大量的程式碼。

一點點積累起來的大量的程式碼又帶來了組織和風格一致性等問題,最後形成了一個新功能基本靠拷的遺留系統。

3.5.還有嗎

爛程式碼還有很多種型別,沿著功能-效能-可讀-可測試-可擴充套件這條路線走下去,還能看到很多匪夷所思的例子。

那麼什麼是爛程式碼?個人認為,爛程式碼包含了幾個層次:

  • 如果只是一個人維護的程式碼,滿足功能和效能要求倒也足夠了。
  • 如果在一個團隊裡工作,那就必須易於理解和測試,讓其它人員有能力修改各自的程式碼。同時,越是處於系統底層的程式碼,擴充套件性也越重要。

所以,當一個團隊裡的底層程式碼難以閱讀、耦合了上層的邏輯導致難以測試、或者對使用場景做了過多的假設導致難以複用時,雖然完成了功能,它依然是坨翔一樣的程式碼。

3.6.夠用的程式碼

而相對的,如果一個工程的程式碼難以閱讀,能不能說這個是爛程式碼?很難下定義,可能算不上好,但是能說它爛嗎?如果這個工程自始至終只有一個人維護,那個人也維護的很好,那它似乎就成了“夠用的程式碼”。

很多工程剛開始可能只是一個人負責的小專案,大家關心的重點只是程式碼能不能順利的實現功能、按時完工。

過上一段時間,其他人蔘與時才發現程式碼寫的有問題,看不懂,不敢動。需求方又開始催著上線了,怎麼辦?只好小心翼翼的只改邏輯而不動結構,然後在註釋裡寫上這麼實現很ugly,以後明白內部邏輯了再重構。

再過上一段時間,有個相似的需求,想要複用裡面的邏輯,這時才意識到程式碼裡做了各種特定場景的專用邏輯,複用非常麻煩。為了趕進度只好拷程式碼然後改一改。問題解決了,問題也加倍了。

幾乎所有的爛程式碼都是從“夠用的程式碼”演化來的,程式碼沒變,使用程式碼的場景發生變了,原本夠用的程式碼不符合新的場景,那麼它就成了爛程式碼。

4.重構不是萬能藥

程式設計師最喜歡跟程式設計師說的謊話之一就是:現在進度比較緊,等X個月之後專案進度寬鬆一些再去做重構。

不能否認在某些(極其有限的)場景下重構是解決問題的手段之一,但是寫了不少程式碼之後發現,重構往往是程式開發過程中最複雜的工作。花一個月寫的爛程式碼,要花更長的時間、更高的風險去重構。

曾經經歷過幾次忍無可忍的大規模重構,每一次重構之前都是找齊了組裡的高手,開了無數次分析會,把組內需求全部暫停之後才敢開工,而重構過程中往往哀嚎遍野,幾乎每天都會出上很多意料之外的問題,上線時也幾乎必然會出幾個問題。

從技術上來說,重構複雜程式碼時,要做三件事:理解舊程式碼、分解舊程式碼、構建新程式碼。而待重構的舊程式碼往往難以理解;模組之間過度耦合導致牽一髮而動全身,不易控制影響範圍;舊程式碼不易測試導致無法保證新程式碼的正確性。

這裡還有一個核心問題,重構的複雜度跟程式碼的複雜度不是線性相關的。比如有1000行爛程式碼,重構要花1個小時,那麼5000行爛程式碼的重構可能要花2、3天。要對一個失去控制的工程做重構,往往還不如重寫更有效率。

而拋開具體的重構方式,從受益上來說,重構也是一件很麻煩的事情:它很難帶來直接受益,也很難量化。這裡有個很有意思的現象,基本關於重構的書籍無一例外的都會有獨立的章節介紹“如何向boss說明重構的必要性”。

重構之後能提升多少效率?能降低多少風險?很難答上來,爛程式碼本身就不是一個可以簡單的標準化的東西。

舉個例子,一個工程的程式碼可讀性很差,那麼它會影響多少開發效率?

你可以說:之前改一個模組要3天,重構之後1天就可以了。但是怎麼應對“不就是做個數據庫操作嗎為什麼要3天”這類問題?爛程式碼“爛”的因素有不確定性、開發效率也因人而異,想要證明這個東西“確實”會增加兩天開發時間,往往反而會變成“我看了3天才看懂這個函式是做什麼的”或者“我做這麼簡單的修改要花3天”這種神經病才會去證明的命題。

而另一面,許多技術負責人也意識到了程式碼質量和重構的必要性,“那就重構嘛”,或者“如果看到問題了,那就重構”。上一個問題解決了,但實際上關於重構的代價和收益仍然是一筆糊塗賬,在沒有分配給你更多資源、沒有明確的目標、沒有具體方法的情況下,很難想象除了有程式碼潔癖的人還有誰會去執行這種莫名其妙的任務。

於是往往就會形成這種局面:

  • 不寫程式碼的人認為應該重構,重構很簡單,無論新人還是老人都有責任做重構。
  • 寫程式碼老手認為應該遲早應該重構,重構很難,現在湊合用,這事別落在我頭上。
  • 寫程式碼的新手認為不出bug就謝天謝地了,我也不知道怎麼重構。

5.寫好程式碼很難

與寫出爛程式碼不同的是,想寫出好程式碼有很多前提:

  • 理解要開發的功能需求。
  • 瞭解程式的執行原理。
  • 做出合理的抽象。
  • 組織複雜的邏輯。
  • 對自己開發效率的正確估算。
  • 持續不斷的練習。

寫出好程式碼的方法論很多,但我認為寫出好程式碼的核心反而是聽起來非常low的“持續不斷的練習”。這裡就不展開了,留到下篇再說。

很多程式設計師在寫了幾年程式碼之後並沒有什麼長進,程式碼仍然爛的讓人不忍直視,原因有兩個主要方面:

  • 環境是很重要的因素之一,在爛程式碼的薰陶下很難理解什麼是好程式碼,知道的人大部分也會選擇隨波逐流。
  • 還有個人性格之類的說不清道不明的主觀因素,寫出爛程式碼的程式設計師反而都是一些很好相處的人,他們往往熱愛公司團結同事平易近人工作任勞任怨–只是程式碼很爛而已。

而工作幾年之後的人很難再說服他們去提高程式碼質量,你只會反覆不斷的聽到:“那又有什麼用呢?”或者“以前就是這麼做的啊?”之類的說法。

那麼從源頭入手,提高招人時對程式碼的質量的要求怎麼樣?

前一陣面試的時候增加了白板程式設計、最近又增加了上機程式設計的題目。發現了一個現象:一個人工作了幾年、做過很多專案、帶過團隊、發了一些文章,不一定能代表他程式碼寫的好;反之,一個人程式碼寫的好,其它方面的能力一般不會太差。

舉個例子,最近喜歡用“寫一個程式碼行數統計工具”作為面試的上機程式設計題目。很多人看到題目之後第一反映是,這道題太簡單了,這不就是寫寫程式碼嘛。

從實際效果來看,這道題識別度卻還不錯。

首先,題目足夠簡單,即使沒有看過《面試寶典》之類書的人也不會吃虧。而題目的擴充套件性很好,即使提前知道題目,配合不同的條件,可以變成不同的題目。比如要求按檔案型別統計行數、或者要求提高統計效率、或者統計的同時輸出某些單詞出現的次數,等等。

從考察點來看,首先是基本的樹的遍歷演算法;其次有一定程式碼量,可以看出程式設計師對程式碼的組織能力、對問題的抽象能力;上機編碼可以很簡單的看出應聘者是不是很久沒寫程式了;還包括對於程式易用性和效能的理解。

最重要的是,最後的結果是一個完整的程式,我可以按照日常工作的標準去評價程式設計師的能力,而不是從十幾行的函式裡意淫這個人在日常工作中大概會有什麼表現。

但即使這樣,也很難拍著胸脯說,這個人寫的程式碼質量沒問題。畢竟面試只是代表他有寫出好程式碼的能力,而不是他將來會寫出好程式碼。

6.悲觀的結語

說了那麼多,結論其實只有兩條,作為程式設計師:

  • 不要奢望其他人會寫出高質量的程式碼
  • 不要以為自己寫出來的是高質量的程式碼

如果你看到了這裡還沒有喪失希望,那麼可以期待一下這篇文章的第二部分,關於如何提高程式碼質量的一些建議和方法。

相關推薦

關於程式碼那些

1.摘要 最近寫了不少程式碼,review了不少程式碼,也做了不少重構,總之是對著爛程式碼工作了幾周。為了抒發一下這幾周裡好幾次到達崩潰邊緣的情緒,我決定寫一篇文章談一談爛程式碼的那些事。 這裡是上篇,談一談爛程式碼產生的原因和現象。 2.寫爛程式碼很容易 剛入程式

關於代碼的那些

天才 是個 莫名其妙 多項目 變量 ++ 經歷 新功能 修改 轉自:http://blog.2baxb.me/archives/1343 1.摘要 最近寫了不少代碼,review了不少代碼,也做了不少重構,總之是對著爛代碼工作了幾周。為了抒發一下這幾周裏好幾次到達崩潰邊

痞子衡嵌入式:飛思卡爾Kinetis開發板OpenSDA偵錯程式那些- 背景與架構

  大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家介紹的是飛思卡爾Kinetis MCU開發闆闆載OpenSDA偵錯程式(上篇)。   眾所周知,嵌入式軟體開發幾乎離不開偵錯程式,因為寫一個稍有程式碼規模(5K行以上)的嵌入式應用程式一般不可能一次性搞定,沒有任何bug,出了bug並不可怕,只要我

關於程式碼那些

1.摘要 這是爛程式碼系列的第二篇,在文章中我會跟大家討論一下如何儘可能高效和客觀的評價程式碼的優劣。 在釋出了《關於爛程式碼的那些事(上)》之後,發現這篇文章竟然意外的很受歡迎,很多人也描(tu)述(cao)了各自程式碼中這樣或者那樣的問題。 最近部門在組織boo

關於程式碼那些

假設你已經讀過爛程式碼系列的前兩篇:瞭解了什麼是爛程式碼,什麼是好程式碼,但是還是不可避免的接觸到了爛程式碼(就像之前說的,幾乎沒有程式設計師可以完全避免寫出爛程式碼!)接下來的問題便是:如何應對這些身邊的爛程式碼。 1.改善可維護性 改善程式碼質量是項大工程,要

淺談 kubernetes service 那些

缺陷 resolv 拆分 load 性能 mar 基本原則 -i let 歡迎訪問網易雲社區,了解更多網易技術產品運營經驗。一、問題首先,我們思考這樣一個問題:訪問k8s集群中的pod, 客戶端需要知道pod地址,需要感知pod的狀態。那如何獲取各個pod的地址?若某一no

一文搞懂C/C++中指標那些

一 指標變數 1.間接存取        指標變數的值為地址;普通變數的值為資料;其中“*”為指標運算子。&是地址操作符,用來引用一個記憶體地址。通過在變數名字前使用&操作符,我們可以得到該變數的記憶體地址。        針對記憶體資料的

軟件project—思考項目開發那些

app 爛代碼 fontsize 模式 大型 不明確 極限 後拋 con 閱讀文件夾: 1.背景2.項目管理,質量、度量、進度3.軟件開發是一種設計活動而不是建築活動4.高速開發(簡單的系統結構與復雜的業務模型)5.技術人員的業務理解與產品經理的業務理解的終於業務模型

TCP 的那些事兒

fas 也說 alt hal 收獲 很好 浪費 服務器 book http://coolshell.cn/articles/11564.html TCP是一個巨復雜的協議,因為他要解決很多問題,而這些問題又帶出了很多子問題和陰暗面。所以學習TCP本身是個比較痛苦的過

懷孕、產檢的那些

div 報告 mage 北京大學 之前 clas spa 測試 class 小孩在北京大學深圳醫院出生的,產檢也是在那裏,以下說的都是北大醫院的情況,每個醫院可能不相同,想起多少寫多少吧。第一次寫,寫的很亂。 最開始呢是發現媳婦沒有來月經,然後就去藥店買了早早孕試紙(選擇在

玩兒蟲那些—— 使用curl

nod -h div sel ant validate 空間 pre rap 目錄 一、爬一個簡單的網站 二、模擬登錄新浪 三、各種請求的發送 四、使用curl 五、模擬登錄QQ空間 六、selenium的使用 七、phantomjs的使用 八、開源框架webmagic

我和 WebSocket 的那些

com lis 都沒有 情況下 系統 並不是 任務管理 js實現 因此   我的策劃大佬離職了,在他go之前我都沒有解決好一個問題,感覺如果我換了工作面試的時候,別人問到 “你在做項目的時候,遇到的最頭疼的問題是什麽,是怎麽解決的?”,首先想到的應該也是他,今天感覺是時候寫

工作那些十一談談碼農與農民工區別和發展之路 工作那些十二如果哪一天,沒有了電腦 工作那些十三再次失業

工作那些事系列連結快速通道,不斷更新中: 工作那些事(一)今年工作不好找 工作那些事(二)應聘時填寫個人資訊ABCD 工作那些事(三)什麼樣的公司能吸引你,什麼樣的公司適合你? 工作那些事(四)大公司VS小公司 工作那些事(五)談談專案資料整理和積累 工作那些事(六)談談

淺談 kubernetes service 那些 下篇

歡迎訪問網易雲社群,瞭解更多網易技術產品運營經驗。 五、K8s 1.8 新特性——ipvs ipvs與iptables的效能差異 隨著服務的數量增長,IPTables 規則則會成倍增長,這樣帶來的問題是路由延遲帶來的服務訪問延遲,同時新增或刪除一條規則也有較大延遲。不同規模下,kube-proxy新增一條

mybatis快取那些

前言 前面在mybatis快取那些事(一) 中,我們介紹了mybatis的一級快取。這裡再和大家一起學習下mybatis中的二級快取。 MyBatis的二級快取是Application級別的快取,它可以提高對資料庫查詢的效率,以提高應用的效能。 MyBatis的快取機制整體設計以及二級快

關於JAVA你必須知道的那些:繼承和訪問修飾符

今天乘著還有一些時間,把上次拖欠的面向物件程式設計三大特性中遺留的繼承和多型給簡單說明一下。這一部分還是非常重要的,需要仔細思考。 繼承 繼承:它是一種類與類之間的關係,通過使用已存在的類作為基礎來建立新類。其中已存在的類稱為父類(或基類); 建立的新類稱為子類(或派生類)。簡單的就是子類繼

關於JAVA你必須知道的那些:封裝

時隔近一年,我突然想起來這個文章還沒有發完,所以就繼續開始寫。也不知道自己上次寫到哪裡了,不管了這裡從面向物件的三個特性說起。 類和物件 在這之前,我們先了解什麼是物件,已經什麼是面向物件?物件:萬物皆物件,現實中實際存在的事物都可以看成一個物件。而面向物件就是人在關注物件, 關注事物的資訊

關於JAVA你必須知道的那些:單例模式和多型

好吧,今天一定要把面向物件的最後一個特性:多型,給說完。不過我們先來聊一聊設計模式,因為它很重要。 設計模式 官方的解釋是,設計模式是:一套被反覆使用,多數人知曉的,經過分類編目,程式碼設計經驗的總結。說人話就是:軟體開發人員在軟體開發過程中面臨的一般問題的解決方案。 常見的設計模式可以

vue前端開發那些1

  如上圖所示,用vue開發一個小型網站所涉及到的知識點。這只是前端部分已經這麼多了。接下來我分解開來說。    1、Node    當我們開發vue專案的時候,首先要安裝Node.js,那麼我們即使當時不理解為什麼,但是專案完成後,應該抽個空,理解下。有兩個問題:    a、什麼是Node?    b、No

大神是如何走過來的,六個專案程式碼瞭解一下

1、抓取知乎圖片,只用30行程式碼: import re # 視訊資料分享 QQ群 519970686 from selenium import webdriver import time import urllib.request d