bliz
bliz

Reputation: 3

Larger Function vs Smaller Functions that Repeat Same Code

I've been reading about keeping functions simple and specific to only one purpose. And that even a function that does a simple calculation and print out the result is already too much.

I've been working on an item shop system for a small game. The shop has a vector of all items available in the game. Each Item keeps track of its own count (is that bad?), and do not show up in the inventory output if its count is zero.

When a player (rider) wants to buy an item, the shop should check if the player has enough credit and if such item is in stock before the item gets removed from the shop (and then added to the player's inventory).

I have combined all of that into one function, thinking that this avoid repetition of traversing through the vector of Items.

/* ItemShop.cpp
 * non-binary boolean:
 *   returns 0 if item is not in stock
 *   returns 1 if rider has insufficient creds to buy the item
 *   returns 2 if purchase is successful */

int ItemShop::buyItem(const string& itemName, const int cred, Item& newItem) {
    // go through shop inventory and search for item with given name
    for (int i = 0; i < items.size(); i++) {
        if (items[i].getName() == itemName) {
            if (items[i].getCount() > 0) { // item is in stock
                if (items[i].getValue() <= cred) { // rider has enough creds to buy the item
                    newItem = items[i];    // pass a copy of the item by ref
                    newItem.setCount(1);   // take one of that item
                    items[i].removeOne();  // remove one from the shop
                    return 2; // purchase is successful
                }
                return 1; // rider cannot afford to buy the item
            }
        }
    }
    return 0; // item is not in stock
}

In doing this approach, I end up with a larger multi-purpose function but I didn't have to go through the Item vector multiple times. I think if I were to break the function into separate ones, they would be:

  1. check if item is in stock
  2. check if player can afford
  3. transaction

Each of those functions would have to go through and find the item in the vector (unless maybe I pass a reference of it.. from the function?).

All in all, does less code repetition make my approach justifiable? If not, how should I break it down?

Upvotes: 0

Views: 146

Answers (4)

Jerry Coffin
Jerry Coffin

Reputation: 490048

I do think an item keeping its own count is a poor idea. An "inventory" (or something on that order) keeps track of the items in stock and the number of each. An item should be just that: an item.

I think the suggestion to use a [unordered_]map is a good one. It pre-implements what is probably the single most complex part of your current function (though none of it is particularly complex).

Do note that some of this can get much trickier when/if multiple threads get involved. If multiple threads of execution are involved, your current pattern of "if we can do this, then do it" breaks down, because it introduces race conditions. You need to assure that removing the item from inventory and paying for the item happen as a single, atomic, transaction. As long as you're sure it'll only have a single thread of execution involved though, your current method is safe.

Upvotes: 0

MikeMB
MikeMB

Reputation: 21126

Your function doesn't do too many things - you are purchasing an item, which includes those three necessary steps (btw.: aren't you missing the payment?).

However (aside from other things that could be improved), you should make sure that the steps don't get intermingled. In particular avoid nested structures whenever possible.
You could e.g.rewrite your body (without changing the interface) like this:

int ItemShop::buyItem(const string& itemName, const int cred, Item& newItem) {
    //find item
    auto it = std::find_if(items.begin(), items.end(), [&](const Item& item) {return item.getName() == itemName; });

    //Check if Item can be purchased
    if (it == items.end() || it->getCount == 0) {
        return 0; //item is not in stock
    }   
    if (it->getValue() > cred) {
        return 1; //rider can't afford item
    }

    //buy item
    newItem = *it;          // pass a copy of the item by ref <- Avoid that if possible
    newItem.setCount(1);    // take one of that item
    it->removeOne();        // remove one from the shop
    return 2; // purchase is successful 
}

Upvotes: 0

πάντα ῥεῖ
πάντα ῥεῖ

Reputation: 1

You can break that up into the following operations:

  • find the appropriate item by name (that's what the loop mainly does) and return a reference to the found item
  • check if there are enough items in stock. item itself already carries that information as it looks, it doesn't need to be executed inside the loop
  • check if the player can afford it. You already have the cred value apparently, it doesn't need to be executed inside the loop
  • if the latter two conditions are fulfilled do the transaction, again it doesn't need to be executed inside the loop

Upvotes: 1

Frank Puffer
Frank Puffer

Reputation: 8215

Two recommendations:

  1. Store your items in a std::map<string,Item> where the string is the item name. This will remove the search loop.
  2. Use an enum as return value instead of an int.

You could also implement simple functions for the different checks, but that's a matter of taste I would say.

Upvotes: 2

Related Questions