Refactoring Binary Type ActionScript Classes

A little while ago I wrote some type safe binary classes Byte and Word for a CPU emulator I'm working on. The classes worked correctly and were good fit for what I needed but something bothered me about them. The Word and Byte classes had a lot of common code. This was not really a surprise because I first wrote the Byte class than did copy and pasted and made a few changes here and there to make the Word class. For just these two class that's probably OK, but I now needed a third class (a Nibble or 1/2 a Byte) so it was time to refactor.

First off lets look at the methods that are common to both the Word and Bytes classes.

view plain print about
1function clearBit(bit:int):void;
2function getBit(bit:int):Bit;
3function isBitSet(bit:int):Boolean;
4function setBit(bit:int):void;
5function toDec(bits:int = -1):int;
6function toString():String;
7function get value():int;
8function set value(value:int):void;
9function dec():Byte or Word
10function inc():Byte or Word

All methods are just about identical except for minor differences in the toDec, toString, and set value methods and the return types of inc and dec. So first step is to move all of these methods to a new Binary class and we change the Word and Byte classes to extend Binary.

view plain print about
1public class Byte extends Binary
2public class Word extends Binary

What's the main difference between a Byte and a Word? Answer is the number of bits, so the next step was to add that property to the new class and set it in the constructor. The Byte and Word constructors are then changed to pass how many bits their types are.

view plain print about
1public function Byte(value:int = 0) {
2    super(8, value);
3}
4
5public function Word(value:int = 0) {
6    super(16, value);
7}

Next we needed to fix the set value method. It was using an expression with a hard coded constant to make sure that values can never be out of range. Like this for Byte:

view plain print about
1_value = _value & 0xFF;

And like this for Word:

view plain print about
1_value = _value & 0xFFFF;

It would be silly to have constants for each bit length so we need to calculate what the maximum value for a given bit length is and set that in the constructor. This is easily done using the left shift operator like so:

view plain print about
1_maxValue = (1 << noBits) - 1;

And then change set value to be:

view plain print about
1_value = value & maxValue;

ToDec was then simply fixed by defaulting the bit length to the number of bits of the binary type.

view plain print about
1public function toDec(bits:int = -1):int {
2    if (bits == -1) {
3        bits = _noBits;
4    }

The toString method was then generalised to work for any number of bits like so:

view plain print about
1public function toString():String {
2    var textmem:String = value.toString(16);
3            
4    while (textmem.length < _noBits/4) {
5        textmem = '0' + textmem;
6    }
7    
8    return textmem;
9}

And the Word's toString method changed to:

view plain print about
1override public function toString():String {
2    return '0x' + super.toString();
3}
As it prefixed strings with '0x'.

That just left the inc and dec methods. This is a bit tricker. While the methods do the same thing in the two classes they have different method signatures. The Word's inc method return a Word and the Byte's inc method returns a Byte. How can we solve this?

A good solution is can create and binary interface and change the inc and dec methods to return that Interface. But in doing so we do ending up loosing some of the type safety. This is one of those cases do you need to decide between simpler code or better type safety, in this instance I chose better type safety.

I changed the inc and dec methods inBinary to be protected, renamed then as intenal and called those methods from type safe version in the Word an Byte classes.

Here is the Binary's dec method:

view plain print about
1protected function decInternal():IBinary {
2    value = _value - 1;
3    return this;
4}

And the Byte's dec method:

view plain print about
1public function dec():Byte {
2    return decInternal() as Byte;
3}
I quite like this solution as in means that by using the set value method in the internal version of the methods overflow and underflow are taken care of. I may revisit it in the future and see if it can't be done in another way.

So was the refactoring worth it?

We ended up with:

  • Far less code in the Word and Byte classes and less code overall.
  • Far less duplication of code.
  • Less hard coded values and constants.
  • Far more flexibility as we now can create Binary types of any bit size (upto 30 bits) either directly with the binary class or extend it to create a new class.
  • We now have an interface (IBinary) that we can use for variables or properties whose size can change during runtime or have different bit sizes in different subclasses (for example 16 and 8 bit timers classes).

Yes it was well worth the small amount of time and effort it too to refractor the classes. The class interface didn't change so no other code outside the class needed to change and we ended up with higher quality, more flexible code which has less lines than before.

I have now released this code as a project on github: ActionScript Binary Types

The code is fully unit tested and comes with ASDocs describing each class.

It's under the MIT license so you are free to use it in commercial or non commercial projects. Enjoy!

Related Blog Entries

TweetBacks
Comments (Comment Moderation is enabled. Your comment will not appear until approved.)