-------------------------------------------------------------------------------------------------------------------------------------html
-------------------------------------------------------------------------------------------------------------------------------------前端
第5 章 運維保障333
王 康/5.1 360 如何用QConf 搞定兩萬以上服務器的配置管理.333
5.1.1 設計初衷333
5.1.2 總體認識334
5.1.3 架構介紹335
5.1.4 QConf 服務端336
5.1.5 QConf 客戶端336
5.1.6 QConf 管理端340
5.1.7 其餘341
5.1.8 疑問與解惑343java
-------------------------------------------------------------------------------------------------------------------------------------mysql
QConf是奇虎360普遍使用的配置管理服務,現已開源,歡迎你們關注使用。react
https://github.com/Qihoo360/QConflinux
本文從設計初衷,架構實現,使用狀況及相關產品比較四個方面進行介紹。git
設計初衷
在分佈式環境中,出於負載、容錯等種種須要,幾乎全部的服務都會在不一樣的機器節點上部署多個實例。而業務項目中又總少不了各類類型的配置文件。所以,咱們經常會遇到這樣的問題,僅僅是一個配置內容的修改,便須要從新進行代碼提交SVN/Git、打包、分發上線的所有流程。當部署的機器有不少時,分發上線自己就是一個很繁雜的工做。況且,配置文件的修改頻率又遠遠大於代碼自己。程序員
追本溯源,咱們認爲麻煩的根源是平常管理和發佈過程當中不加區分配置和代碼形成的。配置自己源於代碼,是咱們爲了提升代碼的靈活性而提取出來的一些常常變化的或須要定製的內容,而正是配置的這種天生的變化特徵給咱們帶了巨大的麻煩。github
所以,咱們開發了分佈式配置管理系統QConf,並依託QConf在360內部提供了一整套配置管理服務,QConf致力於將配置內容從代碼中徹底分離出來,及時可靠高效地提供配置訪問和更新服務。web
-------------------------------------------------------------------------------------------------------------------------------------
尤 勇/5.2 深度剖析開源分佈式監控CAT347
5.2.1 背景介紹347
5.2.2 總體設計348
5.2.3 客戶端設計349
5.2.4 服務端設計352
5.2.5 總結感悟357
-------------------------------------------------------------------------------------------------------------------------------------
CAT(Central Application Tracking)是一個實時和接近全量的監控系統,它側重於對Java應用的監控,基本接入了美團上海側全部核心應用。目前在中間件(MVC、RPC、數據庫、緩存等)框架中獲得普遍應用,爲美團各業務線提供系統的性能指標、健康情況、監控告警等。
自2014年開源以來,Github 收穫 7700+ Star,2800+ Forks,被 100+ 公司企業使用,其中不乏攜程、陸金所、獵聘網、平安等業內知 名公司。在每一年全球 QCon 大會、全球架構與運維技術峯會等都有持續的技術輸出,受到行業內承認,愈來愈多的企業夥伴加入了 CAT 的開 源建設工做,爲 CAT 的成⻓貢獻了巨大的力量。
項目的開源地址是 http://github.com/dianping/cat。
本文會對CAT總體設計、客戶端、服務端等的一些設計思路作詳細深刻的介紹。
背景介紹
CAT整個產品研發是從2011年末開始的,當時正是大衆點評從.NET遷移到Java的核心起步階段。當初大衆點評已經有核心的基礎中間件、RPC組件Pigeon、統一配置組件Lion。總體Java遷移已經在服務化的路上。隨着服務化的深刻,總體Java在線上部署規模逐漸變多,同時,暴露的問題也愈來愈多。典型的問題有:
- 大量報錯,特別是核心服務,須要花好久時間才能定位。
- 異常日誌都須要線上權限登錄線上機器排查,排錯時間長。
- 有些簡單的錯誤定位都很是困難(一次將線上的庫配置到了Beta,花了整個通宵排錯)。
- 不少不了了之的問題懷疑是網絡問題(從如今看,內網真的不多出問題)。
雖然那時候也有一些簡單的監控工具(好比Zabbix,本身研發的Hawk系統等),可能單個工具在某方面的功能還不錯,但總體服務化水平良莠不齊、擴展能力相對較弱,監控工具間不能互通互聯,使得查找問題根源基本都須要在多個系統之間切換,有時候真的是靠「人品」才能找出根源。
適逢在eBay工做長達十幾年的吳其敏加入大衆點評成爲首席架構師,他對eBay內部應用很是成功的CAL系統有深入的理解。就在這樣天時地利人和的狀況下,咱們開始研發了大衆點評第一代監控系統——CAT。
CAT的原型和理念來源於eBay的CAL系統,最初是吳其敏在大衆點評工做期間設計開發的。他以前曾CAT不只加強了CAL系統核心模型,還添加了更豐富的報表。
總體設計
監控總體要求就是快速發現故障、快速定位故障以及輔助進行程序性能優化。爲了作到這些,咱們對監控系統的一些非功能作了以下的要求:
- 實時處理:信息的價值會隨時間銳減,尤爲是事故處理過程當中。
- 全量數據:最開始的設計目標就是全量採集,全量的好處有不少。
- 高可用:全部應用都倒下了,須要監控還站着,並告訴工程師發生了什麼,作到故障還原和問題定位。
- 故障容忍:CAT自己故障不該該影響業務正常運轉,CAT掛了,應用不應受影響,只是監控能力暫時減弱。
- 高吞吐:要想還原真相,須要全方位地監控和度量,必需要有超強的處理吞吐能力。
- 可擴展:支持分佈式、跨IDC部署,橫向擴展的監控系統。
- 不保證可靠:容許消息丟失,這是一個很重要的trade-off,目前CAT服務端能夠作到4個9的可靠性,可靠系統和不可靠性系統的設計差異很是大。
CAT從開發至今,一直秉承着簡單的架構就是最好的架構原則,主要分爲三個模塊:CAT-client、CAT-consumer、CAT-home。
- Cat-client 提供給業務以及中間層埋點的底層SDK。
- Cat-consumer 用於實時分析從客戶端提供的數據。
- Cat-home 做爲用戶給用戶提供展現的控制端。
在實際開發和部署中,Cat-consumer和Cat-home是部署在一個JVM內部,每一個CAT服務端均可以做爲consumer也能夠做爲home,這樣既能減小整個層級結構,也能夠增長系統穩定性。
上圖是CAT目前多機房的總體結構圖,圖中可見:
- 路由中心是根據應用所在機房信息來決定客戶端上報的CAT服務端地址,目前美團有廣州、北京、上海三地機房。
- 每一個機房內部都有獨立的原始信息存儲集羣HDFS。
- CAT-home能夠部署在一個機房也能夠部署在多個機房,在最後作展現的時候,home會從consumer中進行跨機房的調用,將全部的數據合併展現給用戶。
- 實際過程當中,consumer、home以及路由中心都是部署在一塊兒的,每一個服務端節點均可以充當任何一個角色。
客戶端設計
客戶端設計是CAT系統設計中最爲核心的一個環節,客戶端要求是作到API簡單、高可靠性能,不管在任何場景下都不能影響客業務性能,監控只是公司核心業務流程一個旁路環節。CAT核心客戶端是Java,也支持Net客戶端,近期公司內部也在研發其餘多語言客戶端。如下客戶端設計及細節均以Java客戶端爲模板。
設計架構
CAT客戶端在收集端數據方面使用ThreadLocal(線程局部變量),是線程本地變量,也能夠稱之爲線程本地存儲。其實ThreadLocal的功用很是簡單,就是爲每個使用該變量的線程都提供一個變量值的副本,屬於Java中一種較爲特殊的線程綁定機制,每個線程均可以獨立地改變本身的副本,不會和其它線程的副本衝突。
在監控場景下,爲用戶提供服務都是Web容器,好比tomcat或者Jetty,後端的RPC服務端好比Dubbo或者Pigeon,也都是基於線程池來實現的。業務方在處理業務邏輯時基本都是在一個線程內部調用後端服務、數據庫、緩存等,將這些數據拿回來再進行業務邏輯封裝,最後將結果展現給用戶。因此將全部的監控請求做爲一個監控上下文存入線程變量就很是合適。
如上圖所示,業務執行業務邏輯的時候,就會把這次請求對應的監控存放於線程上下文中,存於上下文的實際上是一個監控樹的結構。在最後業務線程執行結束時,將監控對象存入一個異步內存隊列中,CAT有個消費線程將隊列內的數據異步發送到服務端。
API設計
監控API定義每每取決於對監控或者性能分析這個領域的理解,監控和性能分析所針對的場景有以下幾種:
- 一段代碼的執行時間,一段代碼能夠是URL執行耗時,也能夠是SQL的執行耗時。
- 一段代碼的執行次數,好比Java拋出異常記錄次數,或者一段邏輯的執行次數。
- 按期執行某段代碼,好比按期上報一些核心指標:JVM內存、GC等指標。
- 關鍵的業務監控指標,好比監控訂單數、交易額、支付成功率等。
在上述領域模型的基礎上,CAT設計本身核心的幾個監控對象:Transaction、Event、Heartbeat、Metric。
一段監控API的代碼示例以下:
序列化和通訊
序列化和通訊是整個客戶端包括服務端性能裏面很關鍵的一個環節。
- CAT序列化協議是自定義序列化協議,自定義序列化協議相比通用序列化協議要高效不少,這個在大規模數據實時處理場景下仍是很是有必要的。
- CAT通訊是基於Netty來實現的NIO的數據傳輸,Netty是一個很是好的NIO開發框架,在這邊就不詳細介紹了。
客戶端埋點
日誌埋點是監控活動的最重要環節之一,日誌質量決定着監控質量和效率。當前CAT的埋點目標是以問題爲中心,像程序拋出exception就是典型問題。我我的對問題的定義是:不符合預期的就能夠算問題,好比請求未完成、響應時間快了慢了、請求TPS多了少了、時間分佈不均勻等等。
在互聯網環境中,最突出的問題場景,突出的理解是:跨越邊界的行爲。包括但不限於:
- HTTP/REST、RPC/SOA、MQ、Job、Cache、DAL;
- 搜索/查詢引擎、業務應用、外包系統、遺留系統;
- 第三方網關/銀行, 合做夥伴/供應商之間;
- 各種業務指標,如用戶登陸、訂單數、支付狀態、銷售額。
遇到的問題
一般Java客戶端在業務上使用容易出問題的地方就是內存,另一個是CPU。內存每每是內存泄露,佔用內存較多致使業務方GC壓力增大; CPU開銷最終就是看代碼的性能。
之前咱們遇到過一個極端的例子,咱們一個業務請求作餐飲加商鋪的銷售額,業務通常會經過for循環全部商鋪的分店,結果就形成內存OOM了,後來發現這家店是肯德基,有幾萬分店,每一個循環裏面都會有數據庫鏈接。在正常場景下,ThreadLocal內部的監控一個對象就存在幾萬個節點,致使業務Oldgc特別嚴重。因此說框架的代碼是不能想象業務方會怎麼用你的代碼,須要考慮到任何狀況下都有出問題的可能。
在消耗CPU方面咱們也遇到一個case:在某個客戶端版本,CAT本地存儲當前消息ID自增的大小,客戶端使用了MappedByteBuffer這個類,這個類是一個文件內存映射,測試下來這個類的性能很是高,咱們僅僅用這個存儲了幾個字節的對象,正常狀況理論上不會有任何問題。在一次線上場景下,不少業務線程都block在這個上面,結果發現當自己這臺機器IO存在瓶頸時候,這個也會變得很慢。後來的優化就是把這個IO的操做異步化,因此客戶端須要儘量異步化,異步化序列化、異步化傳輸、異步化任何可能存在時間延遲的代碼操做。
服務端設計
服務端主要的問題是大數據的實時處理,目先後端CAT的計算集羣大約35臺物理機,存儲集羣大約35臺物理機,天天處理了約100TB的數據量。線上單臺機器高峯期大約是110MB/s,接近千兆網打滿。
下面我重點講下CAT服務端一些設計細節。
架構設計
在最初的總體介紹中已經畫了架構圖,這邊介紹下單機的consumer中大概的結構以下:
如上圖,CAT服務端在整個實時處理中,基本上實現了全異步化處理。
- 消息接受是基於Netty的NIO實現。
- 消息接受到服務端就存放內存隊列,而後程序開啓一個線程會消費這個消息作消息分發。
- 每一個消息都會有一批線程併發消費各自隊列的數據,以作到消息處理的隔離。
- 消息存儲是先存入本地磁盤,而後異步上傳到HDFS文件,這也避免了強依賴HDFS。
當某個報表處理器處理來不及時候,好比Transaction報表處理比較慢,能夠經過配置支持開啓多個Transaction處理線程,併發消費消息。
實時分析
CAT服務端實時報表分析是整個監控系統的核心,CAT重客戶端採集的是是原始的logview,目前一天大約有1000億的消息,這些原始的消息太多了,因此須要在這些消息基礎上實現豐富報表,來支持業務問題及性能分析的須要。
CAT是根據日誌消息的特色(好比只讀特性)和問題場景,量身定作的,它將全部的報表按消息的建立時間,一小時爲單位分片,那麼每小時就產生一個報表。當前小時報表的全部計算都是基於內存的,用戶每次請求即時報表獲得的都是最新的實時結果。對於歷史報表,由於它是不變的,因此實時不實時也就無所謂了。
CAT基本上全部的報表模型均可以增量計算,它能夠分爲:計數、計時和關係處理三種。計數又能夠分爲兩類:算術計數和集合計數。典型的算術計數如:總個數(count)、總和(sum)、均值(avg)、最大/最小(max/min)、吞吐(tps)和標準差(std)等,其餘都比較直觀,標準差稍微複雜一點,你們本身能夠推演一下怎麼作增量計算。那集合運算,好比95線(表示95%請求的完成時間)、999線(表示99.9%請求的完成時間),則稍微複雜一些,系統開銷也更大一點。
報表建模
CAT每一個報表每每有多個維度,以transaction報表爲例,它有5個維度,分別是應用、機器、Type、Name和分鐘級分佈狀況。若是全維度建模,雖然靈活,但開銷將會很是之大。CAT選擇固定維度建模,能夠理解成將這5個維度組織成深度爲5的樹,訪問時老是從根開始,逐層往下進行。
CAT服務端爲每一個報表單獨分配一個線程,因此不會有鎖的問題,全部報表模型都是非線程安全的,其數據是可變的。這樣帶來的好處是簡單且低開銷。
CAT報表建模是使用自研的Maven Plugin自動生成的。全部報表是可合併和裁剪的,能夠輕易地將2個或多個報表合併成一個報表。在報表處理代碼中,CAT大量使用訪問者模式(visitor pattern)。
性能分析報表
故障發現報表
- 實時業務指標監控 :核心業務都會定義本身的業務指標,這不須要太多,主要用於24小時值班監控,實時發現業務指標問題,圖中一個是當前的實際值,一個是基準值,就是根據歷史趨勢計算的預測值。以下圖就是當時的情景,能直觀看到支付業務出問題的故障。
- 系統報錯大盤。
- 實時數據庫大盤、服務大盤、緩存大盤等。
存儲設計
CAT系統的存儲主要有兩塊:
- CAT的報表的存儲。
- CAT原始logview的存儲。
報表是根據logview實時運算出來的給業務分析用的報表,默認報表有小時模式、天模式、周模式以及月模式。CAT實時處理報表都是產生小時級別統計,小時級報表中會帶有最低分鐘級別粒度的統計。天、周、月等報表都是在小時級別報表合併的結果報表。
原始logview存儲一天大約100TB的數據量,由於數據量比較大因此存儲必需要要壓縮,自己原始logview須要根據Message-ID讀取,因此存儲總體要求就是批量壓縮以及隨機讀。在當時場景下,並無特別合適成熟的系統以支持這樣的特性,因此咱們開發了一種基於文件的存儲以支持CAT的場景,在存儲上一直是最難的問題,咱們一直在這塊持續的改進和優化。
消息ID的設計
CAT每一個消息都有一個惟一的ID,這個ID在客戶端生成,後續都經過這個ID在進行消息內容的查找。典型的RPC消息串起來的問題,好比A調用B的時候,在A這端生成一個Message-ID,在A調用B的過程當中,將Message-ID做爲調用傳遞到B端,在B執行過程當中,B用context傳遞的Message-ID做爲當前監控消息的Message-ID。
CAT消息的Message-ID格式ShopWeb-0a010680-375030-2,CAT消息一共分爲四段:
- 第一段是應用名shop-web。
- 第二段是當前這臺機器的IP的16進制格式,0a01010680表示10.1.6.108。
- 第三段的375030,是系統當前時間除以小時獲得的整點數。
- 第四段的2,是表示當前這個客戶端在當前小時的順序遞增號。
存儲數據的設計
消息存儲是CAT最有挑戰的部分。關鍵問題是消息數量多且大,目前美團天天處理消息1000億左右,大小大約100TB,單物理機高峯期每秒要處理100MB左右的流量。CAT服務端基於此流量作實時計算,還須要將這些數據壓縮後寫入磁盤。
總體存儲結構以下圖:
CAT在寫數據一份是Index文件,一份是Data文件.
- Data文件是分段GZIP壓縮,每一個分段大小小於64K,這樣能夠用16bits能夠表示一個最大分段地址。
- 一個Message-ID都用須要48bits的大小來存索引,索引根據Message-ID的第四段來肯定索引的位置,好比消息Message-ID爲ShopWeb-0a010680-375030-2,這條消息ID對應的索引位置爲2*48bits的位置。
- 48bits前面32bits存數據文件的塊偏移地址,後面16bits存數據文件解壓以後的塊內地址偏移。
- CAT讀取消息的時候,首先根據Message-ID的前面三段肯定惟一的索引文件,在根據Message-ID第四段肯定此Message-ID索引位置,根據索引文件的48bits讀取數據文件的內容,而後將數據文件進行GZIP解壓,在根據塊內偏移地址讀取出真正的消息內容。
服務端設計總結
CAT在分佈式實時方面,主要歸結於如下幾點因素:
- 去中心化,數據分區處理。
- 基於日誌只讀特性,以一個小時爲時間窗口,實時報表基於內存建模和分析,歷史報表經過聚合完成。
- 基於內存隊列,全面異步化、單線程化、無鎖設計。
- 全局消息ID,數據本地化生產,集中式存儲。
- 組件化、服務化理念。
總結
最後咱們再花一點點時間來說一下咱們在實踐裏作的一些東西。
1、MVP版本,Demo版本用了1個月,MVP版本用了3個月。
爲何強調MVP版本?由於作這個項目須要老闆和業務的支持。大概在2011年左右,咱們整個生產環境估計也有一千臺機器(虛擬機),一旦出現問題就到運維那邊看日誌,看日誌的痛苦你們都應該理解,這時候發現一臺機器核心服務出錯,可能會致使更多的問題。咱們就作了MVP版本解決這個問題,當時咱們大概作了兩個功能:一個是實時知道全部的API接口訪問量成功率等;第二是實時能在CAT平臺上看到異常日誌。這裏我想說的是MVP版本不要作太多內容,可是在作一個產品的時候必須從MVP版本作起,要作一些最典型特別亮眼的功能讓你們支持你。
2、數據質量。數據質量是整個監控體系裏面很是關鍵,它決定你最後的監控報表質量。因此咱們要和跟數據庫框架、緩存框架、RPC框架、Web框架等作深刻的集成,讓業務方便收集以及看到這些數據。
3、單機開發環境,這也是咱們認爲對整個項目開發效率提高最重要的一點。單機開發環境實際上就是說你在一臺機器裏能夠把你全部的項目都啓起來。若是你在一個單機環境下把全部東西啓動起來,你就會千方百計地知道我依賴的服務掛了我怎麼辦?好比CAT依賴了HDFS。單機開發環境除了大幅度提升你的項目開發效率以外,還能提高你整個項目的可靠性。
4、最難的事情是項目上線推進。CAT整個項目大概有兩三我的,當時白天都是支持業務上線,培訓,晚上才能code,可是一旦隨着產品和完善以及業務使用逐漸變多,一些好的產品後面會造成良性循環,推廣就會變得比較容易。
5、開放生態。公司越大監控的需求越多,報表需求也更多,好比咱們美團,產品有不少報表,整個技術體系裏面也有不少報表很是多的自定義報表,不少業務方都提各自的需求。最後咱們決定把整個CAT系統裏面全部的數據都做爲API暴露出去,這些需求並非不能支持,而是這事情根本是作不完的。美團內部下游有不少系統依賴CAT的數據,來作進一步的報表展現。
CAT項目從2011年開始作,到如今整個生產環境大概有三千應用,監控的服務端從零到幾千,再到今天的兩萬多的規模,整個項目是從歷時看起來是一個五年多的項目,但即便是作了五年多的這樣一個項目,目前還有不少的需求須要開發。這邊也打個廣告,咱們團隊急缺人,歡迎對監控系統研發有興趣的同窗加入,請聯繫yong.you@dianping.com.
-------------------------------------------------------------------------------------------------------------------------------------
楊尚剛/5.3 單表60 億記錄等大數據場景的MySQL 優化和運維之道359
5.3.1 前言359
5.3.2 數據庫開發規範.360
5.3.3 數據庫運維規範.363
5.3.4 性能優化368
5.3.5 疑問與解惑375
-------------------------------------------------------------------------------------------------------------------------------------
單表 60 億記錄等大數據場景的 MySQL 優化和運維之道 | 高可用架構 - FrancisSoung - SegmentFault 思否此文是根據楊尚剛在【QCON 高可用架構羣】中,針對 MySQL 在單表海量記錄等場景下,業界普遍關注的 MySQL 問題的經驗分享整理而成,轉發請註明出處。
此文是根據楊尚剛在【QCON 高可用架構羣】中,針對 MySQL 在單表海量記錄等場景下,業界普遍關注的 MySQL 問題的經驗分享整理而成,轉發請註明出處。
楊尚剛,美圖公司數據庫高級 DBA,負責美圖後端數據存儲平臺建設和架構設計。前新浪高級數據庫工程師,負責新浪微博核心數據庫架構改造優化,以及數據庫相關的服務器存儲選型設計。
前言
MySQL 數據庫你們應該都很熟悉,並且隨着前幾年的阿里的去 IOE,MySQL 逐漸引發更多人的重視。
MySQL 歷史
-
1979 年,Monty Widenius 寫了最初的版本,96 年發佈 1.0
-
1995-2000 年,MySQL AB 成立,引入 BDB
-
2000 年 4 月,集成 MyISAM 和 replication
-
2001 年,Heikki Tuuri 向 MySQL 建議集成 InnoDB
-
2003 發佈 5.0,提供了視圖、存儲過程等功能
-
2008 年,MySQL AB 被 Sun 收購,09 年推出 5.1
-
2009 年 4 月,Oracle 收購 Sun,2010 年 12 月推出 5.5
-
2013 年 2 月推出 5.6 GA,5.7 開發中
MySQL 的優勢
-
使用簡單
-
開源免費
-
擴展性 「好」,在必定階段擴展性好
-
社區活躍
-
性能能夠知足互聯網存儲和性能需求,離不開硬件支持
上面這幾個因素也是大多數公司選擇考慮 MySQL 的緣由。不過 MySQL 自己存在的問題和限制也不少,有些問題點也常常被其餘數據庫吐槽或鄙視
MySQL 存在的問題
-
優化器對複雜 SQL 支持很差
-
對 SQL 標準支持很差
-
大規模集羣方案不成熟,主要指中間件
-
ID 生成器,全局自增 ID
-
異步邏輯複製,數據安全性問題
-
Online DDL
-
HA 方案不完善
-
備份和恢復方案仍是比較複雜,須要依賴外部組件
-
展示給用戶信息過少,排查問題困難
-
衆多分支,讓人難以選擇
看到了剛纔講的 MySQL 的優點和劣勢,能夠看到 MySQL 面臨的問題仍是遠大於它的優點的, 不少問題也是咱們實際須要在運維中優化解決的,這也是 MySQL DBA 的一方面價值所在。而且 MySQL 的不斷髮展也離不開社區支持,好比 Google 最先提交的半同步 patch,後來也合併到官方主線。Facebook Twitter 等也都開源了內部使用 MySQL 分支版本,包含了他們內部使用的 patch 和特性。
數據庫開發規範
數據庫開發規範定義:開發規範是針對內部開發的一系列建議或規則, 由 DBA 制定 (若是有 DBA 的話)。
開發規範自己也包含幾部分:基本命名和約束規範,字段設計規範,索引規範,使用規範。
規範存在乎義
想一想沒有開發規範,有的開發寫出各類全表掃描的 SQL 語句或者各類奇葩 SQL 語句,咱們以前就看過開發寫的 SQL 能夠打印出好幾頁紙。這種形成業務自己不穩定,也會讓 DBA 每天忙於各類救火。
基本命名和約束規範
-
表字符集選擇 UTF8 ,若是須要存儲 emoj 表情,須要使用 UTF8mb4(MySQL 5.5.3 之後支持)
-
存儲引擎使用 InnoDB
-
變長字符串儘可能使用 varchar varbinary
-
不在數據庫中存儲圖片、文件等
-
單表數據量控制在 1 億如下
-
庫名、表名、字段名不使用保留字
-
庫名、表名、字段名、索引名使用小寫字母,如下劃線分割 ,須要見名知意
-
庫表名不要設計過長,儘量用最少的字符表達出表的用途
字段規範
-
全部字段均定義爲 NOT NULL ,除非你真的想存 Null
-
字段類型在知足需求條件下越小越好,使用 UNSIGNED 存儲非負整數 ,實際使用時候存儲負數場景很少
-
使用 TIMESTAMP 存儲時間
-
使用 varchar 存儲變長字符串 ,固然要注意 varchar(M) 裏的 M 指的是字符數不是字節數;使用 UNSIGNED INT 存儲 IPv4 地址而不是 CHAR(15) ,這種方式只能存儲 IPv4,存儲不了 IPv6
-
使用 DECIMAL 存儲精確浮點數,用 float 有的時候會有問題
-
少用 blob text
關於爲何定義不使用 Null 的緣由
一、浪費存儲空間,由於 InnoDB 須要有額外一個字節存儲
二、表內默認值 Null 過多會影響優化器選擇執行計劃
關於使用 datatime 和 timestamp,如今在 5.6.4 以後又有了變化,使用兩者存儲在存儲空間上大差距愈來愈小 ,而且自己 datatime 存儲範圍就比 timestamp 大不少,timestamp 只能存儲到 2038 年。
索引規範
-
單個索引字段數不超過 5,單表索引數量不超過 5,索引設計遵循 B+ Tree 索引最左前綴匹配原則
-
選擇區分度高的列做爲索引
-
創建的索引能覆蓋 80% 主要的查詢,不求全,解決問題的主要矛盾
-
DML 和 order by 和 group by 字段要創建合適的索引
-
避免索引的隱式轉換
-
避免冗餘索引
關於索引規範,必定要記住索引這個東西是一把雙刃劍,在加速讀的同時也引入了不少額外的寫入和鎖,下降寫入能力,這也是爲何要控制索引數緣由。以前看到過很多人給表裏每一個字段都建了索引,其實對查詢可能起不到什麼做用。
冗餘索引例子
-
idx_abc(a,b,c)
-
idx_a(a) 冗餘
-
idx_ab(a,b) 冗餘
隱式轉換例子
字段:remark varchar(50) NOT Null
MySQL>SELECT id, gift_code FROM gift WHERE deal_id = 640 AND remark=115127; 1 row in set (0.14 sec)
MySQL>SELECT id, gift_code FROM pool_gift WHEREdeal_id = 640 AND remark=‘115127’; 1 row in set (0.005 sec)
字段定義爲 varchar,但傳入的值是個 int,就會致使全表掃描,要求程序端要作好類型檢查
SQL 類規範
-
儘可能不使用存儲過程、觸發器、函數等
-
避免使用大表的 JOIN,MySQL 優化器對 join 優化策略過於簡單
-
避免在數據庫中進行數學運算和其餘大量計算任務
-
SQL 合併,主要是指的 DML 時候多個 value 合併,減小和數據庫交互
-
合理的分頁,尤爲大分頁
-
UPDATE、DELETE 語句不使用 LIMIT ,容易形成主從不一致
數據庫運維規範
運維規範主要內容
版本選擇
-
MySQL 社區版,用戶羣體最大
-
MySQL 企業版,收費
-
Percona Server 版,新特性多
-
MariaDB 版,國內用戶很少
建議選擇優先級爲:MySQL 社區版 > Percona Server > MariaDB > MySQL 企業版,不過如今若是你們使用 RDS 服務,基本還以社區版爲主。
Online DDL 問題
原生 MySQL 執行 DDL 時須要鎖表,且鎖表期間業務是沒法寫入數據的,對服務影響很大,MySQL 對這方面的支持是比較差的。大表作 DDL 對 DBA 來講是很痛苦的,相信不少人經歷過。如何作到 Online DDL 呢,是否是就無解了呢?固然不是!
上面表格裏提到的 Facebook OSC 和 5.6 OSC 也是目前兩種比較靠譜的方案
MySQL 5.6 的 OSC 方案仍是解決不了 DDL 的時候到從庫延時的問題,因此如今建議使用 Facebook OSC 這種思路更優雅
下圖是 Facebook OSC 的思路
後來 Percona 公司根據 Facebook OSC 思路,用 perl 重寫了一版,就是咱們如今用得不少的 pt-online-schema-change,軟件自己很是成熟,支持目前主流版本。
使用 pt-online-schema-change 的優勢有:
值得一提的是,騰訊互娛的 DBA 在內部分支上也實現了 Online DDL,以前測試過確實不錯,速度快,原理是經過修改 InnoDB 存儲格式來實現。
使用 pt-online-schema-change 的限制有:
可用性
關於可用性,咱們今天分享一種無縫切主庫方案,能夠用於平常切換,使用思路也比較簡單
在正常條件下如何無縫去作主庫切換,核心思路是讓新主庫和從庫停在相同位置,主要依賴 slave start until 語句,結合雙主結構,考慮自增問題。
MySQL 集羣方案:
MySQL 半同步複製:
如今也有一些理論上可用性更高的其它方案
紅框內是目前你們使用比較多的部署結構和方案。固然異常層面的 HA 也有不少第三方工具支持,好比 MHA、MMM 等,推薦使用 MHA。
sharding 拆分問題
-
Sharding is very complex, so itʼs best not to shard until itʼs obvious that you will actually need to!
-
sharding 是按照必定規則數據從新分佈的方式
-
主要解決單機寫入壓力過大和容量問題
-
主要有垂直拆分和水平拆分兩種方式
-
拆分要適度,切勿過分拆分
-
有中間層控制拆分邏輯最好,不然拆分過細管理成本會很高
曾經管理的單表最大 60 億+,單表數據文件大小 1TB+,人有時候就要懶一些。
上圖是水平拆分和垂直拆分的示意圖
數據庫備份
首先要保證的,最核心的是數據庫數據安全性。數據安全都保障不了的狀況下談其餘的指標 (如性能等),其實意義就不大了。
備份的意義是什麼呢?
目前備份方式的幾個緯度:
-
全量備份 VS 增量備份
-
熱備 VS 冷備
-
物理備份 VS 邏輯備份
-
延時備份
-
全量 binlog 備份
建議方式:
-
熱備+物理備份
-
核心業務:延時備份+邏輯備份
-
全量 binlog 備份
借用一下某大型互聯網公司作的備份系統數據:一年 7000+次擴容,一年 12+次數據恢復,日誌量天天 3TB,數據總量 2PB,天天備份數據量百 TB 級,整年備份 36 萬次,備份成功了 99.9%。
主要作的幾點:
-
備份策略集中式調度管理
-
xtrabackup 熱備
-
備份結果統計分析
-
備份數據一致性校驗
-
採用分佈式文件系統存儲備份
備份系統採用分佈式文件系統緣由:
-
解決存儲分配的問題
-
解決存儲 NFS 備份效率低下問題
-
存儲集中式管理
-
數據可靠性更好
使用分佈式文件系統優化點:
數據恢復方案
目前的 MySQL 數據恢復方案主要仍是基於備份來恢復,可見備份的重要性。好比我今天下午 15 點刪除了線上一張表,該如何恢復呢?首先確認刪除語句,而後用備份擴容實例啓動,假設備份時間點是凌晨 3 點,就還須要把凌晨 3 點到如今關於這個表的 binlog 導出來,而後應用到新擴容的實例上,確認好恢復的時間點,而後把刪除表的數據導出來應用到線上。
性能優化
複製優化
MySQL 複製:
問題不少,可是能解決基本問題。
上圖是 MySQL 複製原理圖,紅框內就是 MySQL 一直被人詬病的單線程問題。
單線程問題也是 MySQL 主從延時的一個重要緣由,單線程解決方案:
-
官方 5.6 + 多線程方案
-
Tungsten 爲表明的第三方並行複製工具
-
sharding
上圖是 MySQL5.6 目前實現的並行複製原理圖,是基於庫級別的複製,因此若是你只有一個庫,使用這個意義不大。
固然 MySQL 也認識到 5.6 這種並行的瓶頸所在,因此在 5.7 引入了另一種並行複製方式,基於 logical timestamp 的並行複製,並行複製再也不受限於庫的個數,效率會大大提高。
上圖是 5.7 的 logical timestamp 的複製原理圖
剛纔我也提到 MySQL 原來只支持異步複製,這種數據安全性是很是差的,因此後來引入了半同步複製,從 5.5 開始支持。
上圖是原生異步複製和半同步複製的區別。能夠看到半同步經過從庫返回 ACK 這種方式確認從庫收到數據,數據安全性大大提升。
在 5.7 以後,半同步也能夠配置你指定多個從庫參與半同步複製,以前版本都是默認一個從庫。
對於半同步複製效率問題有一個小的優化,就是使用 5.6 + 的 mysqlbinlog 以 daemon 方式做爲從庫,同步效率會好不少。
關於更安全的複製,MySQL 5.7 也是有方案的,方案名叫 Group replication 官方多主方案,基於 Corosync 實現。
主從延時問題
緣由:通常都會作讀寫分離,其實從庫壓力反而比主庫大/從庫讀寫壓力大很是容易致使延時。
解決方案:
-
首先定位延時瓶頸
-
若是是 IO 壓力,能夠經過升級硬件解決,好比替換 SSD 等
-
若是 IO 和 CPU 都不是瓶頸,很是有多是 SQL 單線程問題,解決方案能夠考慮剛纔提到的並行複製方案
-
若是還有問題,能夠考慮 sharding 拆分方案
提到延時不得不提到很坑人的 Seconds behind master,使用過 MySQL 的應該很熟悉。
這個值的源碼裏算法
long time_diff= ((long)(time(0) – mi->rli.last_master_timestamp) – mi->clock_diff_with_master);
Secondsbehindmaster
來判斷延時不可靠,在網絡抖動或者一些特殊參數配置狀況下,會形成這個值是 0 但其實延時很大了。經過 heartbeat 表插入時間戳這種機制判斷延時是更靠譜的
複製注意點:
主從數據一致性問題:
InnoDB 優化
成熟開源事務存儲引擎,支持 ACID,支持事務四個隔離級別,更好的數據安全性,高性能高併發,MVCC,細粒度鎖,支持 O_DIRECT。
主要優化參數:
-
innodbfileper_table =1
-
innodbbufferpool_size,根據數據量和內存合理設置
-
innodbflushlog_attrxcommit= 0 1 2
-
innodblogfile_size,能夠設置大一些
-
innodbpagesize
-
Innodbflushmethod = o_direct
-
innodbundodirectory 放到高速設備 (5.6+)
-
innodbbufferpool_dump
-
atshutdown ,bufferpool dump (5.6+)
上圖是 5.5 4G 的 redo log 和 5.6 設置大於 4G redo log 文件性能對比,能夠看到穩定性更好了。innodblogfile_size 設置仍是頗有意義的。
InnoDB 比較好的特性:
-
Bufferpool 預熱和動態調整大小,動態調整大小須要 5.7 支持
-
Page size 自定義調整,適應目前硬件
-
InnoDB 壓縮,大大下降數據容量,通常能夠壓縮 50%,節省存儲空間和 IO,用 CPU 換空間
-
Transportable tablespaces,遷移 ibd 文件,用於快速單表恢復
-
Memcached API,full text,GIS 等
InnoDB 在 SSD 上的優化:
-
在 5.5 以上,提升 innodbwriteiothreads 和 innodbreadiothreads
-
innodbiocapacity 須要調大 *
-
日誌文件和 redo 放到機械硬盤,undo 放到 SSD,建議這樣,但必要性不大
-
atomic write, 不須要 Double Write Buffer
-
InnoDB 壓縮
-
單機多實例
也要搞清楚 InnoDB 哪些文件是順序讀寫,哪些是隨機讀寫。
隨機讀寫:
-
datadir
-
innodbdata file_path
-
innodbundo directory
順序讀寫:
-
innodbloggrouphomedir
-
log-bin
InnoDB VS MyISAM:
TokuDB:
-
支持事務 ACID 特性,支持多版本控制 (MVCC)
-
基於 Fractal Tree Index,很是適合寫入密集場景
-
高壓縮比,原生支持 Online DDL
-
主流分支都支持,收費轉開源 。目前能夠和 InnoDB 媲美的存儲引擎
目前主流使用 TokuDB 主要是看中了它的高壓縮比,Tokudb 有三種壓縮方式:quicklz、zlib、lzma,壓縮比依次更高。如今不少使用 zabbix 的後端數據表都採用的 TokuDB,寫入性能好,壓縮比高。
下圖是我以前作的測試對比和 InnoDB
上圖是 sysbench 測試的和 InnoDB 性能對比圖,能夠看到 TokuDB 在測試過程當中寫入穩定性是很是好的。
tokudb 存在的問題:
好比咱們以前遇到過一個問題:TokuDB 的內部狀態顯示上一次完成的 checkpoint 時間是 「Jul 17 12:04:11 2014」,距離當時發現如今都快 5 個月了,結果堆積了大量 redo log 不能刪除,後來只能重啓實例,結果重啓還花了七八個小時。
MySQL 優化相關的 case
Query cache,MySQL 內置的查詢加速緩存,理念是好的, 但設計不夠合理,有點 out。
鎖的粒度很是大 MySQL 5.6 默認已經關閉
When the query cache helps, it can help a lot. When it hurts, it can hurt a lot. 明顯前半句已經沒有太大用處,在高併發下很是容易遇到瓶頸。
關於事務隔離級別 ,InnoDB 默認隔離級別是可重複讀級別,固然 InnoDB 雖然是設置的可重複讀,可是也是解決了幻讀的,建議改爲讀已提交級別,能夠知足大多數場景需求,有利於更高的併發,修改 transaction-isolation。
上圖是一個比較經典的死鎖 case,有興趣能夠測試下。
關於 SSD
關於 SSD,仍是提一下吧。某知名大 V 說過 「最近 10 年對數據庫性能影響最大的是閃存」,穩定性和性能可靠性已經獲得大規模驗證,多塊 SATA SSD 作 Raid5,推薦使用。採用 PCIe SSD,主流雲平臺都提供 SSD 雲硬盤支持。
最後說一下你們關注的單表 60 億記錄問題,表裏數據也是線上比較核心的。
先說下當時狀況,表結構比較簡單,都是 bigint 這種整型,索引比較多,應該有 2-3 個,單錶行數 60 億+,單表容量 1.2TB 左右,固然內部確定是有碎片的。
造成緣由:歷史遺留問題,按照咱們前面講的開發規範,這個應該早拆分了,固然不拆有幾個緣由:
咱們後續作的優化 ,採用了剛纔提到的 TokuDB,單表容量在 InnoDB 下 1TB+,使用 Tokudb 的 lzma 壓縮到 80GB,壓縮效果很是好。這樣也解決了單表過大恢復時間問題,也支持 online DDL,基本達到咱們預期。
今天講的主要針對 MySQL 自己優化和規範性質的東西,還有一些比較好的運維經驗,但願你們能有所收穫。今天這些內容是爲後續數據庫作平臺化的基礎。我今天分享就到這裏,謝謝你們。
QA
Q1:use schema;select from table; 和 select from schema.table; 兩種寫法有什麼不同嗎?會對主從同步有影響嗎?
對於主從複製來講執行效率上差異不大,不過在使用 replication filter 時候這種狀況須要當心,應該要使用 ReplicateWildIgnoreTable 這種參數,若是不使用帶 wildignore,第一種方式會有問題,過濾不起做用。
Q2:對於用於 MySQL 的 ssd,測試方式和 ssd 的參數配置方面,有沒有好的建議?主要針對 ssd 的配置哈
關於 SATA SSD 配置參數,建議使用 Raid5,想更保險使用 Raid50,更土豪使用 Raid 10
上圖是主要的參數優化,性能提高最大的是第一個修改調度算法的
Q3:數據庫規範已制定好,如何保證開發人員必須按照規範來開發?
關於數據庫規範實施問題,也是有兩個方面吧,第1、按期給開發培訓開發規範,讓開發能更瞭解。第2、仍是在流程上規範,好比把咱們平常通用的建表和字段策略固化到程序,作成自動化審覈系統。這兩方面結合 效果會比較好。
Q4:如何最大限度提升 innodb 的命中率?
這個問題前提是你的數據要有熱點,讀寫熱點要有交集,不然命中率很難提升。在有熱點的前提下,也要求你的你的內存要足夠大,可以存更多的熱點數據。儘可能不要作一些可能污染 bufferpool 的操做,好比全表掃描這種。
Q5:主從複製的狀況下,若是有 CAS 這樣的需求,是否是隻能強制連主庫?由於有延遲的存在,若是讀寫不在一塊兒的話,會有髒數據。
若是有 CAS 需求,確實仍是直接讀主庫好一些,由於異步複製仍是會有延遲的。只要 SQL 優化的比較好,讀寫都在主庫也是沒什麼問題的。
Q6:關於開發規範,是否有必要買國標?
這個國標是什麼東西,不太瞭解。不過從字面看,國標應該也是偏學術方面的,在具體工程實施時候未必能用好。
Q7:主從集羣能不能再細化一點那?不知道這樣問合適不?
看具體哪方面吧。主從集羣每一個小集羣通常都是採用一主多從方式,每一個小集羣對應特定的一組業務。而後監控備份和 HA 都是在每一個小集羣實現。
Q8:如何跟蹤數據庫 table 某個字段值發生變化?
追蹤字段值變化能夠經過分析 row 格式 binlog 好一些。好比之前同事就是經過本身開發的工具來解析 row 格式 binlog,跟蹤數據行變化。
Q9:對超大表水平拆分,在使用 MySQL 中間件方面有什麼建議和經驗分享?
對於超大表水平拆分,在中間件上經驗不是不少,早期人肉搞過幾回。也使用過本身研發的數據庫中間件,不過線上應用的規模不大。關於目前衆多的開源中間件裏,360 的 atlas 是目前還不錯的,他們公司內部應用的比較多。
Q10:咱們用的 MySQL proxy 作讀負載,可是少許數據壓力下並無負載,請問有這回事嗎?
少許數據壓力下,並無負載 ,這個沒測試過,很差評價
Q11:對於 binlog 格式,爲何只推薦 row,而不用網上大部分文章推薦的 Mix ?
這個主要是考慮數據複製的可靠性,row 更好。mixed 含義是指若是有一些容易致使主從不一致的 SQL ,好比包含 UUID 函數的這種,轉換爲 row。既然要革命,就搞的完全一些。這種 mix 的中間狀態最坑人了。
Q12: 讀寫分離,通常是在程序裏作,仍是用 proxy ,用 proxy 的話通常用哪一個?
這個仍是獨立寫程序好一些,與程序解耦方便後期維護。proxy 國內目前開源的比較多,選擇也要慎重。
Q13: 我想問一下關於 mysql 線程池相關的問題,什麼狀況下適合使用線程池,相關的參數應該如何配置,老師有這方面的最佳實踐沒有?
線程池這個我也沒測試過。從原理上來講,短連接更適合用線程池方式,減小創建鏈接的消耗。這個方面的最佳配置,我還沒測試過,後面測試有進展能夠再聊聊。
Q14: 誤刪數據這種,數據恢復流程是怎麼樣的 (從庫也被同步刪除的狀況)?
看你刪除數據的狀況,若是隻是一張表,單表在幾 GB 或幾十 GB。若是能有延時備份,對於數據恢復速度是頗有好處的。恢復流程能夠參考我剛纔分享的部分。目前的 MySQL 數據恢復方案主要仍是基於備份來恢復 ,可見備份的重要性。好比我今天下午 15 點刪除了線上一張表,該如何恢復呢。首先確認刪除語句,而後用備份擴容實例啓動,假設備份時間點是凌晨 3 點。就還須要把凌晨 3 點到如今關於這個表的 binlog 導出來,而後應用到新擴容的實例上。確認好恢復的時間點,而後把刪除表的數據導出來應用到線上。
Q15: 關於備份,binlog 備份天然不用說了,物理備份有不少方式,有沒有推薦的一種,邏輯備份在量大的時候恢復速度比較慢,通常用在什麼場景?
物理備份採用 xtrabackup 熱備方案比較好。邏輯備份通常用在單表恢復效果會很是好。好比你刪了一個 2G 表,但你總數據量 2T,用物理備份就會要慢了,邏輯備份就很是有用了。
-------------------------------------------------------------------------------------------------------------------------------------
秦 迪/5.4 微博在大規模、高負載系統問題排查方法379
5.4.1 背景379
5.4.2 排查方法及線索.379
5.4.3 總結384
5.4.4 疑問與解惑385
-------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------
秦 迪/5.5 系統運維之爲何每一個團隊存在大量爛代碼387
5.5.1 寫爛代碼很容易.387
5.5.2 爛代碼終究是爛代碼388
5.5.3 重構不是萬能藥.392
5.5.4 寫好代碼很難.393
5.5.5 悲觀的結語394
-------------------------------------------------------------------------------------------------------------------------------------
(1 條消息) 關於爛代碼的那些事 - 爲何每一個團隊存在大量爛代碼_weixin_45583158 的博客 - CSDN 博客
編者按:本文由秦迪向「高可用架構」投稿,介紹編寫好代碼的思考與感悟。轉載請註明來自高可用架構公衆號「ArchNotes」。
秦迪,微博研發中心技術專家,2013 年加入微博,負責微博平臺通信系統的設計和研發、微博平臺基礎工具的開發和維護,並負責微博平臺的架構改進工做,在工做中擅長排查複雜系統的各種疑難雜症。愛折騰,喜歡研究從內核到前端的全部方向,近幾年重點關注大規模系統的架構設計和性能優化,重度代碼潔癖:以 code review 爲己任,重度工具控:有現成工具的問題就用工具解決,沒有工具能解決的問題就寫個工具解決。業餘時間喜歡偶爾換個語言寫代碼放鬆一下。
「一我的工做了幾年、作過不少項目、帶過團隊、發了一些文章,不必定能表明他代碼寫的好;反之,一我的代碼寫的好,其它方面的能力通常不會太差。」 —— 秦迪
最近寫了很多代碼,review 了很多代碼,也作了很多重構,總之是對着爛代碼工做了幾周。爲了抒發一下這幾周裏好幾回到達崩潰邊緣的情緒,我決定寫一篇文章談一談爛代碼的那些事。這裏是上篇,談一談爛代碼產生的緣由和現象。
一、寫爛代碼很容易
剛入程序員這行的時候常常聽到一個觀點:你要把精力放在 ABCD(需求文檔 / 功能設計 / 架構設計 / 理解原理)上,寫代碼只是把想法翻譯成編程語言而已,是一個沒什麼技術含量的事情。
當時的我在聽到這種觀點時會有一種近似於高冷的不屑:大家就是一羣傻 X,根本不懂代碼質量的重要性,這麼下去早晚有一天會踩坑,呸。
但是幾個月以後,他們彷佛也沒怎麼踩坑。而隨着編程技術一直在不斷髮展,帶來了更多的我之前認爲是傻 X 的人加入到程序員這個行業中來。
語言愈來愈高級、封裝愈來愈完善,各類技術都在幫助程序員提升生產代碼的效率,依靠層層封裝,程序員真的不須要了解一丁點技術細節,只要把需求裏的內容逐行翻譯出來就能夠了。
不少程序員不知道要怎麼組織代碼、怎麼提高運行效率、底層是基於什麼原理,他們寫出來的是在我心目中一堆垃圾代碼。可是那一坨垃圾代碼居然能正常工做。
即便我認爲他們寫的代碼是垃圾,可是從不接觸代碼的人的視角來看(好比說你的 boss),代碼編譯過了,測試過了,上線運行了一個月都沒出問題,你還想要奢求什麼?
因此,即便不情願,也必須認可,時至今日,寫代碼這件事自己沒有那麼難了。
二、爛代碼終究是爛代碼
可是偶爾有那麼幾回,寫爛代碼的人離職了以後,事情彷佛又變得不同了。
想要修改功能時卻發現程序裏充斥着各類沒法理解的邏輯、改完以後莫名其妙的 bug 一個接一個,接手這個項目的人開始漫無目的的加班,而且本來一個挺樂觀開朗的人漸漸的開始沒法接受了。
我總結了幾類常常被鄙視到的爛代碼:
2.1 意義不明
能力差的程序員容易寫出意義不明的代碼,他們不知道本身究竟在作什麼。
就像這樣:
1 2 3 4 5 6 |
public void save() { for(int i=0;i<100;i++) { // 防止保存失敗,重試 100 次 document.save(); } } |
對於這類程序員,建議他們儘早轉行。
2.2 不說人話
不說人話是新手最常常出現的問題,直接的表現就是寫了一段很簡單的代碼,其餘人卻看不懂。
好比下面這段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public boolean getUrl(Long id) { UserProfile up = us.getUser(ms.get(id).getMessage().aid); if (up == null) { return false; } if (up.type == 4 || ((up.id >> 2) & 1) == 1) { return false; } if(Util.getUrl(up.description)) { return true; } else { return false; } } |
還有不少程序員喜歡複雜,各類宏定義、位運算之類寫的天花亂墜,生怕代碼讓別人一會兒看懂了會顯得本身水平不夠。
簡單的說,他們的代碼是寫給機器的,不是給人看的。
2.3 不恰當的組織
不恰當的組織是高級一些的爛代碼,程序員在寫過一些代碼以後,有了基本的代碼風格,可是對於規模大一些的工程的掌控能力不夠,不知道代碼應該如何解耦、分層和組織。
這種反模式的現象是常常會看到一段代碼在工程裏拷來拷去;某個文件裏放了一大坨堆砌起來的代碼;一個函數堆了幾百上千行;或者一個簡單的功能七拐八繞的調了幾十個函數,在某個難以發現的猥瑣的小角落裏默默的調用了某些關鍵邏輯。
這類代碼大多複雜度高,難以修改,常常一改就崩;而另外一方面,創造了這些代碼的人傾向於修改代碼,畏懼創造代碼,他們寧願讓本來複雜的代碼一步步變得更復雜,也不肯意從新組織代碼。當你面對一個幾千行的類,問爲何不把某某邏輯提取出來的時候,他們會說:
「可是,那樣就多了一個類了呀。」
2.4. 假設和缺乏抽象
相對於前面的例子,假設這種反模式出現的場景更頻繁,花樣更多,始做俑者也更難以本身意識到問題。好比:
1 2 3 4 |
public String loadString() { File file = new File("c:/config.txt"); // read something } |
文件路徑變動的時候,會把代碼改爲這樣:
1 2 3 4 |
public String loadString(String name) { File file = new File(name); // read something } |
須要加載的內容更豐富的時候,會再變成這樣:
1 2 3 4 5 6 7 8 |
public String loadString(String name) { File file = new File(name); // read something } public Integer loadInt(String name) { File file = new File(name); // read something } |
以後可能會再變成這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public String loadString(String name) { File file = new File(name); // read something } public String loadStringUtf8(String name) { File file = new File(name); // read something } public Integer loadInt(String name) { File file = new File(name); // read something } public String loadStringFromNet(String url) { HttpClient ... } public Integer loadIntFromNet(String url) { HttpClient ... } |
這類程序員每每是項目組裏開發效率比較高的人,可是大量的業務開發工做致使他們不會作多餘的思考,他們的口頭禪是:「我天天要作 XX 個需求」 或者 「先作完需求再考慮其餘的吧」。
這種反模式表現出來的後果每每是代碼很難複用,面對 deadline 的時候,程序員迫切的想要把需求落實成代碼,而這每每也會是個循環:寫代碼的時候來不及考慮複用,代碼難複用致使以後的需求還要繼續寫大量的代碼。
一點點積累起來的大量的代碼又帶來了組織和風格一致性等問題,最後造成了一個新功能基本靠拷的遺留系統。
2.5 還有嗎
爛代碼還有不少種類型,沿着功能 - 性能 - 可讀 - 可測試 - 可擴展這條路線走下去,還能看到不少匪夷所思的例子。
那麼什麼是爛代碼?我的認爲,爛代碼包含了幾個層次:
因此,當一個團隊裏的底層代碼難以閱讀、耦合了上層的邏輯致使難以測試、或者對使用場景作了過多的假設致使難以複用時,雖然完成了功能,它依然是垃圾代碼。
2.6 夠用的代碼
而相對的,若是一個工程的代碼難以閱讀,能不能說這個是爛代碼?很難下定義,可能算不上好,可是能說它爛嗎?若是這個工程自始至終只有一我的維護,那我的也維護的很好,那它彷佛就成了 「夠用的代碼」。
不少工程剛開始可能只是一我的負責的小項目,你們關心的重點只是代碼能不能順利的實現功能、按時完工。
過上一段時間,其餘人蔘與時才發現代碼寫的有問題,看不懂,不敢動。需求方又開始催着上線了,怎麼辦?只好當心翼翼的只改邏輯而不動結構,而後在註釋裏寫上這麼實現很 ugly,之後明白內部邏輯了再重構。
再過上一段時間,有個類似的需求,想要複用裏面的邏輯,這時才意識到代碼裏作了各類特定場景的專用邏輯,複用很是麻煩。爲了趕進度只好拷代碼而後改一改。問題解決了,問題也加倍了。
幾乎全部的爛代碼都是從 「夠用的代碼」 演化來的,代碼沒變,使用代碼的場景發生變了,本來夠用的代碼不符合新的場景,那麼它就成了爛代碼。
三、重構不是萬能藥
程序員最喜歡跟程序員說的謊言之一就是:如今進度比較緊,等 X 個月以後項目進度寬鬆一些再去作重構。
不可否認在某些(極其有限的)場景下重構是解決問題的手段之一,可是寫了很多代碼以後發現,重構每每是程序開發過程當中最複雜的工做。花一個月寫的爛代碼,要花更長的時間、更高的風險去重構。
曾經經歷過幾回忍無可忍的大規模重構,每一次重構以前都是找齊了組裏的高手,開了無數次分析會,把組內需求所有暫停以後纔敢開工,而重構過程當中每每哀嚎遍野,幾乎天天都會出上不少意料以外的問題,上線時也幾乎必然會出幾個問題。
從技術上來講,重構複雜代碼時,要作三件事:理解舊代碼、分解舊代碼、構建新代碼。而待重構的舊代碼每每難以理解;模塊之間過分耦合致使牽一髮而動全身,不易控制影響範圍;舊代碼不易測試致使沒法保證新代碼的正確性。
這裏還有一個核心問題,重構的複雜度跟代碼的複雜度不是線性相關的。好比有 1000 行爛代碼,重構要花 1 個小時,那麼 5000 行爛代碼的重構可能要花 二、3 天。要對一個失去控制的工程作重構,每每還不如重寫更有效率。
而拋開具體的重構方式,從受益上來講,重構也是一件很麻煩的事情:它很難帶來直接受益,也很難量化。這裏有個頗有意思的現象,基本關於重構的書籍無一例外的都會有獨立的章節介紹 「如何向 boss 說明重構的必要性」。
重構以後能提高多少效率?能下降多少風險?很難答上來,爛代碼自己就不是一個能夠簡單的標準化的東西。
舉個例子,一個工程的代碼可讀性不好,那麼它會影響多少開發效率?
你能夠說:以前改一個模塊要 3 天,重構以後 1 天就能夠了。可是怎麼應對 「不就是作個數據庫操做嗎爲何要 3 天」 這類問題?爛代碼 「爛」 的因素有不肯定性、開發效率也因人而異,想要證實這個東西 「確實」 會增長兩天開發時間,每每反而會變成 「我看了 3 天才看懂這個函數是作什麼的」 或者 「我作這麼簡單的修改要花 3 天」 這種神經病纔會去證實的命題。
而另外一面,許多技術負責人也意識到了代碼質量和重構的必要性,「那就重構嘛」,或者 「若是看到問題了,那就重構」。上一個問題解決了,但實際上關於重構的代價和收益仍然是一筆糊塗帳,在沒有分配給你更多資源、沒有明確的目標、沒有具體方法的狀況下,很難想象除了有代碼潔癖的人還有誰會去執行這種莫名 其妙的任務。
因而每每就會造成這種局面:
-
不寫代碼的人認爲應該重構,重構很簡單,不管新人仍是老人都有責任作重構。
-
寫代碼老手認爲應該早晚應該重構,重構很難,如今湊合用,這事別落在我頭上。
-
寫代碼的新手認爲不出 bug 就謝天謝地了,我也不知道怎麼重構。
四、寫好代碼很難
與寫出爛代碼不一樣的是,想寫出好代碼有不少前提:
-
理解要開發的功能需求。
-
瞭解程序的運行原理。
-
作出合理的抽象。
-
組織複雜的邏輯。
-
對本身開發效率的正確估算。
-
持續不斷的練習。
寫出好代碼的方法論不少,但我認爲寫出好代碼的核心反而是聽起來很是 low 的 「持續不斷的練習」。這裏就不展開了,留到下篇再說。
不少程序員在寫了幾年代碼以後並無什麼長進,代碼仍然爛的讓人不忍直視,緣由有兩個主要方面:
而工做幾年以後的人很難再說服他們去提升代碼質量,你只會反覆不斷的聽到:「那又有什麼用呢?」或者 「之前就是這麼作的啊?」 之類的說法。
那麼從源頭入手,提升招人時對代碼的質量的要求怎麼樣?
前一陣面試的時候增長了白板編程、最近又增長了上機編程的題目。發現了一個現象:一我的工做了幾年、作過不少項目、帶過團隊、發了一些文章,不必定能表明他代碼寫的好;反之,一我的代碼寫的好,其它方面的能力通常不會太差。
舉個例子,最近喜歡用 「寫一個代碼行數統計工具」 做爲面試的上機編程題目。不少人看到題目以後第一反映是,這道題太簡單了,這不就是寫寫代碼嘛。
從實際效果來看,這道題識別度卻還不錯。
首先,題目足夠簡單,即便沒有看過《面試寶典》之類書的人也不會吃虧。而題目的擴展性很好,即便提早知道題目,配合不一樣的條件,能夠變成不一樣的題目。好比要求按文件類型統計行數、或者要求提升統計效率、或者統計的同時輸出某些單詞出現的次數,等等。
從考察點來看,首先是基本的樹的遍歷算法;其次有必定代碼量,能夠看出程序員對代碼的組織能力、對問題的抽象能力;上機編碼能夠很簡單的看出應聘者是否是好久沒寫程序了;還包括對於程序易用性和性能的理解。
最重要的是,最後的結果是一個完整的程序,我能夠按照平常工做的標準去評價程序員的能力,而不是從十幾行的函數裏意淫這我的在平常工做中大概會有什麼表現。
但即便這樣,也很難拍着胸脯說,這我的寫的代碼質量沒問題。畢竟面試只是表明他有寫出好代碼的能力,而不是他未來會寫出好代碼。
五、悲觀的結語
說了那麼多,結論其實只有兩條,做爲程序員:
-
不要奢望其餘人會寫出高質量的代碼
-
不要覺得本身寫出來的是高質量的代碼
若是你看到了這裏尚未喪失但願,那麼能夠期待一下這篇文章的第二部分,關於如何提升代碼質量的一些建議和方法。
本文由秦迪投稿,編輯四正,想更多交流上述高質量代碼話題,能夠回覆 join 申請進羣。轉載本文請註明來自高可用架構 「ArchNotes」微信公衆號幷包含如下二維碼。
一、寫爛代碼很容易二、爛代碼終究是爛代碼可是偶爾有那麼幾回,寫爛代碼的人離職了以後,事情彷佛又變得不同了。2.1 意義不明2.2 不說人話2.3 不恰當的組織2.4. 假設和缺乏抽象2.5 還有嗎2.6 夠用的代碼三、重構不是萬能藥四、寫好代碼很難五、悲觀的結語
-------------------------------------------------------------------------------------------------------------------------------------
秦 迪/5.6 系統運維之評價代碼優劣的方法395
5.6.1 什麼是好代碼.395
5.6.2 結語403
5.6.3 參考閱讀403
-------------------------------------------------------------------------------------------------------------------------------------
關於爛代碼的那些事 – 評價代碼優劣的方法 - 雲 + 社區 - 騰訊雲秦迪,微博研發中心技術專家,2013 年加入微博,負責微博平臺通信系統的設計和研發、微博平臺基礎工具的開發和維護,並負責微博平臺的架構改進工做,在工做中擅長排查...
秦迪,微博研發中心技術專家,2013 年加入微博,負責微博平臺通信系統的設計和研發、微博平臺基礎工具的開發和維護,並負責微博平臺的架構改進工做,在工做中擅長排查複雜系統的各種疑難雜症。愛折騰,喜歡研究從內核到前端的全部方向,近幾年重點關注大規模系統的架構設計和性能優化,重度代碼潔癖:以 code review 爲己任,重度工具控:有現成工具的問題就用工具解決,沒有工具能解決的問題就寫個工具解決。業餘時間喜歡偶爾換個語言寫代碼放鬆一下。
「代碼重複分爲兩種:模塊內重複和模塊間重複。不管何種重複,都在必定程度上說明了程序員的水平有問題。」 —— 秦迪
這是爛代碼系列的第二篇,在文章中我會跟你們討論一下如何儘量高效和客觀的評價代碼的優劣。 在發佈了關於爛代碼的那些事(上)以後(參看文末連接),發現這篇文章居然意外的很受歡迎,不少人也描 (tu) 述(cao)了各自代碼中這樣或者那樣的問題。最近部門在組織 bootcamp,正好我負責培訓代碼質量部分,在培訓課程中讓你們花了很多時間去討論、改進、完善本身的代碼。雖然剛畢業的同窗對於代碼質量都很用心,但最終呈現出來的質量仍然沒能達到 「十分優秀」 的程度。 究其緣由,主要是不瞭解好的代碼 「應該」 是什麼樣的。
一、什麼是好代碼
寫代碼的第一步是理解什麼是好代碼。在準備 bootcamp 的課程的時候,我就爲這個問題犯了難,我嘗試着用一些精確的定義區分出 「優等品」、「良品」、「不良品」;可是在總結的過程當中,關於「什麼是好代碼」 的描述卻大多沒有可操做性。
1.1. 好代碼的定義
隨便從網上搜索了一下 「優雅的代碼」,找到了下面這樣的定義:
Bjarne Stroustrup,C++ 之父:
邏輯應該是清晰的,bug 難以隱藏;
依賴最少,易於維護;
錯誤處理徹底根據一個明確的策略;
性能接近最佳化,避免代碼混亂和無原則的優化;
整潔的代碼只作一件事。
Grady Booch,《面向對象分析與設計》做者:
整潔的代碼是簡單、直接的;
整潔的代碼,讀起來像是一篇寫得很好的散文;
整潔的代碼永遠不會掩蓋設計者的意圖,而是具備少許的抽象和清晰的控制行。
Michael Feathers,《修改代碼的藝術》做者:
整潔的代碼看起來老是像很在意代碼質量的人寫的;
沒有明顯的須要改善的地方;
代碼的做者彷佛考慮到了全部的事情。
看起來彷佛說的都頗有道理,但是實際評判的時候卻難以參考,尤爲是對於新人來講,如何理解 「簡單的、直接的代碼」 或者「沒有明顯的須要改善的地方」?
而實踐過程當中,不少同窗也確實面對這種問題:對本身的代碼老是處在一種內心不踏實的狀態,或者是本身以爲很好了,可是卻被其餘人認爲很爛,甚至有幾回我和新同窗由於代碼質量的標準一連討論好幾天,卻誰也說服不了誰:咱們都堅持本身對於好代碼的標準纔是正確的。
在經歷了無數次 code review 以後,我以爲這張圖彷佛總結的更好一些:
代碼質量的評價標準某種意義上有點相似於文學做品,好比對小說的質量的評價主要來自於它的讀者,由個體主觀評價造成一個相對客觀的評價。並非依靠字數,或者做者使用了哪些修辭手法之類的看似徹底客觀但實際沒有什麼意義的評價手段。
但代碼和小說還有些不同,它實際存在兩個讀者:計算機和程序員。就像上篇文章裏說的,即便全部程序員都看不懂這段代碼,它也是能夠被計算機理解並運行的。
因此對於代碼質量的定義我須要於從兩個維度分析:主觀的,被人類理解的部分;還有客觀的,在計算機裏運行的情況。
既然存在主觀部分,那麼就會存在個體差別,對於同一段代碼評價會由於看代碼的人的水平不一樣而得出不同的結論,這也是大多數新人面對的問題:他們沒有一個能夠執行的評價標準,因此寫出來的代碼質量也很難提升。
有些介紹代碼質量的文章講述的都是傾向或者原則,雖說的很對,可是實際指導做用不大。因此在這篇文章裏我但願儘量把評價代碼的標準用(我自認爲)與實際水平無關的評價方式表示出來。
1.2. 可讀的代碼
在權衡好久以後,我決定把可讀性的優先級排在前面:一個程序員更但願接手一個有 bug 可是看的懂的工程,仍是一個沒 bug 可是看不懂的工程?若是是後者,能夠直接關掉這個網頁,去作些對你來講更有意義的事情。
1.2.1. 逐字翻譯
在不少跟代碼質量有關的書裏都強調了一個觀點:程序首先是給人看的,其次纔是能被機器執行,我也比較認同這個觀點。在評價一段代碼能不能讓人看懂的時候,我習慣讓做者把這段代碼逐字翻譯成中文,試着組成句子,以後把中文句子讀給另外一我的沒有看過這段代碼的人聽,若是另外一我的能聽懂,那麼這段代碼的可讀性基本就合格了。
用這種判斷方式的緣由很簡單:其餘人在理解一段代碼的時候就是這麼作的。閱讀代碼的人會一個詞一個詞的閱讀,推斷這句話的意思,若是僅靠句子沒法理解,那麼就須要聯繫上下文理解這句代碼,若是簡單的聯繫上下文也理解不了,可能還要掌握更多其它部分的細節來幫助推斷。大部分狀況下,理解一句代碼在作什麼須要聯繫的上下文越多,意味着代碼的質量越差。
逐字翻譯的好處是能讓做者能輕易的發現那些只有本身知道的、沒有體如今代碼裏的假設和可讀性陷阱。沒法從字面意義上翻譯出本來意思的代碼大多都是爛代碼,好比 「ms 表明 messageService 「,或者 「 ms.proc() 是發消息 「,或者 「 tmp 表明當前的文件」。
1.2.2. 遵循約定
約定包括代碼和文檔如何組織,註釋如何編寫,編碼風格的約定等等,這對於代碼將來的維護很重要。對於遵循何種約定沒有一個強制的標準,不過我更傾向於遵照更多人的約定。
與開源項目保持風格一致通常來講比較靠譜,其次也能夠遵照公司內部的編碼風格。可是若是公司內部的編碼風格和當前開源項目的風格衝突比較嚴重,每每表明着這個公司的技術傾向於封閉,或者已經有些跟不上節奏了。
可是不管如何,遵照一個約定總比本身創造出一些規則要好不少,這下降了理解、溝通和維護的成本。若是一個項目本身創造出了一些奇怪的規則,可能意味着做者看過的代碼不夠多。
一個工程是否遵循了約定每每須要代碼閱讀者有必定經驗,或者須要藉助 checkstyle 這樣的靜態檢查工具。若是感受無處下手,那麼大部分狀況下跟着 google 作應該不會有什麼大問題:能夠參考 google code style ,其中一部分有對應的 中文版 。
另外,沒有必要糾結於遵循了約定到底有什麼收益,就好像走路是靠左好仍是靠右好同樣,即便得出告終論也沒有什麼意義,大部分約定只要遵照就能夠了。
1.2.3. 文檔和註釋
文檔和註釋是程序很重要的部分,他們是理解一個工程或項目的途徑之一。二者在某些場景下定位會有些重合或者交叉(好比 javadoc 實際能夠算是文檔)。
對於文檔的標準很簡單,能找到、能讀懂就能夠了,通常來講我比較關心這幾類文檔:
對於項目的介紹,包括項目功能、做者、目錄結構等,讀者應該能 3 分鐘內大體理解這個工程是作什麼的。
針對新人的 QuickStart,讀者按照文檔說明應該能在 1 小時內完成代碼構建和簡單使用。
針對使用者的詳細說明文檔,好比接口定義、參數含義、設計等,讀者能經過文檔瞭解這些功能(或接口)的使用方法。
有一部分註釋實際是文檔,好比以前提到的 javadoc。這樣能把源碼和註釋放在一塊兒,對於讀者更清晰,也能簡化很多文檔的維護的工做。
還有一類註釋並不做爲文檔的一部分,好比函數內部的註釋,這類註釋的職責是說明一些代碼自己沒法表達的做者在編碼時的思考,好比 「爲何這裏沒有作 XXX」,或者 「這裏要注意 XXX 問題」。
通常來講我首先會關心註釋的數量:函數內部註釋的數量應該不會有不少,也不會徹底沒有,我的的經驗值是滾動幾屏幕看到一兩處左右比較正常。過多的話可能意味着代碼自己的可讀性有問題,而若是一點都沒有可能意味着有些隱藏的邏輯沒有說明,須要考慮適當的增長一點註釋了。
其次也須要考慮註釋的質量:在代碼可讀性合格的基礎上,註釋應該提供比代碼更多的信息。文檔和註釋並非越多越好,它們可能會致使維護成本增長。關於這部分的討論能夠參考簡潔部分的內容。
1.2.4. 推薦閱讀
《代碼整潔之道》
1.3. 可發佈的代碼
新人的代碼有一個比較典型的特徵,因爲缺乏維護項目的經驗,寫的代碼總會有不少考慮不到的地方。好比說測試的時候彷佛沒什麼異常,項目發佈以後才發現有不少意料以外的情況;而出了問題以後不知道從哪下手排查,或者僅能讓系統處於一個並不穩定的狀態,依靠一些巧合勉強運行。
1.3.1. 處理異常
新手程序員廣泛沒有處理異常的意識,但代碼的實際運行環境中充滿了異常:服務器會死機,網絡會超時,用戶會胡亂操做,不懷好意的人會惡意攻擊你的系統。
我對一段代碼異常處理能力的第一印象來自於單元測試的覆蓋率。大部分異常難以在開發或者測試環境裏復現,即便有專業的測試團隊也很難在集成測試環境中模擬全部的異常狀況。
而單元測試能夠比較簡單的模擬各類異常狀況,若是一個模塊的單元測試覆蓋率連 50% 都不到,很難想象這些代碼考慮了異常狀況下的處理,即便考慮了,這些異常處理的分支都沒有被驗證過,怎麼期望實際運行環境中出現問題時表現良好呢?
1.3.2. 處理併發
我收到的不少簡歷裏都寫着:精通併發編程 / 熟悉多線程機制,諸如此類,跟他們聊的時候也說的頭頭是道,什麼鎖啊互斥啊線程池啊同步啊信號量啊一堆一堆的名詞口若懸河。而給應聘者一個實際場景,讓應聘者寫一段很簡單的併發編程的小程序,能寫好的卻很少。
實際上併發編程也確實很難,若是說寫好同步代碼的難度爲 5,那麼併發編程的難度能夠達到 100 。這並非危言聳聽,不少看似穩定的程序,在面對併發場景的時候仍然可能出現問題:好比最近咱們就碰到了一個 linux kernel 在調用某個系統函數時因爲同步問題而出現 crash 的狀況。
而是否高質量的實現併發編程的關鍵並非是否應用了某種同步策略,而是看代碼中是否保護了共享資源:
局部變量以外的內存訪問都有併發風險(好比訪問對象的屬性,訪問靜態變量等)
訪問共享資源也會有併發風險(好比緩存、數據庫等)。
被調用方若是不是聲明爲線程安全的,那麼頗有可能存在併發問題(好比 java 的 hashmap )。
全部依賴時序的操做,即便每一步操做都是線程安全的,仍是存在併發問題(好比先刪除一條記錄,而後把記錄數減一)。
前三種狀況可以比較簡單的經過代碼自己分辨出來,只要簡單培養一下本身對於共享資源調用的敏感度就能夠了。
可是對於最後一種狀況,每每很難簡單的經過看代碼的方式看出來,甚至出現併發問題的兩處調用並非在同一個程序裏(好比兩個系統同時讀寫一個數據庫,或者併發的調用了一個程序的不一樣模塊等)。可是,只要是代碼裏出現了不加鎖的,訪問共享資源的 「先作 A,再作 B」 之類的邏輯,可能就須要提升警戒了。
1.3.3. 優化性能
性能是評價程序員能力的一個重要指標,不少程序員也對程序的性能津津樂道。但程序的性能很難直接經過代碼看出來,每每要藉助於一些性能測試工具,或者在實際環境中執行纔能有結果。
若是僅從代碼的角度考慮,有兩個評價執行效率的辦法:
算法的時間複雜度,時間複雜度高的程序運行效率必然會低。
單步操做耗時,單步耗時高的操做盡可能少作,好比訪問數據庫,訪問 io 等。
而實際工做中,也會見到一些程序員過於熱衷優化效率,相對的會帶來程序易讀性的下降、複雜度提升、或者增長工期等等。對於這類狀況,簡單的辦法是讓做者說出這段程序的瓶頸在哪裏,爲何會有這個瓶頸,以及優化帶來的收益。
固然,不管是優化不足仍是優化過分,判斷性能指標最好的辦法是用數聽說話,而不是單純看代碼,性能測試這部份內容有些超出這篇文章的範圍,就不詳細展開了。
1.3.4. 日誌
日誌表明了程序在出現問題時排查的難易程度,經 (jing) 驗(chang)豐 (cai) 富(keng)的程序員大概都會遇到過這個場景:排查問題時就少一句日誌,查不到某個變量的值不知道是什麼,致使死活分析不出來問題到底出在哪。
對於日誌的評價標準有三個:
日誌是否足夠,全部異常、外部調用都須要有日誌,而一條調用鏈路上的入口、出口和路徑關鍵點上也須要有日誌。
日誌的表達是否清晰,包括是否能讀懂,風格是否統一等。這個的評價標準跟代碼的可讀性同樣,不重複了。
日誌是否包含了足夠的信息,這裏包括了調用的上下文、外部的返回值,用於查詢的關鍵字等,便於分析信息。
對於線上系統來講,通常能夠經過調整日誌級別來控制日誌的數量,因此打印日誌的代碼只要不對閱讀形成障礙,基本上都是能夠接受的。
1.3.5. 擴展閱讀
《Release It!: Design and Deploy Production-Ready Software》(不要看中文版,翻譯的實在是太爛了)
Numbers Everyone Should Know
1.4. 可維護的代碼
相對於前兩類代碼來講,可維護的代碼評價標準更模糊一些,由於它要對應的是將來的狀況,通常新人很難想象如今的一些作法會對將來形成什麼影響。不過根據個人經驗,通常來講,只要反覆的提問兩個問題就能夠了:
他離職了怎麼辦?
他沒這麼作怎麼辦?
1.4.1. 避免重複
幾乎全部程序員都知道要避免拷代碼,可是拷代碼這個現象仍是不可避免的成爲了程序可維護性的殺手。
代碼重複分爲兩種:模塊內重複和模塊間重複。不管何種重複,都在必定程度上說明了程序員的水平有問題,模塊內重複的問題更大一些,若是在同一個文件裏都能出現大片重複的代碼,那表示他什麼難以想象的代碼都有可能寫出來。
對於重複的判斷並不須要反覆閱讀代碼,通常來講現代的 IDE 都提供了檢查重複代碼的工具,只需點幾下鼠標就能夠了。
除了代碼重複以外,不少熱衷於維護代碼質量的程序員新人很容易出現另外一類重複:信息重複。
我見過一些新人喜歡在每行代碼前面寫一句註釋,好比:
// 成員列表的長度 > 0 而且 0 && memberList.size() < 200) {
// 返回當前成員列表
return memberList;
}
看起來彷佛很好懂,可是幾年以後,這段代碼就變成了:
// 成員列表的長度 > 0 而且 0 && memberList.size() < 200 || (tmp.isOpen() && flag)) {
// 返回當前成員列表
return memberList;
}
再以後可能會改爲這樣:
//
// 成員列表的長度 > 0 而且 0 && memberList.size() < 200 || (tmp.isOpen() && flag)) {
// 返回當前成員列表
// return memberList;
//}
if(tmp.isOpen() && flag) {
return memberList;
}
隨着項目的演進,無用的信息會越積越多,最終甚至讓人沒法分辨哪些信息是有效的,哪些是無效的。
若是在項目中發現好幾個東西都在作同一件事情,好比經過註釋描述代碼在作什麼,或者依靠註釋替代版本管理的功能,那麼這些代碼也不能稱爲好代碼。
1.4.2. 模塊劃分
模塊內高內聚與模塊間低耦合是大部分設計遵循的標準,經過合理的模塊劃分可以把複雜的功能拆分爲更易於維護的更小的功能點。
通常來講能夠從代碼長度上初步評價一個模塊劃分的是否合理,一個類的長度大於 2000 行,或者一個函數的長度大於兩屏幕都是比較危險的信號。
另外一個可以體現模塊劃分水平的地方是依賴。若是一個模塊依賴特別多,甚至出現了循環依賴,那麼也能夠反映出做者對模塊的規劃比較差,從此在維護這個工程的時候頗有可能出現牽一髮而動全身的狀況。
通常來講有很多工具能提供依賴分析,好比 IDEA 中提供的 Dependencies Analysis 功能,學會這些工具的使用對於評價代碼質量會有很大的幫助。
值得一提的是,絕大部分狀況下,不恰當的模塊劃分也會伴隨着極低的單元測試覆蓋率:複雜模塊的單元測試很是難寫的,甚至是不可能完成的任務。因此直接查看單元測試覆蓋率也是一個比較靠譜的評價方式。
1.4.3. 簡潔與抽象
只要提到代碼質量,必然會提到簡潔、優雅之類的形容詞。簡潔這個詞實際涵蓋了不少東西,代碼避免重複是簡潔、設計足夠抽象是簡潔,一切對於提升可維護性的嘗試實際都是在試圖作減法。
編程經驗不足的程序員每每不能意識到簡潔的重要性,樂於搗鼓一些複雜的玩意並樂此不疲。但複雜是代碼可維護性的天敵,也是程序員能力的一道門檻。
跨過門檻的程序員應該有能力控制逐漸增加的複雜度,總結和抽象出事物的本質,並體現到本身設計和編碼中。一個程序的生命週期也是在由簡入繁到化繁爲簡中不斷迭代的過程。
對於這部分我難以總結出簡單易行的評價標準,它更像是一種思惟方式,除了要理解、還須要練習。多看、多想、多交流,不少時候能夠簡化的東西會大大超出原先的預計。
1.4.4. 推薦閱讀
《重構 - 改善既有代碼的設計》
《設計模式 - 可複用面向對象軟件的基礎》
《Software Architecture Patterns-Understanding Common Architecture Patterns and When to Use Them》
二、結語
這篇文章主要介紹了一些評價代碼質量優劣的手段,這些手段中,有些比較客觀,有些主觀性更強。以前也說過,對代碼質量的評價是一件主觀的事情,這篇文章裏雖然列舉了不少評價手段。可是實際上,不少我認爲沒有問題的代碼也會被其餘人吐槽,因此這篇文章只能算是初稿,更多內容還須要從此繼續補充和完善。雖然每一個人對於代碼質量評價的傾向都不同,可是整體來講評價代碼質量的能力能夠被比做程序員的 「品味」,評價的準確度會隨着自身經驗的增長而增加。在這個過程當中,須要隨時保持思考、學習和批判的精神。
下篇文章裏,會談一談具體如何提升本身的代碼質量。
原文:http://www.open-open.com/lib/view/open1454117276261.html
-------------------------------------------------------------------------------------------------------------------------------------
秦 迪/5.7 系統運維之如何應對爛代碼404
5.7.1 改善可維護性.404
5.7.2 改善性能與健壯性409
5.7.3 改善生存環境.412
5.7.4 我的感想414
-------------------------------------------------------------------------------------------------------------------------------------
(1 條消息) 如何應對身邊的爛代碼_lilinshugg 的博客 - CSDN 博客
如何應對身邊的爛代碼
1. 改善可維護性
改善代碼質量是項大工程,要開始這項工程,從可維護性入手每每是一個好的開始,但也僅僅只是開始而已。
1.1. 重構的悖論
不少人把重構當作一種一次性運動,代碼實在是爛的無法改了,或者沒什麼新的需求了,就召集一幫人專門拿出來一段時間作重構。這在傳統企業開發中多少能生效,可是對於互聯網開發來講卻很難適應,緣由有兩個:
- 互聯網開發講究快速迭代,若是要作大型重構,每每須要暫停需求開發,這個基本上很難實現。
- 對於沒有什麼新需求的項目,每每意味着項目自己已通過了發展期,即便作了重構也帶來不了什麼收益。
這就造成了一個悖論:一方面那些變動頻繁的系統更須要重構;另外一方面重構又會耽誤開發進度,影響變動效率。
面對這種矛盾,一種方式是放棄重構,讓代碼質量天然降低,直到工程的生命週期結束,選擇放棄或者重來。在某些場景下這種方式確實是有效的,可是我並不喜歡:比起讓工程師不得不把天天的精力都浪費在毫無心義的事情上,爲何不作些更有意義的事呢?
1.2. 重構 step by step
1.2.1. 開始以前
開始改善代碼的第一步是把 IDE 的重構快捷鍵設到一個順手的鍵位上,這一步很是重要:決定重構成敗的每每不是你的新設計有多麼牛逼,而是重構自己會佔用多少時間。
好比對於 IDEA 來講,我會把重構菜單設爲快捷鍵:
這樣在我想去重構的時候就能夠隨手打開菜單,而不是用鼠標慢慢去點,快捷鍵每次只能爲重構節省幾秒鐘時間,可是卻能明顯減小工程師重構時的心理負擔,後面會提到,小規模的重構應該跟敲代碼同樣屬於平常開發的一部分。
我把重構分爲三類:模塊內部的重構、模塊級別的重構、工程級別的重構。分爲這三類並非由於我是什麼分類強迫症,後面會看到對重構的分類對於重構的意義。
1.2.2. 隨時進行模塊內部的重構
模塊內部重構的目的是把模塊內部的邏輯梳理清楚,而且把一個巨大無比的函數拆分紅可維護的小塊代碼。大部分 IDE 都提供了對這類重構的支持,相似於:
- 重命名變量
- 重命名函數
- 提取內部函數
- 提取內部常量
- 提取變量
這類重構的特色是修改基本集中在一個地方,對代碼邏輯的修改不多而且基本可控,IDE 的重構工具比較健壯,於是基本沒有什麼風險。
如下例子演示瞭如何經過 IDE 把一個冗長的函數作重構:
上圖的例子中,咱們基本依靠 IDE 就把一個冗長的函數分紅了兩個子函數,接下來就能夠針對子函數中的一些爛代碼作進一步的小規模重構,而兩個函數內部的重構也能夠用一樣的方法。每一次小規模重構的時間都不該該超過 60s,不然將會嚴重影響開發的效率,進而致使重構被無盡的開發需求淹沒。
在這個階段須要對現有的模塊補充一些單元測試,以保證重構的正確。不過以個人經驗來看,一些簡單的重構,例如修改局部變量名稱,或者提取變量之類的重構,即便沒有測試也是基本可靠的,若是要在快速完成模塊內部重構和 100% 的單元測試覆蓋率中選一個,我可能會選擇快速完成重構。
而這類重構的收益主要是提升函數級別的可讀性,以及消除超大函數,爲將來進一步作模塊級別的拆分打好基礎。
1.2.3. 一次只作一個較模塊級別的的重構
以後的重構開始牽扯到多個模塊,例如:
- 刪除無用代碼
- 移動函數到其它類
- 提取函數到新類
- 修改函數邏輯
IDE 每每對這類重構的支持有限,而且偶爾會出一些莫名其妙的問題,(例如修改類名時一不當心把配置文件裏的常量字符串也給修改了)。
這類重構主要在於優化代碼的設計,剝離不相關的耦合代碼,在這類重構期間你須要建立大量新的類和新的單元測試,而此時的單元測試則是必須的了。
爲何要建立單元測試?
- 一方面,這類重構由於涉及到具體代碼邏輯的修改,靠集成測試很難覆蓋全部狀況,而單元測試能夠驗證修改的正確性。
- 更重要的意義在於,寫不出單元測試的代碼每每意味着糟糕的設計:模塊依賴太多或者一個函數的職責過重,想象一下,想要執行一個函數卻要模擬十幾個輸入對象,每一個對象還要模擬本身依賴的對象…… 若是一個模塊沒法被單獨測試,那麼從設計的角度來考慮,無疑是不合格的。
還須要囉嗦一下,這裏說的單元測試只對一個模塊進行測試,依賴多個模塊共同完成的測試並不包含在內——例如在內存裏模擬了一個數據庫,並在上層代碼中測試業務邏輯 - 這類測試並不能改善你的設計。
在這個期間還會寫一些過渡用的臨時邏輯,好比各類 adapter、proxy 或者 wrapper,這些臨時邏輯的生存期可能會有幾個月到幾年,這些看起來沒什麼必要的工做是爲了控制重構範圍,例如:
class Foo {
String foo() {
...
}
}
若是要把函數聲明改爲
class Foo {
boolean foo() {
...
}
}
那麼最好經過加一個過渡模塊來實現:
class FooAdaptor {
private Foo foo;
boolean foo() {
return foo.foo().isEmpty();
}
}
這樣作的好處是修改函數時不須要改動全部調用方,爛代碼的特徵之一就是模塊間的耦合比較高,每每一個函數有幾十處調用,牽一髮而動全身。而一旦開始全面改造,每每就會把一次看起來很簡單的重構演變成幾周的大工程,這種大規模重構每每是不可靠的。
每次模塊級別的重構都須要精心設計,提早劃分好哪些是須要修改的,哪些是須要用兼容邏輯作過渡的。但實際動手修改的時間都不該該超過一天,若是超過一天就意味着此次重構改動太多,須要控制一下修改節奏了。
1.2.4. 工程級別的重構不能和任何其餘任務並行
不安全的重構相對而言影響範圍比較大,好比:
我更建議這類操做不要用 IDE,若是使用 IDE,也只使用最簡單的 「移動」 操做。這類重構單元測試已經徹底沒有做用,須要集成測試的覆蓋。不過也沒必要緊張,若是隻作 「移動」 的話,大部分狀況下基本的冒煙測試就能夠保證重構的正確性。
這類重構的目的是根據代碼的層次或者類型進行拆分,切斷循環依賴和結構上不合理的地方。若是不知道如何拆分,能夠依照以下思路:
- 優先按部署場景進行拆分,好比一部分代碼是公用的,一部分代碼是本身用的,能夠考慮拆成兩個部分。換句話說,A 服務的修改能不能影響 B 服務。
- 其次按照業務類型拆分,兩個無關的功能能夠拆分紅兩個部分。換句話說,A 功能的修改能不能影響 B 功能。
- 除此以外,儘可能控制本身的代碼潔癖,不要把代碼切成一大堆豆腐塊,會給往後的維護工做帶來不少沒必要要的成本。
- 案能夠提早 review 幾回,多參考一線工程師的意見,避免實際動手時才冒出新的問題。
而這類重構絕對不能跟正常的需求開發並行執行:代碼衝突幾乎沒法避免,而且會讓全部人崩潰。個人作法通常是在這類重構前先演練一次:把模塊按大體的想法拖來拖去,經過編譯器找到依賴問題,在平常上線中把容易處理的依賴問題解決掉;而後集中團隊裏的精英,通知全部人暫停開發,花最多 二、3 天時間把全部問題集中突擊掉,新的需求都在新代碼的基礎上進行開發。
若是歷史包袱實在過重,能夠把這類重構也拆成幾回作:先大致拆分紅幾塊,再分別拆分。不管如何,這類重構務必控制好變動範圍,一次嚴重的合併衝突有可能讓團隊中的全部人幾個周緩不過勁來。
1.3. 重構的週期
典型的重構週期相似下面的過程:
- 在正常需求開發的同時進行模塊內部的重構,同時理解工程原有代碼。
- 在需求間隙進行模塊級別的重構,把大模塊拆分爲多個小模塊,增長腳手架類,補充單元測試,等等。
- (若是有必要,好比工程過於巨大致使常常出現相互影響問題)進行一次工程級別的拆分,期間須要暫停全部開發工做,而且此次重構除了移動模塊和移動模塊帶來的修改以外不作任何其餘變動。
- 重複 一、2 步驟
1.3.1. 一些重構的 tips
- 只重構常常修改的部分,若是代碼一兩年都沒有修改過,那麼說明改動的收益很小,重構能改善的只是可維護性,重構不維護的代碼不會帶來收益。
- 抑制住本身想要多改一點的衝動,一次失敗的重構對代碼質量改進的影響多是毀滅性的。
- 重構須要不斷的練習,相比於寫代碼來講,重構或許更難一些。
- 重構可能須要很長時間,有可能甚至會達到幾年的程度(我以前用斷斷續續兩年多的時間重構了一個項目),主要取決於團隊對於風險的容忍程度。
- 刪除無用代碼是提升代碼可維護性最有效的方式,切記,切記。
- 單元測試是重構的基礎,若是對單元測試的概念還不是很清晰,能夠參考使用 Spock 框架進行單元測試。
2. 改善性能與健壯性
2.1. 改善性能的 80%
性能這個話題愈來愈多的被人提起,隨便收到一份簡歷不寫上點什麼熟悉高併發、作過性能優化之類的彷佛都很差意思跟人打招呼。
說個真事,幾年前在我作某公司的 ERP 項目,裏面有個功能是生成一個報表。而使用咱們系統的公司裏有一我的,他天天要在下班前點一下報表,導出到 excel,再發一封郵件出去。
問題是,那個報表每次都要 2,3 分鐘才能生成。
我當時正年輕氣盛,看到有個兩分鐘才能生成的報表一下就來了興趣,翻出了那段不知道誰寫的代碼,發現裏面用了 3 層循環,每次都會去數據庫查一次數據,再把一堆數據拼起來,一股腦塞進一個 tableview 裏。
面對這種代碼,我還能作什麼呢?
- 我馬上把那個三層循環幹掉了,經過一個存儲過程直接輸出數據。
- sql 數據計算的邏輯也被我精簡了,一些不必作的外聯操做被我幹掉了。
- 我還發現不少 ctrl+v 生成的無用的控件(那時仍是用的 delphi),那些控件密密麻麻的貼在顯示界面上,只是被前面的大 table 擋住了,我固然也把這些玩意都刪掉了;
- 打開界面的時候還作了一些雜七雜八的工做(好比去數據庫裏更新點擊數之類的),我把這些放到了異步任務裏。
- 後面我又以爲不必每次打開界面都要加載全部數據(那個 tableview 有幾千行,幾百列!),因而我 hack 了默認的 tableview,每次打開的時候先計算當前實際顯示了多少內容,把參數發給存儲過程,初始化只加載這些數據,剩下的再經過線程異步加載。
作了這些以後,界面只須要不到 1s 就能展現出來了,不過我要說的不是這個。
後來我去客戶公司給那個操做員演示新的模塊的時候,點一下,刷,數據出來了。那我的很驚恐的看着我,而後問我,是否是數據不許了。
再後來,我又加了一個功能,那個模塊每次打開以後都會顯示一個進度條,上面的標題是 「正在校驗數據……」,進度條走完大概要 1 分鐘左右,我跟那人說校驗數據計算量很大,會比較慢。固然,實際上那 60 秒里程序毛事都沒作,只是在一點點的更新那個進度條(我還作了個彩蛋,在讀進度的時候按上上下下左右左右 BABA 的話就能夠加速 10 倍讀條…)。客戶很開心,說感受數據準確多了,固然,他沒發現彩蛋。
我寫了這麼多,是想讓你明白一個事實:大部分程序對性能並不敏感。而少數對性能敏感的程序裏,一大半能夠靠調節參數解決性能問題;最後那一小撮須要修改代碼優化性能的程序裏,性價比高的工做又是少數。
什麼是性價比?回到剛纔的例子裏,我作了那麼多事,每件事的收益是多少?
- 把三層循環 sql 改爲了存儲過程,大概讓我花了一天時間,讓加載時間從 3 分鐘變成了 2 秒,模塊加載變成了」 唰 「的一下。
- 後面的一坨事情大概花了我一週多時間,尤爲是 hack 那個 tableview,讓我連週末都搭進去了。而全部的優化加起來,大概優化了 1 秒左右,這個數據是經過日誌查到的:即便是我本身,打開模塊也沒感受出有什麼明顯區別。
我如今遇到的不少面試者說程序優化時老是喜歡說一些玄乎的東西:調用棧、尾遞歸、內聯函數、GC 調優…… 可是當我問他們:把一個普通函數改爲內聯函數是把原來運行速度是多少的程序優化成多少了,卻不多有人答出來;或者是扭扭捏捏的說,應該不少,由於這個函數會被調用不少遍。我再問會被調用多少遍,每遍是多長時間,就答不上來了。
因此關於性能優化,我有兩個觀點:
- 優化主要部分,把一次網絡 IO 改成內存計算帶來的收益遠大於捯飭編譯器優化之類的東西。這部份內容能夠參考 Numbers you should know;或者本身寫一個 for 循環,作一個無限 i++ 的程序,看看一秒鐘 i 能累加多少次,感覺一下 cpu 和內存的性能。
- 性能優化以後要有量化數據,明確的說出優化後哪一個指標提高了多少。若是有人由於」 提高性能 「之類的理由寫了一堆讓人沒法理解的代碼,請務必讓他給出性能數據:這頗有多是一坨沒有什麼收益的爛代碼。
至於具體的優化措施,無外乎幾類:
- 讓計算靠近存儲
- 優化算法的時間複雜度
- 減小無用的操做
- 並行計算
關於性能優化的話題還能夠講不少內容,不過對於這篇文章來講有點跑題,這裏就再也不詳細展開了。
2.2. 決定健壯性的 20%
前一陣聽一個技術分享,說是他們在編程的時候要考慮太陽黑子對 cpu 計算的影響,或者是農民伯伯的豬把基站拱塌了之類的特殊場景。若是要優化程序的健壯性,那麼有時候就不得不去考慮這些極端狀況對程序的影響。
大部分的人應該不用考慮太陽黑子之類的高深的問題,可是咱們須要考慮一些常見的特殊場景,大部分程序員的代碼對於一些特殊場景都會有或多或少考慮不周全的地方,例如:
常規的方法確實可以發現代碼中的一些 bug,可是到了複雜的生產環境中時,總會出現一些徹底沒有想到的問題。雖然我也想了好久,遺憾的是,對於健壯性來講,我並無找到什麼立竿見影的解決方案,所以,我只能謹慎的提出一點點建議:
- 更多的測試,測試的目的是保證代碼質量,但測試並不等於質量,你作覆蓋 80% 場景的測試,在 20% 測試不到的地方仍是有可能出問題。關於測試又是一個巨大的話題,這裏就先不展開了。
- 謹慎發明輪子。例如 UI 庫、併發庫、IO client 等等,在能知足要求的狀況下儘可能採用成熟的解決方案,所謂的 「成熟」 也就意味着經歷了更多實際使用環境下的測試,大部分狀況下這種測試的效果是更好的。
3. 改善生存環境
看了上面的那麼多東西以後,你能夠想一下這麼個場景:
在你作了不少事情以後,代碼質量彷佛有了質的飛躍。正當你覺得終於能夠擺脫每天踩屎的日子了的時候,某次不當心瞥見某個類又長到幾千行了。
你憤怒的翻看提交日誌,想找出罪魁禍首是誰,結果卻發現天天都會有人往文件裏提交那麼十幾二十行代碼,每次的改動看起來都沒什麼問題,可是日積月累,一年年過去,當初花了九牛二虎之力重構的工程又成了一坨爛代碼……
任何一個對代碼有追求的程序員都有可能遇到這種問題,技術在更新,需求在變化,公司人員會流動,而代碼質量總會在不經意間偷偷的變差……
想要改善代碼質量,最後每每就會變成改善生存環境。
3.1.1. 統一環境
團隊須要一套統一的編碼規範、統一的語言版本、統一的編輯器配置、統一的文件編碼,若是有條件最好能使用統一的操做系統,這能避免不少無心義的工做。
就好像最近渣浪給開發所有換成了統一的 macbook,一晚上之間之前的不少問題都變得不是問題了:字符集、換行符、IDE 之類的問題只要一個配置文件就解決了,再也不有各類稀奇古怪的代碼衝突或者不兼容的問題,也不會有人忽然提交上來一些編碼格式稀奇古怪的文件了。
3.1.2. 代碼倉庫
代碼倉庫基本上已是每一個公司的標配,而如今的代碼倉庫除了儲存代碼,還能夠承擔一些團隊溝通、代碼 review 甚至工做流程方面的任務,現在這類開源的系統不少,像 gitlab(github)、Phabricator 這類優秀的工具都能讓代碼管理變得簡單不少。我這裏無心討論 svn、git、hg 仍是什麼其它的代碼管理工具更好,就算最近火熱的 git 在複雜性和集中化管理上也有一些問題,其實我是比較期待能有替代 git 的工具產生的,扯遠了。
代碼倉庫的意義在於讓更多的人可以得到和修改代碼,從而提升代碼的生命週期,而代碼自己的生命週期足夠持久,對代碼質量作的優化纔有意義。
3.1.3. 持續反饋
大多數爛代碼就像癌症同樣,當爛代碼已經產生了能夠感受到的影響時,基本已是晚期,很難治好了。
所以提早發現代碼變爛的趨勢很重要,這類工做能夠依賴相似於 checkstyle,findbug 之類的靜態檢查工具,及時發現代碼質量下滑的趨勢,例如:
- 天天都在產生大量的新代碼
- 測試覆蓋率降低
- 靜態檢查的問題增多
有了代碼倉庫以後,就能夠把這種工具與倉庫的觸發機制結合起來,每次提交的時候作覆蓋率、靜態代碼檢查等工做,jenkins+sonarqube 或者相似的工具就能夠完成基本的流程:伴隨着代碼提交進行各類靜態檢查、運行各類測試、生成報告並供人蔘考。
在實踐中會發現,關於持續反饋的五花八門的工具不少,可是真正有用的每每只有那麼一兩個,大部分人並不會去在每次提交代碼以後再打開一個網頁點擊 「生成報告」,或者去登錄什麼系統看一下測試的覆蓋率是否是變低了,所以一個一站式的系統大多數狀況下會表現的更好。與其追求更多的功能,不如把有限的幾個功能整合起來,例如咱們把代碼管理、迴歸測試、代碼檢查、和 code review 集成起來,就是這個樣子:
固然,關於持續集成還能夠作的更多,篇幅所限,就很少說了。
3.1.4. 質量文化
不一樣的團隊文化會對技術產生微妙的影響,關於代碼質量沒有什麼共同的文化,每一個公司都有本身的一套觀點,而且彷佛都能說得通。
對於我本身來講,關於代碼質量是這樣的觀點:
- 爛代碼沒法避免
- 爛代碼沒法接受
- 爛代碼能夠改進
- 好的代碼能讓工做更開心一些
如何讓大多數人認同關於代碼質量的觀點其實是有一些難度的,大部分技術人員對代碼質量的觀點是既不同意、也不反對的中立態度,而代碼質量就像是熵值同樣,放着無論老是會像更加混亂的方向演進,而且寫爛代碼的成本實在是過低了,以致於一個實習生花上一個禮拜就能夠毀了你花了半年精心設計的工程。
因此在提升代碼質量時,務必想辦法拉上團隊裏的其餘人一塊兒。雖然 「引導團隊提升代碼質量」 這件事情一開始會很辛苦,可是一旦有了一些支持者,而且有了能夠參考的模板以後,剩下的工做就簡單多了。
這裏推薦《佈道之道:引領團隊擁抱技術創新》這本書,裏面大部分的觀點對於代碼質量也是能夠借鑑的。僅靠喊口號很難讓其餘人寫出高質量的代碼,讓團隊中的其餘人體會到高質量代碼的收益,比喊口號更有說服力。
4. 最後再說兩句
優化代碼質量是一件頗有意思,也頗有挑戰性的事情,而挑戰不光來自於代碼本來有多爛,要改進的也並不僅是代碼自己,還有工具、習慣、練習、開發流程、甚至團隊文化這些方方面面的事情。
寫這一系列文章前先後後花了半年多時間,一直處在寫一點刪一點的狀態:我自身關於代碼質量的想法和實踐也在經歷着不斷變化。我更但願能寫出一些可以實踐落地的東西,而不是喊喊口號,忽悠忽悠 「敏捷開發」、「測試驅動」 之類的幾個名詞就結束了。
可是在寫文章的過程當中就會慢慢發現,不少問題的改進方法確實不是一兩篇文章能夠說明白的,問題之間每每又相互關聯,全都展開說甚至超出了一本書的信息量,因此這篇文章也只能刪去了不少內容。
我參與過不少代碼質量很好的項目,也參與過一些質量很爛的項目,改進了不少項目,也放棄了一些項目,從最初的單打獨鬥本身改代碼,到後來帶領團隊優化工做流程,經歷了不少。不管如何,關於爛代碼,我決定引用一下《佈道之道》這本書裏的一句話:
「‘更好’,其實不是一個目的地,而是一個方向… 在當前的位置和未來的目標之間,可能有不少至關不錯的地方。你只需關注離開如今的位置,而不要關心去向何方。」
-------------------------------------------------------------------------------------------------------------------------------------
第6 章 大數據與數據庫415王 勁/6.1 某音樂公司的大數據實踐.4156.1.1 什麼是大數據.4156.1.2 某音樂公司大數據技術架構4186.1.3 在大數據平臺重構過程當中踩過的坑4256.1.4 後續的持續改進.430王新春/6.2 實時計算在點評.4316.2.1 實時計算在點評的使用場景4316.2.2 實時計算在業界的使用場景4326.2.3 點評如何構建實時計算平臺4336.2.4 Storm 基礎知識簡單介紹.4346.2.5 如何保證業務運行的可靠性4366.2.6 Storm 使用經驗分享4386.2.7 關於計算框架的後續想法4426.2.8 疑問與解惑442王衛華/6.3 百姓網Elasticsearch 2.x 升級之路.4466.3.1 Elasticsearch 2.x 變化4466.3.2 升級之路4486.3.3 優化或建議4516.3.4 百姓之道4526.3.5 後話:Elasticsearch 5.04536.3.6 升級2.x 版本成功,5.x 版本還會遠嗎454董西成 張虔熙/6.4 Hadoop、HBase 年度回顧4576.4.1 Hadoop 2015 技術發展4576.4.2 HBase 2015 年技術發展4606.4.3 疑問與解惑466常 雷/6.5 解密Apache HAWQ——功能強大的SQL-on-Hadoop 引擎.4696.5.1 HAWQ 基本介紹4696.5.2 Apache HAWQ 系統架構.4726.5.3 HAWQ 中短時間規劃.4796.5.4 貢獻到Apache HAWQ 社區4796.5.5 疑問與解惑480蕭少聰/6.6 PostgresSQL HA 高可用架構實戰.4826.6.1 PostgreSQL 背景介紹.4826.6.2 在PostgreSQL 下如何實現數據複製技術的HA 高可用集羣4836.6.3 Corosync+Pacemaker MS 模式介紹4846.6.4 Corosync+Pacemaker M/S 環境配置4856.6.5 Corosync+Pacemaker HA 基礎配置4886.6.5 PostgreSQL Sync 模式當前的問題4926.6.6 疑問與解惑492王晶昱/6.7 從NoSQL 歷史看將來.4956.7.1 前言4956.7.2 1970 年:We have no SQL4966.7.3 1980 年:Know SQL 4976.7.4 2000 年:No SQL .5026.7.5 2005 年:不只僅是SQL 5046.7.6 2013 年:No,SQL .5056.7.7 阿里的技術選擇.5056.7.8 疑問與解惑506楊尚剛/6.8 MySQL 5.7 新特性大全和將來展望.5086.8.1 提升運維效率的特性5086.8.2 優化器Server 層改進.5116.8.3 InnoDB 層優化5136.8.4 將來發展5176.8.5 運維經驗總結.5186.8.6 疑問與解惑519譚 政/6.9 大數據盤點之Spark 篇5216.9.1 Spark 的特性以及功能5216.9.2 Spark 在Hulu 的實踐.5256.9.3 Spark 將來的發展趨勢5286.9.4 參考文章5306.9.5 疑問與解惑530蕭少聰/6.10 從Postgres 95 到PostgreSQL 9.5:新版亮眼特性5326.10.1 Postgres 95 介紹5326.10.2 PostgresSQL 版本發展歷史5336.10.3 PostgresSQL 9.5 的亮眼特性5346.10.4 PostgresSQL 還能夠作什麼5446.10.5 疑問與解惑547畢洪宇/6.11 MongoDB 2015 回顧:全新里程碑式的WiredTiger 存儲引擎5516.11.1 存儲引擎的發展5516.11.2 複製集改進.5556.11.3 自動分片機制5566.11.4 其餘新特性介紹5566.11.5 疑問與解惑.558王曉偉/6.12 基於Xapian 的垂直搜索引擎的構建分析5616.12.1 垂直搜索的應用場景5616.12.2 技術選型.5636.12.3 垂直搜索的引擎架構5646.12.4 垂直搜索技術和業務細節.5666.12.5 疑問與解惑568第7 章 安全與網絡572郭 偉/7.1 揭祕DDoS 防禦——騰訊雲大禹系統5727.1.1 有關DDoS 簡介的問答.5747.1.2 有關大禹系統簡介的問答5757.1.3 有關大禹系統硬件防禦能力的問答5767.1.4 有關算法設計的問答5777.1.5 大禹和其餘產品、技術的區別.578馮 磊 趙星宇/7.2 App 域名劫持之DNS 高可用——開源版HttpDNS 方案詳解5807.2.1 HttpDNSLib 庫組成.5817.2.2 HttpDNS 交互流程5827.2.3 代碼結構5837.2.4 開發過程當中的一些問題及應對.5867.2.5 疑問與解惑593馬 濤/7.3 CDN 對流媒體和應用分發的支持及優化5957.3.1 CDN 系統工做原理.5957.3.2 網絡分發過程當中ISP 的影響6027.3.3 防盜鏈.6037.3.4 內容分發系統的問題和應對思路6047.3.5 P2P 穿牆打洞6077.3.6 疑問與解惑609馬 濤/7.4 HTTPS 環境使用第三方CDN 的證書難題與最佳實踐611蔣海滔/7.5 互聯網主要安全威脅分析及應對方案6137.5.1 互聯網Web 應用面臨的主要威脅6137.5.2 威脅應對方案.6167.5.3 疑問與解惑624