在公共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; }
}
}
答案 0 :(得分:6)
获取 - 如果验证失败是意外的,或者调用者在调用方法之前验证参数是否可行,则使用此方法。
TryGet - 如果预计验证失败,请使用此选项。可以假设TryXXX模式很常见,因为它在.NET Framework中很常见(例如,Int32.TryParse或Dictonary<TKey, TValue>.TryGetValue)。
TryGet 与 out异常 - 异常可能表示代码中传递给代表的代码中的错误,因为如果参数无效,那么{{ 1}}将返回false而不是抛出异常,并且不会调用_validate
。
GetTuple , GetTupleWithException - 之前从未见过这些。我不推荐它们,因为元组不是自我解释的,因此不是公共界面的好选择。
GetResult - 如果_getMethod
需要返回的信息多于简单的bool,请使用此选项。我不会用它来包装异常(参见: TryGet out Exception )。
答案 1 :(得分:1)
如果通过“公共API”表示API将由您的控件之外的应用程序使用,并且这些客户端应用程序将以各种语言/平台编写,我建议返回非常基本的类型(例如字符串,整数,小数)并使用类似JSON的东西来处理更复杂的类型。
我认为您不能在公共API中公开泛型类,因为您不知道客户端是否支持泛型。
模式方面我倾向于使用类似REST的方法而不是SOAP。 Martin Fowler有一篇关于这意味着什么的高级文章:http://martinfowler.com/articles/richardsonMaturityModel.html
答案 2 :(得分:-1)
在回答之前需要考虑的事项:
1-有关DOTNet编程语言的特殊情况&amp; 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:显式声明常量。