views:

112

answers:

5

I came across this code today and wondering what are some of the ways we can optimize it.

Obviously the model is hard to change as it is legacy, but interested in getting opinions.

Changed some names around and blurred out some core logic to protect.

private static Payment FindPayment(Order order, Customer customer, int paymentId)
    {
        Payment payment = Order.Payments.FindById(paymentId);
        if (payment != null)
        {
            if (payment.RefundPayment == null)
            {
                return payment;
            }

           if (String.Compare(payment.RefundPayment, "refund", true) != 0 )
            {
                return payment;
            }

        }

        Payment finalPayment = null;
        foreach (Payment testpayment in Order.payments)
        {
            if (testPayment.Customer.Name != customer.Name){continue;}

            if (testPayment.Cancelled) 
            {
                continue;
            }

            if (testPayment.RefundPayment != null) 
            {
                if (String.Compare(testPayment.RefundPayment, "refund", true) == 0 )
                {
                    continue;
                }
            }

            if (finalPayment == null)
            {
                finalPayment = testPayment;
            }
            else
            {
                if (testPayment.Value > finalPayment.Value)
                {
                    finalPayment = testPayment;
                }
            }
        }
       if (finalPayment == null) 
       {
           return payment;
       }

       return finalPayment;
    }

Making this a wiki so code enthusiasts can answer without worrying about points.

+1  A: 

It's going to be highly data-dependent. You need to profile this with a "typical" data set, identify the bottlenecks, then consider appropriate optimisations based on your profile data.

Paul R
Looking for code elegance, removing nesting..
CodeToGlory
I only see one loop (foreach) - perhaps you mean something other than `nesting` ?
Paul R
Martin Milan
@Martin: sure - I was just questioning the OP's reference to "multiple nested loops", since there is only a single loop. I still think he should profile this first and *then* decide what to optimise, if anything.
Paul R
Agreed - though he should really look at a re-write... The idea of having the refund string for example - this would be a lot quicker with an enumeration...
Martin Milan
+2  A: 
 if (testPayment.Customer.Name != customer.Name){continue;}

That shouldn't be necessary for a start - surely all the payments against any given order relate to the same customer?

I don't like this function at all, if I'm passing a passing a payment_id, then I would only expect to get either that specific payment, or null... None of this searching around stuff...

Sounds to me like you need to think about redesigning a lot of code, and I think it goes well beyond this specific function...

Martin Milan
Depends on the nature of the app. I've seen business apps where an order can have multiple paying customers with varying percentages each one owes. Though comparing by name could cause issues if name isn't a PK.
Austin Salonen
+1 for that, but given the code we have already seen I think it's unlikely. You're right to point it out though...
Martin Milan
I am just getting around the app..there is lot that needs to be done..I think I will get several opportunities to post trivia.
CodeToGlory
+1  A: 

If you can add members to the Payment class, add a didRefund boolean which is set to true whenever the RefundPayment string is set to "refund". This allows you to avoid the string compares.

Before the loop if you do initialize finalPayment like this:

finalPayment = new Payment;
finalPayment.Value = -1.0e12

then you can avoid the null test in the loop. (Assumes none of the customers are making negative billion dollar payments)

AShelly
+1  A: 

The first test (if (payment.RefundPayment == null)) is redundant. The second test using String.Compare works with null strings. You can use this "optimization" in the second place you use this comparison in the foreach loop.

Erich Mirabal
+1  A: 

You can move one of the conditions into a function of it's own since it's repeated twice (testing for it and its opposite). You can also shove the conditions together and wrap the whole loop in the first conditional. That way you only have one exit point for the function. If that doesn't seem manageable, you can wrap the foreach-loop into a function and just call it that way.

private static boolean IsRefundPayment(Payment payment) {
    return payment.RefundPayment != null && String.Compare(payment.RefundPayment, "refund", true) == 0;
}

private static Payment FindPayment(Order order, Customer customer, int paymentId) {
    Payment payment = Order.Payments.FindById(paymentId);
    if (payment == null || IsRefundPayment(payment)) {
        foreach (Payment testpayment in Order.payments) {
            if (testPayment.Customer.Name == customer.Name && !testPayment.Cancelled && !IsRefundPayment(payment)
                && (testPayment.Value > payment.Value)) {
                payment = testPayment;
            }
        }
    }
    return payment;
}
omouse
Omit finalPayment, just write directly to payment. Then you'll lose a declaration, as well as the final if.
Einar