|
LATEST POSTS
Thursday, April 24, 2008
Watching the Polyglot Programming video courtesy of enabler-extraordinaire, Dave Laribee, and it revealed (or rather re-revealed) something about myself that I hope is common enough that I'll get at least one BOOYAH in the comments. I watched Amadeus when I was just a wee country whelp. And even at a young age, I could identify with the narrator, Antonio Salieri (at least how he is represented in the movie). It's a simplified comparison, to be sure, but one that has stuck with me. In the movie, Salieri is a composer caught in the large shadow left by Mozart and he becomes increasingly enraged at what he feels is a cruel, cosmic joke. Namely, he has been instilled with the ability to recognize genius and the desire to create good music, but not with the talent. (This is compounded into murderous intentions when he is able to recognize that genius in Mozart, who seems to treat his gift so cavalierly. I don't generally identify with him this far.) Ten years into my career, and some thirty-six years into my life, I don't have many illusions about what I can and can't do anymore. On the strong end of the stick, I can learn new applications and languages relatively quickly. I can be pretty productive when I put my mind to it and am able to communicate my intentions clearly. At least that's what my resume says and it's on the internet so it must be true. But watching this video, and indeed, hanging out with the alt.net crowd in general, makes me refer back to Amadeus on more than one occasion. I can't help thinking there are a lot of Mozarts out there. And I don't mean in their day-to-day work. That's the easy part. I can pound out good code and talk best practices, often coherently. Rather, there are people out there who are able to create beautiful music in our industry by asking the right questions and having a clear vision of what the state of the world should be. Or at least, they recognize problems I didn't know existed (and, it must be said, some problems I still don't have - still a challenge to differentiate the two) and are able to steer the conversation in a way that facilitates solving those problems in a clear way. On the other hand, I am able only to recognize when a "good" idea has been mentioned, or when a vision is worth being part of. "Good" being relative to my immediate sense of the world. Plenty of good ideas go whooshing by if they don't have an short-term impact on what I'm doing. I'm able to run with an idea, maybe even expand on it a little. And I can certainly document my trials, hopefully for potential mass benefit. I'm pretty sure this has to do with my tendency not to take anything in my career too seriously. Not sure if the attitude causes the problem or vice versa, but there it is. Whatever the reason, the attitude has worked for me for over three decades so it's not going anywhere. As for the lack of overall industry vision, as mercenary as it sounds, I'm good with that too. It's hard enough copying what others are doing (e.g. starting a user group), without having to make up the rules as you go. To tie this back to alt.net, I'm happy in my role as semi-silent observer. At least with respect to the bleeding edge trends like polyglot programming and future architectures. It'll help to have that background for when I catch up. And to tie it also back to the metaphor I'm using, it doesn't mean I need to react the same way Salieri did upon realization of fate's sense of humour. (That is, I won't drive the likes of Laribee and Scott Bellware into madness and death, steal their work as my own, then absolve the masses for being as mediocre as I am.) I can still be active in my own way and play off the strengths I have. There are plenty of people in the same boat I'm in now and plenty in the one I was in five and ten years ago. I may not be able to push the boundaries of the playing field, but I can certainly invite more people to the game. Kyle the Metaphoric
Monday, April 07, 2008
The hillbilly loves to be humbled. It's why he refers to himself in the third person. It's a liberating feeling knowing there are people out there who are smarter, faster, and know more lyrics to Air Supply songs than you. And I always like to give people that smug feeling of being better than someone, even if it's just me. Such was the feeling I've ended up at after a foray into Linq to NHibernate. But before we get to the part where I felt stupid, the path that led me to Linq to NHibernate is interesting in a "let's pad this out" kinda way. I'm building a search screen for an application for land surveyors. In it, you enter a bunch of criteria for various fields, click Search and moan about how it was easier when everything was on paper while you wait for the results to appear. My initial plan to implement this was to have a search DTO in the client that would collect all the criteria up. That DTO would be converted to a collection of SearchCriteria domain objects which would be passed to the data layer. The data layer would then convert it once more into NHibernate expressions for use with the query. Nice and encapsulated, albeit a little conversion-heavy. Then I got to thinking about Linq for NHibernate and decided to give it a test drive. The end result was a slightly different approach. Namely, I convert the DTO into a collection of Linq Expressions and pass *that* to the data layer, which could then iterate through each of them and append them to a preset Linq query. Here's an example of the code to execute the query in the repository. public IList<T> FindAll<T>( IList<Expression<Func<T, bool>>> criteria )
{
var query = from item in CurrentSession.Linq<T>( )
select item;
foreach ( var criterion in criteria )
{
query.Where( criterion );
}
return query.ToList( );
}
A little different from the traditional from x in y select x where moo examples mostly because I don't know what the where clause is going to be at design time. The type for the criteria looks a little intimidating but because of it, I can add criteria to the list in an aesthetically pleasing way. To wit:
searchCriteria.Add( job => job.JobNumber.Contains( suppliedJobNumber ) );
searchCriteria.Add( job => suppliedOffices.Contains( job.Office.Id ) );
searchCriteria.Add( job => job.OrderedData > suppliedData ) );
Here's where things start to go awry. Linq to NHibernate is still very much experimental. For example, the second criteria above doesn't actually work. It assumes that Contains will be called only on strings, not lists. And during my travels, I discovered two other examples that won't work:
searchCriteria.Add( job => "Name1Name2Name3".Contains( job.Name ) );
searchCriteria.Add( job => job.Id == idList[0] ) );
That is, it won't allow an expression to be evaluated as the argument for a Contains call on a string. And you can't use an array index in your comparisons either.
But I did get the code from a branch called "experiments" after all. So rather than complaining, and since I've got the time, why not try to actually implement these? So I started with the one I actually had an immediate need for: getting Contains working on a List.
I started to feel a little nervous about this early on what with the advanced Reflection/Linq concepts involved, not to mention my tenuous grasp of the Visitor pattern. Eventually, I ended up at the line of code that was failing:
return Expression.Lambda(typeof(Func<>).MakeGenericType(expression.Type), expression)
.Compile().DynamicInvoke();
I don't doubt that this kind of code is necessary. We are, after all, dealing with a Linq provider that needs to build and parse an expression tree. And I'm pretty sure that with some initial guidance, I'd be reasonably productive in implementing this. (To be fair, looking more closely at that line of code, it's only the MakeGenericType call that baffles me completely. And at the time of writing, I was still too lazy to look it up.)
To put Linq to NHibernate back into context, the reason I like it is because I can essentially do what I originally intended (build a list of criteria and pass it to the data layer) without having to write a bunch of conversion code and without having to create NHibernate expressions in my service layer. As a result, it holds great promise for me but the next couple of days will tell if it's a time/productivity trade-off I can make right now. So if you have some time on your hands, I'll respectfully add my vote for taking a look at Linq to NHibernate.
But don't let it interfere with your banjo lessons...
Kyle the Time-slotted
Thursday, March 20, 2008
Normally when I have a problem I can't solve right away, I bang away at it until I have a half-assed solution that I can post here so that others can provide a better solution in the comments. It's how I've found about four methods for testing RedirectToAction, got tips for automating my deployments, and figured out the ConventionController. Quite frankly, I post here so that someone else can show me a better way to do things 'cause I've learned long ago my way ain't it. This time, however, I've searched and experimented and have come up empty. So there will be no facade that I know what I'm doing this time. I'll just plain admit that I can't get it to work. I suppose I could bitch and complain about how hard this is. That seems to be a common theme I've seen in many other places when someone can't figure something out. Whine about how it should be easier, then have some kid in tenth grade post a simple yet elegant solution in the comments within the first five minutes. Alas, I don't have the pride to assume that it's someone else's fault I can't get my work done. 'Spose I should get to the problem at hand lest I lose potential problem-solvers. I have a domain object called Job. It contains a collection of Location objects. A Location is an abstract class with two implementations: RuralLocation and UrbanLocation. The heart of my problem is: How do I represent this in an NHibernate mapping? As defined in the documentation, I'm using a "table-per-concrete-class" strategy. I'll listen to arguments that it's not appropriate but consider this: Locations are value objects. They have no identity in and of themselves. A sample location would be NE-15-1-29-1W, which is a rural location (Northeast quarter section, section 15, township 1, range 29, 1 west of the prime meridian). There is no benefit to storing this as an individual entity in the database and attaching it to many jobs. Locations are essentially bandied around like money when you listen to the clients talk. The reason I'm not considering a "table-per-class" or "table-per-subclass" hierarchy is because the two classes have exactly zero fields in common. So in the former, we'd have a table where half the fields would be null in every single row. And in the latter, we'd have a Location table containing two fields, an ID and a LocationType, and nothing else. Maybe having such a table isn't too bad but it sure feels like I'm altering the schema for the sake of NHibernate. So for better or for worse, I have a "table-per-concrete-class". This is easy enough to represent in mapping files for each class individually as per the NHibernate documentation. Where I'm stuck is how to link these concrete classes back to the Job mapping file. Back when I had only the one location type, the mapping looked so: <bag name="Locations" cascade="all-delete-orphan"> <key column="JobID" /> <one-to-many class="Trilogy.Gunton.Model.RuralLocation" /> </bag>
Fairly straightforward and pretty much right out of the docs. But now, I need to map to an abstract Location class that has no representation in NHibernate. The docs mention the <any> element and Ayende has something similar but for the life of this hillbilly, the answer still eludes.
So to my insightful and, I pray, generous readers: <ahem>....help!
### UPDATE ###
Sorry folks, forgot to include relevant diagrams. Database diagram and class diagram are included below. The Locations property on Job is an IList<Location>. Click for larger versions:
Kyle the Unassisted
Tuesday, March 11, 2008
Got myself a delicious problem but there is another discussion to be had so I will leave you hanging on that one until a later post. I've already hinted at the software I'm working on for the time being. It's essentially something to help manage a land surveying office but what it does isn't really the point. What *is* the point is that it has some major domain object that needs to be managed. In this case, it's a Job (to use the domain term). A brief history: Jobs in land surveying office have always historically been a paper-based entity. There is a lot of physical information to collate. Land titles, plans, deeds, even the sketches the crews make in the field. These are all key pieces of information that need to be kept organized in some fashion. And it's all either visual information or third party data that they have no control over (and sometimes both). So to think that all of this information can be moved into the digital world is a little ambitious at this point. So historically, they tend to organize them into a Job. And naturally, a job must be assigned a number. And typically, companies will use a numbering system that will convey some information in the number. This can be something relatively simple, like M070123 which indicates that this was the 123rd job in 2007 for the Montenegro office. Or it could be somewhat more complex like BLC-08-A31 which might mean it's a BLC (domain term, don't ask 'cause I don't know) from 2008 from zone A of the city and it's the 31st job in that zone for the year. However it is generated, the fact remains that they need to maintain a running list of these numbers in a "book" (another domain term, again don't ask 'cause I've never heard of it either) so that they can generate a new one easily. As a new job is ordered, the person recording it must take care to record a new number in the running list so that there are no duplicates. Now here comes the consultant (i.e. me, and seriously, do NOT ask about this one) to automate some of this data. And as I start working, the inevitable question arises: Why are we still using this archaic process of generating job numbers manually? Computers *love* generating IDs automatically. Databases do it natively. NHibernate can get you a GUID in less time than it takes to remember what the acronym stands for. And so it was that I made a suggestion: Why not ditch the current mechanism and start over with something more computer-friendly? Like starting with an ID of 1 and going up from there. They can still refer to jobs easily. And though we're losing that tiny bit of metadata embedded in the previous version, we can glean that information (and a whole lot more) in the form of reports on the system. My job's easier and they've saved money during development. This being a family business (and my family's business to boot), they have reservations but defer to the "expert". There's a lot of "well, there may be some pushback from the others but if you think it'll be easier, go for it." I'm happy at a job well-done. So confident am I that I think nothing of an e-mail from my brother asking me to contact one of the "others". That is, someone outside the family but who is actively going to *use* the system. She has some reservations but is leery about bringing them up. She's young, inexperienced. What could she possibly say to sway the mind of the big, bad consultant? "How do we enter in existing jobs?" An honest question that deserves an honest answer, rather than the back-pedalling one I gave. Which basically suggested we'll have a field for OldJobNumber to handle legacy job numbers. And even as I spoke the words, the whole idea kind of unravelled in my head. Because the old job number is important. They've got cabinets and cabinets filled with files referring to them. And all of a sudden, I'm suggesting they create a whole new filing system. And that's just the physical aspect. Even within the application I'm writing, I'd have to present the job number differently. I had visions of: if ( old job number exists ) show it, else show job ID peppered throughout the code. So I swallowed my pride and admitted I hadn't thought of that. After which case, the floodgates opened and it was admitted that no one was really looking forward to it. And after some questioning, it turns out there is a very good reason for this, though not an obvious one. There is something psychological in having metadata in the job number. Sitting in the office, you'll notice that they are constantly bandying job numbers about. "What's the status of job M080050?" "Where are the plans for job V070758?" And when they call the jobs out like this, they can make a mental filter as they try to remember which job it is. You can imagine the thought process: "B-07-C98, that's that BLC from late last year in the Soho area, I've got those plans right here." Compare that job number with another that has an ID of 78945 and it suddenly becomes a lot harder to create that mental filter. In essence, it's more than an identifier. It's also a name. And a filter. And even a kind of mini-report. The lesson learned: Don't subvert your client's domain with all this new-fangled computer jargon. The net result is job numbers will still be auto-generated but they will be generated in the form that they're used to. It's not quite as automatic as an auto-increment but it's still very much algorithmic and can be done somewhat easily by the application. But I have *seriously* simplified how they generate them for the sake of this post. The reality is the tasty problem I referred to in the opening paragraph. By way of foreshadowing (or even foreboding), they have not one, not two, but *six* methods of generating a number for a job based on various factors, some of which require intimate knowledge of a map of the area. Stay tuned! Kyle the Auto-generated Final closing point, because someone may comment on it, is that I have no intention of dropping the auto-incrementing ID. But like most IDs, it won't be as in-your-face as I originally expected.
Wednesday, March 05, 2008
Here's the bad thing about ASP.NET MVC. Every little thing about it is bloggable mostly because every little thing is new to it. I'm half considering using Monorail just because everything in it is so well-documented, I wouldn't need to waste my time blogging about it. (Those of you about to regurgitate the holy war about why ASP.NET MVC has caused all this hoopla when Monorail has been around for so long, calm down. It's just software, for Jayzus' sake.) After lamenting the mechanisms I needed to use to pass information to a ControllerAction, Ben Scheirman turned me on to the ConventionController in MvcContrib. Apparently, it works well for pages that create new objects but not so much for ones that update existing objects. *EDIT* I originally had a whole long spiel done that included a history of my stored procedure-writing prowess and a PurportedSibling domain object. It was to explain that because of my genius way of setting up my service, I could use the ConventionController for both creates and updates. But the fact is, it had nothing to do with how I set up my service. It just works out of the box. By deriving my controller from ConventionController, I can create a view that includes: using ( Html.Form( "Save", "Job", FormExtensions.FormMethod.post ) )
{%>
<%=Html.Hidden( "job.Id", ViewData.Id ) %>
Sibling Name: <%=Html.TextBox( "sibling.Name", ViewData.Name ) %>
Is half sibling?: <%=Html.RadioButtonList( "sibling.IsHalfBrother", TRISTATE_ENUM, ViewData.IsHalfBrother ) %>
Rumoured mammy: <%=Html.TextBox( "sibling.RumouredMammy", ViewData.RumouredMammy ) %>
Rumoured pappy: <%=Html.TextBox( "sibling.RumouredPappy", ViewData.RumouredPappy ) %>
<%=Html.SubmitButton( "Submit", "Save" ) %>
<%}%>
Here are the actions. The first launches this view for a new sibling. The second for updating an existing one. And the third saves in both cases.
public void Create( )
{
RenderView( Edit", PurportedSibling.Null( ) );
}
public void Edit( int id )
{
PurportedSibling sibling = _siblingService.GetById( id );
RenderView( "Edit", sibling );
}
public void Save( [Deserialize("sibling")] PurportedSibing sibling )
{
_siblingService.Save( sibling );
RedirectToAction( new { Action = "SiblingUpdated", id = sibling.Id } );
}
Crisp and clean be how I like my controller actions. Compare the Save with what it looked like before:
[ControllerAction]
public void Save( int siblingId, string siblingName, TRISTATE isHalfSibling, string rumouredMammy, string rumouredPappy )
{
_siblingService.Save( siblingId, siblingName, isHalfSibling, rumouredMammy, rumouredPappy );
RedirectToAction( new { Action = "SiblingUpdated", id = sibling.Id } );
}
This was before I was about to add the part where you specify possible offspring of the siblings on the same screen, something that would have made this Save method more complicated than the domain itself, which is an homage to graph theory in and of itself.
In any case, I'll leave the implementation of the Save method to your imagination in both cases. (Hint: It is much nicer in the new version, let me tell you!)
Notice how the new version also doesn't have a [ControllerAction] attribute. That's another feature of the ConventionController which will make it much easier to manage when the next CTP of ASP.NET MVC comes out which removes the need for the attribute.
Back to the ConventionController. I suspect a good chunk of the magic has to do with NHibernate being able to discern whether the object is new or updated based on the ID that is passed to it. But even if you don't use NHibernate, this same technique could be used. I.E. Check if the ID of the object is 0. If so, it's new. Otherwise, it's old.
There is a *very* large caveat to this method. If the object you are updating has other properties that are *not* updated on this page, they will be set to whatever default value is appropriate for its datatype. And when it is then sent to the database for "updating", its corresponding field will be updated to this value right alongside every other field.
In this case, you can add an intermediate step in the process somewhere. I.E. Somewhere along the line you'll need to do the following:
- Retrieve the object from the database (based on the ID)
- If an object is retrieved, update it with the values that were entered in the view.
- Save the object
This way, you won't overwrite any properties with default values just because they aren't editted in this particular view of the object. In theory at least. I haven't actually tried it myself.
Kyle the Purported
Monday, February 25, 2008
Hey, whaddaya know! I have some actual feedback to give on something in the form of "boy, I wish the product could do X". Who knew I had evaluation skillz? The product is ASP.NET MVC and I'm implementing some pretty standard functionality: Get the data from the user and save it in the database. For this post, we'll call the object being saved a Critter. One of the properties on a Critter is Species and its value is selected from a dropdown list. This is not such a far cry from Scott Guthrie's equally descriptive Product/Category example which is an okay example if you're looking only for education and not entertainment. Like Scott's version, I'm passing what amounts to a DTO as the ViewData. The DTO includes a list of Species to select from as well as the actual Critter object. You fill in the name of the critter, select your species, and click Save and watch the magic happen in the form of saving the Critter to the database. Now, according to the tutorial, there are two ways to create your Critter object in the Create action. The first way is to specify a method parameter for each property. For example: [ControllerAction]
public void Create( string CritterName, int SpeciesID )
{
Critter critter = new Critter( );
critter.Name = CritterName;
critter.SpeciesID = SpeciesID;
// Do "stuff" with the critter
}
The second way is shorter and possibly cleaner:
[ControllerAction]
public void Create( )
{
Critter critter = new Critter( );
critter.UpdateFrom( Request.Form );
// Do "stuff" with the critter
}
This one is kinda cool if a little too magic. Updating from a NameValueCollection sounds like it might be fragile but they seem to have a good collective head on their shoulders so that might not be a concern.
Here's where my wish list comes in. Neither of these methods are how I would code the thing if I were doing it in, say, a Windows app. Ideally, I'd like to be able to do this:
[ControllerAction]
public void Create( ICritter critter )
{
// Do "stuff" with the critter
}
That is, I'd like the framework to create my critter for me. It can do this for primitive types but not so much with objects. Admittedly, this is probably a tall order without getting back into a ViewState state of being but a hillbilly can dream, can't he?
Kyle the Utopian
Monday, February 11, 2008
The Hillbilly asks: How do you compile your application in your NAnt script for releases? There are many options, you see, even though the <solution> task is no longer one of them. You could use the <msbuild> task to build the solution as it is built in VS (I think; the documentation is a little sketchy on this task if you don't know the inner workings of msbuild). Or you could use the <csc> or <vbc> tasks to compile a group of code files into an assembly (similar to a .csproj or .vbproj file). Or, for the ultimate control, you could use the <exec> task to call out to msbuild.exe, csc.exe, or vbc.exe directly though I couldn't tell you personally what that offers you that the corresponding tasks don't. Future-proofing maybe. Usually, for testing purposes at least, this entails using <csc> (or <exec> with csc.exe) to compile all the application code into a single assembly that I can run tests against. It's a practice I learned from JP Boodhoo and I've been happy with the results in the absence of anything better. But I don't care much about the unit tests. I'm more interested in your releases. Assuming you've automated them in a NAnt script, how do you build the application in preparation for them? Historically, I've usually used the <msbuild>/msbuild.exe scenario mostly because I couldn't wrap my head around writing a bunch of <csc> tasks to compile the code into the same structure I see in Visual Studio. For example, if your solution has a Domain project, a DataAccess project, and a UI project, I would need three <csc> tasks to create the individual assemblies. Or one <msbuild> task running against the solution. My reasoning was that it was the easiest way to build the application the same way I did in Visual Studio, which I assumed was how I wanted to deploy it. Then I talked to Donald Belcham. Conversations with Donald Belcham are always eye-opening (and I won't expand on that comment this time; happy belated birthday, dude). And during our chat, he admitted to using <csc> to create his releases. I expressed my surprise: "But Iggy, what if your solution has a dozen projects in it? I don't want to deal with the pain of a dozen <csc> tasks in the right order". To which he replied: "Dude, you don't need to deploy the same way you build. The Visual Studio solution is a file management mechanism, not a deployment mechanism." And my mind awakened like it did during my last visit to Brian Head. The basic idea Donald described was thus: Just because you build your application one way in Visual Studio during development, doesn't mean you need to build your releases the same way. Especially if you are using NAnt to create your releases because then you can do it however you like. And as if my mind wasn't expanded enough, he typically shoots for one assembly per physical location. Not one assembly for the domain, one for the data layer, one for the common utilities, and so on and so forth. One. Total. If I were building a WinForms application with no other physical layers, I'd have one file: MyApp.exe. If it were a WinForms app calling a web service, there would be two assemblies. One for the client and one for the web service (plus corresponding .asmx files). This is the extreme position of course and there are caveats. If the application depends on third-party libraries, naturally we won't bundle those up into the assembly. And if you're using an internal client library that is common to more than one project, you'd treat that as you would any other third-party library and keep them separate. But if your application-specific domain code and data access code sits on the same server, that all gets munged into one assembly regardless of how it is organized in the .sln file. NAnt gives us that flexibility. I'm kind of putting Donald out there for potential ridicule so I'll expose myself ever so slightly and admit that I really like this idea. Why *do* we feel we have to ship a dozen assemblies for our application? If something changes in our domain, what difference does it make if we re-deploy MyCompany.MyApp.Domain.dll or MyCompand.MyApp.exe? And in speaking with Donald, he points out that even though we have the flexibility to deploy only parts of an application, rarely do we exercise it. In a typical scenario, new versions and hot fixes are deployed as an "all or nothing" in each physical layer anyway. This matches my experience. Even if nothing changed in certain assemblies, I typically re-deploy everything during an upgrade just to be safe. Now, we still need to be practical about it. If the resulting executable is 300Mb, then ISDN line or not, you'll want to break that into something more manageable. But for the average application, one assembly should function just as easily as five, yesno? Looking forward to hearing the counter-arguments because I haven't quite thought this through too much except from a maintenance point of view. In particular, are there performance differences with loading several smaller assemblies vs. one "larger" (but still manageable) one? What about issues with unloading an assembly from an application pool in IIS? Enquiring minds want to know! Kyle the Inquisitive
Wednesday, February 06, 2008
The topic of this post is pretty much the polar opposite of what I was hoping to achieve when I came to CodeBetter since it is about as technical as your average Teletubby but it's here by popular demand. So if you aren't interested, this will be the last I say on the subject for a bit unless Microsoft decides to open a branch office down the street tomorrow. And it's a long one because I already feel self-conscious about writing one post on the subject so I don't want to break it up into two. I'll start off focusing on the negative aspects of the island life because those are what have always kept us on the brink of moving back. And they will do so for you, too, for at least the first year you live here. Especially if you start out with high hopes. But I'll segue into the positives just like I started to do after giving the place a chance. First and foremost, safety is a factor. Especially for me growing up in rural Manitoba in a town where the only rule of hide 'n seek was "You can't hide outside the town limits". In the second month we lived here, the babysitters that watched our daughter were robbed at gunpoint in the house they were housesitting while they were watching our daughter. This was in a gated community in the middle of the afternoon with 24-hour security at the gates and security cameras. If you ask me today why we didn't pack up and go home after that, I can make up an answer for you but the fact remains, you have to be conscious of it. Of course, these things happen in any city but it seems more prominent here because it's not a very high population. There are multiple ways you can give yourself the illusion of safety, most of which are common sense. This will keep the punks at bay but not anyone who is more determined. Again, just like any other place. Luckily, the punks are usually more common and the more determined people aren't typically that high on the evolutionary chain. But it still factors into your decision-making. The next topic is tradespersons. There are very few direct repercussions for plumbers, carpenters, and the like if they come to your house and say, cut through your main water line. There are codes in place but they are not enforced. (By the way, the same goes for drinking and driving laws which I'll leave for you to decide if that's a positive or a negative.) So you don't phone someone up in the yellow pages and expect a minimum level of service. Instead, you need to network. Talk with other people about who is good and who isn't. Namedrop those people that recommended the company when you call. Offer the tradesperson a drink when they come. Tip them when they leave (assuming you plan to call them back). Like a good CI process, it's all in the name of reducing future friction. Another bee in my bonnet used to be the lack of variety in food and regularly-purchased goods. It's not quite as bad as it was five years ago mostly because the supermarkets have finally started stocking Multi-Grain Cheerios. But I've had to solicit visiting family and friends for things like Ichiban noodles and toilet levers for side-mounted toilets over the years. In short, there ain't no Home Depot or Wal-Mart to "pop out" to. Related to that is the cost of living. It's high but in my opinion, not prohibitively so. The reason things cost so much more here is because there are import duties on anything that's imported. And as I've probably mentioned before, EVERYTHING is imported. The import duties are high because it's the main source of income for the government. Because there's no income tax and no sales tax. So yes, things cost 30% - 40% more here on average. But by the same token, you don't have to pay 30% - 40% of your wages to the government every month. It doesn't quite balance out because salaries here are lower than their counterparts in North America but that's why you'll want to look for a job working remotely. All in all, whether you consider the cost of living high depends on where you come from. Side bar I've heard tell that in the eyes of the IRS, once an American, always an American. I.E. You're pretty much taxed for life no matter where you live. Someone will have to verify that for me but if it's true, well, I feel for ya, man. Last major sticking point is one where your mileage may vary. That's a lack of easily-accessible activities. If you're in the mood to do something different, you're flying or boating to it. There are times when you just get sick of walking around Atlantis pretending to be a tourist. On the other hand, if you're a beach person, this won't affect you too much. In my experience, though, everyone gets island fever eventually. We get away a few times a year just to get a regular dose of civilization. So with all that cathartic negativity out of the way, let's get to the good stuff. And we'll start with the elephant in the room. It really is awesome to go rollerblading at six in the morning in the middle of January. That sounds bad for those of you stuck in snow but there's no getting around it. Rounding a corner to be met with the sun coming up over the ocean does not get old. Ever. Ditto for sitting out on the balcony listening to the waves and drinking tea. It took me a long time to realize it, but I will forgive a country a lot of things to be able to do that. That said, I have a tremendous respect for all you people sticking it out in the -40 weather up in western Canada. Brave, brave coders, the lot o' ya. Next stop is healthcare. I have absolutely no complaints about it and compared with my experience in Calgary, would recommend it to anyone. I can call my doctor at 8:00 in the morning for an appointment and he'll apologize if he can't see me before 2:00 that afternoon. When my daughter fainted unexpectedly one day while out with my wife, by the time I got to the hospital twenty minutes later, she was already in a room and had had a blood sample taken. Before we left two hours later, she had seen two doctors, had another sample taken, and had an ultrasound. Before the week was done, she had seen her regular doctor, who gave her a thorough exam including some other ultra-sound-ish test, and had received an EEG. I have had similar experiences myself, most notably with my hernia operation last year (maybe I should have put a disclaimer at the beginning of this post), which occurred a mere three weeks after diagnosis, and only because the missus was away and I wanted to wait for her to return before I had it for maximum dotage. I've heard horror stories from others but I haven't seen any evidence of it from my end. There is a caveat to all this. We pay approximately $600/month for health coverage for a family of three. And when we left the hospital after my daughter's fainting spell, I was extolling the virtues of the healthcare system and my wife just said ominously, "yeah, well money talks". Sounds vaguely capitalistic but hey, we're talking my daughter's health here! Besides which, that's also a nice little example of, shall we say, reducing friction, which is a very effective technique for a variety of purposes over here. But I shan't elaborate. Earlier I mentioned a lack of quality tradespeople. The other side of that argument is that when you do find someone you like, you get pretty quick service. Same day most of the time and rarely longer than two or three, unless you're dealing with government-run utilities, in which case the universal "three to six weeks" applies. I could go on in both categories but I think that about covers the high points. Plus I have some other topics in the wings that will hopefully bring me more in line with the "Code Better" moniker. The underlying message here is that moving to the Bahamas is not like moving from one city to another in North America where there's always a Starbucks within shouting distance (although there are five on the island). Expect a good dose of culture shock and pack a lot of patience with your sarong. But again, don't pack yet. I'm still working on the industry. Kyle the Indusrial
Monday, January 28, 2008
The Hillbilly turns 36 today and for my birthday, each and every one of you is going to give me a present by listenin' to a little story of when I was but a wee developer fresh out of codin' skool. (Unless you're on the ASP.NET MVC team, in which case you could offer me a job since one of the criteria for working on the team seems to be having Garnet as your birthstone). Eight years ago, I was hired for what was essentially my first full-time development job. I had dabbled with Visual Basic on and off for a year or so and Y2K tested Delphi for a company that didn't trust Borland's public statements (no, seriously) but that's it. This was a job with a small company that sold an ASP-based learning management system with a twenty-year-old C component and some Word VBA templates. And when I say small, I mean that when I came on board, I doubled the size of the development team. At that point, I had never laid eyes on a single line of ASP code. Now, before the interview, I had actually lost the ad (it was posted in the newspaper) so I had no idea what it was about or even what the company was named (it was run out of an office in a local technical college). Note the foreshadowing... Interviewer: Did you look up anything about the company? Coding Hillbilly: Oh yes, of course Interviewer (pleasantly surprised): Really? What did you find? Coding Hillbilly: That you're...ummm...based out of...<school name>...and you build...stuff? ... Interviewer: So I don't see any ASP in your resume. Do you know much about it? CH: Oh yeah, I've been studying it in my free time Interviewer: What can you tell me about it? CH: ...err....that it stands for Active....Server....Programming? And you can...build....stuff? ... Interviewer: There was another website in the job ad. Did you get a chance to take a look at it? CH: For sure, dude! Interviewer (clearly skeptical by this point): What colours are in the logo? CH: .... fuchsia and mauve? Interviewer: Lucky guess. What do they do? CH: They...build...stuff? ... Interviewer: It's very clear to me that you have not done any research on this company or this position. Can you tell me why I should bother to take this any further and consider you? At this point, I came clean, admitted I lost the advertisement, had no idea who they were or what they did, and that I had no practical coding experience. I politely grovelled at his feet and promised to kick my own ass out of the building. The offer came a couple of days later. No, seriously. They called me back and offered me the position. And that's not even the point of this story. But give me a minute, I do have a point. By that time, I had decided to stay where I was. The missus was pregnant with what we later determined was my child and moving from a big oil company with banked holidays to a near-startup that could go under at anytime didn't seem prudent. So after a prolonged stunned silence at even being offered, I politely declined. Two days later, he calls back. What will it take for me to join them? At that point, I had had feelings of regret at turning it down and my wife and I agreed that I would have taken it if they upped the offer by another $5k a year plus a week of holidays. I told him that and he says "Done". (Later, I learned that the only reason my wife agreed to those terms was because she thought there was no hope in purgatory that they would have called back.) By this time, curiosity gets the better of me and I ask the burning question: "Dude! I totally blew chunks at the interview. Why you trippin' to get me?" My eventual boss for whom I gained a tremendous respect replied: To be honest, you weren't the strongest technical candidate by a long shot. But out of all the people we interviewed, you have the best attitude and would fit in with our team. And I've been an educator for many years and have a hunch you're a quick one. Back to the present wherein you probably see where this is going. This fellow took a tremendous risk on me. At the time, I didn't recognize how big a chance it was. The chance was that I had the team skills and the personality fit for the company and that the technical skills would come. Bless him for being able to recognize that and for placing such a high priority on them but my experience is that he's the exception rather than the rule. There have been a number of discussions on interview questions of late in blogs and discussion groups. I'll single out Jeff Atwood's list because...I dunno... it was probably the last blog I read on the topic. I don't have a lot of pride so I don't mind admitting I'd probably flunk his test. And I write code for a living. Worst-case insertion performance of a hashtable? No idea. How do you test if the high-order bit is set? My answer would probably be eleventeen. Even the one on reading integers from a text file I'd probably falter on if I didn't have an IDE with IntelliSense. On a side note, this is probably a dangerous line of thinking coming on the heels of Ray Lewallen questioning the money being spent on mediocrity. But in Ray's post, Dave Laribee comments that the winning attitude and the winning skillz should be rewarded. I.E. Not just the talent and not just the attitude. And even then, what's the definition of "talent"? The ability to recite from a computer science manual? Or someone who knows how to transform business requirements into reasonably designed software and who can figure out the technical problems as they arise. Ideally, you'd want someone that lies somewhere along that spectrum that makes sense to your organization. (Not to imply the two are mutually exclusive, either.) For a research position, business requirements may not be so ambiguous and you need someone who is acutely aware of how to unroll a for loop into assembly. For your average business application, maybe someone nearer the other end. Now, let's not get all crazy and start assumin' I don't care if you are capable of finding all the prime numbers between 1 and 4. You do need to have some base level of knowledge in whatever technology is required. Nor am I suggesting you break out a Rorschach test during the interview. But there needs to be at least some emphasis on the candidate's personality. And maybe I'm overly optimistic/naive, but I have a feeling that if you gear your interview toward determining those less objective traits, you'll be able to determine their level of expertise anyway. By the way, if you're looking for closure on the story, I stayed with them for a year, learned ASP, XSL, DHTML, and VB, then bolted to a consulting company. It sounds rather mercenary and when I think back on it, it was, even if I didn't mean it to be. It was a good move for me career-wise but has always left a bad taste in my mouth. Damn conscience... Kyle the Aged
Friday, January 25, 2008
Ah, it's good to be back on the circuit. The Hillbilly made his first speaking engagement since May '06 earlier this week, thanks to the brave and trusting souls behind the South Florida .NET user groups. They have themselves a nice group out West Palm Beach way and I hope I can come back either there or one of the other places they're in charge of. I can only hope this last meeting was as fateful as my last presentation. The official topic was ASP.NET MVC but we ventured into TDD, DDD, productivity tools, and AJAX if memory serves. Definitely played out differently than I expected but that's why speaking is so fun. Lots left uncovered but that just makes my presentation at South Florida Code Camp next week that much more interesting. The code can be found here (3.8 Mb). It's a music catalog type application that lets you search for music in your library and play it, although only a song at a time. Check the ReadMe.txt for instructions on building. The short version, in theory, is: modify local-properties.xml and run clicktobuild.bat and you're good to go, assuming you have some version of SQL Server installed. It will create an IIS virtual directory pointing to your music folder to allow you to play the songs you select. Before I start rambling on one of the more interesting aspects of the presentation, some acknowledgements. These are the main links I used in preparing both the code and the presentation: Special thanks to Jeff Palermo's CodeCampServer from which I borrowed very heavily. I didn't go as deeply as he did in the architecture (unit tests? Pffft) and I switched out StructureMap for Windsor because it's what I know. Having said that, I've seen enough sample code for StructureMap that it's near the top of my list of things to check out in the near future. The rest of this post discusses something I showed, albeit briefly, during the talk. It's the idea of "Fluent URLs". It started when I created a search controller based on Scott Guthrie's sample search router. In this version, I'm searching a database of songs. Here is the route: RouteTable.Routes.Add(new Route
{
Url = "Search/[query]",
Defaults = new { controller="Search", action = "Results" },
RouteHandler = typeof(MvcRouteHandler)
});
Here are some sample URLs that use this route:
localhost/Suvius.Flamingo/Search/Brubeck
localhost/Suvius.Flamingo/Search/Folk
localhost/Suvius.Flamingo/Search/Muppet Movie Soundtrack
And so on and so forth. The idea is that you pass in a [query] and it will find any song containing that text in the title, artist, album, whatever.
After that, I got to thinkin'. What if we wanted a more specific query that search based on, say, artist only. So I added the route:
RouteTable.Routes.Add(new Route
{
Url = "Search/FindSongsSungBy/[artist]",
Defaults = new { controller="Search", action = "FindSongsSungBy" },
RouteHandler = typeof(MvcRouteHandler)
});
And some sample URLs:
localhost/Suvius.Flamingo/Search/FindSongsSungBy/Dean Martin
localhost/Suvius.Flamingo/Search/FindSongsSungBy/Sarah Mclachlan
localhost/Suvius.Flamingo/Search/FindSongsSungBy/Mary Poppins
That route didn't last long due to the obvious duplication in the action. It's practically begging you to refactor into something more generic like:
RouteTable.Routes.Add(new Route
{
Url = "Search/[searchCriteria]/[queryText]",
Defaults = new { controller="Search" },
RouteHandler = typeof(MvcRouteHandler)
});
In this case, searchCriteria can be one of: Title, Artist, Album, Genre, or whatever criteria you wish to search by.
I didn't even implement this one because that's where the idea of a fluent URL came in. The new route:
RouteTable.Routes.Add(new Route
{
Url = "FindSongsWhere/[searchCriteria]/Is/[artist]",
Defaults = new { controller="Search" },
RouteHandler = typeof(MvcRouteHandler)
});
And some sample URLs:
localhost/Suvius.Flamingo/FindSongsWhere/Artist/Is/Neil Young localhost/Suvius.Flamingo/FindSongsWhere/Title/Is/Come Away With Me
localhost/Suvius.Flamingo/FindSongsWhere/Genre/Is/Disco Funk
localhost/Suvius.Flamingo/FindSongsWhere/Album/Is/William Shatner Sings The Hits
I didn't take it any further than that (by, say, parameterizing the /Is/ into options such as /Contains/ or /IsSimilarTo/).
I will leave it to someone more practical than I to determine the usefulness of something like this but coming up with it did make me feel ever so giddy that such a thing was even possible. Not that I'd advocate this being the sole interface to your search engine but at the very least, it does lend itself to quick launch tools like Launchy and SlickRun where you can set up parameterized URLs in a more intuitive way. On the downside, it's nigh impossible to use things like Url.Action when you insert random, hard-coded words (like "Is") into the Url for the route.
A final important note on the code. It uses Castle Windsor to create the controllers and inject services into them and it uses NHibernate for the data access layer. I remember my first exposure to both of these fairly vividly and if you are not familiar with them, they can be ever so slightly unintuitive. As it is, I'm no NHibernate expert which is why this code took three days to prepare for release. If there is anything about these aspects (or any others) that you need clarification on, please contact me and I'll do my best to edify and elucidate.
Kyle the Routed
Monday, January 07, 2008
ReSharper 3.1 is out, which you already knew. I put it off until this morning when I read Chris Patterson's account of the solution-wide analysis and thought about how funny that might be to run against my current project. I didn't get too far but I was able to finally reformat (Ctrl+Shift+Alt+F) a class that had thus far been resistant to change, as it were. Oddly, the class went from 9,344 lines of code to 10,255 but damn, does it every look pretty. Anyway, that's not what I wanted to talk about today. While perusing over this shrine to monoliths, I noticed that it had apparently been ported over from .NET 1.1 because all the auto-generated code was still in it. Sensing an opportunity to do good, I decided to create the designer class old-school. It wasn't all that difficult to muddle through. You create the class with the same name as the form but named appropriately. E.g. if the form is named Estimate, create a new class called Estimate.Designer.cs and either Visual Studio or ReSharper will wire it up for you. If it doesn't, you'll need to modify the project file manually by examining how another form does its nesting. Once the class is created, mark it and the original form as partial. Then move the components variable, the Dispose method, and the InitializeComponents method into it along with all the form element variables. But again, that's not really what I came here to talk about. It was during this migration from form class to designer class, that I discovered what appears to be an undocumented feature of ReSharper. When I copied InitializeComponents over, I got the expected yellow strip of inadequacy along the right indicating that there were things that could be cleaned up. Now this being auto-generated code as well as somewhat volatile, I had no intention of cleaning it up. But it occurred to me that I'd never seen this in other designer classes. So popping open another designer class for comparison, the only different was that InitializeComponents was wrapped in a region: #region Windows Form Designer generated code So I got to thinkin', what if I wrapped the entirety of my Estimate form class in a region of this name? And lo! All my inadequacy is gone! No more yellow hints and suggestions on how to make the code better. As far as ReSharper is concerned, my code could not get any better. I kid, of course. I'm not advocating wrapping your code in cryptically named regions just to avoid seeing the ReSharper suggestions. But here's the other thing: You can define your own region text that, when wrapped around a piece of code, will also render its inefficiencies hidden from ReSharper. Here is one way to do it, and I dearly hope there's an easier way: - Click ReSharper | Options
- Select "Code Style Sharing" from Languages | Common
- Export your current settings
- Open the exported file in your favorite non-tab-based text editor and scroll to the bottom
- Notice the element, SkipRegions. You should be able to take it from here. You define another child element with the text you want to use (e.g. Ignore this code, Resharper) and remember to increment the length variable (JetBrains doesn't the childNodes.length property, I guess).
- Go back to "Code Style Sharing" and Import your new xml file
Now you can decorate code with #region Ignore this code, ReSharper and it won't display any suggestions for improvement. Note that errors will still be marked in red so it isn't technically ignored completely. So there you have it. #regions aren't pure evil after all. Just ornery. Kyle the Recanted
Friday, January 04, 2008
I don't know how it happened. Throughout my career, I've been careful not to be "one of those guys". I scoffed inwardly at the discussions on the ALT.NET user group claiming that it could happen to anyone. "Surely I am above such nonsense," says I. But let this serve as a cautionary tale to you young'uns out there... I've become a code coverage Nazi. It started innocently enough. We added code coverage statistics a while back but only lately have we broken the build when it dropped below a certain level. We started at 60% and last week, our coverage sat at 67%. So I upped the threshold to 65% and after some good-natured goading among the team, I raised it to 66%. Y'know, just as a joke. No harm, no foul, eh? A couple of days later and the coverage is starting to hover near 70%. "Very exciting," says I, "soon we'll be able to increase the threshold to 70%". And the excitement I felt at that notion should have been a tipoff to seek some form of counselling. Then a funny thing happens. One morning, I get latest and our coverage is now around 68% (I forget the exact number because I'm not compulsive like that but if memory serves, it was 68.30115%). I check the build log and note several new classes staring me in the face, mocking me in their untestedness. So of course, I shrug it off and say to myself, (out loud because I work from home and have forgotten how an internal monologue works), "No big deal. It'll go up again. We're still above our threshold." Or rather, that's what one would expect me to say if they thought I was a well-adjusted coder of the earth. Instead, I e-mailed the person who checked in the code saying (and I think I covered my obsession rather nicely): "Hey, man. How's it going? Weather's not so good up there, I hear. Oh well, at least we still have the app to keep us warm at night, eh? LOL! So...while I've got your attention...and it's not a big deal or anything...cause y'know, it's all cool...and....WTF IS WITH THE UNTESTED CODE TAINTING OUR SOURCE CONTROL, YOU CAVALIER SOB?!?! So...uh...yeah, let me know what you think, k? Later buddy. Code strong!" He doesn't immediately respond which I take to mean he is diligently adding tests to atone for his blatant and carnal sins. I mean, you see my point, don't you? But sadly, it doesn't end there... While I let this heathen repair the gaping hole in our app, I surreptitiously raise the threshold to 68%. Y'know, to send a message. And the message is: "Check in untested code and THE HAMMER WILL FALL!!!" Cut to a couple of days later. I'm merrily working away on some new feature that I could care less about but will increase our stats and I notice in the log: Code coverage 67.7658546%. Build passes satisfactory threshold of 67%. I sat there stunned for a good ten to fifteen billable minutes staring at this message. "How is this possible?" I ask myself out loud while I pull up the build file's change history. "It's not like anyone would *knowingly* taunt me by lowering the thre----LORD TUNDERIN' JAYZUS AND MOSES ON A BOAT, IT CAN'T BE!" And by this time, the neighbours are noticing my lack of internal monologue but I pay no heed. I scream through the offending files noting that they are essentially UI wrappers over Windows Forms controls and thus, largely untestable. So I do some minor refactoring, add the untestable code to the list of exclusions from code coverage, and bring the threshold back up to its natural state. Then I compose a diplomatic and carefully worded e-mail to the team: "I'm coming for you. ALL of you. Sleep tight." Later the next day, one of the team members throws down behind a facade of "being productive": "Beggin' your pardon, sir. I don't mean to interrupt your recent bout of insanity but I humbly beg you reconsider your position on our code coverage. You see, I am removing code that is no longer necessary and the thing is, it was fully covered by tests so our numbers are dropping. Please, Coding Hillbilly. I beseech you. Release your iron grip on our code coverage. I can live with 'about 2/3 covered'. I just want to go home to my family. Find it in your heart to let me check in my code." It was this plea that made me realize what I had become. A code covering monster. A slave to the numbers, neither alive nor dead. Existing only to see our numbers go up. I'm on the road to recovery now, on a strict diet of DataSets and FileDialogs. Our code holds steady at 69.5% and I'm no longer losing sleep waiting for 70%. As I type this, I'm scanning the stats and feeling quite serene. Although I wonder if our cyclomatic complexity could be improved... Kyle the Uncovered
Wednesday, January 02, 2008
Caution: This post has very little technical content but may contain traces of inspiration and could induce vomiting. Do not read if you have heart problems or a healthy streak of cynicism. As you can imagine, hillbillies are optimists by nature. Happy New Year to all! And this being Day One of Ought-Eight, I thought I'd take some time to reflect back on the past year and consider my goals for the next... ...ok, now that I've done that, let's move on to something interesting. The Igloo Coder, Donald Belcham, writes on Brownfield applications. It's a term that has been applied to land reclamation and is likely a term you haven't heard before, at least as applied to software development. But I bet you already know what it means. And it ties in nicely to my initial posts here at CodeBetter, most of which have focused on what is pretty much the textbook definition of the term. As luck would have it, I've also started work on another Brownfield application. It was last developed upon in January 2007 and is filled with the most heinous practices imaginable. Another hand-rolled data layer with a series of classes exposing static methods returning DataSets in some cases, domain objects in another. Again, all logic in the forms. No tests to be found. No patterns applied anywhere. No CI process. In fact, it's not even in a source control repository. It had one developer who clearly had no clue what he was doing. And if that seems uncharacteristically harsh, that's because the developer was me. I'll save you re-reading the last paragraph and remind you that it was last worked on in January 2007, less than one year ago. That's right, kiddies, the hillbilly has risked destroying the gargantuan backlog of credibility he has built up by admitting he was once a young, inexperienced coder who had lost his way. And it wasn't that long ago. OK, I'm saying that a little tongue-in-cheek which I'm feeling more and more compelled to point out now that people who don't know me read this thing. The part I'm jesting about is where I make it sound like I'm on the one true path and everyone else is lost. And that I'm no longer inexperienced. Let me throw out a blanket statement that that will never be true and move on. To shift focus back to the part that isn't going to solicit a bunch of comments that I'm elitist, the point is that for all the talk I do on patterns and NHibernate and IoC and all the other...ummm...alt.net tools and techniques, it was a *very* short time ago that I practiced precious few of these techniques and used even fewer of the associated tools, if any. The app I'm lambasting here uses the *original* Enterprise Library data access block. The one with the static SQLHelper class. And I actively chose to use it over the new one. In November 2006. It was my decision to use stored procedures for all aspects of data access because I simply didn't know NHibernate existed. It's very likely, given the circumstances around this particular app, I would have looked at it and decided I didn't have time to learn it, but the fact remains it was not even an option in my mind at the time. Now here I am about a year later and, like many developers, I shake my head at my naivete. But here's the cool part. At the time, I was doing things so much differently than I was the year before that and loving the way the new techniques made things easier. This year, the same thing applies. I now know the magic behind an IoC container and can use it to make my code that much more testable. I can take out my data access layer completely and replace it (for the most part) with some config files. And TDD, for all my initial misunderstandings, has made software development more fun than it has been in years. In short, software development remains today just as fun as it did a year ago. And I have no reason to doubt I'll be thinking the same thing next year when I look back at this application once more. Like Alec Baldwin says in Glengarry Glen Ross: Always Be Closing. Kyle the Resolute
Thursday, December 13, 2007
Have started a new project at the current contract and I'm muy excited about it. It's a "legacy" .NET 1.1 app that was upgraded to 2.0 by running the wizard, then left alone. It has a web service created by an architect who appears to have worked with Jeremy Miller before. It returns datasets. Lots of them. There's a DataDude project created in some CTP version that can't be upgraded. There is no CI process. I discovered most of this without cracking open the code. I have since done so and analogously speaking, it is clearly the result of the type of relationship we hillbillies are *very* familiar with. I'm pretty pumped to dive into it, actually. The CI process is mostly done. The tests are running regularly (2.5% code coverage, woohoo!). Now it's time to start the grand-scale refactoring. First, however, we need to go to the customer and tell him, "The architecture of this app is not unlike something akin to roadkill. It compiles only during the waxing phase of the moon and the fact that it runs at all has restored my faith in God. I know you've been using it relatively successfully for several months but we'll need several tens of thousands of dollars from you to refactor large portions of it. When we're done, there will be no discernible difference to the application from your perspective but we in the IT department will sure feel better about ourselves." Now I've never been able to say "discernible" out loud convincingly and I'm sure as heck not going to imply that I believe in a higher power so obviously, this isn't going to work for me. Which leads me to a tactic I've become quite fond of: Guerrilla Refactoring Guerrilla refactoring requires some unconventional tactics. Like in guerrilla warfare, I'll need to ambush the code often. Come at it from an angle it's not expecting. Do quick, focused skirmishes and get out again before anyone notices (except the code which will be grateful to be liberated from its bourgeois oppressors). Subterfuge and guile will be my allies. "How long will it take to implement the 'sync' function? Four hours, minimum. Change the layout of the form? A good day of coding. You need the text changed on this label? I'd say....three full days. Not including writing tests to expose the bug." I will bear the brunt of their skeptical, leering eyes for the sake of the common good. No longer will we toil under the totalitarian regime of logic-laden views and hand-rolled data access code. Their armies will *tremble* in fear in the face of my legion of Freedom Fighter Presenters! And the fascists will COWER as I slash at them with my Sword of NHibernate!! JOIN ME, brothers (and to a lesser extent, sisters), as we stamp out the Dictatorship of Duplication and drive a *hammer* through the very heart of hardcodedness!!1!!1!!!! VIVA LA REVOLUCION!!!! Kyle the Pacifist
Disclaimer
The opinions expressed herein are my own personal opinions and do not represent
my employer's view in any way.
Copyright © 2010 Kyle Baley. All rights reserved.
|
|
|
CATEGORIES
|
|
LATEST POSTS
POPULAR POSTS
|
|
ARCHIVE
| March, 2010 (1) |
| February, 2010 (8) |
| January, 2010 (2) |
| December, 2009 (3) |
| November, 2009 (2) |
| September, 2009 (5) |
| August, 2009 (4) |
| July, 2009 (2) |
| June, 2009 (5) |
| May, 2009 (6) |
| April, 2009 (5) |
| March, 2009 (6) |
| February, 2009 (2) |
| January, 2009 (6) |
| December, 2008 (5) |
| November, 2008 (2) |
| October, 2008 (5) |
| September, 2008 (9) |
| August, 2008 (5) |
| July, 2008 (7) |
| June, 2008 (6) |
| May, 2008 (11) |
| April, 2008 (13) |
| March, 2008 (13) |
| February, 2008 (12) |
| January, 2008 (19) |
| December, 2007 (16) |
| November, 2007 (8) |
| October, 2007 (23) |
| September, 2007 (15) |
| August, 2007 (8) |
| July, 2007 (6) |
| June, 2007 (11) |
| May, 2007 (19) |
| April, 2007 (14) |
| March, 2007 (3) |
| February, 2007 (4) |
| January, 2007 (7) |
| December, 2006 (5) |
| November, 2006 (9) |
| October, 2006 (11) |
| September, 2006 (14) |
| August, 2006 (11) |
| July, 2006 (15) |
| June, 2006 (8) |
| May, 2006 (10) |
| April, 2006 (12) |
| March, 2006 (3) |
| February, 2006 (7) |
|
|
|