作者 | Terry Crowley
譯者 | Han Pei-Ru (explosic4) ? ? 共計翻譯:2 篇 貢獻時間:95 天
2016 年 10 月,當我從微軟離職時,我已經在微軟工作了近 21 年,在業界也快 35 年了。我花了一些時間反思我這些年來學到的東西,這些文字是那篇帖子稍加修改後得到。請見諒,文章有一點長。
要成為一名專業的程式員,你需要知道的事情多得令人吃驚:語言的細節、API、演演算法、資料結構、系統和工具。這些東西一直在隨著時間變化——新的語言和程式設計環境不斷出現,似乎總有一些“每個人”都在使用的熱門的新工具或新語言。緊跟潮流,保持專業,這很重要。木匠需要知道如何為工作選擇合適的鎚子和釘子,並且要有能力筆直精準地釘入釘子。
與此同時,我也發現有一些理論和方法有著廣泛的應用場景,它們能使用幾十年。底層裝置的效能和容量在這幾十年來增長了幾個數量級,但系統設計的思考方式還是互相有關聯的,這些思考方式比具體的實現更根本。理解這些重覆出現的主題對分析與設計我們所負責的系統大有幫助。
謙卑和自我
這不僅僅侷限於程式設計,但在程式設計這個持續發展的領域,一個人需要在謙卑和自我中保持平衡。總有新的東西需要學習,並且總有人能幫助你學習——如果你願意學習的話。一個人即需要保持謙卑,認識到自己不懂並承認它,也要保持自我,相信自己能掌握一個新的領域,並且能運用你已經掌握的知識。我見過的最大的挑戰就是一些人在某個領域深入專研了很長時間,“忘記”了自己擅長學習新的東西。最好的學習來自放手去做,建造一些東西,即便只是一個原型或者 hack。我知道的最好的程式員對技術有廣泛的認識,但同時他們對某個技術深入研究,成為了專家。而深入的學習來自努力解決真正困難的問題。
端到端觀點
1981 年,Jerry Saltzer、 Dave Reed 和 Dave Clark 在做因特網和分散式系統的早期工作,他們提出了端到端觀點,並作出了經典的闡述[1]。網路上的文章有許多誤傳,所以更應該閱讀論文字身。論文的作者很謙虛,沒有聲稱這是他們自己的創造——從他們的角度看,這隻是一個常見的工程策略,不只在通訊領域中,在其他領域中也有運用。他們只是將其寫下來並收集了一些例子。下麵是文章的一個小片段:
當我們設計系統的一個功能時,僅依靠端點的知識和端點的參與,就能正確地完整地實現這個功能。在一些情況下,系統的內部模組區域性實現這個功能,可能會對效能有重要的提升。
該論文稱這是一個“觀點”,雖然在維基百科和其他地方它已經被上升成“原則”。實際上,還是把它看作一個觀點比較好,正如作者們所說,系統設計者面臨的最難的問題之一就是如何在系統元件之間劃分責任,這會引發不斷的討論:怎樣在劃分功能時權衡利弊,怎樣隔離複雜性,怎樣設計一個靈活的高效能系統來滿足不斷變化的需求。沒有簡單的原則可以直接遵循。
網際網路上的大部分討論集中在通訊系統上,但端到端觀點的適用範圍其實更廣泛。分散式系統中的“最終一致性”就是一個例子。一個滿足“最終一致性”的系統,可以讓系統中的元素暫時進入不一致的狀態,從而簡化系統,最佳化效能,因為有一個更大的端到端過程來解決不一致的狀態。我喜歡橫向拓展的訂購系統的例子(例如亞馬遜),它不要求每個請求都透過中央庫存的控制點。缺少中央控制點可能允許兩個終端出售相同的最後一本書,所以系統需要用某種方法來解決這個問題,如通知客戶該書會延期交貨。不論怎樣設計,想購買的最後一本書在訂單完成前都有可能被倉庫中的叉車運出厙(LCTT 譯註:比如被其他人下單購買)。一旦你意識到你需要一個端到端的解決方案,並實現了這個方案,那系統內部的設計就可以被最佳化以利用這個解決方案。
事實上,這種設計上的靈活性可以最佳化系統的效能,或者提供其他的系統功能,從而使得端到端的方法變得如此強大。端到端的思考往往允許內部進行靈活的操作,使整個系統更加健壯,並且能適應每個元件特性的變化。這些都讓端到端的方法變得健壯,並能適應變化。
端到端方法意味著,新增會犧牲整體效能靈活性的抽象層和功能時要非常小心(也可能是其他的靈活性,但效能,特別是延遲,往往是特殊的)。如果你展示出底層的原始效能(LCTT 譯註:performance,也可能指操作,下同),端到端的方法可以根據這個效能(操作)來最佳化,實現特定的需求。如果你破壞了底層效能(操作),即使你實現了重要的有附加價值的功能,你也犧牲了設計靈活性。
如果系統足夠龐大而且足夠複雜,需要把整個開發團隊分配給系統內部的元件,那麼端到端觀點可以和團隊組織相結合。這些團隊自然要擴充套件這些元件的功能,他們通常從犧牲設計上的靈活性開始,嘗試在元件上實現端到端的功能。
應用端到端方法面臨的挑戰之一是確定端點在哪裡。 俗話說,“大跳蚤上有小跳蚤,小跳蚤上有更少的跳蚤……等等”。
關註複雜性
程式設計是一門精確的藝術,每一行程式碼都要確保程式的正確執行。但這是帶有誤導的。程式設計的複雜性不在於各個部分的整合,也不在於各個部分之間如何相互互動。最健壯的程式會將複雜性隔離開,讓最重要的部分變的簡單直接,透過簡單的方式與其他部分互動。雖然隱藏複雜性和資訊隱藏、資料抽象等其他設計方法一樣,但我仍然覺得,如果你真的要定位出系統的複雜所在,並將其隔離開,那你需要對設計特別敏銳。
在我的文章[2]中反覆提到的例子是早期的終端編輯器 VI 和 Emacs 中使用的螢幕重繪演演算法。早期的影片終端實現了控制序列,來控制繪製字元核心操作,也實現了附加的顯示功能,來最佳化重新繪製螢幕,如向上向下滾動當前行,或者插入新行,或在當前行中移動字元。這些命令都具有不同的開銷,並且這些開銷在不同製造商的裝置中也是不同的。(參見TERMCAP[3] 以獲取程式碼和更完整的歷史記錄的連結。)像文字編輯器這樣的全屏應用程式希望儘快更新螢幕,因此需要最佳化使用這些控制序列來從一個狀態到另一個狀態螢幕轉換。
這些程式在設計上隱藏了底層的複雜性。系統中修改文字緩衝區的部分(功能上大多數創新都在這裡)完全忽略了這些改變如何被轉換成螢幕更新命令。這是可以接受的,因為針對任何內容的改變計算最佳命令所消耗的效能代價,遠不及被終端本身實際執行這些更新命令的效能代價。在確定如何隱藏複雜性,以及隱藏哪些複雜性時,效能分析扮演著重要的角色,這一點在系統設計中非常常見。螢幕的更新與底層文字緩衝區的更改是非同步的,並且可以獨立於緩衝區的實際歷史變化順序。緩衝區是怎樣改變的並不重要,重要的是改變了什麼。非同步耦合,在元件互動時消除元件對歷史路徑依賴的組合,以及用自然的互動方式以有效地將元件組合在一起是隱藏耦合複雜度的常見特徵。
隱藏複雜性的成功不是由隱藏複雜性的元件決定的,而是由使用該模組的使用者決定的。這就是為什麼元件的提供者至少要為元件的某些端到端過程負責。他們需要清晰的知道系統的其他部分如何與元件相互作用,複雜性是如何洩漏出來的(以及是否洩漏出來)。這常常表現為“這個元件很難使用”這樣的反饋——這通常意味著它不能有效地隱藏內部複雜性,或者沒有選擇一個隱藏複雜性的功能邊界。
分層與元件化
系統設計人員的一個基本工作是確定如何將系統分解成元件和層;決定自己要開發什麼,以及從別的地方獲取什麼。開源專案在決定自己開發元件還是購買服務時,大多會選擇自己開發,但元件之間互動的過程是一樣的。在大規模工程中,理解這些決策將如何隨著時間的推移而發揮作用是非常重要的。從根本上說,變化是程式員所做的一切的基礎,所以這些設計決定不僅要在當下評估,還要隨著產品的不斷發展而在未來幾年得到評估。
以下是關於系統分解的一些事情,它們最終會佔用大量的時間,因此往往需要更長的時間來學習和欣賞。
愛因斯坦宇宙
幾十年來,我一直在設計非同步分散式系統,但是在微軟內部的一次演講中,SQL 架構師 Pat Helland 的一句話震驚了我。 “我們生活在愛因斯坦的宇宙中,沒有同時性這種東西。”在構建分散式系統時(基本上我們構建的都是分散式系統),你無法隱藏系統的分散式特性。這是物理的。我一直感到遠端過程呼叫在根本上錯誤的,這是一個原因,尤其是那些“透明的”遠端過程呼叫,它們就是想隱藏分散式的互動本質。你需要擁抱系統的分散式特性,因為這些意義幾乎總是需要透過系統設計和使用者體驗來完成。
擁抱分散式系統的本質則要遵循以下幾個方面:
我喜歡說你應該“陶醉在非同步”。與其試圖隱藏非同步,不如接受非同步,為非同步而設計。當你看到像冪等性或不變性這樣的技術時,你就認識到它們是擁抱宇宙本質的方法,而不僅僅是工具箱中的一個設計工具。
效能
我確信 Don Knuth 會對人們怎樣誤解他的名言“過早的最佳化是一切罪惡的根源”而感到震驚。事實上,效能,及效能的持續超過 60 年的指數增長(或超過 10 年,取決於您是否願意將電晶體,真空管和機電繼電器的發展算入其中),為所有行業內的驚人創新和影響經濟的“軟體吃掉全世界”的變化打下了基礎。
要認識到這種指數變化的一個關鍵是,雖然系統的所有元件正在經歷指數級變化,但這些指數是不同的。硬碟容量的增長速度與記憶體容量的增長速度不同,與 CPU 的增長速度不同,與記憶體 CPU 之間的延遲的效能改善速度也不用。即使效能發展的趨勢是由相同的基礎技術驅動的,增長的指數也會有分歧。延遲的改進從根本上改善了頻寬[7]。指數變化在近距離或者短期內看起來是線性的,但隨著時間的推移可能是壓倒性的。系統不同元件的效能的增長不同,會出現壓倒性的變化,並迫使對設計決策定期進行重新評估。
這樣做的結果是,幾年後,一度有意義的設計決策就不再有意義了。或者在某些情況下,二十年前有意義的方法又開始變成一個好的決策。現代記憶體對映的特點看起來更像是早期分時的行程切換,而不像分頁那樣。 (這樣做有時會讓我這樣的老人說“這就是我們在 1975 年時用的方法”——忽略了這種方法在 40 年都沒有意義,但現在又重新成為好的方法,因為兩個元件之間的關係——可能是快閃記憶體和 NAND 而不是磁碟和核心記憶體——已經變得像以前一樣了)。
當這些指數超越人自身的限制時,重要的轉變就發生了。你能從 2 的 16 次方個字元(一個人可以在幾個小時打這麼多字)過渡到 2 的 32 次方個字元(遠超出了一個人打字的範圍)。你可以捕捉比人眼能感知的解析度更高的數字影象。或者你可以將整個音樂專輯存在小巧的磁碟上,放在口袋裡。或者你可以將數字化影片錄製儲存在硬碟上。再透過實時流式傳輸的能力,可以在一個地方集中儲存一次,不需要在數千個本地硬碟上重覆記錄。
但有的東西仍然是根本的限制條件,那就是空間的三維和光速。我們又回到了愛因斯坦的宇宙。記憶體的分級結構將始終存在——它是物理定律的基礎。穩定的儲存和 IO、記憶體、計算和通訊也都將一直存在。這些模組的相對容量,延遲和頻寬將會改變,但是系統始終要考慮這些元素如何組合在一起,以及它們之間的平衡和折衷。Jim Gary 是這方面的大師。
空間和光速的根本限製造成的另一個後果是,效能分析主要是關於三件事:區域性化、區域性化、區域性化。無論是將資料打包在磁碟上,管理處理器快取的層次結構,還是將資料合併到通訊資料包中,資料如何打包在一起,如何在一段時間內從區域性獲取資料,資料如何在元件之間傳輸資料是效能的基礎。把重點放在減少管理資料的程式碼上,增加空間和時間上的區域性性,是消除噪聲的好辦法。
Jon Devaan 曾經說過:“設計資料,而不是設計程式碼”。這也通常意味著當檢視系統結構時,我不太關心程式碼如何互動——我想看看資料如何互動和流動。如果有人試圖透過描述程式碼結構來解釋一個系統,而不理解資料流的速率和數量,他們就不瞭解這個系統。
記憶體的層級結構也意味著快取將會一直存在——即使某些系統層正在試圖隱藏它。快取是根本的,但也是危險的。快取試圖利用程式碼的執行時行為,來改變系統中不同元件之間的互動樣式。它們需要對執行時行為進行建模,即使模型填充快取並使快取失效,並測試快取命中。如果模型由於行為改變而變差或變得不佳,快取將無法按預期執行。一個簡單的指導方針是,快取必須被檢測——由於應用程式行為的改變,事物不斷變化的性質和元件之間效能的平衡,快取的行為將隨著時間的推移而退化。每一個老程式員都有快取變糟的經歷。
我很幸運,我的早期職業生涯是在網際網路的發源地之一 BBN 度過的。 我們很自然地將將非同步元件之間的通訊視為系統連線的自然方式。流量控制和佇列理論是通訊系統的基礎,更是任何非同步系統執行的方式。流量控制本質上是資源管理(管理通道的容量),但資源管理是更根本的關註點。流量控制本質上也應該由端到端的應用負責,所以用端到端的方式思考非同步系統是自然的。緩衝區膨脹[8]的故事在這種情況下值得研究,因為它展示了當對端到端行為的動態性以及技術“改進”(路由器中更大的緩衝區)缺乏理解時,在整個網路基礎設施中導致的長久的問題。
我發現“光速”的概念在分析任何系統時都非常有用。光速分析並不是從當前的效能開始分析,而是問“這個設計理論上能達到的最佳效能是多少?”真正傳遞的資訊是什麼,以什麼樣的速度變化?元件之間的底層延遲和頻寬是多少?光速分析迫使設計師深入思考他們的方法能否達到效能標的,或者否需要重新考慮設計的基本方法。它也迫使人們更深入地瞭解效能在哪裡損耗,以及損耗是由固有的,還是由於一些不當行為產生的。從構建的角度來看,它迫使系統設計人員瞭解其構建的模組的真實效能特徵,而不是關註其他功能特性。
我的職業生涯大多花費在構建圖形應用程式上。使用者坐在系統的一端,定義關鍵的常量和約束。人類的視覺和神經系統沒有經歷過指數性的變化。它們固有地受到限制,這意味著系統設計者可以利用(必須利用)這些限制,例如,透過虛擬化(限制底層資料模型需要對映到檢視資料結構中的數量),或者透過將螢幕更新的速率限制到人類視覺系統的感知限制。
複雜性的本質
我的整個職業生涯都在與複雜性做鬥爭。為什麼系統和應用變得複雜呢?為什麼在一個應用領域內進行開發並沒有隨著時間變得簡單,而基礎設施卻沒有變得更複雜,反而變得更強大了?事實上,管理複雜性的一個關鍵方法就是“走開”然後重新開始。通常新的工具或語言迫使我們從頭開始,這意味著開發人員將工具的優點與從新開始的優點結合起來。從新開始是重要的。這並不是說新工具,新平臺,或新語言可能不好,但我保證它們不能解決複雜性增長的問題。控制複雜性的最簡單的方法就是用更少的程式員,建立一個更小的系統。
當然,很多情況下“走開”並不是一個選擇——Office 軟體建立在有巨大的價值的複雜的資源上。透過 OneNote, Office 從 Word 的複雜性上“走開”,從而在另一個維度上進行創新。Sway 是另一個例子, Office 決定從限制中跳出來,利用關鍵的環境變化,抓住機會從底層上採取全新的設計方案。我們有 Word、Excel、PowerPoint 這些應用,它們的資料結構非常有價值,我們並不能完全放棄這些資料結構,它們成為了開發中持續的顯著的限制條件。
我受到 Fred Brook 討論軟體開發中的意外和本質的文章《沒有銀彈》[9]的影響,他希望用兩個趨勢來盡可能地推動程式員的生產力:一是在選擇自己開發還是購買時,更多地關註購買——這預示了開源社群和雲架構的改變;二是從單純的構建方法轉型到更“有機”或者“生態”的增量開發方法。現代的讀者可以認為是向敏捷開發和持續開發的轉型。但那篇文章可是寫於 1986 年!
我很欣賞 Stuart Kauffman 的在複雜性的基本性上的研究工作。Kauffman 從一個簡單的布林網路模型(“NK 模型[10]”)開始建立起來,然後探索這個基本的數學結構在相互作用的分子,基因網路,生態系統,經濟系統,計算機系統(以有限的方式)等系統中的應用,來理解緊急有序行為的數學基礎及其與混沌行為的關係。在一個高度連線的系統中,你固有地有一個相互衝突的約束系統,使得它(在數學上)很難向前發展(這被看作是在崎嶇景觀上的最佳化問題)。控制這種複雜性的基本方法是將系統分成獨立元素並限制元素之間的相互連線(實質上減少 NK 模型中的“N”和“K”)。當然對那些使用複雜隱藏,資訊隱藏和資料抽象,並且使用鬆散非同步耦合來限制元件之間的互動的技術的系統設計者來說,這是很自然的。
我們一直面臨的一個挑戰是,我們想到的許多拓展系統的方法,都跨越了所有的方面。實時共同編輯是 Office 應用程式最近的一個非常具體的(也是最複雜的)例子。
我們的資料模型的複雜性往往等同於“能力”。設計使用者體驗的固有挑戰是我們需要將有限的一組手勢,對映到底層資料模型狀態空間的轉換。增加狀態空間的維度不可避免地在使用者手勢中產生模糊性。這是“純數學[11]”,這意味著確保系統保持“易於使用”的最基本的方式常常是約束底層的資料模型。
管理
我從高中開始擔任一些領導角色(學生會主席!),對承擔更多的責任感到理所當然。同時,我一直為自己在每個管理階段都堅持擔任全職程式員而感到自豪。但 Office 軟體的開發副總裁最終還是讓我從事管理,離開了日常的程式設計工作。當我在去年離開那份工作時,我很享受重返程式設計——這是一個出奇地充滿創造力的充實的活動(當修完“最後”的 bug 時,也許也會有一點令人沮喪)。
儘管在我加入微軟前已經做了十多年的“主管”,但是到了 1996 年我加入微軟才真正瞭解到管理。微軟強調“工程領導是技術領導”。這與我的觀點一致,幫助我接受並承擔更大的管理責任。
主管的工作是設計專案並透明地推進專案。透明並不簡單,它不是自動的,也不僅僅是有好的意願就行。透明需要被設計進系統中去。透明工作的最好方式是能夠記錄每個工程師每天活動的產出,以此來追蹤專案進度(完成任務,發現 bug 並修複,完成一個情景)。留意主觀上的紅/綠/黃,點贊或踩的儀錶板。
我過去說我的工作是設計反饋迴路。獨立工程師,經理,行政人員,每一個專案的參與者都能透過分析記錄的專案資料,推進專案,產出結果,瞭解自己在整個專案中扮演的角色。最終,透明化最終成為增強能力的一個很好的工具——管理者可以將更多的區域性控制權給予那些最接近問題的人,因為他們對所取得的進展有信心。這樣的話,合作自然就會出現。
關鍵需要確定標的框架(包括關鍵資源的約束,如釋出的時間表)。如果決策需要在管理鏈上下不斷流動,那說明管理層對標的和約束的框架不好。
當我在 Beyond Software 工作時,我真正理解了一個專案擁有一個唯一領導的重要性。原來的專案經理離職了(後來從 FrontPage 團隊僱傭了我)。我們四個主管在是否接任這個崗位上都有所猶豫,這不僅僅由於我們都不知道要在這家公司堅持多久。我們都技術高超,並且相處融洽,所以我們決定以同級的身份一起來領導這個專案。然而這槽糕透了。有一個顯而易見的問題,我們沒有相應的戰略用來在原有的組織之間分配資源——這應當是管理者的首要職責之一!當你知道你是唯一的負責人時,你會有很深的責任感,但在這個例子中,這種責任感缺失了。我們沒有真正的領導來負責統一標的和界定約束。
我有清晰地記得,我第一次充分認識到傾聽對一個領導者的重要性。那時我剛剛擔任了 Word、OneNote、Publisher 和 Text Services 團隊的開發經理。關於我們如何組織文字服務團隊,我們有一個很大的爭議,我走到了每個關鍵參與者身邊,聽他們想說的話,然後整合起來,寫下了我所聽到的一切。當我向其中一位主要參與者展示我寫下的東西時,他的反應是“哇,你真的聽了我想說的話”!作為一名管理人員,我所經歷的所有最大的問題(例如,跨平臺和轉型持續工程)涉及到仔細傾聽所有的參與者。傾聽是一個積極的過程,它包括:嘗試以別人的角度去理解,然後寫出我學到的東西,並對其進行測試,以驗證我的理解。當一個關鍵的艱難決定需要發生的時候,在最終決定前,每個人都知道他們的想法都已經被聽到並理解(不論他們是否同意最後的決定)。
在 FrontPage 團隊擔任開發經理的工作,讓我理解了在只有部分資訊的情況下做決定的“操作困境”。你等待的時間越長,你就會有更多的資訊做出決定。但是等待的時間越長,實際執行的靈活性就越低。在某個時候,你僅需要做出決定。
設計一個組織涉及類似的兩難情形。您希望增加資源領域,以便可以在更大的一組資源上應用一致的優先順序劃分框架。但資源領域越大,越難獲得作出決定所需要的所有資訊。組織設計就是要平衡這兩個因素。軟體複雜化,因為軟體的特點可以在任意維度切入設計。Office 軟體部門已經使用共享團隊[12]來解決這兩個問題(優先次序和資源),讓跨領域的團隊能與需要產品的團隊分享工作(增加資源)。
隨著管理階梯的提升,你會懂一個小秘密:你和你的新同事不會因為你現在承擔更多的責任,就突然變得更聰明。這強調了整個組織比頂層領導者更聰明。賦予每個級別在一致框架下擁有自己的決定是實現這一標的的關鍵方法。聽取並使自己對組織負責,闡明和解釋決策背後的原因是另一個關鍵策略。令人驚訝的是,害怕做出一個愚蠢的決定可能是一個有用的激勵因素,以確保你清楚地闡明你的推理,並確保你聽取所有的資訊。
結語
我離開大學尋找第一份工作時,面試官在最後一輪面試時問我對做“系統”和做“應用”哪一個更感興趣。我當時並沒有真正理解這個問題。在軟體技術棧的每一個層面都會有趣的難題,我很高興深入研究這些問題。保持學習。
via: https://hackernoon.com/education-of-a-programmer-aaecf2d35312
作者:Terry Crowley[14] 譯者:explosic4 校對:wxy
本文由 LCTT 原創編譯,Linux中國 榮譽推出