Live in PA? MD? Interested in Flex/Flash?

Wednesday, January 28, 2009

If you're within driving distance of Lancaster, PA and interested in the Flash Platform (Flex, Flash, Catalyst, the whole shebang), then you should know there's a new User Group that would love to have you. Last night was the first meeting of the Susquehanna Valley Flex/Flash User Group. It was mostly introductions and chitchat centered around Flex. I'm excited about it because the guys leading it are clearly knowledgeable and hot & heavy with the Flash Platform. Here's the link: http://groups.adobe.com/groups/f8d9c4add8/summary Meetings are last Tuesday of the month, starting at 6:15. If you're in Maryland and would like to come but don't want to drive the whole way, we could work something out... I could meet you in York somewhere and drive us over to Lancaster. Let me know!

Code Dojo - Applying Kung Fu To Code

Friday, January 23, 2009

What really caught my attention about this TDD adoption article Marc Esher sent me was the concept of a Code Dojo. The idea is to create fun venue where programmers can get together on a regular basis and practice the craft of coding. The idea behind it is that we, as programmers, learn best from practice. Some of the suggestions for a Code Dojo include making sure it's non-competitive, fun, and a safe place for new ideas. I like the pair-programming TDD approach; that is, a couple of programmers sit in the driver's seat and switch off as appropriate. They discuss what they are doing every step of the way and accept questions and comments from the programmer audience. Check out the Dojo Resources below for other ideas. I really like the Code Dojo concept. It gets programmers to talk to one another in a more social way that we are accustomed to, which is an issue due to the solitary nature of writing software and the incessant pressure to deliver. Think about it. How often in your organization do you get a regular opportunity to discuss programming challenges and practice what you do best? I personally know programmers who are starved for this kind of interaction. From a management perspective, it's not Getting Things Done, but could it increase productivity in the long run? I'd be interested to hear what people's experience has been making this a reality! Test and be Happy! Namasté bill Dojo Resources: http://www.codedojo.org/ http://codingdojo.org/ http://wiki.agilefinland.com/?CodingDojo http://pragdave.pragprog.com/

A Fluent Paradigm For Refactoring Conditional Logic in ColdFusion

Thursday, January 15, 2009

After reading this very smart piece, it became apparent that its application points to and addresses a bigger issue: complex conditional logic. When I look at code that has complex conditional logic I get a queasy feeling in the pit of my stomach, thinking about how that logic could be broken and about the complexity of the associated tests. For example, consider this example code:
function createLogin(username, password){
if( len(username) lt 8 or
len(username) gt 12 or
refind('[:punct:]', username) or
refind('[:space:]', username) or
refind('[:cntrl:]', username) ){

_throw('FormatException','Invalid Username format','Username needs to follow these rules: http://www...');

}
//Also need to do something similar for the password. Omitted for simplicity.

//After checks have passed, store credentials. Intentionally omitted.
}

You can see that this implements username requirements. The username needs to be between 8 and 12 characters, and contain only alpha-numeric characters. Yes, there are more elegant regular expressions, but this is intended to be easy to read ;-) There are some issues with this. Functionally, it may fit the specification just fine, but if something fails, how do we know which of the 5 conditions failed and what caused it to fail? Also, a developer writing a test for this code, and naturally wanting that test to cover as much as possible, would need to consider all the possible combinations. Essentially, all of the above conditions need to be false in order for the statement to be true Take a look at the following truth table which illustrates all the possible combinations needed to completely test this expression. P or Q or X or Y or Z all represent a T/F value returned by each of the conditional tests in our expression respectively: Truth table built using Truth Table Constructor What the truth table says, and what our validation also says, is that all expressions must be false in order for the if statement (the expression) to be true. If any of the conditions are true, the statement will be false and throw the FormatException. A test just for the username rule might look like this. This assumes that the createLogin method returns a boolean value. Another issue, is that all the subsequent logic after the checks will be invoked, too, which may generate unwanted side effects.
     function testCreateLogin(){
var user = createObject('component','user');
var password = 'Pa$w00rD';
var badUsernames = 'few,MoreThan12characters,hasPunc$%, ,has space,#chr(10)#,user#chr(13)#Name,user$name';
var i = 1;
var tempUser = '';
for (i; i lte listLen(badUsernames); i=i+1){
tempUser = listGetAt(badUsernames,i);
try{
 //Should fail
 actual = user.createLogin(tempUser, password);
 if(actual) {
    fail('#tempUser# passed and should not.');
 }
}
catch(usernameexception e){
      debug(tempUser);
}
}
}
Note that the above does not test all rows of the truth table; what's missing? Hint: consider row #2 of the truth table, the username should be 8-12 charcters, contain no spaces or punctuation AND contain a control character; e.g., user#char(13)#Name. That one is covered. Eek! Again we are testing the conditional logic and possibly dealing with side effects of the subsequent code. We could extract everything into a separate method, which is better. But that still leaves us with a long conditional statement, it just exists in another object. What about something like this:
function createLogin(username, password){

validator
.checkForLength(username, 'username')
.checkForSpaces(username, 'username')
.checkForPunctuation(username, 'username')
.checkForControls(username, 'username')
.validate();

//Also need to do something similar for the password. Omitted for simplicity.
//After checks have passed, create login logic. Intentionally omitted.
}
I suspect one of several responses to this: 1. What's that dot stuff? 2. Cool. Show me the code, or, maybe, you're like, 'yeah, yeah ... like, show me something new, dude'. All are welcome! :-) For those in the 1st category. That's method chaining. Check out Martin Fowler's piece on the topic. It could just as easily been written in one line:
validator.checkForLength(username, 'username').checkForSpaces(username,'username').checkForPunctuation(username,'username').checkForControls(username, 'username').validate();

Or without chaining as:
validator.checkForLength(username, 'username')
validator.checkForSpaces(username, 'username')
validator.checkForPunctuation(username, 'username')
validator.checkForControls(username, 'username')
validator.validate();

But it reads much better broken down, IMO. This has been coined "Fluency" and is useful for situations such as this; specifically, expression building. I would not recommend implementing chaining just because you can; make sure it provides some benefit. How it's done: Essentially, each validation method in the Validator object returns an instance of itself; i.e., this. This allows for the method chaining. Pretty simple, really. The Validator This is a rather simple component, as all components should be. Maybe look at it as a paradigm and write your own. You can use it out of the box to perform simple validations or tests. It has one validation method, assert(expression, source), where expression is any boolean expression and source is a name you'd like to associate with the expression. In practice, this would likely be the name of an argument or parameter or it could be used to validate a post-condition prior to a method's return statement. The only other Validator methods you need to worry about are collect() and validate(). When you exercise a validation, e.g. assert(...), and there is a failure, the Validator captures the exception information but does not throw the exception until validate() is called. This allows for multiple checks and builds details about where the failure occurred (@see 3rd paragraph). Example:
validator
.assert(1 eq 1, 'param1')
.assert(true , 'param2')
.assert( isValid('struct', {foo='bar'}), 'param3')
.validate();

Rarely are any kind of conditionals and validation like the above so simple! So, the Validator concept is intended to be extended. There are three things to know about extending it:
  1. Make sure : component extends="path.to.Validator"
  2. Each validation method should return THIS so chaining can be accomplished.
  3. When your validation fails, call the collect(...) method in the parent; e.g., collect( 'YourFailureType', value, source);
Example:
<cfcomponent displayname="MyValidator" output="false" extends="Validator">
<cfscript>
this.MIN = createObject('java','java.lang.Integer').MIN_VALUE;
this.MAX = createObject('java','java.lang.Integer').MAX_VALUE;

function isInRange(value,src){
if( !isValid("range", value, this.MIN, this.MAX )){
collect("NumberRangeException", value, src);  }
return this;
}

function shouldtBe5AlphaCharsExactly(val,src){
if( !isValid('regex', val , '[a-zA-Z|a-zA-Z]{5,5}' ) ){
collect('StringFailureException',val,src);
}
return this;
}
</cfscript>
</cfcomponent>
Basic Usage:
function myMethod(someNumber,someId){
validator
.isInRange(someNumber,'someNumber')
.shouldtBe5AlphaCharsExactly(someId,'someId')
.validate();
//...
}
When a client calls myMethod(), the Validator is invoked and checks to see if someNumber is in the acceptable range and that someId is exactly five alpha characters long. If not, it collects the exception information and proceeds until validate() is called. validate() iterates over its exception collection and throws a general failure with details of each exception: ValidationException - One or more parameters failed validation. Parameter 'someNumber' with value '-1.2312312312312312E23' threw NumberRangeException ; Parameter 'someId' with value 'morethan5' threw StringFailureException ; There are a number of clear benefits to this:
  1. The logic becomes a lot easier to test.
  2. Re-use possibilities emerge.
  3. Specifying the implementation of the conditional rules becomes easier.
  4. The code becomes clearer to read and to maintain.
  5. It may even lead to better security.
Example Tests:
<cfcomponent output="false" extends="mxunit.framework.TestCase">
<cfscript>

function outOfRangeParametersShouldFail(){
try{
validator
.isInRange(1,"param1") //ok
.isInRange(-99,"param2") //ok
.isInRange(createObject("java","java.lang.Integer").MIN_VALUE-100,"param3") //capture this
.isInRange(createObject("java","java.lang.Integer").MAX_VALUE+100,"param4") //capture this
.validate();
}
catch(ValidationException e){}
assertEquals(2, arrayLen(validator.getExceptions()),"Should be only 1 exception, because it should fail at first validate().");
}

function charLengthShouldBeOk(){
var exceptions = validator
          .shouldNotBeNoMoreThan5Chars('asdfg','param1')
          .validate()
          .getExceptions();
assertEquals(0, arrayLen(exceptions) ,'Oops. Exceptions are present in array.');
}


</cfscript>
</cfcomponent>

*Source Code Link*: http://mxunit.org/examples/validator.zip Summary: I used a parameter validation application as the example, but you can see than anywhere complex conditional logic is implemented, you could apply this. Next: This and some other things really got me thinking. Should the User object be responsible for validating? Should any object assume that responsibility? This question points to Inversion of Control and the Aspect Oriented Programming techniques of ColdSpring, Spring, and AspectJ. So, on the slate for next time will be a light-weight AOP/cross-cutting approach to this. Test and be happy ... Namasté. -bill

Mid-Michigan CFUG MXUnit Presentation Roundup

Tuesday, January 13, 2009

Thanks! To all who attended, thanks for spending so much time with me. And thanks to Rick and Nick for setting it all up. Here's some follow-up: Remember my onMissingMethod / UserBean problem? Turns out, the reason the test was failing was that I needed to call user.setid(id=5), not user.setid(5). Umm...... that ain't cool. So I wrote some notes into bean.cfc and userbean.cfc for refactoring. Give those refactorings a shot! Follow-up, or, What to do next
  1. Go download MXUnit
  2. If you're using Eclipse, install the Eclipse plugin
  3. Somewhere in your codebase, create a directory named "tests" (or whatever)
  4. Pick a fairly easy existing component that you've already written. Let's say it's named "Bob". Now, go to your tests directory and create a new file called "BobTest.cfc".
  5. Inside that file, add a cfcomponent tag and have it extend mxunit.framework.TestCase
  6. Then, pick a function in Bob to run. Maybe it's "init". Think to yourself, "What's one thing this function should do?" Then, back in your test, write a new test function, something like ....
  7. Run the test to confirm everything's "hooked up" correctly. This test should pass.
  8. Now, try to write a simple test for Bob.init() by creating an instance of Bob and invoking its init() function. Write an assertion or two on the result of that call to init().
Congrats! You've now written your first test. Then.... Next time you add a function to a component, write a test for it first. Think to yourself, "What should this function do?" If it does more than one thing (like maybe "init" returns self but also adds arguments to the variables scope), then write two functions, one for each "behavior". And then use those functions to run the function-under-test in your component, and write assertions that prove the behavior is as expected. Adopt the habit of writing tests for the expected behavior of your components. Practice As practice, Here's where we left off with the initial tests on the Feed processing in the ColdFusionBloggers code. Download it, and try the following: Open the components/querysim_instructions.txt and do what it says Open the components/creating a directory test runner.txt and do what it says. Open components/UserRefactored.cfc and UserRefactoredTest.cfc and read my comments. Give the TODOs a shot if you're feeling frisky. Or, at the very least, *think* about how you might approach it... use it as a thought experiment try refactoring the feed-processing file (scheduled/processes.cfm) into the Processor.cfc to make it easier to test. Work iteratively... a little bit of improvement at a time. ProcessTest.cfc is already stubbed for you. Then, post your results back here, or send them to the MXUnit Google Group and we can talk about it there. Stick with it, and good luck! You have a lot of people out here willing to help. When things get tough to test Bill and I presented at MAX 08 on some solid strategies for when the going gets tough with testing. Definitely check out the download and the Recording of the presentation, which is now on adobe TV. Links are in here.

Two New MustSeeTV MXUnit Recordings now Online


In case you missed this the other day when Sean posted it on his site: the recording is now available for our presentation at the Post-MAX BACFUG. See Sean's blog for details. We had a great time at the BACFUG that night. Bill was in rare form, especially for a normally low-key bow-tied sock-clip-wearing Fed. In the presentation, Bill makes the case for how adopting a test mentality improves your life. In my 15 minutes or so, I show some timesavers for generating and running tests. A word of caution: our presentation is probably not the kind of thing you show on the bigscreen at lunchtime to a room full of executives, children, or other people with sensitive ears. If you wilt at the mere hint of a swear word, or cringe at blatant/immature/overt/unnecessary sexual innuendo, then please... skip it. The second half of the presentation -- Joe Rinehart talking about CF and Groovy -- is definitely worth watching. Joe is a top-notch speaker and super smart to boot. Finally, a big thanks to Sean and Paul for organizing the event, and to the folks in the audience who indulged us (or, maybe, suffered our immaturity). In addition, I'm excited to see that our MAX presentation is now available on MAX TV. Watch it here. This is not your typical "introduction to unit testing" presentation. Instead, it's about strategies you can use for making your code more testable. My follow-up post, with all code, is available here. If you're in the mood to practice designing for testability, that post talks about a "Challenge" that's part of the download zip. Be-de Be-de Be-de That's all Folks!

Are you presenting at cf.Objective, CFUnited, CFMeetup, etc?

Monday, January 12, 2009

This is not a paid advertisement. I do not have an Amazon referrer account. I am a conference attendee. I have to sit in your presentations for an hour. Sometimes, they rock. Sometimes, they suck. Sometimes, they're average. If you're cool with that -- with being "OK", then please move along. If, however, you want to make sure you're not killing your audience with page upon page of bullet points; if you want to engage your audience; if you don't want to see "shouldn't put this snoozer after lunch" on your eval forms (ask Bill!); if you want to really grab people as Bill did at the bacfug (I think he did, anyway) -- then I strongly urge you to buy this book: Presentation Zen. I have a presentation at work coming up. It's a presentation on the concepts in Dale Dauten's Great Employees Only, which has become one of my favorite books. My boss and I are presenting it to the "management team". The concepts are tough. It's a disruptive book on a close-to-the-heart topic (hiring and 'de-hiring'). It will sting. The audience will want to strangle me at one point or another for being an uppity, haughty, high-minded know-it-all who really doesn't know sh*t about sh*t. Knowing that, I know that this presentation needs to be the best presentation I've ever given. And as I work through Presentation Zen, I can see the changes already. The difference between "what we would've presented" and "what we will present" is dramatic, and we still have two weeks of revising to go. I tell you this because if this book is good enough to transform a critical presentation to a "management team" from good to great, then it's good enough to transform a geek-to-geek presentation from good to great, too. My audience does not want to be there. They are resistant. They will fight the concepts violently. They will be directly challenged, called out, questioned, backed against a wall. Your audience is choosing to give you their time. They want to be there. They want to be dazzled, engaged, taught. They want to walk out of your conference room a better, smarter, more thoughtful person than when they walked in. They want you to succeed! You owe it to them to be the best you can be. Bah! Words words words.
  • Words
  • Words words
  • Words. and. more. words
Maybe look at this instead?