3

(The question is language-agnostic, but to illustrate the point, I wrote the examples in C#.)

Say I'm trying to model the behavior of "components" that communicate via "ports".

I might model this as:

public interface Component
{
    int  Read(int portID, byte[] buffer);
    int Write(int portID, byte[] buffer);
}

and perhaps implement the interface as:

public class MyComponent : Component
{
    private class Port
    {
        int  Read(byte[] buffer) { ... }
        int Write(byte[] buffer) { ... }
        void DoSomethingComplex()
        {
            this.Read(...);
            // ...
            this.Write(...);
        }
    }
    private Dictionary<int, Port> ports;
    public override int  Read(int portID, byte[] buffer) { ports[portId]. Read(buffer); }
    public override int Write(int portID, byte[] buffer) { ports[portId].Write(buffer); }
}

Simple enough, but consider what you would expect Read and Write to do:
From the perspective of an outsider, it seems natural for Read to read data from the port of the component into the given buffer. However, as an insider who is calling this.Read(...), is that really what you expect? Or would you expect the data to be read from the buffer into the port?

My question is twofold:

  1. Given no additional documentation, which meaning of Read and Write is more intuitive?

  2. How can I reasonably clearly name these functions so that the intent is clear without the need for additional documentation?
    (Obviously, I can name them ReadFromPortIntoBuffer and WriteToPortFromBuffer or something like that, but one hopes for more succinct names.)

  • 2
    It should behave just like the System.IO.Stream does on the respective platforms, from the perspective of the API users, . Questions? (Keep in mind the portID parameter is just an additional parameter; you could have exposed the Port class; though there are plenty of other reasons not to expose it as well.) – rwong Apr 12 '15 at 6:32
  • @rwong That should be an answer. – Philip Kendall Apr 12 '15 at 7:18
  • There's no reason you have to give the methods inside Port the same names as the methods inside MyComponent. – Ben Aaronson Apr 12 '15 at 8:07
  • @BenAaronson: I guess, but that's neither the real problem here nor can I think of better names. The problem still exists inside the other methods in MyComponent and in derived classes, doesn't it? – Mehrdad Apr 12 '15 at 8:21
  • @Mehrdad: To adequately answer this question, you have to list exhaustively all of the differences that MyComponent differs from a generic System.IO.Stream - not just the API methods, but all of the subtleties that could differ. For example, System.IO.Stream assumes it is safe to break up a large request into smaller requests, and that anyone could cache (latch) its data operations (pretending data has been written through, while in effect none of that reaches the destination). The possible differences are so many that we can't imagine and ask you; you have to tell it. – rwong Apr 12 '15 at 8:42
4

Naming things is hard. But in my experience, ambiguity like this can almost always be solved by using a slightly more verbose name in the right place. The alternative method names you suggested are indeed ugly; in this example my first choice would be to rename the parameters:

public interface Component
{
    int  Read(int portID, byte[] outputBuffer);
    int Write(int portID, byte[] inputBuffer);
}

Though as @rwong pointed out, if this particular example is real code, the goal should be to resemble a standard C# Stream as much as possible.

As for which "direction" is more intuitive for Read/Write, I don't believe either one is. In fact, my personal preference would be a Read method that returns the stuff it just read, and no "output parameters" in either method (imo those are just an ugly workaround for the lack of efficient/complex/multiple return values in many languages). I'm sure you could find loads of people who would assert that reading to or reading from are the "obvious" default meanings, depending on what Streams/Readers/Writers/languages they're most familiar with.

  • +1. Regarding your comment: returning the data is ideal, except that it's a huge performance hit to allocate a new buffer every time, so it's not done in practice (as you can tell in Stream). – Mehrdad Apr 12 '15 at 9:01
  • @Mehrdad Agreed, which is one reason why I mentioned "efficient" in that remark. I know C++ recently got much better about this as move semantics make it acceptable to simply return a whole STL container, though I have no idea if the buffer reallocation issue is one that can ever be automagically optimized away in any language. – Ixrec Apr 12 '15 at 9:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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