While pulling the images from an Exif formatted Canon camera flash memory card I noticed this off-by-one error in the way the images are stored.
This is an interesting bug. Think about the code behind this problem for a moment. If we were writing this from scratch, we might start like this:
[Test] public void Should_increment_the_image_number() { _imageNumber = 0; int next = GetNextImageNumber(); next.ShouldBeEqualTo(1); _imageNumber.ShouldBeEqualTo(1); } [Test] public void Should_reset_the_image_number_to_1_given_9999() { _imageNumber = 9999; int next = GetNextImageNumber(); next.ShouldBeEqualTo(1); _imageNumber.ShouldBeEqualTo(1); }
with implementation
private volatile int _imageNumber; private int GetNextImageNumber() { _imageNumber = _imageNumber + 1; if (_imageNumber > 9999) { _imageNumber = 1; } return _imageNumber; }
Next we’ll want to handle incrementing the directory number.
[Test] public void Should_increment_the_directory_number() { _directoryNumber = 100; IncrementDirectoryNumber(); _directoryNumber.ShouldBeEqualTo(101); } [Test] public void Should_reset_the_directory_number_to_100_given_999() { _directoryNumber = 999; IncrementDirectoryNumber(); _directoryNumber.ShouldBeEqualTo(100); }
with implementation
private volatile int _directoryNumber; private void IncrementDirectoryNumber() { _directoryNumber = _directoryNumber + 1; if (_directoryNumber > 999) { _directoryNumber = 100; } }
Now when the image number crosses to the next 100 we have to increment the directory number.
[Test] public void Should_increment_the_directory_number_when_the_image_number_goes_to_the_next_hundred() { _imageNumber = 99; _directoryNumber = 100; int next = GetNextImageNumber(); next.ShouldBeEqualTo(100); _imageNumber.ShouldBeEqualTo(100); _directoryNumber.ShouldBeEqualTo(101); } private int GetNextImageNumber() { if (_imageNumber % 100 == 99) { IncrementDirectoryNumber(); } _imageNumber = _imageNumber + 1; if (_imageNumber > 9999) { _imageNumber = 1; } return _imageNumber; }
And lastly we need to build the file path.
[Test] public void Should_get_path__100CANON_IMG_0002__starting_with_directory_number_100_and_image_number_1() { _directoryNumber = 100; _imageNumber = 1; var path = GetNextImagePath(); path.ShouldBeEqualTo(@"100CANON\IMG_0002"); _directoryNumber.ShouldBeEqualTo(100); _imageNumber.ShouldBeEqualTo(2); } public string GetNextImagePath() { return String.Format( @"{0:000}CANON\IMG_{1:0000}", _directoryNumber, GetNextImageNumber()); }
All tests pass so we’re done right? Nope. The following test fails (as expected) given the values from my image above.
[Test] public void Should_get_path__320CANON_IMG_2000__starting_with_directory_number_319_and_image_number_1999() { _directoryNumber = 319; _imageNumber = 1999; var path = GetNextImagePath(); path.ShouldBeEqualTo(@"320CANON\IMG_2000"); _directoryNumber.ShouldBeEqualTo(320); _imageNumber.ShouldBeEqualTo(2000); }
Did you cringe/notice when we wrote the code that introduced the bug? If not, you might find the answers to this stack exchange question enlightening.
There are two routes to fixing this problem, the easy way and the correct way. The easy way is:
public string GetNextImagePath() { var imageNumber = GetNextImageNumber(); return String.Format( @"{0:000}CANON\IMG_{1:0000}", _directoryNumber, imageNumber); }
The correct way is to remove the side effect so that the method becomes:
public string GetNextImagePath() { IncrementImageNumber(); return String.Format( @"{0:000}CANON\IMG_{1:0000}", _directoryNumber, _imageNumber); }
… a coding problem you might use as a refactoring TDD Kata.