Refactoring the XMLNotepad

Published 20 July 04 03:14 PM

I’ve been reading Extreme Programming Adventures in C#.  Currently reading Chapter 28 (Undo).

 

Through most of the book, there has been a bit of Refactoring that the code has been crying out for.  At first I thought Ron was waiting to until the duplication got worse, thereby justifying the Refactoring as providing real customer value.

 

I finally downloaded the final sources from the MSPress site, and see that the smells are still there.  So I did the Refactorings myself, and found that the code is much improved for it.

 

Duplication between SkipTags and Insert Tags:

 

    private static InsertAction[] insertActions = new InsertAction[] {

            new InsertAction(

            Tags.Pre,                                                                 

            "Insert &Pre",

            new string[] { "<pre></pre>" },           // <------ here

            new string[] { "<pre>" }),                // <------ here

 

I changed it to take an array of tags:

 

            new string[] { "pre" }),

 

 And figure out the insert & skip text later:

 

            public string[] TagsToInsert

            {

                  get

                  {

                        List<string> list = new List<string>();

 

                        list.AddRange(TagsToSkip);   

 

                        List<string> reversed = new List<string>(tags); reversed.Reverse();

 

                        foreach (string s in reversed)

                        {

                              list.Add("</" + s + ">");

                        }

 

                        return list.ToArray();

                  }

            }

 

            public string[] TagsToSkip

            {

                  get

                  {

                        List<string> list = new List<string>();

                        foreach (string s in tags)

                        {

                              list.Add("<" + s + ">");

                        }

                        return list.ToArray();

                  }

            }

 

This is not, strictly speaking, a Refactoring.  The original code could insert tags on the same line, and my code always puts new tags on new lines.  I should probably talk to my customer to understand how important this is to them.  If it is important, I’d need to do enough additional work that the Refactoring might be too expensive and should be abandoned. 

 

Still, I like the effect on the code, so I’m going to try to get my customer to go along.

 

Note: I’m cheating by using C# 2.0 features like List<>.  Ron didn’t have them when writing his book.

 

Duplication around the ‘Tags’ enum

 

There’s a lot of it.  There’s a bunch mapping back and forth between the enum & the InsertAction command.  I decided to make them the same:

 

            public static class InsertActions

            {

                  public static InsertAction Pre = new InsertAction(

                        "Insert &Pre",

                        new string[] { "pre" }

                  );

 

                  public static InsertAction Section = new InsertAction(

                        "Insert &Section",

                        new string[] { "sect1", "title" }

                  );

 

                  public static InsertAction UnorderedList = new InsertAction(

                        "Insert &UL",

                        new string[] { "UL", "LI" }

                  );

 

                  public static InsertAction OrderedList = new InsertAction(

                        "Insert &OL",

                        new string[] { "OL", "LI" }

                  );

 

                  public static InsertAction Paragraph = new InsertAction(

                        null,

                        new string[] { "P" }

                  );

 

                  public static InsertAction ListItem = new InsertAction(

                        null,

                        new string[] { "LI" }

                  );

 

                  //

                  // it's important that all new InsertActions be added to this list, or they

                  // won't get handled correctly.  That means there's a little duplication still,

                  // but it's very much localized now.  Yay OO.

                  //

                  public static InsertAction[] All

                  {

                        get

                        {

                              return new InsertAction[] {

                                    Pre,

                                    Section,

                                    UnorderedList,

                                    OrderedList,

                                    Paragraph,

                                    ListItem

                              };

                        }

                  }

            }

 

 

A lot of the associated changes are quite simple:

 

            public void Enter()

            {

                  if (InListItem())

                        InsertTags(InsertActions.ListItem);

                  else

                        InsertTags(InsertActions.Paragraph);

            }

 

We also get rid of a bit of code.  SectionCommand() is replaced with InsertActions.Section, which is a huge win in removing duplication.

 

    public InsertAction SectionCommand() {

      foreach (InsertAction a in InsertActions) {

        if (a.MenuString == "Insert &Section" ) return a;

      }

      return null;

    }

 

And InsertActionForCommand is gone:

 

    private InsertAction ActionForCommand(Tags command) {

      foreach ( InsertAction action in InsertActions) {

        if (action.Command == command)

          return action;

      }

      return null;

    }

 

 

 

Now that it’s done, now how much simpler the creation of InsertAction is.

 

Before

 

            new InsertAction(

            Tags.Pre,                                                                 

            "Insert &Pre",

            new string[] { "<pre></pre>" },           // <------ here

            new string[] { "<pre>" }),                // <------ here

 

After

 

                  public static InsertAction Pre = new InsertAction(

                        "Insert &Pre",

                        new string[] { "pre" }

                  );

 

I think this is a real improvement.

 

Comments

# Nicholas Allen said on July 20, 2004 3:41 PM:
What if you added an attribute to each InsertAction you created and then built the All list using reflection? That would be one way to eliminate the duplication.
# Ron Jeffries said on July 20, 2004 3:51 PM:
Nice changes! Why weren't you there to pair with me? ;->
# jaybaz [MS] said on July 20, 2004 4:02 PM:
Nicholas: I might do that in my own code, in my current job.

However, I wouldn't do that in the book, because it doesn't fit with the principles of the book. In particular, using Reflection in this way is confusing to me (because I'm not familiar with it), and because it seems like overkill for the problem (the unknown often seems like overkill).

Following Ron's lead, if you & I were pairing & you suggested Reflection, I probably let you explain/show it to me. So, why not write up an example for us?
# Nicholas Allen said on July 20, 2004 4:15 PM:
Jay, I agree it's overkill for an example in a book like this (although it would fit in well if you had a solid chapter on reflection). I was trying to brainstorm a little for how far something like "eliminate duplication" could be taken. I'll try to write up something later if I have the time.
# Nicholas Allen said on July 20, 2004 5:38 PM:
Let me try this somewhat off the top of my head.

First, we need to declare an attribute that will mark the fields we want to include. I'm not creative so I'll use

[AttributeUsage(AttributeTargets.Field)]
public class ActionAttribute : Attribute {}

The usage attribute tells the compiler that only fields can be tagged with this attribute.

Next, we'd change the declarations to include our new attribute like this

[ActionAttribute]
public static InsertAction Pre = new InsertAction(
"Insert &Pre",
new string[] { "pre" }
);

Finally, we'd change the All accessor to look like

FieldInfo[] fields = typeof(InsertAction).GetFields(
BindingFlags.Public | BindingFlags.Static
);
List<InsertAction> all = new List<InsertAction>();
foreach (FieldInfo field in fields) {
if(field.GetCustomAttributes(typeof(ActionAttribute), false).Length > 0)
all.Add((InsertAction) field.GetValue(null));
}
return all.ToArray();

I'll have to hope I didn't kill the formatting too much there!

The first line takes our class and reflects out all of the fields that are public and static. Then we go through the fields and ask each one if it has a copy of our attribute. If it does, we add the action to our list.

If we wanted to, we could add some error checking to make sure the coder didn't use the attribute incorrectly.

There's another way to do this using the FindMembers method. That method lets us do a filtered search which would combine the first two steps above into one. However it requires defining a delegate to be the filter.
# jaybaz [MS] said on July 20, 2004 6:40 PM:
Cyrus (http://blogs.msdn.com/cyrusn/) suggested an intesting trick: have each of the singleton objects register themselves with the collection.

1. Change the 'All' property to:

public static List<InsertAction> All = new List<InsertAction>();

2. In the InsertAction ctor, add:

TextModel.InsertActions.All.Add(this);

Pretty simple, and it works. Thanks Cyrus.
# Nicholas Allen said on July 20, 2004 7:42 PM:
I like the trick from Cyrus. It's concise and you can add actions without thinking too much. We still have to be a little careful because now it's possible for the All list to include actions not present in the enumeration.

Is it ok for the list of actions and the enumeration to be mutable? Again, something for thinking about rather than the book. I wonder if untrusted code could inject an action and gain some priviledges or information.
# jaybaz [MS] said on July 21, 2004 9:16 AM:
The mutability question is a good one.

In my implementation, I marked all the "enum" members as 'readonly', so we get most of the way there.

There's some broken encapsulation with the 'All' array as presented. I have some ideas for how to improve it, but they seemed to be bigger steps than I wanted to take at the time.

I think the code is telling us that the code that the part about "TextModel.InsertActions" and "class InsertAction" are closely coupled, but that coupling isn't properly respected in the code.

I would start by bringing them closer together, possibly with some nested classes or a factory or something.

There's another refactoring that I'd like to explore, which also Cyrus suggested. Instead of 6 instances of 1 class, have 6 derived classes which pass these parameters to the base ctor. I think it might clean up the code further.

In the book context, though, I'd want to stop here & let it go for now. But you know that.
# jaybaz MS WebLog Refactoring the XMLNotepad | Menopause Relief said on June 9, 2009 9:11 PM:

PingBack from http://menopausereliefsite.info/story.php?id=1499

New Comments to this post are disabled

This Blog

Syndication

Page view tracker