views:

234

answers:

7
+2  Q: 

Refactor help c#

I have several hundred lines of code like this:

if (c.SomeValue == null || c.SomeProperty.Status != 'Y')
{
    btnRecordCall.Enabled = false;
}

if (c.SomeValue == null || (c.SomeProperty.Status != 'Y' &&
    c.SomeOtherPropertyAction != 'Y'))
{
    btnAddAction.Enabled = false;
}

if (c.SomeValue == null || c.SomeProperty.Processing != 'Y')
{
    btnProcesss.Enabled = false;
}

How can I refactor this correctly? I see that the check 'c.SomeValue == null' is being called every time, but it is included with other criteria. How can I possibly eliminate this duplicate code?

+1  A: 

I'd start by making sure all such code is contiguous. Anything other than this code should be moved before or after the code.

Then, for each reference to a control property, create a corresponding local variable, e.g., processEnabled. Define it before the first if statement. For each such property, move, e.g., btnProcesss.Enabled = false; to the end of this code block, and change "false" to processEnabled. Replace the original with processEnabled = false;.

When the code block has no more references to controls (or to anything else having to do with the UI), select the entire block, from the added variables to the control property sets at the end, and use the Extract Method refactoring. That should leave you with a method that accepts c, and produces values you can later use to set control properties.

You can even get fancier. Instead of individual local variables, define a class that has those "variables" as properties. Do pretty much the same thing, and the extracted method will wind up returning an instance of that class, instead of individual out parameters.

From there, you may start to see more things to clean up in the extracted method, not that you'll have removed anything to do with UI from that code.

John Saunders
+2  A: 

Create properties on "c" such as "CanRecordCall", "CanAddAction", "CanProcess" so that your code becomes this:

btnRecordCall.Enabled = c.CanRecordCall;
btnAddAction.Enabled = c.CanAddAction;
btnProcess.Enabled = c.CanProcess;

The "c.SomeValue == null" is a typical response to NullReferenceExceptions. You could improve "c" by initializing its SomeValue property to a null object so that there is never a null reference (just an object that does nothing).

Austin Salonen
+2  A: 

In specific, since you seem to be setting UI elements state, you could consider more of a two-way data binding model where you set up a data context and a control-to-property mapping and let that govern the control state. You can also consider a more heavy-weight solution that would be something like the Validation Application Block from Enterprise Library. There are also some fluent validation projects that you should take a look at.

JP Alioto
+4  A: 

If you don't want to do much refactoring, you can easily pull the null check out.

if (c.SomeValue == null)
{
    btnRecordCall.Enabled = false;
    btnAddAction.Enabled = false;
    btnProcesss.Enabled = false;
}
else
{
    if(c.SomeProperty.Status != 'Y')
    {
        btnRecordCall.Enabled = false;
    }

    if((c.SomeProperty.Status != 'Y') &&
       (c.SomeOtherPropertyAction != 'Y'))
    {
        btnAddAction.Enabled = false;
    }

    if(c.SomeProperty.Processing != 'Y')
    {
        btnProcesss.Enabled = false;
    }
}

If you're looking to refactor instead of shuffle, the wall of boolean testing could be moved in to methods/extension methods of whatever class your object c is an instance of - that way you could say

btnRecordCall.Enabled = c.IsRecordCallAllowed();
48klocs
I went with the 2nd approach. Extended a partial class and implemented these behaviors. Thanks! ~ck
Hcabnettek
A: 

I'm guessing the issue here is about 'boolean map' style refactorings, i.e., being able to refactor complementary boolean cases where there might be some gaps and some repetition. Well, if that's what you're after, you can certainly write a tool to do this (it's what I would do). Basically, you need to parse a bunch of if statements and take note of condition combinations that are involved. Then, through some fairly simple logic, you can get your model to spit out a different, more optimized model.

The code you show above is one reason why I love F#. :)

Dmitri Nesteruk
+5  A: 

I would use the specification pattern, and build composite specifications that map to a proper Enabled value.

The overall question you want to answer is whether some object c satisfies a given condition, which then allows you to decide if you want something enabled. So then you have this interface:

interface ICriteria<T>
{
    bool IsSatisfiedBy(T c);
}

Then your code will look like this:

ICriteria<SomeClass> cr = GetCriteria();

btnAddAction.Enabled = cr.IsSatisfiedBy(c);

The next step is to compose a suitable ICriteria object. You can have another ICriteria implementation, (in additon to Or and And), called PredicateCriteria which looks like this:

class PredicateCriteria<T>  : ICriteria<T>
{
    public PredicateCriteria(Func<T, bool> p) {
        this.predicate = p;
    }

    readonly Func<T, bool> predicate;

    public bool IsSatisfiedBy(T item) {
        return this.predicate(item);
    }
}

One instance of this would be:

var c = new PredicateCriteria<SomeClass>(c => c.SomeValue != null);

The rest would be composition of this with other criteria.

eulerfx
A: 

Interestingly, in our current Winforms app, the three conditions would be in three different classes, since each button would be attached to a different Command.

The conditions would be in the CanExecute methods of the commands and control the enable/disable behaviour of the button that triggers the command. The corresponding execution code is in the Execute method of the class.

flq