Безболісне розвиток програми
Як рідко практикуючому програмістові випадають дні, коли він починає створювати нову програму "з чистого екрану"! Яке блаженство відчуває він в такий момент, знаючи напевно, що ні одна з набраних ним зараз рядків не принесе шкоди нічому з того, що він сам або його колеги написали коли-небудь раніше. І навпаки, яка туга долає розробника, коли він досягає певного порогу складності своєї програми і далі не може вже без здригання чути про появу нових вимог до неї, оскільки у нього просто не піднімається рука для внесення чергових доповнень. Зазвичай розробник швидко знаходить, що і де треба буде міняти, але тут згадує, як недавно виправлення пари символів в третьорядною, як тоді здавалося, рядку вихідного тексту на тиждень вивело програму з ладу, зірвавши терміни відповідального виробничого розрахунку. Однак в сучасному світі виживають лише енергійно розвиваються програми, і згнітивши серце розробник підходить до комп'ютера ...
Такого роду драматичні колізії вельми неприємні, повсюдні і загальновідомі. Будь-яка спроба поліпшити стан справ у цій сфері була б з вдячністю прийнята спільнотою програмістів. Проте складається враження, що далеко не кожен з творців програміста інструментарію хоч зрідка задає собі пряме запитання: "Що я зробив для полегшення розвитку програми?"
У поширених інструментальних середовищах розвиток програми зазвичай немислимо без редагування. Для користувача таке середовище постійні хворобливі модифікації налагоджених текстів настільки повсякденні, що навіть не викликають почуття протесту. Більш того, нерідко він помилково вважає, що доповнити програму новою частиною без редагування написаних раніше або взагалі неможливо, або для цього потрібно зовсім екзотичне інструментальне оточення, щось з розряду штучного інтелекту. Насправді ж існують досить прості механізми безболісного розвитку програми, здатні органічно увійти в традиційну середу розробки. Потрібно лише трохи вдосконалити цю середу, включивши в неї дві-три нескладні конструкції. В результаті вдається поставити справу таким чином, що знову написані частини програми будуть тільки доповнювати безліч існуючих вихідних текстів, не вимагаючи їх редагування.
Саме редагування налагоджених раніше частин програми є джерелом найбільш згубних помилок, внесених до програми при її розвитку. Тому виключення такого редагування радикально підвищує надійність процесу розвитку, забезпечує безумовне збереження працездатності попередньої версії програми - іншими словами, втілює сподівання кожного розробника.
Borland C ++
Розглянемо характерний приклад. Нехай є налагоджена програма, написана на мові програмування С ++ для середовища MS Windows і використовує апарат меню. Крім того, нехай в розпорядженні розробника є типова сучасна інструментальне середовище - транслятор Borland C ++ [1]. Потрібно доповнити одне з меню, що складається, скажімо, з шести пунктів, новим, сьомим пунктом. Нехай для визначеності додається пункт носить назву "Про програму".
Що доведеться робити для внесення цього доповнення? Перш за все треба виявити всі точки програми, що мають відношення до пунктів нашого меню. В результаті ретельного вивчення вихідного тексту будуть виявлені принаймні п'ять (!) Таких точок. У кожній з них вже перебуває за шість однорідних компонентів, що реалізують окремі аспекти шести вже наявних пунктів меню. Виконує зміни розробнику належить відповідно відредагувати вихідний текст в п'яти місцях, додаючи кожен раз до шести наявних однорідним компонентам новий, сьомий (рис.1).
Мал. 1. Додавання пункту меню в середовищі Borland C ++
Спочатку спробуємо розібратися, через що зміни не локалізовані в одній точці. На перший погляд може здатися, що виявлена багатозв'язна реалізації пункту меню відображає всього лише недбалість, допущену при проектуванні структури програми. Адже відразу кидається в очі, що додавати і видаляти одинзв'язні компоненти програми простіше, ніж багатозв'язкові, а це важливо, особливо якщо згадати, що свобода маніпулювання складом програми нерідко є необхідною передумовою її багаторазового використання. Якщо зміни вносяться не в одну, а в п'ять точок вихідного тексту, то, грубо кажучи, уп'ятеро збільшується ймовірність здійснення помилок редагування, які можуть торкнутися сусідні, налагоджені раніше частини програми і тим самим поставити під загрозу її працездатність. Нарешті, не відбулася однозв'язна реалізація виглядала б наочніше і мала б ще цілу низку незаперечних переваг.
Однак при більш уважному аналізі з'ясовується, що розчленовування реалізації пункту меню на п'ять розкиданих по тексту програми компонентів вироблено цілком свідомо. Більш того, таке розчленовування диктується цілком розумними організаційними рішеннями, прийнятими в середовищі Borland C ++.
Перш за все, це середовище пропонує оформляти у вигляді окремих самостійних частин ресурси програми - різноманітні зовнішні прояви її функціонування: гарячі клавіші; графічні елементи, що задають частина екранної картинки, піктограму або форму курсора; панелі діалогів, які вирішують окремі підзадачі; шрифти; виводяться тексти та ін. Опис ресурсу виконується на спеціальній мові, з якого свідомо вигнано все, що пов'язано з алгоритмами, що реалізують змістовну сторону цього ресурсу. Зрозуміло, в мові ресурсів передбачена конструкція, що задає пункт меню:
MENUITEM текст, повідомлення де текст - назва пункту, яке виводиться на екран для користувача створюваної програми, а вказане повідомлення надсилається виконуваної програмою, коли користувач звертається до даного пункту. (Реальний синтаксис MENUITEM трохи складніше, але зараз це не має значення.) Додаючи в меню новий пункт "Про програму" і прив'язуючи до нього повідомлення з номером CM_ABOUT, розробник зобов'язаний відредагувати опис ресурсів, доповнивши його ще однієї, сьомий конструкцією MENUITEM:
MENUITEM "Про програму", CM_ABOUT Переваги оформлення ресурсу як самостійного об'єкта досить очевидні. Головне з них укладено в тому, що значно полегшується процедура зміни елементів зовнішнього оформлення: тепер для цього не потрібно вторгатися редактором в вимагає особливо делікатного поводження алгоритмічну частину, а досить поміняти опис ресурсу. Крім того, мова ресурсів - це ефективний засіб швидкого макетування: він дозволяє, зокрема, не написавши жодного рядка алгоритму, побачити на екрані основні образотворчі рішення проектованої програми. Вводити і модифікувати проектований ресурс можна як у формі тексту на мові ресурсів, так і в стилі візуального програмування, безпосередньо отримуючи на екрані його графічне втілення і інтерактивно маніпулюючи його розташуванням, розмірами, способом оформлення та іншими зовнішніми атрибутами.
Отже, однозв'язної реалізації пункту меню домогтися не вдається: для полегшення подальших змін елементів зовнішнього оформлення один з компонентів цієї реалізації повинен бути записаний нема на Сі ++, а на мові ресурсів, і розміщується цей компонент (# 1) в окремому, самостійному модулі. Але через що ж розривається на віддалені один від одного шматки решта реалізації? Виявляється, і тут причини досить переконливі.
Повідомленням, яке передається програмі при зверненні до нового пункту меню, в реченні MENUITEM було присвоєно ідентифікатор CM_ABOUT. Цьому ідентифікатору треба зіставити деяке ціле число, оскільки в Windows всі повідомлення пронумеровані:
#define CM_ABOUT 213
Пропозиція #define розміщується там, де вже записані шість подібних пропозицій для попередніх пунктів, - в заголовки, який використовують всі пов'язані з меню модулі. Таких модулів принаймні два: опис ресурсів і алгоритмічна частина на Сі ++ - так що без загального заголовки ніяк не обійтися. Тим самим від реалізації пункту відколюється ще один окремий фрагмент (# 2).
Далі, в Borland C ++ потрібно заповнити певного виду таблицю, де повинні бути зібрані всі повідомлення, оброблювані містить меню вікном. Тут нашому повідомленню CM_ABOUT зіставляється ім'я обробної його функції CmAbout:
EV_COMMAND (CM_ABOUT, CmAbout)
Зведення разом всіх оброблюваних повідомлень безсумнівно корисно: воно не тільки вирішує ряд технологічних проблем, а й служить поліпшенню наочності програми. І знову від реалізації пункту відколовся фрагмент (# 3), що доповнив компанію з шести йому подібних.
Але і це ще не все. Функція CmAbout повинна бути оголошена (слідом за шістьма аналогічними оголошеннями) як метод класу, обслуговуючого містить меню вікно (# 4). І нарешті, зрозуміло, буде потрібно власне опис функції CmAbout, яке несе основну алгоритмічну навантаження (# 5).
Разом є принаймні п'ять розкиданих по тексту програми фрагментів реалізації пункту меню, відокремлений розташування кожного з яких має досить вагоме обгрунтування. У традиційній операційному середовищі багатозв'язна реалізації означає, що для додавання в меню нового пункту розробник буде змушений відредагувати наявний налагоджений вихідний текст програми як мінімум в п'яти місцях, кожен раз піддаючи серйозної небезпеки околиця вироблених змін. З такого роду технологічними кошмарами мільйони програмістів стикаються на кожному кроці, але ж для позбавлення від них, як зараз буде показано, не потрібно нічого надприродного.
# Set-конструкція
Схематично вирішення проблеми виглядає наступним чином. Кожен пункт меню заноситься в базу даних проекту у формі запису c полями: Text - текст пункту меню, MessageName - ім'я повідомлення, Message Number - номер повідомлення, FunctionName - ім'я обробної функції і т.д. Таким чином формується сукупність однорідних записів, які позначаються як належать набору OurMenu - наше меню.
Далі в кожній з п'яти розкиданих по тексту програми точок, що належать до реалізації пунктів меню, записується спеціальна конструкція #Set. Вона задає шаблон, на основі якого генерується група однорідних елементів тексту, які з зберігаються в базі даних пунктів. Наприклад, на місці елементів таблиці повідомлень буде записано:
#Set OurMenu EV_COMMAND (#MessageName, #FunctionName), #End
Ці три рядки після обробки препроцесором перетворюються в звичні сім рядків EV_COMMAND, так що потім на вході компілятора виявляється точно такий же текст, як і раніше.
Синтаксис і семантика конструкції #Set досить очевидні. Перший рядок - заголовок - вказує однорідний набір записів (OurMenu), які повинні вилучатися з бази даних проекту. За нею йде тіло #Set, що представляє собою текст, серед якого розташовуються посилання на поля записи, відмічені символом '#'. Завершує конструкцію пропозицію #End.
#Set є циклом періоду компіляції: тіло конструкції повторюється в генерованому тексті стільки раз, скільки на даний момент в базі даних проекту зберігається записів набору, зазначеного в заголовку. На кожному витку циклу з бази даних витягується чергова запис, і її поля підставляються в генерований текст на місце відповідних посилань, що містяться в тілі #Set.
Які переваги несе застосування конструкції #Set? Перш за все звертає на себе увагу скорочення довжини вихідного тексту програми. Скорочення може виявитися досить значним, але, з точки зору полегшення подальшого розвитку, воно не представляє особливого інтересу.
Істотно більше важливий результат - підвищення наочності. У традиційній інструментальній середовищі розробник в подібній ситуації змушений вибирати: або спробувати все прояви пункту меню втілити в одному наочному суцільному шматку тексту, але тоді позбутися перерахованих переваг многосвязной реалізації, або змиритися з багатозв'язна, прирікаючи себе на наступні болісні пошуки всіх точок внесення змін при необхідності додати або видалити деякий пункт. Конструкція #Set з'єднує в собі кращі сторони цих двох рішень: реалізація пункту локалізується в одній записи бази даних проекту, але і всі переваги багатозв'язна залишаються в силі, оскільки остаточний вихідний текст, що утворився після розкриття конструкцій #Set, нічим не відрізняється від старого багатозв'язного.
Біда традиційного тексту програми в його одномірності, яка вступає в протиріччя з двовимірної структурою, утвореною пунктами меню (вертикалі) і розкиданими по програмі окремими компонентами їх реалізації (горизонталі). За часів перших обчислювальних машин текст програми асоціювався з текстом книги, і тому його одномірність представлялася чимось природним або навіть єдино можливим. Але в епоху повсюдного поширення гіпертексту, СУБД та інших подібних багатовимірних засобів одномірність тексту, що відображає двовимірну структуру, безумовно виглядає анахронізмом.
Сучасна операційне середовище просто зобов'язана надати розробнику можливість в повній мірі проявити міститься в програмі двовимірна. Оформивши сукупність реалізацій пунктів меню у вигляді набору однотипних записів в базі даних проекту, розробник має право подивитися на будь-який горизонтальний (сукупність однойменних полів) або вертикальний (повна реалізація пункту) зріз утворилася структури, і можливість ця ніяк не повинна обмежувати ефективність реалізації. Крім того, підтримка пропонованого апарату повинна включати перегляд всіх # Set-конструкцій зазначеного набору як у формі вихідного коду, так і в формі його розширення - результату роботи препроцесора.
Але найбільш цікаві наслідки застосування # Set-конструкції пов'язані з організацією підключення до програми нових частин. Тепер новий пункт додається до меню безболісно, без якого б то не було редагування існуючого налагодженого вихідного тексту програми: досить помістити в базу даних проекту новий запис і оголосити її належить однорідному набору OurMenu. Так само безболісно, без участі редактора відбувається і видалення пункту. В результаті ми позбавляємося від джерела вельми частих помилок, кидав важку тінь неблагополуччя на повсякденний процес розвитку програми.
Згадаймо, що в традиційному середовищі першим кроком роботи по підключенню нового пункту був пошук численних точок програми, в які потрібно внести зміни. Пошук цей неминуче супроводжувався осмисленням призначення кожної з точок-претендентів, оскільки навіть виявлення де-небудь групи з шести однорідних членів, що відносяться до шести наявних пунктам, взагалі кажучи, не означало, що в даній точці повинен з'явитися і представник сьомого пункту: не можна було механічно відсікти припущення про те, що виявлена група могла утворитися в результаті випадкового збігу. Застосування # Set-конструкцій, в яких канонізуються зв'язку реалізації пункту з іншою програмою, знімає з розробника ці турботи. Підключаючи пункт, він може зовсім не заглянути в продумані і розміщені раніше в програмі # Set-конструкції, а лише заповнити поля відповідної структури для бази даних проекту.
Отже, залучення # Set-конструкції забезпечило безболісність підключення до меню нового пункту. Однак у досвідченого читача, ймовірно, давно вже зріє питання: а чи варто було через таку малість нагромаджувати новий, ні на що не схожий елемент інструментального середовища? Інакше кажучи, наскільки часто ситуації, близькі до описаної вище, зустрічаються в реальних програмах? Спробуємо показати, що подібні обставини досить типові, для чого звернемося до іншого популярного продукту фірми Borland - до транслятора з мови Паскаль Delphi [2, 3].
Delphi
Зараз Delphi - одна з найбільш відоміх інструментальніх середовище, Які сповідують ідеологію візуального програмування. Типовий сеанс роботи з Delphi почінається з того, что програміст вібірає з меню и розставляє на спочатку порожній панелі різноманітні необхідні для майбутньої програми прімітіві MS Windows: кнопки, поля вступу, основної меню, лінійкі прокрутки и т.д. Потім, вказано на Деяк прімітів, ВІН может поміняти его атрибути або Записати фрагмент алгоритму, Який реалізує реакцію зазначеним прімітіву на будь-яку подію. Для запису фрагмента алгоритму перед ним відкривається вікно з каркасом відповідної процедури, де курсор послужливо підморгує у вільному поки проміжку між begin і end.
У Delphi завдання доповнення меню новим пунктом вирішується істотно більш технологічні. (Втім, деякі можливості візуального програмування є і в Borland C ++, але вони значно відстають від Delphi, тому проведене розгляд досить-таки правдоподібно.) Програміст, маніпулюючи мишею, в стилі візуального програмування розширює меню в потрібному місці. В результаті на екрані з'являється таблиця атрибутів новоствореного пункту, куди він може записувати потрібні йому значення, наприклад назва пункту ( "Про програму") і т.д. Таблиця ця по структурі частково нагадує запис бази даних проекту, винайдену в попередньому розділі. Потім, як уже згадувалося, програміст вводить в заготовлений Delphi каркас програмний фрагмент, який реалізує дії, які обслуговують обіг користувача до даного пункту.
Намальована ідилічна картинка програмування в Delphi при більш уважному розгляді кілька тьмяніє. Захопленість візуальної стороною привела до того, що якщо раніше програміст міг працювати тільки в послідовності "запрограмував - побачив", то тепер - лише "намалював (побачив) - запрограмував".
Наприклад, нічого не варто, вказавши на будь-якої візуалізується об'єкт, отримати доступ до будь-якого аспекту його реалізації. Більш того, крім початкового тексту алгоритму перед розробником завжди знаходиться таблиця з атрибутами зазначеного об'єкта. Але якщо зробити подорож по тексту програми і дійти таким чином до реалізації іншого об'єкта, то таблиця з атрибутами так і залишиться колишньою, і при поверненні в режим візуалізації панелі новий об'єкт не стане поточним.
У той же час, якщо вже тексту програми відведена така відверто вторинна роль, можна було очікувати, що творці Delphi рішуче візьмуть на себе турботи по підтримці цілісності пари "візуальний образ об'єкта - його програмна реалізація". На жаль, ця цілісність легко може бути порушена при будь-якому невірному редагує русі програміста. Каркас програми, що виготовляється Delphi для введення програмних фрагментів, служить опорою лише в перший момент - потім вводиться в гнізда каркаса текст невиразно зливається з текстом Delphi і програміст з рівним успіхом може видаляти і змінювати як свої писання, так і будь-які конструкції, зведені Delphi для програмного відображення візуалізуються об'єктів. Наслідки видалення або зміни таких конструкцій не завжди передбачувані і в більшості випадків дуже неприємні.
Відсутність будь-якої межі між текстом, що генерується Delphi, і текстом, що вводиться програмістом, до такої міри заважає працювати, що одне з посібників [3] навіть рекомендує користувачеві Delphi набирати службові слова мови програмування Паскаль прописними буквами (BEGIN), щоб відрізняти їх від оточуючих службових слів, що належать каркасу і записаних малими літерами (begin). Що ж завадило авторам Delphi, безстрашно вчинили потужний стрибок у малодосліджених сферу візуального програмування, доповнити Паскаль щодо нескладної конструкцією каркаса? Не виключено, що причина криється в до образливого слабкої теоретичної базі і в відсутності досвіду широкого застосування такого роду конструкцій. Адже якщо бути послідовним, то легалізація каркаса закономірно призведе до впровадження в мову чогось подібного # Set-конструкції з попереднього розділу, а застосована там асоціативна вибірка з бази даних проекту поки що сприймається як ексцентрична витівка, недозволена для розробника повсякденного програміста інструментарію.
Таким чином, зануривши в середу Delphi нашу задачу про підключення пункту меню, ми отримали два цікавих результату. З одного боку, рішення задачі радикально покращився: зменшилася кількість розкиданих по тексту програми фрагментів реалізації пункту і, крім того, фрагменти виявилися пов'язаними (правда, тільки в одному напрямку), оскільки від візуального представлення пункту тепер можна перейти до таблиці його атрибутів і далі до текстів фрагментів. З іншого боку, для упорядкування програмного уявлення фрагментів знову треба було щось, що нагадує # Set-конструкцію.
У той же час описані засоби Delphi охоплюють тільки обмежене коло візуалізуються примітивів і їх модифікацій. Всі інші частини програми пишуться як і раніше, і там завдання обслуговування двовимірних структур залишається настільки ж актуальною. Переконливою ілюстрацією останнього твердження може послужити приклад, наведений в керівництві по Delphi [3].
У першій частині керівництва докладно розбирається програма розрахунку платежів за позикою. Користувач задає кілька параметрів позики, за якими обчислюється і виводиться на екран таблиця майбутніх платежів погашення. У таблиці кожен платіж характеризують численні атрибути: номер платежу; основний капітал, оплачувану цим платежем; відсотки на капітал, оплачувані цим платежем; скільки основного капіталу вже оплачено; скільки позики залишається після цього платежу і т.д.
Видно, що автори керівництва - три міцних професіонала - не один рік працювали над своєю програмою, прагнучи перетворити її в зразок для наслідування. Програма дійсно вийшла непоганою. Вона розчленована на два модуля. Один з них зайнятий зовнішньою стороною справи і заразливо енергійно використовує кошти візуального програмування. Другому модулю доручена обчислювальна робота, і він успішно з нею справляється.
Все йде цілком благополучно до того моменту, поки нам не знадобиться додати новий або видалити існуючий атрибут платежу. Тут з'ясовується, що реалізація атрибута розпадається на вісім (!) Віддалених один від одного фрагментів, вільно розкиданих по текстам обох модулів. Ділянка програми, що містить перші дві точки збору фрагментів реалізації атрибуту платежу, має вигляд:
TPayment = CLASS (TObject) {one element in the amortization table} END;
Перша точка скупчення описує сім наявних атрибутів платежу, а друга задає відповідно сім параметрів процедури, перетворюючої кожен з цих атрибутів в текстове представлення.
Споглядання рівних стовпчиків однорідних компонентів когось, можливо, призведе до розчулення. Однак тут більш доречні дещо інші емоції. Зображений вище ділянку програми нагадує горе-господаря, який зустрічає гостей (нові атрибути розрахунку) на порозі свого житла пропозицією залишити калоші в одній кімнаті, плащ - в інший, а капелюх і рукавички - в третій. Неважко передбачити, що прихід і відхід кожного візитера ускладниться болісними пошуками гардеробів і речей. Але біда не тільки в цьому. Розкидані всюди гардероби неминуче виявляться по сусідству з чимось крихким - і околиці оголосить скорботний крик незручного гостя, що зачепила оселяється на вказану позицію капелюхом (додається програмним фрагментом) дорогоцінну китайську вазу (сусідній налагоджений оператор).
І знову # Set-конструкція рятує становище. Складові реалізації атрибуту платежу треба оформити у вигляді полів записи бази даних проекту, а на місці кожної з восьми точок збору однорідних компонентів розмістити відповідні # Set-конструкції. Тоді з'явиться можливість перегляду утворилася двовимірної структури в будь-якому напрямку, відпаде необхідність в пошуку точок розміщення компонентів реалізації, і, що не менш важливо, підключення і виключення атрибутів відбуватимуться безболісно.
Ареал безболісного програмування
Розбирання приклади, можливо, переконали доброзичливо настроєного читача в тому, що однорідні набори в реальних програмах - не така вже й велика рідкість. Однак важко собі уявити, що хтось беззастережно прийме на віру такі два екстремістських тези.
1) У реальній програмі, як правило, вдається виокремити один або кілька однорідних наборів, які охоплюють основну частину її обсягу.
2) В реальній програмі, як правило, вдається виокремити один або кілька однорідних наборів, що володіють тим властивістю, що основна частина внесених до програми змін зводиться до додавання або виключення записів, що належать цим наборам.
Суворе обгрунтування справедливості цих тез - справа, мабуть, безнадійне, незважаючи на припасені в них безхребетні застереження типу "як правило" і "основна частина". Все, що можна тут зробити, це спробувати викликати довіру до них, звернувшись до непрямих підтверджень.
На підтримку тези (1) наведемо приклади виокремлення великих однорідних наборів в загальновідомих програмах. У трансляторі значну частку загального обсягу становлять однорідні компоненти, що реалізують окремі конструкції вхідного мови. В текстовому редакторі - процедури, які обслуговують функціональні клавіші. У програмі оптимізації - методи, які залучаються для наближення до екстремуму заданої функції. Ще кілька прикладів великих однорідних наборів можна знайти в роботі [4].
Досить характерні і приклади, наведені в цій статті. Досить часто левову частку обсягу програми займає обслуговування однорідних пунктів меню, подібних розглянутим в першому розділі. У програмі розрахунків за позикою обслуговування однорідних атрибутів платежу дійсно охоплює близько половини загального обсягу. Нарешті, читач, який бажає попрактикуватися у виявленні великих однорідних наборів, може для початку спробувати зробити це в тексті на природній мові, виокремивши, наприклад, в тексті даного розділу набір з двох однорідних компонентів, які обслуговують тези (1) і (2).
Уявімо собі тепер, що теза (1) вірний. Для практичного програмування з цього випливало б принаймні два важливих наслідки.
По-перше, у більшості програм є істотний резерв підвищення наочності. Адже, як уже було показано, в традиційному середовищі програмування відсутні кошти наочного відображення елементів однорідності. І якщо обсяги реальних однорідних наборів справді настільки значні, то їх роль в загальній структурі програми відповідно вельми велика, і те, що їх обриси через слабкість інструментарію залишаються невияв, відчутно знижує наочність програмного тексту.
По-друге, заслуговує на увагу експлуатуюча однорідність стратегія поетапної розробки програми, яка називається програмуванням "вшир". На першому етапі пропонується створити мінімальне число компонентів в кожному з виявлених великих однорідних наборів. Якщо теза (1) вірний, то обсяг робіт першого етапу буде відносно невеликий. Незважаючи на це, отримана програма вже має достатню самостійністю: для її налагодження не буде потрібно ніяких заглушок, без яких не можуть обійтися популярні стратегії "зверху вниз" і "знизу вгору". Наприклад, буде отримано транслятор, який реалізує гранично мале підмножина конструкцій мови, меню, що містить єдиний пункт "Вихід", і т.д.
На наступних етапах розробки програмуються і налагоджують все нові і нові однорідні компоненти. Якщо в тексті програми застосовані # Set-конструкції, то підключення нових компонентів буде відбуватися безболісно, не вимагаючи редагування текстів написаних і налагоджених на попередніх етапах частин. Більш того, для підключення однорідного компонента розробник може просто заповнити пред'явлену йому таблицю потрібними значеннями полів, навіть не заглядаючи в осяжний ці поля текст програми.
Перейдемо тепер до тези (2). На його користь говорить одне відоме спостереження. Якщо розробники мають справу з постійними і інтенсивними змінами, то рано чи пізно вони приходять до деякого регулярному механізму розвитку програми. Зазвичай такий механізм грунтується на тому, що вдається виявити всі або майже всі змінювані фактори, що впливають на програму. Кожному фактору ставиться у відповідність свій однорідний набір, і поява нового значення фактора проектується в поповнення цього набору новим компонентом. Звичайно, всі мислимі зміни не вдається укласти в канонічне русло: час від часу можуть знадобитися революції, радикально перекроювати структуру програми. І все ж основна маса змін благополучно прогнозується і реалізується в рамках такого регулярного механізму.
Не слід думати, що регулярний механізм розвитку завжди потребує будь-якого аналога # Set-конструкції, де в формований текст програми включаються всі зберігаються в базі даних проекту записи однорідного набору. Часто вдається обмежитися більш простий в реалізації конструкцією #Variant, де з наявного набору однорідних записів вибирається і потрапляє в формований текст лише одна. Конструкція #Variant широко застосовується при програмуванні різноманітних завдань (рис. 2). У цих завданнях поява нового значення змінюваного чинника означає, що стара запис, що відображає колишнє значення фактора, повинна бути замінена новою. Однак і стара запису не відкидається, оскільки згодом знову може знадобитися колишнє значення фактора; таким чином в базі даних проекту накопичується однорідний набір змінних записів, що відображають різні коли-небудь використовувалися значення змінюваного чинника.
Мал. 2. Збірка різноманітної програми
Оскільки в генерований текст потрапляє рівно один запис, відпадає потреба в циклі періоду компіляції, а поля запису безпосередньо підставляються в текст програми. Тому конструкція #Variant вельми проста:
#Variant набор.поле
де набір - ім'я однорідного набору змінних записів (зазвичай в якості імені використовується мнемонічне позначення змінюваного чинника), поле - ім'я вставляється поля записи.
На відміну від # Set-конструкції, де однозв'язна реалізація велика рідкість, відображення значення змінюваного чинника в конструкції #Variant нерідко вдається локалізувати в одній точці програми. В цьому випадку однорідна запис являє собою єдине поле, і в конструкції #Variant вказується тільки ім'я набору. Наприклад, якщо потрібно періодично міняти метод розрахунку, який має однозв'язного реалізацію, то на його місці в програмі буде записано:
#Variant Method
де Method - ім'я однорідного набору методів розрахунку.
Для того щоб можна було зібрати виконувану версію програми, необхідно вказати, які змінні записи повинні підставлятися на місце конструкцій #Variant. З цією метою на вхід генеруючого остаточний текст програми препроцесора подаються конструкції призначення набір
Незважаючи на істотні зовнішні відмінності, конструкції #Set і #Variant схожі в головному: і та й інша забезпечує безболісне розвиток програми. Тому, якщо вірний теза (2), то основний потік робіт з розвитку програми можна направити в русло безболісних змін. Зрозуміло, для потреб реального програмування описані конструкції потрібно вдосконалити і доповнити [4], але це того варте: виключення з процесу розвитку такої небезпечної процедури як редагування налагодженого раніше тексту виводить цей процес на новий рівень надійності.
Так чи інакше, якщо в тезах (1) або (2) є хоча б частка правди, засоби системної підтримки однорідних наборів заслуговують включення в повсякденний інструментарій. Саме відсутність підтримки стримує масове застосування пропонованого апарату. Реалізація засобів підтримки однорідних наборів не зажадає сьогодні значних зусиль, оскільки основні причини цього кроку цілком дозріли.
Найбільш важливою передумовою є практично відбувся перехід від незалежної трансляції до роздільної (навіть мову Сі ++, спочатку тяжіли до незалежної трансляції, потихеньку дрейфує в сторону Java). Можливо, говорити про повноцінної бази даних проекту, як про щось загальновживаним, поки і зарано, але те, що всі вихідні тексти зараз розглядаються у взаємозв'язку, - це доконаний факт. В такому середовищі цікавлять нас набори однорідних записів вже не виглядають як абсолютно чужорідне тіло.
Ще один необхідний елемент інструментарію - підтримка процесу створення і редагування записи однорідного набору. У цього елемента також є готовий прообраз - екранні таблиці атрибутів в Delphi. Їх має бути тільки доповнити загальнодоступним механізмом формування складу нової таблиці, кожен екземпляр якої перетвориться потім у запис однорідного набору.
Останній суттєвий елемент - це препроцесор, что спріймає конструкції #Variant и #Set. Препроцесори зараз не в моді, але зате є багатющий попередній досвід їх розробки. Головне, що тут потрібно, - це оснастити препроцесор узгодженими засобами перегляду вихідного і згенерованого тексту, а також забезпечити навігацію по всій базі даних проекту в пошуках точок звернення до зазначеного однорідному набору.
Таким чином, обсяг робіт по реалізації не так вже й великий. Тому існує надія, що в доступному для огляду майбутньому в арсеналі розробника з'являться кошти підтримки однорідного набору, здатні надати процесу розвитку програми привабливість, настільки відсутню йому зараз.
література
1. Сван Т. Програмування для Windows в Borland C ++. - М .: Біном, 1995 г. - 480 с.
2. Сван Т. Основи програмування в Delphi для Windows95. - Київ: Діалектика, 1996 г. - 480 с.
3. Дантеман Д., Мішел Д., Тейлор Д. Програмування в середовищі Delphi. - Київ: Диасофт, 1995 г. - 608 с.
4. Горбунов-Посадов М.М. Конфігурації програм. Рецепти безболісних змін. - 2-е изд., Испр. і доп. - М .: Маліп, 1994 г. - 272 с.
Михайло Горбунов-Посадов ( [email protected] ) - співробітник, Інститут прикладної математики ім. М.В. Келдиша РАН (Москва).
Проте складається враження, що далеко не кожен з творців програміста інструментарію хоч зрідка задає собі пряме запитання: "Що я зробив для полегшення розвитку програми?Що доведеться робити для внесення цього доповнення?
Але через що ж розривається на віддалені один від одного шматки решта реалізації?
Які переваги несе застосування конструкції #Set?
Однак у досвідченого читача, ймовірно, давно вже зріє питання: а чи варто було через таку малість нагромаджувати новий, ні на що не схожий елемент інструментального середовища?
Інакше кажучи, наскільки часто ситуації, близькі до описаної вище, зустрічаються в реальних програмах?
Що ж завадило авторам Delphi, безстрашно вчинили потужний стрибок у малодосліджених сферу візуального програмування, доповнити Паскаль щодо нескладної конструкцією каркаса?