11

Say I re-implement a method for finding the position of a string within a string. So I get either the position which is an integer, or a "magic number" like -1. I see this pattern in so many programming languages, where some return a value like null, some return some random "out of range" value like -`, some totally throw an error.

Is there a "best practice" on this or at least something that's a bit more explicit in other programming language that I'm not aware of?

anemaria20
  • 219
  • 1
  • 3

6 Answers6

21

Follow the principle of least surprise for whatever language you are coding in. People are going to, at some level, expect your function to follow the paradigms and standards of your language. So if your language typically uses error codes like -1 on search functions (like C, C++, etc.), do that. If you're in a functional language where an option type is commonplace, do that. If your language typically uses nulls for that sort of thing, do that. Etc, etc. Make your function easy to use for the people that will be using it. They are far more likely to get it right when it works like other things they are familiar with.

Becuzz
  • 4,855
16

A popular way, amongst functional languages at least, is to use a Maybe type (or Option in some functional languages).

Maybe<int> FindPosition(string stringToSearch, char charToFind)
    // Return Some(int) if found or none if not.

The function then will yield an int value of the position if found, and none if nothing was found.

This avoids the problems inherent in using a struct or tuple to return a flag indicating success, and a posibly undefined int value depending on that flags state; throwing an exception; using a magic number etc.

David Arno
  • 39,599
  • 9
  • 94
  • 129
4

Sometimes a TryGet Pattern can be useful in the scenario that you describe.

Example:

public bool TryGetValue(string input, string toFind, out int value)

Note: This is also telling you that no exceptions should come out of this method as well for languages that support exceptions.

Jon Raynor
  • 11,773
2

There are lots of approaches to this and while I do agree with Becuzz to some degree that you should usually follow the conventions of the language/team, it's not always completely cut-and-dried. In java, there are competing ideas about the best way to approach this kind of thing and what the right answer is depends on the context of the method.

One thing that I would first try to disabuse you of is that returning -1 is a 'random' value or magic number. You really should not be coding to the specific value. In that case, you should check for values less than 0 for not found. I've seen assumptions like that lead to bugs. For example, in Java Integer.compare is implemented using subtraction. That means pretty much any int (positive or negative) may be returned but I've seen code that assumes compare calls will only return -1, 0, or 1. Another case in point is Arrays.binarySearch which, when an element is not found returns a negative number indicating where the element would be in the order if it were in the array.

These kinds of approaches are useful because they return more information that simply "not found". There are other cases though different approaches are common. For example, a method meant to retrieve a collection of items meeting a certain criteria could return a null. Another option is to return an empty collection. In most cases, the latter is preferable because you don't have to worry about null-pointer checks and there are usually no checks required if all you want to do is iterate over the results. Returning null is common and expected in Java but it's also problematic. Another option would be to throw an exception but that is generally discouraged because of the overhead of creating a stack trace which is usually overstated but real.

The point is You don't need to blindly follow convention if you can achieve superior results. But in lieu of that, do what people expect.

JimmyJames supports Canada
  • 30,578
  • 3
  • 59
  • 108
1

One way is to return a 2-field struct by value, in C++ this typically looks like this:

struct find_result
{
    bool found;
    size_t index;
};

Then the callee can set found to true or false, only set index when found is true, and document that index can only be used if found is true.

The C++ standard library uses this with std::pair<>, where first is a bool and second carries the context (like index does above).

1

In this specific case a valid return value (indicating a match was found) will be from zero up, while -1 indicates nothing was found. You are left with all the other negative values to indicate there was a problem. So specifically here I would just return -2 to indicate there was an error.

As Johann said above you could use complex structures to return 'status' type values but I would only use that for APIs where complex return values are required and it is necessary to indicate if the actual result returned was 'real'. So in your specific case if it were possible to return valid negative values then you do not have the option of using the negative numbers to indicate state and so returning a pair of values would be useful.

JohnXF
  • 154