CodeBetter.Com
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @CodeBetter

Jeremy D. Miller -- The Shade Tree Developer

Under the hood and working with .Net, TDD, Software Design, and Agile Stuff

August 2005 - Posts

  • How Agile practices can help staunch the flow of defects

    I was in a project retrospective meeting today, and I’ve got another one tomorrow.  I think we had a fairly low rate of defects compared to the complexity of the code, but we had some definite inefficiency in the amount of time it took us to detect and resolve issues.  Some of the defects were clearly preventable, so it’s time to reflect on our development approaches.

     

    Defects are an inevitable fact in any nontrivial project.  If you think about it, a lot of the activities in a software project are dedicated to the detection and elimination of defects.  The problem with defects is the inherent overhead associated with the administrative process of tracking defects.  The efficiency of your project can be greatly affected by excessive churn as issues bounce back and forth between developers and testers.  Not to mention decreased team morale (and increased management stress) as the bug count rises.

     

    Much of our project retrospectives have involved determining ways to staunch the flow of defects by preventing them.  The first step is to think about the sources of our defects.  The second step is to determine what practices can be applied to cut down our defects. 

    Another topic dear to my heart is optimizing the total time it takes to fix a bug. 

     

    Our approach is based on Scrum and XP, so I’m naturally thinking about how and where Agile practices can both help reduce the number of defects and make defect fixes more efficient.  Agile development isn’t a silver bullet for defects, but I’ve observed a quality of “smoothness” in the disciplined Agile projects I’ve been on that clearly wasn’t there on previous waterfall projects (or sloppy Agile projects for that matter). 

     

    Finding Defects Early

     

    It’s generally accepted in the software industry that defects are easier to correct the sooner they are detected.  One of the best things about Agile development is involving the testers early inside of rapid iterations.  It’s so much simpler to fix a defect in code that you’ve written a couple of days previously than it is to spelunk code that’s 4-5 months old.  Not to mention the difficulty of fixing a bug is much greater if it’s intertwined with a great deal of the later code (I do think that TDD alleviates this to some degree by more or less forcing developers to write loosely coupled code).

     

    Developer Mistakes

     

    Most defects are simply a result of developer error.  Most of these defects are simple in nature.  Disciplined Test Driven Development goes a long way towards eliminating a lot of bugs.  Writing the tests first in the TDD manner means that our code behaves exactly the way we intended.  I feel pretty confident about making the statement that TDD drastically mitigates the number of bugs due to simple developer error.  Looking closely at the bugs that we had on the last project showed an obvious correlation between the areas of code with poor unit test coverage and the defects that I felt were mostly attributable to developer error. 

     

    If you’re suffering a rash of defects, it’s worth your time to think about how you’re doing unit testing and find a remedy.

     

    Of course it’s not that hard to create incorrect unit tests, but that’s where Pair Programming should come into play.  Having another developer actively involved with both the unit testing and the coding should act as a continuous code review to correct the unit tests. 

     

    There is also the issue of whether a developer fully understands the code they’re writing.    Having a second mind engaged on the coding problem at hand should increase the total understanding.  The simple act of talking about a coding problem with another developer can lead to a better understanding of the code.

     

    Not Understanding Requirements

     

    We clearly think that requirements defects have been our Achilles Heel so far.  Consistently using TDD and CI means that our code mostly worked the way we developers intended, but doesn’t guarantee that we’re creating the correct functionality.  As I see it, there are three issues here:

     

    1.      Determining the requirements

    2.      Communicating the requirements in an unambiguous manner to the developers to create a shared understanding between the analysts, developers, and testers

    3.      Automating the conformance to the requirements to stop the “Ping Pong” iterations of fixing defects

     

    Our thinking right now is to utilize FitNesse as the primary mechanism to solve these three issues by doing Acceptance Test Driven Development.  I’ll blog much more on this later, because we’re still figuring out how this impacts our iteration management and who’s responsible for what work and when.

     

    In the meantime, I’d strongly recommend picking up a copy of Fit for Developing Software : Framework for Integrated Tests by Ward Cunningham and Rick Mugridge for a background on using FIT for acceptance testing.  My first experience with FIT wasn’t all that positive, but I’m rapidly changing my mind after reading the book.  I’m optimistic about FitNesse so far.

     

     

     

    Edge Cases

     

    Lately I’ve been dealing with about a dozen defects that can only be described as “edge cases.”  These bugs are a combination of inputs or actions that nobody anticipated.  Some of these bugs might just be from some missed analysis, but a lot of these bugs are never going to be caught until later in the project when the team has a much better understanding of the project domain.  Either way, I think the appropriate action is to turn to the tester and just say “Good catch, I’ll get right on it.” 

     

    I think that Agile practices indirectly contribute to catching and eliminating these kinds of defects.  By more quickly eliminating the defects in the mainline code logic with TDD and Acceptance Testing, testers *should* have more time to do the kind of intensive exploratory testing that finds problems like the one Jonathon Kohl talks about here.  There’s also the very real benefit of the automated test suites acting as a safety net to mitigate the creation of new regression bugs.

     

    This section would be a lot longer, but I think Charles Miller sums up the subject better anyway right here.

     

     

    Invalid Testing Environment

     

    Occasionally something will go wrong in the testing environment that basically invalidates any and all test runs.  Maybe a testing database isn’t available, a URL to a web service is configured incorrectly (can you say scar tissue?), or a Windows service isn’t correctly installed on a test server.  All of these things lead to testers either sitting on their hands waiting for you to get the test environment fixed, or report a batch of bugs that aren’t necessarily due to coding mistakes.  On previous projects I’ve often been saddled with bugs that arose because the database stored procedures were updated through a different process than the middle tier code.  A particularly irksome situation is when the new version doesn’t get correctly installed before the tester tries to re-test (we had an issue with this last week with an MSI installer created with WiX).

     

    This kind of project friction needs to be eliminated.  One of the best tools is an automated build script chained to a Continuous Integration practice.  At this point, I would unequivocally say that any project team that doesn’t have a dependable automated build of some sort is amateurish, period. 

     

    A good automated build can shut down the chances of an invalid testing environment.  Using CI should serve to keep the testers from wasting their finite time on obviously incorrect builds.  CI also reduces the amount of time between checking in bug fixes and making the code push to the testing environment while simultaneously improving the reliability of the code pushes.

     

    I’d also recommend creating a small battery of environment tests that run in your testing environment after code moves just to validate that all the moving pieces (databases, web services, Windows services, etc.) are accessible from the test application.  My team just inherited a product with quite a few external dependencies that are hard to troubleshoot from the integration tests.  We’ll be writing some automated tests just to diagnose environment issues before the integration tests run in the automated builds. 

     

    I’ve developed a strategy for self-validating configuration based on my StructureMap tool that’s relevant.  I’ll blog on this soon.

  • The Importance of Being Explicit

    I've gotten burned several times lately with little defects that are indirectly caused by implicit "read the tea leaves" style programming.  Here's an example of what I mean taken from my work last week.

    <Rule DayLimit="5" />
    

    It's just an attribute in an XML configuration file that specifies configurable business rules, no big deal.  The problem was that a value of "0" in the DayLimit attribute was interpreted as a completely different business rule than a positive value in the DayLimit attribute.  Fortunately an automated regression test picked up the forgotten requirement.  I think it would have elminated some confusion if there had been a separate attribute for the "0" case like AllowPostDating="False" to be more explicit.

    I caused an edge case bug by a little bit of sloppy programming.   I was pulling data from some validation tables in the database into an array of objects that would be consumed by business logic classes.  In this case it was perfectly legal to have "child" rows even if the "header" row didn't exist.  In the case of orphan records, I just created the header object and assigned a zero value to it's Rate property.  In some cases during business validation I would check whether Rate != 0 to check if the business entity existed at the header level. This was confusing, but workable until some automated tests failed with false validation errors when existing header records had a rate of zero.  Once I understood the issue, I corrected the object structure to make the existence test more explicit like the following.

    	public class ImplicitRecord
    	{
    		private decimal _rate;
    
    		public decimal Rate
    		{
    			get { return _rate; }
    			set { _rate = value; }
    		}
    	}
    
    	public class ImplicitBusinessClass
    	{
    		public void Process(ImplicitRecord record)
    		{
    			// Check if there is ANY rate
    			if (record.Rate == 0)
    			{
    				// create an error message
    			}
    		}
    	}
    
    	public class ExplicitRecord
    	{
    		private decimal _rate;
    		private bool _hasRate;
    
    		public decimal Rate
    		{
    			get { return _rate; }
    			set { _rate = value; }
    		}
    
    		public bool HasRate
    		{
    			get { return _hasRate; }
    			set { _hasRate = value; }
    		}
    	}
    
    	public class ExplicitBusinessClass
    	{
    		public void Process(ExplicitRecord record)
    		{
    			// Check if there is ANY rate
    			if (!record.HasRate)
    			{
    				// create an error message
    			}
    		}
    	}
    

    One of the scariest, most error prone idioms in all of software development is passing around a Hashtable/ArrayList of Hashtable/ArrayList objects.  How many pernicious bugs have been caused by fouling up the key values to the Request, Session, and QueryString collections?  Assuming you have a choice in the matter, which class below would you rather consume based on it's public API?  The answer is ExplicitActionClass unless you're being perverse just to spite me.  Remember that other developers will follow behind you, so code to reduce the probability of their mistakes.  Make the public API easy to use and intention revealing.

    	public class HashtableActionClass
    	{
    		public ArrayList Execute(Hashtable arguments)
    		{
    			// unwrap things in the arguments hashtable and perform work
    			string userName = (string) arguments["USER_NAME"];
    			decimal purchaseAmount = (decimal) arguments["PURCHASE_AMOUNT"];
    
    			// perform work and return an answer
    
    			return new ArrayList();
    		}
    	}
    
    	public class ReturnClass
    	{
    	}
    
    	public class ExplicitActionClass
    	{
    		public ReturnClass Execute(string userName, decimal purchaseAmount)
    		{
    			ReturnClass returnValue = new ReturnClass();
    
    			// perform work and log results to the returnValue variable
    
    			return returnValue;
    		} 
    	}
    

    I've never coded in C++, but I'm guessing that passing around pointer structures between objects led to some truly wicked bugs.

    Evil Databases

    Ambiguous database designs can cause even more damage.  I've often run across database tables whose columns represent very different conceptual things depending upon the values in another column.  I know there is a little bit of database inefficiency by having a bunch of columns with lots of null data, but I'd still much rather have separate columns for separate logical concepts and pieces of information. 

    I've been in several situations where the only way to integrate two or more systems was to directly peek into another system's underlying database.  This is fraught with so much risk that it's borderline insane.  It's risky because you're duplicating a lot of logic required to "interpret" the business meaning in the underlying data.  On one hand you have to correctly reproduce the interpretation, and on the other hand you must keep the duplicated logic synchronized through later changes.  The synchronization just isn't going to happen because the different codebases are probably being built and tested separately.   Any change in the database schema becomes risky without coordinated, large scale automated testing on every downstream system or any form of compile time checks keeping the applications synchronized with the database. 

    At a previous employer we had an absolutely humongous Operational Data Store (ODS) that contained answers to every question.  Many applications in the enterprise touched this monster.  A much greener, more idealistic version of myself foolishly tried to give the ODS team a suggestion to optimize a terribly sluggish view I needed to access.  I basically had my ear chewed off and told that the code change would take 6 months of regression testing to do something like that.  I ended up creating a polling mechanism to cache the very important, extremely volatile data every 15 minutes just so our application could actually function with any kind of decent performance.  The biggest usage of our user interface turned out to be a report on the cached data that we'd thrown in at the last minute as a "nice to do" feature request.  We almost had to add another web server to the farm just because of the demand for this data that was too difficult to pull out of the ODS.

    I think that people are dangerously overexuberant about SOA in general, but prudent usage of SOA should eliminate the need for duplicating the fragile database sharing crap, and that sounds pretty darn good to me (using web services as a thin pass-through data layer instead of raw ADO.NET or JDBC seems rather foolish to me though). 

    I feel that coding explicitly is orthogonal to the Static versus Dynamic Typing debate.  "Duck Typing" is one thing, but hidden meaning in code is just plain bad coding.

  • Good quote from today

    During an iteration kickoff meeting--

    "Guys, I know you're just becoming familiar with XYZ, but could you tell us when every un-scheduled downtime event is going to be this year?"

    Promptly followed by:

    "Can you go ahead and estimate the time to fix the bugs we haven't found yet?"

    He was kidding.  I think.

  • Yes, but CruiseControl.Net and its ilk are still important

    James Shore has been running a series of somewhat controversial posts about continuous integration. 

    The gist of his writings is that the benefit of continuous integration is realized by the discipline and approach of the team, not tools like CruiseControl (that silly "People over Tools and Processes" thing again.  He really needs to go back for some CMM reeducation).  He's also decrying the seeming reliance on CruiseControl as a crutch instead of a last chance safety net.  I touched on the same subject a couple of weeks back on a post culled from the CI talks I have done this summer:  Using Continuous Integration? Better do the "Check In Dance".

    I agree with him, except about the usefulness and goodness of a tool like CruiseControl.Net.  Even if your team is very disciplined about doing the "Check In Dance," it's still useful to have CruiseControl.Net around to do builds on a clean box.  Like Shore says on his blog, people are always human.  From time to time you're going to miss checking in a prerequisite code file or dependency assembly.  Better source control clients like Ankh or TortoiseSVN for Subversion have cut that down to almost nil for us, but every so often it still happens.  Those poor fools trying to do nontrivial development with VSS for their source control will suffer greatly from this (The WinCVS client for CVS is another common culprit).

    Getting down into the muck of developing software systems, another very important raison d'etre for CC.NET is to catch environment setup needs.  When we install Visual Studio.Net on a developer workstation the installation leaves all kinds of binaries scattered over your GAC, not to mention some lingering COM DLL's.  These kind of dependencies might not be noticed when you're running the build locally, but will almost certainly blow up the clean CC.NET build.  Using Crystal Reports comes to mind rather quickly.  Another example that's bitten me plenty of times is making some sort of tweak to the local IIS web server to accomodate the code.  Running locally?  No problems.  Running on CC.NET forces me to put the environment setup into the NAnt build script to keep everyone else from getting fouled up by the changed environment dependency.  Bugs or just inefficiency due to environment mismatches is a huge pet peeve of mine. 

    Those are just some sample reasons to have a CI tool.  Communication, records keeping, and statistics gathering are also aided by a CI tool.  Take the "people" aspect of CI very seriously, but don't throw away CruiseControl.Net by any means.

  • Application Re-write Wackiness

    To any colleagues that stumble across this, please don't take any offense.  It was a bad, bad day around the Austin office that day. - Jeremy 10/26/2005

     

     

    Have you ever had to do a re-write of an existing application?  If you have, you'll know exactly what I've been going through much of this year.  The entire requirements is often "just make it work like <old thing>."  Nobody really understands how the old thing works, so you have to go spelunking in the code to yank out business logic.  The fun part is that the old system is usually being rewritten specifically because the code is rotten.  There might be some documentation left over from the original project, but it's a lie until proven truthful.  I've had some success with what Michael Feathers describes as "characterization" tests to verify the new system replicates the old, but I feel so much better whenever there is some kind of real business requirement from real business people driving the development.

    My team built a subsystem in C# to replace a gnarly VB6 COM component that was accurately code-named Marquis (De Sade.  I'm not making this up) that was partially written by one of the company's founders.  My early investigation of that code spawned a longish rant on absurdly large classes and methods.  Just to put it into perspective, one giant VB6 class module in the old world with a "Select Case from Hell" section literally became about a hundred little C# classes.  I'm guessing that when the original developers left the company that their keyboards exhibited excessive wear and tear on the "CTRL", "C", and "V" keys from all the copy/paste action.

    The absolute worst development activity has to be data migration from one database structure to another.  It never goes smoothly, but you learn a *lot* about how the existing system actually works by getting your fingers burned by working with legacy data.  We're reusing the database, but we're translating the old configuration files to the new system.  Inevitably we've found a lot of obscure, undocumented quirks in the old configuration format that's causing me some heartburn.  What's really been fun is that asking questions to the three business experts on the existing system leads to three different answers in how it's supposed to be working.

    Yesterday and this morning I've been fixing "defects" in the new system that arose from doing regression testing that compares the results from the old system to the results from the new.  Some of the defects are just edge cases, so nobody gets too upset [Edit:  I don't mean to imply that the bugs aren't serious, just that these bugs are requirement things that slipped through the net and odd combinations, not purely developer error.  I feel bad about bugs that are just a failure to unit test or developer sloppiness].  Several of them have been nothing but recreating a quirk in the old system.  In at least one case I'm pretty sure that we're recreating a bug in the original system, dammit!  The original system had a very permissive XML format for rules configuration.  I'm having to recreate how the old system behaves with what is supposed to be invalid configuration (ugh).  The one thing that absolutely set me off yesterday was finding out a legacy database table did not have any unique constraints.  Silly me for assuming the tables were designed intelligently.  Lesson re-learned, don't play with strange databases.  Apparently an unhealthy obsession with T-SQL coding doesn't go hand in hand with a healthy regard for database niceties like primary keys and unique constraints.  Both the old and new code simply looks at the first row that comes up in the query.  Essentially, the old code uses a randomly selected row to use as the "official" data for a validation rule.  Strangely enough, this is generating odd bugs.  Go figure.  Since we can't go clean up the data, we're having to create an algorithm to pull out the correct validation row out of a bag of rows.  So, just how do you recreate effectively indeterminate behavior?

    Of course what I'm really whining about today is that the edge cases spring a leak in what was a nice, tidy Chain of Responsibility pattern abstraction.  Oh well.

    [Edit:  Removed some offensive wording.  My bad] Today's exercise in silliness involved a NetMeeting session to write NFit tests for the edge case bugs.  The meeting went just fine and the NFit fixtures help to define the expected behavior, but when I looked at the list of screen names I saw "The Dragon Reborn", "Yoda", and "UmpaLumpa."  Yes, we're geeky.

    On a re-write death march several years ago a business analyst told me that she had completed the business requirement specification for the replacement in a only a couple of weeks.  I think I hurt her feelings when I laughed at her.  I explained that there was a tremendous amount of hard-coded business rules lurking in the code that didn't appear in any old documentation.  The project team had had a very serious morale problem over the years and suffered a couple rounds of complete team turnover.  Needless to say, there wasn't any tribal knowledge to fill in the blanks of the incomplete documentation.  We either had to recreate all the functionality or do a lot of serious analysis to determine which rules were dead code (I suspect most of the hard-wired exception cases were long since obsolete).  The project was cancelled before we got a chance to screw it up.

    My first client engagement as a consultant was one of the oddest projects I've ever been on.  The client (a diehard J2EE shop) had just purchased a homegrown ASP.NET application from one of their partners for an exorbitant fee.  The client had purchased the application sight unseen.  Our job was to take the purchased system and do whatever it took to put it into production.  About the time we discovered that the ASP.NET application could only support one user at a time*.  "Whatever it took" turned out to be reusing the custom graphics and rewriting everything else.  The idiots that purchased the system couldn't admit to their management that they had screwed up and had to do a rewrite, so we couldn't get any real support for requirements analysis or testing.  Our entire requirement specification was to "make it work like XYZ."  One little problem, XYZ was a prototype that had never been finished.  Major modules didn't function at all.  When we would reply that "XYZ didn't do anything yet" we were told to do what the mocked up screen seemed to be doing.  The project was later declared a failure and our team was the scapegoat.  Sigh.

    For that matter, every single project I did as a consultant was wacky.  Three projects, three strong teams of developers, three unsatisfying project outcomes.  One project was unnecessary architectural refactoring and the other two were severe cases of requirements mismanagement.

    *Instead of using the nice built in IPrincipal/IUser security stuff in .Net they had stored their own custom user object on a static field on the Global class for the web application.  Anytime a new user logged on they effectively kicked everyone else off.  True story.  You can't make this stuff up.

     

  • Testing Granularity, Feedback Cycles, and Holistic Development

    So far this year I've done a greenfield development project, made changes to a legacy application, and am about to take over development on a third product.  The new code was built with Test Driven Development and Continuous Integration from the beginning.  It's relatively simple to create granular unit tests and work in a TDD manner.  Our feedback cycles between coding and testing are fairly short.  I can work confidently with the code because I know I can verify the correctness of the code at any time.  Compliance with the requirements via automated acceptance tests is a different matter, but we'll get there. 

    By contrast, the older code wasn't built with TDD or CI and it shows.  We're in an unfortunate situation with the legacy code.  I've been employing the dependency breaking techniques from Michael Feather's book on legacy code to write new code with unit tests, but the interaction with existing code has been the source of most of the errors.  In order to do any kind of integrated testing I effectively have to migrate code changes over to a Virtual PC image via an MSI package (don't ask, I don't want to talk about it) and take several manual steps to cleanse the test environment.  That sluggishness in the feedback cycle was extremely frustrating and helped stretch a miserable project by a couple of weeks.  The work went a lot faster when I partially automated the code migration with some shortcuts.

    One of the sharpest contrasts between the greenfield code and the legacy code has been the length of time between doing something and the subsequent verification of that something.  Rapid and continuous feedback is one of the best and most important attributes of Agile development.  Without constant and efficient feedback mechanisms, the coding work has many more chances to go off in the wrong directions.  Moreover, the adaptive manner of doing work in Agile projects is dangerous without the constant corrective steps generated from feedback.

    Test Small Before Testing Big

    Things always go better when you code and test in small chunks.  The difficulty in diagnosing a test failure is geometrically proportional to the granularity of the test.  Granular unit tests are easy to debug.  Coarse-grained integration tests involving a dozen classes and external resources are many times more difficult to handle when they fail.  Do the granular unit tests first to mitigate the "debugger hell" of the large integration tests.  Don't even attempt to execute a large integration test until you are relatively certain that each individual piece of code works in its own set of unit tests.  The product that I'm working on for the rest of the year has very good coverage from integration tests, but rather anemic coverage from unit tests.  We'll be able to make changes in the code with confidence because of the integration test coverage, but fixing any regression bugs is going to be miserable because of the spotty unit tests.  I'm not writing this post to criticize my peers, I just think that writing the unit tests first would have made their development go faster.

    One of the things Feathers mentions in his book is that tests are also a way to preserve behavior in the face of later changes.  A rigorous unit test suite should tell you exactly when and where your new code changes break existing code.  Coarse-grained tests may only tell you that you've broken something -- somewhere.

    Unit Tests Should Be Fast

    I want to write code in a rapid cycle of "write a unit test, make unit test pass, refactor, repeat."  We use TestDriven.Net to quickly compile and run individual unit tests from the IDE as we're constructing the code.  In a normal coding day I may literally be running several hundred unit test runs, each with an accompanying compile of the code.  A slow compile step or post-compile step (watch what you're doing in post-build steps!) can bring coding velocity down to a crawl.  The product I just inherited has up to a 4 1/2 minute compile time for the whole solution and a minute plus for running an individual unit test.  That compile time is nothing but overhead and friction.  We've identified a strategy for consolidating the code to bring the compile time down, but it won't be fun.  The code consolidation may be miserable and time consuming, but say I really do run unit tests at least a hundred times a day.  If I can cut just a half minute off the compile time I've saved almost an hour of thumb twiddling a day.  Over time the overhead charge of the code consolidation will rapidly pay for itself many times over in improved developer efficiency.

    Optimizing Build Time

    If you're doing Continuous Integration, you want the build to be as fast as possible for quick feedback.  Long build times are an insidious form of project friction.  The whole point of doing CI is to know the current code revision is in a valid state.  SDTimes is running an article on the challenges and tools for streamlining the build process for larger projects.  There is a great quote in the article from Pragmatic Programmer Andy Hunt - "Teams that want to be more agile are headed for a train wreck if they have long build times; they’ll need to find ways to build all or part of the software more frequently to get the kind of continuous feedback that helps agile teams move quickly."

    My colleague and I have had the opportunity to present on Continuous Integration a couple of times this year.  We spend a little bit of time talking about strategies to optimize the build process.  Typically you would expect the automated testing to be the bottleneck.  Integration and blackbox tests can be relegated to a secondary staged build, but unit testing is pretty well mandatory for adequate feedback.  Testing database access, user interface screens or web pages, and SOAP calls are obvious culprits for slow tests.  I know that web services are the greatest invention since fire, but they're absolutely unusable for quick unit testing.  Here is some of my recommendations for dealing with slow unit testing.

    1. Aggressively mock the database during unit tests (don't mock ADO.NET directly though, that'll hurt).  A domain model approach with "Persistence Ignorant" classes is even better for optimizing unit test execution times.  The major point being to leave yourself a way to test business and control logic without the database.
    2. Use some sort of Model View Presenter architecture for user interface code, a.k.a. "The Humble Dialog Box."  Isolate as much user interface code as possible from whatever GUI framework you're developing with.  Slice the actual view code as thin as possible and mock the view when you test the controllers.
    3. Always use the Dependency Inversion Principle when accessing web services in code.  Leave a mechanism to mock out the web services while testing client code.
    4. Decouple the actual functionality of a web service from the SOAP message transport.  The web service should be a thin wrapper delegating to a plain old object.  If you can bypass the SOAP serialization and the HTTP calls your unit tests will be much faster.  I don't know about the J2EE world, but in .Net development the IIS web server is a huge pain in the ass during unit testing.

    The point I'm trying to make with this list is that the primary way to optimize the build process is by making appropriate application design and architecture choices.  An experienced TDD team will purposely design an application with ease of testing and build automation as a first class consideration.  Just as an aside I have my own private "Chicken or Egg" debate.  I don't know if your code is testable because you're doing TDD or if you write testable code in order to do TDD.  Either way, TDD and CI will be miserable experiences unless a team builds up a feel for creating testable code.

    Holistic Approach

    One of the most profound philosophic shifts in Agile development may be a holistic view of software development.  The real goal of my work is to get the code into production, not just over to the testers on time.  Blurring the responsibilities and roles on a software development team leads to better informed individuals that can look at the bigger picture.  Getting code into production requires testing to remove defects, so I start building code to be easier to test.  The team's velocity is affected greatly by the build process, so I architect the application to be both easier to deploy (X-Copy!) and faster to build with automated testing.  Designing for testability will very often require more coding than you would do otherwise.  The extra code in unit test suites is an obvious source of objections to doing TDD.  In the end, the extra time spent writing unit test code seems to consistently pay off by getting the code to production quality faster. 

    One of my colleagues made a simple, profound statement to me last week that "TDD drives you to a different approach."  Keeping in mind that TDD is only the means and not an end in itself, my reply is that any technique that gets me to done faster is worth writing more or just different code.

    I think that unnecessary specialization of personnel in software development causes a tremendous amount of damage in our industry.  How in the world can a Non-Coding Architect truly account upfront for ease of testing, building, and deployment if they're never involved in the downstream project activities?  I honestly think that being much more involved in the entire software process has made me a better software designer.

    The "Idea Wall"

    A nice practice I've used on a couple of Agile projects now is the "Idea Wall."  Pick some visible spot on the wall in the team area, and post any sort of idea for improving the project infrastructure on "Post-It" notes or index cards.  Distributed teams can use a Wiki instead.  Capture any issue that is slowing down the team like slow compiles, missing environment setup, or nice to have items like code statistics or better testing tools.  When any developer is idle they can pick something off of the idea wall.  If your team is doing pair programming, you can use the idea wall as a way of collecting non-coding tasks for the "exposed" person on odd-numbered teams.  If the idea wall starts to get too cluttered, it may be a good sign that you're incurring unnecessary friction and you really need to invest in the build infrastructure.  Don't tolerate inefficiency in your build or development.  Sometimes you really need to stop and work on the build before you write any new code.  Project managers need to understand the necessity of these kinds of chores.

     

    If you actually finished reading this post, thank you for putting up with a bag of incoherent rambling.

     

  • James Shore dissects the FBI's Trilogy project failure

    James Shore has a good article in the current issue of SDTimes called It’s Not Too Late to Learn about the massive failure of the FBI's Trilogy project.   
  • Use a refactoring checklist to pay down Technical Debt as you work

    If you've never heard the term "Technical Debt," check out Martin Fowler's definition and Ward Cunningham's Wiki on the subject.

    The triumvirite TDD coding cycle of "Red, Green, Refactor" is plastered all over the web and endlessly repeated by all us TDD zombies.  If you're going to drink at the agile Koolaid fountain, don't stop with just "Red Bar, Green Bar."  You've got to do the third part and refactor as you work to keep the code clean.  By itself the "Green Bar" is not some sort of holy talisman that wards off all coding evil.  Your code may be passing all of its unit tests today, but can you easily add more code to the solution tomorrow?  Is your code becoming difficult to read and understand?  Are you spending too much time with your debugger?  Is there a class or namespace you avoid changing out of fear? 

    You will slow down if you let problems accumulate.  If you're using evolutionary or incremental design you probably don't know exactly where you're going with your design.  You are purposely delaying design decisions until you have more information and knowledge about the design needs.  Keeping the code clean and each class cohesive with low coupling will maximize your ability to change later.  Applications are often discarded and replaced when they become uneconomical or too risky to alter.  Constant vigilence and an adherence to good coding practice can extend the lifecycle of a system by reducing the risk and cost of change.

    Use aggressive refactoring to keep the technical debt from building up.  Integrate refactoring into your normal working rhythm, don't let it just build up into day long tasks.  Here's the good news though -- a lot of refactoring work is quick and easy, especially if you'll invest some time in learning the automatic refactorings in tools like ReSharper.  Look for opportunities to make quick structural improvements.  Here's a sample checklist of quick refactorings (with the ReSharper shortcut combinations) to make on your code as you work.  As you work, constantly scan and analyze the code you've just written with something like this little checklist and make small refactorings.   At a bare minimum, make a refactoring pass before any check in.

    1. Does the name of the class still reflect its purpose?  If not, rename the class (SHIFT-F6).  Now, not later. 
    2. Does the name of the method describe the functionality?  If not, Rename Method (SHIFT-F6).
    3. If any method is too long, Extract Method (CTRL-ALT-M) to break up the method into smaller methods.  Be aggressive with this technique.  Long methods and classes are evil.  If there is any part of a method that has a distinct purpose, pull it out.
    4. If a method is very complex and has a lot of temporary variables and state, consider pulling the method out into its own class (Replace Method with Method Object).
    5. Is a class getting too big?  Does it have a set of responsibilities, methods, or fields that don't seem to be related to the rest of the class?  Extract Class.  The Single Responsibility Principle is one of the most important principles in all of software development.  Follow it religiously and your TDD experience will be better.
    6. From the original CodeComplete book, do only one thing on each line of code.  A line of code like this:  someClass.SomeMethod(variable1 * variable2, variable3.Configure(variable4)); can be confusing because there are so many different things going on in this one line of code.  Use the Introduce Variable refactoring (CTRL-ALT-V) to quickly break up the line of code.  Besides understandibility, this will make the code easier to step through in the debugger.  I'm struggling a little bit with debugging and tracing some code I've inherited right now and this is one of the main culprits.
    7. Look for an opportunity to replace excessive conditional logic with either State or Strategy patterns.  Deeply nested "IF/THEN/ELSE" code blocks are a veritable breeding ground for software bugs.  You might Decompose Conditional instead to improve the understandability of the code.
    8. Are you obeying the Law of Demeter (someClass.childThing.grandChildThing.doSomething())?  If not, encapsulate the child member calls.  This helps to improve coupling between classes. 
    9. Is  a class not following the Tell, Don't Ask principle?  Does a method really seem to belong somewhere else?  If not, Move Method to put the functionality in another class to improve cohesion and coupling.
    10. Note to self:  replace Magic Number's with constants where appropriate.  Magic numbers are a serious affliction within the .Net community because of our widespread usage of DataSet's and DataReader's.  I'm bad about this myself.

    The technical debt metaphor is a very apt description.  Delaying refactoring is a lot like compounding interest payments on your credit card.  The longer you wait, the more it'll cost in the end.  On a WinForms project last year we had an absurdly complex navigation scheme.  Moving from one screen to another screen involved a variety of security checks, "dirty" screen checks, and activation logic.  We knew we needed to refactor our screen controllers to support the navigation in a generalized way, but the team was under severe schedule pressure to make iterations.  Against my better judgement I agreed to put off the refactoring to push through more new stories.  To my chagrin, a junior pair worked on a couple of new navigation stories and created even more spaghetti code on top of the existing smelly code.  The end result was that what should have been a 4-6 hour refactoring turned into about 20+ hours of work.  We eliminated the spaghetti code by implementing a Layer Supertype pattern in the controller classes to generalize the navigation checks (CanLeave(), TryEnter(), Start(), etc.).  New screens went faster once we made the refactoring.  Needless to say, we missed our iteration and the project bogged down.  The moral of the story is to recognize and act on the need to refactor earlier rather than later. 

    Doing something quick and dirty only gives you a short burst of velocity.  You'll pay for it over the long run through reduced coding velocity.  It's like a wide receiver in (American) football making a diving catch.  You can only get away with one dive at the end of the run, then you gotta pick yourself off the ground.  Brush your teeth twice a day and see the dentist occasionally, and everything is copacetic.  Bypass refactoring work and you'll either slow down the team as the code gets harder and more risky to change, or perform an expensive root canal restructuring on your application to bring it back to health.  The most important thing is to be constantly reevaluating your code and design every single day as you work.

    One last rant, don't ever let a project manager or non-coder get away with telling you that "you can just refactor it later."  That's a little bit like saying you can rest when you're dead.  Refactoring != "throw it away and do it over."  Don't ever fall into that trap.  PM's seem to assume we're just goldplating because they often don't understand the technical situation and the lost efficiency caused by sloppy coding.  I'm no longer saddled with bad project managers, but they're certainly lurking out there.

    Do you have some checks to add to the list, or want to disagree with some of the list?  I'd be happy to hear your thoughts.

      

  • Why aren't design patterns common knowledge?

    Take this all with a grain of salt, I'm just a little frustrated. 

    I received a glimmer of hope today.  We were interviewing a candidate for a senior developer role today and he was able to speak knowledgeably and confidently about design patterns.  I don't know what everyone else's experience is, but finding developers that are strong in Object Oriented Programming concepts and conversant with Design Patterns has been extremely rare for me.

    I don't think this is just a lacking in the .Net community.  I've both interviewed and graded code submissions from Java developers, too.  I've observed just as much ignorance of Object Oriented Programming concepts and Design Patterns from them as well.  I do think it might be worse with .Net because of the widespread VB6 heritage and what always appeared as indifference to OOP from Microsoft prior to .Net.  You could technically use some OO concepts with VB6, but it was often more effort than it was really worth (trust me I know).  Even today I would guess that most .Net applications are largely procedural code.  I know that almost every legacy .Net code I've worked with has been almost exclusively procedural in structure.  Being procedural doesn't automatically mean that it's bad code -- but it usually is.

    To their credit, MS has tried to push patterns through the PatternShares website and a lot of the guidance from the P&P group, but I don't know if the audience has been listening.

    Personally, I think design patterns should be a basic skill for all but entry level developers.  I don't really understand why after 10+ years of literature and hype that design patterns are still considered esoteric knowledge reserved for ivory tower, arm-waving architects.  It seems to me that Service Oriented Architecture gets all the attention these days, but I don't think that SOA (or AOP for that matter) in any way, shape, or form diminishes the need for OO and design pattern knowledge.  If anything, it just creates an additional set of patterns (if you're doing anything resembling SOA or traditional EAI, I heartily recommend reading Enterprise Integration Patterns by Gregor Hohpe and Bobby Woolfe).  

    Thanks for listening, I feel better now.

  • Chill out on the Singleton Fetish

    My little team is taking over development for one of our other products.  Today is the first day of the typical "I'm getting the app up on my box and the NAnt build doesn't work on my box" hell.  One of the things I'm seeing in trying to debug the broken unit tests is a bag of Singleton's that perform data access.  Just to make it more fun, one singleton uses another singleton to get its configuration.  My real problem is that the database connection string isn't correct yet, but the test failures exposed some larger structural issues to address later. 

    Look at the code below for a second.  It's nothing special really, just a generic Singleton that serves as a gateway to some kind of resource, but this little bit of innocuous code can thoroughly hose the testability of any application.

     

    	public class Singleton
    	{
    		private static Singleton _instance;
    // Lazy initialization because that's more *efficient* public static Singleton GetInstance() { if (_instance == null) { lock (typeof(Singleton)) { if (_instance == null) { _instance = new Singleton(); } } } return _instance; } private object _something; private Singleton() { _something = getSomethingFromDatabase(); } private object getSomethingFromDatabase() { // go fetch some kind of configuration information from the database return null; } }

    So what's so wrong with this?  Plenty.  The instance of Singleton touches the database in its constructor function.  Unit testing any class that depends on the Singleton class automatically includes some sort of live data access -- or does it?  One of the problems with singleton's in unit testing is the lack of control over when the singleton was created.  To the best of my knowledge, NUnit does not guarantee the order of unit test execution, so the singleton instance will be instantiated by whichever unit test happens to run first.  Why is this such a bad thing?  Because your tests aren't starting from a known state and therefore the results of the test are not reliable.

    For another thing your unit test isn't a unit test, it's an integration test. Integration tests are cool too, but unit tests might be more important from the standpoint of creating working code.  One of the qualifications and benefits of a unit test is that it pinpoints the exact trouble spot in the code when it fails.  If a coarse-grained test fails, you've got to look in a lot more code to find the exact problem.  If there's a database involved, then you're troubleshooting search widens outside of your code.  It's possible to test against a live database if you can control the database state, but why do all that extra work if you don't need to?  Solve one problem at a time and data access is often a separate issue.  One obvious way to tell whether code is adequately unit tested at a fine-grained level is the amount of time spent with the debugger to fix problems.  If you're using the debugger a lot, you really need to reconsider your unit testing approach. 

    Forget TDD for a second.  Using a stateful singleton opens yourself up to all kinds of threading issues.  Not many developers that write business software are going to be threading experts.  When you screw up threading safety you can create really wacky bugs that are devilishly hard to reproduce.  You do love bugs that can't be reproduced don't you?  The code I'm looking at today seems to handle the thread safety issues quite well, but all the "lock(this, that, or the other)" code blows up the lines of code count.  It adds complexity to the code and makes the code harder to understand and read.  Add in a bunch of tracing code and you end up with a whole lot of noise code surrounding a little piece of code that actually does something useful.  My biggest issue with the code is whether or not it was really necessary in the first place.  One of the truisms in software development you bump into occasionally is that "Premature Optimization is the root of all evil" in software development.  I'm guessing that the singletons were put in place due to performance concerns.  Do it a simple way first and forget caching out of the gate.  The performance bottlenecks are almost never where you think they'll be anyway.  By doing things the simple way upfront you can leave yourself with more time later when the real performance problems become evident.  Don't do anything that makes the code harder to understand unless you absolutely have to.

    My best advice is to not use caching via static members until you absolutely have to for performance or resource limitation reasons.  If you do need singleton-like functionality, you might try some of the techniques from this article.  You can also get around the singleton issues by just being able to replace the single instance with a mock or stub from a static member.  It works, but it's not my preference.

     

  • Pair Programming Ergonomics

    One of the first things you need to do if you’re going to do pair programming is to get a good physical workspace.  I don’t know about you, but I’m not very effective if I have a crick in my neck or I can’t even see the code my pair partner is typing.  I know the original XP guys say to use a single keyboard and mouse, but my experience has been much better with a second monitor, keyboard, and mouse plugged into the shared workstation.  At any time both members of the pair can see the code and either can quickly take over the “driving.”  Do observe a little bit of etiquette with each other to avoid wrestling over who’s typing and moving the mouse at any time.

     

    To pull it off, you really want a place where both of you can sit shoulder to shoulder without being cramped.  In my current office space in Austin I have a pretty nice desk with drawers on either side, but it’s terrible for pairing (we have to trade seats to switch the guy at the keyboard and that sucks).  When we move to a new (larger) space, I’m insisting on getting plain tables instead of desks.  Take advantage of the new world of notebook computers and wireless connectivity to just pick up and go somewhere else to find a better physical setup.  Always let the rest of the team know where you’re going and keep an instant messenger tool up if you do go off into the cafeteria though.

     

    I would also say to have a whiteboard or notepad of some kind nearby so you can sketch things out together.  Noted methodologist Alistair Cockburn has famously described two people talking at a whiteboard to be the most efficient means of communicating ideas.  On a project last year we had 3 or 4 whiteboards on rollers that could be moved anywhere.  I promise you that we got far more bang for the buck from the couple hundred dollars we spent on the whiteboards than a typical team will get from a series of Rational Rose licenses.

  • More on Pairing

    Kicking Off a Task or Story

     

    Some people can just sit down at a keyboard and start generating unit tests.  I can’t do this myself.  Personally, I’m much more effective if I spend just a little bit of time sketching out an approach in crude UML or merely writing out a list of the changes to make in the code (new method here, new class there, etc.).  One very effective tactic I like is to do in a pairing session is to start at a whiteboard and task out the work together and make a list of the coding work.  It’s a simple and quick way to get a shared understanding of the work.  You’ll undoubtedly throw away some of the list and go a different way, but it’s the act of making the list together that’s important, not the list itself.

     

    The Backseater Must Be Engaged

     

    The person not driving has to remain actively engaged in the coding.  When you’re not the driver, you’re Goose from Top Gun watching the driver’s “Six”.  The backseater should be thinking ahead and looking for potential problems with the current approach.  According to the old folks like Steve McConnell and Robert Glass, code inspections (reviews) are one of the most valuable tools for removing defects from the code.  The backseater should be performing a code inspection in real-time. 

     

    I think it’s okay to have the backseater doing some sort of research for the next piece of work sometimes, but otherwise the backseater is focused on the coding at hand.

     

    When you’re the backseater, don’t be an ass.  Slow down on the whole “you forgot a semicolon” commentary at least until the driver has moved on to the next line of code.  Nitpicking is a great way to be a bad backseater.

     

    Switch roles often to keep both people involved in the story.  No decent developer likes to just watch someone else coding.  There was a somewhat sad incident in a stand up meeting last year when my pair from the day before listed his activity from the day before as “watching Jeremy code.”  That’s being a bad pair on both our parts.

     

    Smooth out the Worst Tendencies of any Sole Developer

     

    By having input from two coders into any coding activity, you have a sanity check that can help rein in the worst tendencies of any one developer.  As for myself, I’m filled to the brim with design pattern theory and it leads to some temptation to go off and be an Architect Astronaut.  Coding in a pair with somebody who does not share this affliction helps to keep me grounded and write code that makes sense to the rest of the team. 

     

    On the other hand, if your partner suggests implementing a task in a 1400 line stored procedure you can remind them that T-SQL is not a very good solution because it’s hard to test, debug, and read in this context than C#/VB.NET/Java (and Jeremy has implicitly threatened bodily harm to anyone who puts business logic in a stored procedure ever again).

  • Pairing is a Great Way to Learn and Teach

    Let’s be pretty clear about this.  You’re doing pairing primarily to gain in throughput, not as a training tool.  That being said, pairing can be a very effective way to transfer knowledge and mentor junior coders. 

     

    Something to keep in mind as you pair is to look at the bigger picture of the project.  Sometimes pairing with a much junior developer will make the immediate task take longer than it would if you were flying solo, but that’s not completely the point.  You (I) must be patient.  If you can use the pairing experience to improve the effectiveness of your colleagues the team as a whole will come out ahead in the end in terms of team velocity.  To employ a sports metaphor in basketball, the very best players are considered to be the ones who can make their teammates better.  I was thrown into leadership positions very early in my career when I was still struggling with improving my own personal velocity, so I never really grasped the value of improving team velocity until much later.  One of the very real advantages of agile development is the concept of collective ownership and putting the focus on a team completing working software instead of checking off your own personal deliverables.  I think it’s a subtle shift, but it’s really changed my outlook on software development.  Someday I'll even internalize that kind of thinking.

     

    One simple benefit of pairing for me has been picking up IDE tricks and learning new tools from other developers.  Don’t underestimate how much faster you can be with a good toolbox of keyboard shortcuts.  Last summer my team was a very early adopter of the original ReSharper beta (I’m gonna wait a few more builds before I try the ReSharper 2.0 beta this time though).  We had several guys that had quite a bit of experience with the IntelliJ IDE for Java.  These guys suddenly became much quicker mechanically than I and the others who weren’t IntelliJ users.  We made a new rule for a little while that the driver had to yell out the keyboard shortcut they were using to make the ReSharper magic happen.  A couple months later I was coding solo on an airplane and realized how much faster I was getting with the IDE.

     

    Other examples I can vividly remember was my first exposure to WinForms development, Subversion tricks from my “BuildMaster” guru colleague, and getting other developers to use TestDriven.Net for faster cycling between coding and unit testing with NUnit. 

  • Pairing Rotation

    The question of how often you need to rotate pairs has to be answered by the development team.  I’ve heard some teams say their pairing rotation was 90 minutes and others would rotate much less frequently.  My preference is to lean towards pair continuity on either the story or task level, having a pair work a task from start to finish.  Of course, I also think stories or tasks should be ruthlessly subdivided to fit within a day or two of coding effort (iteration management is a topic for another day), so that still leads to pair rotation at least every other day.  I think it’s convenient to define the pairs immediately after the standup meeting in the morning or right after lunch.  Don’t let any kind of schedule like this idle your developers though.  I’m a morning person and I’ve always been more effective in the early morning with a fresh mind.  Keep a backlog of non-pairing work around so no one is ever idle waiting on a pair to do something useful.

     

    I think the only constant in pair rotation is that the developers need to be self-organizing and manage themselves in this respect.  Having one person, whether the development lead or project manager, assign the pairs as a dictator doesn’t seem to be as effective as a more egalitarian approach.  What I’m really trying to drive at is not to automatically give the most challenging coding work to the most senior guys because it never affords the others a chance to learn and it can breed resentment.

     

    One thing we did on a project last summer was to designate one developer as a story owner for the duration of a user story throughout the iteration.  I thought that turned out to be a great way to balance continuity with pair rotation.

     

    I’ve heard a practice described several times is having some kind of bell or buzzer that rings to tell the developers to play musical chairs.  I think that’s obnoxious myself, but whatever floats your boat.

  • Pair Programming Braindump

    Pairing for Beginners (and Skeptics)

     

    My shop is about to introduce more pair programming into our daily development routine.  Here’s a more or less “braindump” on my pair programming experiences.  I can’t say I like everything about pairing or always enjoy it, but overall the benefits I’ve seen are real enough to make me a believer.  I’m the exact profile (introverted, used to working solo) of the guy who will have trouble doing pair programming and I can still do it, so it’ll surely be easier for you.

     

    “This is the Way We Do It”

     

    My favorite metaphor for software design these days is a fishing tackle box.  I want a place for everything and everything in its place.  When I need a top water lure, I know exactly where to look.  I put data access code here, business logic there, and hook the two things up like this.  When I’m creating new code I want to organize it along a predictable structure that anyone else on the project will instantly recognize.  When other coders are making changes I want them to follow the same basic organization so I can find and understand their code later. 

     

    Each application is a little bit different so the application’s design is always going to vary.  In any agile project you should hit an inflection point in the team’s velocity that I think of as the “This is the Way We Do It” moment.  Things become smoother.  There are fewer surprises.  Story estimates become more consistent and accurate.  When any pair starts a new story, they understand the general pattern of the system structure and can make the mechanical implementation with minimal fuss.

     

    You really want to get to this point as soon as you can.  There are two separate issues to address before you can reach this inflection point. 

     

    1. Determining the pattern and architecture for the system under development
    2. Socializing the design throughout the development team

     

    To the first point, pairing allows you bring to bear the knowledge and experience of everybody on the team to the work at hand.  It’s just not possible for any one developer to understand every technology and design pattern in the world.  By having every developer active in the project design, you can often work out a workable approach faster than a solo architect ever could.  On the one project I’ve done with theoretical 100% pairing, we had a couple of developers with a lot of heavy client experience and me with more backend and web development experience.  By pairing together with our disparate knowledge we could rapidly create a workable general design strategy for the system as a whole by bringing a wider skill set to any coding task. 

     

    As an example from my short stint on an architecture team, I saw one of my architect peers design an elaborate solution for an ETL (Extract, Transform, Load) infrastructure between several systems with an absurdly complex error handling subsystem.  We had a site license for Sql Server and every single thing he designed could have been accomplished out of the box with Data Transformation Services in Sql Server instead of his custom design.  If he’d been listening to or working with the other developers they could have solved the ETL issue quickly and spent more time on the infrastructure management aspect of the project to create a design that the developers could actually code.

     

    If you’re a senior developer or the technical lead, one of your responsibilities is fostering an understanding of the technical direction to the other developers.  Nothing else I’ve ever done as a lead (design sessions, documentation, presentations, “do this,” etc.) beats working shoulder to shoulder with other developers as a mechanism for creating a shared understanding of the project strategy.  By making every developer be involved or at least exposed to the thinking behind the design, they’ll have much more contextual information about the design. 

     

    One unpleasant fact I’ve discovered over and over again is that the more detailed instructions you give to another developer, the worse the code is that comes back to you.  I simply can’t do the thinking for someone else.  If the developer understands the “why” of the design, they always seem to do a better job and often make improvements as they go. 

     

    For example, my wife routinely organizes our large CD collection.  She promptly becomes angry with me when I put CD’s in the wrong place because I don’t understand her organization rules.  I don’t understand why the CD’s are on what shelf because I had no part in creating the organization.  After 9 years, I still don’t understand how my wife thinks either, but that’s a different problem.

     

     

    Improving Our Code vs. Defensiveness about My Code

     

    Don’t for one second discount the psychological advantages of pair programming.  Formal or even just peer code reviews can be nasty affairs.  They can often amount to a divide between the prosecution and the accused.  In my admittedly limited experience, they’ve been largely blown off or devolve into meaningless compliance checks with coding style standards.  Even worse is the fact that they are generally used as a gating process just prior to the next stage of the waterfall, eliminating the usefulness because it’s too late to make any kind of big change.  

     

    The collective ownership achieved with pair programming can turn this situation on its head.  My peers and I can now start to talk about how to improve our code instead of being defensive or sensitive to criticism about my code.  Since we’ve all got visibility now into the majority of the code, we can have informed conversations about the technical direction overall.  The in depth code review happens in real time, so problems are caught sooner.  Add in the shifting of different coders through different areas of the code and you end up with more eyes on any important piece of code.  The ability to be self-critical about existing code, without feeling defensive, helps to continuously improve the system design and code.  I think this is one of the primary ways in which agile development can lead to a better, more pleasant workplace.

More Posts Next page »