可控函數(Controllable Function),是指那種能夠經過改變函數的參數或者簡單的修改函數在執行過程當中的外部依賴的而產生肯定結果的一類函數,這是我根據純函數的概念推廣出來的一個新的名詞(大概是新的吧?)。編程
純函數在函數式編程的世界中很是的廣泛,並且最近它也逐漸在向面向對象的世界中展示出愈來愈多的魅力,你甚至能夠在一些框架或者技術的文檔中看到文檔做者對純函數的推崇。在我看來,純函數可以吸引到更多開發者的關注的一個重要的緣由就是它更容易測試,由於咱們能夠很是確定的肯定在給定輸入的狀況下純函數的輸出。可是在面向對象的世界中,由於有類的存在,因此咱們常常編寫的函數(或者說是方法)很難設計成爲純函數 —— 畢竟咱們老是會在類的方法中訪問類的其餘成員。框架
而可控函數則在對外界環境的訪問以及修改的限制較爲寬鬆。不知你是否還記得人教版高中生物書中提到的「控制變量法」,在設計實驗的時候,咱們須要儘量地作到僅改變單個對試驗結果產生影響的變量。編寫單元測試也是這樣,若是咱們可以很是簡單的控制某個函數的所有依賴(包括函數參數以及外部依賴,例如,類的其餘成員)的變化,那麼咱們就能夠很容易地使用控制變量法設計咱們的測試用例,並簡化劃分等價類的過程,這對於編寫白盒測試來講,好處不言而喻。編程語言
若是函數依賴「系統當前時間」這樣的沒法使用代碼來控制的外部變量的話,那麼這個函數經常會變得不可控。由於在大多數的編程語言或者技術框架中,修改當前系統時間或者 Mock 獲取系統時間的全局變量每每是很困難的。像 JS 能夠很是容易的 Mock window.Date
類,可是在 C# 中,Mock DateTime
會很是的艱難。ide
另外一種讓函數成爲脫繮野馬的狀況極可能是它依賴了過於複雜的外部對象,例如 EF Core 的 DbContext
。DbContext
很難被完美的 Mock,不論是 InMemory
仍是 SQLite InMemory
,都有其限制所在。就更不要說查詢姿式(是否 AsNoTracking
,是否使用了第三方 EF 拓展)會對真正的執行過程產生的影響了。原本沒有那麼複雜邏輯,卻由於引入的複雜的外部對象而變得難以預測了。函數式編程
在這裏我想先介紹一款編程語言 —— Elm
,在 Elm 的基礎類庫中,它很反常的沒有提供直接獲取系統時間的函數,相反地,Elm 的設計者認爲獲取系統時間並非一個純函數,由於咱們沒辦法在不一樣時刻讓這個函數返回相同的值。爲了描述這一現象,Elm 將這個函數實現爲了一種反作用,簡單來講就是開發者須要是使用相似於 Callback 的機制來獲取系統當前時間,這樣就強迫咱們解除了對系統當前時間的強依賴,由於咱們只能經過 Callback 的參數來獲取真正的系統時間。函數
模仿 Elm 的理念,在使用咱們沒法使用代碼來控制的外部變量的時候,咱們可使用「包裝一層」的方法來解決強依賴的問題。例如,在面向對象的世界中,能夠經過使用一個 ISystemTimeProvider
接口來獲取當前的系統時間,它就像 Elm 中的 Callback 同樣,躲在一個抽象層後面,默默無聞地爲咱們的代碼提供當前系統的準確時間。單元測試
除了上面提到的咱們沒法控制的外部依賴以外,還有一些外部依賴是咱們難以控制的,它們每每來自於咱們所使用的第三方類庫,而且它們的組成也一般是比較複雜的,例如 AspNetCore 中的 HttpContext
以及 DbContext
。能夠想象,若是一個函數接收這種類型的參數,那麼如何去建立一個指望的輸入就變成了一件困難的事了。這種時候咱們能夠試着 Keep it simple and stupid。例如,當咱們只須要使用 DbContext
的查詢結果的時候,直接讓查詢函數返回來自 BCL 的類型而不是 IQueryable<T>
,這樣,依賴查詢結果的函數就能夠擺脫對 DbContext
的依賴了。測試