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