Записи в категории ·

Без рубрики

· Category...

SourceOddity => FixInsight

Я уже писал о своем проекте SourceOddity. Он у меня, к сожалению, немного забуксовал. Но наконец закончилось лето, прошли отпуска и я вернулся к работе. И в связи с этим у меня есть три новости. Надеюсь, все три — хорошие.

1) Я переименовал SourceOddity в FixInsight. Считаю, что новое название лучше передает суть;
2) Я выложил часть проекта в open source;
3) Я наконец довел проект до состояния, когда его вроде бы не стыдно показать публике.

И так, встречайте FixInsight 2014.10beta. Первая публичная версия :)

Я благодарю людей, которые все это время помогали, советовали, тестировали анализатор на разных примерах кода. Эта помощь неоценима. Без вас я бы не справился.

Greek and Roman Mythology

В конце лета прошел на Coursera курс о греческой и римской мифологии от Пенсильванского университета.
Итоговый результат: 90.1 из 100.

Это третий мой онлайн-курс после Programming Languages и Algorithms: Design and Analysis, Part 1.

Самостоятельно я бы, пожалуй, никогда не нашел другого повода прочитать столько античной классики. Закрыл немало пробелов в гуманитарной части. Потрясающе повел эти 10 недель, рекомендую :)

  • Одиссея (Гомер)
  • Теогония (Гесиод)
  • Гомеровские гимны
  • Орестея (Эсхил)
  • Царь Эдип (Софокл)
  • Вакханки (Еврипид)
  • Энеида (Вергилий)
  • Метаморфозы (Овидий)

Сегодня наконец прислали сертификат.

DelphiAST Announce

In this post I would like to announce DelphiAST project.

DelphiAST is built on the top of publically available version of CastaliaDelphiParser which was much improved (see SimpleParser directory). I fixed a number of issues, added new syntax support and so forth.

With DelphiAST you can take real Delphi code and get an abstract syntax tree. One unit at time and without a symbol table though. Take a look at the sample code to see how it works.

This is a part of my effort to build a static analysis tool for Delphi. I decided to publish this under MPL 2.0 licence as my contribution to the community. I hope the community will contribute to this project too :)

Behavior Driven Development in Delphi

In this Skill Sprint I introduce behavior driven development principles, tell a few words about Gherkin language and demonstrate how this can be applied to Delphi programming.

August 12, 2014
6AM San Francisco / 9AM New York / 2PM London / 3PM Milan
11AM San Francisco / 2PM New York / 7PM London / 8PM Milan
5PM San Francisco / Wed & Fri 9AM Tokyo / Wed & Fri 10AM Sydney

Click here to register now for Skill Sprints.

Slides and links from this Skill Sprint:

DelphiSpec

Samples

Gherkin

Blog Posts

Первые шаги SourceOddity

Хочу рассказать о том, на что я потратил почти все свободное время в начале этой весны. Это статический анализатор кода для Delphi. Назвал я его скромно — SourceOddity. Очень краткий анонс и чуть больше скриншотов доступны по ссылке.

SourceOddity Screenshot

Мотивация достаточно проста.

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

Какие-то ситуации в коде встречаются часто, какие-то реже, какие-то совсем редко. И вот здесь проявляется преимущество статического анализа — бездушной машине можно поручить регулярный поиск даже очень редких случаев. Машина безмолвна, она не будет против. И чем больше будет таких правил, тем больше пользы принесет проверка.

Иногда, вовремя обнаружив подобную ситуацию, можно сэкономить много сил и нервов:

SourceOddity Screenshot

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

Статический анализ безусловно полезен.

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

Первая публичная версия будет доступна в ближайшие недели, а пока я хотел бы обсудить эту тему и, может быть, услышать идеи — какие проблемные места в коде вы хотели бы автоматически определять.

Тестируемая архитектура

Тема юнит-тестирования не устает подниматься в блогах и вебинарах. Раз тема актуальна, выскажу и я некоторые свои мысли.

У всех подобных статей и вебинаров есть одна общая и очень серьезная проблема. Людей убеждают, что тестирование — это хорошо, показывают на простых примерах как использовать инструменты. Многим это действительно интересно, люди слушают, загораются энтузиазмом. А на следующий день приходят на работу, видят свой старый проблемный код (а у кого он не проблемный?) и понимают, что тестирование вероятно не для них. Приходится им приглушить свой энтузиазм до следующего раза.

Для всех этих людей проповедники юнит-тестирования предлагают один и тот же рецепт: «читайте книжки про рефакторинг, исправляйте архитектуру». Хороший совет. Примерно такой же хороший как «пишите хорошие программы» или «мойте руки перед едой».

Я позволил себе немного иронии, но совет действительно хороший. Практика многих людей показывает, что разработка с учетом необходимости тестирования кода положительно влияет на стройность архитектуры. Иногда даже доходит до крайностей (извини, Александр):

ПРАВИЛЬНУЮ АРХИТЕКТУРУ без тестов и ТЗ построить — НЕВОЗМОЖНО.

Можно немного побыть Сократом (или троллем, кому как нравится) и спросить: «а что такое правильная архитектура»? Боюсь, ответить не сможет никто. Мне кажется, на самом деле каждый программист в глубине души знает, что его архитектура «неправильная». Я не встречал еще более-менее долгоживущих проектов с «правильной» архитектурой. Архитектура — это всегда компромиссы.

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

В конце концов, если мы будем долго тыкать пальцем в небо, со временем мы сможем составить карту дырок.

Собственно, что такое юнит-тест? Это довольно шаблонная и очень простая программа, вся суть которой заключается в подготовке входных данных, выполнении тестируемой функции и сравнения результата с неким эталоном. Какими свойствами должна обладать тестируемая функция, чтобы ее проще было тестировать? Будем считать, что вызывать все функции одинаково просто. Остается упростить подготовку входных данных и постараться сделать сравнение результата с эталоном как можно более простым. В связи с чем у меня есть вот такая мысль…

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

  • Отсутствие состояния;
  • Отсутствие побочных эффектов.

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

Что проще тестировать — функцию, которая принимает числовой параметр, преобразует его в строку и возвращает эту строку как результат или функцию, которая берет число из поля класса, преобразует его в строку и сохраняет результат в базе данных? Очевидно, первую функцию. Вторая нарушает сразу оба принципа — она и имеет побочные эффекты (работает с БД), и зависит от некоего состояния (поле класса).

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

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

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

Так мы приходим к анемичной модели, сильно не любимой Мартином Фаулером. Анемичная модель подразумевает разделение классов на классы данных и классы-сервисы. Этот подход расходится с классикой ООП, рекомендующей совмещать данные и методы их обработки в одних и тех же классах. Поэтому Фаулер анемичную модель не любит (мне кажется, что эти его взгляды немного устарели).

Но мы здесь говорим не о некой «идеальной архитектуре в вакууме», мы говорим об архитектуре, которую удобно тестировать. Так что я надеюсь, что мистер Фаулер нас простит.

И так… Какая архитектура более пригодна для юнит-тестирования? Как модифицировать существующую архитектуру, чтобы ее стало удобно тестировать? С учетом вышеизложенного можно прийти к следующим ответам на вопросы.

  • Изолировать состояние;
  • Изолировать побочные эффекты;
  • Тяготеть к анемичной модели.

Советов может быть много, но они скорее будут касаться каких-то более универсальных соображений. Я же постарался выделить именно те моменты, которые должны упростить внедрение юнит-тестирования в действующий проект.

DelphiSpec grows

Even though I didn’t have much time lately, I managed to do some significant progress with DelphiSpec library over the last couple of weeks. Some features were introduced in my previous post, now please let me introduce new features.

First of all, there is a new quite cosmetic, but very convenient and nice feature — now it is possible to declare step definitions without the use of attributes (special thanks to Stefan Glienke).

In the previous post I showed you this syntax:

    // sample scenario: "Given user ROOT exists"    
    [Given_('user (.*) exists')]
    procedure MyProcName(const Name: string);

Though this still works, now it’s possible to do the same without attributes at all:

 
    // sample scenario: "Given user ROOT exists"
    procedure Given_user_name_exists(const Name: string);

Thus you can use method name to declare step type and pattern. It is less flexible, but sometimes it is more convenient.

Also not only the string type is supported. This will work fine:

 
    // sample scenario: "Given I have 3 apples"
    procedure Given_I_have_N_apples(N: Integer);

You are even allowed to pass an array:

 
    // sample scenario: "Given I have a list 1,2,3,4"
    procedure Given_I_have_a_list_Ns(Ns: TArray<Integer>);

Or even array of records:

 
type   
  TUserInfo = record
    Name: string;
    Password: string;
    Id: Integer;
  end;

  // sample scenario
  // Given users exist:
  //  | id | name  | password |
  //  | 1  | Roman | pass1    |  
  //  | 2  | Other | pass2    |
  procedure Given_users_exist(Table: TArray<TUserInfo>);

Last example demonstrates a cool Gherkin data type — data table. Please note that this table is passed to a step definition method as a dynamic array of typed records. All type casting stuff is done by DelphiSpec and you don’t have to think about it… It is RTTI magic :)

One more type that Gherkin has and DelphiSpec supports is python-like multi-line strings. This kind of string is enclosed by three quotes and is passed to a step definition method as a regular Delphi string:

 Given I have a blacklist:
   """
   m@mail.com
   123@mail.com
   """

Besides there are a couple more interesting features.

Background allows you to add some context to all scenarios in a single feature. A Background is like an untitled scenario, containing a number of steps. The difference is when it is run: the background is run before each of your scenarios.

Feature: Accounts

Background:
  Given users exist:
    | id | name  | password |
    | 1  | Roman | pass1    |  
    | 2  | Other | pass2    |

Scenario: Correct Login
  Given my name is "Roman"
    And my password is "pass1"
  When I login
  Then I have access to private messages

Scenario: Incorrect Login
  Given my name is "Roman"
    And my password is "pass2"
  When I login
  Then access denied

Also now you can not only describe a single scenario, but a more general Scenario outline:

Scenario Outline: Add two numbers
  Given I have entered <num1> in calculator
    And I have entered <num2> in calculator
  When I press Add
  Then the result should be <sum> on the screen
  
  Examples:
    | num1 | num2 | sum |
    |  1   |  2   |  3  | 
    |  4   |  5   |  9  |
    |  3   |  1   |  4  |

The Scenario outline steps provide a template which never run directly. A Scenario Outline is run once for each row in the Examples section beneath it. The Scenario Outline uses placeholders, which are contained within angle brackets in the Scenario Outline’s steps

This is how a Scenario outline is showed in DUnit test tree:

«Add two numbers» has a single test for each case.

Full demo code is available on github. A plan for the future is to implement bindings to DUnitX and support both DUnit and DUnitX.

DelphiSpec неделю спустя

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

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

Во-первых, произошла по сути косметическая, но очень приятная модернизация — в классе содержащем step definitions простые шаги можно описывать без применения атрибутов.

В первом посте я показывал вам такой синтаксис:

    // пример сценария: "Given user ROOT exists"    
    [Given_('user (.*) exists')]
    procedure MyProcName(const Name: string);

Он отлично работает и сейчас, но теперь можно сделать еще проще — без атрибутов:

 
    // пример сценария: "Given user ROOT exists"
    procedure Given_user_name_exists(const Name: string);

То есть можно использовать имя самого метода для описания типа и синтаксиса шага. Выбирайте как удобнее.

Также теперь поддерживаются не только строковые параметры. Такой код будет работать:

 
    // пример сценария: "Given I have 3 apples"
    procedure Given_I_have_N_apples(N: Integer);

Можно передать даже массив:

 
    // пример сценария: "Given I have a list 1,2,3,4"
    procedure Given_I_have_a_list_Ns(Ns: TArray<Integer>);

И даже массив записей:

 
type   
  TUserInfo = record
    Name: string;
    Password: string;
    Id: Integer;
  end;

  // пример сценария
  // Given users exist:
  //  | id | name  | password |
  //  | 1  | Roman | pass1    |  
  //  | 2  | Other | pass2    |
  procedure Given_users_exist(Table: TArray<TUserInfo>);

Последний пример демонстрирует интересный тип данных Gherkin — data table. Обратите внимание, что такая таблица в метод-обработчик передается как массив обычных типизированных записей.

Всю работу с типами берет на себя DelphiSpec, вам об этом думать не придется. Библиотека может это делать благодаря магии RTTI. Не верьте, когда вам говорят, что волшебства не бывает :)

Еще один тип данных, который поддерживает Gherkin и теперь реализован в DelphiSpec — это «многострочные строки» в стиле Python. Такая строка обрамляется троекратными двойными кавычками сверху и снизу, а в метод-обработчик передается как обычный string:

 Given I have a blacklist:
   """
   m@mail.com
   123@mail.com
   """

С типами данных я, кажется, закончил. Расскажу еще о паре интересных возможностей.

Добавлена поддержка блока background. Этот блок позволяет описать контекст выполнения сценариев — он выполняется перед каждым сценарием.

Feature: Accounts

Background:
  Given users exist:
    | id | name  | password |
    | 1  | Roman | pass1    |  
    | 2  | Other | pass2    |

Scenario: Correct Login
  Given my name is "Roman"
    And my password is "pass1"
  When I login
  Then I have access to private messages

Scenario: Incorrect Login
  Given my name is "Roman"
    And my password is "pass2"
  When I login
  Then access denied

Также теперь можно описывать не только отдельные сценарии, но и более общие структуры сценариев:

Scenario Outline: Add two numbers
  Given I have entered <num1> in calculator
    And I have entered <num2> in calculator
  When I press Add
  Then the result should be <sum> on the screen
  
  Examples:
    | num1 | num2 | sum |
    |  1   |  2   |  3  | 
    |  4   |  5   |  9  |
    |  3   |  1   |  4  |

Такая структура будет развернута в несколько сценариев — по одному на каждую строку таблицы. В дереве DUnit подобная ситуация будет выглядеть вот так:

То есть создан не один тест «Add two numbers», а три. По одному для каждого случая.

Полный код примеров лежит вместе с библиотекой на github. Как я и обещал, DelphiSpec я не забрасываю и развиваю. Впереди мне предстоит тестирование и написание хотя бы краткой документации. А там уже и 1.0 не за горами :)

WordLines — работа на конкурс

Update: Теперь игра есть и в Google Play.

Много лет назад я написал небольшую игрушку — WordLines. Старожилы возможно помнят.

В связи с тем, что с недавнего времени Delphi поддерживает Android, а также в связи с подвернувшимся под руку конкурсом, стало интересно сделать то же самое для планшета. Хорошо получилось или не очень — судите сами. Главное помнить, что это не коммерческий продукт, а результат эксперимента.

На удивление все прошло довольно просто. Это непередаваемое ощущение — компилировать и запускать все то же самое, но на новом устройстве. Без шероховатостей конечно не обошлось и именно с процессом работы над WordLines связаны некоторые мои недавние посты, но в целом я остался доволен. Основные сложности были связаны не с Delphi, а с отсутствием у меня опыта разработки мобильных приложений — из-за разных форм-факторов заставить одну и ту же форму выглядеть одинаково на разных устройствах не так уж и просто (возможно, это требует привычки).

Картинка мелкая, да, но вот такое уж у меня на планшете разрешение, извините :)

Также поигрался с рекламируемым TRESTClient. О нем сказать особенно нечего. Он работает, все как обещали.

С помощью REST организованно взаимодействие с небольшим веб-сервисом. Этот функционал позволяет игроку отправить свои результаты на сервер и получить от него информацию о рекордном результате среди всех игроков. Так же есть онлайн-таблица рекордов. Уделять внимание дизайну мне было некогда, так что не пугайтесь :)

Бэкенд сделан с помощью эмбаркадеровского HTML5 Builder (люблю экстрим). Впечатления неоднозначные. Возможно, когда-нибудь о них расскажу.

Плюс ко всему бесплатно я получил версию WordLines для Mac и iOS. Это круто.

То есть обещанная 100% переносимость кода между платформами реально работает, нас не обманули. У меня во всем проекте буквально полтора IFDEF’а. Убедитесь сами.

Напоследок в двух словах расскажу о самой игре, если вдруг не все очевидно.

Все помнят Color Lines — на поле выпадают разноцветные шары, их можно перемещать по полю и составлять линии одного цвета, которые исчезают.

В WordLines на поле выпадают буквы и перемещая эти буквы нужно составлять слова, которые затем исчезают. Но исчезают они не автоматически, а после нажатия кнопки «Убрать слова» (в этой версии — зеленая стрелка под списком найденных слов). За убранные с поля слова начисляются очки. Если убирать слова автоматически, то играть не так интересно, как мне кажется — буквы пропадают внезапно и становится труднее составлять длинные слова.

Есть много идей, как игру можно улучшить, но не забывайте, что в данный момент это в первую очередь эксперимент.

Ну и конечно же ссылки: .exe (win32), .apk и исходники.

DelphiSpec Library Announce

Martin Fowler:

That said, I do think that the greatest potential benefit of DSLs comes when business people participate directly in the writing of the DSL code. The sweet spot, however is in making DSLs business-readable rather than business-writeable. If business people are able to look at the DSL code and understand it, then we can build a deep and rich communication channel between software development and the underlying domain.

In this post I’d like to announce DelphiSpec library. It was inspired by Cucumber. Actually, this library is the implementation of Gherkin language in Delphi. This implementation is not complete (consider it as an alpha version). Some features haven’t been implemented yet, but this project is still in development.

In a few words, the essence of the project is that DelphiSpec allows to turn the scenario into the appropriate unit test for DUnit:

Feature: Calculator

Scenario: Add two numbers
  Given I have entered 50 in calculator
    And I have entered 70 in calculator
  When I press Add
  Then the result should be 120 on the screen

Scenario: Add two numbers (fails)
  Given I have entered 50 in calculator
    And I have entered 50 in calculator
  When I press Add
  Then the result should be 120 on the screen

Scenario: Multiply three numbers
  Given I have entered 5 in calculator
    And I have entered 5 in calculator
    And I have entered 4 in calculator
  WHEN I press mul
  Then the result should be 100 on the screen

I’ll underline that these are not tests but scenarios. In other words, this is the Behavior-Driven Development (BDD) in addition to the famous Test-Driven Development (TDD). Such scenarios serve as documentation, automated tests and development-aid — all rolled into one format. You should not describe internal system logic using scenarios: they should describe how user sees an application behavior.

Use of Gherkin is a good choice for DelphiSpec because it’s now considered as de-facto standard in the particular niche and if you took interest in it you can even buy a book with the best practices, tips on writing scenarios and examples in this language. Also there is lots of information on the Internet.

Let’s return to the example (the Calculator). The scenarios execution result in DUnit will look like:

How does this work? Gherkin is very small itself. Actually its only aim is to describe Given-When-Then formula. There are several keywords, all the rest could be customized.

We test a very simple class: a calculator. I will not post its code here, you can find it on github. The interesting thing is: how will the program understand and execute commands like “Given I have entered 50 in calculator”? Here comes the helper class which describes these steps (so-called «step definitions»). This is the helper class itself:

unit SampleCalculatorStepDefs;
 
interface
 
uses
  SampleCalculator, DelphiSpec.Attributes, DelphiSpec.StepDefinitions;
 
type
  [_Feature('calculator')]
  TSampleCalculatorSteps = class(TStepDefinitions)
  private
    FCalc: TCalculator;
  public
    procedure SetUp; override;
    procedure TearDown; override;
 
    [_Given('I have entered (.*) in calculator')]
    procedure EnterInt(Value: string);
 
    [_When('I press Add')]
    procedure AddInt;
 
    [_When('I press Mul')]
    procedure MulInt;
 
    [_Then('the result should be (.*) on the screen')]
    procedure TestResult(Value: string);
  end;
 
implementation
 
uses
  System.SysUtils, TestFramework, DelphiSpec.Core;
 
{ TSampleCalculatorSteps }
 
procedure TSampleCalculatorSteps.AddInt;
begin
  FCalc.Add;
end;
 
procedure TSampleCalculatorSteps.EnterInt(Value: string);
begin
  FCalc.Push(Value.ToInteger);
end;
 
procedure TSampleCalculatorSteps.MulInt;
begin
  FCalc.Mul;
end;
 
procedure TSampleCalculatorSteps.SetUp;
begin
  FCalc := TCalculator.Create;
end;
 
procedure TSampleCalculatorSteps.TearDown;
begin
  FCalc.Free;
end;
 
procedure TSampleCalculatorSteps.TestResult(Value: string);
begin
  if FCalc.Value <> Value.ToInteger then
    raise ETestFailure.Create('Incorrect result on calculator screen');
end;
 
initialization
  RegisterStepDefinitionsClass(TSampleCalculatorSteps);
 
end.

In fact, every step has a type (Given/When/Then) and the regular expression that describes the rest part of the step. Regular expressions help to extract the parameters which will be passed to the class methods to perform the relevant part of the scenario.

The only thing we need to do so that this system works is to add into project file (.dpr) the procedure call which receives the path to a folder which contains “.feature” files to process.

begin
  PrepareDelphiSpecs(TPath.Combine(ExtractFilePath(Application.ExeName), 'features'), True, 'EN');
  DUnitTestRunner.RunRegisteredTests;
end.

The last parameter (“EN”) allows us to specify a language in which we write the script. This is another interesting Gherkin possibility. Since, generally, there is no difference in what language the text is processed using regular expressions; it would be a good idea to translate a few keywords into different languages (using this XML).

So nothing prevents us from writting scenarios in any language:

Функционал: Калькулятор

Сценарий: Сложить два числа
  Допустим я ввожу 50 на калькуляторе
         И я ввожу 70 на калькуляторе
  Когда я нажимаю СЛОЖИТЬ
  Тогда результат на экране должен быть 120

These are the very first steps, but I’m planning to implement full Gherkin functionality.

P.S. There are some comments in G+: https://plus.google.com/u/0/+RomanYankovsky/posts/2S789T49dX8