All the picture coming from http://fsharpforfunandprofit.com or http://www.slideshare.net
最近看到這個很有趣的設計概念,後來發現原來之前有介紹過的Functional Programming就是此概念的一種實作。
作者scottw其實是以F#介紹如何設計並完成一支程式,分為三篇文章:
- How to design and code a complete program
 - Railway Oriented Programming,並提供影音及投影片介紹
 - Organizing modules in a project
 
作者以一個簡單的use case當做範例,基本上是使用者更新資訊的一個處理過程: 
轉個方向來看: 
再延伸成: 
基本上綠色成功路徑作者稱之為:Happy path,紅色錯誤路徑當然是Unhappy path了。
Happy path的程式可表示為: 

一般加上簡單的錯誤處理或Log後可能會變為: 

整個程式可能由單頁變成雙頁,對可讀性和維護性都會有些許的困擾,還好目前在C#至少有基於此概念的兩套函式庫可參考,介紹如下。
Vladimir Khorikov在Functional C#: Handling failures, input errors的文章
其NuGet。
Happy path:
[HttpPost]
public HttpResponseMessage CreateCustomer(string name, string billingInfo)
{
    Customer customer = new Customer(name);
    _repository.Save(customer);
    _paymentGateway.ChargeCommission(billingInfo);
    _emailSender.SendGreetings(name);
    return new HttpResponseMessage(HttpStatusCode.OK);
}
Error Handling:
blablabla…
[HttpPost]
public HttpResponseMessage CreateCustomer(string name, string billingInfo)
{
    Result<CustomerName> customerNameResult = CustomerName.Create(name);
    if (customerNameResult.Failure)
    {
        _logger.Log(customerNameResult.Error);
        return Error(customerNameResult.Error);
    }
    Result<BillingInfo> billingIntoResult = BillingInfo.Create(billingInfo);
    if (billingIntoResult.Failure)
    {
        _logger.Log(billingIntoResult.Error);
        return Error(billingIntoResult.Error);
    }
    try
    {        
        _paymentGateway.ChargeCommission(billingIntoResult.Value);
    }
    catch (FailureException)
    {
        _logger.Log(“Unable to connect to payment gateway”);
        return Error(“Unable to connect to payment gateway”);
    }
    Customer customer = new Customer(customerNameResult.Value);
    try
    {
        _repository.Save(customer);
    }
    catch (SqlException)
    {
        _paymentGateway.RollbackLastTransaction();
        _logger.Log(“Unable to connect to database”);
        return Error(“Unable to connect to database”);
    }
    _emailSender.SendGreetings(customerNameResult.Value); 
    return new HttpResponseMessage(HttpStatusCode.OK);
}
ROP way
[HttpPost]
public HttpResponseMessage CreateCustomer(string name, string billingInfo)
{
    Result<BillingInfo> billingInfoResult = BillingInfo.Create(billingInfo);
    Result<CustomerName> customerNameResult = CustomerName.Create(name);
    return Result.Combine(billingInfoResult, customerNameResult)
        .OnSuccess(() => _paymentGateway.ChargeCommission(billingInfoResult.Value))
        .OnSuccess(() => new Customer(customerNameResult.Value))
        .OnSuccess(
            customer => _repository.Save(customer)
                .OnFailure(() => _paymentGateway.RollbackLastTransaction())
        )
        .OnSuccess(() => _emailSender.SendGreetings(customerNameResult.Value))
        .OnBoth(result => Log(result))
        .OnBoth(result => CreateResponseMessage(result));
}
噹噹!看到這裡你的內心應該會感動很多下吧…
stormy-ua的Railway
其NuGet。
ROP way
    // combine validation functions
    var combinedValidation = Railway
        // log inpiut request
        .Apply<Request> (r => LogRequest(r))
        // do email and name validation in parallel and combine errors
        .OnSuccess(
            (r1, r2) => r1,
            (e1, e2) => new AggregateException(e1, e2),
            r => ValidateName(r),
            r => ValidateEmail(r)
        )
        // extract request name
        .OnSuccess (request => request.Name)
        // log extracted name
        .OnSuccess (name => Console.WriteLine ("Name: {0}", name))
        // append dash to name
        .OnSuccess (name => name + "-")
        // log name
        .OnSuccess (name => Console.WriteLine ("Name: {0}", name))
        // make nume uppercase
        .OnSuccess (name => name.ToUpper ())
        // log name
        .OnSuccess (name => Console.WriteLine ("Name: {0}", name))
        // log failure if any occured during the pipeline execution
        .OnFailure (e => Console.WriteLine ("Failure: {0} ", e.Message));
    // invoke combined function
    var result = combinedValidation (new Request { Name = "", Email = "" });
    //var result = combinedValidation (new Request { Name = "", Email = "a@b.c" });
    //var result = combinedValidation (new Request { Name = "Kirill", Email = "" });
    //var result = combinedValidation (new Request { Name = "Kirill", Email = "a@b.c" });
    // process result
    switch (result.IsSuccess) {
    case true:
        Console.WriteLine ("Success. {0}", result.Value);
        break;
    case false:
        Console.WriteLine ("Failure: {0}", result.Error);
        break;
    }
基本上我會選用第一種,不過這兩個函式庫的實作方式都值得瞭解。
Written with StackEdit.
RRB Chandigarh Result
回覆刪除