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

发布于 2024-11-26 00:16:16 字数 4953 浏览 0 评论 0原文

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

更新 通过公共 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; }
    }
}

There are a few different common patterns for returning the result of a function call in public APIs. It is not obvious which is the best approach. Is there a general consensus on a best practice, or, at least convincing reasons why one pattern is better the others?

Update By public API, I mean the public members that are exposed to dependent assemblies. I am not referring exclusively to an API that is exposed publicly as a web service. We can make the assumption that clients are using .NET.

I wrote a sample class below to illustrate the different patterns for returning values, and I have annotated them expressing my concerns for each one.

This is a bit of a long question, but I'm sure I'm not the only person to have considered this and hopefully this question will be interesting to others.

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; }
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

把人绕傻吧 2024-12-03 00:16:16
  • Get - 如果验证失败是意外的,或者调用者可以在调用方法之前自行验证参数,则使用此选项。

  • TryGet - 如果预期验证会失败,请使用此选项。 TryXXX 模式可以假定为熟悉的,因为它在 .NET Framework 中常见(例如,Int32.TryParseDictonary.TryGetValue)。

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

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

  • GetResult - 如果 _validate 需要返回比简单布尔值更多的信息,请使用此方法。我不会用它来包装异常(请参阅:TryGetout Exception)。

  • Get - use this if validation failing is unexpected or if it's feasible for callers to validate the argument themselves before calling the method.

  • TryGet - use this if validation failing is expected. The TryXXX pattern can be assumed to be familiar due it's common use in the .NET Framework (e.g., Int32.TryParse or Dictonary<TKey, TValue>.TryGetValue).

  • TryGet with out Exception - an exception likely indicates a bug in the code passed as delegates to the class, because if the argument was invalid then _validate would return false instead of throwing an exception and _getMethod would not be called.

  • GetTuple, GetTupleWithException - never seen these before. I wouldn't recommend them, because a Tuple isn't self-explaining and thus not a good choice for a public interface.

  • GetResult - use this if _validate needs to return more information than a simple bool. I wouldn't use it to wrap exceptions (see: TryGet with out Exception).

傲娇萝莉攻 2024-12-03 00:16:16

如果“公共 API”是指一个 API 将被您无法控制的应用程序使用,并且这些客户端应用程序将以多种语言/平台编写,我建议返回非常基本的类型(例如字符串、整数、小数)并使用类似于 JSON 等更复杂的类型。

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

从模式角度来看,我会倾向于类似 REST 的方法而不是 SOAP。 Martin Fowler 有一篇关于这意味着什么的好文章:http://martinfowler.com/articles/richardsonMaturityModel .html

If by "public API" you mean an API by will be consumed by applications outside of your control and those client apps will written in a variety of languages/platforms I would suggest returning very basic types (e.g. strings, integers, decimals) and use something like JSON for more complex types.

I don't think you can expose a generic class in a public API since you don't know if the client will support generics.

Pattern-wise I would lean towards a REST-like approach rather than SOAP. Martin Fowler has a good article high level article on what this means: http://martinfowler.com/articles/richardsonMaturityModel.html

℉服软 2024-12-03 00:16:16

回答之前要考虑的事情:

1- DOTNet 编程语言和语言有一种特殊情况。 Java,因为您可以轻松检索对象,而不仅仅是原始类型。示例:“纯 C”API 可能与 C# API 不同

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

答案:

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

该函数可能有多个参数,大部分是只读参数或输入参数,以及单个引用out参数,该参数可能是原始类型,对对象的引用,可能会更改,或指向对象或数据结构的指针。

如果出现异常,一些开发人员可能会捕获它们并生成特定的错误代码。

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 结果。干杯。

更新 1:提及异常处理。

更新 2:显式声明常量。

Things to consider, before answer:

1- There is a special situation about DOTNet programming languages & Java, due that you can easily retrieve objects, instead of only primitive types. Example: so a "plain C" A.P.I. may differ to a C# A.P.I.

2- If there is an error in you A.P.I., while retriving a data, how to handle, without interrumpting you application.

Answer:

A pattern, I have seen in several libraries, its a function, that its main result its an integer, in which 0 means "success", and another integer value means an specific error code.

The function may have several arguments, mostly read-only or input parameters, and a single reference or out parameter that maybe a primitive type a reference to an object, that maybe changed, or a pointer to an object or data structure.

In case of exceptions, some developers, may catch them and generate an specific error code.

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

Its similar to your tuple result. Cheers.

UPDATE 1: Mention about exception handling.

UPDATE 2: explicit declare constants.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文