Показаны сообщения с ярлыком DSL. Показать все сообщения
Показаны сообщения с ярлыком DSL. Показать все сообщения

четверг, 1 августа 2013 г.

Ссылки про DSL

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

О кодогенерации

Вот код, он генерируется с ОДНОГО "квадрата на модели":


program SearchTest;
 
uses
...
  ;
 
{$R SearchTest.res}
 
begin
 {$IfDef nsTest}
 g_CVSPath := 'w:common\components\SearchTest';
 {$EndIf nsTest}
 //#UC START# *511B4CA100B2CVSPath*
 //#UC END# *511B4CA100B2CVSPath*
 TAutoTestsSuite.Register;
 try
  if KTestRunner.NeedKTestRunner([TToK]) then
   KTestRunner.RunRegisteredTests
  else
  if System.IsConsole then
   TextTestRunner.RunRegisteredTests
  else
   GUITestRunner.RunRegisteredTests;
 except
  on E: Exception do
  begin
   {$If defined(MTDORB) AND defined(NoKPageTool)}
   if TKBridge.Exists then
    TKBridge.Instance.Logout;
   {$IfEnd}
   l3System.Exception2Log(E);
   Halt(2);
  end;//Exception
 end;//try..except
 if (TestsExitCode <> 0) then
  Halt(TestsExitCode);
end.


А генерируется он так:

// реализация абстрактного стереотипа Delphi интерфейсы и реализация::MDAGenerator
// Реализация на Delphi(.pas)
+ impl.pas
R  
//#UC START# *470F15B800CBfor470382290251*
//#UC END# *470F15B800CBfor470382290251*
 
// реализация абстрактного стереотипа Delphi интерфейсы и реализация::MDAGenerator
// Родные Delphi интерфейсы (.pas)
+ intf.pas
R  
//#UC START# *470F1571031Cfor470382290251*
O [{"%f_pas_UnitFileName(%S)"!=""}[{%S{finished}!=true}NOT_FINISHED_]%f_pas_UnitFileName(%S)]
C %S%f_pas_UnitPath()
 %f_clear_list(TOTAL_USES)
 %f_pas_OpenUnitInterface(%S)
 
 [{%S{console}=true}
 
 {$APPTYPE CONSOLE}
 ]
 
 [{%SS=VCMTestTarget}
 <{}{%D#f_IsVCMGUI()=true}
 %D%f_OutExtIntfUses()
 %f_cycle_break(%S)
 >
 ]
 
 %S%f_pas_TotalUses()
 [{%S{need UC in project}=true}
 n%U[{manualuses}n]
 ]
 %f_pas_CloseUses(%S)
 %f_clear_TotalUses(%S)
 
 [{%S{console}!=true}
 {$R [{%S{finished}!=true}NOT_FINISHED_]%f_pas_UnitName(%S).res}
 [{%S{needs second icon}=true}n{$R main_icon2.res} // вторая иконка приложения]
 
 ]
 [{%S{need UC in project}=true}
 %U[{manualres}n]n
 ]
 
 begin
 [{%S%f_IsTest()=true}{
  %U[{manualcode}n ]
 }
  {$IfDef nsTest}
  g_CVSPath := '%f_str_replace(%SR%Sd,/,\)';
  {$EndIf nsTest}
  %U[{CVSPath}n ]
 [{%S{need UC in project}=true}{
 [{%SS=VCMTestTarget}{
 [{%S{no scripts}!=true}
  TAutoTestsSuite.Register;\n
 ]
  try
   if KTestRunner.NeedKTestRunner
 ([<{, }{%C#f_IsTestResults()=true}%f_pas_TypeName(%C)>])
  then
    KTestRunner.RunRegisteredTests
   else
   if System.IsConsole then
    TextTestRunner.RunRegisteredTests
   else
    GUITestRunner.RunRegisteredTests;
  except
   on E: Exception do
   begin
    {$If defined(MTDORB) AND defined(NoKPageTool)}
    if TKBridge.Exists then
     TKBridge.Instance.Logout;
    {$IfEnd}
    l3System.Exception2Log(E);
    Halt(2);
   end;//Exception
  end;//try..except
  if (TestsExitCode <> 0) then
   Halt(TestsExitCode);
 }
  TF1AutoTestSuite.Register;
 <{}{%D#f_IsVCMGUI()=true}
 %D%f_OutApplicationBody()
 %f_cycle_break(%S)
 >
 ]
 }
  %U[{manualcode}n ]
 ]
 ]
 %f_pas_CloseUnit(%S)
 <{}{%CV!=PrivateAccess&%C#f_IsClassInner()!=true&"%CO"!=""}%CX>
//#UC END# *470F1571031Cfor470382290251*

-- по-моему - всё понятно.

понедельник, 22 апреля 2013 г.

Тестировщик

Пишет коллега (то что выделено - выделено - мной):

Раньше я и не подозревал, что в нашем городе (Тверь) можно найти работу, которая непосредственно связана с тестированием каких-либо программных продуктов, приложений и т.п. Да, и вообще о существовании должности тестировщика, отделах тестирования и прочих тематических деталях мне не приходилась слышать до некоторых пор. Но как только это произошло, моё внимание было полностью скоординировано именно на этой, так сказать, карьерной ветви. Тогда я ещё учился в университете, а точнее, даже уже писал дипломный проект. А так как моё образование было целиком и полностью связано с IT-сферой, я решил, что тестирование для меня будет интересным родом деятельности, в котором я смогу стать квалифицированным специалистом.

И вот, я устроился работать тестировщиком программного обеспечения. Я был ознакомлен с программным продуктом, алгоритмами тестирования, внутренней инфраструктурой и прочими элементами, которые распахнули передо мной двери в так называемый мир Тестировщика. Задача моя была весьма понятной. Необходимо было «брать» тест-кейсы, применять их для приложения и находить, таким образом, баги. Этим я, собственно, и занимался. На самом деле, хоть это и не было довольно «крутым» делом (на мой взгляд), чувствовалось, что приобретается ценный опыт не только по данной профессии, но и в развитии аналитического мышления. И благодаря этому опыту получалось смотреть на приложения не только глазами обычного пользователя (как было в начале), но и глазами хорошего тестировщика.

Конечно, не всегда всё упиралось в тест-кейсы, так как была свобода действий (никто «не бил по рукам»), но, несмотря на это, спустя примерно полгода работа стала казаться рутинной. Тест-кейсы брались уже по n-ному кругу, а тестируемое приложение, по большому счёту, оставалось таким же (кардинальных изменений не было, а новый функционал практически не прибавлялся). Поэтому лично я чувствовал, что могу, хочу и должен делать что-то большее, приносить более весомую пользу тестируемому приложению. И вышло так, что уважаемые коллеги из Москвы предоставили мне возможность заниматься автоматизацией. Бинго! Это то, что было нужно. Новый уровень, да и вообще, другая, более серьёзная деятельность. Мне дали шанс заняться программированием. Необходимо было покрывать автоматическими тестами найденные ошибки, что считалось весьма полезным делом, ведь это:
  • обеспечивало пополнение базы автоматических тестов, которые наряду с тестировщиками, регулярно проверяли приложение на те или иные ошибки;
  • помогало программистам, которые исправляли баги, без труда прогонять тесты и проверять результаты своих исправлений;
  • позволяло отслеживать работу приложения в целом и находить такие ошибки, которые тестировщику найти было бы проблематично из-за сложного, неочевидного и/или непостоянного алгоритма повторения.
Язык, на котором требовалось писать авто-тесты, называется FORTH и представляет собой «живой язык» программирования, сгенерированный на основе Delphi. Сначала всё казалось гораздо проще, особенно, когда писался первый тест, ведь это выглядело примерно так:

: Тест
«Открыть документ»
«Выделить всё»
«Сравнить выделенный текст с эталоном»
;
Тест

Всё по-русски, всё понятно, да и работает, как надо. Но со временем всё чаще приходилось сталкиваться с ситуациями, когда содержимого словаря языка было недостаточно для того, чтобы написать тот или иной тест. И пришлось лезть ВНУТРЬ. А там ждали совсем не «Открыть документ» и «Выделить всё», там ждали уже отголоски Delphi, а также ООП. Но благодаря уважаемым коллегам и это стало более или менее перевариваться. Спустя несколько месяцев уже было в моих силах реализовать новое функционально «русское» слово, которое делало бы то, что, собственно, необходимо. Массивы, строки, деревья, стеки, рекурсия и прочие «хорошие вещи», с которыми связывала меня только студенческая жизнь, оказались очень полезными. Приходилось «играться» с размерами форм, модальными окнами, таймерами и прочим. И выходило всё достаточно неплохо, да и в удовольствие :)

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

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

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

Владимир Селянкин.

четверг, 18 апреля 2013 г.

Ещё о DSL

Вот так выглядит наш кодогенератор "изнутри":


// Выводит объявление класса
%f _pas_OutClassInterface
//#UC START# *494B7197025Afor470F151302FD*
[{%S#f_IsMixIn()=true}\
%f_set_var(POS,"%f_string_find(%S{ifndef},DesignTimeLibrary)")\
[{%{POS}N!=-1}\
\n\{$IfDef DesignTimeLibrary\}
%f_pas_TypeName(%S) = %f_pas_ImpurityParentName(%S);
\{$EndIf DesignTimeLibrary\}
]\
]\

[\n%S%f_open_ifdef()]\
<{}{%t_interface(%R)=true}%R<{}{%CS=impurity value}%f_set_var(%CN,"%f_pas_TypeName(%C%T)")>>\
// - устанавливаем типы параметров реализуемых методов
[{%S#f_IsMixIn()=true}%f_set_var(WAS_TYPE,"1")]\
// - чтобы лишнее ключевое слово не выводить
%f_shift_intend(-1)\

<{}{%CV!=PrivateAccess&%C%f_IsClassInner()!=true&"%CO"=""}\
[{%CS!=Typedef|%C%GU!=%SU|%C{isClassRef}=true}\
[{"%C{ifdef}"!=""|"%C{ifndef}"!=""}\
%f_pas_OpenType(%S)\
]\
[%CX\n]]\
>\

%f_shift_intend(+1)\
%f_pas_OpenType(%S)\
[{%S{need UC}=true}%U[{ci}\n]\n]\
%S%f_pas_OutMixInValues()\
<{}{"%CS"="impurity value"}\
[{%SU=%C%TU}%SF]\
#_%CN_ = %f_pas_TypeName(%C%T);\n\
>\
<{}{%C#f_IsDefine()=true&%CV=PublicAccess}%CX>\
//
[{%S#f_IsMixIn()=true}{\
%f_set_var(PARENT,"%f_pas_MainParentName(%S)")}\
[{<{}{%G#f_IsMixIn()!=true}{C}>=0}{\
%f_set_var(PARENT,"%f_pas_TypeName(%G)")}\
%f_set_var(PARENT,"%f_pas_ImpurityParentName(%S)")\
]\
]\
//
%f_set_var(WAS_PROLOGUE,"false")\
<{}{%G#f_IsMixIn()=true}\
// [{"%{PARENT}N"!=""}#%f_pas_ImpurityParentName(%G) = %{PARENT}N;\n]\
[{%G<{}{%g#f_IsMixIn()!=true}{C}>=0&"%{PARENT}N"!=""}#%f_pas_ImpurityParentName(%G) = %{PARENT}N;\n]\
%f_set_var(PARENT,"%f_pas_TypeName(%G)")\
%S%f_OutL3ItemsPrologue(%G,"WAS_PROLOGUE")\
%f_OutIncludePrim(%G)\n\
>\
//
<{}{%R#f_IsMixIn()=true}\
#%f_pas_ImpurityParentName(%R) = %{PARENT}N;\n\
%f_set_var(PARENT,"%f_pas_TypeName(%R)")\
%S%f_OutL3ItemsPrologue(%R,"WAS_PROLOGUE")\
%f_OutIncludePrim(%R)\n\
>\
//
#%f_pas_TypeName(%S) = \

[{%Sa=abstract}{\
[{%Sa=final}{\
%f_set_var(CLASS_MARK_PREFIX,"")\
}\
%f_set_var(CLASS_MARK_PREFIX,"final")\
]}\
%f_set_var(CLASS_MARK_PREFIX,"abstract")\
]\
[{"%S%f_pas_OutClassMark()"!=""}{\
[{"%{CLASS_MARK_PREFIX}N"!=""}\{%{CLASS_MARK_PREFIX}N\} ]\
}\
\{\
[{"%{CLASS_MARK_PREFIX}N"!=""}%{CLASS_MARK_PREFIX}N ]\
%S%f_pas_OutClassMark()\} ]\
class[(%S%f_pas_OutAncestor()\
[<{}{%R#f_ClassImplementable()=true&%S%f_SomeAncestorImplements(%R)!=true}\
[ %R#f_open_ifdef()]\
, %f_pas_TypeName(%R)\

[%R<{}{%R#f_ClassImplementable()=true&%1#f_Implements(%R)!=true}\
, %f_pas_TypeName(%R) \{from %f_pas_TypeName(%S)\}\
>\
]\

[%R#f_close_ifdef()\n#]\

>\

])\
]\
[\n# \{* %f_Doc(%S) \}]\
%S%f_pas_OutBeforeFields()
[#private
#// private fields
<{}{%CV=PrivateAccess&%C#f_IsAttribute()=true&%C#f_IsProp()!=true&%CS!=static}[%CX\n]>]\
\

// [{%S%f_HasAnyVCMAreaLink()=true}\
//// #private
//// #// auto fields
////// %f_with_gen_id(intf3.pas,<%R<{}{%CC=Class&%C#f_IsVCMAreaLink()=true}[%CX\n]>>)\
//// %f_with_gen_id(intf3.pas,<{}{%o#f_IsVCMAreaLink()=true}[%oX\n]>)\

// #protected
// #  procedure ClearRefs; override;
// ]\

[#protected
%S%f_pas_OutOverridesIntf()\
]\
\
// [#private
// #// property fields
// %f_with_gen_id(intf3.pas,<{}{%C#f_IsStaticObject()!=true&%C#f_IsStruct()!=true}[%CX\n]>)]\
\
[#private
#// private methods
<{}{%CV=PrivateAccess&%C#f_IsAttribute()!=true&%C%f_IsClassInner()=true}[%CX\n]>]\
\
[#protected
#// property methods
<{}{%C#f_IsProp()=true}[%CX\n]>]\
\

%f_clear_list(REALIZED_METHODS)\
[#protected
#// realized methods\

// Генерируем интерфейс методов "квадратиком", вложенных в фасеты/интерфейсы:
<\

%R<{}{%CC=Class&%C#f_IsClassImplementableElement()=true&%C#f_IsRealizedBySomeClassElement(%1)!=true}\
[\n%C%f_pas_MethodInterface(%1,"r")]\
>\

%R<{}{%1#f_Implements(%G)!=true}{%G}\
%G<{}{%CC=Class&%C#f_IsClassImplementableElement()=true&%C#f_IsRealizedBySomeClassElement(%2)!=true}\
[\n%C%f_pas_MethodInterface(%2,"r")]\
>\
>\

>\
// end Генерируем интерфейс методов "квадратиком"...

<{}{%oS!=inline}\
[{%o#f_IsAttribute()=true|%S%f_IsPublicRealizedMethod(%o)!=true}\
[\n%o%f_pas_MethodInterface(%S,"r")]\
]\
>\n\
]\
// #protected

[#public
#// realized methods
<{\n}{%oS!=inline}\
[{%o#f_IsAttribute()!=true&%S%f_IsPublicRealizedMethod(%o)=true}\
%o%f_pas_MethodInterface(%S,"r")\
]\
>\n\
]\
// #public

[#protected
#// overridden property methods
<{\n}{%O#f_IsAttribute()=true}%O%f_pas_MethodInterface(%S,"o")>\n]\
[#protected
#// overridden protected methods
<{\n}{%OV=ProtectedAccess&%O#f_IsAttribute()!=true&%OS!=inline}\
%O%f_pas_MethodInterface(%S,"o")\
>\n\
]\
// #protected

[#public
#// overridden public methods
<{\n}{%OV=PublicAccess&%O#f_IsAttribute()!=true&%OS!=inline}\
%O%f_pas_MethodInterface(%S,"o")\
>\n\
]\
// #public

[#protected
#// protected fields
<{}{%CV=ProtectedAccess&%C#f_IsAttribute()=true&%C#f_IsProp()!=true&%CS!=static}[%CX\n]>\
]\

[#public
#// public fields
<{}{%CV=PublicAccess&%C#f_IsAttribute()=true&%C#f_IsProp()!=true&%CS!=static}[%CX\n]>\
]\

\
[#protected
#// protected methods
<{}{%CV=ProtectedAccess&%C#f_IsAttribute()!=true&%C%f_IsClassInner()=true&%C#f_IsPublishedAccess()!=true}[%CX\n]>]\
[#public
#// public methods
<{}{%CV=PublicAccess&%C#f_IsAttribute()!=true&%C%f_IsClassInner()=true&%C#f_IsPublishedAccess()!=true}[%CX\n]>]\
\
[#private
#// private properties
%f_with_gen_id(intf2.pas,<{}{%CV=PrivateAccess}[%CX\n]>)]\
[#protected
#// protected properties
%f_with_gen_id(intf2.pas,<{}{%CV=ProtectedAccess}[%CX\n]>)]\

[#public
#// public properties
%f_with_gen_id(intf2.pas,<{}{%CV=PublicAccess}[%CX\n]>)\
%f_with_gen_id(intf2.pas,<{}{%o#f_IsProp()=true&%o%P#f_IsContract()=true}[%oX\n]>)\
]\

[{%S{need UC}=true}%U[{publ}\n]\n]\
[{%S{singleton}=true&<{}{%C#f_IsFactory()=true}{C}>=0}\
#public
#// singleton factory method
#  class function Instance: %f_pas_TypeName(%S);
#   {- возвращает экземпляр синглетона. }
]\

%f_clear_list(CAST_METHODS)\
[#protected
#// Методы преобразования к реализуемым интерфейсам
<{}{%t_interface(%R)=true&%R%f_InTie()!=true}\
%R<{\n}{%R#f_IsPureMixIn()!=true&%t_interface(%R)=true&%f_exists_in_list(CAST_METHODS,R)!=true}\
%f_add_to_list(CAST_METHODS,R)\
%f_pas_OutCastMethodInterface(%R,%1)\
>\
>
]\
%f_clear_list(CAST_METHODS)\

[#published
#// published methods
<{}{%CV=PublicAccess&%C#f_IsAttribute()!=true&%C%f_IsClassInner()=true&%C#f_IsPublishedAccess()=true&%C{isPublishedAccess}!=true}\
[%CX\n]\
>\
]\

%S%f_pas_BeforeClassEnd()\
#end;//%f_pas_TypeName(%S)\
%S%f_pas_AfterClassEnd()\
<{}{%t_interface(%R)=true}%R<{}{%CS=impurity value}%f_set_var(%CN,"")>>\
%f_shift_intend(-1)\

<{}{%CV!=PrivateAccess&%C%f_IsClassInner()!=true&"%CO"=""}\
[{%CS=Typedef&%C%GU=%SU&%C{isClassRef}!=true}[%CX\n]]\
>\

%f_shift_intend(+1)\
%S%f_pas_BeforeClassCloseIfDef()\
[\n%S%f_close_ifdef()]\
%f_shift_intend(-1)\
// %f_warning(%SN)\
%f_with_gen_id(intf3.pas,<{}{%C#f_IsStaticObject()=true|%C#f_IsStruct()=true}\
// %f_warning(%CN)\
[\n%CX]>)\
// - генерируем псевдо-конструкторы записей и StaticObject'ов
%f_shift_intend(+1)\
[\n\
<{}{%CV=PublicAccess&%C#f_IsAttribute()=true&%C#f_IsProp()!=true&%CS=static}\
[\n%CX]\
>\
]\

%f_clear_list(REALIZED_METHODS)
//#UC END# *494B7197025Afor470F151302FD*

или так:


// перекрытие базового стереотипа Delphi интерфейсы и реализация::MDAGenerator
%f _DoSpell
//#UC START# *4B2A19E3038Bfor4B386A11030E*
%S%[inherited]\
[{"%{Tl3StringIDEx}N"=""}%f_find_element(Tl3StringIDEx,Tl3StringIDEx)]\
[{"%{Tl3MessageID}N"=""}%f_find_element(Tl3MessageID,Tl3MessageID)]\
[{"%{Dialogs}N"=""}%f_find_element(4AB0EE02004E,Dialogs)]\

// <{}{%C#f_IsMessage()=true}\
// %C<{}{%C#f_IsChoices()=true}\
// %C%f_DoSpell()>\
// >\

%f_set_var(FOUND,"false")\
%f_set_var(PARENT,"")\
<{}{%P#f_IsClassBase()=true|%P#f_IsUtilityPack()=true}{%P}\
[{%{FOUND}N=false}\
%f_set_var(FOUND,"true")\
%f_set_var(PARENT,P)\
]\
>\

%{PARENT}%f_make_accessable(%{Tl3StringIDEx}U)\
%{PARENT}%f_make_accessable(%{Tl3MessageID}U)\
[{%{PARENT}C=Class}\
%{PARENT}%f_add_dependency(%SU_%{Tl3StringIDEx}U_uses,%{Tl3StringIDEx}U,uses,,USES_Inst)\
%{PARENT}%f_add_dependency(%SU_%{Tl3MessageID}U_uses,%{Tl3MessageID}U,uses,,USES_Inst)\
]\
%{PARENT}%f_add_class(%SU_%{Tl3StringIDEx}U_LCImpl,Constants,LCImpl[{%S#f_IsChoices()=true}_%PN_]%SN,LocalConst_Inst)\
%{LocalConst_Inst}%f_set_visibility_type(%SV)\

[{%S#f_IsChoices()=true}{\
%{LocalConst_Inst}%f_set_documentation(Локализуемые строки %SN)\
}\
%{LocalConst_Inst}%f_set_documentation(Варианты выбора для диалога %PN)\
]\

%{LocalConst_Inst}%f_set_up(TreatAsVars,true)\
// - шаманство, чтобы превратить константы в переменные
%{LocalConst_Inst}%f_set_up(ifdef,%S{ifdef})\
%{LocalConst_Inst}%f_set_up(ifndef,%S{ifndef})\
%{LocalConst_Inst}%f_set_up(children prefix,%S{children prefix})\
%{LocalConst_Inst}%f_set_up(elements prefix,str[_%S{elements prefix}])\

%f_set_var(WAS_USES_TO_DIALOGS,"false")\

<{}{}{%C}\

[{%C#f_IsMessage()=true}{\
%f_set_var(CONST_TYPE,{Tl3StringIDEx})\
}\
[{%{WAS_USES_TO_DIALOGS}N!=true}\
%{PARENT}%f_make_accessable(%{Dialogs}U)\
%f_set_var(WAS_USES_TO_DIALOGS,"true")\
%{PARENT}%f_add_dependency(%SU_%{Tl3MessageID}U_uses_Dialogs,%{Dialogs}U,uses,,USES_Inst)\
]\
%f_set_var(CONST_TYPE,{Tl3MessageID})\
]\

%{LocalConst_Inst}%f_add_attribute(%{LocalConst_Inst}U_%CU_Impl,,%C#f_AdditionalPrefix()%f_N(%C) :\
%{CONST_TYPE}U = (rS : -1; rLocalized : false;\
rKey : '%C%f_pas_Prefix()%C#f_AdditionalPrefix()%f_N(%C)';\
rValue : \

[{"%CD"=""|%C#f_CanUseDocAsValue()!=true}{\
//                 ^ - ибо UserType'ы неправильно выливаются
%CD\
}\
[{"%C{Value}"=""}{\
%C{Value}\
}\
%C%VN\
]\
]\

),Attr_Inst)\

%{Attr_Inst}%f_set_visibility_type(%CV)\
%{Attr_Inst}%f_set_documentation([{}{%C{Value}}[{}{%C%VN}%CD]])\
%{Attr_Inst}%f_set_up(ifdef,%C{ifdef})\
%{Attr_Inst}%f_set_up(ifndef,%C{ifndef})\
%{PARENT}%f_add_operation(%CU_Init,ini,Init_%f_N(%{Attr_Inst}) (),Op_Instance)\
%{Op_Instance}%f_set_documentation(Инициализация %{Attr_Inst}%f_pas_Prefix()%f_N(%{Attr_Inst}))\
%{Op_Instance}%f_set_abstraction_type(final)\
%{Op_Instance}%f_set_visibility_type(PrivateAccess)\

[{"%S{ifdef}"!=""}{\
%f_set_var(IFDEF_VALUE,"%C{ifdef}")\
}\
[{"%C{ifdef}"!=""}{\
%f_set_var(IFDEF_VALUE,"%S{ifdef}")\
}\
%f_set_var(IFDEF_VALUE,"%S{ifdef},%C{ifdef}")\
]\
]\

%{Op_Instance}%f_set_up(ifdef,%{IFDEF_VALUE}N)\

[{"%S{ifndef}"!=""}{\
%f_set_var(IFDEF_VALUE,"%C{ifndef}")\
}\
[{"%C{ifndef}"!=""}{\
%f_set_var(IFDEF_VALUE,"%S{ifndef}")\
}\
%f_set_var(IFDEF_VALUE,"%S{ifndef},%C{ifndef}")\
]\
]\

%{Op_Instance}%f_set_up(ifndef,%{IFDEF_VALUE}N)\
%{Op_Instance}%f_set_uc_content(intf.pas,,\
%{Attr_Inst}%f_pas_Prefix()%f_N(%{Attr_Inst}).Init;\
%C%f_AdditionalInitCode("%{Attr_Inst}%f_pas_Prefix()%f_N(%{Attr_Inst})")\
)\
>\

[{%Gx=true}\
%{PARENT}%f_add_class(%SU_%{CONST_TYPE}U_LCConstArr,ConstantArray,%SNMap,LocalConstArray_Inst)\
[{"%{Pl3StringIDEx}N"=""}%f_find_element(Pl3StringIDEx,Pl3StringIDEx)]\
%{LocalConstArray_Inst}%f_add_inheritable(%{Pl3StringIDEx}U)\
%{LocalConstArray_Inst}%f_add_realized(%GU)\
%{LocalConstArray_Inst}%f_set_up(ifdef,%S{ifdef})\
%{LocalConstArray_Inst}%f_set_up(ifndef,%S{ifndef})\
%{LocalConstArray_Inst}%f_set_visibility_type(%SV)\
%{LocalConstArray_Inst}%f_set_documentation(Карта преобразования локализованных строк %SN)\
%{LocalConstArray_Inst}%f_set_up(Need mapping support,%S{Need mapping support})\
%{LocalConstArray_Inst}%f_set_up(Need map interface,%S{Need map interface})\

%{LocalConst_Inst}<{}{}{%C}\
%{LocalConstArray_Inst}%f_add_attribute(%CU_Arr,,%f_N(%C) :\
%{Pl3StringIDEx}U = %{LocalConst_Inst}N::%CN,Attr_Inst)\
>\

]

//#UC END# *4B2A19E3038Bfor4B386A11030E*

понедельник, 15 апреля 2013 г.

Тестирование глазами молодого специалиста

Написал мой коллега:


Тестирование глазами молодого специалиста.

К процессу тестирования, зачастую, относятся довольно пренебрежительно, не видя необходимости прибегать к разработке отдельных программных средств нахождения ошибок. Максимум, что может своевременно найти ошибку – это проверка самим разработчиком измененного кода, ревью кода других разработчиков или специальный человек (или коллектив), который контролирует работоспособность конечного продукта. Так мне казалось, когда закончив ВУЗ, поступило предложение заняться тестированием. Казалось бы, что может быть проще, чем проверить работоспособность программы, когда в твоем распоряжении находится актуальное ТЗ, архив с уже созданными и, в большинстве своем, исправленными ошибками, огромное количество обсуждений, в которых можно найти ответ практически на любой вопрос. Но ожидания оказались не совсем верными. И дело даже не в не умении найти нужный материал, а в разнице между текущим поведением программы и неясностями (двояким толкованием, пробелами) в текущих требованиях. И самый надежны способ определить единственно верное поведение – обратиться к людям, это поведение реализовывающим. К разработчикам. Но, естественно, с течением времени, с ростом количества реализованных и назначенных задач знания об определенных объектах и методах их реализации подзабываются. Это человеческое естество и с ним ничего не сделать. И на первый план выходят выходят автоматические тесты – минипрограммы, в которых четко зафиксировано ТЕКУЩЕЕ поведении системы, особенности ее работы. И чем больше этих программ – тем лучше, т.к. они будут охватывать всю систему (в идеале). Любое отклонение от нормы будет зафиксировано и человек вынесет решение – новая ли это ошибка или требования изменились и нужно изменить тест (эталон). Эту систему невозможно обмануть, новую ошибку нельзя пропустить (при наличии работоспособного теста), как это мог бы сделать, например, не вовремя отвлекшийся человек, ищущий ошибку. Но сильно забегаю вперед, перенесемся в самое начало пути – к тестированию системы вручную.
Моя работа тестировщиком началась со знакомства с системой. И сразу же появились первые вопросы – “Почему это работает так?”, ”Верно ли это?”. Вообще говоря, именно первые недели наиболее благоприятны для поиска ошибок, т.к. новый человек может заметить то, что большинство уже и за ошибку не считают. Новый сотрудник обращает внимание на каждый клик, каждое новое открывающееся окно, к любому событию он относится с подозрением - “А верно ли это?”. Далее, ответ на спорное поведение ищется в описании прецедентов в требованиях. Конечно, всегда можно спросить у знающих людей, но самостоятельный поиск информации ведет к гораздо более глубокому пониманию системы. Со временем, эти знания накапливаются, и новую ошибку уже можно найти не только в результате выполнения тесткейсов, но и проанализировав текущие изменения в системе. Приходит понимание возможных причин появления ошибок. С другой стороны, ежедневный поиск ошибок по четко зафиксированным алгоритмам интересен и разнообразен лишь первые 2-3 прогона всех заданий. Далее, внимание притупляется, тесткейсы уже вызывают меньше вопросов, становится очень трудно проверять задания не механически. Выходом стало тестирование другого проекта. И пусть задания похожи, но нужно вновь разбираться, активно работать с ТЗ, учиться новому. Далее следует возврат к первому проекту, затем опять на новый. Только такой подход, на мой взгляд, позволяет эффективно находить всё новые и новые ошибки. Но рано или поздно, сам процесс такого тестирования начинает казаться неэффективным. И я стал присматриваться к работе с автоматическими тестами, благо все условия были для этого созданы. Но жизнь вносит свои правки, и не успев даже поверхностно познакомится с автоматизированным тестированием, меня выбрали основным человеком, который этими тестами и занимается.
Переход от ручного тестирования к автоматизированному прошел на удивление гладко. Конечно, большая заслуга в этом людей, которые мне помогали и в простоте языка FORTH. Он идеально подошел для тестирования системы. Создавая простейший по реализации код, самостоятельно заводя новые функции и переменные, используя в качестве имен терминологию, принятую в системе, можно с легкостью проверить практически всё.

: Кликаем_на_кнопку_ДАЛЕЕ
 : Действия
  “Переместить курсор мыши на кнопку ДАЛЕЕ”
  “Кликнуть левой кнопкой мыши”
  “Определить, был ли переход по Далее”
 ; \\ Действия
 “Запомнить позицию курсора мыши и выполнить {(@ Действия)}”
; \\ Кликаем_на_кнопку_ДАЛЕЕ

Это кажется очень простым. Но это работает и позволяет держать руку на пульсе системы, практически мгновенно определяя появление неполадок.
Поначалу, небольшие сложности вызывала обратная польская запись кода, а также обилие форм, контролов и классов. Но это объяснимо: последний раз самостоятельно программировать пришлось в далеком 2005 году в колледже (если не ошибаюсь, последней работой стала программа “Определение уровня безопасности помещения” на Delphi). Программированием это назвать сложно – все в рамках учебных курсов. Но тем не менее, основы, заложенные тогда, помогают разбираться и теперь с тестами. Когда приходит понимание, как найти контрол на форме, как передать ему фокус – настроение сразу поднимается. Это увлекательно, здесь нет однообразия: нужно успевать фиксировать результаты прогона тестов, писать новые, править старые, распределять тесты по пресетам, разбираться в старых ошибках, чужом коде и думать (последнее самое ценное). Поэтому, мое отношение к автотестам – очень позитивное и положительное. Да, можно проверить уже исправленные ошибки вручную, но здесь главную роль играет время – его уйдет неимоверно много на повторение старой ошибки, по сравнению со временем прохождения теста.
Помимо всего прочего, довольно интересно наблюдать за работой автоматических тестов со стороны – кажется удивительной “самостоятельная жизнь” системы, часто вижу заинтересованные взгляды коллег.
В чем же главные преимущества автоматизированного тестирования от ручного? В быстроте проверки, в точности полученных результатов, в однозначном полученном результате, в том, что для эффективного нахождения ошибок достаточно всего одного человека. К минусам можно отнести огромное количество тестов (десятки тысяч), которыми нужно покрыть все прецеденты, сложность обработки результатов (для этого требуются специальные знания).
Но плюсов, безусловно, больше, ведь главное – не пропустить ошибку. А если есть тест, который проверяет исправленную ошибку, то в случае ее возврата, она будет найдена.
На мой взгляд, в перспективе – мало покрыть все ошибки тестами, т.к. они охватывают не всю систему. Всю систему охватывает ТЗ – и нужно каждое его предложение покрыть тестом. Или двумя, или тремя. Чтобы они полностью и однозначно следили за текущим поведением системы, и в случае отклонения от ТЗ – сразу сообщали об этом. Они должны быть изолированы от тестов, работающих с ошибками (запускаться отдельно). В этом случае, напрямую идет проверка основного описанного функционала системы. И чем подробнее будет ТЗ, тем тщательнее будет проходить проверка системы.

Прим.: Мнение автора может не совпадать с мнением коллег.

Трофимов Андрей

DSL-ный хардкор


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

Приведу лишь практические изыскания на тему.

Ниже приведён файл, который разбирает один входной поток и строит - другой. (Полный текст тут - https://www.box.com/s/p7dzynlw7d9klk2gwez1)

Поскольку он "типа" самодокументируемый, то пока дальнейшие коммментарии "от себя" - опускаю.
----------------------------

USES
 'W:\shared\models\NewSchool\Templates\MDATuning.tpl.script'
; 

: "Ничего не делаем"
; // "Ничего не делаем"

: log
 .
 //DROP
; // log

WORDWORKER "начинается с" W-STRING IN aStr
 WordToWork DO aStr WString:Starts
; // "начинается с"

// InitedVarProducer VAR-I

: "Обработать шаблоны MDA"

 ARRAY "Обработанные файлы"

 FORWARD "Обработать файл"

 : "Обработать файл" STRING IN "Имя входного файла"

  "Обработанные файлы" "Имя входного файла" array:HasText ? (
   [[ 'duplicated: ' "Имя входного файла" ' skipped' ]] strings:Cat log
   EXIT
  ) // "Обработанные файлы" "Имя входного файла" array:HasString ?
  
  "Имя входного файла" >>>[] "Обработанные файлы"

  STRING VAR "Путь к входному файлу"
  "Имя входного файла" sysutils:ExtractFilePath =: "Путь к входному файлу"
  "Путь к входному файлу" log

  VAR "Имя выходного файла" 
  "Имя входного файла" '.script' Cat =: "Имя выходного файла"
  
  CONST "Пробел" ' '
  CONST "Пустая строка" ''
  CONST "Кавычка" ''''
  CONST "Открывающаяся скобка" '('
  CONST "Закрывающаяся скобка" ')'
  CONST "Запятая" ','
  CONST "Знак процента" '%'
  CONST "Спецсимволы" '\%[]{}<>#()'
  CONST "Цифры" '1234567890'
  CONST "Разделитель частей стереотипа" '::'
  
  STRING VAR "Имя диска"
  "Имя выходного файла" sysutils:ExtractFileDrive =: "Имя диска"
  
  '\' string:SplitTo! "Имя выходного файла"
  "Имя диска" ?== 'Имя файла не содержит указание диска' ASSERTS
  
  [[ "Имя диска" '\NewSchool\' "Имя выходного файла" ]] strings:Cat =: "Имя выходного файла"
  
  "Имя выходного файла" log
  
  STRING VAR "Путь к выходному файлу"
  "Имя выходного файла" sysutils:ExtractFilePath =: "Путь к выходному файлу"
  "Путь к выходному файлу" log
  
  "Путь к выходному файлу" sysutils:ForceDirectories 'Не удалось создать директории' ASSERTS
  
  //script:FileName sysutils:ExtractFileName "Пустая строка"  "Пробел" Cat "Имя выходного файла" sysutils:ExtractFileName Cat =: "Имя выходного файла"
  
  FILE VAR "Входной файл"
  "Имя входного файла" file:OpenRead =: "Входной файл"
  TRY
   FILE VAR "Выходной файл"
   "Имя выходного файла" file:OpenWrite =: "Выходной файл"
   TRY
   
    W-STRING VAR "Текущая строка входного файла"
    
    : "Строка пустая"
     "Текущая строка входного файла" WString:IsNil
    ; // "Строка пустая"
    
    WORDWORKER "Строка начинается с"
     VAR l_Begin
     WordToWork DO =: l_Begin
     "Текущая строка входного файла" "начинается с" l_Begin
    ; // "Строка начинается с"
   
    WORDWORKER "Строка равна"
     VAR l_EQ
     WordToWork DO =: l_EQ
     "Текущая строка входного файла" l_EQ ?==
    ; // "Строка начинается с"
    
     BOOLEAN VAR "Накапливаемая строка пустая"
     BOOLEAN VAR "Была кавычка"
      
     : "Вывести строку"
      "Выходной файл" file:WriteStr
     ; // "Вывести строку"
    
     : "Вывести кавычку"
      "Кавычка" "Вывести строку"
     ; // "Вывести кавычку"
     
     : "Закрыть кавычку, если была"
      "Была кавычка" ? (
       "Вывести кавычку"
       false =: "Была кавычка"
      ) // "Была кавычка" ?
     ; // "Закрыть кавычку, если была"
      
     : "Вывести пробел"
      "Пробел" "Вывести строку"
     ; // "Вывести пробел"
     
     : "Сбросить накопленную строку без перевода"
      "Накапливаемая строка пустая" ! ? (
      
       "Закрыть кавычку, если была"
        
       "Вывести пробел" 
       true =: "Накапливаемая строка пустая"
       false =: "Была кавычка"
      ) // "Накапливаемая строка" string:Len !=0 ?
     ; // "Сбросить накопленную строку без перевода"
     
     BOOLEAN VAR "Был отступ"
     
     : "Перевести строку"
      "Закрыть кавычку, если была"
      "Пустая строка" "Выходной файл" file:WriteLn
      false =: "Был отступ"
      "Была кавычка" ! 'Похоже не закрыли кавычку' ASSERTS 
      //false =: "Была кавычка"
     ; // "Перевести строку"
     
     : "Сбросить накопленную строку"
      "Накапливаемая строка пустая" ! ? (
      
       "Закрыть кавычку, если была"
        
       "Перевести строку"
       true =: "Накапливаемая строка пустая"
      ) // "Накапливаемая строка" string:Len !=0 ?
     ; // "Сбросить накопленную строку"
     
     : "Сбросить накопленную строку, чтобы кавычка случайно не переехала на другую строку"
      "Сбросить накопленную строку"
     ; // "Сбросить накопленную строку, чтобы кавычка случайно не переехала на другую строку"
      
     : "Вывести строку как есть"
      "Текущая строка входного файла" "Выходной файл" file:WriteWStrLn
     ; // "Вывести строку как есть"
     
     : "Добавить к строке / и вывести"
      "Сбросить накопленную строку, чтобы кавычка случайно не переехала на другую строку"
      '/' "Вывести строку"
      "Вывести строку как есть"
     ; // "Добавить к строке / и вывести"
     
     : "Вывести строку как комментарий. Чтобы в конечном файле было с чем сравнивать"
      "Сбросить накопленную строку, чтобы кавычка случайно не переехала на другую строку"
      "Был отступ"  ? "Перевести строку"
      '//' "Вывести строку"
      "Вывести строку как есть"
      false =: "Был отступ"
     ; // "Вывести строку как комментарий. Чтобы в конечном файле было с чем сравнивать"
     
     BOOLEAN VAR "Был открыт стереотип" 
      false =: "Был открыт стереотип"
     STRING VAR "Имя стереотипа"
      "Пустая строка" =: "Имя стереотипа" 
     STRING VAR "Имя класса стереотипа"
      "Пустая строка" =: "Имя класса стереотипа" 
     STRING VAR "Имя под-стереотипа"
      "Пустая строка" =: "Имя под-стереотипа" 
     STRING VAR "Имя класса под-стереотипа"
      "Пустая строка" =: "Имя класса под-стереотипа" 
     STRING VAR "Имя под-под-стереотипа"
      "Пустая строка" =: "Имя под-под-стереотипа" 
     STRING VAR "Имя класса под-под-стереотипа"
      "Пустая строка" =: "Имя класса под-под-стереотипа"
     
     BOOLEAN VAR "Была открыта функция"
      false =: "Была открыта функция" 
     STRING VAR "Имя функции"
      "Пустая строка" =: "Имя функции"
     BOOLEAN VAR "Был открыт трансформатор"
      false =: "Был открыт трансформатор" 
     STRING VAR "Имя трансформатора"
      "Пустая строка" =: "Имя трансформатора"
     
     BOOLEAN VAR "Был открыт генератор"
      false =: "Был открыт генератор"
     STRING VAR "Имя генератора"
      "Пустая строка" =: "Имя генератора"
     
     CONST Разделители ' '
     CONST "Двойная кавычка" '"'
     CONST "Параметр Self" ' OBJECT IN %S'
     CONST "Скобка закрытия функции" '; // '
     CONST "Открываем строку стереотипа" '<<'
     CONST "Закрываем строку стереотипа" '>>'
     CONST "Табуляция" #9
     
     : "Вывести строку с переводом строки"
      "Вывести строку"
      "Перевести строку"
     ; // "Вывести строку с переводом строки"
     
     : "Вывести исходную строку"
      "Текущая строка входного файла" "Выходной файл" file:WriteWStr
     ; // "Вывести исходную строку"
     
     : "Записать имя стереотипа"
      "Имя под-стереотипа" string:Len !=0 IF
       ':: ' "Вывести строку"
       "Имя стереотипа" "Вывести строку"
       "Пробел" "Вывести строку"
       "Имя под-стереотипа" "Вывести строку"
       
       "Имя под-под-стереотипа" string:Len !=0 IF
        "Пробел" "Вывести строку"
        "Имя под-под-стереотипа" "Вывести строку"
       ENDIF // "Имя под-под-стереотипа" string:Len !=0 IF
       
       ' ;' "Вывести строку"
       
      ELSE
       "Имя стереотипа" "Вывести строку"
      ENDIF //  "Имя под-стереотипа" string:Len !=0
      "Перевести строку"
     ; // "Записать имя стереотипа"
     
     : "Записать имя функции"
      "Имя функции" "Вывести строку"
     ; // "Записать имя функции"
     
     : "Записать имя трансформатора"
      "Имя трансформатора" "Вывести строку"
     ; // "Записать имя трансформатора"
     
     : "Записать имя генератора"
      "Имя генератора" "Вывести строку"
     ; // "Записать имя генератора"
     
     FORWARD "Закрыть вложенные опеределения"
     
     : "Закрыть стереотип"
      "Был открыт стереотип" ?
       ( 
        "Закрыть вложенные опеределения"
        'end. // ' "Вывести строку" 
        "Записать имя стереотипа"
        "Перевести строку"
       ) // "Был открыт стереотип" ?
      false =: "Был открыт стереотип"
     ; // "Закрыть стереотип"
     
     INTEGER VAR "Отступ"
     INTEGER VAR "Количество открытых IF"
     INTEGER VAR "Количество открытых циклов"
     INTEGER VAR "Количество открытых скобок параметров функции"
      
     : "Закрыть все скобки"
      0 =: "Отступ"
      0 =: "Количество открытых IF"
      0 =: "Количество открытых циклов"
      0 =: "Количество открытых скобок параметров функции"
      "Закрыть вложенные опеределения"
      "Закрыть стереотип"
     ; // "Закрыть все скобки"
     
     : "Закавычить имя стереотипа"
      Разделители "Имя стереотипа" string:HasAnyOf ? (
       [[ "Двойная кавычка" "Имя стереотипа" "Двойная кавычка" ]]
        strings:Cat =: "Имя стереотипа"
      )
     
      "Имя класса стереотипа" 'MDAGenerator' ?!= ? (
       [[ "Открываем строку стереотипа" "Имя стереотипа" "Закрываем строку стереотипа" ]] 
        strings:Cat =: "Имя стереотипа"
      )
     ; // "Закавычить имя стереотипа"
     
     : "Закавычить имя под-стереотипа"
      "Имя под-стереотипа" string:Len !=0 ? (
       Разделители "Имя под-стереотипа" string:HasAnyOf ? (
        [[ "Двойная кавычка" "Имя под-стереотипа" "Двойная кавычка" ]]
         strings:Cat =: "Имя под-стереотипа"
       ) // Разделители "Имя под-стереотипа" string:HasAnyOf ?
      
       // "Имя класса стереотипа" 'MDAGenerator' ?!= 
       true ? (
        [[ "Открываем строку стереотипа" "Имя под-стереотипа" "Закрываем строку стереотипа" ]] 
         strings:Cat =: "Имя под-стереотипа"
       ) // true ?
      ) // "Имя под-стереотипа" string:Len !=0 ?
     ; // "Закавычить имя под-стереотипа"
     
     : "Открыть стереотип"
      "Был открыт стереотип" ! ? (
        'implementation @ ' "Вывести строку"
        
        "Записать имя стереотипа"
        true =: "Был открыт стереотип"
      ) // "Был открыт стереотип" !
     ; // "Открыть стереотип"
     
     : "Разобрать заголовок стереотипа"
     
      "Закрыть стереотип"
      
      2 WString:+! "Текущая строка входного файла"
      "Текущая строка входного файла" WString:ToString =: "Имя стереотипа"
      
      "Имя стереотипа" "Разделитель частей стереотипа" string:Split
      
      =: "Имя класса стереотипа"
      =: "Имя стереотипа"
      
      "Имя класса стереотипа" "Разделитель частей стереотипа" string:Split
      =: "Имя под-стереотипа"
      =: "Имя класса стереотипа"
      
      "Имя под-стереотипа" "Разделитель частей стереотипа" string:Split
      =: "Имя класса под-стереотипа"
      =: "Имя под-стереотипа"
      
      "Имя класса под-стереотипа" "Разделитель частей стереотипа" string:Split
      =: "Имя под-под-стереотипа"
      =: "Имя класса под-стереотипа"
      
      "Закавычить имя стереотипа"
      "Закавычить имя под-стереотипа"
      
      "Открыть стереотип"
      
     ; // "Разобрать заголовок стереотипа"
     
     : "Закрыть функцию"
      "Была открыта функция" ? ( "Скобка закрытия функции" "Вывести строку" "Записать имя функции" 
      "Перевести строку"
      "Перевести строку"
      )
      false =: "Была открыта функция"
     ; // "Закрыть функцию"
     
     : "Закрыть трансформатор"
      "Был открыт трансформатор" ? ( "Скобка закрытия функции" "Вывести строку" "Записать имя трансформатора" 
      "Перевести строку"
      "Перевести строку"
      )
      false =: "Был открыт трансформатор"
     ; // "Закрыть трансформатор"
     
     : "Закрыть генератор"
      "Был открыт генератор" ? ( "Скобка закрытия функции" "Вывести строку" "Записать имя генератора" 
      "Перевести строку"
      "Перевести строку"
      )
      false =: "Был открыт генератор"
     ; // "Закрыть генератор"
     
//     : "Было открыто хоть одно вложенное определение"
//      "Была открыта функция" %|| 
//      "Был открыт трансформатор" %|| 
//      "Был открыт генератор"
//     ; // "Было открыто хоть одно вложенное определение"
     
     : "Закрыть вложенные опеределения"
      "Закрыть генератор"
      "Закрыть функцию"
      "Закрыть трансформатор"
      false =: "Был отступ"
     ; // "Закрыть вложенные опеределения"
     
     : "Записать параметры функции" BOOLEAN IN aGlobal
      "Параметр Self" "Вывести строку"
     ; // "Записать параметры функции"
     
     : "Записать параметры трансформатора" BOOLEAN IN aGlobal
      "Параметр Self" "Вывести строку"
     ; // "Записать параметры трансформатора"
     
     : "Разобрать заголовок функции" BOOLEAN IN aGlobal
      "Закрыть вложенные опеределения"
      aGlobal IF
       "Закрыть стереотип"
      ELSE
       "Открыть стереотип"
      ENDIF 
      
      aGlobal IF
       2 WString:+! "Текущая строка входного файла"
       // - отрезаем f
      ELSE 
       3 WString:+! "Текущая строка входного файла"
       // - отрезаем %f
      ENDIF
       
      "Строка начинается с" '_' ? WString:++! "Текущая строка входного файла"
      // - отрезаем поддчёркивание
      "Текущая строка входного файла" WString:ToString =: "Имя функции"
      // - получаем имя текущей функции
      
      ': ' "Вывести строку"
      "Записать имя функции"
      
      aGlobal "Записать параметры функции"
      
      "Перевести строку"
      
      true =: "Была открыта функция"
     ; // "Разобрать заголовок функции"
     
     : "Разобрать заголовок трансформатора" IN aGlobal
      "Закрыть вложенные опеределения"
      aGlobal IF
       "Закрыть стереотип"
      ELSE
       "Открыть стереотип"
      ENDIF 
      
      aGlobal IF
       2 WString:+! "Текущая строка входного файла"
       // - отрезаем t
      ELSE 
       3 WString:+! "Текущая строка входного файла"
       // - отрезаем %t
      ENDIF
       
      "Строка начинается с" '_' ? WString:++! "Текущая строка входного файла"
      // - отрезаем поддчёркивание
      "Текущая строка входного файла" WString:ToString =: "Имя трансформатора"
      // - получаем имя текущего трансформатора
      
      '<<transformator>> ' "Вывести строку"
      "Записать имя трансформатора"
      
      aGlobal "Записать параметры трансформатора"
      
      "Перевести строку"
      
      true =: "Был открыт трансформатор"
     ; // "Разобрать заголовок трансформатора"
     
     : "Разобрать заголовок функции стереотипа"
      false "Разобрать заголовок функции"
     ; // "Разобрать заголовок функции стереотипа"
     
     : "Разобрать заголовок глобальной функции"
      true "Разобрать заголовок функции"
     ; // "Разобрать заголовок глобальной функции"
     
     : "Разобрать заголовок трансформатора стереотипа"
      false "Разобрать заголовок трансформатора"
     ; // "Разобрать заголовок трансформатора стереотипа"
     
     : "Разобрать заголовок глобального трансформатора"
      true "Разобрать заголовок трансформатора"
     ; // "Разобрать заголовок глобального трансформатора"
     
     : "Записать параметры генератора"
      "Параметр Self" "Вывести строку"
     ; // "Записать параметры генератора"
     
     : "Разобрать заголовок генератора"
      "Закрыть вложенные опеределения"
      
      2 WString:+! "Текущая строка входного файла"
      "Текущая строка входного файла" WString:ToString =: "Имя генератора"
      
      "Открыть стереотип"
      
      '<<generator>> ' "Вывести строку"
      "Записать имя генератора"
      
      "Записать параметры генератора"
      
      "Перевести строку"
      
      true =: "Был открыт генератор"
     ; // "Разобрать заголовок генератора"
     
     BOOLEAN FUNCTION "Обрабатываем незначащие строки" BOOLEAN IN "Надо переводить строку"
     
      : "Вывести комментарий, котрый был в исходном файле"
       "Сбросить накопленную строку, чтобы кавычка случайно не переехала на другую строку"
       "Надо переводить строку" IF
        "Перевести строку"
       ELSE
        "Был отступ"  ? "Перевести строку"
       ENDIF // "Надо переводить строку"
       "Вывести строку как есть"
      ; // "Вывести комментарий, котрый был в исходном файле"
     
      : "Сигнализировать о неуспехе"
       false =: Result
      ; // "Сигнализировать о неуспехе"
      
      true =: Result
      // - будем оптимистами
      RULES
       "Строка пустая"
        ( 
         "Закрыть кавычку, если была"
         "Надо переводить строку" ? "Перевести строку"
         "Вывести строку как есть"
        ) // "Строка пустая"
       "Строка начинается с" '//#UC END# *'
        (
         "Вывести комментарий, котрый был в исходном файле"
         "Закрыть вложенные опеределения"
          // - т.к. наверное функция, трансформатор или генератор - закончились
        ) 
       "Строка начинается с" '//'
        "Вывести комментарий, котрый был в исходном файле"
       "Строка начинается с" '/'
        (
         "Надо переводить строку" ? "Перевести строку"
         "Добавить к строке / и вывести"
        ) // "Строка начинается с" '/'
       DEFAULT
        "Сигнализировать о неуспехе"
      ; // RULES
     ; // "Обрабатываем незначащие строки"
     
     : "Обрабатываем незначащие строки без перевода строки перед ними"
      false "Обрабатываем незначащие строки"
     ; // "Обрабатываем незначащие строки без перевода строки перед ними"
     
   
Полный текст тут - https://www.box.com/s/p7dzynlw7d9klk2gwez1

Я не предлагаю взять это всё и использовать в "готовом виде," тем более, что в "моём" готовом виде - это вряд ли востребовано. Я пытаюсь поделиться лишь одним из возможных направлений подходов к самодокументируемому коду и использованию не языков высокого уровня общего назначения, а использования языков ориентированных на предметные области решаемых задач (DSL).

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

Похожая тема - http://18delphi.blogspot.com/2013/04/forth-vs-lisp.html