後端好書閱讀與推薦(續)

MageekChiu發表於2019-02-16

續前文 後端好書閱讀與推薦 – Mageek`s Wonderland ,幾十天過去了,又看了兩本好書(還有以前看過的書),這裡依然把它們總結歸納一下,加入一些自己的看法、有用的連結和可能的延伸閱讀,並推薦給需要的同學。

深入理解Java虛擬機器

深入理解Java虛擬機器 (豆瓣) https://book.douban.com/subje…

Java怎麼用,是一個問題;怎麼用好是一個大問題;這麼用是為什麼,是一個更大的問題。搞懂這三個問題應該是每一個搞Java的人都要追求的目標,讀完本書,就能把這個更大的問題搞懂了。

本書亮點:

  • 模組化是解決應用系統與技術平臺越來越複雜,越來越龐大的問題的一個重要途徑,也是建立各種功能的標準件的前提。

  • Java執行時資料區幾個主要部分:程式計數器(可看作當前執行緒所執行的位元組碼的行號指示器)、虛擬機器棧(每一個方法從呼叫直至執行完成的過程,就對應著一個棧幀在虛擬機器棧中入棧到出棧的過程,而棧幀儲存區域性變數表、 運算元棧、 動態連結、 方法出口等資訊)、本地方法棧(Native方法對應的棧)、(所有執行緒共享的一塊記憶體區域,存放物件例項)、方法區(各個執行緒共享的記憶體區域,儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料)、常量池(方法區的一部分,存放編譯期生成的各種字面量和符號引用)。

  • 物件訪問方式取決於虛擬機器實現而定的,目前主流的訪問方式有使用控制程式碼和直接指標兩種:如果使用控制程式碼訪問的話,那麼Java堆中將會劃分出一塊記憶體來作為控制程式碼池,reference中儲存的就是物件的控制程式碼地址,而控制程式碼中包含了物件例項資料與型別資料各自的具體地址資訊;如果使用直接指標訪問,reference中儲存的直接就是物件地址。

  • Java垃圾回收採用分代回收機制,新生代和老生代採用不同的演算法(node.js也是),而不管什麼機制,判斷一個物件是否存活都是基本的步驟,方法有:引用計數法,給物件新增一個引用計數器,每當一個地方引用它時,計數器就加1,引用失效時,計數器就減1,任何時刻計數器為0的物件就是不可能再被使用的,這個方法實現簡單,但是不能解決迴圈引用問題,所以主流JVM不使用,但是這個演算法也適用於許多地方如Python,微軟的COM;可達性分析,按照物件之間的引用關係維護一個引用鏈,如果一個物件不可達GC Roots,那麼就是可回收的,應用於主流JVM中。

  • finalize是一種迎合C++程式設計師的妥協,執行代價高昂,不確定性大,無法保證各個物件的呼叫順序。有些教材中描述它適合做“關閉外部資源”之類的工作,這完全是對這個方法用途的一種自我安慰。finalize能做的所有工作,使用try-finally或者其他方式都可以做得更好、更及時,所以建議大家完全可以忘掉Java語言中有這個方法的存在。

  • 幾乎各種語言或多或少都提供過一些語法糖來方便程式設計師的程式碼開發,這些語法糖雖然不會提供實質性的功能改進,但是它們或能提高效率,或能提升語法的嚴謹性,或能減少編碼出錯的機會。不過也有一種觀點認為語法糖並不一定都是有益的,大量新增和使用“含糖”的語法,容易讓程式設計師產生依賴,無法看清語法糖的糖衣背後程式碼的真實面目。所以我們既要會使用語法糖,同時也要搞懂背後的原理,這樣才能進階呀。比如泛型技術:泛型只在程式原始碼中存在,在編譯後的位元組碼檔案中,就已經替換為原來的原生型別。

  • Java程式最初是通過直譯器(Interpreter)進行解釋執行的,當虛擬機器發現某個方法或程式碼塊的執行特別頻繁時,就會把這些程式碼認定“熱點程式碼”(Hot Spot Code)。 為了提高熱點程式碼的執行效率,在執行時,虛擬機器將會把這些程式碼編譯成與本地平臺相關的機器碼,並進行各種層次的優化,完成這個任務的編譯器稱為即時編譯器(Just In Time Compiler,簡稱JIT編譯器)

  • 基於快取記憶體(Cache)的儲存互動很好地解決了處理器與記憶體的速度矛盾,但是也為計算機系統帶來更高的複雜度,因為它引入了一個新的問題:快取一致性;除了增加快取記憶體之外,為了使得處理器內部的運算單元能儘量被充分利用,處理器可能會對輸入程式碼進行亂序執行(Out-Of-Order Execution)優化,類似的JVM也有指令重排(Instruction Reorder)。這兩點是提高程式執行的主要方法,然而也是多執行緒程式難以正確編寫的主要原因。

  • 先行發生(happens-before)是Java記憶體模型中定義的兩項操作之間的偏序關係,如果說操作A先行發生於操作B,其實就是說在發生操作B之前,操作A產生的影響能被操作B觀察到,“影響”包括修改了記憶體中共享變數的值、傳送了訊息、呼叫了方法等。時間先後順序與先行發生原則之間基本沒有太大的關係,所以我們衡量併發安全問題的時候不要受到時間順序的干擾,一切必須以先行發生原則為準。

這本書是基於jdk1.7的,如果要追蹤最新的Java和JVM規範,可以看這裡
另外,上面提到的第一個問題可以參閱Java核心技術,第二個問題可以參閱Java程式設計思想Effective Java,第三個問題還可以參閱HotSpot實戰。這幾本書我都瀏覽過,部分有細讀,都是相當經典的。

高效能MySQL

高效能MySQL (豆瓣) https://book.douban.com/subje…

這本書可謂是MySQL領域的權威之作,從架構到測試,從效能分析到查詢優化,從軟體配置優化到伺服器硬體優化,從單例項到主從複製、負載均衡,從底層資料庫優化到應用層優化……本書真可謂是面面俱到,同時又很有深度,絕非淺嘗輒止。

本書亮點:

  • 所謂的鎖策略,就是在鎖的開銷和資料的安全性之間尋求平衡,這種平衡自然也影響到效能。這句話適用於世間的一切工具,安全性和可用性總是矛盾的,我們在使用工具或者開發工具的時候,都要尋求一個最佳平衡點

  • 除非需要使用InnoDB不具備的的特性,並且沒有其他辦法可以替代,則都應該優先使用InnoDB引擎,也不要多引擎混用,例如全文索引可以使用InnoDB+Sphinx,而不要使用MyISAM引擎,因為InnoDB其他方面的優點可以完全碾壓MyISAM,比如崩潰恢復快,支援事務,支援行級鎖,支援真正的熱備份等等。

  • 基準測試可以驗證對於系統的假設,檢查異常行為,找出擴充套件性瓶頸等等。需要注意的是不要使用真實資料的子集,作物的資料分佈,忽略預熱等等,這些錯誤的操作會使得測試結果無用或者不精確。而且要建立引數與結果文件化的規範,這樣才利於結果分析與優化。

  • 很多人在優化時都將精力放在修改某些東西上,卻很少去測量;正確的做法是要儘量測量響應花的時間在哪,正確的測量一般都能將效能問題的點暴露出來,我們就能更好的對症下藥,而不是盲目優化(花1000塊優化一個只值500塊的業務,或者已經處於頂點的業務不就是虧了嗎)。所以說,決策要基於資料而不是感覺。

  • 良好的邏輯設計和物理設計是高效能的基石,某些反正規化的設計可能加快某些查詢,比如計數表和彙總表是一種很好的查詢優化方式,能提高統計類的查詢速度,但是維護起來就比較麻煩,可能降低資料插入速度。這些都需要自己根據業務來進行權衡(比如讀寫比),有陰就有陽嘛。

  • 選擇能正確儲存資料的最小型別:既省空間又省計算時間,簡單就好:比如使用MySQL內建時間戳而不是自己使用字串,儘量避免null:因為null使得索引、統計、值比較都更加複雜還可能會佔用更多空間。

  • InnoDB有一個“自適應雜湊索引”的功能,當引擎注意到某些索引值被頻繁使用時就會在記憶體中基於B+Tree索引之上再建立一個hash索引,這樣就讓B+Tree也具有hash索引的優點比如快速查詢。

  • 小表通常全表掃描更高效,中大型表才適合用索引,使用索引過程中要注意,索引列必須單獨的出現在比較符號的右側而不是表示式的一部分(這會使索引失效),使用字首索引來節省空間提高檢索效率,多列索引要注意順序不然容易失效,聚簇索引可以提高訪問速度。

  • 慢查詢優化:只向資料庫請求需要的列(比如不要隨意select * )、避免不必要的行掃描、必要的時候分解查詢(拆分大的查詢,分解關聯查詢)。

  • 預設配置檔案是經過大量測試,所以屬於較優解,一般符合普通使用者,要修改也主要是根據業務而不是伺服器配置;任何打算長期使用的配置都應該寫到全域性配置檔案而不是在命令列指定,因為如果偶然啟動忘了(事實是經常會忘,好記性不如爛筆頭是個真理)設定就會有風險。

  • MySQL複製功能不僅有利於構建高效能應用,同時也是高可用性(負載均衡、故障切換)、可擴充套件性(升級)、災難恢復、備份以及資料倉儲等工作的基礎。

  • 資料如果非常龐大,比如幾億行了,單臺機器已經撐不住了,通常要採取分片技術,分片最大的問題就是查詢與獲取資料,我們的目標是對最重要且頻繁查詢的資料減少分片(熱點資料通常就那麼多)。所以分片關鍵問題就在於選擇一個好的分割槽鍵,通常是一個資料庫中非常重要的實體的主鍵。

  • 不僅要關注MySQL,還要關注應用層優化:Apache伺服器處理靜態檔案都可能使用一個佔用記憶體很大的程式(上一個請求處理完後,該程式仍然保持著),所以最好使用Nginx或者Lighttpd來處理靜態內容服務,而且靜態檔名不要重用,要加上版本號,這樣就能避免瀏覽器快取問題;主動快取如Squid,被動快取如Memcached,都可以對效能獲得數量級的提升,關鍵就在於找到正確的粒度和快取過期策略組合,通常主動快取更好,因為對應用層隱藏了檢查-生成-儲存這個過程;

  • ……

亮點太多,列不完了,需要大家自己去尋找。另外,這本書不適宜一次性的精讀完畢(太厚,內容太多),可以快速瀏覽一遍,大概瞭解,以後遇上問題就可以把這本書當作一本問題解答手冊來查詢解決方案,或者找找靈感。

Redis實戰

Redis實戰 (豆瓣) https://book.douban.com/subje…

本書對redis的介紹是相當全面了,從基本用法講起,然後講了許多應用場景,包括購物車、資料庫快取等,然後講了一些常見問題的解決辦法,比如記憶體佔用過高,自定義擴充套件來豐富redis的用法等等,看完一本書過後就能很好的使用redis了(還有本好書:redis設計與實現,我大致瀏覽了一下,這本書主要講了redis的實現原理,這兩本書加起來就能既懂原理又會使用,把redis搞個透徹)。

本書亮點:

  • 使用冒號 : 或者管道號 | 等來實現名稱空間的作用,比如一個名為 article:12222 的hash存了這篇article的title,link,time等屬性,article:12223又是另一篇文章,這個可以部分實現資料庫檢索的功能。

  • 為了減少redis與客戶端之間的通訊次數,可以用multi和exec來做事務處理,事務會一次性的把一批命令一次發給redis,提高吞吐率;另外事務還能保證一批操作的原子性。在node-redis實現中,如果只是想提高吞吐率則可以用batch替代multi。

  • 通過複製(主從)和AOF能夠增強redis抵抗系統崩潰損失資料的能力,AOF如果用得不好的話,要麼損失很多資料,要麼嚴重降低吞吐量,比較合適的做法是appendfsync everysec 亦即把每一秒的命令一次同步進檔案。

  • 使用 redis 的 setnx 來實現基本上正確的的分散式鎖,再加上expire可以實現具有超時功能的鎖,保證即使獲得鎖的客戶端崩潰沒有主動釋放鎖時,其他程式也有機會獲得鎖。

  • 用 list 來替代 subscribe、publish 實現更可靠的釋出訂閱系統;另外,利用subscribe、publish加list來實現具有離線快取的訊息佇列系統,保證即使發生連線故障也能把訊息送達。

  • 利用反向索引以及 redis 集合的並、交、差功能可以實現簡易的搜尋引擎,利用 hash 結構的 sort 功能還能對搜尋結果進行簡易的排序功能,利用有序集合 zset 能實現更高階的排序功能。

  • 社交網站通常是用時間線這一資料結構來實現新鮮事瀏覽這一功能,雖然導致了大量的冗餘資訊,但是能夠節省查詢時間,這是典型的以空間換時間的操作,我以前做社交APP後臺的時候直接把所有新鮮事直接放在一張表中,然後按使用者id查詢,這樣雖然省了空間,但是如果使用者劇增((⊙﹏⊙)b,我們的APP並沒有使用者劇增)就不行了,一張表幾億行還怎麼查。

  • 使用短結構來節約記憶體,使用較短的鍵名節約記憶體,分片降低單例項的記憶體佔用。

  • 使用lua指令碼,在不編寫C程式碼的情況下,為redis新增新的功能。

時效性問題,本書沒有一些最新特性 比如 geohash 解決了地理座標問題,鍵的非同步釋放使得我們可以放心刪除而不必擔心大量資料的刪除使得redis短暫不可用。總之,要更好的利用redis還是要追蹤redis的最新變化,以便更簡潔、更靠譜的解決問題。

深入剖析Nginx

深入剖析Nginx (豆瓣) https://book.douban.com/subje…

本書從原始碼入手,依次講解了程式模型、模組、響應處理機制、過濾、負載均衡等相關原理,極大的滿足了我的好奇心,因為之前一直就對nginx高併發處理能力有一丟丟了解(比如nginx採用事件驅動機制而非apache的程式、執行緒每請求方式),自己也用過nginx,但是對他的原理還不是特別的明白,本書算是填補了我的這個空白。

本書亮點:

  • Nginx 將職責分為監控程式(主程式)和工作程式(主程式fork的子程式),監控程式與使用者互動並對工作程式進行監控管理,工作程式完成具體業務邏輯,兩者都有一個無限的for迴圈,這是服務程式的基本寫法。

  • Nginx僅提供針對大塊記憶體的回收不提供小塊記憶體的回收,這是因為web server的特殊性亦即階段和時效,請求就申請記憶體,處理完畢就釋放記憶體,所以不會存在nginx長時間佔據大量無用記憶體的情況,那麼小記憶體也自然不必急於回收,而是成為大記憶體後在回收。

  • 對於客戶端的請求,nginx將整個過程分為11個階段,每個階段有數個回撥函式進行專門的處理,每個階段的處理功能都比較單一,達到高內聚低耦合的目的。

  • Nginx是以事件為驅動的,也就是說Nginx內部流程的向前推進基本都是靠各種事件的觸發來驅動,內部事件主要有兩類:IO事件與定時器事件。其中IO事件主要靠epoll,epoll主要優點是監控數目不受檔案描述符限制、事件響應是觸發式的,不需要遍歷描述符(select需要)。

  • Nginx要處理動態的內容一般需要轉發給後端伺服器,常見的搭配是nginx+fastcgi+php,nginx把http請求轉化為fastcgi協議的資料後轉發給PHP引擎,PHP引擎處理結果後把資料返回給nginx,nginx把資料轉化為http格式返回給客戶端。

  • 負載均衡有多重含義(或者說多重級別),可以是程式上的(根據master程式根據子程式壓力調整其獲取監聽套介面的機率),更廣的意義上是指反向代理上,亦即nginx把請求均衡的轉發給後端伺服器如PHP引擎,發揮多個單元的整體效能。一般採取加權輪詢、IP hash等策略,但是隻靠nginx是不能實現完整的負載均衡的,詳見我以前寫的一篇文章

這本書主要從應用及其原理方面來介紹nginx,對於後端程式設計師應該是夠用了(我也忽略了許多原始碼,因為只是想了解一下原理)。但是對於要想自己深入、進行模組編寫的讀者應該還不夠用,可以再參考一下這本書 深入理解Nginx(第2版)(我大概瀏覽了一下,本書會指導讀者編寫具體的模組及其底層原理,比我們今天介紹的書更深入一些)。

第一本Docker書

第一本Docker書 修訂版 (豆瓣) https://book.douban.com/subje…

書如其名,這就是真正的第一本docker書。docker是什麼、怎麼安裝、如何使用、測試整合、構建服務等都有介紹。之前就久仰docker大名,也試著試用了一下,但是直到這本書讀完我才對docker有了一個完整的認知。另外,不出意料,本書的推薦序也很精彩。

本書亮點:

  • docker的核心價值在於可能改變軟體的交付方式和執行方式。傳統的交付方式下,軟體執行期依賴的環境是無法控制,不能標準化的,開發人員常常需要解決開發環境和生產環境的差別帶來的問題,而docker則把軟體及其依賴環境打包在一起,以映象形式交付,讓軟體執行在標準環境中,非常符合雲端計算的需求,同時docker的輕量虛擬化技術也符合例項水平擴充套件,資源動態調整的要求。

  • docker提供以下幾個好處:簡單輕量的建模方式,工程容易docker化,隨時修改程式碼,執行快速;職責分離,開發人員只管開發,運維人員只管容器管理,減少環境不同帶來的問題;快速高效的開發生命週期,程式容易部署、移植和協作;容易實現面向服務的架構和微服務架構

  • docker只支援64位架構,原生的Linux容器格式:libcontainer,使用名稱空間來隔離檔案系統(每個容器都有自己的root系統)、程式(每個容器都執行在自己的程式環境中)和網路(容器間虛擬網路地址和IP地址都是分開的),使用cgroups將CPU記憶體之類的資源獨立分配給容器,寫時複製使得檔案系統分層隔離、速度更快、佔用空間更小,還提供日誌和互動式shell。

  • 映象分層:新映象是從 base 映象一層一層疊加生成的,檔案系統發生變化時,就在現有映象的基礎上增加一層,這一層叫做“容器層”(讀寫層),“容器層”之下的都叫“映象層”(只讀層),只有當需要修改時才從映象層複製一份資料到容器層,這種特性被稱作寫時複製(Copy-on-Write),達到了映象共享,快速構建的目的。

  • 可以使用docker commit來構建映象,也可以基於dockerfile和docker build命令構建,通常建議使用後者,因為dockerfile更具備透明性(可以清晰地看出安裝了什麼軟體,修改了什麼配置)、可重複性(一次編寫,多次使用。此外構建快取還可以製作構建模板)、冪等性(同一個dockerfile不論執行多少次,結果都是相同的)。

  • docker容器之間通訊有3種方式,1.9之前推薦用Docker Link(安全:只有link之前的容器可以通訊,不必硬編碼,不支援多主機),1.9之後推薦Docker Networking(支援多主機容器連線,可以熱更新容器,Networking網路內部容器可以自主發現),不太推薦docker內部網路(IP硬編碼等導致該方法不夠靈活)。

  • volume(卷)具有一些有用的特性:容器之間共享資料,對卷的修改會直接反映在包含改卷的容器裡所以可以在不修改容器的情況下向容器里加入、更新、刪除資料,更新映象時不會影響卷。利用這些特性可以更好的進行資料共享與持久化。

  • docker編排與叢集化之路:Docker Compose 是用來做 docker 的多容器控制,使用Compose ,你可以在一個檔案中定義多個容器應用,然後使用一條命令來啟動你的所有應用,避免繁複操作,docker 自動化構建容器棧;Consul提供了一個易於使用,基於開放標準的服務發現解決方案,服務發現允許某個元件在想與其他元件通訊時自動找到對方;Docker Swarm是一個用於建立Docker主機(執行Docker守護程式的伺服器)叢集的工具,使用Swarm操作叢集,會使使用者感覺就像是在一臺主機上進行操作亦即將容器抽象到叢集級別。還有其他很多工具如:fleet、etcd、Kubernetes、Apache Mesos、Helios、Centurion。

時效性原因,書中一些例子已經過時(如Docker1.12開始內建編排機制,Docker1.13正式支援docker stack),需要結合最新版本來使用。
另外,想要深入理解docker可以閱讀這本書 Docker——容器與容器雲。我大致瀏覽了下,這本書不僅講了docker如何使用,還深入講解了docker的核心原理如namespace資源隔離、cgroups資源限制、libcontainer原理和一些高階實踐技巧。此外,還講了對容器、容器雲的思考,包括如何構建自己的容器雲,以及Kubernetes實現一切皆容器的“大同理想”。

UNIX/Linux 系統管理技術手冊

UNIX/Linux 系統管理技術手冊 (豆瓣) https://book.douban.com/subje…

又是一本進千頁的大部頭,但是不怕,這本書如其名,是一本手冊性質的書,非常大而全,包括基本管理技術、網路管理技術和其他補充管理技術,幾乎包攬了所有我們可能用到的功能(小到一行程式碼整麼寫,大到資料中心怎麼建),對於巨集觀把握整個Linux生態系統有很大作用。我的應對策略是跳躍式閱讀(不錯,就像上面那本MySQL),留下整體映像,等遇上問題時再來具體的查詢相關部分的內容。也就是說,大腦相當於記憶體,書本相當於硬碟資料庫,我們首次閱讀就是在記憶體中建立索引,便於提升以後的查詢速度:-D。

本書亮點:

  • Linux各個發行版其實並沒有那麼巨大的差別,我們選擇一個發行版時主要考慮幾點:是否能長期存在,是否會有持續的安全補丁,是否會持續更新軟體,發行商是否會在出了問題時幫我們解決,不同發行版側重點會有所不同我們要根據自己的業務來進行選擇。

  • 編寫指令碼時注意形成一種指導風格,這樣你和你的團隊成員可以按照相同的規範來書寫程式碼,有了這種指導在閱讀別人寫的程式碼或者別人閱讀你的程式碼時都會更容易;註釋不要多也不要少,最好的效果是一兩個月後再來讀程式碼發現註釋和有用。

  • 一個程式由一個地址空間(一組記憶體頁面)和核心一部分資料(有關程式的資訊如地址空間對映、狀態、優先順序、資源)組成;一個執行緒是在程式內執行fork的結果,繼承了包含它的程式的許多屬性,多個執行緒可以共享該程式內資料,並行(多核)或併發(單核,模擬並行)執行

  • Unix家族的檔案系統目前沒有一個標準,我們儘量按照如下標準來組織。bin:核心作業系統命令;sbin:系統最小規模執行所需命令;boot:核心及載入核心所需軟體;etc:關鍵啟動檔案及配置檔案;usr:次要的命令檔案;var:隨主機變化的檔案如日誌,資料檔案;mnt:可移動介質臨時掛載點;opt:可選的應用軟體包;proc:正在執行的程式資訊;tmp:臨時檔案;

  • 合適的備份計劃取決於:檔案系統的活躍性,轉儲裝置的容量,使用者期望的冗餘度,想要購買的備份介質數量。

  • 版本控制與多人合作:svn是集中式的,一臺中央伺服器充當了一個專案的權威庫;git是分散式的,沒有中央庫,每個使用者都含有一個完整的專案,採用的是拷貝-分支策略。

  • 資訊保安領域的基本思想——CIA原則:Confidentiality(機密性),Integrity(完整性),Availability(可用性)。在設計、實現或者維護系統的時候,需要考慮CIA安全三原則,正如老話所說“安全性是一個過程”。

  • 負載均衡既提高了效能又增加了冗餘性,包括幾種方式:迴圈域名服務(也就是DNS輪詢)、負載均衡硬體(比如Big-IP Controller、Content Services Switches等)、軟體負載均衡(比如Nginx)。

  • Squid既是一個快取記憶體軟體也是一個代理服務程式,代理服務很有用,但是Squid真正厲害之處是其快取記憶體,它甚至能夠形成一個快取層次結構,以最大化提高快取命中率。Squid是有意義的,因為使用者對web的探索具有趨同性,所以在適度的規模上會出現相當多重複請求,執行快取記憶體可以節省頻寬和計算資源。

  • 虛擬化技術讓多個彼此獨立的作業系統同時執行在相同的物理硬體上,系統管理員把每個虛擬機器當做一臺獨立的伺服器,既滿足了軟體廠商的要求,又降低了單一服務的成本。包括全虛擬化(如VMwareESX)、半虛擬化(Xen)、作業系統級別虛擬化(如workload partition)。除了傳統的虛擬化技術,近年來的雲端計算也是一種(或者說類)虛擬化技術,它把計算能力作為對外提供的服務類似於水電等基礎設施,直接使得硬體層對開發人員和系統管理員透明,提高了效率。

  • 分析效能問題步驟:明確表述問題、收集證據並分類、批判性的評價資料、用語言和圖示總結證據、形成一份總結說明。這一套其實不止適用於效能分析問題,也適用於大多數其他問題,比如架構、重構、debug等等。

  • 系統管理不是一種行為藝術。無論做的什麼,都應該能重複完成,切前後一致。通常意味著最底層的變化應該有指令碼或者配置程式來做,而不是管理員來做。配置上的變化應該體現在系統管理軟體的配置檔案裡。說白了,就是文件的重要性,在我看來:首先文件就是一家公司的財富,沒有文件,人走了,那麼之前公司的積累也就沒了,又得重頭來,損失很大;其次,文件是一種保證,大家都按約定辦事,保證操作可重複,避免歧義與甩鍋;再其次,文件能節約時間,雖然寫的時候可能費點事,但是能節省後面大量的人員溝通的時間;最後,文件保證了系統的完整性,亦即文件保證了系統的後續修改遵循一致的思路和風格,才不至於系統隨著時間流逝而越來越亂,難以維護與使用。

本書教會了我們怎麼使用Linux,這對於我們後端開發人員是足夠受用了。而這本書Linux核心設計與實現(原書第3版)就會告訴我們Linux是如何實現它這麼多這麼強大的功能的內在原理,精力有限,我只是瀏覽了一下沒有細讀,等以後真的用得上Linux的深入知識時,或者遇上了什麼解決不了的問題,我會再來求助於這本書的。

程式碼整潔之道

程式碼整潔之道 (豆瓣) https://book.douban.com/subje…

本書提出一個觀點就是:程式碼質量與整潔度成正比,圍繞提高整潔度,作者展開了方方面面的闡述,從命名的方法到函式的定義,從註釋的使用到格式的目的,還介紹了物件,錯誤處理等等等等。看完後的感覺就是:寫好程式碼,從本書開始。雖然我現在寫的程式碼還比較“亂”,但是今後的程式碼中會努力踐行書中的原則,爭取寫出整潔的程式碼。

本書亮點:

  • 軟體質量既依賴於架構和專案管理,又與程式碼質量緊密相關,而程式碼質量與整潔度成正比,所以我們要致力於寫出乾淨整潔的程式碼。並且不僅要知道書寫整潔程式碼的原則,還有檢視大量程式碼例項,進行案例研究,在實踐中貫徹這些原則,才能真正做到知行合一

  • 不同的人對整潔程式碼有不同的理解,總歸起來,有如下一些特點:程式碼邏輯直截了當,依賴關係很少,效能最優,每個函式模組類專注於一件事,可以輕易的被其他人閱讀,具有完整的測試,作者自己要在乎自己的程式碼悉心維護,沒有重複程式碼提前構建重複程式碼應有的抽象,體現系統的所有設計理念,包含儘量少的實體。

  • 命名看似簡單卻無處不在,所以我們不妨命好名,幾個原則:名副其實,不用註釋也能明白這個變數代表什麼;避免誤導,不要用保留詞或太相同的詞引起歧義;做有意義的區分,不要新增在名字後數字或者廢話而是以讀者能鑑別不同之處的方式來區分;名字要讀的出來,便於交流;名稱要便於搜尋;一個概念統一用一個詞,比如不要混用manager和Controller;避免雙關詞;給名詞新增語境,但是避免冗餘的語境,含義明確的情況下,短名字總是比長名字好呀……

  • 函式構成了當今程式的基石,函式要寫的明白要遵循如下原則:短小;只做一件事,也就是函式中的語句要在同一個抽象層級上,不在同一個抽象層級就得拆出來形成一個新的函式;把switch埋藏在較低的抽象層級,比如抽象工廠中,雖然依然免不了判斷或者條件增多時要增添程式碼,但是能夠把變化截留在工廠內,減少影響範圍;不要向函式傳入標誌引數,比如TRUE/FALSE,而是應該直接把這個函式重構成兩個函式;一個函式要麼下達什麼指令(set)要麼回答什麼問題(get),不要新增副作用;DRY(別重複自己)。

  • 註釋是一種必須的惡,如果程式碼寫的好根本不需要註釋(感覺有點過了,至少一個大的模組用來幹啥還是因該用註釋或者說文件直接來說明),而且程式碼才是真實的地方,註釋很可能沒有被維護而導致失效;別給糟糕的程式碼加註釋,重寫吧;版本控制系統可以幫我們省掉許多註釋如作者署名,註釋掉的程式碼。

  • 或許你認為“讓程式碼工作”是開發者的頭等大事,但是其實並不是這樣的,程式碼風格關係著溝通,而溝通才是頭等大事。程式碼風格影響著可維護性和擴充套件性,即使程式碼已不復存在,其風格和律條依然存在,因而一個團隊應該形成一個統一的風格,便於溝通與維護。

  • 隱藏實現並非就是簡單的在變數之間放一個函式層。隱藏關乎抽象,類並不是簡單的用取值器和賦值器將變數向外推,而是暴露抽象介面,以便使用者無需瞭解資料的具體實現就能運算元據本身。物件暴露行為,隱藏資料;資料結構暴露資料,沒有明顯的行為。要合理的利用二者。

  • 錯誤處理一旦處理不好就容易汙染整潔的程式碼:使用異常來保證錯誤處理不會打亂正常的程式邏輯;可檢異常可能會破壞開閉原則,要謹慎使用;打包第三方API就降低了對它的依賴;不要返回null值,只是突然增加了工作量,可以用異常或者特例物件來代替。

  • 測試非常重要:測試程式碼和生產程式碼一樣重要,髒測試就等於沒測試;有了測試就可以毫無顧慮的進行重構和改善,因為有保證,消除了清理程式碼就會破壞程式碼的恐懼(這句話可謂是戳中了我,我之前的專案也有過一些重構的努力,但是就是怕重構過後不能正常執行,導致重構舉步維艱);測試程式碼要清晰,符合構造-操作-檢驗三個環節;單個測試中的斷言應該最小化,保證一個測試對應一個概念;整潔的測試遵循 FIRST 原則(fast,independent,repeatable,self-validing,timely)。

  • 系統層級的整潔:城市能有效運轉是因為演化出了恰當的抽象層級和模組,有人負責全域性有人負責細節,所以軟體系統也有架構師和專案經理等;將構造和使用分開(依賴注入,控制反轉);擴容(要考慮可擴充套件性,但是注重現有的系統的構造,將來再重構和新增,沒必要Big Design Up Front,考慮好模組化和關注切面劃分就好)。

  • KISS(Keep It Simple, Stupid),簡單設計原則:執行所有測試(測試是對一個系統的保證);不要重複(重複意味著額外的工作、風險、複雜度);表達了程式設計師的意圖(清晰可讀);儘可能少的類和方法數量(避免教條如:每個類都要有介面)。

  • 程式設計是一種技藝甚於科學的東西,不要指望一開始就寫出優雅整潔的程式碼,一般是先寫出骯髒的程式碼,然後進行重構,清理。所以程式碼僅僅能工作還不夠,滿足於程式碼能工作的程式設計師不夠專業,他們害怕沒時間進行程式碼結構的重新設計,其實沒有什麼比糟糕的程式碼給專案帶來更長遠的損害了,糟糕的程式碼會一直腐敗發酵,影響各個模組(找出這些依賴和影響相當不容易)無情的腐蝕整個專案和團隊。

  • ……

這本書裡面的良心建議實在是太多了,無法一一列舉,真的得自己看一遍才能有收穫。

重構-改善既有程式碼的設計

重構 (豆瓣) https://book.douban.com/subje…

上一本書教會我們怎樣書寫整潔的程式碼,那麼面對不整潔的程式碼,我們怎麼辦呢?這本書就手把手教我們怎麼重構,改善現有的程式碼。從一個例項入手,講了重構的理由、原則、技巧、步驟與時機。我讀完過後感覺心裡就有了些烙印,無論是寫程式碼還是改程式碼,基本都會不自主的向這本書靠攏,很有收穫。

本書亮點:

  • 重構是在不改變程式碼外在行為的前提下對程式碼作出修改,以改程式序的內部結構,本質上說重構就是在程式碼寫好後改進其設計,提高其可理解性,降低修改成本;記住所有程式碼的“壞味道”及其對應的重構手法,才能有信心面對各種情況——學會所有招式才可能“無招勝有招”;為了避免自掘墳墓,重構必須系統化進行,用一些經過檢驗的重構手法,就可以一次一小步的修改程式碼,所以任何錯誤都可以比較容易的發現,降低了重構過程中的風險。

  • 如果你發現自己需要為程式新增一個特性,而現有的程式碼結構使你無法方便的新增,那就先重構程式,使得特性新增變得容易,然後再新增這個特性。而重構的第一步就是建立一組可靠的測試,來儘量避免bug,保證重構的正確性。

  • 物件A最好不要在另外一個物件B的屬性基礎上使用switch語句,因為將來B變了A也必須變,如果不得不使用switch也要在自己的屬性上使用,也就是把switch移動至B裡面去;最好是用多型替換switch語句(其實也不是替換,而是把switch放在了較低的抽象層級,使得可能變化的部分就在一個物件裡面,將其與不變的部分隔離開)。

  • 新增新功能和重構是兩種行為,一定要加以區分,並且同一時刻只做好一件事就行。

  • 任何能夠查詢的東西,我都不太願意去記,因為怕把大腦擠爆了。所以說大腦這個珍貴而有限的記憶體主要做的事一定是建好索引,而非儲存資料;另一方面,常用的資料也應該存進大腦,以提高效率,這和記憶體中的快取是一個道理

  • 不要為了重構而重構,一定是因為你想做某件事時重構恰好可以幫你做好,重構的幾個時機:新增新特性時,修補錯誤時,複審程式碼時。

  • 程式有兩面價值“今天可以為你做什麼”和“明天可以為你做什麼”,如果只關注今天的工作,那麼明天我們將無法工作,所以需要重構,來避免程式碼出現以下四個情況:難以閱讀,重複太多,新行為無法簡單新增,邏輯複雜。這四個情況導致的結果都是程式難以改變(修改),而唯一不變的就是改變:-D

  • 程式碼的壞味道——重構的時機:重複程式碼、過長函式、過大的類、過長的引數列、發散式變化、霰彈式修改、依戀情結、資料泥團、基本型別偏執、switch語句、平行繼承體系、冗贅類、誇誇其談未來性、臨時欄位、過渡耦合的訊息鏈、中間人、不適合的親暱關係、異曲同工的類……

  • 本書給出了一份重構列表,包含了很多很多的重構型別的名稱、動機、方法、範例,涵蓋了函式、物件、資料、表示式、呼叫、概括關係、大型專案等方方面面,值得我們借鑑,下面摘抄幾條作為示例。

  • 當我看見一個過長的函式或者一段需要註釋才能讓人理解用途的程式碼就會把這段程式碼放進一個獨立的函式中。短函式的好處:更可能被複用,讓高層函式看起來更清晰易懂,複寫更容易。“短”的含義不在程式碼行數,而在於函式名稱和函式本體之間的語義距離。

  • 如果一個類的某個方法、欄位被另一個類頻繁使用,就應該搬移該方法、欄位。

  • 混亂的繼承體系是一個嚴重的問題,因為它會導致重複的程式碼,這正是程式設計師職業生涯的致命毒藥。它還會使修改變得困難,因為特定問題的解決策略被分散到了整個繼承體系,最終你的程式碼難以理解。所以可以通過建立多個繼承體系,並利用委託來互相呼叫,使得原來負責多個任務的繼承體系變成多個負責單個任務的繼承體系。

  • ……

感覺本書最大的問題就是太老了(1999年),有些工具或者方法在如今技術進步的情況下顯得有些多餘,比如現在的IDE如Eclipse或者IDEA都有很強大的功能,書裡提到的一些技巧完全用不著。不過經典終究是經典,裡面的絕大部分思想我們如今都還是可以借鑑的,尤其是對於重複程式碼的觀點——應該堅決消滅重複。

2017.9.8 後記

花費了幾個月來看書,又花了幾天來整理,希望對我們都有所幫助:-D。歡迎拍磚,我的主頁Mageek`s Wonderland
檢視原文

相關文章