25

I am creating an object model for a device that has multiple channels. The nouns used between the client and I are Channel and ChannelSet. ("Set" isn't semantically accurate, because it's ordered and a proper set isn't. But that's a problem for a different time.)

I'm using C#. Here is a usage example of ChannelSet:

// load a 5-channel ChannelSet
ChannelSet channels = ChannelSetFactory.FromFile("some_5_channel_set.json");

Console.Write(channels.Count);
// -> 5

foreach (Channel channel in channels) {
    Console.Write(channel.Average);
    Console.Write(",  ");
}
// -> 0.3,  0.3,  0.9,  0.1,  0.2

All is dandy. However, the clients are not programmers and they absolutely will be confused by zero indexing - the first channel is channel 1 to them. But, for the sake of consistency with C#, I want to keep the ChannelSet indexed from zero.

This is sure to cause some disconnects between my dev team and the clients when they interact. But worse, any inconsistency in how this is handled within the codebase is a potential problem. For example, here's a UI screen where the end user (who thinks in terms of 1 indexing) is editing channel 13:

Channel 13 or 12?

That Save button is eventually going to result in some code. If ChannelSet is 1 indexed:

channels.GetChannel(13).SomeProperty = newValue;  // notice: 13

or this if it's zero indexed:

channels.GetChannel(12).SomeProperty = newValue;  // notice: 12

I'm not really sure how to handle this. I feel like it's good practice to keep an ordered, integer-indexed list of things (the ChannelSet) consistent with all of the other array and list interfaces in the C# universe (by zero indexing ChannelSet). But then every piece of code between the UI and the backend will need a translation (subtract by 1), and we all know how insidious and common off-by-one errors already are.

So, has a decision like this ever bitten you? Should I zero index or one index?

kdbanman
  • 1,447

8 Answers8

35

Show the UI with index 1, use index 0 in code.

That said, I worked with audio devices like this and used index 1 for the channels and designed the code never to use "Index" or indexers to help avoid frustration. Some programmers still complained, so we changed it. Then other programmers complained.

Just pick one and stick with it. There are bigger problems to solve in the grand scheme of getting software out the door.

Telastyn
  • 110,259
24

It feels like you're conflating the Identifier for the Channel with its position within a ChannelSet. The following is my visualisation of how your code/comments would look at the moment :

public sealed class ChannelSet
{
    private Channel[] channels;
    /// <summary>Retrieves the specified channel</summary>
    /// <param name="channelId">The id of the channel to return</param>
    Channel GetChannel(int channelId)
    {
        return channels[channelId-1];
    }
}

It does feel like you've decided that because Channels within a ChannelSet are identified by numbers that have an upper and lower bound they must be indexes and therefore as it's C#, 0 based. If the natural way to refer to each of the channels is by a number between 1 and X, refer to them by a number between 1 and X. Don't try and force them into being indexes.

If you really want to provide a way to access them by 0 based index (what benefit does this give your end user, or developers that consume the code?) then implement an Indexer:

public sealed class ChannelSet
{
    private Channel[] channels;
    /// <summary>Retrieves the specified channel</summary>
    /// <param name="channelId">The id of the channel to return</param>
    public Channel GetChannel(int channelId)
    {
        return channels[channelId-1];
    }

    /// <summary>Return the channel at the specified index</summary>
    public Channel this[int index]
    {
        return channels[index];
    }
}
Rob
  • 442
21

Use both.

Do not mix the UI with your core code. Internally (as a library) you should code "without knowing" how each element on the array will be called by the final user. Use the "natural" 0-indexed arrays and collections.

The part of the program that joins the data with the UI, the view, should take care to correctly translate data between the User's Mental Model and the 'Library' that does the actual work.

Why is this better?

  • The code can be cleaner, no hacks to translate indexing. This also helps the programmers by not expecting them to follow and remember to use unnatural conventions.
  • The users will use the indexing that they expect. You don't want to annoy them.
Dietr1ch
  • 376
12

You can see the collection from two different angles.

(1) It is, in the first place, a regular sequential collection, like an array or a list. Index from 0 is obviously the right solution then, following the convention. You allocate enough entries and map channel number to indices, which is trivial (just subtract 1).

(2) You collection is essentially a mapping between channel identifiers and channel info objects. It just happens that channel identifiers is a sequential range of integers; tomorrow it might be something like [1, 2, 3, 3a, 4, 4.1, 6, 8, 13]. You use this ordered set as mapping keys.

Choose one of the approaches, document it, and stick to it. From flexibility standpoint, I'd go with (2), because the representation of channel number (at least the displayed name) is relatively likely to change in the future.

9000
  • 24,342
3

Everybody and their dog uses zero based indexes. Using one-based indexes inside your application will cause you maintenance problems forever.

Now what you display in a user interface, that is entirely up to you and your client. Just display i+1 as the channel number, together with channel #i, if that makes your client happy.

If you expose a class, then you expose it to programmers. People you are confused by a zero-based index are not programmers, so there is a disconnect here.

You seem to worry about the conversion between UI and code. If that worries you, create a class carrying the user interface representation of a channel number. One call turning the number 12 into the UI representation "Channel 13", and one call turning "Channel 13" into the number 12. You should do that anyway, in case the customer changes their mind, so there are just two lines of code to change. And it works if the customer asks for roman numerals, or letters A to Z.

gnasher729
  • 49,096
3

You are mixing up two concepts: indexing and identity. They are not the same thing and should not be confused.

What is the purpose of indexing? Fast random access. If the performance isn't an issue, and given your description it isn't, then using a collection that has an index is unnecessary and probably accidental.

If you (or your team) is being confused by the index vs the identity, then you can solve that problem by not having an index. Use either a dictionary or an enumerable and get the channel by value.

channels.First(c=> c.Number == identity).SomeProperty = newValue;
channels[identity].SomeProperty = newValue;

The first is more clear, the second is shorter -- they may or may not get translated into the same IL, but either way, given you are using this for a dropdown, it should be fast enough.

jmoreno
  • 11,238
2

You are exposing an interface through your ChannelSet class that you expect your users to interact with. I would make it as natural for them to use as possible. If you expect your users will start counting from 1 instead of 0, then expose this class and its usage with that expectation in mind.

Bernard
  • 8,869
2

0-based indexing was popular, and defended by important people, because the declaration shows the size of the object.

With modern computers, with the computers your users will be using, is the size of the object important at all?

If your langauge (C#) doesn't support a clean way of declaring the first and last element of an array, you can just declare a bigger array, and use declared constants (or dynamic variable) to identify to the logical start and end of the active area of the array.

For UI objects, you can almost certainly afford to use a dictionary object for your mapping, (if you've got 10 million objects, you've got a UI problem), and if the best dictionary object turns out to be a list with wasted space at either end, you haven't lost anything important.

david
  • 275
  • 1
  • 3