哪种结果模式最适合公共API,为什么?

本文关键字:API 为什么 结果 模式 | 更新日期: 2023-09-27 18:03:20

在公共api中返回函数调用结果有几种不同的常见模式。哪一种方法是最好的还不清楚。对于最佳实践是否有普遍的共识,或者至少有令人信服的理由说明为什么一种模式优于其他模式?

Update这里的公共API是指公开给依赖程序集的公共成员。我是而不是专门指的是作为web服务公开暴露的API。我们可以假设客户端正在使用。net。

我在下面写了一个示例类来说明返回值的不同模式,并对它们进行了注释,以表达我对每个模式的关注。

这个问题有点长,但我相信我不是唯一一个考虑过这个问题的人,希望这个问题会引起其他人的兴趣。

public class PublicApi<T>       //  I am using the class constraint on T, because 
    where T: class              //  I already understand that using out parameters
{                               //  on ValueTypes is discouraged (http://msdn.microsoft.com/en-us/library/ms182131.aspx)
    private readonly Func<object, bool> _validate;
    private readonly Func<object, T> _getMethod;
    public PublicApi(Func<object,bool> validate, Func<object,T> getMethod)
    {
        if(validate== null)
        {
            throw new ArgumentNullException("validate");
        }
        if(getMethod== null)
        {
            throw new ArgumentNullException("getMethod");
        }
        _validate = validate;
        _getMethod = getMethod;
    }
    //  This is the most intuitive signature, but it is unclear
    //  if the function worked as intended, so the caller has to
    //  validate that the function worked, which can complicates 
    //  the client's code, and possibly cause code repetition if 
    //  the validation occurs from within the API's method call.  
    //  It also may be unclear to the client whether or not this 
    //  method will cause exceptions.
    public T Get(object argument)
    {
        if(_validate(argument))
        {
            return _getMethod(argument);
        }
        throw new InvalidOperationException("Invalid argument.");
    }
    //  This fixes some of the problems in the previous method, but 
    //  introduces an out parameter, which can be controversial.
    //  It also seems to imply that the method will not every throw 
    //  an exception, and I'm not certain in what conditions that 
    //  implication is a good idea.
    public bool TryGet(object argument, out T entity)
    {
        if(_validate(argument))
        {
            entity = _getMethod(argument);
            return true;
        }
        entity = null;
        return false;
    }
    //  This is like the last one, but introduces a second out parameter to make
    //  any potential exceptions explicit.  
    public bool TryGet(object argument, out T entity, out Exception exception)
    {
        try
        {
            if (_validate(argument))
            {
                entity = _getMethod(argument);
                exception = null;
                return true;
            }
            entity = null;
            exception = null;   // It doesn't seem appropriate to throw an exception here
            return false;
        }
        catch(Exception ex)
        {
            entity = null;
            exception = ex;
            return false;
        }
    }
    //  The idea here is the same as the "bool TryGet(object argument, out T entity)" 
    //  method, but because of the Tuple class does not rely on an out parameter.
    public Tuple<T,bool> GetTuple(object argument)
    {
        //equivalent to:
        T entity;
        bool success = this.TryGet(argument, out entity);
        return Tuple.Create(entity, success);
    }
    //  The same as the last but with an explicit exception 
    public Tuple<T,bool,Exception> GetTupleWithException(object argument)
    {
        //equivalent to:
        T entity;
        Exception exception;
        bool success = this.TryGet(argument, out entity, out exception);
        return Tuple.Create(entity, success, exception);
    }
    //  A pattern I end up using is to have a generic result class
    //  My concern is that this may be "over-engineering" a simple
    //  method call.  I put the interface and sample implementation below  
    public IResult<T> GetResult(object argument)
    {
        //equivalent to:
        var tuple = this.GetTupleWithException(argument);
        return new ApiResult<T>(tuple.Item1, tuple.Item2, tuple.Item3);
    }
}
//  the result interface
public interface IResult<T>
{
    bool Success { get; }
    T ReturnValue { get; }
    Exception Exception { get; }
}
//  a sample result implementation
public class ApiResult<T> : IResult<T>
{
    private readonly bool _success;
    private readonly T _returnValue;
    private readonly Exception _exception;
    public ApiResult(T returnValue, bool success, Exception exception)
    {
        _returnValue = returnValue;
        _success = success;
        _exception = exception;
    }
    public bool Success
    {
        get { return _success; }
    }
    public T ReturnValue
    {
        get { return _returnValue; }
    }
    public Exception Exception
    {
        get { return _exception; }
    }
}

哪种结果模式最适合公共API,为什么?

  • Get -如果验证失败是意外的,或者如果调用者在调用方法之前验证参数是可行的,那么使用这个

  • TryGet -在预期验证失败时使用。TryXXX模式可以被认为是熟悉的,因为它在。net框架(例如Int32)中经常使用。TryParse or Dictonary<TKey,>. trygetvalue).

  • TryGet with out Exception -异常可能表明作为委托传递给类的代码中存在错误,因为如果参数无效,则_validate将返回false而不是抛出异常,并且_getMethod将不会被调用。

  • GetTupleGetTupleWithException -以前从未见过这些。我不会推荐它们,因为Tuple不是自解释的,因此不是公共接口的好选择。

  • GetResult -如果_validate需要返回比简单bool更多的信息,使用这个。我不会用它来包装异常(参见:TryGet with out Exception)。

如果你所说的"公共API"是指一个API将被你控制之外的应用程序使用,并且这些客户端应用程序将用各种语言/平台编写,我建议返回非常基本的类型(例如字符串,整数,小数),并使用JSON之类的东西来处理更复杂的类型。

我不认为你可以在公共API中公开泛型类,因为你不知道客户端是否支持泛型。

在模式方面,我倾向于类似rest的方法,而不是SOAP。Martin Fowler有一篇关于这意味着什么的高级文章:http://martinfowler.com/articles/richardsonMaturityModel.html

在回答之前要考虑的事情:

1-关于DOTNet编程语言有一个特殊的情况&Java,因为您可以很容易地检索对象,而不仅仅是基本类型。示例:因此,"普通C"api可能与c# api不同。

2-如果您的api在检索数据时出现错误,如何在不中断应用程序的情况下处理。

答:

一个模式,我在几个库中看到,它是一个函数,它的主要结果是一个整数,其中0表示"成功",另一个整数值表示特定的错误代码。

函数可以有几个实参,主要是只读形参或输入形参,还有一个单独的referenceout形参,该形参可能是原始类型,可能是对对象的引用,也可能是指向对象或数据结构的指针。

在异常的情况下,一些开发人员可能会捕获它们并生成特定的错误代码。

public static class MyClass
{
  // not recomended:
  int static GetParams(ref thisObject, object Param1, object Params, object Param99)
  {
    const int ERROR_NONE = 0;
    try
    {
      ...
    }
    catch (System.DivideByZeroException dbz)
    {
      ERROR_NONE = ...;
      return ERROR_NONE;
    }
    catch (AnotherException dbz)
    {
      ERROR_NONE = ...;
      return ERROR_NONE;
    }
    return ERROR_NONE;
  } // method
  // recomended:
  int static Get(ref thisObject, object ParamsGroup)
  {
    const int ERROR_NONE = 0;

    try
    {
      ...
    }
    catch (System.DivideByZeroException dbz)
    {
      ERROR_NONE = ...;
      return ERROR_NONE;
    }
    catch (AnotherException dbz)
    {
      ErrorCode = ...;
      return ERROR_NONE;
    }
    return ERROR_NONE;
  } // method
} // class

它类似于你的tuple结果。欢呼。

UPDATE 1:提及异常处理。

相关文章: