LE Blog

Инженер с поэтической душой

19.08.2009 firtree_right Бинарные операции для работы с цветом в ActionScript

Введение

Закончился период отпусков и авральных возвращений к работе. Акклиматизация пройдена успешно, и настала пора возобновить ведение блога.

Задача

Возможно, это уже давно всем известно, но тем не менее, мне бы хотелось об этом написать. В ActionScript существует как минимум два способа обращаться с цветом. Если мы работаем с BitmapData, то используется ARGB, то есть прозрачность и цвет задаются одним числом типа uint. Кстати, 0xffffffff — непрозрачный белый цвет — максимальное число этого типа.

Когда же мы работаем с, например, Graphics, то цвет и прозрачность задаются отдельно друг от друга. Причём прозрачность в диапазоне от 0 до 1. Хотелось бы быстро раскладывать ARGB на части и снова собирать.

Решение

В одну сторону:

var argb:uint = 0xccabcdef;
var a:Number = (argb >>> 24) / 255.0;
var rgb:uint = argb & 0xffffff;

Аналогично, кстати, можно выделить и каналы по отдельности:

var red:uint  =(argb & 0xff0000) >>> 16;
var green:uint  =(argb & 0xff00) >>> 8;
var blue:uint  =(argb & 0xff);

Оператор >>> вместо >> используется чтобы избежать проблем со знаком.

В обратную сторону, соответственно:

argb = (uint(a*255) << 24) | rgb;
argb = (uint(a*255) << 24) | (red << 16) | (green << 8) | blue;

Всё совершенно логично. Чтобы разделить, используется бинарное и, а чтобы объединить — бинарное или.

Для возобновления трансляции, как мне кажется, этого материала достаточно.

28.05.2009 firtree_right Тестирование в ActionScript с помощью AsUnit

Введение

Сейчас занимаюсь тестированием и отладкой достаточно большого проекта на флексе. При каждом обновлении продукт проходит длительный и подробный этап тестирования. Как нельзя более актуальной становится проблема «исправив одну ошибку не сделать новых».

Один из инструментов, который мог бы мне помочь, если бы я его использовал сразу — это тестирование, а точнее Unit Tests (изолированное тестирование).

Понятно, что ActionScript не самый удобный язык для изолированного тестирования. Сложно сделать связанные объекты независимыми друг от друга, чтобы тестировать их по отдельности. Да и в целом продукты на Flash и Flex имеют большую интерфейсную составляющую. И тестировать зачастую нужно впечатления пользователя. Но тем не менее, покрытие тестами, пусть и небольшое, — это удобно.

Задача

Протестировать объект, который получает данные из xml и выдает их в качестве собственных параметров. Попробуем сделать это в лучших традициях TDD

Ресурсы

Нам понадобится AsUnit — прекрасная библиотека с открытыми исходниками для изолированного тестирования (unit testing).

Так же используем лучшие традиции TDD:

  1. Никакого кода, пока нет провалившегося теста.
  2. Тест пишется до тех пор, пока он не начнет проваливаться.
  3. Кода нужно писать ровно столько, чтобы проваливающийся тест прошёл.

Для решения будем использовать Adobe Flash CS3 в качестве редактора.

Решение

Итак, скачиваем AsUnit с сайта по ссылке выше. Создаем среду для тестирования. ConfigTest.as:

package {
  import asunit.framework.TestCase;

  public class ConfigTest extends TestCase {
    private var instance:Config;

    /**
     * Запускается перед каждый тестом
     */
    protected override function setUp():void {
      instance = new Config();
    }

    /**
     * Запускается после каждого теста
     */
    protected override function tearDown():void {
      instance = null;
    }

    /**
     * Тест-проверка, что созданный объект нужного класса
     */
    public function testIsConfig():void {
      assertTrue("Example is Config", instance is Config);
    }
  }
}

Это набор тестов для нашего будущего класса, который будет называться Config. Теперь, если бы у нас было несколько классов и несколько наборов тестов, их нужно было бы собрать воедино. AllTests.as:

package {
import asunit.framework.TestSuite;

  public class AllTests extends TestSuite {
    public function AllTests() {
      super();
      addTest(new ConfigTest());
    }
  }
}

Теперь нам нужен, собственно, тестировщик. AsUnitRunner.as:

package {
import asunit.textui.TestRunner;

  public class AsUnitRunner extends TestRunner {
    public function AsUnitRunner() {
      start(AllTests);
    }
  }
}

Теперь создаем AsUnitRunner.fla, в настройках File->Publish Settings->Flash->Settings прописываем в Document class базовый класс AsUnitRunner и добавляем путь к asunnit/as3/src в Classpath.

Попробуем запустить (ctrl+enter) — неудача! :) Можно сказать, провалившийся тест. Чтобы тест прошёл достаточно создать описание класса. Config.as:

package {
  public class Config {
  }
}

И теперь когда мы запускаем наш тестировщик мы видим:

AsUnit 3.0 by Luke Bayes and Ali Mills

Flash Player version: WIN 9,0,115,0

.

Time: 0.024

OK (1 test)


Time Summary:

23ms : ConfigTest

Все тесты проходят. Пора закончить писать код и снова перейти к написанию тестов. Добавим тестирование желаемого функционала. Я хочу загружать в Config xml и получать значения из него в виде заданных параметров. В описание ConfigTest.as добавим метод:

    /**
     * Тест-проверка, что из xml получаются параметры x и y
     */
    public function testParsesCoordinates():void {
      var xml:XML = <root>
                      <point>
                        <x>10</x>
                        <y>20</y>
                      </point>
                    </root>
      instance.fromXML(xml);
      assertEquals("X property should equal 10", 10, instance.x);
      assertEquals("Y property should equal 20", 20, instance.y);
    }

Попробуем запустить тестировщик — не компилируется, говоря, что у класса отсутствуют методы. Создаем описания методов в Config.as. Наша задача исправить только те ошибки, о которых нам сообщили. Теперь он выглядит так:

package {
  public class Config {
    public function fromXML(xml:XML):void {
    }
    public function get x():int {
      return 0;
    }
    public function get y():int {
      return 0;
    }
  }
}

Результат тестирования теперь выглядит так:

AsUnit 3.0 by Luke Bayes and Ali Mills

Flash Player version: WIN 9,0,115,0

..F

Time: 0.037
There was 1 failure:
0) ConfigTest.testParsesCoordinates
AssertionFailedError: X property should equal 10 expected:<10> but was:<0>
    at asunit.framework::Assert$/fail()
    at asunit.framework::Assert$/failNotEquals()
    at asunit.framework::Assert$/assertEquals()
    at ConfigTest/testParsesCoordinates()
    at asunit.framework::TestCase/runMethod()
    at asunit.framework::TestCase/runBare()
    at Function/http://adobe.com/AS3/2006/builtin::apply()
    at <anonymous>()
    at SetIntervalTimer/onTimer()
    at flash.utils::Timer/_timerDispatch()
    at flash.utils::Timer/tick()

FAILURES!!!
Tests run: 2,  Failures: 1,  Errors: 0


Time Summary:

36ms : ConfigTest

Заметьте, что после первой ошибки тестирование прекращается. Теперь мы можем написать код, чтобы пройти этот тест. Наш класс выглядит теперь так:

package {
  public class Config {
    private var _x:int;
    private var _y:int;
    public function fromXML(xml:XML):void {
      _x = int(xml.point.x);
      _y = int(xml.point.y);
    }
    public function get x():int {
      return _x;
    }
    public function get y():int {
      return _y;
    }
  }
}

А результат теста так:

AsUnit 3.0 by Luke Bayes and Ali Mills

Flash Player version: WIN 9,0,115,0

..

Time: 0.037

OK (2 tests)


Time Summary:

36ms : ConfigTest

Ураа!! Можно перестать писать код и написать ещё тесты! :)

Выводы

Понятно, что с непривычки это выглядит дико. Вместо одной программы нужно писать две. И вроде бы кажется, что я же знаю, как дальше писать, я четко вижу функционал. И в простых проектах это действительно так. Но...

В более сложных системах, когда множество одних объектов опираются на функционал других, не так легко удержать в голове все связи и все последствия исправлений. Возможно, именно то, что я только что исправил, использовалось другим объектом в том виде, в каком оно было.

Так же покрытие кода тестами обеспечивает более легкий вход изменений. Чисто даже психологически проще начать что-то менять.

Естественно, изолированное тестирование не оставляет без работы тестеров-людей. Но позволяет им сосредоточиться на тестировании именно того, что нельзя протестировать автоматически.

Материалы для изучения

Прекрасное видео про tdd в руби и просто прекрасное видео Документация и примеры AsUnit

16.05.2009 firtree_right Эволюция алгоритма замены в строке ActionScript

В последнее время моя работа в текущем проекте заключается в отладке, оптимизации и отлове багов. Поэтому в самой работе практически не встречается того, о чём можно было бы здесь написать. Но на помощь пришла гугл-группа ruFlash и комьюнити молодых программистов. :)

Задача

Один из участников попросил составить выражение для удаления из текста ссылок с определенным текстом внутри целиком. Например, в выражении:

var str:String = '<a href="somelink">_some text_</a> ';
str += 'More text! ';
str += '<a href="anotherlink">**remove me**</a> ';
str += '<a href="yetanotherlink"><s>another text</s></a>';

Нужно удалить целиком ссылку, содержащую фразу remove me.

Понятно, что первое приходящее в голову выражение /<a.+?remove me.*?</a>/ захватит две первые ссылки. И «жадность» не поможет, т.к. поиск осуществляется по порядку, и, найдя первый <a, выражение не остановится до самого remove me.

Решение номер один

Поскольку к концу недели отладки и пересмотра одного и того же кода голова моя не была готова что-то изобретать, я последовал пути, предлагавшемуся одним из ответивших, слегка его доделав:

var re0:RegExp = /<a[^>]+>[^a]*remove me.*?<\/a>/g;

trace(str.replace(re0, "!removed!"));

Недостаток его очевиден. Хотя для приведенного примера он работает, но всё-таки, может и отказать, если встретит a между > и remove me. Например:

str += '<a href="anotherlink">eh! ah? **remove me**</a> ';

Решение номер два (рекурсивное)

Поскольку к этому моменту мозг ещё не расслабился окончательно и не готов был отказаться от выбранного способа думать о задаче, второе решение, пришедшее сразу за первым, было значительно сложнее.

Оно использовало возможность подсовывать функцию в качестве аргумента. Вот оно:

var re1:RegExp = /<a(.+?remove me.*?<\/a>)/g;

var replacer1:Function = function():String {
  var s:String = arguments[1].toString();
  if (s.indexOf("<a") > 0) {
    return "<a" + s.replace(re1, replacer1);
  } else {
    return "!removed!";
  }
}

trace(str.replace(re1, replacer1));

Здесь речь идёт о том, чтобы в группе (см. скобки), следующей после <a проверять наличие ещё одного <a. И в случае его наличия запускать ту же процедуру замены, но уже на группе.

Довольный собой, я запостил своё решение в ruFlash и поехал домой. По дороге домой мозг окончательно расслабился, и я смог увидеть задачу в отрыве от способа думать, который я выбрал изначально. И мне пришло

Решение номер три

Зачем городить рекурсию, когда можно просто перебирать все ссылки и заменять (удалять) только те, что нужно?

var re2:RegExp = /<a[^>]+>(.+?)<\/a>/g;

var replacer2:Function = function():String {
   var s:String = arguments[1].toString();
  if (s.indexOf("remove me") > 0) {
    return "!removed!";
  } else {
    return arguments[0];
  }
}

trace(str.replace(re2, replacer2));

Это ли не чудесно?

Выводы

  1. Решайте задачи.
  2. Решив (или не решив), записывайте то, что получилось, покажите кому-нибудь. Это позволит выкинуть решение из головы.
  3. Если есть решение лучше, то оно придет на освободившееся место.