44

I have written a struct that represents latitude/longitude coordinates. Their values range from -180 to 180 for longtitudes and 90 to -90 for lattitudes.

If a user of that struct gives me a value outside of that range, I have 2 options:

  1. Throw an exception (arg out of range)
  2. Convert the value to the constraint

Because a coordinate of -185 has meaning (it can very easily be converted to +175 as those are polar coordinates), I could accept it and convert it.

Is it better to throw an exception to tell the user that his code has given me a value that it shouldn't have?

Edit: Also I know the difference between lat/lng and coordinates, but I wanted to simplify that for easier discussion - it wasn't the brightest of ideas

11
  • 13
    Should the user be allowed to insert a value out of the range? If the answer is no, throw an exception. If the rules are not as strict, do the conversion, but explicitly state in documentation that the conversion may happen. Also be aware that in some languages exception handling is quite costly.
    – Andy
    Feb 18, 2016 at 11:15
  • C#, does it matter? It doesn't natively support constraints if this is what you mean.
    – RaidenF
    Feb 18, 2016 at 11:16
  • 2
    It seems pretty clear to me, then, the correct approach is diallowing the user to input anything outside the range and throw an exception when they do so (if the standard says the abs value must not be above 180, putting larger value than that is a clear violation). By the way, C# is actually one of the languages where exceptions are quite costly, so use them really only in situations, which are exceptional, meaning not catching it will break your application, situations such as this one.
    – Andy
    Feb 18, 2016 at 11:24
  • 2
    I tend to stay away from making assumptions about what the user 'meant' by passing particular parameter values, especially those my code doesn't cater for. Sounds like a similar case. Feb 18, 2016 at 11:44
  • 2
    Web Mercator coordinates are not from -180 to 180 and -90 to 90. That is latitude/longitude (and there are even several coordinate systems for that). Mercator projections are typically in the hundreds of thousands or millions and have units of "meters" (not even strictly that, since the length of each unit covers increasing real ground distance as you approach the poles), not degrees. Even in terms of degrees, it's constrained to ±85.051129 degrees because the projection becomes infinitely wide at the poles. (I've submitted an edit correcting this, since it's not the core of the question.)
    – jpmc26
    Feb 18, 2016 at 18:56

8 Answers 8

52

If the core of your question is this...

If some client code passes an argument whose value is invalid for the thing that my data structure is modeling, should I reject the value or convert it to something sensible?

...then my general answer would be "reject", because this will help draw attention to potential bugs in the client code that are actually causing the invalid value to appear in the program and reach your constructor. Drawing attention to bugs is generally a desired property in most systems, at least during development (unless it's a desired property of your system to muddle through in case of errors).

The question is whether you're actually facing that case.

  • If your data structure is intended to model polar coordinates in general, then accept the value because angles out of the -180 and +180 range aren't really invalid. They are perfectly valid and they just happen to always have an equivalent that's within the range of -180 and +180 (and if you want to convert them to target that range, feel free - the client code doesn't usually need to care).

  • If your data structure is explicitly modeling Web Mercator coordinates (according to the question in its initial form), then it's best to follow any provisions mentioned in the specification (which I don't know, so I won't say anything about it). If the specification of the thing you're modeling says that some values are invalid, reject them. If it says that they can be interpreted as something sensible (and thus they're actually valid), accept them.

The mechanism you use to signal whether the values were accepted or not depends on the features of your language, its general philosophy and your performance requirements. So, you could be throwing an exception (in the constructor) or returning a nullable version of your struct (through a static method that invokes a private constructor) or returning a boolean and passing your struct to the caller as an out parameter (again through a static method that invokes a private constructor), and so on.

6
  • 12
    A longitude outside the range -180 to +180 should probably be considered acceptable, but latitude outside the range -90 to +90 would seem nonsensical. Once one reaches the North or South Pole, further travel north or south is undefined.
    – supercat
    Feb 18, 2016 at 15:55
  • 4
    @supercat I tend to agree with you, but since I don't know which values are actually invalid in the Web Mercator specification, I'm trying not to draw any hard conclusions. The OP knows the problem domain better than I do. Feb 18, 2016 at 16:16
  • 1
    @supercat: start where meridian reaches equator. travel along the meridian according to the latitude. If latitude is > 90 then just keep going along the same great circle. No problem. Document it and leave the rest to the client. Feb 18, 2016 at 19:08
  • 4
    @supercat It's worse than that. In the Web Mercator projection, ±90 actually gets projected to an infinite width. So the standard actually cuts it off at ±85.051129. Also, lat/long != web mercator coordinates. Mercator coordinates use a variation of meters, not degrees. (As you get closer to the poles, each "meter" actually corresponds to a larger and larger piece of ground.) The OPs coordinates are purely lat/long. Web Mercator has nothing to do with them, other than they might be displaying on top of a Web Mercator base map and some spatial library is projecting under the hood for them.
    – jpmc26
    Feb 18, 2016 at 19:13
  • 1
    I should say "the equivalent of ±85.051129," since that's not the actual coordinate.
    – jpmc26
    Feb 18, 2016 at 20:54
10

It depends a lot. But you should decide to do something and document it.

The only definitively wrong thing for your code to do is to forget to consider that user input might be outside the expected range, and write code that accidentally has some behaviour. Because then some people will make an incorrect assumption about how your code behaves and it will cause bugs, while others will end up depending on the behaviour your code accidentally has (even if that behaviour is completely bonkers) and so you'll cause more bugs when you later fix the problem.

In this case I can see arguments either way. If someone travels +10 degrees from 175 degrees, they should end up at -175. If you always normalise user input and so treat 185 as equivalent to -175 then client code can't do the wrong thing when it adds 10 degrees; it always has the right effect. If you treat 185 as an error you force every case where client code is adding relative degrees to put in the normalisation logic (or at least remember to call your normalisation procedure), you'll actually cause bugs (though hopefully easy to catch ones that will be quickly squashed). But if a longitude number is user-entered, written literally in the program, or calculated through some procedure intended to always be in [-180, 180), then a value outside that range is very likely to indicate an error, so "helpfully" converting it could hide problems.

My ideal in this case would probably be to define a type that represents the correct domain. Use an abstract type (don't let client code simply access the raw numbers inside it), and provide both a normalising and a validating factory (so the client can make the tradeoff). But whatever a value of this type is made, 185 should be indistinguishable from -175 when seen through your public API (doesn't matter whether they're converted on construction or you provide equality, accessors and other operations that ignores the difference somehow).

3

If it does not really matter to you to choose one solution, you could just let the user decide.

Given your struct is readonly value object and created by a method/constructor, you could provide two overloads based on the options the user have:

  • Throw an exception (arg out of range)
  • Convert the value to the constraint

Also never let the user have an invalid struct to pass to your other methods, make it right on creation.

Edit: based on the comments, I assume you are using c#.

5
  • Thanks for the input! I'll think about it, though I'm afraid that it would undermine the purpose of the exception throwing constructor, if one could just avoid it. It's interesting though!
    – RaidenF
    Feb 18, 2016 at 12:32
  • If you are going to use this idea, it would be better to define an interface and have two implementations that throw or convert. It would be difficult to overload a constructor in a sensible way to work as you described.
    – user22815
    Feb 18, 2016 at 16:06
  • 2
    @Snowman: Yeah, it'd be difficult to overload a constructor with the same argument types, but it wouldn't be difficult to have two static methods and a private constructor.
    – wchargin
    Feb 18, 2016 at 16:10
  • 1
    @K.Gkinis: The purpose of the exception throwing constructor isn't "make sure the application dies"—after all, the client can always catch your exceptions. As others have said, it's to allow the client to restrict itself if it so desires. You're not really circumventing anything.
    – wchargin
    Feb 18, 2016 at 16:11
  • This suggestion complicates the code for no benefit. Either the method should convert invalid input or it shouldn't. Having two overloads makes the code more complex without solving the dilemma.
    – JacquesB
    Feb 23, 2016 at 7:36
0

It depends if the input is directly from a user through some UI, or it is from the system.

Input through an UI

It is a user experience question how to handle invalid input. I don't know about your specific case, but in general there are a few options:

  • Alert the user to the error and have the user fix it before proceeding (Most common)
  • Automatically convert to the valid range (if possible), but alert the user to the change and allow the user to verify before proceeding.
  • Silently convert to the valid range and proceed.

The choice depends on the expectations of you users and how critical the data is. For example Google automatically fixes spelling in queries, but this is low risk because a unhelpful change is not a problem and is easy to fix (and even then it is made clear on the result page that the query was changed). On the other hand, if you are entering coordinates for a nuclear missile you might want a more rigid input validation and no silent fixes of invalid data. So there is no universal answer.

Most importantly, you should consider if correcting input even has a benefit for the user. Why would a user enter invalid data? It is easy to see how someone might make a spelling error, but why would anyone enter a longitude of -185? If the user really meant +175 they would probably have typed +175. I think it is most likely that an invalid longitude simply is a typing error, and the user meant -85 or something else. In this case silently converting is bad and unhelpful. The most user friendly approach for you app would probably be to alert the user to the invalid value, and have the user correct it themselves.

Input through an API

If the input is from another system or subsystem, there is no question. You should throw an exception. You should never silent convert invalid input from another system, since it might mask errors elsewhere in the system. If input is "corrected" it should happen in the UI layer, not deeper into the system.

0

You should throw an exception.

In the example you gave, sending 185 and converting that to -175, then it might be handy in some cases to provide that functionality. But what if the caller sends 1 million? Do they really mean to convert that? It seems more likely that is an error. So if you need to thrown an exception for 1,000,000 but not for 185, then you have to make a decision about an arbitrary threshold for throwing an exception. That threshold is going to trip you up sometime as some calling app is sending values around the threshold.

Better to throw the exception for values out of the range.

-1

The most convenient option for a developer would be a compile time error support in the platform, for values out of range. In this case, the range should also be part of method signature, just like the type of the parameters. The same way your API user cannot pass a String if your method signature is defined to take an integer, user should not have been able to pass a value without checking if the value is within the range given in the method signature. If not checked, he should get a compile time error and thus, runtime error could be avoided.

But currently, very few compilers/platforms support this type of compile time checking. So, it's a developers headache. But ideally, your method should just throw a meaningful exception for unsupported values and document it clearly.

BTW, I really love the error model proposed by Joe Duffy here.

5
  • How would you provide compile time errors for user input?
    – JacquesB
    Feb 23, 2016 at 7:56
  • @JacquesB The compiler will issue an error if the user input is not checked to be within the range after insertion, regardless of the actual value being within the range.
    – Gulshan
    Feb 23, 2016 at 8:42
  • But then you still have to decide if you want to reject the input or convert to a valid range, so this does not answer the original question.
    – JacquesB
    Feb 23, 2016 at 13:24
  • @JacquesB I should say this, at first- I always thought that, OP is developing an API and UI is developed by someone else, consuming his API. I was wrong about this point. I just read the question again and realized this. All I am trying say is, validation should be done at API consumers and if not, compiler should throw an error.
    – Gulshan
    Feb 23, 2016 at 15:25
  • So... you need to make a new class that contains the inputs and validates. Annnnnd when you construct the class object from the raw inputs, what happens? Throw an exception? Silently pass? You just move the problem.
    – djechlin
    Feb 23, 2016 at 18:08
-1

Default should be to throw an exception. You could also allow an option like strict=false and do the coercion based on the flag, where of course strict=true is default. This is fairly common:

  • Java DateFormat supports lenient.
  • Gson's JSON parser also supports a lenient mode.
  • Etc.
3
  • This complicates the code for no benefit.
    – JacquesB
    Feb 23, 2016 at 7:32
  • @JacquesB throwing exception by default, or allowing strict=false?
    – djechlin
    Feb 23, 2016 at 18:06
  • @JacquesB I added some example of APIs that support strict and lenient modes, please take a look.
    – djechlin
    Feb 23, 2016 at 18:11
-2

For me best practice is to never change user input. The approach I usually take is to separate validation from execution.

  • Have a simple class that just uses the given parameters.
  • Use a decorator to provide a layer of validation that can be altered at will without affecting the execution class (or else inject a validator if this approach is too difficult).
1
  • The "user" the question is referring to is the developer using the API, not the end user...
    – Jay Elston
    Feb 24, 2016 at 1:34

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