Let me just start by stating what I mean by a Bug Generator. It's a style of code that is likely to contain bugs or produce bugs in the future as the code is extended. This is of course an anti-pattern. There are corresponding Prevention Patterns, basically less error-prone alternatives. That being said, let's dive into the Bug Generator Use-of-indices.
Use of indexed access is low level, off-by-one errors are very common. Also index out of bounds can happen. Usually we can simply avoid this by using higher level constructs such as for-each-loops and higher-order functions such as map and filter.
Bug: Off-by-one errors
We access the data by some index. The problem can range from some simple error in a loop construct. For instance we use <= instead of < in loop. To harder-to-spot errors like passing the index to some function and using the index while modifying it by a -1 or +1.
A basic example of this. Say we need to find the first cat in a list of animals. The bug will only occur when there is no cat in the collection
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let foundAnimal | |
let i = 0 | |
while (i <= animals.length && !foundAnimal) { | |
if (animals[i].type === 'cat') { | |
foundAnimal = animals[i] | |
} | |
} |
Bug: Index-out-of-bounds
Variation of the above bug. This is off-by-one where we actually step outside the array.
Prevention pattern: use lambdas and higher-order-functions
higher order functions like map, filter, some/first, etc have already implemented the looping and we just have to provide a function that the higher order function will use on each element.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let isCat = animal => animal.type === 'cat' | |
let foundAnimal = animals.some(isCat) |
Prevention pattern: for-each-loop
If we really must use imperative style
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let foundAnimal | |
for (animal in animals) { | |
if (animal.type === 'cat') { | |
foundAnimal = animal | |
break; | |
} | |
} |
Prevention pattern: link the structure length to the index calculation
E.g. modulo on length. An example would be picking cards from a deck. Lets assume that whenever we get to the end of the deck we're supposed to start from the beginning. A naïve implementation would be to
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
cardIndex = cardIndex + someStep | |
if (cardIndex > cards.length) { | |
cardIndex = 0 | |
} | |
let card = cards[cardIndex] | |
// Use of a modulo would be simpler and less error-prone. | |
cardIndex = (cardIndex + someIndex) % deckOfCards.length | |
let card = deckOfCards[cardIndex] |
Prevention pattern: Wrap the array and index in a class (they are nearer).
This is particularly useful when the same calculation is done in several places. Whenever we have a class it is easy to find the code we need to manipulate the date inside. Another advantage is that the behaviour becomes easily testable when isolated in a class.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let card = deckOfCardsObject.getNextCard(someStep) |
It'd be very easy and natural (i.e. likely) that we'd test getNextCard both by calling it many times in a row and with large values of someStep.
This solution is related to the Bug generator Primitive-Obsession that will probably be described in the future.
Credits to CodeCop (Peter Kofler) with whom I've worked a lot to explore the concept of Bug Generators. We sketched out this pattern together.
Credits to CodeCop (Peter Kofler) with whom I've worked a lot to explore the concept of Bug Generators. We sketched out this pattern together.
Aucun commentaire:
Enregistrer un commentaire