原文:http://weibo.com/p/1001643877361430185536java
什麼是好代碼程序員
如何編寫優雅的代碼算法
如何作出優雅的設計docker
如何規劃合理的架構數據庫
如何處理遺留代碼編程
對於代碼質量的定義須要於從兩個維度分析:主觀的,被人類理解的部分;還有客觀的,在計算機裏運行的情況。設計模式
我把代碼質量分爲五個層次,依次爲:api
完成功能的代碼緩存
高性能的代碼安全
易讀的代碼
可測試的代碼
可擴展的代碼
在不少跟代碼質量有關的書裏都強調了一個觀點:程序首先是給人看的,其次纔是能被機器執行。
在評價一段代碼能不能讓人看懂的時候,能夠本身把這段代碼逐字翻譯成中文,試着組成句子,以後把中文句子讀給另外一我的沒有看過這段代碼的人聽,若是另外一我的能聽懂,那麼這段代碼的可讀性基本就合格了。
而實際閱讀代碼時,讀者也會一個詞一個詞的閱讀,推斷這句話的意思,若是僅靠句子沒法理解,那麼就須要聯繫上下文理解這句代碼,若是簡單的聯繫上下文也理解不了,可能還要掌握更多其它部分的細節來幫助推斷。大部分狀況下,理解一句代碼在作什麼須要聯繫的上下文越多,意味着代碼的質量越差。
逐字翻譯的好處是能讓做者能輕易的發現那些只有本身知道的、沒有體如今代碼裏的假設和可讀性陷阱。沒法從字面意義上翻譯出本來意思的代碼大多都是爛代碼,好比「ms表明messageService「,或者「ms.proc()是發消息「,或者「tmp表明當前的文件」。
約定包括代碼和文檔如何組織,註釋如何編寫,編碼風格的約定等等,這對於代碼將來的維護很重要。
你們剛開始工做時,通常須要與部門的約定保持一致,包括一些強制的規定,如代碼的格式化設置文件;或者一些非強制的約定,如工程的命名等。
從更大的範圍考慮,整個行業的約定和規則也在不斷的更新。因此在工做中也要對行業動向和開源項目保持關注,若是發現部門中哪一項約定已通過時了,那麼能夠隨時提出來,由平臺的架構師小組review以後推動平臺更新這些規則。
對於文檔的標準很簡單,能找到、能讀懂就能夠了,通常一個工程至少要包含如下幾類文檔:
對於項目的介紹,包括項目功能、做者、目錄結構等,讀者應該能3分鐘內大體理解這個工程是作什麼的。
針對新人的QuickStart,讀者按照文檔說明應該能在1小時內完成代碼構建和簡單使用。
針對使用者的詳細說明文檔,好比接口定義、參數含義、設計等,讀者能經過文檔瞭解這些功能(或接口)的使用方法。
有一部分註釋實際是文檔,好比javadoc。這樣能把源碼和註釋放在一塊兒,對於讀者更清晰,也能簡化很多文檔的維護的工做。
還有一類註釋並不做爲文檔的一部分,好比函數內部的註釋,這類註釋的職責是說明一些代碼自己沒法表達的做者在編碼時的思考,好比「爲何這裏沒有作XX」,或者「這裏要注意XX問題」。
通常來講函數內部註釋的數量應該不會有不少,也不會徹底沒有,通常滾動幾屏幕看到一兩處左右比較正常。過多的話可能意味着代碼自己的可讀性有問題,而若是一點都沒有可能意味着有些隱藏的邏輯沒有說明,須要考慮適當的增長一點註釋了。
其次也須要考慮註釋的質量:在代碼可讀性合格的基礎上,註釋應該提供比代碼更多的信息。文檔和註釋並非越多越好,它們可能會致使維護成本增長。
剛開始接觸高併發線上系統的新同窗常常容易出現一個問題:寫的代碼在發佈以後才發現不少考慮不到的地方,好比說測試的時候沒問題,項目發佈以後發現有不少意料以外的情況;或者出了問題以後不知道從哪下手排查,等等。
這裏介紹一下從代碼編寫完成到發佈前須要注意的一些地方。
新手程序員廣泛沒有處理異常的意識,但代碼的實際運行環境中充滿了異常:服務器會死機,網絡會超時,用戶會胡亂操做,不懷好意的人會惡意攻擊你的系統。
對一段代碼異常處理能力的第一印象應該來自於單元測試的覆蓋率。大部分異常難以在開發或者測試環境裏復現,依靠測試團隊也很難在集成測試環境中模擬全部的異常狀況。
而單元測試能夠比較簡單的模擬各類異常狀況,若是一個模塊的單元測試覆蓋率連50%都不到,很難想象這些代碼考慮了異常狀況下的處理,即便考慮了,這些異常處理的分支都沒有被驗證過,怎麼期望實際運行環境中出現問題時表現良好呢?
而是否高質量的實現併發編程的關鍵並非是否應用了某種同步策略,而是看代碼中是否保護了共享資源:
局部變量以外的內存訪問都有併發風險(好比訪問對象的屬性,訪問靜態變量等)
訪問共享資源也會有併發風險(好比緩存、數據庫等)。
被調用方若是不是聲明爲線程安全的,那麼頗有可能存在併發問題(好比java的hashmap)。
全部依賴時序的操做,即便每一步操做都是線程安全的,仍是存在併發問題(好比先刪除一條記錄,而後把記錄數減一)。
前三種狀況可以比較簡單的經過代碼自己分辨出來,只要簡單培養一下本身對於共享資源調用的敏感度就能夠了。
可是對於最後一種狀況,每每很難簡單的經過看代碼的方式看出來,甚至出現併發問題的兩處調用並非在同一個程序裏(好比兩個系統同時讀寫一個數據庫,或者併發的調用了一個程序的不一樣模塊等)。可是,只要是代碼裏出現了不加鎖的,訪問共享資源的「先作A,再作B」之類的邏輯,可能就須要提升警戒了。
性能是評價程序員能力的一個重要指標,但要評價程序的性能每每要藉助於一些性能測試工具,或者在實際環境中執行纔能有結果。
若是僅從代碼的角度考慮,有兩個評價執行效率的辦法:
算法的時間複雜度,時間複雜度高的程序運行效率必然會低。
單步操做耗時,單步耗時高的操做盡可能少作,好比訪問數據庫,訪問io等,這裏須要創建對各類操做的耗時的概念。
而實際工做中,也會見到一些同窗過於熱衷優化效率,相對的會帶來程序易讀性的下降、複雜度提升、或者增長工期等等。因此在優化以前最好多想一想這段程序的瓶頸在哪裏,爲何會有這個瓶頸,以及優化帶來的收益。
再強調一下,不管是優化不足仍是優化過分,判斷性能指標最好的辦法是用數聽說話,而不是單純看代碼。
日誌表明了程序在出現問題時排查的難易程度,對於日誌的評價標準有三個:
日誌是否足夠,全部異常、外部調用都須要有日誌,而一條調用鏈路上的入口、出口和路徑關鍵點上也須要有日誌。
日誌的表達是否清晰,包括是否能讀懂,風格是否統一等。這個的評價標準跟代碼的可讀性同樣,不重複了。
日誌是否包含了足夠的信息,這裏包括了調用的上下文、外部的返回值,用於查詢的關鍵字等,便於分析信息。
對於線上系統來講,通常能夠經過調整日誌級別來控制日誌的數量,因此打印日誌的代碼只要不對閱讀形成障礙,基本上都是能夠接受的。
可維護性要對應的是將來的狀況,可是通常新人很難想象如今的一些作法會對將來形成什麼影響。
代碼重複分爲兩種:模塊內重複和模塊間重複。兩種重複都在必定程度上說明了編碼的水平有問題。現代的IDE都提供了檢查重複代碼的工具,只需點幾下鼠標就能夠判斷了。
除了代碼重複以外,還會出現另外一類重複:信息重複。
比方說每行代碼前面都寫一句註釋,一段時間以後維護的同窗忘了更新註釋,致使註釋的內容和代碼自己變得不一致了。此時過多的註釋反而成了雞肋,看之無用,刪之惋惜。
隨着項目的演進,無用的信息會越積越多,最終甚至讓人沒法分辨哪些信息是有效的,哪些是無效的。
若是在項目中發現好幾個東西都在作同一件事情,好比經過註釋描述代碼在作什麼,或者依靠註釋替代版本管理的功能,這些都是須要避免的。
模塊內高內聚與模塊間低耦合是大部分設計遵循的標準,經過合理的模塊劃分可以把複雜的功能拆分爲更易於維護的更小的功能點。
通常來講能夠從代碼長度上初步評價一個模塊劃分的是否合理,一個類的長度大於2000行,或者一個函數的長度大於兩屏幕都是比較危險的信號。
另外一個可以體現模塊劃分水平的地方是依賴。若是一個模塊依賴特別多,甚至出現了循環依賴,那麼也能夠反映出做者對模塊的規劃比較差,從此在維護這個工程的時候頗有可能出現牽一髮而動全身的狀況。
有很多工具能提供依賴分析,好比IDEA中提供的Dependencies Analysis功能,學會這些工具的使用對於評價代碼質量會有很大的幫助。
值得一提的是,絕大部分狀況下,不恰當的模塊劃分也會伴隨着極低的單元測試覆蓋率:複雜模塊的單元測試很是難寫的,甚至是不可能完成的任務。
只要提到代碼質量,必然會提到簡潔、優雅之類的形容詞。簡潔這個詞實際涵蓋了不少東西,代碼避免重複是簡潔、設計足夠抽象是簡潔,一切對於提升可維護性的嘗試實際都是在試圖作減法。
編程經驗不足的程序員每每不能意識到簡潔的重要性,樂於搗鼓一些複雜的玩意並樂此不疲。但複雜是代碼可維護性的天敵,也是程序員能力的一道門檻。
跨過門檻的程序員應該有能力控制逐漸增加的複雜度,總結和抽象出事物的本質,並體現到本身設計和編碼中。一個程序的生命週期也是在由簡入繁到化繁爲簡中不斷迭代的過程。
簡潔與抽象更像是一種思惟方式,除了要理解、還須要練習。多看、多想、多交流,不少時候能夠簡化的東西會大大超出原先的預計。
當程序的功能愈來愈多時,編程就再也不只是寫代碼,而會涉及到模塊的劃分、和模塊之間的交互等內容。對於新同窗來講,一開始很難寫出優雅的設計。
這一節會討論一下如何能讓本身編寫的代碼有更強的「設計感」。
最容易快速上手的提高本身代碼設計水平的方式就是參考其餘人的設計,這些前人總結的面對常見場景時如何進行模塊劃分和交互的方式被稱做設計模式。
結構型模式主要用於處理類或對象的組合。
行爲型模式主要用於描述對類或對象怎樣交互和怎樣分配職責。
因爲篇幅有限,這裏再也不展開每一種設計模式的用途。這部分資料和書籍已經比較全了,能夠課下學習。
維基百科上的詞條說明:
單元測試是針對程序模塊(軟件設計的最小單位)來進行正確性檢驗的測試工做。程序單元是應用的最小可測試部件。在過程化編程中,一個單元就是單個程序、函數、過程等;對於面向對象編程,最小單元就是方法,包括基類(超類)、抽象類、或者派生類(子類)中的方法。
因此,我眼中的「合格的」單元測試須要知足幾個條件:
測試的是一個代碼單元內部的邏輯,而不是各模塊之間的交互。
無依賴,不須要實際運行環境就能夠測試代碼。
運行效率高,能夠隨時執行。
瞭解了單元測試是什麼以後,第二個問題就是:單元測試是用來作什麼的?
不少人第一反應是「看看程序有沒有問題」。可是,只是使用單元測試來「看看程序有沒有問題」的話,效率反而不如把程序運行起來直接查看結果。緣由有兩個:
單元測試要寫額外的代碼,而不寫單元測試,直接運行程序也能夠測試程序有沒有問題。
即便經過了單元測試,程序在實際運行的時候仍然有可能出問題。
在這裏我總結了一下幾個比較常見的單元測試的幾個典型場景:
開發前寫單元測試,經過測試描述需求,由測試驅動開發。
在開發過程當中及時獲得反饋,提早發現問題。
應用於自動化構建或持續集成流程,對每次代碼修改作迴歸測試。
做爲重構的基礎,驗證重構是否可靠。
還有最重要的一點:編寫單元測試的難度和代碼設計的好壞息息相關,單元測試測的三分是代碼,七分是設計,能寫出單元測試的代碼基本上能夠說明這段代碼的設計是比較合理的。能寫出和寫不出單元測試之間體現了編程能力上的巨大的鴻溝,不管是什麼樣的程序員,堅持編寫一段時間的單元測試以後,都會明顯感覺到代碼設計能力的巨大提高。
測試代碼不像普通的應用程序同樣有着很明確的做爲「值」的輸入和輸出。舉個例子,假如一個普通的函數要作下面這件事情:
接收一個user對象做爲參數
調用dao層的update方法更新用戶屬性
返回true/false結果
那麼,只須要在函數中聲明一個參數、作一次調用、返回一個布爾值就能夠了。但若是要對這個函數作一個「純粹的」單元測試,那麼它的輸入和輸出會有不少狀況,好比其中一個測試是這樣:
假設調用dao層的update方法會返回true。
程序去調用service層的update方法。
驗證一下service是否是也返回了true。
而具體的測試內容能夠依賴單元測試框架提供的功能來完成。
運行框架:
jUnit
TestNG
Spock
Mock框架:
Mockito
EasyMock
PowerMock
Spock
因爲篇幅限制,這裏再也不展開具體的框架用法了,有興趣的同窗能夠自行搜索相關文章。
不少新同窗的規劃都是將來成爲架構師,要作架構師就免不了設計架構。而在微博平臺工做也會常常跟架構打交道,因爲後面有獨立的架構課程,這裏只是簡單介紹一下常見的架構模式。
分層架構是應用很廣泛架構模式,它能下降模塊之間的耦合,便於測試開發,它也是程序員須要掌握的基礎。
典型的分層架構模式以下:
上圖中是一個4層的架構,在現實場景中,層數不是一個定值,而是須要根據業務場景的複雜度決定。使用分層模型中須要注意,通常來講不能跨層、同層或反向調用,不然會讓整個層次模型由單一的樹形結構變爲網狀結構,失去了分層的意義。
但隨着程序複雜度的逐漸提高,要嚴格的按照分層模型逐級調用的話,會產生不少無用的空層,它們的做用只是傳遞請求,這也違背了軟件設計儘可能簡潔的方向。因此實際場景中能夠對各個層次規定「開放」或「關閉」屬性,對於「開放」的層次,上層能夠越過這層,直接訪問下層。
對層次定義「開放」或「關閉」能夠幫助程序員更好的理解各層次之間的交互,這類約定須要記錄在文檔中,而且確保團隊中的每一個人都瞭解這些約定,不然會獲得一個複雜的、難以維護的工程。
事件驅動架構能比較好的解耦請求方和處理方,常常被用在寫入請求量變化較大,或者是請求方不關心處理邏輯的場景中,它有兩種主要的實現方式:
在mediator方式中,存在一箇中介者角色,它接收寫入方的請求,並把事件分配到對應的處理方(圖中的channel),每一個處理方只須要關心本身的channel,而不須要與寫入方直接通訊。
在微博前些年應用比較多的應用服務-隊列-消息處理服務能夠認爲是屬於這種模式。
在broker方式中不存在中介者的角色,取而代之的是消息流在輕量的processor中流轉,造成一個消息處理的鏈路,如圖:
前一段時間開始推廣的storm流式處理屬於這種模式,對於較長的處理流程,用broker方式能夠避免實現Mediator的複雜性,相對的,管理整個流程變得複雜了。
微內核架構相對於普通架構最主要的區別是多了「內核」的概念,在編寫程序時把基礎功能和擴展功能分離:內核中再也不實現具體功能,而是定義「擴展點」,增長功能時再也不修改主邏輯,而是經過「擴展點」掛接到內核中,如圖:
以前介紹的motan RPC框架應用了這種設計,這讓motan能夠經過不一樣的插件支持更多的功能,好比增長傳輸協議、定義新的服務發現規則等。
近年來微服務架構的概念一直比較火,它能夠解決服務逐漸增加以後形成的難以測試及部署、資源浪費等問題,但也帶來了服務調度和服務發現層面的複雜度,如圖:
微博底層實際包含了不少業務邏輯,這些業務邏輯被抽象成一個個服務和模塊,不一樣模塊之間經過motan rpc、grpc或http rest api進行通訊,經過docker和之上的調度服務進行調度和部署,最終成爲一個完整的系統。
微服務隔離了各服務之間的耦合,可以有效提高開發效率;除此以外,當微博面對巨大的流量峯值時,能夠進行更精細的資源調配和更有效率的部署。
傳統的分層架構每每會存在一些中心節點,如數據庫、緩存等,這些節點每每容易成爲性能瓶頸,而且存在擴容比較複雜的問題。
在面臨對擴展性和性能有極端要求的場景時,能夠考慮使用單元化架構:對數據進行切分,並將每一部分數據及相關的邏輯部署在同一個節點中,如圖:
在單元化架構中,每一個「單元」均可以獨立部署,單元中包括獨立的計算和存儲模塊;計算模塊只與單元內的存儲模塊交互,再也不須要分庫分表等邏輯;而數據與存儲更近也下降了網絡消耗,進而提升了效率。
微博平臺經過對羣發服務的單元化改造,實現了百萬級每秒的私信推送服務。
對於一個不斷髮展的系統,必然有一些遺留下來的歷史問題。當遇到了遺留下來的爛代碼時,除了理解和修改代碼,更重要的是如何讓代碼朝着好的方向發展,而不是聽任無論。
重構是處理遺留代碼的比較重要的手段,這一節主要討論一下重構的相關話題。
新同窗每每對重構抱有恐懼感,認爲重構應該找一個比較長的時間專門去作。這種願望很好,可是現實中通常很難找出一段相對穩定的時間。
另外一方面,重構是比較考驗編程水平的一項工做。代碼寫的很差的人,即便作了重構也只是把很差的代碼變了個形式。要達到比較高的水平每每須要不斷的練習,幾個月作一次重構很可貴到鍛鍊,重構效果也會打折。
因此,重構最好是可以做爲一項平常工做,在開發時對剛寫完的代碼作重構每每單位時間的收益是最大的。
通常來講,重構能夠抽象成四個方面:
若是對當前程序的理解是錯的,那麼重構以後的正確性也就無從談起。因此在重構以前須要理解待重構的代碼作了什麼,這個過程當中能夠伴隨一些小的、基本無風險的重構,例如重命名變量、提取內部方法等,以幫助咱們理解程序。
在理解了程序作了什麼事情以後,第二個須要作的事情就是須要提早想好重構以後的代碼是什麼樣的。
改變代碼結構比較複雜,而且每每伴隨着風險和不可控的問題。因此在實際動手以前,須要從更高的層次考慮重構以後的模塊如何劃分,交互是如何控制等等,在這個過程當中實際與寫代碼要作的事情是一致的。
爛代碼每每模塊的劃分有一些問題,在重構時牽一髮而動全身,改的越多問題越多,致使重構過程不可控。因此在動手重構前須要想辦法減小重構改動的範圍,通常來講能夠只改動相鄰層次的幾個類,而且只改動一個功能相關的代碼,不求一次性所有改完。
爲了可以劃分範圍,在重構時須要採用一些方法解除掉依賴鏈。好比增長適配器等等,這些類可能只是臨時的,在完整的重構完成以後就能夠刪除掉,看起來是增長了工做量,可是換來的是更可控的影響範圍。
爲了能保證重構的正確性,須要一些測試來驗證重構是否安全。最有效的是單元測試,它能提供比集成測試更高的覆蓋率,也能驗證重構以後的代碼設計是不是合理的。
在作一次重構以前須要整理模塊的單元測試。遺留代碼有可能測試不全,而且難以編寫單元測試,此時能夠適當的犧牲待重構代碼的優雅性,好比提升私有方法的可見性,知足測試的需求。在重構的過程當中,這部分代碼會被逐漸替換掉。
今天跟你們討論了一下關於編程的各個方面,關於編程的話題看似很基礎,想要作好卻並不容易。新同窗比較容易急於求成,每每過多的關注架構或者某些新技術,卻忽視了基本功的修煉,而在後續的工做過程當中,基本功不紮實的人作事每每會事倍功半,難以有更一步的發展。
勿在浮沙築高臺,與各位共勉。
新兵訓練營簡介
微博平臺新兵訓練營活動是微博平臺內部組織的針對新入職同窗的團隊融入培訓課程,目標是團隊融入,包括人的融入,氛圍融入,技術融入。當前已經進行4期活動,不少學員迅速成長爲平臺技術骨幹。
微博平臺是很是注重團隊成員融入與成長的團隊,在這裏有人幫你融入,有人和你一塊兒成長,也歡迎小夥伴們加入微博平臺,歡迎私信諮詢。
講師簡介
秦迪,@蛋疼的AXB 微博平臺及大數據部技術專家
我的介紹:http://blog.2baxb.me/about-me