Two Designs for SequenceIdGenerator

In my previous, marathon post on id generation, I mentioned that I was generating unique sequence numbers. It was straightforward to write a class to generate them:

class SequenceIdExhaustion < RangeError ; end

class SequenceIdGenerator # has 14 bits allocated to it in the CallNumber MAX_SEQUENCE_ID = 2 ** 14 - 1

def initialize reset! end

def reset! @sequence_id = 0 end

def sequence_id raise SequenceIdExhaustion if @sequence_id > MAX_SEQUENCE_ID @sequence_id end

def consume_sequence_id! sid = sequence_id increment! sid end

private

def increment! @sequence_id += 1 sequence_id end end

Simple interface: start at zero, a method to consume an id, a method to reset the generator, and a reader to get the id. All while making sure to raise an appropriate exception when the generator has moved into an invalid state.

@pushcx tweet: I seem to be settling on a ruby style where ! indicates methods with side effects{.aligncenter .size-full .wp-image-2205 width=”589” height=”99”}

This object is all about maintaining state, so it’s mostly bang methods. That aside, I didn’t love this implementation.

There’s not really any need for the sequence_id getter because that state of “what sequence id are you at now?” doesn’t and shouldn’t matter to any code. And that temporary variable in consume_sequence_id! is just clunky.

So, second pass:

class SequenceIdGenerator # has 14 bits allocated to it in the CallNumber MAX_SEQUENCE_ID = 2 ** 14 - 1

def initialize reset! end

def reset! @sequence_id = -1 end

def consume_sequence_id! increment! @sequence_id end

private

def increment! @sequence_id += 1 raise SequenceIdExhaustion if sequence_id > MAX_SEQUENCE_ID end end

This has a smaller public interface, which is generally a win. I’ve cleaned up the that temporary variable in consume_sequence_id! in an odd way: by starting in an invalid state.

No one from the outside can see that sequence_id starts invalid, but this is still not OK. Every method potentially needs to account for this. Alternately I could restore the sequence_id getter and have it raise if it’s accessed before consume_sequence_id! has moved it to a valid state, but having a to protect a class from itself just feels wrong.

Here’s a change to remove that temporary variable nicely:

def reset! @sequence_id = 0 end

def consume_sequence_id! @sequence_id.tap { increment! } end

This uses the K combinator. It used to be implemented in Rails as returning, then migrated into Ruby 1.9 as tap. It’s usually used with the value passed into the block, but works fine without.

After that last long post, I wanted to share this nice little bit of OO design. This keeps state hidden while not succumbing to the temptation of allowing hidden state to be invalid.