Thursday, September 25, 2008

Flex Tree Component Bug

Yesterday I encountered a bug with setting the selectedIndex of the Flex tree component. The default behavior for dropping an item onto a Tree, or any List control, is to drop items in between the items currently in the list. I, however, wanted to drop items onto the items currently in the list. One common way to do this is to set a handler for the dragOver event of the Tree and in this handler call calculateDropIndex on the Tree and set the Tree's selectedIndex equal to this result. This gives some nice visual feedback and lets you easily get access to the element in the Tree at the target drop location.

The problem I encountered manifests itself when a new item is added to the Tree. In particular, if the dataProvider of the Tree is an Array, there seems to be an off-by-one error when dragging over the elements of the Tree after an element has been added to the underlying Array. If you use an ArrayCollection as the dataProvider for the Tree, everything works fine.

In the demo below, try dragging any of the elements in the two Trees over the other elements in the Tree. All seems to work. Now reset the demo, and click the "Add Item" button for the Tree on the right (the one backed by the ArrayCollection.) Note that it continues to work as expected. Now try to do the same thing with the Tree on the left. After doing a fresh reset and clicking "Add Item", good luck trying to drop something onto the element with label "5".

After doing a small amount of debugging, I've come to the conclusion that the calculateDropIndex method is always returning the right value, but that setting selectedIndex is not correctly finding the right element in the Tree.

You can check out the source for this here.



Thursday, September 18, 2008

How to fix: slashes urlencoded as %2F giving 404 in Apache

I ran into a problem today where URLs that contained "%2F" in them were giving 404s in Apache. By enabling mod_rewrite logging it became evident that Apache was short circuiting the request even before entering the module.

After some googling I came upon the AllowEncodedSlashes directive. I have no idea why this is off by default, but it fixes the problem.

Monday, September 15, 2008

Pet Peeve of the Week: Commented Code

My fellow engineers at Amie Street can attest that I am full of almost inexplicable hatred for some very specific things. Today I would like to rant about my hatred for commented code.

By commented code, I don't mean the good kind of commented code. You know, the kind where it answers the questions you have about how something works but doesn't ramble on. I love this kind of commented code and try to write it wherever possible. Good commented code looks something like:

/**
* Parse a color parameter in the request. Expected format is
* six digit hex (like in HTML but without the #)
*/
private function getColorParam($request, $param, $default='FFFFFF') {
...

or maybe:

// Copy topleft corner
imagecopy(
$targetIm, $templateIm,
0, 0,
0, 0,
$slice_l, $slice_t);

Today I'm ranting about the bad kind of commented code. You know, the kind of commented code that is actually code that has been commented out. It looks like this:
public function newReleasesAction($request) {
/*
$n = date('N', time());
if($n==2){
// Its tuesday
$dateString = date("F jS", time());
}
else{
$dateString = date("F jS", strtotime('last Tuesday'));
}
*/
$dateString = date("F jS", Amie_DB::getUnixTimeForNow());
$this->stash('newReleasesDate', $dateString);

Note that the majority of the above code has been commented out with a block comment. Rather than answering questions, this kind of insidious "comment" leaves you with more. Mainly, why is it commented out? The developer trying to read this code can make several different guesses:

  • The code isn't necessary anymore - Maybe this code used to be important, but the variable it's setting wasn't used anywhere so someone commented out.

  • The code didn't work properly - the commented code was causing problems, so someone replaced it with the line below the comment block. The replacement code may or may not do the same thing as the original, but the developer needed to patch the issue to get the page to load, or whatever.

  • The code was commented out for debugging purposes and someone forgot to uncomment it. This kind of thing happens all the time - how often have you made a commit with random error logs like "Got here" in your codebase?

  • The code was replaced with a better version (or refactored) but someone had an emotional attachment- for some reason some developers are afraid of the delete key and the rm command.



Frankly, every one of these reasons is stupid. One by one:

The code isn't necessary anymore


Delete it! If you ever find that it becomes necessary again, this is why you use version control.

The code didn't work properly


Commenting it out just risks that someone else will come and uncomment it again at a later date. If you don't know how to fix it, you should replace the broken code with something like "/* TODO: the algorithm that used to be here had a flaw. See bug #234291. Replaced algorithm with hardcoded data below */" or something of that nature.

The code was commented out for debugging


Can't say I haven't done this plenty of times myself, but before you commit it's good practice to do a quick diff to make sure you haven't inadvertently added useless log messages or commented out code that should be working.

The emotional attachment


Get over it! If you ever want to admire your old work you can always check out an old version of the code from the repository. It's pretty easy to pull back old code, but being a code packrat is kind of like taking your leftovers at dinner, putting them in a shoebox, and shoving them under your bed in case you're hungry in a couple of weeks. When you come back to the dead code it will be full of bugs that have grown since it was last used.

Friday, September 12, 2008

Getting Click Events from the Slider Track in Flex

In the past few days I've run into two separate situations in which I needed to be able to get an event when the track of a slider was clicked. In one case, I was using an HSlider control to set the volume of a music player. When the track was clicked the thumb of the slider would nicely tween to its new position, but the volume of the sound would abruptly jump when the thumb was done moving.

Ideally you would be able to tween the volume at the same rate that the slider thumb is moving, but to do this you need to be able to know when the track is clicked, before the slider thumb is done moving. Furthermore, you need to know the values the slider is moving from and to.

Ordinarily, the only event the slider component will dispatch when the track is clicked is the "change" event, and this happens after the slider thumb is done moving to the new location on the track. It is possible, however, to receive the track click event by extending the HSlider or VSlider classes. Below is some example code. Though we shouldn't really be accessing mx_internal members, I can't see any other way to do it. I'm open to suggestions as to another way to get access to this event, and the from and to values of the slider.


package {
import flash.events.MouseEvent;
import mx.controls.HSlider;
import mx.core.mx_internal;

use namespace mx_internal;

public class ExtendedHSlider extends HSlider {
override protected function createChildren():void {
super.createChildren();

super.mx_internal::getTrackHitArea().addEventListener(MouseEvent.MOUSE_DOWN,
function(event:MouseEvent):void {
var fromValue:Number = value;
var toValue:Number = mx_internal::getValueFromX(event.localX);
trace("Hooray! We're getting events when the track is clicked!");
trace("The slider is going to move from " + fromValue + " to " + toValue);
});
}
}
}

Thursday, September 11, 2008

big sort_buffer_size causes slow filesort

I spent several hours tonight trying to track down why a 12-row filesort was taking upwards of 30ms on our production DBs. After installing 4 different versions of mysql and not figuring anything out, I finally took a guess and decreased sort_buffer_size from 64M to 256K.

I'm not entirely sure why I thought a 64M sort buffer was a great idea, but it turns out it's a terrible one! After setting it to 256K the query returns in 0.00sec as expected.

Looks like the guys at Percona have noticed this too.

Monday, September 08, 2008

array_intersect_key is terrible

I always had a hunch that array_intersect_key was slow in PHP, but I didn't quite realize how slow until tonight when I decided to benchmark it. Here's a quick test script:


$big = array();
for ($i = 0; $i < 10000; $i++) {
$big[$i] = 234;
}

$small = array();
for ($i = 0; $i < 10; $i++) {
$big[$i] = 2435;
}

////////////////////////////////////////////////////////////

print "Testing big first\n";

$st = microtime(true);
for ($i = 0; $i < 100; $i++) {
$junk = array_intersect_key($big, $small);
}
$bigfirst = microtime(true) - $st;

////////////////////////////////////////////////////////////

print "Testing small first\n";

$st = microtime(true);
for ($i = 0; $i < 100; $i++) {
$junk = array_intersect_key($small, $big);
}
$smallfirst = microtime(true) - $st;


print "array_intersect_key\n";
print "========================================\n";
print "Big first: $bigfirst\n";
print "Small first: $smallfirst\n";

////////////////////////////////////////////////////////////

function common_keys($a, $b) {
$res = array();
foreach ($a as $key => $val) {
if (isset($b[$key]))
$res[] = $key;
}
return $res;
}

print "Testing big first\n";

$st = microtime(true);
for ($i = 0; $i < 100; $i++) {
$junk = common_keys($big, $small);
}
$bigfirst = microtime(true) - $st;

////////////////////////////////////////////////////////////

print "Testing small first\n";

$st = microtime(true);
for ($i = 0; $i < 100; $i++) {
$junk = common_keys($small, $big);
}
$smallfirst = microtime(true) - $st;

////////////////////////////////////////////////////////////

print "common_keys\n";
print "========================================\n";
print "Big first: $bigfirst\n";
print "Small first: $smallfirst\n";
?>


And the results:


Testing big first
Testing small first
array_intersect_key
========================================
Big first: 13.1110720634
Small first: 12.5442099571
Testing big first
Testing small first
common_keys
========================================
Big first: 0.363513946533
Small first: 0.000243902206421


By implementing this in pure PHP you get a 50000x speedup over using the built-in in some cases. Wow...

Update: this appears to be fixed in PHP 5.2.5. See the Release Notes.

Thursday, September 04, 2008

Flexdoc for Ubiquity

Mike Chambers whipped up an API for AS3 reference on Google App Engine yesterday. The API allows you to send a class name or fragment of a class name a get the links to the livedocs pages for any matches. Combine this with Ubiquity and you have a super simple way to get to livedocs for any AS3 class. To install Ubiquity, you can grab the xpi from here. To add the command, go to this page and just click the subscribe button in the top right.

ubiquity_flexdoc gist