轉帖|行業資訊|編輯:龔雪|2014-10-20 09:29:54.000|閱讀 251 次
概述:提高代碼質量系列之:如何設計函數
# 界面/圖表報表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
先介紹下什么是"純函數" 純函數其實并沒有一個很統一的定義,像Haskell的定義,就太苛刻,幾乎是數學領域了,我比較認同下面這個定義:
我自己總結下,意思是一個設計良好的函數,應該就像一個黑盒子一樣,你完全不需要關注函數內部的實現,你只需要關注三點, 1.函數名 2.函數接受的參數類型 3.函數返回值的類型,只要我們確定了這三 點,我們即可完全"掌控"這個函數, 我們給定一個輸出,必然會返回預設的結果,這個結果不受其他任何因素的干擾. 當然,這其實是最理想的情況,"純函數"也并非就是非黑即白的定性修飾,它更多的是一個程度上的修飾,有些函數是無論如何也不可能寫成成純函數的,比如訪問非托管資源的函數. 但我們可以這樣說:FunA和FunB都不是純函數,但FunA比FunB更"純函數"(可以類比"聲明式"這個概念)。
更具體的介紹,可以看msdn里面的一個小專題 .
那么,我們為什么要寫純函數呢?因為省事省心, 直接來兩段代碼,
第一個函數要考慮的東西很多,比如session里面是否有值,-p2這個全局變量會不會受到其他地方的干擾,而這些其實不該是doSth應該關心的,它的職責范圍被擴大了.
這兩個函數,其他人或者過段時間我們自己調用的時候,誰更讓人放心?
所以我們要使函數顯得純.第一步就是盡可能避免全局變量,我們分析一個函數,就只分析這個函數的全部代碼(有效范圍)就好,如果引入了全局變量,我們分析的時候,關注范圍也難免會被強制擴大到全局,同理,能聲明為靜態函數的,就應該避免聲明為成員函數,因為成員函數可以訪問對象的實例,而該對象在調用成員函數的時候,是個什么狀態,有無初始化,函數是否會修改實例(引用類型)的參數,如果我們要對這個函數做重構,就難免會束手束腳.
寧愿多花一點功夫,將需要的變量在封裝的純函數中不斷傳遞,也不要輕易將它設置為全局變量,因為在函數中傳遞,按照你調用的順序,它的流程仍然是穩定的,而一旦使用全局變量,那么它就失去的約束,在哪里被人初始化了?怎么初始化的,順序是不是按我要求的,有沒有哪個地方在我做第二次初始化之前,就調用了第二次處理的功能邏輯?
再看一個例子:
以上四個函數的純函數程度,是依次遞增的,都是大家很常用的寫法,那么這四個函數的區別是什么呢?
是我們調用者對函數內部實現邏輯的關注程度,依次遞減,他們的功能也越來越純粹(意味著更容易提煉和復用),調用起來也更省心,
當然,也難免會更瑣碎,比如GetType3,還需要做一些具體的取值,傳值,賦值操作.
其實他們也沒有什么優劣之分,這之間的度,自己把握就好.
變量盡可能少: 函數內部的變量,有效范圍是整個函數,如果我們在函數前面聲明了10個變量,那么我們都必須時刻關注這些變量的使用情況,有些變量其實就在前面用了一次,但后來閱讀的時候,你也不記得后面是不是還用到了它,所以減少變量數量,就意味著減少代碼復雜度.舉例:
聲明盡可能晚:可能我們寫類的時候養成了習慣,將變量放在最上面,統一聲明,易于整理和查閱. 其實類的聲明和函數的聲明是不一樣的,類的所有成員(變量和函數)都是無所謂先后的,而函數里面的局部變量,則是有先后順序的,我們在不必要的地方引入了不必要的約束,也就意味著不必要的麻煩.
比如我們有一個200行代碼的函數,我們在最前面聲明了10個變量,這些變量是依次在函數不同部位使用的,但因為在最前面已經聲明了,所以我們閱讀這個函數的時候,也需要時刻注意這10個變量在函數中的使用情況, 這里我們簡單的引入一個"關注度"的概念: G = 變量個數*變量的有效代碼范圍 ,那么這時候的總G數 = 10*200 = 2000.
而如果開始只聲明2個變量,剩下的變量在使用的時候才聲明,比如p3,p4是在101行代碼里面聲明的,那么你閱讀1-100行代碼的時候,就不需要關注p3,p4了(也沒法關注,都還沒聲明呢),然后剩下6個變量在151行聲明,那么現在的關注度,就只有G=2*200 +2*100 +6*50 = 900.
禁止一值多用:前面不是說要盡可能少的聲明變量么,有些人就這樣做:比如我聲明一個state,表示Appointment的狀態,用完之后,后面需要用訂單狀態的時候,我仍然用state字段去接值,參與新的,屬于Order的業務邏輯,這個我還真見過.不過相信這種大神應該還是極少數吧.
幾乎所有提到程序設計的書籍,都是推薦將函數中比較獨立的業務抽取出來,放在一個新的函數中,好處很多:結構清晰,代碼復用,業務解耦合.
但有時候我們的情況很尷尬,說功能獨立吧,也不是特別獨立,說要提公吧,其實在其他地方用的可能性也不大,但要就這樣和主體業務放在一起,代碼也確實顯得比較亂,提公之后,又將業務邏輯分散了,這種情況應該怎么辦呢?
其實我們可以選一個折中的方案:委托.
比如一個流程,需要在保存之前篩選初始數據,這個篩選的方法很大可能只在這里用(但也不排除以后再其他地方也會用,雖然可能性不大),和主體業務耦合也比較強,其實我們可以在函數中聲明一個
Func<IList<Product>, AttrItemDTO, bool> FilterProduct1= (lambda Express) 或Func<IList<Product>, AttrItemDTO,int, bool> FilterProduct2= (lambda Express)
我們可以通過傳遞參數的形式,寫成純函數形式的FilterProduct2(第三個參數就是state),也可以寫成FilterProduct1,在lambda里面直接使用前面函數中聲明的"全局變量"state,
這兩者都是將篩選這一流程進行了一次折中的"重構",而且花銷很小, 首先它的業務邏輯還是線性順序進行的,一條線下來,再次即使以后需要重構或者提公,也非常容易.
Ps:其實委托和lambda等函數式思維的引入,真的可以給我們帶來很多新的思維啟發, 不過可能是我們以前都太習慣于過程式的編碼, 還需要鍛煉鍛煉這種新的開發理念吧.
Ps2: 關于這種函數式寫法的一個非常炫酷的示例,可以參考下csdn
原文://www.cnblogs.com/suijing/p/how_to_design_method.html
本站文章除注明轉載外,均為本站原創或翻譯。歡迎任何形式的轉載,但請務必注明出處、不得修改原文相關鏈接,如果存在內容上的異議請郵件反饋至chenjj@fc6vip.cn
文章轉載自:慧都控件網