Чому динамічні мови програмування створюють труднощі при супроводі великих об’ємів коду?


Дізнайтесь більше про нові кар'єрні можливості в EchoUA. Цікаві проекти, ринкова оплата, гарний колектив. Надсилайте резюме та приєднуйтеся до нас.

Євгеній Брікман – провідний розробник Play Framework для LinkedIn сказав у відео-презентації, записаній на JaxConf 2013, що великі кодові бази важче підтримувати, якщо вони написані на динамічних мовах.

На одному із Q&A сайтів було поставлене це запитання, і кращу відповідь на нього дав користувач Ерік Ліпперт.

 

Отже, його відповідь

Попередження: я не дивився презентацію.

Я працював у проектних комітетах із JavaScript (дуже динамічна мова), C # (в основному статична мова), і Visual Basic (як статична, так і динамічна), у мене є декілька думок, якими я хотів би поділитися.

Розпочнемо з того, що складно підтримувати велику кодову базу. Великий об’єм коду дуже важко написати, незважаючи на те, які інструменти є у Вашому розпорядженні. Ваше питання не означає, що супровід великого коду в статично типізованих мовах, буде простим, а припускає тільки те, що Ви матимете більше проблем, якщо будете підтримувати код у динамічній мові, а не в статичній. Є причини, через які зусилля, витрачені на підтримку великої кодової бази в динамічній мові дещо більші, ніж на статично типізованих мовах. Я розгляну тільки деякі з них.

Трохи забігаю вперед. Ми повинні чітко визначити, що розуміємо як “динамічну” мову: “динамічну” мову я вважаю протилежною “статичній” мові.

Статично типізована мова  – це мова, розроблена для полегшення автоматичної перевірки правильності за допомогою інструментів, які мають доступ тільки до вихідного коду, а не до даних у робочому стані програми. Дані, виведені за допомогою засобів, називають “типами”. Розробники мови розробляють набір правил, які перетворюють програму на типобезпечну, і засоби намагаються перевірити, чи програма наслідує ці правила.

Динамічно типізована мова, на відміну від статичної, не стає легшою від такого роду перевірки. Значення даних, що зберігаються у будь-якому конкретному місці, можуть бути визначені тільки шляхом перевірки під час роботи програми.

Ми могли б навести відмінності між динамічною зоною видимості й лексично обмеженими мовами, але давайте не відходитимемо від цілей цього обговорення. Динамічно типизована мова не має бути з динамічною зоною видимості й статично типізована мова не має бути лексично обмежена, проте часто між ними є взаємозв’язок.

Оскільки у нас є конкретні умови питання, давайте поговоримо про великі кодові бази, що, як правило, мають деякі загальні характеристики.

Ці характеристики створюють перешкоди на шляху до розуміння коду, і, відповідно, створюють перешкоди для правильної зміни коду. Інакше кажучи: час = гроші, й внести правильні зміни в масивному коді дорого внаслідок даних проблем.

Бюджет кінцевий, і ми хочемо зробити якомога більше з наявними ресурсами. Люди, які супроводжують великі кодові бази, прагнуть знизити вартість за допомогою прийняття правильних рішень для пом’якшення цих перешкод. Деякі способи для розв’язання проблем:

  • Модульність: код розділений на модулі, де кожен модуль має чітку задачу. Логіка коду може бути задокументованою і зрозумілою без того, щоб користувачеві треба було розбиратися в деталях його реалізації.
  • Інкапсуляція: у модулях проведені відмінності між їх “зовнішньою” поверхнею та їх “внутрішніми” деталями реалізації, так, що останні можуть бути поліпшені без завдання шкоди правильності роботи програми в цілому.
  • Повторне використання: коли проблема розв’язана правильно один раз, так відбувається завжди. Результат може бути повторно використаний для розв’язання інших проблем. Такі технології, як створення бібліотеки корисних функцій, або створення функціональності базового класу, які можуть бути розширені за допомогою похідного класу, або архітектури, в якій вітається вміст, що складається з методів, реалізованих повторним використанням коду. Все це необхідне для того, щоб зменшити витрати.
  • Анотації: анотований код описує допустимі значення, які можуть приймати змінні.
  • Автоматичне виявлення помилок: команді, яка працює над великою програмою, має сенс створити додаток, який відразу визначить помилку і розповість про неї, щоб вона була виправлена швидко. Такі технології, як написання набору тестів, використання статичного аналізатора потрапляють до цієї категорії.

Статично типізована мова є прикладом останнього. Ви дістаєте в компіляторі можливість побачити помилки, пов’язані з типами даних, і компілятор повідомить про це. Явно типізована мова вимагає, щоб в місцях оголошення змінних, були примітки, які несуть інформацію про те, що може міститися в цих змінних.

Таким чином, через тільки цю причину, в  динамічно типізованих мовах важче супроводжувати об’ємну кодову базу, тому що роботу, яка виконується компілятором “безкоштовно”, Ви повинні робити у вигляді написання набору тестів. Якщо Ви хочете коментувати свої змінні, то маєте продумати чітку систему, і якщо новий член Вашої команди випадково порушить її, то Ви повинні знайти порушення якомога раніше.

Тепер я б хотів згадати ключовий момент: є сильний взаємозв’язок між динамічною типизованістю мови і браком можливостей, які могли б знизити витрати на підтримання, і цей ключовий момент – причина, чому важче підтримувати великий код у динамічній мові. І такий же взаємозв’язок є між статичною типізованістю і наявністю засобів, які  полегшують програмування.

Давайте візьмемо JavaScript як приклад. Я працював з оригінальними версіями JScript у Microsoft з 1996 по 2001 рік) мета проекту полягала в тому, щоб мавпа танцювала, при наведенні на неї курсором. Скрипти часто складалися з одного рядка. Ми розглядали десятирядкові скрипти, і було непогано, скрипти із сотні рядків вже вважалися великими, а скрипти з тисячі рядків були надміру великими. Мова абсолютно не призначена для програмування об’ємних задач, і наші рішення при реалізації, контрольні задачі ґрунтуються на цьому припущенні.
JavaScript був спеціально розроблений для програм, які великі настільки, що людина могла бачити все це на одній сторінці. JavaScript не лише динамічно типізується, а також має засоби, які допомагають програмувати великі кодові бази:

  • Немає модульної системи, ні класів, інтерфейсів, або навіть просторів імен. Ці елементи є в інших мовах, щоб допомагати організовувати об’ємні кодові бази.
  • Система спадкоємства, а так само спадкоємство прототипів слабко розвинені. І не зрозуміло, як правильно будувати глибокі ієрархії (типу: капітан вид пірата, пірат вид людини, людина вид чогось там ще…) в out-of-the-box JavaScript.
  • Немає інкапсуляції: кожна властивість об’єкта може змінюватися за бажанням певної частини програми.
  • Там немає жодного способу, щоб позначити обмеження пам’яті: змінна може містити будь-яке значення. Це не просто брак тих самих можливостей, які полегшують програмування. Є також функції, які роблять його важчим.
  • Система управління помилками в JavaScript розроблена з припущенням, що скрипт виконується на веб-сторінці, і що ціна помилки невелика, і користувач, який побачить неточності, швидше за все не буде здатний їх виправити. Тому, як би багато помилок не було, програма спробує виконатися до кінця. Це розумно, для цих цілей мови, але вона, безумовно, робить програмування великих об’ємів коду в рази складніше, оскільки це збільшує складність написання тестів. Часто шукати помилки в таких програмах важче, ніж писати тести, які їх виявлять.
  • Код може змінити сам себе на основі даних з користувацького введення через об’єкти, такі як Eval, або за допомогою додавання нових блоків сценаріїв у DOM динамічно. Будь-які інструменти статичного аналізу не можуть навіть знати, що код зробить з програмою!
  • І так далі.

Ясно, що можна подолати ці перешкоди і створити велику програму в JavaScript. Нині є багато програм JavaScript, що складаються з мільйонів рядків. Проте великі команди, які супроводжують ці складні проекти використовують різні інструменти і в них діє сувора дисципліна, щоб долати перешкоди, які JavaScript створює на Вашому шляху:

  • Вони пишуть тест-кейси для кожного ідентифікатора того, що використовується в програмі. У світі, де орфографічні помилки ігноруються, це необхідно й економічно вигідно.
  • Пишуть код у типізованих мовах, таких як TypeScript, і компілюють як JavaScript.
  • Вони використовують фреймворки, які сприяють програмуванню в стилі, який краще піддається аналізу, більше схильний до модульності й з меншою ймовірністю відтворить поширені помилки.
  • У них сувора дисципліна у найменуванні, розподілі обов’язків і так далі. Знову ж таки, це знижує вартість, і ці задачі будуть виконані компілятором у типовій статично типізованій мові.

Насамкінець скажу, що не лише динамічний характер типізації збільшує витрати на підтримання великого коду. Звичайно, це призводить до підвищення витрат і не лише. Наприклад, я міг би спроектувати мову, яка динамічно типізувалася б, але мав би і простір імен, і модулі, і спадкоємство, і бібліотеки, і закриті дані, і так далі, до речі, C# 4 є подібною мовою. Вона динамічна й дуже зручна в програмуванні великих кодових баз.

Найчастіше саме те, чого часто бракує в динамічних мовах, у результаті й підвищує витрати на супровід великих програм. Динамічні мови, які мають засоби для якісного тестування модульності, повторного використання, інкапсуляції і так далі, насправді можуть істотно знизити витрати при програмуванні великих кодових баз, але багато з часто використовуваних динамічних мов не мають цих корисних додатків. Хтось має їх створити, і це збільшить витрати.

Переклад статті “Why do dynamic languages make it difficult to maintain large codebases”?

Київ, Харків, Одеса, Дніпро, Запоріжжя, Кривий Ріг, Вінниця, Херсон, Черкаси, Житомир, Хмельницький, Чернівці, Рівне, Івано-Франківськ, Кременчук, Тернопіль, Луцьк, Ужгород, Кам'янець-Подільський, Стрий - за статистикою саме з цих міст програмісти найбільше переїжджають працювати до Львова. А Ви розглядаєте relocate?


Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *