Skip navigation links
Home
Document Center
Blog
Videos
Forum
PhillyXAML.org > Blog > Posts > Clean Code 1: Formatting for Readability
Clean Code 1: Formatting for Readability
One of the most common things I see when working with WPF and Silverlight (and even C# or XML), is a virtually unreadable markup file (or code in the case of C#). The reason for this is because it is all too easy to rely too heavily on the Intellisense feature of Visual Studio, allowing it to add all of our attributes to our XAML tags in a linear, "go until youre done" approach which causes our code to run off the side of the page, and gets real messy.
 
The first thing you should notice about the example below is that it indeed runs way off the side of the page, which is often the case when we view this type of code in Visual Studio. Specifically notice line 24. In order to view the entire markup for our tag, to get the big picture, we need to scroll, and scroll, and get bits and pieces of what we wish to view.
 
Consider the following snippet for a standard grid containing some labels, and some text blocks for displaying some bound data in a data template.
   1:  <Grid Width="600" Height="Auto">
   2:      <Grid.RowDefinitions>
   3:          <RowDefinition Height="Auto" />
   4:          <RowDefinition Height="Auto" />
   5:          <RowDefinition Height="Auto" />
   6:          <RowDefinition Height="Auto" />
   7:          <RowDefinition Height="Auto" />
   8:          <RowDefinition Height="Auto" />
   9:      </Grid.RowDefinitions>
  10:      <Grid.ColumnDefinitions>
  11:          <ColumnDefinition Width="Auto" MinWidth="113" />
  12:          <ColumnDefinition Width="*" />
  13:      </Grid.ColumnDefinitions>
  14:   
  15:      <Label Grid.Column="0" Grid.Row="0" Content="Description:" Foreground="#FFFFFFFF" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
  16:      <Label Grid.Column="0" Grid.Row="1" Content="Status:" Foreground="#FFFFFFFF" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
  17:      <Label Grid.Column="0" Grid.Row="2" Content="Percent Complete:" Foreground="#FFFFFFFF" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
  18:      <Label Grid.Column="0" Grid.Row="3" Content="Critical Path:" Foreground="Black" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
  19:      <Label Grid.Column="0" Grid.Row="4" Content="Start:" Foreground="#FFFFFFFF" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
  20:      <Label Grid.Column="0" Grid.Row="5" Content="Duration:" Foreground="#FFFFFFFF" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
  21:      <TextBlock Grid.Column="1" Grid.Row="0" Text="{Binding Path=ProcessDescription}" Width="475" TextWrapping="WrapWithOverflow" Foreground="#FFFFFFFF" HorizontalAlignment="Left" VerticalAlignment="Stretch" />
  22:      <TextBlock Grid.Column="1" Grid.Row="1" Text="{Binding Path=StatusDomainObj.StatusText}" Width="475" Foreground="#FFFFFFFF" HorizontalAlignment="Left" VerticalAlignment="Stretch" />
  23:      <TextBlock Grid.Column="1" Grid.Row="2" Text="{Binding Path=PercentComplete}" Width="475" Foreground="#FFFFFFFF" HorizontalAlignment="Left" VerticalAlignment="Stretch" />
  24:      <CheckBox Grid.Column="1" Grid.Row="3" Content="" IsChecked="{Binding Path=IsCriticalPath, Mode=TwoWay, NotifyOnValidationError=True, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, ValidatesOnExceptions=True}" Width="475" HorizontalAlignment="Left" VerticalAlignment="Stretch" />
  25:      <TextBlock Grid.Column="1" Grid.Row="4" Text="{Binding Path=StartDelta}" Width="475" Foreground="#FFFFFFFF" HorizontalAlignment="Left" VerticalAlignment="Stretch" />
  26:      <TextBlock Grid.Column="1" Grid.Row="5" Text="{Binding Path=Duration}" Width="475" Foreground="#FFFFFFFF" HorizontalAlignment="Left" VerticalAlignment="Stretch" />
  27:  <

We could use the built in wrapping feature of Visual Studio, but this tends to muddy things up as well, since we dont know where Visual Studio will wrap the line, based on where the attribute we are looking for is so we are probably going to have just as hard a time finding what we are looking for anyway.
 
To make our code more readable, we can add a new line to the opening tag, before the first attribute. By doing the first attribute first, we set the automatic tabbing that Visual Studio will use as we move our cursor to the beginning of each successive attribute name, and hit Enter, moving the attribute to a new line.
 
After we do this for all the lengthy tags (tags with more than two or three attributes, which will cause you to have to scroll to the right to see them) we end up with something like this. Notice the way I also broke up the data binding on line 86 so there is no doubt about where the object boundaries are.
 
   1:  <Grid Width="600" Height="Auto">
   2:      <Grid.RowDefinitions>
   3:          <RowDefinition Height="Auto" />
   4:          <RowDefinition Height="Auto" />
   5:          <RowDefinition Height="Auto" />
   6:          <RowDefinition Height="Auto" />
   7:          <RowDefinition Height="Auto" />
   8:          <RowDefinition Height="Auto" />
   9:      </Grid.RowDefinitions>
  10:      <Grid.ColumnDefinitions>
  11:          <ColumnDefinition Width="Auto" MinWidth="113" />
  12:          <ColumnDefinition Width="*" />
  13:      </Grid.ColumnDefinitions>
  14:   
  15:      <Label 
  16:          Grid.Column="0" 
  17:          Grid.Row="0" 
  18:          Content="Description:" 
  19:          Foreground="#FFFFFFFF" 
  20:          HorizontalAlignment="Stretch" 
  21:          VerticalAlignment="Stretch" />
  22:      <Label 
  23:          Grid.Column="0" 
  24:          Grid.Row="1" 
  25:          Content="Status:" 
  26:          Foreground="#FFFFFFFF" 
  27:          HorizontalAlignment="Stretch" 
  28:          VerticalAlignment="Stretch" />
  29:      <Label 
  30:          Grid.Column="0" 
  31:          Grid.Row="2" 
  32:          Content="Percent Complete:" 
  33:          Foreground="#FFFFFFFF" 
  34:          HorizontalAlignment="Stretch" 
  35:          VerticalAlignment="Stretch" />
  36:      <Label 
  37:          Grid.Column="0" 
  38:          Grid.Row="3" 
  39:          Content="Critical Path:" 
  40:          Foreground="Black" 
  41:          HorizontalAlignment="Stretch" 
  42:          VerticalAlignment="Stretch" />
  43:      <Label 
  44:          Grid.Column="0" 
  45:          Grid.Row="4" 
  46:          Content="Start:" 
  47:          Foreground="#FFFFFFFF" 
  48:          HorizontalAlignment="Stretch" 
  49:          VerticalAlignment="Stretch" />
  50:      <Label 
  51:          Grid.Column="0" 
  52:          Grid.Row="5" 
  53:          Content="Duration:" 
  54:          Foreground="#FFFFFFFF" 
  55:          HorizontalAlignment="Stretch" 
  56:          VerticalAlignment="Stretch" />
  57:      <TextBlock 
  58:          Grid.Column="1" 
  59:          Grid.Row="0" 
  60:          Text="{Binding Path=ProcessDescription}" 
  61:          Width="475" 
  62:          TextWrapping="WrapWithOverflow" 
  63:          Foreground="#FFFFFFFF" 
  64:          HorizontalAlignment="Left" 
  65:          VerticalAlignment="Stretch" />
  66:      <TextBlock 
  67:          Grid.Column="1" 
  68:          Grid.Row="1" 
  69:          Text="{Binding Path=StatusDomainObj.StatusText}" 
  70:          Width="475" 
  71:          Foreground="#FFFFFFFF" 
  72:          HorizontalAlignment="Left" 
  73:          VerticalAlignment="Stretch" />
  74:      <TextBlock 
  75:          Grid.Column="1" 
  76:          Grid.Row="2" 
  77:          Text="{Binding Path=PercentComplete}" 
  78:          Width="475" 
  79:          Foreground="#FFFFFFFF" 
  80:          HorizontalAlignment="Left" 
  81:          VerticalAlignment="Stretch" />
  82:      <CheckBox 
  83:          Grid.Column="1" 
  84:          Grid.Row="3" 
  85:          Content="" 
  86:          IsChecked=
  87:          "{
  88:              Binding Path=IsCriticalPath, 
  89:              Mode=TwoWay, 
  90:              NotifyOnValidationError=True, 
  91:              UpdateSourceTrigger=PropertyChanged, 
  92:              ValidatesOnDataErrors=True, 
  93:              ValidatesOnExceptions=True
  94:          }" 
  95:          Width="475" 
  96:          HorizontalAlignment="Left" 
  97:          VerticalAlignment="Stretch" />
  98:      <TextBlock 
  99:          Grid.Column="1" 
 100:          Grid.Row="4" 
 101:          Text="{Binding Path=StartDelta}" 
 102:          Width="475" 
 103:          Foreground="#FFFFFFFF" 
 104:          HorizontalAlignment="Left" 
 105:          VerticalAlignment="Stretch" />
 106:      <TextBlock 
 107:          Grid.Column="1" 
 108:          Grid.Row="5" 
 109:          Text="{Binding Path=Duration}" 
 110:          Width="475" 
 111:          Foreground="#FFFFFFFF" 
 112:          HorizontalAlignment="Left" 
 113:          VerticalAlignment="Stretch" />
 114:  </Grid>

Stacking the attributes (and data bindings) this way makes our code much more readable, and easy to compare to other similar nodes. For example, we might notice that in the example above, the fourth Label control has the wrong foreground color, which would render the text invisible. This type of error is fairly visible when reading down through your code. Additionally, you can process the code much faster when only reading in one direction (down), instead of having to constantly mentally adjust between down and over, down and over. Throw scroll in there as well, and thats the formula for a very mentally trying review of your code. You should hopefully see a noticable difference in your ability to process your code mentally, after adopting this approach.
 
As you can see by the samples below, this approach works nicely for C# and T-SQL as well. Both editors by default support the automatic tabbing on new lines for the arguments being passed.
 
C# Before:
    Person p1 = new Person(){ FirstName="John", LastName="Smith", Email="john.smith@nowhere.com", Email2="jsmith123@nowhere.com", PhoneNumber="609-555-1234"};
    Person p2 = new Person(){ FirstName="Sally", LastName="Jones", Email="sally.jones@nowhere.com", Email2="sjones@nowhere.com", PhoneNumber="609-555-2345"};
    Person p3 = new Person(){ FirstName="Peter", LastName="Williams", Email="peter.williams@nowhere.com", Email2="pwilliams@nowhere.com", PhoneNumber="609-555-3456"};
    List<Person> people = new List<Person>();
    people.Add(p1);
    people.Add(p2);
    people.Add(p3);
    Person pOut = people.Where(p=>{return p.LastName == "Jones";}).First();
    MessageBox.Show(string.Format("{0} {1}",pOut.FirstName,pOut.LastName));
 
C# After (Notice the Lambda expression in the LINQ call is also broken out):
    Person p1 = new Person()
    { 
        FirstName="John", 
        LastName="Smith", 
        Email="john.smith@nowhere.com", 
        Email2="jsmith123@nowhere.com", 
        PhoneNumber="609-555-1234"
    };
    Person p2 = new Person()
    { 
        FirstName="Sally", 
        LastName="Jones", 
        Email="sally.jones@nowhere.com", 
        Email2="sjones@nowhere.com", 
        PhoneNumber="609-555-2345"
    };
    Person p3 = new Person()
    { 
        FirstName="Peter", 
        LastName="Williams", 
        Email="peter.williams@nowhere.com", 
        Email2="pwilliams@nowhere.com", 
        PhoneNumber="609-555-3456"
    };
    List<Person> people = new List<Person>();
    people.Add(p1);
    people.Add(p2);
    people.Add(p3);
    Person pOut = people.Where
        (
            p=>
            {
                return p.LastName == "Jones";
            }
        ).First();
    MessageBox.Show(
            string.Format
            (
                "{0} {1}",
                pOut.FirstName,
                pOut.LastName
            )
        );
 
SQL Before:
SELECT FirstName, LastName, ID, Email
FROM People
WHERE FirstName = "John" AND LastName="Smith" AND Email IS NOT NULL
ORDER BY LastName, FirstName, ID
 
SQL After:
SELECT       FirstName, 
                LastName, 
                ID, 
                Email

FROM        People

WHERE       FirstName = "John"
                AND 
                LastName = "Smith" 
                AND 
                Email IS NOT NULL

ORDER BY    LastName, 
                FirstName, 
                ID

Comments

There are no comments yet for this post.