有了前篇所描述的解決方案後
用相同的模型來創造一個屬於這個專案專屬的回傳型別吧
public abstract class CrawlerResult<T>
{
    public abstract CrawlerResult<T> OnError<TException>(Func<TException,Exception> handler)
        where TException : Exception;
    public abstract CrawlerResult<T> OnSuccess<TResult>(Func<T,TResult> map);
    public abstract T Result { get; }
}
看起來有點不一樣 但仍有幾分神似
接著再來完成 Left 和 Right
  public class OkResult<T> : CrawlerResult<T>
    {
        private readonly T _result;
        public OkResult(T result)
        {
            _result = result;
        }
        public override T Result => _result;
        public override CrawlerResult<T> OnError<TException>(Func<TException, Exception> handler)
            => this;
        public override CrawlerResult<TNext> OnSuccess<TNext>(Func<T, TNext> map)
            => new OkResult<TNext>(map(_result));
    }
    public class ErrorResult<T> : CrawlerResult<T>
    {
        private readonly Exception _exception;
        public ErrorResult(Exception exception)
        {
            this._exception = exception;
        }
        public override T Result => ThrowIfExistException(CreateEmpty);
        public override CrawlerResult<T> OnError<TException>(Func<TException, Exception> handler)
            => new ErrorResult<T>(_exception is TException ex ? handler(ex) : _exception);
        public override CrawlerResult<TNext> OnSuccess<TNext>(Func<T, TNext> map)
            => new ErrorResult<TNext>(_exception);
        private T CreateEmpty()
            => new HtmlElementCollection(Enumerable.Empty<HtmlElement>()) is T result ? result : default;
        
        private T ThrowIfExistException(Func<T> factory)
            => _exception == null ? DefaultIfNotHtmlCollection(factory) : throw _exception;
        private T DefaultIfNotHtmlCollection(Func<T> factory)
            => typeof(T) == typeof(IHtmlElementCollection)
                ? factory()
                : default;
    }
作為輔助使用,所以在加一個呼叫類別
    public abstract class CrawlerResult
    {
        public static CrawlerResult<IHtmlElementCollection> Ok(IHtmlElementCollection htmls)
            => new OkResult<IHtmlElementCollection>(htmls);
        public static CrawlerResult<IHtmlElementCollection> Error(Exception exception)
            => new ErrorResult<IHtmlElementCollection>(exception);
    }
別忘了改變回傳的結果,我們不應該直接改變 interface 與 class
所以我們改用後來追加的擴充方法來完成
這只是過程,請不要這樣寫擴充方法,後續會慢慢將他轉為合適的作法
async public static Task<CrawlerResult<IHtmlElementCollection>> TryFindAsync(
    this ICrawler crawler,
    Action<HttpRequestMessageBuilder> config)
    {
        try
        {
            return CrawlerResult.Ok(await crawler.FindAsync(config));
        }
        catch (Exception ex) 
        {
            return CrawlerResult.Error(ex);
        }
    }
如此一來我們就可以做到下面這件事情
var result = await crawler.TryFindAsync(req => req.Set(GOOGLE));
var urls = result.OnError(ex => {
    logger.LogError(ex);
    return ex;
}).OnSuccess(htmls => htmls.Where(html => html.Name == "a")
    .SelectMany(htmls => htmls,(htmls,html) => htmls.Attributes["href"]))
    .Result;
或像先前一樣的作法
var result = await crawler.TryFindAsync(req => req.SetUrl(GOOGLE));
var urls = result.OnError<Exception>(WriteErrorLog)
    .OnSuccess(GetUrls)
    .Result;
Assert.Single(urls);
Assert.Equal(GOOGLE, urls.Single());
private Exception WriteErrorLog(Exception ex)
{
    logger.LogError(ex);
    return ex;
}
private IEnumerable<string> GetUrls(IHtmlElementCollection htmls)
    => htmls.Where(html => html.Name == "a")
    .SelectMany(
        htmls => htmls,
        (htmls,html) => htmls.Attributes["href"]);
下一篇來Diff 一下看看插了那些以及為何要改變
Try/Catch.除非你當下可以 Handle ,但往往可以 Handle 的例外都可以在前面被檢測出來,所以該 throw 時就應該要 throw
Log 應統一交由最終輸出前的 Logger 來完成Handle 並轉為正常的錯誤訊息Try/Catch 因為 Exception 會記錄 traceStack 停留在擴充方法內,但擴充方法基於前面所介紹的,只是一個連接語意,所以如果是可能會發生例外的一件事情,建議還是獨立的類別 較為妥當