пятница, 29 марта 2013 г.

О качестве диаграмм UML

Я считаю вот это:



- "максимально хорошей" диаграммой. По количеству элементов. Хотя и к ней уже есть вопросы. Например - про пересечения. Я пересечения тут - СПЕЦИАЛЬНО оставил, чтобы ПОДЧЕРКНУТЬ, что это ОДИН И ТОТ ЖЕ элемент. Но это не значит, что так надо делать в "реальной жизни".

Если диаграмма становится больше - это повод разбить её на две, или пересмотреть АРХИТЕКТУРУ. Это - "звоночек".

Я сам - ДАЛЕКО не всегда это делаю. НО ИМЕЮ В ВИДУ этот критерий.

Чего и вам - искренне советую.

Попробуйте - может быть вам понравится.

О БД и SQL

Не стоит спрашивать меня про БД, SQL и DataSet'ах etc.

Я не найду что ответить.

Я скажу лишь СВОЁ мнение - "SQL - это "зло"". Это - моё мнение. У меня есть конечно аргументы. Когда-нибудь я их наверное напишу.

По историческим причинам так сложилось, что я работаю с "другими БД".

Можете пытаться начать холивар. Я в нём всё равно - участвовать не буду.

И ещё - попробуйте увидеть в этом посте сарказм и самоиронию :-) Она тут - конечно же есть.

Как я исправляю ошибки

"Shit happens". Это постулат.

Если я получаю ошибку в разработку, то я делаю примерно следующее:

1. Выясняю все необходимые детали, если они ещё не выяснены.
2. Пишу тест (или прошу коллег сделать это, есть люди - более подкованные).
3. Идеально, если получается тест на синтетических данных и в "песочнице", а не на реальом приложении.
4. Если не получается, то я делаю скриптовый тест к реальному приложению. (Может быть когда-нибудь я дойду до того - как я это делаю)
5. Запускаю тест, убеждаюсь, что он падает.
6. Кладу тест и его эталоны в репозитарий.
7. Отлаживаю ошибку используя тест как полигон для испытаний.
8. Исправляю ошибку.
9. Убеждаюсь, что тест не падает.
10. Кладу исправленный код, тест и эталоны в репозитарий.
11. Прогоняю другие тесты.
12. Смотрю на упавшие.
13. Из упавших отбираю те где "это не ошибка, а стало только лучше". Исправляю эти тесты или их эталоны.
14. Исправляю остальные упавшие тесты или привлекаю коллег или УБЕЖДАЮСЬ в противоречивости ТЗ (тут надо писать ОТДЕЛЬНЫЙ пост - отчасти тут - http://18delphi.blogspot.com/2013/04/blog-post.html).
15. Прогоняю все тесты.
16. Убеждаюсь, что ошибок больше нет.
17. ОСТАВЛЯЮ НОВЫЙ тест в репозитарии тестов. ОТНЫНЕ - он работает на меня.
18. Сплю спокойно.

Попробуйте. Может быть и вам - понравится.

VCL и FireMonkey

Пока напишу лишь только своё мнение - "надо начинать стараться забыть о VCL".

... to be continued ...

UML

Если я своими постами не убедил вас, что UML (с кодогенерацией) - "это здорово!". То я буду всеми силами стараться это сделать. А пока привыкайте к тому, что я код предваряю диаграммами. Я работаю так. Сначала рисую диаграмму. Получаю скелет кода, а потом - заполняю его "мясом". Я думаю - диаграммами. Кодом - тоже думаю, но реже.

Немного об использовании DUnit

Тут мы говорили о шаблонах и примесях:
http://18delphi.blogspot.com/2013/03/blog-post_29.html

Теперь я расскажу о простейшем использовании фреймворка DUnit.

ЗАЧЕМ нужны тесты я немного написал вот тут - http://18delphi.blogspot.com/2013/03/blog-post.html

Думаю, что со временем приведу примеры "из жизни".

Пока рассмотрим лишь технику на абстрактном примере.

Буду описаться на классы полученные в предыдущем примере.

Как всегда начнём с диаграммы:
Теперь, что получаем на Delphi:

SandBox.dpr:

program SandBoxTest;
 
uses
  TestFrameWork
  GUITestRunner,
  IntStack,
  IntStackTest,
  StringStack,
  StringStackTest;
 
 
begin
 GUITestRunner.RunRegisteredTests;
end.

----------------------------------
IntStackTest.pas:

unit IntStackTest;
 
interface
 
uses
  TestFrameWork
 
  ;
 
type
 TIntStackTest = {final} class(TTestCase)
 published
 // published methods
   procedure DoIt;
 end;//TIntStackTest
 
implementation
 
uses
  IntStack,
  SysUtils
  ;
 
// start class TIntStackTest
 
procedure TIntStackTest.DoIt;
const
 cEtalons : array [0..3] of integer = (10, 20, 3, 5);
var
 l_S : TIntStack;
 l_I : Integer;
begin
 l_S := TIntStack.Create;
 try
  for l_I := Low(cEtalons) to High(cEtalons) do
   l_S.Push(cEtalons[l_I]);
  for l_I := High(cEtalons) downto Low(cEtalons) do
   Check(l_S.Pop = cEtalons[l_I]);
 finally
  FreeAndNil(l_S);
 end;//try..finally
end;//TIntStackTest.DoIt
 
initialization
 TestFramework.RegisterTest(TIntStackTest.Suite);
 
end.


----------------------------------
StringStackTest.pas:

unit StringStackTest;
 
interface
 
uses
  TestFrameWork
  ;
 
type
 TStringStackTest = class(TTestCase)
 published
 // published methods
   procedure DoIt;
 end;//TStringStackTest
 
implementation
 
uses
  StringStack,
  SysUtils
  ;
 
procedure TStringStackTest.DoIt;
const
 cEtalons : array [0..3] of String = ('мыма', 'мыла', 'раму', 'весело');
var
 l_S : TStringStack;
 l_I : Integer;
begin
 l_S := TStringStack.Create;
 try
  for l_I := Low(cEtalons) to High(cEtalons) do
   l_S.Push(cEtalons[l_I]);
  for l_I := High(cEtalons) downto Low(cEtalons) do
   Check(l_S.Pop = cEtalons[l_I]);
 finally
  FreeAndNil(l_S);
 end;//try..finally
end;//TStringStackTest.DoIt
 
 
initialization
 TestFramework.RegisterTest(TStringStackTest.Suite);
 
end.


-- получили тесты работоспособности наших классов :-)

По-моему - здорово. Особенно если тесты КАЖДЫЙ день гонять :-) Если гоняем каждый день, то оперативно видим - чего сломали.

Не говорите только - "это ОЧЕНЬ просто" :-) Дьявол - он всегда в деталях.

Более сложные примеры "из жизни" - я постараюсь позже привести.

Главное, что для многих проектных классов - тесты это очень простой путь отладки - чтобы не поднимать полномасштабное приложение, которое компилируется 10 мин. Не разворачивать сервер БД и т.п. Придумали "данные из головы", написали тест - и всё - можно отлаживать. Данный конкретный класс.

Понятно, что ИМЕННО НА ЭТИХ данных. Но ничто не мешает - постепенно пополнять базу тестов и базу "данных из головы".

Позже я напишу - как я пользуюсь mock'ами. На основе файлов эталонов.

Что это такое пока читайте тут:

Если вы скажете - "покажите тестирование форм и GUI", то я отвечу вам - "покажу. Непременно. У меня - богатый опыт".

А пока - сосредоточьтесь на том факте, что формы как раз - "не страшно трогать". Ибо они "торчат наружу юзеру". А вот базовые классы - страшно. Особенно если их использует несколько десятков форм. Разных. Особенно если они написаны "тем парнем который пять лет назад уволился". Особенно если нет тестов. И чем более "базовый" класс. И чем больше в нём найдено ошибок, тем больше для него будет написано тестов. Со временем. Если вы будете прислушиваться к моим рекомендациям. И тем более стабильным - он станет.

Попробуйте - может быть вам понравится.

Следующая серия тут:

О шаблонах и примесях

Первая серия была тут:

http://18delphi.blogspot.com/2013/03/generic-generic.html

Теперь поговорим немного о примесях.

Теорию можно почитать тут:
http://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%B8%D0%BC%D0%B5%D1%81%D1%8C_(%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5)

Мы же немного займёмся практикой.

Посмотрим на определение - "При́месь (англ. mix in) — элемент языка программирования (обычно класс или модуль), реализующий какое-либо чётко выделенное поведение. Используется для уточнения поведения других классов, не предназначен для порождения самостоятельно используемых объектов. В объектно-ориентированных языках программирования является способом реализации классов, отличным от широко используемых принципов, пришедших из языка программирования Simula. Механизм впервые реализован в Flavors. Преимуществом примесей является то, что повышая повторную используемость текстов программ, этот метод избегает многих проблем множественного наследования. Однако при этом метод накладывает свои ограничения."

Тем кто программирует на С++ - "повезло" - у них есть множественное наследование и вопросов "как встроить примесь в существую иерархию классов" - не возникает. Программистам на Delphi - "не повезло". Множественного наследования - нет. Да в общем и слава богу. Ибо примеси это гораздо более узкоспециализированный инструмент, чем множественное наследование.

Про конкретные примеры использования примесей в жизни я напишу отдельно. А сейчас рассмотрим чисто абстрактный пример иллюстрирующий лишь технику встраивания примесных классов в иерархию наследования проектных Классов.

В предыдущей статье у нас был пример:
Перерисуем диаграмму следующим образом:













Видим, что весь функционал Stack'а переехал в StackPrim.
И появилось ещё два класса - TIntStackFromPersisten наследующийся от TPersistent и StackPrim, и класс TIntStackFromComponent наследующийся от TComponent и StackPrim.

Множественное наследование - спросите вы. Логически - да. На уровне стрелочек диаграмм. 

Теперь посмотрим как это выглядит на Delphi:

StackPrim.imp.pas:


{$IfNDef StackPrim_imp}
 
{$Define StackPrim_imp}
 ItemsHolder = array of _ItemType_;
 
 _StackPrim_ = {mixin} class(_StackPrim_Parent_)
 private
 // private fields
   f_Items : ItemsHolder;
 public
 // public methods
   procedure Push(const anItem: _ItemType_);
   function Pop: _ItemType_;
 end;//_StackPrim_
 
{$Else StackPrim_imp}
 
// start class _StackPrim_
 
procedure _StackPrim_.Push(const anItem: _ItemType_);
var
 l_L : Integer;
begin
 l_L :=  Length(f_Items);
 SetLength(f_Items, l_L + 1);
 f_Items[l_L] := anItem;
end;//_StackPrim_.Push
 
function _StackPrim_.Pop: _ItemType_;
var
 l_L : Integer;
begin
 l_L :=  Length(f_Items) - 1;
 Result := f_Items[l_L];
 SetLength(f_Items, l_L);
end;//_StackPrim_.Pop
 
{$EndIf StackPrim_imp}

---------------------------------------------
Stack.imp.pas:


{$IfNDef Stack_imp}
 
{$Define Stack_imp}
 _StackPrim_Parent_ = TObject;
 {$Include StackPrim.imp.pas}
 _Stack_ = {mixin} class(_StackPrim_)
 end;//_Stack_
 
{$Else Stack_imp}
 
{$Include StackPrim.imp.pas}
 
{$EndIf Stack_imp}

--------------------------------------------
StringStack.pas:


unit StringStack;
 
interface
 
type
 _ItemType_ = AnsiString;
 {$Include Stack.imp.pas}
 TStringStack = class(_Stack_)
 end;//TStringStack
 
implementation
 
{$Include Stack.imp.pas}
 
end.
--------------------------------------------
IntStack.pas:


unit IntStack;
 
interface
 
type
 _ItemType_ = Integer;
 {$Include Stack.imp.pas}
 TIntStack = class(_Stack_)
 end;//TIntStack
 
implementation
 
{$Include Stack.imp.pas}
 
end.
--------------------------------------------
!!! А вот и два НОВЫХ класса:
--------------------------------------------
IntStackFromPersistent.pas:


unit IntStackFromPersistent;
 
interface
 
uses
  Classes
  ;
 
type
 _ItemType_ = Integer;
 _StackPrim_Parent_ = TPersistent;
 {$Include StackPrim.imp.pas}
 TIntStackFromPersistent = class(_StackPrim_)
 end;//TIntStackFromPersistent
 
implementation
 
{$Include StackPrim.imp.pas}
 
end.
--------------------------------------------
IntStackFromComponent.pas:


unit IntStackFromComponent;
 
interface
 
uses
  Classes
  ;
 
type
 _ItemType_ = Integer;
 _StackPrim_Parent_ = TComponent;
 {$Include StackPrim.imp.pas}
 TIntStackFromComponent = class(_StackPrim_)
 end;//TIntStackFromComponent
 
implementation
 
{$Include StackPrim.imp.pas}
 
end.
--------------------------------------------
По-моему - весело :-) Примесь - ОДНА, а включается в ЧЕТЫРЕ разных класса и даже в разные места иерархии наследования.

Попробуйте. Может и вам понравится.

В следующих сериях я постараюсь рассказать - ЗАЧЕМ я это использую.


четверг, 28 марта 2013 г.

О тестах и не только

Кратко - так:

UseCase - проецируется в класс реализации, вложенные UseCase - во вложенные классы.. 

Далее ТЗ детализируются УТВЕРЖДЕНИЯМИ (предикатами) внутри UseCase - Типа - "должна быть настройка ТАКАЯ-ТО" это проецируется также во вложенный класс.. или в функтор.. 

ну и тесты - примерно так же 

ну и АСПЕКТЫ - присущие ВСЕЙ системе и ОРТОГОНАЛЬНЫЕ UseCase.. т.е. это такие UseCase которые могут включаться в ОСНОВНЫЕ прецеденты системы.. 

пример АСПЕКТОВ - логирование, печать, аудит.. 

АСПЕКТЫ описываются отдельно и от прецедентов ставим к аспектам стрелки РЕАЛИЗАЦИИ. На выходе - получаем конкретны классы, позволяющие модифицировать аспекты для конкретного их применения.. 

Например АСПЕКТ "печать" реализуется "печатью документа" или "печатью картинки" в соответствующих прецедентах "работа с документом" и "работа с картинкой".. 

аспекты в части реализации - по сути - ПРИМЕСИ есть основная реализация аспекта, которая примешивается к реализации прецедента и детализуется некоторыми особенностями прецедента.. например для аспекта "печать" - ОСОБЕННОСТЬ - это то ЧТО печатаем - ДОКУМЕНТ или КАРТИНКУ.. 

всё остальное скрыто в ОПИСАНИИ и РЕАЛИЗАЦИИ аспекта.. например что есть предварительный просмотр.. есть собственно печать.. что в печати бывают колонтитулы... 

логирование - так это вообще АСПЕКТ, который описывается отдельно и который включается практически во ВСЕ прецеденты.. ДЕТАЛИЗАЦИЯ логирования это то ЧТО ИМЕННО логируем.. а детали логирования... сбора логов.. и т.п - скрыто собственно в АСПЕКТЕ 

АСПЕКТ это также - сохранение в форматы, DnD, копирование в Clipboard, разные граничные условия, типа обработки исключений и отсылки логов

 АСПЕКТ - это показ различных уведомлений и предупреждений пользователю.. они конечно присущи каждому конкретному ПРЕЦЕДЕНТУ, но имеют общую АРХИТЕКТУРУ, описание и ТЗ 

СКИНЫ - это классический ПРИМЕР аспекта 

скин конечно инкорпорирован в каждую конкретную реализацию ПРЕЦЕДЕНТА, но при этом скин - это целостная сущность т.е. в итоге имеем набор шаблонов АСПЕКТОВ и ПРЕЦЕДЕНТОВ, и каждый последующий прецедент конструируем на основе этих шаблонов, если спроектировалось, то всё отлично, если не спроектировалось - расширяем библиотеку шаблонов 

про ТЕСТЫ - наверное отдельная серия... Тесты на языке типа - 

"Открываем Конституцию" 
"Смещаемся в конец"
"Выделяем 5 параграфов"
"Печатаем." 

Это - реальность 

это FORTH 

на самом деле главное, что тесты МОЖНО и НУЖНО писать в терминах разрабатываемой системы 

И если ТЗ расписано в терминах ПРЕЦЕДЕНТОВ и АСПЕКТАХ, то автоматически получаем КАРКАСЫ тестов автоматических 

а из АВТОМАТИЧЕСКИХ тестов получаем - автоматическое тестирование и управление баг-трекингом 

есть ПРЕЦЕДЕНТ.. под него есть тест.. пока тест проходит - всё хорошо.. тест перестал проходить - идём в баг-треккер.. 

ищем такую ошибку про тест.. если не нашли, то заводим.. отправляем в разработку.. 

если нашли - переводим из состояния ЗАКРЫТО в состояние В РАЗРАБОТКЕ.. 

постим в ошибку новые подробности типа логов и скриншотов.. и так - по кругу. 

то же касается и ОШИБОК, а не только ПРЕЦЕДЕНТОВ.. 

была ошибка в баг-треккере - пишем под неё тест.. вносим в базу тестов.. пускаем каждый день.. дальше - по кругу.. либо тест прошёл.. либо - СМ ВЫШЕ 

КАЖДУЮ ошибку которую ПРАВИТ разработчик - покрываем тестом... или находим УЖЕ существующий.. 
---------------------------------
P.S. Если вы думаете, что я написал, что-то сложное, то читайте ДЕЙСТВИТЕЛЬНО сложное:
http://ru.wikipedia.org/wiki/%D0%A2%D0%B5%D0%BE%D1%80%D0%B5%D0%BC%D0%B0_%D0%BE_%D0%B4%D1%83%D1%88%D0%B5

а то, что я написал - я постараюсь в конечном итоге проиллюстрировать диаграммами на UML и конечно примерами кода. А пока это конечно не статья, а "поток сознания".

Кто-то ещё использует "парсинг" из RX?

http://traditio-ru.org/wiki/%D0%90%D0%BB%D0%B3%D0%BE%D1%80%D0%B8%D1%82%D0%BC_%D0%BC%D0%B0%D0%BB%D1%8F%D1%80%D0%B0_%D0%A8%D0%BB%D0%B5%D0%BC%D0%B8%D1%8D%D0%BB%D1%8F

http://russian.joelonsoftware.com/Articles/BacktoBasics.html

в RX есть функции работы со строками - GetWordCount и GetWord.

for i := 0 to GetWordCount(aStr) do
 doString(GetWord(aStr, i));

Понятно где тут "алгоритм маляра"?

Надеюсь - Америку ни для кого не открыл.

P.S. А тут кстати - самое дело - использовать лямбды. В Delphi XE3 они кстати есть? Я пока - не нашёл... :-(

P.P.S callback и Event - ТОЖЕ можно, но это все знают и БЕЗ меня.

P.P.P.S. Рассказать как это у меня устроено на l3Stub? Или неинтересно?

Generic'и без Generic'ов....


Когда-то Акжан Абдулин это уже писал... Делюсь... Я лет 8-мь это уже 
использую...

шаблоны в Delphi 7:

TList.intf:
TList = class(Parent)
Add(a: ItemType);
Insert(i: Integer; a: ItemType);
Sort();
end;

..

TList.impl
TList.Sort()
..
CompareItems(a, b) : Integer;

..
Parent = TObject;
ItemType = integer;

{$include TList.intf}

TIntList = class(TList)
..

CompareItems(a, b : Integer) : Integer;
begin
Result := a - b;
end;

{$Include TList.impl} 
-------------------------------------------------------------------

Пример:


------------------------------------------------------------------------------------------------
ИТОГО:

stack.imp.pas:

{$IfNDef Stack_imp}
 
{$Define Stack_imp}
 ItemsHolder = array of _ItemType_;
 
 _Stack_ = {mixin} class(TObject)
 private
 // private fields
   f_Items : ItemsHolder;
 public
 // public methods
   procedure Push(const anItem: _ItemType_);
   function Pop: _ItemType_;
 end;//_Stack_
 
{$Else Stack_imp}
 
// start class _Stack_
 
procedure _Stack_.Push(const anItem: _ItemType_);
var
 l_L : Integer;
begin
 l_L :=  Length(f_Items);
 SetLength(f_Items, l_L + 1);
 f_Items[l_L] := anItem;
end;//_Stack_.Push
 
function _Stack_.Pop: _ItemType_;
var
 l_L : Integer;
begin
 l_L :=  Length(f_Items) - 1;
 Result := f_Items[l_L];
 SetLength(f_Items, l_L);
end;//_Stack_.Pop
 
{$EndIf Stack_imp}

----------------------------------------------------------------------------------------------
IntStack.pas:

unit IntStack;
 
interface
 
type
 _ItemType_ = Integer;
 {$Include Stack.imp.pas}
 TIntStack = class(_Stack_)
 end;//TIntStack
 
implementation
 
{$Include Stack.imp.pas}
 
end.

----------------------------------------------------------------------------------------------
StringStack.pas:

unit StringStack;
 
interface
 
type
 _ItemType_ = AnsiString;
 {$Include Stack.imp.pas}
 TStringStack = class(_Stack_)
 end;//TStringStack
 
implementation
 
{$Include Stack.imp.pas}
 
end.


!!!! Понятно, что ДИНАМИЧЕСКИЙ МАССИВ тут используется просто для примера. Понятно, что было можно ввести PItemType = ^_ItemType_ и оперировать указателями и GetMem. Но это усложнило бы пример.
И понятно, что тут нету проверки граничных условий.

Попробуйте. И может быть - вам понравится.

Ну а про настоящие generic'и читаем тут - http://keeper89.blogspot.ru/2011/07/delphi.html

А одна из моих реализаций данной идеи описана тут - http://18delphi.blogspot.com/2013/07/2_18.html