咱們常常談論架構,討論設計,卻甚少關注實現和代碼自己,架構和設計當然重要,但要說代碼自己不重要,我不一樣意,Robert C.Martin大叔也不一樣意,Martin認爲「源碼即設計」。linux
在討論具體的實施細則以前,咱們不妨討論一下什麼是好代碼?蘿蔔特 C.Martin認爲:衡量代碼質量的惟一標準是:WTF/min,也就是review代碼的時候每分鐘說「握草」的次數。這個定義雖有辱斯文,但粗野中不失奔放,調皮中又蘊含哲理。nginx
好的代碼如同文筆優美的散文,行雲流水,賞心悅目,閱讀的時候,如沐春風,帶給人愉悅與啓迪。c++
好的代碼猶如構思精巧的小說,它或許不夠平鋪直述,卻足夠引人入勝,讀到最後,你會豁然開朗,我去,原來是這樣的啊,那一刻,你會以爲過程當中的曲折和探索都是值得的。程序員
好的代碼,透過一個個函數,你彷彿能夠窺視到做者有趣的靈魂;透過一行行代碼,你彷彿在與一個充滿智慧的朋友聊天,她老是條理清晰,邏輯嚴謹,有條不紊,娓娓道來。編程
而壞的代碼,猶如病毒,它不只癱瘓你的程序,還有很強的傳播效應,等到它擴散開來,神仙難治。安全
壞的代碼,像一個泥團,閱讀的時候,你彷彿被困於黑暗的迷宮,又彷彿在跟一個絮絮不休的人交談,她的腦回路常常短路,說話含混不清,主次不分,叨逼半天,你依然get不到她的中心思想,你經常感受智商受到了莫大的侮辱,你面露艱難神色,心中萬馬奔騰。架構
有不少區分好壞代碼的規則,我也看過一些,對於文章中提到的一些標準作法,就不重複嚼舌頭根子了,我想結合本身的工做經歷,談一談本身的切身體會。框架
閒扯半日,言歸正傳,要編寫瀰漫好味道的代碼,要遵循哪些約束和指引呢?模塊化
鍥而不捨的聽從一致性規則,在代碼風格上,爭論個三天三夜估計也定不出個好壞出來,但好的風格必定是強一致性的,這一點應該比較容易達成一致吧?風格的好壞其實更多受習慣的影響,頭髮少一點的程序員應該都有本身風格變遷的經歷,多年前本身篤信不疑的good style或許正是當前深惡痛絕的bad style,因此我主張在style上擱置嘴炮,一個項目應該有一個編碼規則,好的規則應該是以理服人的,好的規則應該是拒絕任性夾帶私貨的,規則定了以後,就遵守執行吧,可能某個風格跟你不相符,但不要緊,你要知道,這並不意味,你在style之戰敗下陣來,也並不表示它說服了你,你遵照的是規則和紀律自己。函數
變量(包括文件、類/結構體、函數)命名,好比ohmygod,你可能搞不清哪些字母是一夥的,因此須要界定單詞。駝峯經過單詞首字母大寫來界定單詞,另外一個慣用作法是用下劃線拼接單詞。駝峯的弊端是醜,下劃線拼接的弊端是增長了標識符長度(相比首字母大寫),好處是跟std c/c++、linux kernel的作法一致,喜歡kernel的碼農容易找到如家般的歸屬感。
c++有namespace避免衝突,c常常用prefix防止命名污染全局空間,但我認爲命名簡潔扼要很重要,因此我支持簡短的前綴而反對冗長的前綴。
實現一樣的功能,你喜歡100行代碼,仍是20行代碼?若是貴leader不以代碼行數考覈績效我建議你把代碼寫的精簡,而若是貴leader以代碼行數考覈績效,我建議你轉行,開滴滴,送外賣或者擺攤都行,由於在這樣的leader下面耗費青春基本上也不會有什麼發展前途。
把簡單的東西搞複雜化很容易,你只須要找一個能力平庸的人就能實現化簡爲繁的願望,而化繁爲簡則堪稱化腐朽爲神奇。也許你要說,我欠缺簡化的能力,這並不奇怪,坦白講,這不是一件容易的事,你作不到不要緊,但你擁有正確的理念更重要,它將幫助你認清前進的方向,而不是在錯誤的道路上越走越遠。
有些項目,充斥各類無效代碼,其實你只須要稍加思考,你就能識別出來。
好比大塊註釋掉的代碼像發臭的屍體同樣遍及其中。好比大量功能重複的代碼像垃圾同樣堆砌在那裏。好比本不須要返回值的函數恆定的返回true。
又或者函數一進來,無論三七二十一,對入參一頓檢查,全然忘記你在編寫的是一個私有實現函數,你在調用它以前已經檢查過一遍,私有函數是一個受控的安全上下文,這不只不優雅並且不綠色(低效耗電)而且不安全(在該崩的時候沒崩把雷埋到了更隱蔽的地方),話說你看標準庫函數strcpy/strcat,vector operator[]檢查傳參了嗎?
提升代碼密度或者說濃度有利於理清思路,有利於突出重點,有利於提升維護性,而充斥各類無效語句的代碼只會把關鍵語句淹沒在汪洋大海,使得review代碼的人get不到重點,看不清主次。像聽一個絮絮不休的人作報告,滿篇廢話,像看一個劇情拖沓的連續劇,昏昏欲睡,像喝一瓶二鍋頭兌十斤白開水,口能淡出個鳥來。
重構是程序員的口頭禪,重構是在保持程序功能不變的狀況下調整架構和實現,我認爲提升代碼密度應做爲重構的一項追求。
linux kernel、lua、nginx、skynet這些優秀的開源庫代碼濃度都很高,建議讀者朋友品嚐一下。
咱們最常乾的一件事就是把重複編寫的代碼封裝到一個函數裏去,用多處調用替代重複編寫,這個很好理解,但其實即便不被多處調用,把相關的一段代碼封裝到一個實現函數也是有必要的,由於把代碼平鋪開來,把細節暴露出來,容易掩蓋重要的東西,即框架和脈絡會變得不夠清晰。
一個見名知義的函數調用比堆砌在那裏的一段代碼給個人感覺好,我若是關心它是怎麼作的,我能夠跳轉到定義看看實現。
封裝的一個核心原則是單一職責,符合單一職責的函數更易於被複用。
linus大神分享過他心中的好代碼,說的是針對鏈表的操做,他更喜歡統一性的處理方式,而不是作特例化的處理,我想這個例子頗有表明性,它其實表明一種理念,那就是自始至終,咱們的頭腦裏必須優先考慮normal化的處理方式,固然這實際上是一個比較高層次的要求,菜鳥互啄能夠先跳過這一層要求。
慎用縮寫,相比縮寫帶來的含混不清,我寧願多敲幾下鍵盤,若是要縮寫請符合慣例聽從常規,好比AI,好比App,好比cfg,可是你把threshold縮寫成threshod,把Item縮寫成Iem,我特木真的搞不懂這是拼錯了仍是縮歪了?
構建鬆散耦合的系統一直是軟件工程的一個目標,模塊化的一個方向即是解耦,但咱們口口聲趁心心念想的解耦,在實施層面又有幾分體現呢?
好比,我常常乾的一件事就是把相似配置文件,或者宏定義的東西集中的一個頭文件裏去,看起來很統一也很正規,起碼我以前也是這樣認爲的,但突然有一天,發現本身這樣作顯得很不聰明的樣子,爲何呢?由於你想把全部模塊配置相關的東西都塞進配置公共文件真的合適嗎?是否是把公共接口抽離出來更好,把配置相關的數據下沉到各模塊更合適?
另外,把宏都定義到一塊兒,這意味隨便改點東西,都會須要修改宏頭文件,而這個頭文件就會成爲程序世界的中心,修改公共宏文件幾乎會引發整個系統的全部源文件rebuild,這簡直就是AOE團滅啊。因此更好的方式是分而治之,去集中式。
咱們知道c/c++的編譯單元是source file(.c/.cpp),編譯的第一步是預處理,全部include都會展開替換,因此咱們要避免引入任何沒必要要的頭文件,也應該把本編譯單元用到的頭文件都include進來,這就是所謂的頭文件自給自足。這點很重要,但不少人會不覺得然,甚至有些人會自做聰明的搞一個allincluded.h,把經常使用的一些頭文件所有include進來,而後自認爲一勞永逸的完美的解決了問題,包含沒必要要的頭文件會增長編譯時間,會增長依賴,咱們不只應該避免錯誤的包含,還應該精心設計和劃分文件,使得每一個文件的功能足夠內聚單一。
我遇到每一個模塊單獨定義本身的各類原生(build-in)數據類型,但我建議不要這樣作。若是你只是須要解決不一樣體系結構下long等整型的長度差別,我想告訴你,c庫頭文件stdint.h已經從標準層面統一解決了這個問題,裏面int8_t/16_t/32_t/64_t,還有uint8_t等等應有盡有。
宏是c的一個有效武器,在有些狀況下確實行之有效,關於宏,我是騎牆派,我既反對禁用宏,也反對濫用宏,inline能夠部分替代宏,但不能徹底替代宏。
若是項目裏處處都是宏,全大寫,至少1/3的代碼都是各類詭異的宏,你review代碼的時候,不停的跳來跳去,看了一眼,哦,就這樣啊,而後切回來,頻繁的上下文切換是低效的,它打斷了你的思路,其實不少時候徹底沒有必要。
命名有一些指引,好比類/結構體應該用名詞,函數應該用相似動詞或者doSomething這樣的動賓結構,這些規矩都是耳熟能詳的。
我主張命名應該簡明扼要,不要羅裏吧嗦,要準確的表達出它要作的事情,若是你碰到命名困難,你可能須要考慮你的類定義或者接口劃分是否合適。
命名是接口的一部分,很重要,好的命名是自注釋的。
我反對匈牙利命名法,理由:不能一致性的解釋各類類型,把類型編碼進變量不合理,變量名自己就能體現它的類型,沒法適用template狀況,始做俑者ms放棄了它。
若是你沒有思路,那我建議你參考一下STD C/C++ API,畢竟這些接口歷經幾十年沒有大的變化,算是經受住了歷史的考驗,好比malloc/free/atoi,stl 容器的成員函數也有點意思:size()、 capacity()、resize()、reserve()、push()、pop()、top()、back(),很乾脆,不廢話,我以爲很好。
因此,若是你編寫的是某某管理器,好比ItemManager,我建議你直接取名add(),remove(),而不用AddItem(),RemoveItem(),由於你自己就是Item的Manager,操做的必然是Item,並且從參數上也能體現出來,少便是多,多不如少。
開閉原則是應對擴展性的rule,人無遠慮必有近憂,說的是咱們不能侷限於眼前,但也請不要過分設計,不可盲目迷信擴展性,戲太多也是病。
知乎有一篇神貼講的是如何把helloworld搞成一個big project,當你想給別人項目挑刺的時候,你能夠用擴展性說事,但我建議你離開口閉口擴展性的人遠一點,據我觀察,這種人大多比較虛僞並且很水。
有不少避免運行低效的作法,好比哈希、減小拷貝、提升局部性、buffer/cache、空間時間置換、內聯、分支預測、判斷前置、計算延遲、無鎖編程。
提升魯棒性的關鍵是保持簡單,任何引入複雜性的動做都須要保持足夠警戒。不信任/零信任設計,面向failed編程,假設依賴的上下文,上下游都是不可靠的,去中心化去關鍵路徑,熔斷,降級,避免驚羣效應,方法不少,不一一列舉了。
做者:華爲雲專家 人民副首席碼仔