Rob Smyth
Wednesday, 26 March 2008
HTML C# Code Fragments - Generator
I've found a great online tool to generate HTML for C# code fragments so I can put code fragments in posts on this blog. Check it out here.
Dependency Injection (IoC) & NDependencyInjection
In the last couple of months we have, at work, started using a new dependency injection (IoC) framework (NDependencyInjection) on a code base that had not been fully using IoC. As this is a more advanced pattern being retrospectively applied to an existing code base, it has been difficult to demonstrate the benefits. It had been a bit of leap of faith. But, in the last few days we have reached that 'critical mass' point were we are repeatedly finding that it has reduced our cost of change. Adding new features and refactoring has become easier. It is already saving us time ($$).
I'm finding that IoC simplifies:
Before:
After:
What I'm now finding is that I can add a parameter to a constructor and the application 'just works' without any other code changes as the IoC framework just finds the required objects using its wiring rules. No need to work out life cycles and walk up the ladder of factories. This means that the product's architecture is less likely to become corrupted as developers add more features. So it becomes an enabler for less skilled developers to work on the code.
One 'gotcha', and perhaps a future feature for NDependencyInjection is that adding a new parameter to a constructor does mean that unit tests must be updated to provide the mocked object. Wouldn't it be great if NDependencyInjection could be set to a unit testing mode for a given type so that when and instance of the type is requested all required types are generated as mocked objects? Perhaps a 'GetTestObject' method to compliment the current 'Get' method? Further code generation automation ... write less code and reduce the risk of less skilled developers introducing integration tests disguised as unit tests.
NDependencyInjection is well worth the effort. Great work Nigel!.
I'm finding that IoC simplifies:
- Object life cycle control.
- Object wiring.
- Object construction - Automates code generation for circular dependencies.
Before:
CfaRegionsChangedListenerConduit regionChangedConduit = new CfaRegionsChangedListenerConduit();
ICfaRegions cfaRegions = new CfaRegions(regionChangedConduit, persistenceService);
FormatterListenerConduit formatterListenerConduit = new FormatterListenerConduit();
IncidentGridViewCellFormatter incidentGridViewCellFormatter =
new IncidentGridViewCellFormatter(cfaRegions, formatterListenerConduit);
regionChangedConduit.SetTarget(incidentGridViewCellFormatter);
IncidentsGridViewController incidentsGridViewController = new IncidentsGridViewController();
IncidentsGridView incidentsGridView =
new IncidentsGridView(cfaDataSet, incidentsGridViewController, incidentGridViewCellFormatter);
incidentsGridViewController.Inject(incidentsGridView, new BrowserMapView());
formatterListenerConduit.SetTarget(incidentsGridViewController);
incidentsView = new IncidentsView(new RegionSelectionControl(cfaRegions), incidentsGridView);
hostServices.Show(incidentsView, DockState.Document);
After:
system.HasSubsystem(new IncidentsViewBuilder()).Provides<DockContent>();Although a trivial example, I find the after code to be more readable. It has also separated the object wiring from the usage code. The wiring necessary to build a ContentForm object is hidden in the builder. This makes code reuse easier. It also abstracts the use of common objects (in this case the CFADataSet and the PersistenceService. Traditionally this is done by using a factory, but that would required common objects to be passed explicitly down in a series of constructors/methods. This makes managing object life cycles much easier.
hostServices.Show(system.Get<ContentForm>(), DockState.Document);
:
public class IncidentsViewBuilder : ISubsystemBuilder
{
public void Build(ISystemDefinition system)
{
system.HasSingleton<CfaRegions>().Provides<ICfaRegions>();
system.HasSingleton<IncidentGridViewCellFormatter>().Provides<IncidentGridViewCellFormatter>();
system.HasSingleton<IncidentsGridViewController>().Provides<IncidentsGridViewController>();
system.HasSingleton<IncidentsGridView>().Provides<IncidentsGridView>();
system.HasSingleton<BrowserMapView>().Provides<BrowserMapView>();
system.HasSingleton<RegionSelectionControl>().Provides<RegionSelectionControl>();
system.HasSingleton<IncidentsView>().Provides<ContentForm>();
}
}
What I'm now finding is that I can add a parameter to a constructor and the application 'just works' without any other code changes as the IoC framework just finds the required objects using its wiring rules. No need to work out life cycles and walk up the ladder of factories. This means that the product's architecture is less likely to become corrupted as developers add more features. So it becomes an enabler for less skilled developers to work on the code.
One 'gotcha', and perhaps a future feature for NDependencyInjection is that adding a new parameter to a constructor does mean that unit tests must be updated to provide the mocked object. Wouldn't it be great if NDependencyInjection could be set to a unit testing mode for a given type so that when and instance of the type is requested all required types are generated as mocked objects? Perhaps a 'GetTestObject' method to compliment the current 'Get' method? Further code generation automation ... write less code and reduce the risk of less skilled developers introducing integration tests disguised as unit tests.
NDependencyInjection is well worth the effort. Great work Nigel!.
Thursday, 13 March 2008
CFA Reader Rev:1.0.0 Available
CFA incidents RSS reader (CFA Reader) rev 1.0.0 is now available here.
Features:
Features:
- Region filtering
- Colour highlighting of incident relevance with emphasis on bushfire/wildfire incidents.
- Double clicking on an incident shows the general location in Google Maps.
- Total fire ban notifications.
- Real time 'no touch' incident updates.
- Internet connection is not configurable. Will probably not work from behind a business firewall.
- Windows positions are not saved to disk. The application's panels will be in the same position each time the application is opened.
- No documentation/help.
Thursday, 6 March 2008
If You Break The Build - Revert
Our team at Varian Australia, decided in our last iteration to adopt the rule:
I learnt Subversion can revert changes in a commit, even if not the most recent commit, without loosing the changes ... quickly. TortoiseSVN offers options like "revert from ..." which allow a change set of just the changes in that commit to be reverted by a following commit of the change set. If using Continuouse Integration (CI) it means that a revert is very low cost (lost work).
The team has found it very enabling. We can break the build but if we do the break is only for minutes. It does mean that we do need a fast build.
I've worked in companies with slow builds (hours). This experience emphasises the need to always have a fast build. It is always possible it is just a matter of finding how. The increased productivity of Continuouse Integration is significant.
"If you break the build, revert your commit immediately."It has surprised me how successful this has been. We are using Subversion (SVN) and while I thought that I knew how to revert a commit I found I learnt so much more about the powerful features Subversion offers to revert a commit. More importantly I, and I think others, are now much more comfortable/confident on quickly reverting a commit. This is empowering, it lowers cost/inhibitors.
I learnt Subversion can revert changes in a commit, even if not the most recent commit, without loosing the changes ... quickly. TortoiseSVN offers options like "revert from ..." which allow a change set of just the changes in that commit to be reverted by a following commit of the change set. If using Continuouse Integration (CI) it means that a revert is very low cost (lost work).
The team has found it very enabling. We can break the build but if we do the break is only for minutes. It does mean that we do need a fast build.
I've worked in companies with slow builds (hours). This experience emphasises the need to always have a fast build. It is always possible it is just a matter of finding how. The increased productivity of Continuouse Integration is significant.
Unit Testing Internal C# Classes
I like to keep my production code and my unit testing code in separate assemblies. A downside of this is has been that all classes must be public but I have now found that C# does support 'friend' assemblies via an AssemblyInfo.cs attribute:
I have not used this attribute yet, but I like the idea of making classes as internal. It makes the intent (usage scope) self evident. I wonder if it will help detect orphaned code?[assembly: InternalsVisibleTo("UnitTests")]
Wednesday, 5 March 2008
It Takes GUTs To Succeed
Alistair Cockburn proposed the value of a TLA for Good Unit Tests in his blog mentioned on InfoQ here. The idea is best put in the InfoQ article as:
I could not resist the title :-)
Checkout his blog entry The modern programming professional has GUTs.He (Alistair) suggests that there is a shift in assertion by Bob on what makes a true professional. Though Bob starts with TDD, he seems to agree that to be a professional you need to have good unit tests.
Alistair believes that, till date, there has been no good term for "good unit tests," as there is for TDD. Had there been a term like 'GUTs' for good unit tests then people could brag about GUTs without implying whether they were written before or after the code.
I could not resist the title :-)
Sunday, 2 March 2008
VS Production/Test Macro Jumper
When using TDD it seems that repetitive patterns that we perform are:
- Create a test fixture for a class
- Create a test for a test a method
- Switch between the test fixture and the production code.
"while the job of software developers is to automate end user processes, it seems that developers are the last to automate their processes."So with this in mind I have written a Visual Studio macro to do one of the above, switch between a test fixture and the related production code class. Depending how this goes at work I may extend this to create the fixture and create template test cases for method. See how it goes.
Imports System
Imports EnvDTE
Imports EnvDTE80
Imports System.Diagnostics
Public Module Jumper
Sub BetweenProductionClassTestFixture()
'Copyright 2008 Robert Smyth'
''
'Jump between product class file and its test fixture'
'This macro makes the assumes that all unit tests for'
'classes in a files are located in a file of the same'
'name as the file being tested (production code) with'
'a Tests suffix.'
''
'This is based on the common practice of one class per'
'file, the filename being the class name, and one test
'fixture per class.'
''
'It also assumes:'
'- All test fixtures for an assembly are located in a'
' child folder called Tests.'
'- All test fixtures are located in a mirror folder'
' structure within the child folder called Tests.'
Dim fileNameExtension = System.IO.Path.GetExtension(Application.ActiveDocument.FullName)
Dim activeProject As Project = GetActiveSolutionProject()
Dim projectPath = System.IO.Path.GetDirectoryName(activeProject.FullName)
Dim currentFilePath = System.IO.Path.GetDirectoryName(Application.ActiveDocument.FullName)
Dim classRelativePath = Right(currentFilePath, Len(currentFilePath) - Len(projectPath))
Dim currentClassName
Dim newFilePath = ""
currentClassName = System.IO.Path.GetFileName(Application.ActiveDocument.FullName)
currentClassName = Left(currentClassName, Len(currentClassName) - Len(fileNameExtension))
If (Right(currentClassName, 5) = "Tests") Then
newFilePath = Left(currentClassName, Len(currentClassName) - Len("Tests")) + fileNameExtension
newFilePath = Left(projectPath, Len(projectPath) - Len("Tests")) + newFilePath
Else
newFilePath = currentClassName + "Tests" + fileNameExtension
newFilePath = projectPath + "\Tests" + classRelativePath + "\" + newFilePath
End If
If newFilePath <> "" Then
Application.Documents.Open(newFilePath)
End If
End Sub
Public Function GetActiveSolutionProject() As Project
' Sets global miPrj = currently selected project and
' return the project to the caller.
Dim projs As System.Array
Dim proj As Project
Dim projects As Projects
projs = DTE.ActiveSolutionProjects
If projs.Length > 0 Then
proj = CType(projs.GetValue(0), EnvDTE.Project)
Return proj
End If
End Function
End Module
Subscribe to:
Posts (Atom)