This question is important for me in growing in my technical abilities. I find I swing from end-to-end, like a pendulum, in writing code that is simultaneously DRY yet readable & efficient. And I'm constantly doing this... which leads to too many re-factors and never a good, satisfied resolution.
The real example is an e-commerce cart, where you have two basic actions: add_item
and remove_item
. Both actions do the following things, adjust an item_counter
used to calculate a total_price
(which is shown in the view), add/remove a cart_item
in the view.
Initially, I sought for the most streamlined code possible, so it ended up looking like:
$(".item").click(function() {
type_of_operation = $(this).data("type") // can be add or remove
item_id = $(this).data("id")
doOperation(type_of_operation, item_id)
})
function doOperation(type_of_operation, item_id) {
count = toggleItemCounter(type_of_operation)
updatePrice(count)
toggleCartItem(type_of_operation, item_id)
}
function toggleItemCounter(type_of_operation) {
old_count = JSON.parse(sessionStorage.getItem("counter")
if (type_of_operation == "add") {
new_count = old_count + 1
} else if (type_of_operation == "remove") {
new_count = old_count - 1
}
return new_count
}
function updatePrice(count) {
price = count * 10
$("#total_price").text(price)
}
function toggleCartItem(type_of_operation, item_id) {
if (type_of_operation == "add") {
item = "<div class='item' data-type='remove' data-id='item_id'></div>"
$("#item-list").append(item)
} else if (type_of_operation == "remove") {
$("#item-list").find("[data-id='"+item_id+"']").remove()
}
}
As the code base grew, I realized that there are certain exceptional tasks for add/remove, and certain item_ids, so one function, for example, grew to something like this:
function doOperation(type_of_operation, item_id) {
if (item_id = 999) {
specialPriceUpdate()
} else {
if (type_of_operation == "modify") {
adjustCartItem(item_id)
} else {
count = toggleItemCounter(type_of_operation)
updatePrice(count)
toggleCartItem(type_of_operation, item_id)
}
}
}
This growth continued like a tumor until doOperation
became a bulky and very hard to read, with nests of conditionals and special returns and such. I realized my downfall was that I tried to make everything "generic" when I should have been more specific. So in a refactor, I rewrote the above to something like this:
$(".add_item").click(function() {
item_id = $(this).data("id")
count = addItemCounter()
updatePrice(count)
addCartItem(item_id)
}
$(".remove_item").click(function() {
item_id = $(this).data("id")
count = reduceItemCounter()
updatePrice(count)
removeCartItem(item_id)
}
$(".modify_item").click(function() {
item_id = $(this).data("id")
adjustCartItem(item_id)
}
$(".special_item") {
specialPriceUpdate()
}
function addItemCounter() {
old_count = JSON.parse(sessionStorage.getItem("counter")
return old_count + 1
}
function reduceItemCounter() {
old_count = JSON.parse(sessionStorage.getItem("counter")
return old_count - 1
}
function updatePrice(count) {
// same as previous
}
function addCartItem(item_id) {
item = "<div class='item' data-type='remove' data-id='item_id'></div>"
$("#item-list").append(item)
}
function removeCartItem(item_id) {
$("#item-list").find("[data-id='"+item_id+"']").remove()
}
function adjustCartItem(item_id) {
$("#item-list").find("[data-id='"+item_id+"']").append("some adjustment")
}
And then I thought, ok that looks more clear and readable. But then (because the real example has more functions), I was like... well wait a minute, you're generally always following the same pattern: adding or removing the counter, updating the price, then adding or removing the item, so maybe there should be a unified add
and a unified remove
function, so something like...
function addItem(item_id) {
count = addItemCounter()
updatePrice(count)
addCartItem(item_id)
}
function removeItem(item_id) {
count = reduceItemCounter()
updatePrice(count)
removeCartItem(item_id)
}
And knowing myself, once I'm at that point, I'll look at it and think, by golly addItem
and removeItem
are basically just the same, except for one's add and one's remove! Why don't I just collapse it into a single function with a bunch of toggles, and then some conditionals to allow for the exceptions? Yup, and then that will get me back to square 1...
See, unlike a pendulum, I don't ever stop swinging from side to side in reach some sort of good middle ground. Literally I could be going round and round in circles every time I sit to re-examine the code. My mind basically is like... "big god functions are more efficient... to... woah no way, need very distinct functions for everything... to... hmm it looks like these distinct functions could be a bit more dry, let's dry them... to... ok well I've dried them all the way back to a big god function again..."
I think part of it is that I'm not confident sticking to one solution, because there is so much context on what's "optimal". Part of it is that I'm self-taught, so I don't have guiding principles to really say, OK stop swinging, this is the appropriate rule of thumb/principle to follow.
Has this/does this happen to you? What do you do??!!