函數式編程一
在java中用函數式的方式去做事情,Happy Path確實很好玩,但是編程中最不好玩的就是異常的情況。
通常函數式都是流式,然而通常不希望數據在流的過程中出現異常。於是出現了這麼三種處理方式:
1.
F#中提出了Railway Oriented Programming[2] 特別有意思的一個想法。
2.
Try/Success/Failed 模式最早是在Twitter中提出的, 後被引入Scala的標準類庫中。
3.
Option/Some/None模式是另一種處理模式。
2和3 在Scala文章中有詳細的說明: FUNCTIONAL ERROR HANDLING IN SCALA[3]
強烈推薦去看一下,文章不長,也不枯燥。中間對比和2和3的優缺點,和使用場景。
了解了一番之後,回過頭看Java的Optional弱雞一個。
背景1.
項目中用的是Java,然後處理數據是一批一批的處理,之後改成了函數式,並沒有大動結構,只是用函數式給串了起來。
2.
由於傳統面向對象思想的束縛,throw exception,在流中的操作原子中有出現。
3.
每個操作原子中可能會有副作用。
4。對業務代碼無感知
1.能夠給調用方返回數據
函數式編程最佳實「踩」踐「坑」指南函數式編程通常有這麼一個原則:
所有在操作原子中主動拋異常的行為都是耍流氓。
標準的函數式異常處理模式有什麼問題:無法攜帶異常數據。只能攜帶異常。
Java中有人實現了標準Scala的Try[4]。
有興趣的可以看看,代碼也很簡單,一會就能看完。看完之後,自己手動實現java Optional就很輕鬆了。
Try的實現增強版1.定義接口,用於承接數據
public interface IError<T> { T toSystemError();}1.定義Try
// 定義承載數據類型為IN, 並且要實現IERROR// 定義異常類型為ERRORpublic final class Try<IN extends IError<ERROR>, ERROR> {
// 流進來的數據 private final IN value; // 承載的異常數據 private final ERROR error; // 發生異常的現場 private final Throwable exception;
// success的構造函數 private Try(IN IN) { this(IN, null, null); }
// failed的構造函數 private Try(IN IN, ERROR error, Throwable exception) { this.value = IN; this.error = error; this.exception = exception; }
public boolean isSuccess() { return Objects.isNull(exception); }
public boolean isFailed() { return !this.isSuccess(); }
public IN get() { return this.value; }// 流中數據類型的轉化 public <R extends IError<ERROR>> Try<R, ERROR> map(Function<IN, R> mapper) { return getData(mapper); }// 流中數據類型轉化處理 private <R extends IError<ERROR>> Try<R, ERROR> getData(Function<IN, R> mapper) { if (this.isFailed()) { return new Try<>(null, error, exception); } if (Objects.isNull(value)) { return new Try<>(null); } try { final R newValue = mapper.apply(value); return new Try<>(newValue); } catch (Exception e) { return new Try<>(null, value.toSystemError(), e); } }// 類似於flatMap,但是又不是。 public <R> R transformTo(Function<IN, R> converter) { return converter.apply(value); }// 這裡參照CompletableFuture的做法,並且參照了NodeJs的做法(error first) public void whenComplete(BiConsumer<Throwable, ERROR> afterFailed, Consumer<IN> afterSuccess) { if (this.isSuccess()) { afterSuccess.accept(value); } else { afterFailed.accept(exception, error); } }
// 這裡是Try的構造。 public static <I extends IError<E>, R extends IError<E>, E> Try<R, E> apply(Supplier<R> supplier) { return new Try<>(supplier.get()); }
}1.用法
class TryDemoTest {
class TestClass implements TryDemo.IError<String> {
private String id;
public TestClass(String id) { this.id = id; }
@Override public String toSystemError() { return id; } }
@Test void shouldExecNextStep() { final TestClass test = TryDemo.Try.apply(() -> new TestClass("start")) .map(testClass -> new TestClass("1")) .map(testClass -> new TestClass("2")) .get();
assertEquals("2", test.id); }
@Test void shouldNotExecNextStep() { Function<TestClass, TestClass> exception = testClass -> { throw new NullPointerException(); }; TryDemo.Try.apply(() -> new TestClass("start")) .map(testClass -> new TestClass("1")) .map(exception) .map(testClass -> new TestClass("3")) .whenComplete((e, data) -> { // log ERROR and get Error data assertTrue(true); assertEquals("1", data); }, successData -> { // handle success Logic assertFalse(true); }); }}總結這裡和原始的Try的不同在於原始的Try是一個抽象類,Success和Failed都是其具體的實現,沒法同時持有success和failed,導致它沒法持有異常數據。
References[2] Railway Oriented Programming: https://fsharpforfunandprofit.com/rop/
[3] FUNCTIONAL ERROR HANDLING IN SCALA: https://docs.scala-lang.org/overviews/scala-book/functional-error-handling.html
[4] Try: https://github.com/lambdista/try