Какой шаблон результатов лучше всего подходит для публичного API и почему? - PullRequest
5 голосов
/ 21 июля 2011

Существует несколько различных общих шаблонов для возврата результата вызова функции в общедоступных API. Это не очевидно, какой подход является лучшим. Существует ли общее согласие в отношении наилучшей практики или, по крайней мере, убедительные причины того, почему один шаблон лучше других?

Обновление Под общедоступным API я подразумеваю общедоступные элементы, подверженные зависимым сборкам. Я не имею в виду исключительно 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; }
    }
}

Ответы [ 3 ]

6 голосов
/ 21 июля 2011
  • Get - используйте этот параметр, если сбой проверки произошел неожиданно или если вызывающие стороны могут сами проверить аргумент перед вызовом метода.

  • TryGet - используйте, если ожидается сбой проверки. Паттерн TryXXX можно считать знакомым, поскольку он широко используется в .NET Framework (например, Int32.TryParse или Dictonary .TryGetValue ).

  • TryGet с out Exception - исключение, скорее всего, указывает на ошибку в коде, передаваемом в качестве делегатов в класс, потому что, если аргумент неверен, _validate будет верните false вместо генерации исключения и _getMethod не будет вызвано.

  • GetTuple , GetTupleWithException - никогда раньше не видел. Я бы не рекомендовал их, потому что Tuple не требует объяснений и поэтому не является хорошим выбором для публичного интерфейса.

  • GetResult - используйте это, если _validate нужно вернуть больше информации, чем простой bool. Я бы не использовал его для переноса исключений (см .: TryGet с out Exception ).

1 голос
/ 21 июля 2011

Если под «общедоступным API» вы подразумеваете, что API by будет использоваться приложениями, находящимися вне вашего контроля, и эти клиентские приложения будут написаны на различных языках / платформах, я бы предложил возвращать очень простые типы (например, строки, целые числа, десятичные дроби). ) и использовать что-то вроде JSON для более сложных типов.

Я не думаю, что вы можете представить обобщенный класс в общедоступном API, поскольку вы не знаете, будет ли клиент поддерживать обобщенные.

По шаблону я бы склонялся к REST-подобному подходу, а не к SOAP. У Мартин Фаулер есть хорошая статья высокого уровня о том, что это значит: http://martinfowler.com/articles/richardsonMaturityModel.html

0 голосов
/ 22 июля 2011

Что нужно учесть перед ответом:

1- Особая ситуация с языками программирования DOTNet и Java связана с тем, что вы можете легко извлекать объекты, а не только примитивные типы. Пример: так "обычный C" A.P.I. может отличаться от C # A.P.I.

2- Если у вас есть ошибка A.P.I., при получении данных, как обрабатывать, не прерывая ваше приложение.

Ответ:

Шаблон, который я видел в нескольких библиотеках, это функция, основным результатом которой является целое число, в котором 0 означает «успех», а другое целочисленное значение означает конкретный код ошибки.

Функция может иметь несколько аргументов, в основном только для чтения или входных параметров, и один параметр reference или 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: явное объявление констант.

...