從夏農熵談設計文件寫作
我在架構師的崗位上幹了十幾年,這裡有一個Everyday的工作是評審設計工程師的設計文件,雖然我一直呆在同一個單位,但也接觸了來自不同其他組織和國家的工程師寫的設計文件,在我的經驗中,文件寫得爛是個大概率事件,而且,感覺商業公司比開源社群的設計寫得更爛,國內背景的工程師比歐美背景的工程師寫得更爛。所以,我一直嘗試找方法說明一個設計文件應該怎麼寫,如果讀者看我這個專欄,就會發現我嘗試過用所謂的守弱,邏輯鏈,立體程式碼,設計和科普的區別等一系列概念來說明這個問題。但感覺離關鍵的問題還是有點遠。最近在一些設計討論中,我開始引入“夏農熵”的概念,我自己感覺這個效果要好一些,所以在這裡把這個邏輯表述一下。
首先理解一下夏農熵的概念,我自己是在學習神經網路的時候接觸這個概念的,在實踐上,它被用於梯度下降的時候比較兩次收斂的效果從而得到Loss值的。但夏農熵本身是一個更為普適的概念。
假設你要決定明天干什麼,只有出門,在家兩個選擇,我給你提供一個資訊:“選擇出門”,這裡就為你提供了一個“資訊”。假定我是上帝,我給你的資訊是對的,如果你選擇在家,你就會死於地震。這個資訊對你的價值,我們稱為a。
好了,同一個問題,假設你有3個選擇:出門,在家,坐在門口。我還是給你提供一樣的資訊:“選擇出門”。這個資訊也對你有價值,這個價值我們稱為b。
很明顯,b的價值更大,因為你沒有給我提供a,我選錯的可能性是50%,但你沒有給我提供b,我選錯的可能性就是2/3了。
如果我給你提供的選擇是:“選擇出門,在家,坐門口都挺有道理”。這個資訊,如果從“你應該做什麼選擇”這個主題的角度,資訊量為0。
夏農熵,本質是用數學方法來定義這個資訊量的方法,它是所關注的隨機變數的數學期望規整後的結果。我們這裡不用那個結果,我們這裡只取a,b,0這個概念。
也就是說,我們取其中的這個資訊:
如果我們就一個決策來討論問題,對決策產生影響的資訊,在這個決策上,具有夏農熵。夏農熵決定了這些資訊價值的大小。如果某個資訊對決策沒有影響,則其夏農熵為0。
我在知乎上經常用“神棍”這個詞,如果用夏農熵來理解,我們很容易定義什麼是神棍:提供大量夏農熵為0的資訊幫助你決策的人,就是神棍。現實中那些一般意義的裝神弄鬼的,買保健品的,算命的,傳教的,自稱國學的……大部分都是玩這個技巧的。而在企業中,被“趕鴨子上架”的工程師,大部分也是玩這個技巧的。所以我個人是非常反感“過程績效主義”的,因為這種手法導致我經常要看垃圾設計文件,這種文件洋洋灑灑幾十上百頁,等你看完了,你才注意到,他麼的一個有用的資訊都沒有。你的時間本身就不多,每天浪費在這種文件上,你不想殺他就有鬼了。以前看《明朝的那些事》,說朱元璋看大臣送上來的報告,寫了一大堆,一點實際的東西都沒有,他就要把那人拉上來打,我是很能理解那種心情的。
設計必須在討論的主題上給出選擇,它的夏農熵才不等於0,它才有實際的價值。說“正確的話”本身不是!
所以,不要來問我,“難道我說得不對嗎?”,我不關心你說得對不對,我關心你的夏農熵是否為0。我問你晚上到哪去吃飯,你給我回答“飯是一種碳水化合物”?然後問我“難道這句話有錯嗎?”,你說你是不是欠揍?
對於設計文件,我們一般關心的東西是“你如何選擇,從而達成需求上要求的目的”,在這個主題上,我們一般會需要如下有效的資訊:
- 分解多個功能和步驟,逐步實現所述的目的
- 這些工程和步驟之間的關係細節,特別是邏輯關係無法在下一層設計中體現,只能成為“原則”的那些關係細節。
理解第二點就能很好理解第一點。什麼是“邏輯關係無法在下一層設計中體現”?——我們用最簡單的“設計-編碼”這樣的分層關係來理解,比如你做一個RoCE的驅動,這個驅動必然和你的網絡卡驅動發生關係(如果你複用相關硬體的話),然後你要用起來,你還需要增加使用者態OFED驅動,然後你可能還需要調整OpenMPI的一些引數,才能保證你的業務流的效能……這些邏輯,在你具體寫某個核心模組或者使用者模組的時候,邏輯鏈已經斷了,你沒有了一個完整的資料流是怎麼工作的上下文了,這種東西,就必須設計。否則下一層就會丟掉這個資訊。
我們還有更多這樣的東西,比如:
模組之間傳遞資訊的充要性:比如你在OpenMPI層提供新的使用者介面,你要求哪些引數?這些引數是否足夠讓中間的模組拿到足夠的引數來完成需要的功能?
模組之間的依賴關係:比如你打開了一個RoCE的QP,你就必須鎖住網絡卡驅動(get_module),這個,在你寫RoCE驅動的時候你就不可能考慮,因為那個邏輯上下文中,在某個流程中突然調一下get_module()是莫名其妙的。這個get_module的必要性是在討論模組間的依賴關係的時候暴露出來的,不是在你“如何開啟一個檔案,填充其fd控制代碼”這個邏輯中暴露出來的。
鎖:你有多個模組,多個資源,不同的鎖,這些鎖經過多個模組,可以從不同的介面進來,是否會產生互鎖的情況?是否會導致主業務流的多個執行緒被同一個critical區限流的情況?
狀態機:軟體和硬體的內部狀態切換,是否會導致系統進入不可恢復的狀態?是否有可能讓系統的狀態不可判斷?提出功能的時候,系統是否有可能處於不正常的工作狀態?
……
這種,都是編碼階段無法或者難以判斷的決策,它們就是設計階段必須提供的資訊。缺了這部分資訊,編碼就很難可靠,等大量的細節邏輯補進去以後(比如cache效率不高,加了preload。插入一個初始化到init和start之間保證硬體和使用者程式的同步等),這些問題解決起來的成本就非常高。這種決策的資訊夏農熵,就很高了。
我們對一個設計(或者說設計文件)進行評判,分兩個部分,第一個部分是資訊是否具有夏農熵,這個判斷本身包括兩個方面:
第一:討論目的是否是我們的目的。所以,我們一般會在設計文件中複述我們要實現的功能。比如,我們會說:“我們的RoCE驅動必須支援Memory Window原語,包括……”
第二:這個資訊是否支援這個目的(是否具有夏農熵)。比如,我們需要說:“因為我們的RoCE驅動支援Memory Window原語,所以,驅動中必須具有alloc_wm……”
第二部分是這個資訊是否正確。所謂正確也包括兩個方面:
第一:這個資訊是否違背事實。比如,“我將通過gup(get_user_page)鎖住記憶體頁進行DMA操作”,這種情況,如果這個過程中發生fork,這個頁面將因為COW(Copy-on-Write)而丟失,導致這個DMA操作失敗。一般來說,我們同行評審,主要是發現這種型別的錯誤。
第二:這個資訊是否具有充要性。比如,“我在ioctl的時候將使用讀寫鎖保護對qp(RoCE的概念,Queue Pair,一個通訊通道)進行保護”,這個地方,是否只有ioctl這一個入口?如果不是,這個設計不具有充要性,這個資訊喪失其價值。
很多時候,有理智的人和別人討論問題,或者一個稱職的工程師和別人討論設計,是在預期進行第二部分的討論。但我們常常碰到神棍和為了證明“我還能幹這個工作”的工程師,然後我們永遠就停在第一個問題上了。
對於一般的神棍,我們沒有什麼辦法,心裡有氣,能揍他就揍他,不能揍他(通常都不能:)),就躲遠點吧。對於工程師,我做個簡單的思想工作:我們常常希望掩蓋自己的無知而把自己真正的有知覆蓋了,這是一個失敗的戰略,我希望你可以認清這一點。我們很多工程師太小看自己的能力了,在一些無謂的地方(通過熵為0的資訊)表演自己的高大上,本質是,是因為你認為你真有能力的地方“不值一提”。但你要注意到,能讓你去幹這個事情,必然是你有不可取代的能力,可能這個能力不是毀天滅地,不可取代,但如果你可以取代,當初為什麼要讓你去幹?
所以,“我這也不知道,我那也不知道”不要緊,要緊的是,你把你你不可取代的部分給他幹好了。我不知道gup在COW的時候會失效不重要,重要的是我現在把沒有COW的場景給他做了。我的邏輯還是我原來邏輯,在這個特定的場景下還是有用。我不懂檔案open的時候不需要get_module不重要,重要的是我在這個上面有邏輯,我就有可能在後面的推演中改變它。
當然,這仍是個稽式,也許你的領導,你的客戶就是這樣的傻逼,你需要用神棍學忽悠他。但當你忽悠別人的時候,不要忘了,你別連自己一起忽悠了,否則你就真的成了神棍,只能混神棍的日子,而不是工程師的日子了。