1

I have a function which returns either true/false, each return plays nicely with the function name: isOnline, however, there are cases in which I want to throw an error inside of it. Maybe the status server isn't available or what-not. But the problem is, errors in PHP are not that well-supported, nor is the community to keen about them. A try/catchis a foreign concept to most, so, I need to return a custom ErrorObject. So, your code ends up looking something like this:

$online = isOnline();

if( !$online ) {
  return False;
}

if( gotError( $online ) ) {
  //Return the error or do something, execution stops here.
}

I personally like it. While verbose, I genuinely never found this level of error checking to be exhaustive or to slow-down development, but a function that has more than 2 return possibilities just feels wrong.

Is there any literature/thoughts about returning errors?

7
  • 5
    Can you create a response object from a JSON? something like ``` {"code": xxx, "status": xxx, "is_online": false, "error_msg": xxx} ```
    – lennon310
    Jan 4, 2021 at 7:07
  • 5
    What about a new return type ServerStatus? The component can hold codes, descriptions and possible errors. So if u want to know if a given server is online, you ask to the new type serverStatus.isOnline() or serverStatus.isAvailable() ?
    – Laiv
    Jan 4, 2021 at 8:23
  • 1
    I can be biased by my java background, but I see PHP has native support for Exception and try/catch blocks. If you need to break the execution path due to an exception that's the way to go. In another comment, you are concerned about the "professionalism" of your team, no workaround made by code will prevent you from bad developers. If a function needs to return different types, IMO, it means you are lacking on abstraction. The response itself must be new class or set of classes. Depends on your strategy (inheritance, enums, composition, whatever).
    – Laiv
    Jan 4, 2021 at 8:37
  • 5
    "But the problem is, errors in PHP are not that well-supported, nor is the community to keen about them. A try/catch is a foreign concept to most" [citation needed] I've done PHp development, and every dev worth their salt knows and uses exceptions.
    – Polygnome
    Jan 4, 2021 at 9:30
  • 3
    "A try/catch is a foreign concept to most" - even if that was true, take also into consideration who you're writing the code for. Is it for yourself? For your team? For people who tend to work with a certain class of problems? For people working in a particular domain? Is it a general purpose library that someone with an average skill level can use comfortably? Is it something that a beginner should be able to use? Once you know your audience, you don't have to go for the least common denominator approach. "The community" is too wide a designation. Jan 4, 2021 at 11:29

4 Answers 4

3

A try/catch is a foreign concept to most, so, I need to return a custom ErrorObject

Honestly, i think, this is a very vague statement and definitely doesn't apply to the most of the PHP community.

Well, is it a bad idea?

I think, especially in php it is a bad idea and even more a bad idea in this certain case. If there is a Error Object returning, the check isOnline() becomes true but should be obviously false. This is a preprogrammed bug.

If it should be more verbose, an idea could be to wrap it generally in a Response-like object and rename the function to something like getOnlineStatus() which returns an object that can hold multiple information but doesn't suggest the developer to return just a plain true/false value.

5
  • 1
    This is a pretty awesome answer that didn't even occur to me. As a side-note, you always want to return something of the same type. You don't want your consumer to have to think, "Maybe it's an int, maybe it's boolean, maybe it's an object" so returning a higherlevel response object makes sense. Of course, I tend to raise an exception whenever something happens that makes a method invalid. I would lean towards that.
    – unflores
    Jan 4, 2021 at 9:35
  • Also: Having multiple return types often indicates multi-purpose of the function which violates SRP principle
    – Jim Panse
    Jan 4, 2021 at 9:43
  • The last paragraph is really the answer. Return a "status" object. Exceptions are for unexpected conditions. It sounds like the expectation of this method is to detect whether something is online, and it should expect that thing to be off line at times. Therefore the other system being "offline" is not unexpected, and throwing an exception is overkill. Jan 4, 2021 at 13:21
  • Basically what i have suggested in my comment but it seems it was not clear enough.
    – Laiv
    Jan 4, 2021 at 15:04
  • Why was it not clear enough?
    – Jim Panse
    Jan 5, 2021 at 13:26
2

Where I've seen this go wrong in the past: people forget to write the error checking side of the code, so you have the if( !$online ) fragment from your example, but not the if( gotError( $online ) ) fragment. When isOnline() then does return an error, it doesn't get handled properly. You can say "we'll catch this during code review" but in practice I find that's not true, because it's not obvious that isOnline() might return an error.

Where I've seen this work well: when your language / data types enforce you to think about the error handling, or at the very least make it very obvious when you're not - effectively the Either type from functional programming, where you cannot just access the "non-error" value without at least some code to handle the error case. Anything like this almost certainly wouldn't be idiomatic PHP.

Perhaps to summarise: a function that returns more than two values is definitely not wrong (because some functions do honestly have more than two return values), but I would be wary about making it "easy" to ignore the error case.

2
  • Well, that was my issue exactly. In practice, unless people are decent developers, they won't take care of the "handle the error too" case. I have to rely on them reading the @return statement. Jan 4, 2021 at 8:25
  • I've seen plenty of better than "decent developers" make this kind of mistake - it's just something which is too easy to forget to read the docs when you're writing the code. This is why I now favour stuff like the Either monad which means your tooling enforces thinking about it. Jan 4, 2021 at 8:31
2

I see two valid solutions:

  • Throw an exception.
  • Change the return type to an enumeration of ONLINE, OFFLINE, UNKNOWN.

Exception

Exceptions are meant for situations where a function cannot fulfill its contract, and that's exactly your situation. The current contract of isOnline() is to return true or false, reflecting the "online" status of some service or whatever. There are situations where isOnline() can't give a valid answer to that question, and the general advice is to throw an exception in such cases.

Why is that good?

Your caller typically wants to know the online status as it needs it to base some decision on that information. If you can't provide that information, it doesn't make sense for your caller to continue, as it doesn't know which execution path to correctly choose. And without any error handling code, the caller aborts its processing and signals its failure to its caller, and so on, until some higher-level function knows how to continue after a failure (by having a try/catch construct). And that's very often the best thing to do after an error.

So, you get for free an error handling covering at least 90% of situtions, and a source code that isn't cluttered with lots of error handling statements.

But, if your caller knows how to handle an unknown-status situation, the exception-throwing approach makes things slightly more complicated than necessary. It has to surround the isOnline() call with an appropriate try/catch. Then the enumeration approach might be better.

Enumeration

As return values to cover the unknown-status case, I'd strongly recommend against using TRUE and FALSE for the known states, as then nobody will expect a third possible outcome, especially in a function named isOnline().

That's why I'd both rename the function to something like onlineStatus() and have it only return non-boolean values like ONLINE, OFFLINE, UNKNOWN.

Other answers proposed quite complex status objects, but I think that's overkill, and a three-fold enumeration suffices. From my experience, your caller is not interested to know why the online status turned out to be unknown, so it's enough to signal the "unknown" fact.

What would your caller do with a detailed description of the failure reason? Hopefully nothing, as it's not the responsibility of isOnline()'s caller. If there's a way to deal with an error while trying to get the online status, that should be done inside isOnline(), as that's the function responsible for that information. If this function fails, then its callers should assume that every reasonable approach to get the information has been attempted, and simply accept that the information isn't available.

The failure reason should be encapsulated and handled inside the isOnline() function, maybe written to some log file if desired.

0

In general, there are three ways to do error handling/messaging:

  • Return false, NULL or another magic value
  • Throw a (checked) exception
  • Return an intermediate result object

The first is what C-style APIs and old PHP APIs which are just thin wrappers around those do. It is incredibly bad and leads to horrible code. Magic values are bad.

It leads to code like this (JSON parsing, XML parsing, what have you):

if ($data = xyz_parse($data) === FALSE)
    $error = xyz_last_error();

The downsides to this approach are (not exhaustive):

  • It is not clear from the method signature what the magic values are
  • Without a-priory knowledge about the magic values, checking for them and interpreting them correctly is impossible
  • No compiler support, forcing the caller to correctly handle all scenarios by themselves, making errors easy
  • The order of operations is important - if there is work done in-between, the last error might not be the correct one.

In your situation, it gets even worse. If the user doesn't use isOnline() === TRUE to check for online state and you return an Error object, then isOnline() == TRUE evaluates to true since on object is a trueish value. That is a bug waiting to happen. Could it be avoided by due diligence on the site of the caller? Yes. But you practically set them up for failure.

You could return an OnlineStatus object that has fields to check if it was an error or a valid state, and inspect that object.

$status = getOnlineStatus();
if ($status->error) {
    echo "Online: unknown (" . $status->errorMsg . ")";
} else {
    echo "Online: " . $status->online;
}

Using such an intermediate object is very helpful if you expect the operation to fail. This is often done e.g. when writing parsing libraries:

$result = $parser->parse($document);
if ($result->isValid) {
     $dom = $result->getDocument();
}

In languages with support for algebraic types or union types, you frequently see types of the form Result<T, E> = Success<T> | Fail<E>. With proper compiler and IDe supports, thats a blast to work with. Unfortunately, PHP isn't the language for that, but if you wanted you could mimic that, but you'd strife away from idiomatic PHP in that case, so I wouldn't really recommend that.

PHP unfortunately doesn't have checked exceptions. Still, exceptions are the proper use-case for exceptional behavior. A network connection failing is such an exceptional behavior.

But the problem is, errors in PHP are not that well-supported, nor is the community to keen about them. A try/catch is a foreign concept to most

I would like to challenge that assumption. Exceptions are well-understood by every PHP programmer worth their salt and are a proper and good tool of the language. Use it when it is appropriate.

So, in summary:

  • Use intermediate objects when it makes sense (when failure is expected)
  • Use exceptions and try-ctach when it makes sense (when failure is exceptional)
  • Avoid magic return values and xyz_last_error-style APIs as much as possible

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.