我正在使用PHP(但在这种情况下,我认为编程语言无关紧要(,在我的类方法中,我通常会遇到以下情况:
- 方法必须返回 true 或 false
- 方法必须返回 true 或错误消息
- 方法必须返回 true + 成功消息或 false + 错误消息
- 方法必须返回 true + 成功结果(对象、数组等(或 false
- 方法必须返回 true + 成功结果(对象、数组等(或 false + 错误消息
- 等。
我的问题是,当我在代码中的某个地方使用此类方法时,我总是必须回到类,并检查该方法实际返回的内容:简单的真或假,真或错误消息等。
标准化返回值是个好主意吗?如果是,如何?
我的想法是:
- 如果函数必须返回真或假
- ,则只需返回真或假
如果函数必须返回 true 或错误消息,则:
if (success) { return array( TRUE, null ); } else { return array( FALSE, $error_message ); }
如果函数必须返回 true + 成功消息或错误消息,则:
if (success) { return array( TRUE, $success_message, ); } else { return array( FALSE, $error_message ); }
等。
我希望你们理解我的问题,甚至认为我的解释不是那么好:)您的建议或最佳实践是什么?我应该如何处理?
更新:让我们举一个简单的例子:
function login($username, $password)
{
// Login logic here ..
if ($logged_in)
{
return TRUE;
}
{
return $error_message;
}
}
因此,执行此操作的正确方法是:返回 true,或抛出异常,并且在调用 login 方法时使用 try catch。因此,当某些人出错(验证失败等(时,我应该使用异常。
说返回布尔值和其他东西的前提是错误的。
函数应该有明确的目的和明确的结果。如果可以实现此结果,则返回结果。如果无法实现结果,该函数将返回false
或引发异常。哪个更好取决于情况和您的一般错误处理理念。无论哪种方式,让函数返回错误消息通常都没有用。该消息对调用函数的代码没有用。
PHP 除了返回false
结果外,还有自己的机制来输出错误消息:trigger_error
。不过,它纯粹是一个帮助调试的工具,它不会取代标准返回值。不过,它可能非常适合您希望仅为了帮助开发人员而显示错误消息的情况。
如果函数足够复杂,可能导致需要以不同方式处理的几种不同类型的错误,则应使用异常来执行此操作。
例如,一个非常简单的函数,目的明确,只需要返回true
或false
:
function isUserLoggedIn() {
return $this->user == 'logged in';
}
具有可能无法实现该目的的函数:
function convertToFoo($bar) {
if (!is_int($bar)) {
return false;
}
// here be dragons
return $foo;
}
同样触发消息的相同函数,对调试很有用:
function convertToFoo($bar) {
if (!is_int($bar)) {
trigger_error('$bar must be an int', E_USER_WARNING);
return false;
}
// here be dragons
return $foo;
}
一个函数,可能会合法地遇到调用代码需要了解的几种不同类型的错误:
function httpRequest($url) {
...
if (/* could not connect */) {
throw new CouldNotConnectException('Response code: ' . $code);
}
...
if (/* 404 */) {
throw new PageNotFoundException('Page not found for ' . $url);
}
return true;
}
我也会在这里粘贴这条评论:
准备、返回不应该是函数的责任 或显示最终用户错误消息。如果函数的目的 就是,比如说,从数据库中获取一些东西,然后显示错误 消息不关它的事。调用 从数据库获取函数只需要被告知 结果;从这里开始,需要有代码,其唯一工作是 显示一条错误消息,以防数据库函数无法获取 必填信息。不要将这两种责任混为一谈。
在特定情况下,返回具有多个元素的数组的解决方案可能是可接受的解决方案,该元素不是同类实体的集合。但总的来说,这是一种代码气味。
这样的设计可以暗示一些问题:
- 您没有正确使用异常处理
- 您的设计违背了单一责任原则
- 您总是在等待返回,直到方法结束
如果一个方法确实无法执行它应该执行的操作,则它不应返回任何内容,而应引发异常。
如果您的方法执行多个任务,则必须重构。
尽早返回。编写方法更像这样:
if (!$something)
{
return FALSE;
}
//do some other stuff
return 'great, it worked';
您的特定登录函数不应返回消息。此类特定于操作的用户消息应解耦并由消息队列处理。
因此,您可以有一个注入控制器的 Messenger 类,以便您可以在任何地方使用它并将消息添加到队列中。
function login($username, $password)
{
// Login logic here ..
if ($logged_in)
{
$this->messenger->addMessage('success', 'You are loggend in.');
return TRUE;
}
{
$this->messenger->addMessage('error', $message);
return FALSE;
}
}
其他一些语言(即用,C#(的一个常见习惯用语是Do/TryDo方法配对。
/**
* @param MyInput $input
* @return MyOutput
* @throws MyException
*/
function myOperation(MyInput $input)
{
}
myOperation
必须抛出一个异常(在本例中MyException
(,指示操作失败。成功后,返回结果值(在本例中为 MyOutput
(。
/**
* @param MyInput $input
* @param MyOutput $output
* @return bool
*/
function tryMyOperation(MyInput $input, &$output = null)
{
}
tryMyOperation
必须返回一个布尔值,指示操作的失败或成功。它不得引发异常(与操作的成功或失败直接相关(。该值被分配给通过引用传递的参数(在本例中$output
(。通常,try*
方法可以代理非try*
方法。
一个人为的例子:
/**
* @param string $input
* @return string
* @throws TypeException
*/
function stringToUpper($input)
{
if (!is_string($input))
{
throw new TypeException('String expected');
}
return strtoupper($input);
}
/**
* @param string $input
* @param string $output
* @return boolean
*/
function tryStringToUpper($input, &$output)
{
try
{
$output = stringToUpper($input);
return true;
}
catch (TypeException $exception)
{
$output = null;
return false;
}
}
并将用作:
try
{
$output = stringToUpper($input);
// use $output
}
catch (TypeException $exception)
{
// recover
}
if (tryStringToUpper($input, &$output))
{
// use $output
}
else
{
// recover
}
通过遵循这样的约定,代码的语义和意图变得更加清晰。如果要捕获错误信息,请使用 myOperation()
并catch
错误信息。如果您不太关心为什么某些东西坏了,而不是东西坏了,请使用 tryMyOperation()
.
错误消息与返回值无关。反模式可能有助于避免已知的编码错误。函数应返回与其目的一致的值,即:
canWriteFile() { return true or false }
writeFile() { should return void }
writeFile()
根据名称,程序员不期望任何价值,他必须学习文档,这需要时间,不直观,可能导致错误。发明不需要文档的名称。
您绝对不应该使用带有第一项布尔值、第二项错误消息的数组 - 这返回复杂数据类型而不是简单的直观值,您将以为常见函数编写适配器而告终,并且您的代码很快就会变坏。
有 3 种处理错误的可能性:
1( 错误标志/状态对通知有用
$error = "";
function foo() {
if($somethingBad) $error = "error occured";
return !$somethingBad;
}
2( 错误处理程序对大多数错误有用
function handleError($message) {
...
}
function foo() {
if($somethingBad) handleError("error occured");
return !$somethingBad;
}
3( 错误捕获对于无法处理的错误很有用(即服务器在请求期间脱机(
function foo() {
try {
// dangerous code here
}
catch($e) {
// handle error here
}
return !$somethingBad
}
尝试异常
我建议深入研究一下例外,因为它们可以派上用场。首先,你摆脱了返回的错误消息:你抛出了一个异常。
其次,你不再需要真或假的返回代码:如果一切按预期工作,你就知道这一点,因为没有抛出异常。
如果在类中处理成功和错误消息,则可能还需要删除它们。这些事情应该在一个非常前端的类中处理,该类检查异常,然后根据异常或成功消息设置错误消息。
可重用类
方法应返回应用程序可以使用的对象。当你开始使用返回值作为通过系统传递的消息时,你依赖于这些消息的存在,这使得很难替换底层类。
一个好的思维方式如下:我可以在另一个项目中使用我的类吗?如果你有自定义的错误消息,这可能很难,因为你可能需要另一个项目中的其他人,并且必须更改你的类。因此,您只想处理全局成功或错误(异常(,然后在前端附近添加自定义错误消息。
一个标准的方法显然是这样的:返回结果或假。
出错时引发异常。
这取决于你的应用正在做什么。在一个案例中,我必须设计一个基于 REST API 的大型应用程序,我希望错误能够冒泡并详细到 API 的任何部分,直到用户级别。所以我所做的是:
- 每个函数返回一个错误/成功代码,这应该是有意义的。
- OK 为 1,"常规故障"为 0,因此符合布尔值。
- 专用错误代码是小于 0 的值。 您
- 有一个枚举所有值的类,因此您不必记住代码。
- 此类还具有用于显示和日志记录的错误的文本表示形式。
- 这个类还有 2 个方法
OK($errorCode)
和FAIL($errorCode)
,可以在if
S 内部使用
该类看起来像
class Error {
const OK = 1;
//general error
const FAIL = 0;
//invalid url
const RequestParseError = -1;
//resource not found
const ResourceNotFound = -2;
....
static public function _($errCode) {
switch ($errCode) {
case Error::OK:
return "OK";
case Error::FAIL:
return "General Failure";
case Error::RequestParseError:
return "Request Parse Error";
.....
}
}
这样,应用程序的每个级别都可以返回一个错误代码,并且它会随心所欲地向上冒泡。 OK(( 和 FAIL(( 函数与布尔返回函数兼容。