Minggu, 06 Februari 2011

Documenting functions and named parameters

Sometime ago someone asked me how I’d pass custom paramaters to event listeners. I don’t remember the exact use case neither who asked me but basically he was asking for something like:

mc.addEventListener(MouseEvent.CLICK, myListener, "myCustomParam", 2);

functoin myListener(event: MouseEvent, custom :String, custom2 : int) : void;


While the exact syntax above would not be possible, closures are in any case the way to go. I explained him how I’d do it and I forgot about the topic until today.


Today while waiting for a long compilation process to complete I’ve came up with an idea on how to improve code readability on cases similar to this. Here’s the cleaneast syntax I’ve came up with (ideas are more than welcome to improve it):



mc.addEventListener( MouseEvent.CLICK, createListenerAdapterBasedOn(myListener, withParameters("myCustomParam", 2)));


Does it look too verbose? I guess it will highly depend on the exact use case where this is used. But under some circumstances I think the readability can be substantially improved.


createListenerAdapterBasedOn is a package level method. I could have used a utility static method like FunctionUtils.createListenerAdapter. But instead I’ve used a package function because I can reduce the length of the Class + method name (or use the same number of characters to add verbosity and clarity). On the other hand it’s unlikely that this utility class would have more methods.


Documenting functions


This is a concept I’ve never read about or used before, but I think I’ll start using in different places. withParameters method is a completely dummy method that simply returns the parameters it gets but giving them a semantic and contextual meaning. The solely purpose of this method is to improve the readability by converting the method invocation in a Fluent API that can be read as natural language. The method is simply documenting the usage of the call. Putting this together with the Builder pattern and named parameters I discussed a while ago it gives us an incredible weapon to create self-documented code that doesn’t need most-of-the-times useless, needless and confusing ASDoc or inline comments.


If necessary when you have a method accepting several parameters of the same type and that can be confusing to read you could used Documenting functions to create kind of named-parameters:



myMethod(name("John"), surname("Green"), age(33), weight(70))


Here are the details of the implementation in case you are interested in seeing the internal details and some of the tests.


createListenerAdapterBasedOn.as



package com.rialvalue.utils

{

public function createListenerAdapterBasedOn(callback:Function, args):Function

{

return function( rest):*

{

if (args.length == 1)

return callback.apply(null, rest.concat(args[0]));

else

return callback.apply(null, rest.concat(args));

}

}

}


withParameters.as



package com.rialvalue.utils

{

public function withParameters( args):Array

{

return args;

}

}


createListenerAdapterBasedOnTest.as



package com.rialvalue.utils

{

import flash.display.MovieClip;

import flash.events.Event;

import flash.events.IEventDispatcher;

import flash.events.MouseEvent;



import flashx.textLayout.debug.assert;



import org.flexunit.assertThat;

import org.hamcrest.object.equalTo;

public class ListenerUtilsTest

{


private static const ORIGINAL_PARAMETER:String = "originalParameter";


private static const FIRST_PARAM:String = "firstParam";


private static const SECOND_PARAM:String = "secondParam";



[Test]

public function createCallbackAdapterWithNoParameters():void

{

var callbackAdapter:Function = createListenerAdapterBasedOn(function(data:String):void

{

assertThat(ORIGINAL_PARAMETER, equalTo(data));

});


callbackAdapter(ORIGINAL_PARAMETER);

}


[Test]

public function createCallbackAdapterWithIndividualParameter():void

{

var callbackAdapter:Function = createListenerAdapterBasedOn(function(data:String, p1:String):void

{

assertThat(ORIGINAL_PARAMETER, equalTo(data));

assertThat(FIRST_PARAM, equalTo(p1));

}, withParameters(FIRST_PARAM));


callbackAdapter(ORIGINAL_PARAMETER);

}


[Test]

public function createCallbackAdapterWithIndividualParameterNoFluent():void

{

var callbackAdapter:Function = createListenerAdapterBasedOn(function(data:String, p1:String):void

{

assertThat(ORIGINAL_PARAMETER, equalTo(data));

assertThat(FIRST_PARAM, equalTo(p1));

}, FIRST_PARAM);


callbackAdapter(ORIGINAL_PARAMETER);

}


[Test]

public function createCallbackAdapterWithIndividualParameters():void

{

var callbackAdapter:Function = createListenerAdapterBasedOn(function(data:String, p1:String, p2:String):void

{

assertThat(ORIGINAL_PARAMETER, equalTo(data));

assertThat(FIRST_PARAM, equalTo(p1));

assertThat(SECOND_PARAM, equalTo(p2));

}, withParameters(FIRST_PARAM, SECOND_PARAM));


callbackAdapter(ORIGINAL_PARAMETER);

}


[Test]

public function createCallbackAdapterWithIndividualParametersNoFluent():void

{

var callbackAdapter:Function = createListenerAdapterBasedOn(function(data:String, p1:String, p2:String):void

{

assertThat(ORIGINAL_PARAMETER, equalTo(data));

assertThat(FIRST_PARAM, equalTo(p1));

assertThat(SECOND_PARAM, equalTo(p2));

}, FIRST_PARAM, SECOND_PARAM);


callbackAdapter(ORIGINAL_PARAMETER);

}

}

}

Tidak ada komentar: