A Refactoring Excercise: What if you couldn’t program with loops?

Have you ever tried restricting yourself from using certain tools when doing something creative?

For example, when I was studying musical composition my teacher once tasked me to compose a song with only 4 consecutive notes (this was a considerable upgrade from her first lesson where I was only allowed 1 note).

After some tries, I liked the result to which I had arrived and she also did. However only with the time I realized that by focusing only on 4 notes instead of having a whole range of options she allowed me to concentrate and squeeze everything I could from that small piece I was working with.
And when I went back to having the full range of notes available to myself I felt incredible free.

Because software development is one of the most creative tasks that I know, I firmly believe that what I learned in that music lesson can be translated into code.

Let’s try it then: you need to highlight some products in your ecommerce website. You must highlight every product that has either more than 100 units and is priced below $5 or every product priced between $20 and $35 that is from the outdoor category or every product that contains “mountain” in its name.

BUT!

You can’t use loops of any kind. Fortunately you know that you’ll be given an array of exactly 20 products.

function highlightProducts(aListOfProducts){

    // what do we write here?

}

Remember we cant use loops. But we know for sure there are only 20 items…

Idea! we could copy and paste what would be the inside of the loop for each of them. It can’t be that bad right? let’s see what would it look like for the three products:

     // First Product
    if(aListOfProducts[0].name.indexOf('mountain') !== -1 ){ 
        aListOfProducts[0].highlighted = true
        
    }
    if(aListOfProducts[0].stock > 100 && aListOfProducts[0].price < 5 ){ 
        aListOfProducts[0].highlighted = true
        
    }
    if(aListOfProducts[0].category == 'outdoors' && aListOfProducts[0].price < 35 && aListOfProducts[0].price > 20 ){ 
        aListOfProducts[0].highlighted = true
        
    }

    // Second Product
    if(aListOfProducts[1].name.indexOf('mountain') !== -1 ){ 
        aListOfProducts[1].highlighted = true
        
    }
    if(aListOfProducts[1].stock > 100 && aListOfProducts[1].price < 5 ){ 
        aListOfProducts[1].highlighted = true
        
    }
    if(aListOfProducts[1].category == 'outdoors' && aListOfProducts[1].price < 35 && aListOfProducts[1].price > 20 ){ 
        aListOfProducts[1].highlighted = true
        
    }
    // Third product
    if(aListOfProducts[2].name.indexOf('mountain') !== -1 ){ 
        aListOfProducts[2].highlighted = true
        
    }
    if(aListOfProducts[2].stock > 100 && aListOfProducts[2].price < 5 ){ 
        aListOfProducts[2].highlighted = true
        
    }
    if(aListOfProducts[2].category == 'outdoors' && aListOfProducts[2].price < 35 && aListOfProducts[2].price > 20 ){ 
        aListOfProducts[2].highlighted = true
        
    }

That is some really awful awful code. Let’s prettify it!

First of all let’s take out the product into it’s own variable

    // First Product
    var currentProduct = aListOfProducts[0]
    if(currentProduct.name.indexOf('mountain') !== -1 ){ 
        currentProduct.highlighted = true
        
    }
    if(currentProduct.stock > 100 && currentProduct.price < 5 ){ 
        currentProduct.highlighted = true
        
    }
    if(currentProduct.category == 'outdoors' && currentProduct.price < 35 && currentProduct.price > 20 ){ 
        currentProduct.highlighted = true
        
    }

    var currentProduct = aListOfProducts[1]
    if(currentProduct.name.indexOf('mountain') !== -1 ){ 
        currentProduct.highlighted = true
        
    }
    if(currentProduct.stock > 100 && currentProduct.price < 5 ){ 
        currentProduct.highlighted = true
        
    }
    if(currentProduct.category == 'outdoors' && currentProduct.price < 35 && currentProduct.price > 20 ){ 
        currentProduct.highlighted = true
        
    }

    var currentProduct = aListOfProducts[2]
    if(currentProduct.name.indexOf('mountain') !== -1 ){ 
        currentProduct.highlighted = true
        
    }
    if(currentProduct.stock > 100 && currentProduct.price < 5 ){ 
        currentProduct.highlighted = true
        
    }
    if(currentProduct.category == 'outdoors' && currentProduct.price < 35 && currentProduct.price > 20 ){ 
        currentProduct.highlighted = true
        
    }

Notice how everything apart from setting currentProduct is now the same?
We could abstract that into it’s own function. But I’m afraid that if we do that we we’ll sweep the problem under the rug and leave that new function as is. So let’s refrain from abstracting all of that into a new function for now.

What can we do? If we look closely we can separate the condition of highlighting from the actual highlighting:

    // First Product
    var currentProduct = aListOfProducts[0]
    if(productShouldBeHighlighted(currentProduct)){
        currentProduct.highlighted = true
    }

    var currentProduct = aListOfProducts[1]
    if(productShouldBeHighlighted(currentProduct)){
        currentProduct.highlighted = true
    }

    var currentProduct = aListOfProducts[2]
    if(productShouldBeHighlighted(currentProduct)){
        currentProduct.highlighted = true
    }

and we must accompany that with:

function productShouldBeHighlighted(aProduct){
    if (aProduct.name.indexOf('mountain') !== -1 ) return true;
    if (aProduct.stock > 100 && aProduct.price < 5 ) return true;
    if (aProduct.category == 'outdoors' && aProduct.price < 35 && aProduct.price > 20 ) return true;
    return false
}

Now we could further refactor the productShouldBeHighlighted function. But I’m gonna leave it like it is for the sake of the blog post.
You can however abstract the if conditions into new functions; this could be pretty handy if the conditions for highlighting start getting more complex with time.

Notice also that we are left with a nice centralized point where our highlighting logic is done, which we can change very easily by commenting / uncommenting lines.

Now, to close up the blog post we’ll allow loops back in:
 

function highlightProducts(aListOfProducts){

    for (product in aListOfProducts) {
        if(productShouldBeHighlighted(product)){
            product.highlighted = true
        }
    }
    
}

function productShouldBeHighlighted(aProduct){
    if (aProduct.name.indexOf('mountain') !== -1 ) return true;
    if (aProduct.stock > 100 && aProduct.price < 5 ) return true;
    if (aProduct.category == 'outdoors' && aProduct.price < 35 && aProduct.price > 20 ) return true;
    return false
}


Much more readable than what we started! 🙂

Bonus: can we borrows some methods from functional programming?

function highlightProducts(aListOfProducts){
    aListOfProducts.filter(productShouldBeHighlighted).map(p => p.highlighted = true)
}

function productShouldBeHighlighted(aProduct){
    if (aProduct.name.indexOf('mountain') !== -1 ) return true;
    if (aProduct.stock > 100 && aProduct.price < 5 ) return true;
    if (aProduct.category == 'outdoors' && aProduct.price < 35 && aProduct.price > 20 ) return true;
    return false
}

 
Do you think you would’ve given this treatment to the body of the loop had you been able to write loops without restrictions from the beginning?

Maybe if you are an experienced programmer you would’ve! However for those of you that wouldn’t have done that I hope that this blog posts serves as a little trick for when you need to write loops:

Before writing them, try unrolling them. Pretend that loops don’t exist, so you get into the position confronting the logic of the body of the loop face to face.

Try working with that, and when you are finished put the loop back in!

7 thoughts on “A Refactoring Excercise: What if you couldn’t program with loops?”

  1. The final result could then be further processed by just saying “` product.highlighted = productShouldBeHighlighted(product)“` inside of the loop. Helps remove the if statement as well!

  2. The productshouldbehighlightrd function does not need any if statements at all
    Just return the result of orring all conditions and assign result directly

      1. For long boolean expressions, I prefer the version with multiple ifs (/elses) because it gives a clear separation whereas using or and parentheses can be a bit hard to grasp (and error-prone) when statements beccome long.

  3. If you truely did not want to use for loops, here’s how you could further simplify your code:

    “`
    function highlightProducts(aListOfProducts, i=0){
    if (i === aListOfProducts.length) return null // We are done!
    if(productShouldBeHighlighted(product)){
    product.highlighted = true
    }
    return highlightProducts(aListOfProducts, i + 1)
    }
    “`

    The added benefit of the above approach is that debugging is easier! If you want to debug the last 5 items, you can simply call the function like this:

    highlightProducts(aListOfProducts, aListOfProducts.length – 5)

    1. Thank you Anon Recursion is always nice 🙂 Although the goal of this post is not to discard loops but to artificially create a situation that forces you to refactor some pieces of code.

Leave a Comment

Your email address will not be published. Required fields are marked *