I can pass the initializer a negative
size. Or a non-numeric
size. I won't get any complaints until I try to use the buffer.
If I use
write! exclusively, the buffer will always be empty. You're always shifting an element off of the array - even if it's the one you just added.
It's impossible to write
false to the buffer, though either of those are values someone might legitimately want to write.
But even if you manage to write a false'y value, you can't get it back; you'll just get a
BufferEmptyException even if the buffer's not empty. Raise if the array's truly empty, and do so before you shift; not if the value you already shifted off is false'y. And, worse yet, the value is just gone forever. You shifted it off the array, so it's just disappeared. Since you can just use the array's length to tell you the state of the buffer, it's no problem to store false'y values.
It's generally good practice to define a non-bang method in terms of its bang counterpart. Right now you have 2 separate places where things are added to the buffer, instead of having one method call the other, just with an extra check.
Speaking of, it'd be nice to have a bang version of
read that doesn't raise exceptions.
It'd be nice to have an
attr_reader for the size, and a
to_a for the items.
It'd also be nice to have a
full? method. Otherwise you'll always have to either rescue an exception from
write, or exclusively use
write!. Besides, you could use the same
full? method within the class too.
empty? method would be nice to complement the
full? method. While you're at it, you might even add a method that returns the number of slots left in the buffer, and/or one that that returns the number of slots occupied.
Of course, it's not quite a circular buffer since, for one, it's not a perfectly fixed size. If
write! worked as intended, and only shifted off items when necessary, it'd still (in it's current form) shift them off only after the buffer's "overflowed".
For another, it's not actually circular. You don't end up overwriting previously set slots. Instead you push and shift and array that expands up to a certain size. From the outside, it might well look like a circular buffer, but none of its internals match that description.
What you have is a general (almost) fixed-length (or rather "limited-length") FIFO buffer, but I wouldn't call it circular. But then again, implementing a strictly circular buffer in Ruby isn't really that useful. A FIFO buffer has its uses, sure, but a strictly circular one seems like trying to do low-level programming in a very high-level language. It's a technique that's useful when you have to allocate memory, and it's cool when you can do pointer arithmetic. But you don't allocate memory in Ruby, and you can't do pointer manipulation, so the arguments and tools for creating efficient buffers aren't really there.
It's like camping in you own back yard. It's fun to try, but it's not survivalism (and it'd probably be kinda dangerous to make a campfire).
But hey, it is fun
class BufferEmptyException < StandardError; end;
class BufferFullException < StandardError; end;
@size = size.to_i # still lacking a check for values below 1
@buffer = 
@buffer.size >= size
raise CircularBuffer::BufferFullException if full?
@buffer.shift if full? # maybe use read! instead and return the value?
@buffer << data
raise CircularBuffer::BufferEmptyException if empty?