【本人禿頂程序員】在Java中使用函數範式提升代碼質量

←←←←←←←←←←←← 快!點關注java

在一個範式和技術堆棧一直在變化的世界中,保持競爭力和提升生產力和質量的鬥爭有時候證實是一項挑戰。程序員

在本文中,我想首先展現一下函數編程(FP)的優點,特別是增強Java編碼體驗。在嘗試將範式轉換爲函數式編程時,我將嘗試迭代我發現最重要的幾個緣由。請記住,這毫不是一個巨大的創新,我相信FP自70年代以來一直存在,但僅在最近幾年它纔得到吸引力並增長了人們的興趣。咱們來看看爲何!數據庫

併發

隨着多核/多線程處理器的出現,函數式編程開始受到更多關注。這毫不是一個簡單的巧合,由於函數式編程鼓勵使用不可變對象,屬性和變量應該是一種其值不能更改的數據容器)。看看下面代碼:編程

private int aNumber;  
public void setNumber(int numberParameter){   
  this.aNumber = numberParameter;   
}

很簡單吧?你之前可能已經看過不少次了。可是若是兩個線程同時訪問setNumber方法會發生什麼?你能夠想象某種阻塞可能會發生,最後,只有訪問該方法的最後一個線程纔會對aNumber的值有最終決定權。但這不肯定,取決於各類因素,所以,咱們能夠說方法setNumber不是引用透明的(後面會詳細介紹)。這種狀況下不變性有助於推理代碼,由於咱們確信不管有多少線程訪問它的一部分,它的值老是相同的。數組

引用透明和可測試性

函數式編程鼓勵使用引用透明的函數。那是什麼意思?嗯,這意味着一個函數老是能夠被它的值替換,一切都將保持不變。看一下如下代碼塊:多線程

import java.util.Random;  
public class RandomValueProvider {   
  public int getSomeRandomValue(){   
    Random rand = new Random();  
    return rand.nextInt(50);  
  }   
}

getSomeRandomValue()方法引用透明嗎?試着用它的值替換它,它老是保持不變嗎?可能不會。儘量嘗試使用引用透明的函數多是一個好習慣。想象一下,測試上面的getSomeRandomValue方法比測試如下方法要困可貴多:併發

public int getSum(int a,int b){ 
  return a + b; 
}
具備暗示名稱的小函數一般比式樣表示它們返回值的表達式更好。好處是能確保咱們編寫的(至少大部分)函數是肯定性的。這將增長代碼推理的簡易性以及可測試性。dom

函數組合

在應用FP原則時,操做如今更簡單,更具肯定性,這一事實使咱們可以經過將不一樣的功能組合在一塊兒來建立更復雜的行爲。將其餘函數做爲參數或返回函數一塊兒接收的函數稱爲高階函數。ide

這裏的一些示例來自Java 8 Stream API。自2014年成爲JDK的一部分(甚至在此以前)以來,已經在流上編寫了大量內容。我只是想在這裏使用Consumer函數接口展現一個簡單的例子:函數式編程

public void processListOfNumbers(List<Integer> listOfNumbers, Consumer<Integer> processor) {  
  return listOfNumbers.stream()  
    .forEach(number -> processor.accept(number));  
}

客戶端代碼:

List<Integer> numbers = Arrays.asList(5, 6, 7, 8);  
Consumer<Integer> numberPrinter = n -> System.out.println(n);  
processListOfNumbers(numbers, numberPrinter);

方法processListOfNumbers是函數組合的一個示例,您可能會聽到它有時被命名爲高階函數。在Java中,函數(也包括suppliers, consumers)是對象。這意味着咱們能夠應用它們,組合它們並將它們做爲參數傳遞。

以FP風格編寫的應用程序更增強大

在以函數式編寫代碼時,應用程序自己的更不容易出錯。這是由於當您的移動一些組件時,應用程序每每變得更容易預測,更容易推理而且更能適應逆境。函數組合和不變性的通常用法將確保全部那些由於應用程序不一樣部分的狀態變化而致使的錯誤如今默認消失了。該應用程序將更增強大,能夠提供更短的開發 - >測試 - >調試迭代循環。

專一於「什麼」而不是「如何」

假設咱們有一個getUserById方法(在同一個類中)負責從數據庫中獲取相應的User對象,請使用如下Java流的經典應用程序:

public List<User> getAdultUsers(List<Integer> listOfUserIds) {   
  return listOfUserIds.stream().map(this::getUserById)  
    .filter(user -> user.getAge() >= 18)  
    .collect(Collectors.toList());  
}

如今讓咱們看看非函數風格的相同代碼:

public List<User> getAdultUsers(List<Integer> listOfUserIds) {  
  List<User> adultUsers = new ArrayList<>();  
  for(int id: listOfUserIds) {  
    User user = getUserById(id);  
    if (user.getAge() >= 18) {  
      adultUsers.add(user);  
    }  
  }  
  return adultUsers;  
}

除了第二段略長外,咱們還能夠注意到這段代碼須要花時間來「解釋」此操做的每一個步驟是如何完成的:建立一個空白列表,迭代id,獲取每一個用戶,添加一些基於條件表達式的用戶到空白列表,完成並返回收集的用戶。

另外一方面,在第一段中,功能方法更側重於「什麼」。代碼在作什麼?它將一些ID映射到某些用戶,將其過濾掉並將其他用戶收集到列表中。有人可能會爭辯說,經過在第二種狀況下提取小方法能夠實現一樣的目的,但我相信第一段的流和函數做爲數據方法仍然更好。它將咱們的函數置於業務邏輯的最前沿,具備與在咱們的應用程序中移動的任何其餘數據相同的狀態。

更好看的方法簽名

當咱們的功能從命令式轉變爲函數式時,命名也一目瞭然,如下方法很難經過其簽名來閱讀:

public void executeProcess() {  
  // executing some mysterious stuff!  
}

代碼作了什麼?爲何它不想要咱們的任何輸入參數,爲何它不想返回任何結果?你能測試一下嗎?你能讀懂嗎?不容易吧。若是像下面這樣看起來如何?

public ExecutionStatus executeProcess(Process processToBeExecuted) {  
  // execute "processToBeExecuted" and return some status  
}

只需採用一些FP概念,並在這個簡單的狀況下使用它們,代碼就變得更具可讀性。函數如今是可經過查看它的方法簽名來講明本身(雖然方法名稱可能仍然能夠改進)。它須要一個Process輸入並以某種ExecutionStatus狀態返回。除了直接在代碼中提供更好的「文檔」以外,簽名變得更有意義。執行什麼Process?咱們能夠查看Process對象並在運行時查看它。它發揮做用後會發生什麼?我而後可在咱們的流程中使用該函數的返回結果。

結論

現在,不管咱們是在處理遺留代碼仍是新建綠地項目,咱們均可以使用一些東西來提升平常工做的質量和生產率。函數編程從不一樣的角度進行編碼。它一般意味着更簡潔,但若是給予適當的照顧,也會提升可讀性。它還幫助咱們解決一些常見的痛苦,例如併發編程中的競爭條件,使人討厭的對象狀態錯誤或難以遵循的代碼。

寫在最後:

禿頂程序員的不易,看到這裏,點了關注吧! 點關注,不迷路,持續更新!!!

相關文章
相關標籤/搜索