Say I have a method like this*:
public T GetItem(int index)
{
if (index < 0 || index >= _privateList.Count)
{
throw new ArgumentOutOfRangeException("index");
}
return _privateList[index];
}
Would you include that throw or leave it out? On the one hand I feel like it’s good to handle invalid input right away, since this allows you to feel more confident with what you’re dealing with when writing the actual implementation code. But in this case, if the bounds check were omitted, the end result would be much the same (the list would throw the ArgumentOutOfRangeException instead of the enclosing type).
Furthermore, since the above code is checking for something that I, the developer, know will be checked for anyway (in the call to _privateList[index]), it seems I’m making the code do more work than it needs to above, essentially performing the exact same set of comparisons twice.
Any guidance on this subject would be appreciated.
*Specifically I’m talking about .NET (C#), but I would imagine the same or a similar question could be asked of many different languages/frameworks—hence the “language-agnostic” tag.
This is the kind of code you’ll find back in the .NET framework. It is rather important there, source code is (was) not readily available. If the argument check were not included, you’d get an exception that is hard to diagnose. It bombs on invisible code, concluding that the argument was wrong is not exactly easy.
This is less of a concern with code that you write. But not absent, it depends on who will be the end-user of the code. If that is you, and you maintain the code, then you’ll have little trouble diagnosing the cause. If it is somebody else then it becomes important that they have ready access to an accurate copy of the source code as well. If that is muddled at all then don’t hesitate to include the check.
Another consideration is what happens when you deploy the Release build of your code. Without the throw statement, it is quite likely that this particular code will be inlined. In other words, you won’t see the GetItem() method on the top of the stack. This can make finding the real source of the exception more difficult, especially since release stack traces don’t have line numbers.
Then the cost is relevant. It is quite low in this case, maybe a nanosecond or two if the Count property is cheap enough. But the actual work done by the method is very cheap as well. Your test makes it easily 20% slower. It is the kind of method that could very well live deeply nested on the critical path of the program. The more the method does, the less relevant the extra test becomes.