如何编写好的代码?
hi,各位小伙伴,大家好,最近主導項目正在進行code review,發現不同人寫代碼風格不一樣:
完成任務型,怎么簡單怎么來,目的快速完成任務,盡量復制粘貼搞定,沒有自己的代碼設計思想,代碼雜亂無章,不喜歡重構,bug多。
實用性,注重簡潔,能不新增代碼,就盡量不新增代碼,設計比較簡單,擴展性一般,但可以滿足需求,不到萬不得已不會重構代碼,對系統侵入性小,bug少。
過度設計型,注重解耦(添加中間層),可以稱為解耦大師,代碼層次豐富,可擴展強,喜歡重構老代碼,一般人不易看懂,需要花大量時間理解其設計,對系統侵入性大,bug多(因為新增代碼多,bug也會增多,需要各種測試手段才能讓其穩定下來)。
你是哪種類型呢,還是混合型(不同情況,不同比例)?
讓我們看一看如何編寫好的代碼。
Programs are meant to be read by humans and only incidentally for computers to execute. ——Donald Knuth
“代碼始終是寫給人看的,只是恰好能被計算機執行。”
什么是好的代碼?局部干凈,核心邏輯簡潔。
本文是一篇總結筆記,是以往工作學習中關于如何實現“局部干凈”的一些見聞、教訓、團隊實踐和一些思考。寫出整潔代碼不僅需在函數、類級別上用功,也應該理解一些其他主題,如項目架構、設計原則等,軟件工程是復雜(complex)的,只有各個方面都處理得干干凈凈,才能在整體上做到代碼整潔。
指導原則:消除重復,分離關注點,統一抽象層次
程序員終其一生所做得事大抵不超過這幾個層次
函數與類
包與模塊(依賴)
服務(系統)與服務域
產品
在各個層面,這十五個字都足以作一些指導或參考。
消除重復
重復的代碼會讓系統臃腫,難以維護,增加程序員的心智負擔。消除重復的手段不外乎封裝,抽取函數、類等。
代碼重復
完完全全重復的代碼,應該抽取出公共的函數。同一段代碼出現兩次及以上,就應該抽取出函數。
結構重復
代碼雖然不一樣,但結構類似,也應該抽取。結構重復可以推導出一些高級技術,如
繼承體系
泛型
模板方法(template method,四人幫 23 種設計模式之一)
高階函數,lambda
可惜的是,這些在 golang 里支持不夠,各有喜憂。
過程重復
如果總是重復做同一件事,應該使其自動化。
分離關注點
物以類聚,人以群分,代碼也是一樣。關注點相同的代碼應該在一起,天然具有親和性,這句話的另一個含義,對關注點不同的代碼天然具有隔離性,相互之間不應該太深入了解。
分離主線和支線
這是最應該注意的,特別是在業務代碼開發中。主要業務邏輯是主線,應該突出主線,淡化支線,按照人的思維,這樣才是好理解的。例如旋律音和伴奏音,應該突出旋律,而淡化伴奏。假使伴奏音和旋律音差不多強,喧賓奪主,這樣的音樂一定是難聽的,因為我們聽不出旋律。代碼也是這樣,應該突出主線,使核心邏輯一目了然。
例如在下單的邏輯中,可能的主線是:檢查庫存、檢查余額、生成訂單。那這個下單方法里就應該只有 3 行代碼,而不應該有諸如權限判斷、性能記錄等,如果出現就會有 2 行代碼是跟主線無關的,造成不必要的干擾,不要造成無謂的心智負擔,應該解放心智去完成更復雜的事情。
分離主線和支線的技術如:
AOP
interceptor、filter 等
分離技術和業務
技術型代碼常常是公用的,如日期計算、日志記錄、性能測量、數據庫鏈接、基礎工具類。這些應該和業務邏輯分開,相信這點大家都沒有疑問。
按業務性質分離
對業務開發來說,業務知識永遠都是第一位的。一個技術水平很高的程序員,但是對業務不理解,他也發揮不了全部水平,就像殺雞用牛刀,施展不了全部功力。不同業務應該分開,在模塊級、服務級甚至更高的產品級,這也應該是共識。但是在一個系統內部,推薦也應該按業務分成不同的包,同一業務下的對象是天然親和的,同樣也是對不同業務的對象是隔離的。
分離變化快慢的代碼
變化快的代碼和長年不變的代碼分開。
分離性能高低的代碼
重 I/O 的代碼和重 CPU 的代碼理應分開,方便合理分配資源,其他諸如此類的代碼應該注意分開。
統一抽象層次
將有關認識與那些在實際中和他們同在的所有其他認識隔離開,這就是抽象,所有具有普遍性的認識都是這樣得到的。——John Locke 《關于人類理解的隨筆》
怎么理解抽象?抽象的反面是具體,具體是細節,可見抽象是細節的反面,抽象刻畫了統一的畫像,描述能力,是對事物在某些方面的特征的提取總結。總之,抽象表達的是意圖,另一個理解就是,它不表達細節。“Tom 要成為世界首富”,這句話的抽象層次就很高,意圖很明顯,但是關于 Tom 如何成為世界首富、用什么貨幣衡量等細節,一概不知。抽象層次高,偏意圖,語義(代碼在上下文中表達的語義)清晰,信息量小;抽象層次低,偏實現,語義模糊,信息量大。
兩個原則:
同一抽象層次上的對象才能直接對話;
同一抽象層次上的對象之間存在著緊密合作;
典型的函數結構
一個好的函數結構,應該這樣像一棵樹一樣層次分明。一方面,每一個層次都只有 2~5 個步驟,一般而言我們做一件事也就 2~5 個步驟,分解太多太少都不好,太少沒必要分解,太多記不住,增加心智負擔。實際上,更多的情況,我們都喜歡 3 這個數字,例如在會議總結時,總結 3 點足夠了,更多估計不會有太多人愿意繼續集中注意力聽超過 3 點的總結。所以一個好的函數,不應該超過 5 行,我們之所以做不到,除了抽象層次劃分不準確之外,還有很大一部分原因是表達能力不足,畢竟英語不是我們的母語。(函數 10 行代碼,是我在過去工作中合代碼的及格線,20 行是紅線。)另一方面,只有葉子節點才表達實現,非葉節點都應該表達意圖。
以“把大象裝進冰箱”為例,不外乎三步:
打開冰箱門
放進大象
關閉冰箱門
所以關于如何把大象切成碎片,不應該出現在上面,應該在步驟 2 的后續調用中。
由這個函數結構還可以得見,好的程序讀起來應該像自然語言,極少部分像數學語言(偏算法),不好的程序讀起來就像是程序。當我們讀一段程序,一眼看去它就像是程序,那不是它太好,它就是不夠好的。一直認為,寫作能力才是成為優秀程序員最重要的能力。
隔離與隱藏
信息隱藏,是抽象的一種手段。通過信息隱藏,來暴露只想讓外界知道的東西,表達意圖。隔離是實現信息隱藏的重要手段。隱藏與隔離有一個天然的好處,例如我們有一個包,我們只提供數個 public 的方法,包內的其他對象、方法都只是包可見的,這樣,我們可以隨意修改內部實現,只要保證那些 public 方法的行為不變。特別是對于復雜系統,如果做不好隔離與隱藏,到處都是 public 方法,到后面誰都不敢隨意改動代碼,誰也不知道哪位大哥在方法上加了一個 if-else 分支。
編碼 tips
以下都是一些簡單實用的技術,以如何寫出整潔代碼,很多是出自《代碼整潔之道》,一些是出自過去團隊的經驗。
1. 類
類應該足夠小
最初級的程序員可能會在一個 Controller 里做完所有的業務邏輯,最終會使這個類成為 God Class。一個類太大,代碼太多,會使類的結構不清晰,職責混亂,維護代碼時花費很多時間去尋找修改位置。譬如我們所見的世界,由分子、原子甚至更小的粒子排列組合而成,所以才有繽紛多彩的各色物質(對象),但如果構成物質的最小粒子就是人,那還能組合出什么其他物質呢?代碼也是如此,類應該足夠小,才能發揮排列組合的威力。
單一職責
類的職責應該單一,即“SOLID”五大原則的 S,職責單一意味著,“只有一個理由可以修改它”。另外,類名一般而言應該是名詞,且描述其職責。
如果無法為一個類名以精確的名稱,這個類大概就太長了。類名越含混,該類越有可能擁有過多的權責。
——《Clean Code》
內聚
內聚的含義是,類的每一個字段都應該被某個(些)方法所使用到。如果不能達到這個結果,應該考慮是否類的字段應該拆分出去成為新的類。
嚴格控制訪問權限,注意信息隱藏,OCP
訪問權限應該能小則小。能 private 就不要 package,能 package 就不要 protected。這樣做能使我們更好的遵循 OCP 原則。最穩定的系統,是從不修改的系統。
2. 函數
盡可能小
經過漫長的試錯,經驗告訴我,函數就應該小 ——《Clean Code》
應該控制在 10 行以內,至多 20 行,除非是細節代碼。這是完全可以做到的,做不到的原因可能有:函數功能太多,職責不單一;函數抽象層次劃分不清;語言支持不夠等。前面已經說過,做一件事大概也就 2~5 步,每一步一個函數,加上可能的條件判斷,10 行是一個比較合理的數字。而且,函數越小,功能越集中,越便于取一個好名字。
單一職責
一個函數只做一件事。這一點很容易理解,難的是我們如何確定函數做的那件事是什么。一千個讀者就有一千個哈姆雷特,同樣的,不同的人對一個函數的理解也有所不同,對于做一件事的步驟拆分也可能有所不同。對此,一個可靠的判斷準則是:函數的內容(函數體內的代碼)只是做了函數所在抽象層級的步驟,那這個函數就是只做了一件事。函數所在抽象層級,根據對業務的理解,應該用良好的函數名加以示意。
單一抽象層次
一個函數應該只在一個抽象層次上。計算機世界都是層層疊加的,例如:寄存器 -> 高速緩存 -> 主存 -> 硬盤 -> 網絡(可參見《CSAPP》第六章),再如硬件 -> 機器指令 -> 匯編 -> C -> C++ -> JVM -> Java -> Servlet -> Spring -> SpringBoot。嚴格禁止跨層次搞事。我們應該熟悉業務,根據業務上的一次用例,劃分抽象層次,使每一個函數都只在某一個抽象層次上,不要跨層次。還是以把大象裝進冰箱為例:
最頂層的函數是 f,f 里就只應該有 s1, s2, s3 三個函數。s2a, s2b 里的實現代碼則不應該出現在 f 里。同理在 s2 函數里,只應該有 s2a, s2b 函數,而不應該有抽象層次更低(更具體)的 s2aα, s2aβ 的實現代碼等。
綠色部分是最低抽象層次的具體實現,這部分是無法拆分,且難以控制代碼行數的,因為有些情況下做一件事就是有很多細節實現步驟。
參數盡量少
最理想是 0 個,其次是 1 個,2 個,最多 3 個參數,不要超過 3 個參數,除非你有非常特殊的理由。——《Clean Code》
參數帶了極大的語義干擾,而且也難于測試。一個典型的不好的設計,就是用 bool 作為公開函數的參數,因為 bool 變量天然地會使人想到這個函數不會只做一件事,它分情況處理,bool 入參的命名稍有歧義就會使人困惑。例如
func?GoToWork(raining?bool)?{if?raining?{//?開車去}?else?{//?走路去} }更推薦的做法是,將 bool 參數的函數私有,另外公開兩個語義清晰的函數。
func?WalkToWork()?{goToWork(false) } func?DriveToWork()?{goToWork(true) } //?私有 func?goToWork(raining?bool)?{if?raining?{//?開車去}?else?{//?走路去} }任何時候,我們維護代碼,最關心的都是對外可訪問的函數,這些函數應該盡我們所能使其整潔。另一個例子,在 JUnit 里曾有這樣的方法,不知給多少初學者帶來困擾
assertEquals(expected,?actual)對使用者來說,完全沒有必要去記憶兩個參數的相對位置。相較而言,assertJ 里的連貫式接口就要友好得多
assertThat(actual).isEqualTo(expected)golang 里能夠返回多個返回值,但這絕不可以濫用。試看
func?func1(/*?params?*/)?(string,?string,?string,?string,?string)?{//?函數職責不單一,功能太多 } func?func2(/*?此處多達6個參數?*/)?{//?函數職責不單一,功能太多 }這樣多入參、多返回值,給調用方造成很大困擾,調用方需要反復分辨每個參數、返回值的對應關系。不能因為眼前就只有自己調用自己寫的函數而這樣放縱,我們寫的代碼,終究是會由別人接手的。
無副作用
一般而言,函數應該是無副作用的,對于調用方來說,它就是一個黑盒:給定輸入,產生輸出。僅此而已。不要讓調用方去思考我這次調用會不會產生輸出以外的其他結果。例如應該盡量避免這種情況:一個函數,以指針作為參數,返回一個結果的同時,還修改了指針所指向的內容。一個函數的作用,要么是 get,要么是 post,即要么函數無修改的 get 一個結果,要么就是單純修改而不返回修改以外的結果。jdk 里有一個典型的反例,各種集合的 add/set 總返回了一個 bool 值,就會出現這樣的代碼
//?numbers?is?a?list if?(numbers.add(1))?{// }對于新手這可能就是一個讓人迷惑的地方,可見,無副作用也不是絕對的,強如 JDK 也有不得已的折衷處理。
if 嵌套不應超過 2 層
if 不要嵌套超過 2 層,這初聽起來有些強人所難,仿佛要求每個職業籃球運動員都應該以喬丹的能力作為基準。可人的天性就是不喜歡思考的,喜歡簡單。在此再一次強調統一抽象層次,if 嵌套太多,一定要思考,是不是函數做的事情太多,跨層次在搞事情。我們應該用一些高標準去檢驗自己的代碼,想辦法去滿足,這個過程才會有所成長,否則除了收獲經驗以外,不會有進階的成長(其實人生又何嘗不是如此)。
消除多層 if 嵌套的一些手段
提前返回,將嵌套 if 鋪陳開來,使不滿足條件的分支提前返回;
碰到第三個 if,直接將其抽取為函數(簡單粗暴);
lambda,在 Java 里利用 stream 的扁平化處理,使 filter、map 等語法元素都可以接收簡單的函數,從而避免在 for 里加 if 判斷。對于集合的遍歷處理,都應該盡量先采用 stream 的做法,這種流水線的思想,在一個步驟里就剔除了不滿足條件的對象,然后流轉到下一個步驟。
語義和實現距離不為 0 時應該抽取函數
好的代碼讀起來就應該像自然語言,而不是像程序,這就要求在高抽象層次時,函數應該表達意圖,而只有在葉子結點——抽象層次最低的實現部分才表達實現,這個地方的代碼更像是程序。所以,在代碼中的某個位置,我們本應該表達意圖,卻寫了細節實現代碼,這就應該抽取出函數。以下面這段代碼為例。
tom?:=?&Person{} if?tom.Age?>=?18?{//?do?your?bussiness }一般認為這是表達 tom 是否成年,但實際的業務含義中卻是判斷 tom 是否可以申領 C1 駕照。即使是想表達是否成年,這樣也要使大腦經過一層轉換,由Age >= 18推理一次,才能得出結論這是表達是否成年,這是典型的“代碼 prase 語義”,不要小看這層 parse 對人腦的開銷,特別是所見之處都是這樣的代碼會讓我們的大腦長期忙于“線程切換”活動,造成的思維停頓讓人非常沮喪;此外,如果一個日本人看到這段代碼,一定不會想是表達是否成年這個語義,因為他們的法定成年年齡是 20 歲(2022 年 4 月 1 日起改為 18 歲),這是代碼不靈活的體現。推薦的做法是
if?tom.isAdult()?{//?do?your?bussiness } func?(p?*Person)?isAdult()?bool?{return?p.Age?>=?18 }這樣,在isAdult方法里還可以更改實現,也更靈活,很多時候,如果我們程序寫得好,實現比較靈活,就能夠從容的應對經常變化的需求;如果需求稍微變化一下,現有代碼就頂不住了,就應該思量實現是否足夠好。代碼應該表達意圖,特別是 if 條件分支里,不要讓人再去推理,直接表達語義。就像人走路,相比于一馬平川,我們不會更喜歡岔路;但凡岔路,就應該明確指明路線,而不是在路口打個機鋒,才讓你思考十年然后頓悟才選擇出了某一條路。
童子軍軍規
走的時候,比來的時候干凈一點。代碼中如果我們能經常注意這一點,那我們每時每刻都在改善代碼。世界是朝著熵增的方向發展的,譬如一個房間,即使我們完全不去干擾它,久而久之它也會變得更加混亂,代碼也是這樣,它終究會變得越來越混亂、難以修改、難以維護。如果我們不注意這一點,反而每次來都扔一點垃圾,久而久之就會成為“破窗”直至“破樓”。
hardcode
任何時候都不應該在代碼中直接出現 hardcode,hardcode 難以表達語義,且難以管理。
3. 命名與注釋
命名是一個哲學問題,我們所知的一切,都是命名,存在、宗教、知識、倫理...沒有命名,我們所知的一切所謂知識都將崩塌。
There are only two hard things in Computer Science: cache invalidation and naming things. ——Phil Karlson
“計算機世界只有兩個難題:緩存失效和命名。”(可讀一讀《CSAPP》關于存儲層次結構的描述,對此會深有體會。)
坊間流傳著一句話,給變量命名猶如給自己親女兒命名一般,只因如此,就不會隨意命名了。命名的一般原則無外乎完整、簡潔、準確等。
顧名思義、望文知義、無歧義
清楚明白無歧義地表達含義,不要讓別人猜你的意思。在 API 設計里,有一條原則即是“Don't Let Me Think”,命名也應該如此,乃至日常工作溝通中也應當如此。
名副其實
cat := &Dog{}?
表達語義,避免誤導
userList實際實現是一個 Set,users 這個命名會更好,語義更清晰,userList 有一些語義干擾。命名不應該表達實現(如 List 實現,數據結構等),而應該表達語義。
使用讀得出來的名字,謹慎使用縮寫
人看代碼,實際是在默讀代碼,包括你現在看到這句話的時候,心里也是在默念出來的。如xxCmd這樣的命名,一定會在腦海中多了一次 parse,對于一些更不常見的縮寫,這種情況更嚴重。前面提過,這種腦內 parse 會使大腦忙于“線程切換”,思維停頓更是讓人沮喪。
團隊統一業務術語
DDD 的一個重要理念就是同一術語,在一個團隊內部就應該統一術語,從運營產品到開發測試等,都應該對某一個業務專有詞不產生任何歧義。我見過太多因為產品和開發對某一個詞的理解不同而“大打出手”的事。
注釋
好的代碼是自注釋的。
命名雖然重要,但也無需發展成為圣戰。
4. 單元測試
應該重視單元測試。單元測試,保證軟件質量和代碼質量。單元測試是我們所寫函數的第一個調用者,如果發現單元測試很難寫,那不用說,函數實現絕對是有問題的,或者抽象層次劃分不清,或者依賴復雜等。如果連我們自己調自己的方法都用得這么不爽,那可想而知其他調用者,特別是網絡接口。這是為什么單元測試可以保證代碼質量,它可以檢驗我們的代碼是否寫得足夠好。
單元測試對于修改代碼或重構的重要性無可替代,對于擁有一組完善單測的函數,我們可以隨意更改,只要讓修改后的函數通過單測,就幾乎是安全修改的,單元測試鋪了一張安全網,讓我們像走鋼絲一樣地寫代碼不至于失足跌入深淵萬劫不復。
關于單元測試有很多實踐,最著名的可能莫過于 TDD,我們雖不至于按 TDD 的實踐來開發,但我們應該善用單元測試,來檢驗我們的函數實現是否合理,實現得好的函數,單測一定是好寫的,逆否亦然。
一些 tips:
不能依賴真實依賴,這是大忌。如依賴真實數據庫且數據庫出錯,并不能檢驗單測所測函數邏輯失敗,而是外部造成的,應該 mock,且對一般對象也應該盡量使用 mock 對象;否則即為集成測試;
路徑應該盡可能全;
不能有條件分支,任何條件分支都應該新開單測;
單測也應該像業務代碼一樣,干凈整潔;
realBug 測試是必要的,發生過一次的事情很有可能會反復發生,我們選擇題第一次選錯了,第二次還是很可能選擇上次的那個錯誤答案;
...
其他話題
以下這些話題,單獨拎出來都是一個很大的主題,這里只是拋磚引玉,簡單談談一些和整潔代碼相關的感悟和實踐,實是整潔代碼需要各個方面的努力,而非僅代碼一途用功。
心智負擔與復雜
Complexity is caused by two things: dependencies and obscurity.
軟件開發的復雜性由兩樣東西帶來:依賴和晦澀。這兩者都會加重心智負擔。消除心智負擔一定程度上意味著增加可讀性和可維護性。
其實我們所做的一切,都是在馴服復雜度。人腦終究是有限的,我們眼所能見、腦所能別的資源幾乎都是有限的。馴服復雜度,代碼寫好了,升職加薪,業余時間沒有 bug 找上門,提高生活質量,我們所做的一切不就是為了這個嗎?
復雜是我們軟件生涯的一生之敵。
分層分包
分層是除“模塊化”之外最古老的架構模式,馮諾依曼計算機模型是模塊化的架構,但同時計算機世界也是層層疊加的。分層分包的本質就是隔離,人處理難題的能力是有限的,無法同時處理很多復雜的事情,所以不把所有東西都放在同一層次,譬如行政體系也是分層的。隔離使得各個層次職責更清晰,更容易管理。
分層的原則是只能上層調用下層,而不能反過來,反之容易導致循環依賴。分包的原則是,同一個包中的對象天然是親和的,同時對包外的對象是不親和(隔離)的。
從分層的理念理解,則 controller/api 層 的 request 不應該一直傳遞到 service 層甚至是 dao 層,然而這種現象卻是非常常見。業務層不應該對界面層有所了解,而是相反,界面層調用業務層來完成一次用戶用例。凡是進入業務層,就不應該有界面層的對象,而應該在界面層轉換成業務對象,進而使業務層只處理它所能知的業務對象。這種跨層次的信息傳遞,無異于鄉長直接向省長匯報工作。
傳統 MVC 的分層對于簡單業務而言,是簡單實用的。但是其對于復雜業務系統的架構能力十分有限,一個 service 包里有上百個 xxxService 類,業務表達能力有限,如果所有對外服務都可以叫做 service,那為何要區分餐廳、醫院、商場,統一叫服務不就好了?而且很多時候,往往就是一些無法準確劃分職責的類干脆就合并到 Service 類里,這讓 Service 類成了一個大雜燴直至成為 God Class,最終退化成過程式代碼,只是機械的代碼堆積,沒有層次分明、職責分明的對象,沒有設計感。
對于業務復雜的系統,DDD 微服務經典四層分層是一個更好的實踐,重視業務、重視 OO,整個系統設計感十足,對象林立,可以做一些了解。但是對于業務簡單的系統,則不應該為了炫技而使用技術。因地制宜,學會取舍。
此外,關于 dao,業務復雜情況下應該避免使用。dao 的表達能力同樣很弱,dao 里的方法很難表達意圖,語義表達能力很弱,findByXXX 實際是沒有業務語義的,例如 findByAge 接受參數 18,還是上面的例子,并不是選擇成年的業務意義。此外 dao 難以管理。例如一個 dao 里有上百個 findByXXX 方法,如果業務需要新增方法,一般最省事的做法就是直接又加一個 findByXXX 方法,這樣下去 dao 會越來越膨脹并趨于崩壞。業務復雜情況應該使用 repository,repository 通過組合規格(specification)來表達查詢語義,repository 是倉儲的概念,類似一個 ADT,只有有限幾個經過仔細設計的方法,類比一個 map 就理解了。關于更多為何不使用 dao 而應該使用 repository 的知識,可參考 https://thinkinginobjects.com/2012/08/26/dont-use-dao-use-repository/
設計原則
遵循良好的設計原則,能使代碼更整潔,當然意義不僅于此。有關設計原則的資料很多,我們也應該對此有所了解。常見設計原則如:
SOLID
ADP
REP
CCP
CRP
SDP
SAP
DRY
KISS
YAGNI
SLAP
POLA
LoD
代碼的非功能特性
只完成功能的代碼,是最基礎的代碼。好的代碼還應該盡量完成代碼的非功能特性,有興趣可以了解下,不外乎:
可操作性
健壯性
可測試性
可維護性
易用性
可重用性
其實還有些主題是無法避而不談的,如錯誤處理,但限于篇幅和能力,只能推薦讀兩遍《Clean Code》。
最后,人生不過是“看山是山,看山不是山,看山仍是山”,代碼也是如此,不要著相。
- END -
看完一鍵三連在看,轉發,點贊
是對文章最大的贊賞,極客重生感謝你
推薦閱讀
程序員必讀的經典書單!快來領當當大額優惠券!全場半價+大額優惠劵!
深入理解重要的編程模型
深入理解編程藝術之策略與機制相分離
超強干貨來襲 云風專訪:近40年碼齡,通宵達旦的技術人生總結
- 上一篇: 从TCP到RDMA网络最新技术|扩展技术
- 下一篇: 经典|Linux:为什么性能工具需要 B