tags:

views:

88

answers:

6

I'm getting tossed into an existing codebase, and part of the job is to get the code gradually under test as I make updates. So it's a process of taking something old and ugly and making it nicer.

In this case, there is a code similar to this:

foreach (var thingamajigLocator in thingamajigLocators)
{
    Thingamajig thingamajig;
    try
    {
        thingamajig = thingamajigservice.GetThingamajig(thingamajigLocator);
    }
    catch
    {
        // exception logged further down, but is rethrown
        continue;
    }

    thingamajigCollection.Add(thingamajig);
}

It's ugly and in theory, if the exception is handled further down, it shouldn't be necessary to handle it here, but that's how the code is and currently, it's too much work to handle the service code.

I would love to do something like this:

thingamajigCollection = thingamajigLocators
                            .Select(tl => thingamajigservice.GetThingamajig(tl))
                            .Where( /* some condition that strips out the ones throwing exceptions */ );

Is this possible in any way? Any other suggestions? I can certainly leave the foreach with the try/catch, but it seems like it could be more elegant since I don't care what the actual exception is in this case. Which again, I know is horrible form, and will need to be addressed, but no time for it right now.

A: 

I suspect no. Any method could ultimately throw an exception.

Preet Sangha
+2  A: 

I wonder exactly how is it that your exceptions are handled higher up, if you swallow and essentially ignore exceptions coming out of your service locators.

Instead of catching and swallowing the exception, I would implement a Null Object Pattern, returning a NullThingamajig from your service locators in case of an error. Then, you could simply use the following query to filter out the results:

var thingamajigs = thingamajigCollection.OfType<Thingamajig>();

EDIT

If you're unable to modify the service locators themselves, wrap them in a proxy object as well, that catches the exception and returns the null object for you:

public class ThingamajigLocatorProxy : IThingamajigLocator
{
    private readonly IThingamajigLocator locator;

    public ThingamajigLocatorProxy(IThingamajigLocator locator)
    {
        this.locator = locator;
    }

    public Thingamajig Locate()
    {
        try
        {
            return locator.Locate();
        }
        catch
        {
            // log exception
            return new NullThingamajig();
        }
    }
}

Then you could use the complete query below:

var thingamajig = thingamajigLocators
    .Select(locator => service.GetThingamajig(new ThingamajigLocatorProxy(locator)))
    .OfType<Thingamajig>();
hmemcpy
I should have said lower down. The exception is logged and rethrown, but when it gets to the code I'm modifying it's just ignored. Not my design that part. I agree that in an ideal world null object or a similar solution would be better, but digging into the service code isn't an option at this time, so working with what I've got.
McMuttons
Aah, I see. Then I'm afraid that short of wrapping your service locators in a proxy class, where you handle the exceptions and returning the null object, you're pretty much stuck with swallowing the exception...
hmemcpy
The edit makes a lot of sense. I've gone with the simple method variant as noted above, mostly because it doesn't hide the "hack". When things get cleaned up, there shouldn't be a need to handle exceptions at this level either way, so it helps serve as a reminder that this will have to be fixed.
McMuttons
+1  A: 

You could have a static helper method which does what you want:

public static KeyValuePair<T, Exception> TryExecute<T>(Func<T> func) {
    try {
        return new KeyValuePair<T, Exception>(func(), null);
    } catch (Exception ex) {
        return new KeyValuePair<T, Exception>(default(T), ex);
    }
}

thingamajigCollection = thingamajigLocators
                            .Select(tl => TryExecute(() => thingamajigservice.GetThingamajig(tl)))
                            .Where(p => p.Value is null)
                            .Select(p => p.Key));

That should do the trick... and if you need, you can still examine the exception which has been thrown. And if you want to make it nicer, create a custom struct instead of KeyValuePair which has more suitable property names - but the concept would remain the same.

Lucero
+2  A: 

How about a method as follows:

public IEnumerable<Thingamajig> GetThingamajigs()
{
    foreach (var thingamajigLocator in thingamajigLocators)
    {
        Thingamajig thingamajig;
        try
        {
            thingamajig = thingamajigservice.GetThingamajig(thingamajigLocator);
        }
        catch
        {
            // exception logged higher up
            // (how can this be? it just got swallowed right here)
            continue;
        }
        yield return thingamajig;

    }
}

The returned IEnumerable could then be used in a Linq statement, and the intent of the original code is not hidden in too much abstraction.

spender
Sorry, I should have said lower down, I guess. It gets logged and then rethrown.
McMuttons
+2  A: 

Actually, there is no difference. Func is just a delegate. So, you can make it really straightforward:

thingamajigCollection = thingamajigLocators
                        .Select(tl => 
                             {
                             try
                                 {
                                     return thingamajigservice.GetThingamajig(tl);
                                 }
                             catch(Exception)
                                 {
                                     return null;
                                 }
                             })
                          .Where(t1 => t1!=null);

As an option you can wrap GetThingamajig in new method to catch exceptions there.

NOTE: As hmemcpy said swallowing exceptions isn't the best way to go. So, you better try to redesign things.

MAKKAM
Yeah, redesign is a continuous process, but this is a large 10yo legacy system, so it happens gradually and in small chunks at a time.
McMuttons
Much as I'm terribly pleased with my own solution, of course, I suspect this is the most practical - short, comprehensible, and a little bit ugly to remind oneself that this is a workaround for a situation you don't really want to be in anyhow :-).
Eamon Nerbonne
Incidentally, I prefer writing the whole select statement on one line in cases like this - it more clearly illustrates the overall pattern you're following (namely filtering), and the messy details are also largely irrelevant details.
Eamon Nerbonne
Decided to go with this variant, mostly due to simplicity. And then I can sit and look forward to when I can get some proper exception handling in place in the service layer.
McMuttons
+2  A: 

If you want to go a bit over-the-top to fairly cleanly ignore the excpetions, how 'bout something along the lines of:

thingamajigCollection = 
    thingamajigLocators
    .Select(tl => F.Try(()=> thingamajigservice.GetThingamajig(tl)))
    .Where(thing=>thing.HasValue)
    .Select(thing=>thing.Value)

With the following static helper class F:

public static Maybe<T> Try<T>(Func<T> f) {
    try {
        return Maybe<T>.FromValue(f());
    } catch (Exception e) {
        return Maybe<T>.FromException(e);
    }
}


public struct Maybe<T> {
    readonly T val;
    readonly Exception e;
    Maybe(T val, Exception e) { this.val = val; this.e = e; }

    public bool HasValue { get { return e == null; } }
    public T Value { 
        get {
            if (!HasValue) throw new InvalidOperationException("Attempted to get a value with an error", e); 
            else return val; 
        } 
    }
    public static Maybe<T> FromException(Exception e) { 
        return new Maybe<T>(default(T), e); 
    }
    public static Maybe<T> FromValue(T val) { 
        return new Maybe<T>(val, null); 
    }
}
Eamon Nerbonne
Pretty much what I posted 10 minutes earlier, but nice `Maybe<>` struct!
Lucero
Ah yes - I see :-). Well, you know, great minds and all ;-). Cheers!
Eamon Nerbonne