前言#
作者並不是軟體科班,所以一直以來沒有真正主動或被動學習過程式的設計,而是基於觀察和實踐的經驗主義。
而板上嵌入式事實上也不關注這些,更多的是評估 RAM/ROM 用量、運行速度、業務邏輯是否滿足需求,這也就導致一個嚴重的問題,在這樣的崗位上的產出大多特別地為某個特定的產品定制。
這裡有兩個討論,
- 在真正資源受限的 MCU 上真的無法使用可重用的代碼嗎?我想是否定的,這在後續再討論原因。
- 這樣定制的軟體代碼歸檔,極少註釋幾無說明,真的可以稱之為
軟體資產
嗎?我個人認為是否定的,在我的認知裡,無法讓初學者在 8 小時內無人指導即可使用的軟體模塊都是不合格的。
最近想要思考一下如何產出真正的軟體資產
,需要了解一下基礎的程式設計。所以花了十分鐘完全了解
下面是對這些設計的紙上談兵,笑笑就好,我也沒有真正對這些玩意進行過多少實踐。
面向對象編程#
面向對象是一個被說爛掉的詞,我其實完全搞不清楚那些專有名詞。
在我看來,面向對象的精髓是面向一件事物的定義,也就是定義一個事務包含有哪些功能、哪些變量、哪些定量。例如我們定義一個鍵盤,他有定量 104 個鍵、功能 “按下 A”“按下 B”。
但是不對啊,我明明在市面上看見了奇形怪狀的各種鍵盤,這涉及到所謂 "繼承",也就是奇形怪狀的鍵盤只是重寫了我們標準鍵盤類的一些東西。例如屏蔽了小鍵盤 "按下 1" 的功能、重寫定量說有 88 個鍵。
在面向對象的世界,一切都被定義為對象,然後我們再對對象進行定義,固化這個對象的映像,再進行繼承拓展,就可以實現一個模塊的落地。再對多個對象排列組合,實現項目的落地。
我認為在平衡移植難度、功能後,面向對象編程無疑是模塊化封裝的最好實現 (暫時是這樣想)。
但在項目的角度,面向對象可能是非常不友好的,如果在項目進行的過程中,某個對象事物的定義變掉了。那麼整個系統是否就產生了大變,導致排查臭味來源變得比較困難。如果在每個對象的使用上都額外編寫中間層進行隔離,那豈不完全多此一舉又產生了新的定義。總之定義的變化影響過於巨大,這實際上對維護者是不太友好的。
我們事實上是希望爽,瞻前顧後、水平不一、四處混沌,這樣是不爽的。
函數式編程#
主要參考1.7. 範疇論入門 (*) - 香蕉空間,該參考資料僅介紹數學知識,不涉及編程。
這其實不算難懂,一切皆函數。但是一切皆函數又能實現業務功能,這樣就比較神奇。
在函數式編程中有這樣兩條最重要的規則 (我說的)
- 任何函數都需要有輸入、有返回。
- 任何函數在任何情況下,相同的輸入會有相同的返回。
在我看來,函數式的精髓是獲得一個無副作用確定功能的函數。
下面的圖中,每個箭頭就是一個函數,每個點都既是輸入也是輸出。例如左起第一個箭頭,第一個點輸入這個函數,輸出為第二個點,而第二個點又可以通過兩個函數變成另外的兩個點。這裡的點可以是任何東西,當然在這個思考方式下任何東西都是函數,實數值是固定輸出的函數,取變量是單位態射1。
![圖片剽竊自函數式編程入門教程 - 阮一峰的網絡日誌 https://ruanyifeng.com/blog/2017/02/fp-tutorial.html)
例如 CRC16 算法的實現以及各種各樣古典加密算法的實現一定都是函數式編程,一組待校驗值 / 明文經過函數一定輸出一組校驗值 / 密文。包括其校驗過程和解析,也同樣是函數式的,所以在這兩個場景下輸入和輸出同構2。
我們可以發現,所有純粹的數學問題,使用函數式編程都是簡單的。不要杠數學可以描述世界啊 oi oi。
但是實際的工程中,所有的態不可能簡單被定義,涉及到大的輸入輸出,函數的編寫也是困難重重,同時函數的數量規模我想不會太小。如果分解問題,那函數的數量級是海量。
其實函數式編程是符合工業思維的,確定的動作帶來確定的反饋,在某些針對有限情況的業務邏輯上大可以使用函數式編程,而非管理相對困難的狀態機。
這裡回到前言中的 "討論 1:在真正資源受限的 MCU 上真的無法使用可重用的代碼嗎?",顯然函數式的編程在任何情況下消耗的資源都是類似的,我們可以縮小範疇 3直接刪掉不需要的函數以實現重用。
我的觀點是函數式編寫部分數學相關函數是一個好的封裝,但將這種思維帶到工程和業務上大可不必,工作量純純指數級增加。
或許指令生成也是一個好的函數式方向,有機會改造一下。
過程式編程#
上述兩種編程方法都是在減少函數 / 方法的副作用,使其在不同系統中幾乎可以獲得相同的功能。過程式就不一樣了,過程式的精髓是使用副作用獲得想要的功能,而非使用傳入值 / 傳出值在各種函數 / 方法中遊走來獲得。
過程式是最符合人考慮解決方案的編程方式,不贅述了,大家都懂。
總結#
總而言之,對於軟體資產
的分層,最底層的是函數式編程形成組件模塊,而功能模塊採用面向對象編程並調用函數式組件,這樣是我思考的結果。
我們考慮最經典的案例,” 將大象放入冰箱 “。
面向對象的編程,需要實現大象對象的定義,冰箱的定義 (包含存儲空間、放入方法、取出方法),實例化兩個對象,隨後使用冰箱的放入方法。
函數式編程,預先定義三個物件 —— 空物件、冰箱和大象、放著大象的冰箱,函數 1 實現傳入” 空物件 “傳出” 冰箱和大象 “,函數 2 實現傳入” 冰箱和大象 “傳出” 放著大象的冰箱 “。
過程式編程,抓大象函數 (副作用是產生大象),買冰箱函數 (副作用是產生冰箱),塞大象到冰箱函數 (副作用改變冰箱為塞著大象的冰箱)。
此文由 Mix Space 同步更新至 xLog 原始鏈接為 https://www.yono233.cn/posts/novel/25_4_25_coder