§ October 3, 2011 04:27 by
beefarino |
In the last few talks I’ve given on PowerShell for Developers, I’ve focused on build, test, and code automation. When discussing StudioShell, I’ve pulled a very effective demo from a real-life project…
After wrapping up an iteration with a client, they returned to me with a new 300,000-line code base spread over about 200 (very messy) files. They were in the process of implementing a set of “corporate” coding standards and wanted some help getting their existing codebase to these standards.
One standard in particular was that and property that meets these criteria:
- is public
- returns a boolean
should start with “Is”. So in this example code:
public class Demo
{
public bool Enabled { get;set; }
public bool Valid { get;set; }
public bool IsRunning { get;set; }
public string Name { get;set; }
private bool Applied { get;set; }
}
The properties Enabled and Valid need to be renamed to IsEnabled and IsValid (lines 3 & 4). The IsRunning property already meets the standard (line5), the Name property returns a non-boolean type so the standard doesn’t apply (line 6), and the Applied property is private and excluded from the standard. In the end this file needs to look as follows:
public class Demo
{
public bool IsEnabled { get;set; }
public bool IsValid { get;set; }
public bool IsRunning { get;set; }
public string Name { get;set; }
private bool Applied { get;set; }
}
While it’s entirely possible to apply this standard by hand, the thought of scouring 300,000+ lines of code for properties that meet this criteria is not an attractive task. FxCop can probably find these properties for us, but it can’t apply a fix – that still requires human intervention. Best case scenario, I’m looking at days of mind-numbing monkey-on-a-keyboard work.
This is the kind of problem that screams for automation. This is where StudioShell shines. Here’s how I slashed through this Gordian knot…
First, I isolated a single code file in my StudioShell console, so I could work out the pipeline logic I needed without obliterating the codebase:
> cd DTE:\solution\codemodel\demoapp\program.cs
Second, I use PowerShell’s filtering capabilities to isolate all public properties in the file:
# spacing added for readability
> ls -recurse | where {
$_-match 'property' -and
$_.access -match 'public' }
Location: dte:\solution\codemodel\demoapp\program...
File: program.cs
Code Container: Class Demo
Available Operations: d+ <
Kind Name
----- ---- ---------
d+ < vsCMElementProperty Enabled
d+ < vsCMElementProperty Valid
d+ < vsCMElementProperty IsRunning
d+ < vsCMElementProperty Name
Once I have all the properties isolated, I need to filter the properties to those that returning booleans. This required a little investigation using get-member, but once I found the Type property of the CodeProperty class, I was home free:
#spacing added for readability
> ls -recurse | where { $_-match 'property' -and
$_.access -match 'public' -and
$_.type.asFullName -match 'bool' }
Location: dte:\solution\codemodel\demoapp\program...
File: program.cs
Code Container: Class Demo
Available Operations: d+ <
Kind Name
-------------- ----
d+ < vsCMElementProperty Enabled
d+ < vsCMElementProperty Valid
d+ < vsCMElementProperty IsRunning
The last factor to consider is the property name – I only need names that do not start with “Is”. Easily done:
#spacing added for readability
> ls -recurse | where { $_-match 'property' -and
$_.access -match 'public' -and
$_.type.asFullName -match 'bool' -and
$_.name -notmatch '^Is' }
Location: dte:\solution\codemodel\demoapp\program...
File: program.cs
Code Container: Class Demo
Available Operations: d+ <
Kind Name
------------- -----
d+ < vsCMElementProperty Enabled
d+ < vsCMElementProperty Valid
At this point, my pipeline is filtering out the correct properties – only public properties returning a boolean that do not start with “Is” are being included in the pipeline output. The next step is to take corrective action against these offending property names. The RenameSymbol method will execute the appropriate rename activity for me, ensuring that the new name is propagated across the entire codebase that is loaded in Visual Studio:
#spacing added for readability
> ls -recurse | where { $_-match 'property' -and
$_.access -match 'public' -and
$_.type.asFullName -match 'bool' } |
foreach { $_.RenameSymbol( "Is"+$_.Name ) }
> ls
Location: dte:\solution\codemodel\demoapp\program...
File: program.cs
Code Container: Class Demo
Available Operations: d+ <
Kind Name
-------------- ----
d+ < vsCMElementProperty IsEnabled
d+ < vsCMElementProperty IsValid
d+ < vsCMElementProperty IsRunning
d+ < vsCMElementProperty Name
d+ < vsCMElementProperty Applied
Once I verified that this worked as expected, applying the changes across the entire code base was a trivial matter of changing the root of my recursive search to the root of the code base:
#spacing added for readability
> ls dte:/solution/codemodel -recurse | where {
$_-match 'property' -and
$_.access -match 'public' -and
$_.type.asFullName -match 'bool'
} | foreach { $_.RenameSymbol( "Is"+$_.Name ) }
Done. The total time it took me to complete the task across the 200+ files, including the necessary research, prototyping, and the full script execution: less than 30 minutes. My input focus never moved from the StudioShell console, and my hands never left the keyboard (except when the script was running across the code base, during which time I helped my kids study their math facts).
This is why I made StudioShell – so I can scale iterative code and IDE solutions the same way an IT pro scales administrative solutions. If a solution can be applied to a single line of your code, StudioShell can be apply it to all lines of your code.