Validate your conventions

I'm a big pro­po­nent of the whole Con­ven­tion over con­fig­u­ra­tion idea. I give up some metic­u­lous con­trol over plumb­ing of my code, and by virtue of adher­ing to con­ven­tions the code just finds its way into the right place. With great power comes great respon­si­bil­ity, as some­one once said. You may give up direct con­trol over the process, but you should still some­how val­i­date your code adheres to the con­ven­tions, to avoid often sub­tle bugs.

So what is it about?

Say you have web, MVC appli­ca­tion, with con­trollers, views and such. You say con­trollers have a layer super­type, com­mon base name­space and com­mon suf­fix in class name. Great, but what hap­pens if you (or the other guy on your team), fail to con­form to these rules? There's no com­piler to raise a warn­ing, since the com­piler has no idea about the rules you, (or some­one else) came up with. What hap­pens is, most likely your IoC con­tainer if you use one, or per­haps rout­ing engine of your appli­ca­tion will fail to prop­erly locate the type, and the appli­ca­tion will blow into your face dur­ing a demo for very impor­tant cus­tomer.
Take another sce­nario. Say you're using AutoMap­per, and you came up with con­ven­tion that enti­ties in name­space Foo.Entities are mapped to DTOs in name­space Foo.Dtos, so that Foo.Entities.Bar gets its map­ping to Foo.Dtos.BarDto reg­is­tered auto­mat­i­cally. Again — it won't pick map­ping to Fizzbuz­zDto when you acci­den­tally put Fizzbuzz entity in Foo.Services. Depend­ing on your test cov­er­age you will either find out sooner or later.

These are usu­ally a hard rules, and both AutoMap­per and most IoC frame­works will be able to fig­ure out some­thing is miss­ing and notify you (with an excep­tion). This is a good thing, since you fail fast, and quickly get to fix your rules. You won’t be that lucky all the time though. Say you’re writ­ing an MVVM frame­work, sim­i­lar to one Rob built, and cre­ate  a con­ven­tion wiring enable state of some con­trols bound to method Exe­cute, with prop­erty CanEx­e­cute on your view model (if you have no idea what the heck I’m talk­ing about, go see Rob’s pre­sen­ta­tion from Mix10). When you fail to con­form to this rule, no excep­tion will occur, world will not end. Merely a  but­ton will be click­able all the time, instead of only when some valid­ity rule is met. This is a bug though, just one harder to spot, which means usu­ally it won’t be you who finds it – your angry users will.

So I have a problem?

So what to do? You can loosen your con­ven­tions. If you had an issue due to name­space mis­match, per­haps don't require name­spaces to match, just type names? Or if you for­got to put a Con­troller suf­fix on your con­troller, just ditch that require­ment and it will work just fine, right?

Yes it will (prob­a­bly), or at least you find your­self break­ing some other require­ment of your con­ven­tion and feel the need to shed it as well. This how­ever does not fix the prob­lem — it merely replaces it with another — you will have rot­ten code.

Prob­lem is not that your con­ven­tion was too rigid (although some­times this will indeed be the case as well), you not adher­ing to the con­ven­tion was the real prob­lem. Con­ven­tions are like com­piler rules — fol­low them or don't con­sider your code 'work­ing'. To get compiler-like behav­ior you need to take this one step fur­ther and along with find­ing code adher­ing to your con­ven­tions, find the code that does not.

Take the AutoMap­per exam­ple again. We have des­ig­nated name­space for our con­ven­tion — Foo.Dtos. For each type in this name­space we require that a match­ing type in Foo.Entities name­space exists. You should check if an unmatched dto type exists and send some noti­fi­ca­tion in case there is. Also since you require DTOs and only DTOs to live in that name­space you should express that require­ment in code — check for types with Dto suf­fix in other name­spaces, and types with­out this suf­fix in Foo.Dtos name­space, and in each case send an appro­pri­ate noti­fi­ca­tion as well.

This is a form of exe­cutable spec­i­fi­ca­tion. When a new guy/gal comes to your team and starts work­ing on the code base you don't have to worry about them break­ing the code because they don't know the con­ven­tions you fol­low. The code will tell them when some­thing is wrong, and what to do to make it right. It will also help you keep your code clean and struc­tured. Improv­ing its read­abil­ity and in the long run, also decreas­ing pos­si­bil­ity of code rot.

What to do?

I still haven't touched upon one impor­tant thing — how and where do you imple­ment the con­ven­tion val­i­da­tion rules. There are three places I would put con­ven­tion validation.

  • The boot­strap­ping code itself. It often makes sense to take advan­tage of the fact you're scan­ning for con­ven­tions to per­form the con­ven­tions boot­strap­ping and do check for misses as well. Espe­cially when the frame­work you're work­ing with does not have any sup­port for con­ven­tions built in and you're doing it all manually.
  • Unit tests. Add tests that call AutoMapper’s ‘Assert­Con­fig­u­ra­tionIs­Valid’ or in case of home­grown frame­work, reflect over your model and look for devi­a­tions from your con­ven­tions. It really is very lit­tle work upfront com­pared to mas­sive return it gives you.
  • NDe­pend. NDepend's CQL rules lend them­selves quite nicely to this task. They work in a sim­i­lar man­ner to unit tests, but they are exter­nal to your code. Espe­cially with very nice Visual Stu­dio inte­gra­tion in ver­sion 3, it has very low fric­tion, and pro­vides imme­di­ate feed­back. In addi­tion they are pretty easy to read, lend­ing them­selves to the task of being exe­cutable documentation.
  • http://igorbrejc.net/ Igor Brejc

    Krzysztof, what do you think about this kind of val­i­da­tion? It's not strictly convention-testing, it's more about IoC basics.
    igorbrejc.net/…/disposing-of-the-evidence

  • Arnis L.

    Thing that fright­ens me even more — con­ven­tions that are based on con­ven­tions. It's like an invis­i­ble depen­dency tree, pure time killer when you need to men­tally debug it down to root.

    No prob­lems if you know them all though.

  • http://kozmic.pl/Default.aspx Krzysztof Koźmic

    @Arnis,

    when *you* define the con­ven­tions, and your code is checked against them that should not be a problem.

    Whole Ruby on Rails frame­work is con­ven­tion based and it seems to work pretty well, so I wouldn't worry that much.

    Just put mean­ing­ful excep­tion mes­sages in your ConventionExceptions

    "Type Foo.Controllers.Bar is not a valid con­troller. Con­troller types must have 'Con­troller' suf­fix. Change the name to Foo.Controllers.BarController"