Chapter 5. Refactoring

Like test-driven development, refactoring is a mindset that can be best achieved by doing it and understanding what has been gained from the process. Martin Fowler’s book, Refactoring: Improving the Design of Existing Code (Addison-Wesley, 1999), is the most complete resource to date, and I have no interest in competing with it. This chapter instead presents scenarios in which refactoring the code leads to a dramatic improvement in the readability, lowers the cost of change, and increases the quality of the code produced. These scenarios take the form of step-by-step exercises.

What Is Refactoring

Refactoring is changing the structure of existing code without changing the behavior of that code. A refactoring (noun) is one particular (usually small) well-defined change that is made to the structure of the code. When developers are refactoring (verb) code, they are applying one or more refactorings (noun) to their code; they are making one or more changes to the code’s structure.

The changes are made to the code to improve the design, with the aim of making the code easier to read, simpler to understand, and cheaper to change. In Chapter 4, we simplified the code and removed code duplication several times. We were refactoring the code.

Do It As You Go

In XP, refactoring is done after each new piece of functionality is added to the system. The aim is to write the tests, write the code to get the tests running, and then simplify the code by refactoring. This cycle of activities is then repeated over and over again for each new function added to the system. By carrying out this constant refactoring, the changes made each time will be small and few, and so they should also be easy to do.

The Importance of Tests

If you are not working in an XP environment, you can still reap some benefits from using refactoring, but I strongly recommend you do this only if you have tests in place. The tests will validate that when you carry out refactorings you have not changed the behavior of the software. The tests are your safety net.

The Benefits

It is important to understand the benefits that you should expect to see from performing refactoring on your code as you develop it. Refactoring is certainly no “silver bullet,” but Martin Fowler describes refactoring as “a pair of silver pliers that helps you get a good grip on your code.”

Refactoring is an important tool for all programmers to have in their toolboxes. There will always be certain points in any software project where the structure of the code needs to be changed, and the refactoring tool can be used for this.

By continually refactoring and using the test-code-refactor approach to developing code, you will see patterns[1] emerge in the code. This enables you (the developer) to work with these emerging patterns and further improve the structure of your code.

Always being on the look out for changes that will improve the structure of your code will force you to be reviewing the code. By constantly reviewing the code, you will be getting a deeper understanding of how the code works and what it is doing. Without knowing the code, it is much harder to refactor it.

Let’s look at how refactoring works with the XP values we discussed in Chapter 1.

Feedback

The ability to refactor your code takes the pressure off the design phase of software development. Formal methods (such as waterfall) advocate developing a perfect design before any code is written. In the real world, perfect design upfront is impossible because during the implementation of the code you will learn lessons that affect the design.

Refactoring enables you to change the design of the code at a later stage. This means that you do not have to get the design absolutely right before you write any code. You can get a rough design worked out, code it up, and then if (when) you spot a better design you can refactor your code toward the better design.

Refactoring enables you to get started on the coding earlier than you might have been able to otherwise. Getting started earlier enables you to get feedback on the progress from other coders and even customers at an earlier time. This feedback is valuable because it might force you to rethink the original design. The earlier you rethink designs, the higher your chances are of getting the final result that the customer wants.

Communication

Some of the refactorings we will do in this chapter do nothing more than improve the readability of the code. The benefit in the short term is that other programmers will find it easier to work with the code. In the medium to long term, more-readable code is easier to understand and therefore easier to maintain.

Simplicity

By simplifying your code through refactoring, you get the immediate benefit to the development team of having less to understand. This in turn improves the quality of the code. Bugs often result because of misunderstanding existing code and writing code that causes existing code to break.

Some More Big “Why” Questions

Our eXtreme .NET team has gathered together again to question Eddie about refactoring.

Why Should I Do It When No One Else Does?

This is not a question that has much to do with refactoring but more to do with team dynamics; however, with refactoring, the question comes up more often than other techniques because developers believe they are spending time refactoring “other peoples’ code,” and see this as unfair. A common cry I hear is this: “Why can’t they refactor their own code?”

My opinion is that when you are working as part of a team, the goal is for the team to succeed, not individuals. If this means I spend more of my time refactoring “other people’s code” than they do and that refactoring is making the system easier to develop and maintain, then I am adding value to the team. If I ignore code that would be better refactored, I am doing damage to the team. Ideally, the whole team will be refactoring their code. If they are not, however, I still want to add as much value to the team as I possibly can, so I refactor code even when those around me don’t.

Why Do Something That Doesn’t Add Any New Features to the Code?

All the answers I have given to the previous questions contribute to answer this one question. Refactoring does not add any new features to the code, but it is very valuable to the longevity of the code. If I had finished working on a piece of software, it was tested and ready to ship, and I knew there was no more work to do on the code; I would not refactor it. A metaphor I like to use is that refactoring is like doing exercises for your code. To function well and get a sense of well being, humans need to exercise and stretch their bodies. Code is similar; if you want your code to live for a long time, it’s good to exercise it, and refactoring is a workout for your code. After refactoring your code, it is in better shape and ready to take on the challenges presented in the future.

Let’s Start Refactoring

Hopefully by now you can see there are some benefits to giving your code the refactoring workout. So how do you go about doing it? Let’s get started with an exercise to introduce some basic ideas about refactoring.

Exercise 5-1: Currency Converter Refactoring

For this exercise to be both achievable and printable within the size constraints of this book, the example is reasonably small. If this were the entire piece of software, it is highly unlikely you would refactor it. It is also unlikely you would get paid anything for building it! The application is a simple currency converter that works with conversion rates that are purely the wishes of an Englishman who spends much of his time in Australia and the United States. We will start with the existing Currency Converter application and take some steps to refactor the code. You can download the source code for this application from http://eXtreme.NET.Roodyn.com/BookExercises.aspx.

After we have done the refactoring, the code will be in better shape for us to add new functionality, such as increasing the number of currencies. The refactoring steps each contain one or two well-defined refactoring techniques. The names of these techniques have come from Martin Fowler’s Refactoring book.

Step One: Extract Method

  1. Open the Currency Converter solution in Visual Studio.

  2. Examine the code and the form. Notice that it is a very simple program with a basic form. Things to notice include the following:

    1. All the functionality occurs in the form code.

    2. There are no tests.

    3. There is not much else to it!

  3. First we need some tests. We cannot refactor without validating we have not changed behavior. This presents a dilemma: There are no tests to start with, so how do we know we are not refactoring a bug? Also all the code is tightly tied into the GUI; this makes it hard to test and write tests for. Should we move the code away from the GUI first, or should we attempt to write some clever tests to test the code in the GUI layer? As with many real-world scenarios, we are going to compromise. We will write tests for the code we are going to extract from the GUI layer. Then we will get the tests to pass as we move the code. First we need to add a reference to NUnit in the References section, so right-click References, select Add Reference, and browse for the Nunit.Framework.dll file.

  4. We will create a test fixture class to hold our tests. Right-click the project and select Add / Add Class from the pop-up menu. We will name this class ConvertTests.cs.

  5. Edit the class code so that it uses the Nunit.Framework namespace, and add the TestFixture attribute to the class.

    using System;
    using NUnit.Framework;
    
    namespace CurrencyConverterCS
    {
    
        [TestFixture]
        public class ConvertTests
        {
        }
    }
    
  6. Now we can start writing some tests. The first test will be a breadth test that enables us to start refactoring some functionality with the confidence that we haven’t broken anything.

    [Test]
    public void ConversionBreadth()
    {
        decimal result;
        decimal amount;
        CurrencyType fromCur;
        CurrencyType toCur;
    
        amount = 100.0M;
        fromCur = CurrencyType.US;
        toCur = CurrencyType.US;
        result = MainForm.CurrencyConvert(amount,
            fromCur, toCur);
        Assert.AreEqual(100.0M, result,
            "US to US should be no change");
    
        fromCur = CurrencyType.UK;
        toCur = CurrencyType.UK;
        result = MainForm.CurrencyConvert(amount,
            fromCur, toCur);
        Assert.AreEqual(100.0M, result,
            "UK to UK should be no change");
    
        fromCur = CurrencyType.AUS;
        toCur = CurrencyType.AUS;
        result = MainForm.CurrencyConvert(amount,
            fromCur, toCur);
        Assert.AreEqual(100.0M, result,
            "AUS to AUS should be no change");
    
        decimal expected;
        fromCur = CurrencyType.US;
        toCur = CurrencyType.AUS;
        result = MainForm.CurrencyConvert(amount,
            fromCur, toCur);
        expected = amount * 2;
        Assert.AreEqual(expected, result,
            "US to AUS is incorrect");
    
        fromCur = CurrencyType.UK;
        toCur = CurrencyType.AUS;
        result = MainForm.CurrencyConvert(amount,
            fromCur, toCur);
        expected = amount / 0.5M * 2;
        Assert.AreEqual(expected, result,
            "UK to AUS is incorrect");
    }
    
  7. As you have probably worked out, this won’t compile. We have added a new enumerated type called CurrencyType and are calling a method on the MainForm that doesn’t exist. We need to go back to the MainForm and create these. In the MainForm above the class, we add the CurrencyType enum.

    public enum CurrencyType
    {
        US,
        AUS,
        UK,
    }
    
  8. Next we add the CurrencyConvert method to the MainForm code. We need to make it static or shared so that it can be accessed from the test code. This isn’t ideal, but we can work on refactoring away from that later.

    public static decimal CurrencyConvert(decimal amount,
        CurrencyType fromCur, CurrencyType toCur)
    {
    
        return 0;
    }
    
  9. Now you can compile and run the test. It will fail because the method has no body. We need to move the body from the button click.

    public static decimal CurrencyConvert(decimal amount,
                   CurrencyType fromCur, CurrencyType toCur)
    {
        decimal converted = 0.0M;
        decimal initial = 0.0M;
    
        initial = amount;
        converted = initial;
    
        if (fromCur == CurrencyType.UK)
        {
            converted = initial / UKInUS;
        }
        else if(fromCur == CurrencyType.AUS)
        {
            converted = initial / AusInUS;
        }
    
        if (toCur == CurrencyType.UK)
        {
            converted = converted * UKInUS;
        }
        else if(toCur == CurrencyType.AUS)
        {
            converted = converted * AusInUS;
        }
    
        return converted;
    }
    
  10. This will not compile because the UKInUS and AusInUS variables are not static, so we need to change the declaration of these, too.

    private static decimal AusInUS = 2;
    private static decimal UKInUS = 0.5M;
    
  11. Now compile and run the test in the NUnit GUI (or console if you prefer); the test should pass.

  12. We can now call this new method from the button click event.

    private void ConvertButton_Click
        (object sender, System.EventArgs e)
    {
        decimal converted = 0.0M;
        decimal initial = 0.0M;
    
        CurrencyType fromCur = CurrencyType.US;
        CurrencyType toCur = CurrencyType.US;
    
        initial = Convert.ToDecimal(Amount.Text);
    
        if (FromUK.Checked)
        {
            fromCur = CurrencyType.UK;
        }
        else if (FromAUS.Checked)
        {
            fromCur = CurrencyType.AUS;
        }
       
        if (ToUK.Checked)
        {
            toCur = CurrencyType.UK;
        }
        else if (ToAus.Checked)
        {
            toCur = CurrencyType.AUS;
        }
    
        converted = CurrencyConvert(initial, fromCur, toCur);
    
        Result.Text = converted.ToString();
    
    }
    

You can see that we now have more code than before, and in fact it appears less obvious. This often happens in the early stages of a refactoring session. Don’t be afraid of this. If you have an idea of where you want to go, you think at first that you are going in the wrong direction. Have the courage that you are going to get somewhere that is much better in the end, however, and you will find you can get there most of the time. Occasionally, you will be wrong, and then you need to have the courage to admit defeat and throw your changes away. Just do it.

Step Two: Extract Class

At this point, I am looking at the code and thinking that the CurrencyType should be encapsulated in a class that has the conversion routines as methods. To move in this direction, we must create a new class called ConvertibleCurrency. Do this from the right-click pop-up menu in Solution Explorer, as we did before.

  1. Before we get started on the code in the new class, we will define some behaviors of the class in a test. This documents what we are expecting the class to do. Back in the ConvertTests class, we add a new method:

    [Test]
    public void ConvertTo()
    {
        ConvertibleCurrency currency;
        decimal result;
        decimal expected;
    
        currency = new ConvertibleCurrency(
            CurrencyType.US, 100.0M);
    
        result = currency.ConvertTo(CurrencyType.US);
        Assert.AreEqual(100.0M, result,
                "US to US should be no change");
    
        currency = new ConvertibleCurrency(
            CurrencyType.AUS, 100.0M);
        result = currency.ConvertTo(CurrencyType.UK);
        expected = 100.0M / 2 * 0.5M;
        Assert.AreEqual(expected, result,
            "AUS to UK incorrect result");
    }
    
  2. We have now defined the constructor of the new class and one method, so let’s add those to the class.

    public ConvertibleCurrency(CurrencyType type, decimal val)
    {
    }
    
    public decimal ConvertTo(CurrencyType type)
    {
         decimal converted = 0.0M;
         return converted;
    }
    
  3. Compile and run the tests; the latest test will (as expected) fail. We need to implement the constructor and move the code from the MainForm static method into the class.

    public class ConvertibleCurrency
    {
       private decimal amount;
       private CurrencyType currency;
    
       public ConvertibleCurrency(CurrencyType type, decimal val)
       {
           currency = type;
           amount = val;
       }
    
       public decimal ConvertTo(CurrencyType type)
       {
           decimal converted = amount;
    
           if (currency == CurrencyType.UK)
           {
              converted = converted / UKInUS;
           }
           else if(currency == CurrencyType.AUS)
           {
              converted = converted / AusInUS;
           }
    
           if (type == CurrencyType.UK)
           {
               converted = converted * UKInUS;
           }
           else if(type == CurrencyType.AUS)
           {
               converted = converted * AusInUS;
           }
    
           return converted;
       }
    }
    
  4. The code will not compile yet because the UKInUS and AusInUS variables cannot be reached from this class; they need to be moved into the class, but they no longer need to be static.

    private decimal AusInUS = 2;
    private decimal UKInUS = 0.5M;
    
  5. Now compile and run the tests; they should pass. We are now ready to use this class from the MainForm code.

  6. Change the CurrencyConvert method in the MainForm as follows.

    public static decimal CurrencyConvert(decimal amount,
                   CurrencyType fromCur, CurrencyType toCur)
    {
        decimal converted = 0.0M;
    
        ConvertibleCurrency currency =
            new ConvertibleCurrency(fromCur, amount);
        converted = currency.ConvertTo(toCur);
    
        return converted;
    }
    
  7. You can also delete the UKInUS and AusInUS variables from the MainForm code. These variables are now declared and used in the ConvertibleCurrency class.

  8. Now would also be a good time to move the CurrencyType enum declaration into the ConvertibleCurrency class file.

  9. Compile the project and run the tests. The tests should pass, and the program should still function as before. We have encapsulated the behavior of the currency converter into a class and reduced the functionality in the GUI. This helps with testing and with porting the program to another user interface such as the Web.

Step Three: Move Method and Extract Method

Before we finish with the GUI layer, we can move the static CurrencyConvert method from the MainForm to the ConvertibleCurrency class. This is a more sensible place for this method because it deals with the ConvertibleCurrency class.

Cut and paste the method into the class and compile the project. You should get some build errors—six to be precise. Five are in the test class for calls to the MainForm method we just moved, and the sixth one is in MainForm where the call to the method was made.

The method we moved is still static, so we need to prefix it with the class name ConvertibleCurrency in these six places.

I often use the compiler in this way to detect the places I have broken code when I move a method or change a name. The compiler is far better at finding all the places the change has effected than I am!

  1. Now compile and run the tests again; they should pass. The program should still function as before.

  2. We can now turn our attention to the code in the ConvertibleCurrency class. The if-else blocks of code that convert the amount to U.S. dollars and then to the currency requested look like good candidates for extraction. We’ll start by taking the block that converts the amount to U.S. dollars and create a method called ConvertToUS.

    private decimal ConvertToUS()
    {
        decimal converted = 0.0M;
        converted = amount;
    
        if (currency == CurrencyType.UK)
        {
            converted = converted / UKInUS;
        }
        else if (currency == CurrencyType.AUS)
        {
            converted = converted / AusInUS;
        }
    
        return converted;
    }
    

    Then replace the code in the ConvertTo function so it looks like this:

    public decimal ConvertTo(CurrencyType type)
    {
        decimal converted = ConvertToUS();
    
        if (type == CurrencyType.UK)
        {
            converted = converted * UKInUS;
        }
        else if(type == CurrencyType.AUS)
        {
             converted = converted * AusInUS;
        }
    
        return converted;
    }
    

    Compile and run the tests to validate we haven’t broken anything.

  3. We can do the same with the second if-else block by creating a function called ConvertFromUS.

    private decimal ConvertFromUS(CurrencyType type,
            decimal USAmount)
    {
        decimal converted = 0.0M;
    
        converted = USAmount;
    
        if (type == CurrencyType.UK)
        {
            converted = converted * UKInUS;
        }
        else if (type == CurrencyType.AUS)
        {
            converted = converted * AusInUS;
        }
    
        return converted;
    }
    
  4. Again we need to change the code in the ConvertTo method so that it calls this new method.

    public decimal ConvertTo(CurrencyType type)
    {
        decimal converted = ConvertToUS();
    
        converted = ConvertFromUS(type, converted);
    
        return converted;
    }
    
  5. Compile and run the tests. The fact they work shows us we haven’t made any changes to the functionality, even though we have changed the structure of the code in a fairly big way.

Step Four: Replace Type Code with State/Strategy and Replace Conditional with Polymorphism

We are now in a far stronger position to change the structure of the code further and remove some more “bad smells”[2] from the code. There are two areas of the code that are smelly: the enumerated type and the if-else blocks. Interestingly, the enumerated type and if-else code are related. The fact they are related makes the smell even fouler!

  1. We will start by using Replace Type Code with State/Strategy. The CurrencyType enumerated type can be replaced with a simple object hierarchy. We’ll start by coding the hierarchy.

    public abstract class BaseCurrency
    {
        public abstract decimal InUS
        {
            get;
        }
    }
    
    public class USCurrency : BaseCurrency
    {
        public override decimal InUS
        {
            get{ return 1; }
        }
    }
    
    public class UKCurrency : BaseCurrency
    {
        public override decimal InUS
        {
            get{ return 0.5M; }
        }
    }
    
    public class AUSCurrency : BaseCurrency
    {
        public override decimal InUS
        {
            get{ return 2; }
        }
    }
    
  2. Check it compiles and the tests still run. They should because we haven’t yet touched any of the code being called. We will do this now by replacing the use of the enumerated type in the CovertibleCurrency class with the class types we have just created.

    public class ConvertibleCurrency
    {
        private decimal amount;
        private BaseCurrency currency;
    
        public static decimal CurrencyConvert(decimal amount,
            BaseCurrency fromCur, BaseCurrency toCur)
        {
            decimal converted = 0.0M;
    
            ConvertibleCurrency currency =
                new ConvertibleCurrency(fromCur, amount);
            converted = currency.ConvertTo(toCur);
    
            return converted;
        }
    
        public ConvertibleCurrency(BaseCurrency type, decimal val)
        {
            currency = type;
            amount = val;
        }
    
        public decimal ConvertTo(BaseCurrency type)
        {
            decimal converted = ConvertToUS();
    
            converted = ConvertFromUS(type, converted);
    
            return converted;
        }
    
        private decimal ConvertToUS()
        {
            decimal converted = 0.0M;
    
            converted = amount / currency.InUS;
    
            return converted;
        }
    
        private decimal ConvertFromUS(BaseCurrency type,
            decimal USAmount)
        {
            decimal converted = 0.0M;
    
            converted = USAmount * type.InUS;
    
            return converted;
        }
    }
    
  3. Pay careful attention to what has changed in the ConvertibleCurrency class. The if-else statements have disappeared through the use of the hierarchy. The replacement of the enumerated type with a hierarchy has enforced a better code base. The two member variables AusInUS and UKInUS are no longer needed; they have also been deleted.

    If we now compile this project, we should find all the places that call the class that also need to be changed; this will be in the MainForm and in the tests. The tests are an area of the code where we need to tread especially carefully. Changing the test code could cause us to inadvertently break one of the tests. The methods in the test code should now read as follows.

           [Test]
           public void ConversionBreadth()
           {
               decimal result;
               decimal amount;
               BaseCurrency fromCur;
               BaseCurrency toCur;
    
               amount = 100.0M;
               fromCur = new USCurrency();
               toCur = new USCurrency();
               result = ConvertibleCurrency.CurrencyConvert(amount,
                   fromCur, toCur);
               Assert.AreEqual(100.0M, result,
                   "US to US should be no change");
    
               fromCur = new UKCurrency();
               toCur = new UKCurrency();
               result = ConvertibleCurrency.CurrencyConvert(amount,
                   fromCur, toCur);
               Assert.AreEqual(100.0M, result,
                   "UK to UK should be no change");
    
               fromCur = new AUSCurrency();
               toCur = new AUSCurrency();
               result = ConvertibleCurrency.CurrencyConvert(amount,
                   fromCur, toCur);
               Assert.AreEqual(100.0M, result,
                   "AUS to AUS should be no change");
    
               decimal expected;
               fromCur = new USCurrency();
               toCur = new AUSCurrency();
               result = ConvertibleCurrency.CurrencyConvert(amount,
                   fromCur, toCur);
               expected = amount * 2;
               Assert.AreEqual(expected, result,
                   "US to AUS is incorrect");
    
               fromCur = new UKCurrency();
               toCur = new AUSCurrency();
               result = ConvertibleCurrency.CurrencyConvert(amount,
                   fromCur, toCur);
               expected = amount / 0.5M * 2;
               Assert.AreEqual(expected, result,
                   "UK to AUS is incorrect");
               }
    
               [Test]
               public void ConvertTo()
               {
                   ConvertibleCurrency currency;
                   decimal result;
                   decimal expected;
    
                   currency = new ConvertibleCurrency(
                       new USCurrency(), 100.0M);
    
                   result = currency.ConvertTo(new USCurrency());
                   Assert.AreEqual(100.0M, result,
                       "US to US should be no change");
    
                   currency = new ConvertibleCurrency(
                       new AUSCurrency(), 100.0M);
                   result = currency.ConvertTo(new UKCurrency());
                   expected = 100.0M / 2 * 0.5M;
                   Assert.AreEqual(expected, result,
                       "AUS to UK incorrect result");
               }
    
  4. In the MainForm code, the button click code should now look like this.

               private void ConvertButton_Click
                   (object sender, System.EventArgs e)
               {
                   decimal converted = 0.0M;
                   decimal initial = 0.0M;
    
                   BaseCurrency fromCur;
                   BaseCurrency toCur;
    
                   initial = Convert.ToDecimal(Amount.Text);
    
                   if (FromUK.Checked)
                   {
                       fromCur = new UKCurrency();
                   }
                   else if (FromAUS.Checked)
                   {
                       fromCur = new AUSCurrency();
                   }
                   else
                   {
                       fromCur = new USCurrency();
                   }
    
                   if (ToUK.Checked)
                   {
                       toCur = new UKCurrency();
                   }
                   else if (ToAus.Checked)
                   {
                       toCur = new AUSCurrency();
                   }
                   else
                   {
                       toCur = new USCurrency();
                   }
    
                   converted = ConvertibleCurrency.CurrencyConvert(
                       initial, fromCur, toCur);
    
                   Result.Text = converted.ToString();
    
               }
    
  5. Notice that we have actually added an extra else condition to the if-else block. The fact that we have replaced the enum with a hierarchy means we no longer can have a default value; this is forcing our code to be more type safe. This lack of type safety might have been the cause of a bug in a later stage, but we have prevented this bug from being born.

  6. Compile and run the tests. We should be back to a working state and ready to move forward to the last step.

Step Five: Replace Conditional with Polymorphism

  1. Because we are now confident that we do not need the enumerated type anymore, we can delete it from the code. However, there is still a small whiff in the MainForm code. Although we’ve eliminated the if-else blocks of code in the ConvertibleCurrency class, they still linger in the button click method. To remove them, we can use the same hierarchy we have already created. We can also take advantage of a feature of the .NET Framework; if you bind a list of objects to a list box or a combo box, the box will display each object using its ToString method. We will therefore override the ToString method in our concrete currency type classes.

    public class USCurrency : BaseCurrency
    {
        public override decimal InUS
        {
            get{ return 1; }
        }
        public override string ToString()
        {
            return "US$";
        }
    }
    
    public class UKCurrency : BaseCurrency
    {
        public override decimal InUS
        {
            get{ return 0.5M; }
        }
        public override string ToString()
        {
            return "UK£";
        }
    }
    
    public class AUSCurrency : BaseCurrency
    {
        public override decimal InUS
        {
            get{ return 2; }
        }
        public override string ToString()
        {
            return "AU$";
        }
    }
    
  2. Return to the MainForm in the design view and replace the From and To radio button groups with combo boxes (called fromCombo and toCombo), as shown in Figure 5-1.

    Replace radio buttons with combo boxes.

    Figure 5-1. Replace radio buttons with combo boxes.

  3. Double-click the form to generate the MainForm_Load method. In here we will create a list of the concrete currency types and bind them to the combo boxes.

    private void MainForm_Load(object sender, System.EventArgs e)
    {
        ArrayList currencyList = new ArrayList();
        currencyList.Add(new UKCurrency());
        currencyList.Add(new USCurrency());
        currencyList.Add(new AUSCurrency());
    
        fromCombo.DataSource = currencyList;
        toCombo.DataSource = currencyList.Clone();
    }
    
  4. In the button click event, we now need to get the selected object from the combo boxes.

    private void ConvertButton_Click
         (object sender, System.EventArgs e)
    {
        decimal converted = 0.0M;
        decimal initial = 0.0M;
    
        BaseCurrency fromCur;
        BaseCurrency toCur;
    
        initial = Convert.ToDecimal(Amount.Text);
    
        fromCur = fromCombo.SelectedItem as BaseCurrency;
        toCur = toCombo.SelectedItem as BaseCurrency;
    
        converted = ConvertibleCurrency.CurrencyConvert(initial,
            fromCur, toCur);
    
        Result.Text = converted.ToString();
    }
    
  5. Compile and run the tests, and then do some manual testing to verify you have now created a working piece of software.

Step Six: Customize

I leave it to you to now explore what else you can do with this code. Some ideas for you are as follows:

  • Create an ASP.NET Web interface for the code.

  • Add another set of currencies to the application.

  • Collect the currency exchange rates from an XML file.

When Not to Refactor

Now that you are all excited about refactoring, you probably want to go and look at some other code and start doing some refactoring. First some words of warning:

  1. Do not refactor code that is not going to change. You will be wasting your time. If you know the code is working and doesn’t need a bug fix or enhancement, leave it alone.

  2. Learn when to stop refactoring. It takes practice, but be aware you only want to refactor enough to make the code easy to understand and maintainable. The code doesn’t have to be a thing of beauty; it is far better if it works and you can deliver your solution quickly.

  3. Refactoring is not an excuse to hide the fact that you think someone else’s code is rubbish and so you need to rewrite it. I have seen people who rewrite other people’s code because they don’t like it and call this “refactoring.” It is not refactoring. The fact this is happening indicates you have a bigger problem with your development team that needs to be addressed. Don’t hide behind refactoring.

Conclusion

This chapter provided you with some hands-on experience with refactoring and explained how you can develop your code using the test-code-refactor cycle. I strongly recommend reading Refactoring by Martin Fowler; though the examples are in Java, don’t let that scare you from seeing how they would apply to C#.

If you are the only one on your team doing refactoring, it will be hard at first, but don’t try to force others to see the wisdom of your ways; instead, let them learn by seeing the results you produce. If they think you are wasting your time, that is fine, too. Many people think I am wasting my time going to the gym three days a week or getting up early for a run on the beach. I know I am doing some good for myself and for the good of my team, and that is enough. They will see that the code that has been refactored has more flexibility and is easier to work with. Your code will be fitter than code that has not been refactored.

1.

Gamma, Erich, Richard Helm, Ralph Johnson, & John Vlissides. Design Patterns. Reading, Massachusetts: Addison-Wesley Professional, 1995.

2.

Fowler, Martin. Refactoring. Reading, Massachusetts: Addison-Wesley Professional, 1999. In this book, Fowler describes code that has something amiss as having a bad smell.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.216.175