A team was proposing a method that was at its essence a Read(n)
that takes the maximum number of items to read. The reality was more complicated than that: The parameter n was really a configuration property on an operation, but it had the same meaning: It set the maximum number of items to return.
The team said that their method returns E_
if you pass n = 0.
I argued that this is an incorrect design: If somebody asks for “at most zero items”, then you should succeed and give them zero items. Zero is at most zero.
For example, maybe the program calculates the size of its window, divides by the height of an item, and requests only as many items as fit in the window without overflowing. After all, there’s no point fetching data that you never use.
And then the user resizes the window so small that no items fit, so the division rounds down to zero, and the program asks for zero items and crashes because “somebody” decided that it was wrong to ask for zero items.
Let them ask for zero items. Give them nothing.
Edge cases are hard, so remove edge cases from the interface.
Related reading: Embracing the power of the empty set in API design (and applying this principle to selectors and filters).
This is exactly why LINQ methods in C# are wrong to penalize callers with a NullReferenceException when passed a null source. Null is the most efficient version of an empty collection, and if you have to check it everywhere, you can’t really write fluent call chains efficiently. Any method that implicitly converts a null input to a non-null empty collection would be a blessing to any business software I’ve ever worked with.
An empty collection is not the same as no collection.
Instance-like methods are intended to be called on non-null targets in C#, so any extension method should mimic that behavior unless specifically designed for the other case. Linq methods can be implemented on the instance and not from an extension, so throwing on null is the consistent choice.
Yes, far too many developers feel a strange compulsion to treat empty collections as a special case... returning null instead of empty if nothing is found, or checking if a collection is empty before iterating over it, that sort of thing. It's pointless, creating extra work for no benefit.
That said, your example does hint at one case where such sanity checks are meaningful - pagination. If I ask for a page of size 0, the obvious corollary is that I want to know how many such pages of data there are... at which point we're back to division-by-zero, and...
I’d argue that return an error on zero is the correct behaviour, it’s notifying the caller that they have a bug in their code. No-one goes to Amazon and orders zero batteries, or asks for zero litres of petrol at the pump, or goes to a takeaway to place an order for zero burgers, so passing in zero to a function like this should alert the caller via an error return, not continue as if nothing unusual was going on.
“No-one…goes to a takeaway to place an order for zero burgers”
But people do go to, say, Starbucks and order zero drinks (if you’re meeting somebody there for an interview).
Computer programs are not human beings. Analogies of what a person would or wouldn't do don't really work. Would you crash and burn if something unexpected happened in your life?
You may have missed it, but Raymond has given an example in the post where asking for zero items is not a bug. Whenever your caller arrives at your parameter value via a calculation, they may happen to arrive at one of the edge cases.
To put it in different terms: you either handle a request for zero items yourself, or you force all your callers to do their own zero checks...
Completely agree about eliminating edge cases and aggravating exceptions/errors like that, but following convention is more important. For example, in many APIs a limiting value of 0 means "unlimited" so if that's the case here then this method should behave the same.
Of course, sometimes -1 means unlimited. Or any negative number. Or you're supposed to use . Or . Etc, etc. One of my favorites is the conflict in .NET between and .
I like having an explicit way to signal that a response should be unlimited, but admit that just using a Very...
A slight issue with this are other method contracts. For example when reading from a byte stream, the method could promise to return 0 if and only if the stream is at the end, so either it reads at least one element or indicates the end by returning 0. That is incompatible with reading 0 bytes, so you end up with an edge case either way.