The samples in question are c#, but it applies to any language.
If you have a function which reads content from a stream. Who should be responsible for ensuring that the stream position is correct before reading?
The caller, or the function doing the reading?
e.g.
Caller positions the stream:
public byte[] ReadBytesFromStream(Stream inputStream)
{
using (var ms = new MemoryStream())
{
inputStream.CopyTo(ms);
return ms.ToArray();
}
}
public void DoThingWithStream(Stream stream)
{
// .. do some stuff
stream.Position = 0;
var bytes = ReadBytesFromStream(stream);
// .. do some more stuff
}
Reader positions the stream:
public byte[] ReadBytesFromStream(Stream inputStream)
{
inputStream.Position = 0;
using (var ms = new MemoryStream())
{
inputStream.CopyTo(ms);
return ms.ToArray();
}
}
public void DoThingWithStream(Stream stream)
{
// .. do some stuff
var bytes = ReadBytesFromStream(stream);
// .. do some more stuff
}
The reader positioning the stream means that I can call the function over an over again with the same stream and get the same bytes out.
The caller positioning the stream means that
- It’s a little more work to set up and if you’re throwing the stream around a lot there may be a lot of re-positioning going on. But,
- I could use the same reader function to read from an arbitrary point in the stream if I really wanted to.
I’ve pretty much convinced myself that the caller should do the positioning, but if that’s the case then are there any circumstances when it shouldn’t?
1
The caller is responsible for putting the Stream in a suitable state to be consumed by the reader. There are two main reasons:
-
Not all streams support repositioning. You should only set the
Stream.Position
property if thatStream.CanSeek
. Otherwise, you’ll get an exception. (see theStream.Position
documentation). -
“Reading from the beginning” is a special case of simply “reading”. It is generally advisable to write your functions as general as possible (as far as it’s sensible, compare also YAGNI). If we frequently need a special case, we can always add a convenience wrapper with very little effort. However, getting a general function out of a special-cased function is not possible (or requires refactoring of the code).
Especially when creating a public interface is it important to make this interface composable. One possibility would be to pass in the wanted location as a parameter. This works in general, but not in this specific case because not every
Stream.CanSeek
.
And then there’s also the matter of avoiding “spooky action at a distance”. It’s irrelevant what your function does as long as the effects are contained inside that function. However, mutating non-local state can lead to hard to locate bugs. If functions have side effects or unexpectedly mutate their arguments, this should perhaps be reflected in their name, but certainly in their documentation. In this case, it’s expected that the reader function will advance the position by reading from the stream. However, it’s probably not expected that the reader will reset the position first.
If we work backwards from that, we can find a few cases where the reader method should set the position:
-
In the context of your application, you’ll only ever want to read streams from the beginning, and read the same stream multiple times. You’ve documented the behaviour that, as a convenience, the reader method will reset the position first.
-
The method is private. That’s a carte blanche to ignore any argument referring to “composability”, since you control all uses of that method.