tags:

views:

340

answers:

12

So everyone who cares about best practices in OOP and keeping code clean and in OOP knows that methods shouldn't be doing more than one thing. Methods are discrete units that do one thing and get out.

But here's a situation though where you could save some processing and improve performance if you were to combine 2 methods which are really doing 2 things into one and reuse the existing for loop that you already have in the first method:

private void RemoveDiscontinuedItems()
{
    for(int s = 0; s < itemList.Count; s++)
    {
        if(!itemList[s].ItemIsOnSite)
        {
            RemoveItem(itemList[s].Id); // remove it from the DB
            itemList.RemoveAt(s); // remove it from the collection

            s--;
        }              
    }
}

private void RemovePriceChangedItems()
{
    for (int s = 0; s < itemList.Count; s++)
    {
        if(!PricingOptionIsValid(itemList[s]))
        {
            RemoveItem(itemList[s].Id); // remove it from the DB
            itemList.RemoveAt(s); // remove it from the collection

            s--;
        }
    }
}

These are called at page load. One removes items that are discontinued. The other removes items that have some pricing options that have changed and removes them from the same list.

Now if we were to stick with best practices, one could say that these are 2 completely independent purposes, thus we should not combine the logic in both these methods. That would then make the method be doing 2 things and I'd also have to come up with some f'd up name like RemoveDiscontinuedAndPriceChangeItems() or a generic name that doesn't tell me jack sh** like RemoveInvalidItemsFromList():

private void RemoveDiscontinuedItems()
{
    for(int s = 0; s < itemsList.Count; s++)
    {
        if((!itemList[s].ItemIsOnSite))
        {
            RemoveItem(orderItemsSavedList[s].Id);  // remove it from the DB
            itemList.RemoveAt(s); // remove it from the collection

            s--;
        }
        else if(!PricingOptionIsValid(itemList[s]))
        {
            RemoveItem(itemList[s].Id); // remove it from the DB
            itemList.RemoveAt(s); // remove it from the collection

            s--;
        }
    }

however thinking about the performance side, calling 2 methods that are looping through the same list to remove some items, would be more costly in cycles.

So, anyone against combining or are you for combining in this situation? Would like to hear some opinions out there.

+19  A: 

Why not refactor so that each method performs a single action, rather then doing the loop. Then in the body of the loop call each method as needed.

Update Here is a quick example based on your methods. Obviously the Loop method would be something different, in you application, however, I didn't have any other context for what you were doing. Also, I changed your for loop to a foreach.

private void Loop()
{ 
    foreach (Item i in itemList)
    {
         if(!item.ItemIsOnSite)
         {
          RemoveDiscontinuedItems(i)
         }
         if(!item.PricingOptionIsValid)
         {
            RemovePriceChangedItems(i)
         }

    }
}

private void RemoveDiscontinuedItems(itemType item)
{

    RemoveItem(item.Id); // remove it from the DB
    item.Remove; // remove it from the collection              

}

private void RemovePriceChangedItems(itemType item)
{
    RemoveItem(item.Id); // remove it from the DB
    item.Remove; // remove it from the collection

}
Irwin M. Fletcher
Not getting ya, isn't that what I did in the combined method?
CoffeeAddict
each method is removing an item from that list...I am not following what you mean here by each method performs a single action rather than a loop.
CoffeeAddict
He means that the method only removes 1 item from the collection. You'd have to modify the interface just a bit so you'd know what item to remove. You'd then call the method in a loop instead of having the loop in the method.
Esteban Araya
it removes 1 or more items depending on if the item in the current loop satisfies the if statement in the loop. I don't see why you say it only removes 1 item.
CoffeeAddict
RemoveItem is removing it from the DB. RemoveAt removes it from the generic list. I'm doing 2 things there in the if statement.
CoffeeAddict
(You'd have to modify the interface just a bit so you'd know what item to remove) what interface?
CoffeeAddict
his proposal is 3 methods: one that loops through all the items, and inside the loop calls the other 2 - one that identifies a single discontinued, and one that identifies a single pricechanged. the logic for identifying each type gets its own method, and the loop is only traversed once. Even cleaner than your first approach and just as performant as your second.
Philip Rieck
Philip, excellent explanation
Irwin M. Fletcher
Philip, the final code example did just that. Loops only with one loop, and with 2 if statements checks a value of the object in the current loop twice by calling 2 methods.
CoffeeAddict
You can't remove an item from a Generlic List collection using a foreach..you will get an error. Second, in your revised example, you're showing a remove method..that's only one piece I had..removes it from the DB but if you were to add the RemoveAt() back in, you're foreach would not work....and error out. You can only do this with a for.
CoffeeAddict
@coffee, see this http://stackoverflow.com/questions/644692/best-practice-removing-item-from-generic-collection-in-c
Irwin M. Fletcher
@Coffee, what happen to be forbidden from using for loops? http://stackoverflow.com/questions/1946925/forbidden-to-use-for-loop-closed
Irwin M. Fletcher
+6  A: 

A few things:

  • I don't see why you are looping forward when removing the items. You should be looping backwards and avoid the messy index manipulation when you perform a removal.

  • Thinking of this in terms of removing one item vs another based on an attribute is incorrect. You should see it as filtering the list. To that end, you should have a method that takes a Predicate<T> and then returns an IEnumerable<T> which you can then enumerate though (or an IList<T>, the same, if you want to just mutate the list). This way, you have the operation of filtering the list and the conditions separate (which is better separation IMO).

  • If you have access to LINQ, then there really is no reason to do this. You should be using a where filter on the original list, and if you want, factoring out the filtering logic into separate methods which will take the item and see if it should be returned. You can then construct your where clause (or Predicate<T> to pass to the Where extension method) from that.

casperOne
(I don't see why you are looping forward when removing the items. You should be looping backwards and avoid the messy index manipulation when you perform a removal.) Can you explain more.
CoffeeAddict
I'm not a guru at generics yet...so factor out generics at this point just for me.
CoffeeAddict
He meant that instead of for(int s = 0; s < itemList.Count; s++){... s--; ...} you should use for(int s = itemList.Count - 1; s > -1; s--){...}. This way there would be no need to change index manipulator inside the loop as you loop backwards, so removing item from the list would not affect previous list items (or their positions in the list to be precise).
Audrius
@coffeeaddict: I'd recommend taking time out ASAP and learning generics. The basics are simple, and the concepts will open up a range of options for you.
TrueWill
+1  A: 

Seems like the standard approach if possible would be to make a function out of the loop contents, and a function that does both things:

doBothThings()
{
    for(sharedLoop)
    {
        function1(loop counter);
        function2(loop counter);
    }
}

Gives you the performance but still separates the two pieces of functionality into separate functions. Obviously, not so simple if the two functions involve code before/after the loop.

Brian Schroth
I believe this is the core of the approach described (without code, which helps) by Irwin M. Fletcher.
Philip Rieck
+7  A: 

I think you're factoring is in the wrong place.

If you wrote it like this:

public void ConditionallyRemoveItems(Func<Item,bool> predicate)
{
    for (int s=0; s < itemsList.Count; s++) {
        if (predicate(itemList[s])) {
            RemoveItem(orderItemsSavedList[s].Id);
            itemList.RemoveAt(s);
            s--;
        }
    }
 }

 // ...
 ConditionallyRemoveItems(i => !i.ItemIsOnSize || !PricingOptionIsValid(i));

I also don't really like your style of messing with the loop variable - I prefer this style:

List<Item> itemsToRemove = new List<Items>();
foreach (Item i in itemList) {
    if (predicate(i)) {
        itemsToRemove.Add(i);
    }
}
foreach (Item i in itemsToRemove)
    itemList.Remove(i);

and if you don't like the performance of that, you can always do this:

List<Item> itemsToKeep = new List<Items>();
foreach (Item i in itemList) {
    if (!predicate(i)) {
        itemsToKeep.Add(i);
    }
 }
 itemList = itemsToKeep;
plinth
Alternatively, `for (int s = itemsList.count - 1; s >= 0; s--) {`
Anon.
why create a whole new object and take up memory just to add to a new list. I don't see what's wrong with just removing from the existing list which is already taking memory space. Just I need to go backwards, start from the last of the list and move backwards which now I get.
CoffeeAddict
@plinth the items to keep part would be fine, except it doesn't address removing items from the database... so identifying items to remove and then removing them from the original list and the database is more appropriate
dan
@plinth: While I appreciate your intentions here, you should be aware that your suggested change to the `for` loop is extremely inefficient. The OP is dealing with a `List<T>`, which provides random access by index; it would make much more sense to use a `for` loop, add the *index* to an `indicesToRemove` collection (if you insist on doing that), and then calling `RemoveAt` on each index. `Remove` performs a linear search for the item in question and then uses `RemoveAt`. Since `List<T>` is *giving* you the index, why not use it?
Dan Tao
+1  A: 

In C# I would almost certainly not repeat the removal logic at all. Consider something like:

    private delegate bool RemovalExpression(ItemType item);
    private void RemoveItems(RemovalExpression shouldRemove)
    {
        for (int s = 0; s < itemList.Count; s++)
        {
            if (shouldRemove(itemList[s]))
            {
                RemoveItem(itemList[s].Id);
                itemList.RemoveAt(s);

                s--;
            }
        }
    }

Which can be naturally used as:

RemoveItems(item => !item.ItemIsOnSite);
RemoveItems(item => !PricingOptionIsValid(item));
RemoveItems(item => (!item.ItemIsOnSite || !PricingOptionIsValid(item)));

etc.

Also from the sounds of it, you shouldn't be worrying about looping micro-optimizations at this point. If you don't have data that explicitly indicates that you're spending a disproportionate amount of your time in item removal, you have no way of knowing which construct will be "faster", nor any objective way of knowing whether your choice was worth the time investment, or a needless complication, or an outright pessimization of performance.

Therefore, write for maintainability; simple and clear and with no repetition of logic.

mbarnett
+1  A: 

Since both of your methods are removing items from your list, it doesn't necessarily make sense to combine the loops. You should determine if there's a significant performance difference between the two methods that you're using.

For example, if PricingOptionIsValid is an expensive operation (hits the database or whatever), you would want to call that in a second loop, after you've pruned as many items as possible with the first loop.

If the order of the tests doesn't matter, you should place the more likely branch first, and the less likely branch second. If ItemIsOnSite is false only 1% of the time, you're going to spend a lot of cycles skipping it.

You also might consider using an iterator instead of manipulating your loop variable. Either that or just find items to remove in the loop, then do another loop to remove them all.

Seth
+2  A: 

this is probably the simplest way to do it, using a loop:

//counting backwards is easier when you are doing removals from a list
for( int i = lst.Count -1; i>= 0; i--)
{
    if(condition1 || condition2)
    {
         RemoveFromDB(lst[i]);
         lst.RemoveAt(i); 
    }
}

you can refactor that to use the functional methods provided by the framework:

var toRemove = lst.FindAll( 
        item => !PricingOptionIsValid(item) || !item.ItemIsOnSite() 
       );
toRemove.ForEach( item => 
        {
            RemoveFromDB(item.ID);
            lst.Remove(item);
        });

and you could write this without the toRemove variable, by chaining the ForEach onto the FindAll

dan
+1  A: 

Use LINQ. And, aren't we past the 15 char min limit?

Esteban Araya
15 char min limit?
CoffeeAddict
+2  A: 

There are several good suggestions on how to simplify and clarify your current code.

When considering performance always start with clean and concise code, don't worry about optimization until you have PROVEN a need to optimize.

Almost everything anyone writes in a high-level language:
a) COULD be faster.

b) is fast ENOUGH.

ScottS
+1  A: 

Well if we're talking about proper OO, it's probably not a good idea put the responsibility of keeping itemList and whatever RemoveItem does in sync on the caller.

Personally I would use a list which has an OnRemove event you can hook into, and add RemoveItem as the event. This removes the need for the caller to remember to call RemoveItem.

This then makes the code much simpler, allowing you to use something like:

private void RemoveDiscontinuedItems()
{
    itemList.RemoveAll(x => !x.ItemIsOnSite);
}

private void RemovePriceChangedItems()
{
    itemList.RemoveAll(x => !PricingOptionIsValid(x));
}

The code is cleaner and the logic and purpose more obvious.

The performance obviously needn't be a concern unless it becomes a problem, though you must remember to test with extreme values (in this case a large list).

If you find that iterating through the list multiple times is actually a bottleneck then I'd propose something like this:

private bool IsDiscontinuedItem(Item item)
{
    return !item.ItemIsOnSite;
}

private bool IsPriceChangedItem(Item item)
{
    return !PricingOptionIsValid(item);
}

private bool IsInvalidItem(Item item)
{
    return IsDiscontinuedItem(item) ||
           IsPriceChangedItem(item);
}

private void RemoveInvalidItems()
{
    itemList.RemoveAll(IsInvalidItem)
}
ICR
I guess my and your suggestions are quite similar. Gave you a plus vote. I do like your idea of hooking an event. Though, as I mention in my answer, I'd personally prefer to use an immutable list if possible.
Phil
+1  A: 

Do whatever makes sense, reduces complexity, and is easiest to maintain. Best practices are a guide.

Jason
best practices have been proven.
CoffeeAddict
Failure to follow "best practices" causes lots of WTF code. However, blind adherence to "best practices" causes more.
Philip Rieck
best practices have been proven for whatever exact scenario they apply to. That doesn't mean they're the best practice for what you're doing and you must strictly adhere to them. It's better if you understand the "why" of the best practice as well as the "how" so that you can apply the same logic later if you need to. So I stand by my answer.
Jason
Gave a an upvote to both comments on "best practices" and blind adherence. One of my pet peeves is people blindly following what they understand to be best practices, as if they are hard and fast rules, without consideration of whether they're really a good idea in the particular use case.
Phil
I'm not referring to blind adherence. I'm not that naive.
CoffeeAddict
A: 

Here's my version:

    private void RemoveDiscontinuedItems()
    {
        RemoveItems(item => item.ItemIsOnSite);
    }

    private void RemovePriceChangedItems()
    {
        RemoveItems(item => PricingOptionIsValid(item));
    }

    private void RemoveAll()
    {
        RemoveItems(item => item.ItemIsOnSite || PricingOptionIsValid(item));
    }

    private void RemoveItems(Predicate<Item> removeIfTruePredicate)
    {
        itemList.RemoveAll(
            item =>
            {
                if(removeIfTruePredicate(item))
                {
                    RemoveItem(item);
                    return true;
                }

                return false;
            });
    }

Note, this is based on your use of a mutable list. I'd be tempted to consider whether a mutable list is really needed. If not, I'd prefer to use a more functional style, and use a linq where clause to create a new list that excludes the items which need removing.

Given your desire for performance though, my guess is that RemoveAll (I'm assuming you're using List<T>) will be quicker than creation of a new list. Of course you'd really need to check this to be sure.

Phil