Що таке інваріанти, як їх можна використовувати та чи використовували ви коли-небудь у своїй програмі?


48

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

Чи правильний мій опис, чи я щось пропустив? Ви коли-небудь використовували їх у своїй програмі? І якщо так, то як вони принесли користь?



@ Роберт Харві: Так, я просто це прочитав насправді. Але мені здається, що інваріанти корисні лише тоді, коли ви намагаєтесь щось довести. Це правильно (каламбур не призначений)?
габлін

Це моє розуміння; коли ви намагаєтесь розмірковувати про свою програму, щоб довести її коректність.
Роберт Харві

3
@ user9094: твердження - це твердження про те, що щось є істинним у певний момент часу виконання, і представлене в коді. Інваріант - це твердження (сподіваємось, що воно обгрунтоване), яке завжди буде істинним, коли воно застосовується, і не представлене в самому коді.
Девід Торнлі

1
Інваріанти справді корисні для доведення правильності, але вони не обмежуються цим випадком. Вони також корисні для оборонного програмування та під час налагодження. Вони не просто допомагають довести ваш код правильним, вони допомагають міркувати про код і знаходити місцезнаходження помилок, близьких до джерел.
Однодумство

Відповіді:


41

В OOP інваріант - це набір тверджень, які завжди повинні бути правдивими протягом життя об'єкта, щоб програма була дійсною. Це має бути істинним від кінця конструктора до початку деструктора, коли об'єкт не виконує метод, який змінює його стан.

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

Я колись використовую функцію-член об'єкта, щоб перевірити, чи є інваріант. Якщо це не так, подається ствердження. І метод викликається при запуску та виході кожного методу, який змінює об'єкт (у C ++ це лише один рядок ...)


11
+1 для згадки про інваріантів не повинно бути правдою посеред методу виконання.
Однодумство

1
@Oddthinking Краще уникати цього, коли можливо. Легко було б увійти в стан, що порушує інваріант, і забути відновити все правильно перед поверненням. Винятки також можуть доставити вам проблеми.
Олександр

3
@ Олександр: Нетривіальних інваріантів уникнути майже неможливо. Якщо вам потрібно оновити більше однієї змінної у методі, як описано у відповіді, є момент, коли оновлено лише одну, а інваріант - помилковий. Існує достатньо обмежень для написання хорошого коду без додавання нових.
Однодумство

@ Доречне мислення Так, це часто зовсім неминуче. Наприклад, якщо є група змінних, які логічно належать разом (наприклад, масив та індекс "вибраного" елемента в масиві), то, ймовірно, варто витягнути їх до типу. Звідти мутації масиву чи типу можуть бути виражені як одне призначення нового екземпляра цього типу
Олександра

13

Що ж, все, що я бачу в цій темі, все чудово, але в мене є визначення «інваріанта», яке дуже допомагало мені на роботі.

Інваріант - це будь-яке логічне правило, якого слід дотримуватися протягом усього виконання програми, яке може бути передано людині, але не вашому компілятору.

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

Також це визначення корисне, оскільки дозволяє використовувати узагальнення "Інваріанти погані".

Наприклад, перемикач автомобілів з механічною коробкою передач сконструйований, щоб уникнути інваріанта. Якби я хотів, я міг би побудувати коробку передач з одним важелем на кожну передачу. Цей важіль може бути вперед ("зачеплений") або назад ("відключений"). У такій системі я створив "інваріант", який може бути задокументований як такий:

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

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

Таким чином, можна сказати, що передача була спроектована для "видалення інваріанта", оскільки вона не дозволяє механічно налаштовуватися таким чином, що порушує логічне правило.

Кожен подібний інваріант, який ви вилучите зі свого коду, є вдосконаленням, оскільки він знижує когнітивне навантаження роботи з ним.


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

1
Порівняння передач автомобіля мені дуже зрозуміле. Дякую!
Marecky

"Інваріант - це будь-яке логічне правило, якого слід дотримуватися протягом усього виконання вашої програми, яке може бути передано людині, але не вашому компілятору." - Мені це дуже подобається, лаконічно і легко запам'ятовується.
ZeroKnight

@DustinCleveland Я думаю, що в цьому прикладі механізми, що стоять за зміною палиці, - це "компілятор", який "виконує" правила, тоді як драйвер, який в іншому випадку може спричинити інцидент, є одним із багатьох клієнтів, які повинні споживати та пам'ятати інформацію, яка є були "задокументовані, обговорені, прокоментовані чи іншим чином повідомлені".
ebernard

Блискуче пояснення! Я справді розумію, що міркування, чому мати інваріанти у своєму коді - це погана практика.
Бен С Ван

3

Інваріант (в здоровому розумінні) означає деякі умови, які повинні бути справжніми в якийсь момент часу або навіть завжди, коли ваша програма виконує. наприклад, PreConditions і PostConditions можна використовувати для затвердження деяких умов, які повинні бути істинними, коли функція викликається і коли вона повертається. Об'єктні інваріанти можна використовувати для твердження, що об'єкт повинен мати дійсний стан протягом усього часу його існування. Це оформлення за контрактом.
Я неформально використовував інваріанти, використовуючи чеки в коді. Але останнім часом я граю з бібліотекою кодових контрактів для .Net, яка безпосередньо підтримує інваріантів.


3

Спираючись на наступну цитату Coders At Work ...

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

... я здогадуюсь "інваріантний" = "стан, який ви хочете підтримувати, щоб забезпечити бажаний ефект".

Здається, що інваріант має два органи чуття, які відрізняються тонко:

  1. Щось, що залишається тим самим.
  2. Щось, що ви намагаєтесь зберегти так само, щоб досягти мети X (наприклад, "час пошуку журналу" вище).

Отже 1 - це як твердження; 2 - це як інструмент доказування правильності, продуктивності чи інших властивостей - я думаю. Дивіться статтю Вікіпедії на прикладі 2 (доведення правильності рішення головоломки MU).

Насправді 3-е почуття інваріантного:

.3. Що передбачає робити програма (або модуль чи функція); іншими словами, його призначення.

З того самого інтерв'ю Coders At Work:

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


1

Інваріант - це як правило або припущення, яке можна використовувати для диктування логіки вашої програми.

Наприклад, припустимо, у вас є програмне забезпечення, яке відстежує облікові записи користувачів. Припустимо також, що користувач може мати кілька облікових записів, але з будь-якої причини вам потрібно розмежовувати основний обліковий запис користувача та "псевдонім".

Це може бути запис БД або щось інше, але поки що припускаємо, що кожен обліковий запис користувача представлений об’єктом класу.

class userAccount {private char * pUserName; приватний char * pParentAccountUserName;

...}

Інваріантом може бути припущення, що якщо pParentAccountUserName NULL або порожній, то цей об'єкт є батьківським обліковим записом. Ви можете використовувати цей інваріант, щоб виділити різні типи рахунків. Напевно, є кращі методи розрізнити різні типи облікових записів користувачів, тому пам’ятайте, що це лише приклад, який показує, як може використовуватися інваріант.


Інваріанти перевіряють стан програми. Вони не є дизайнерськими рішеннями.
Ксав'є Нодет

3
Інваріанти нічого не перевіряють. Ви можете перевірити стан програми, щоб побачити, чи є інваріант ПРАВИЛЬНИМ чи ЛІЖНИМ, але самі інваріанти нічого не роблять.
Pemdas

2
Як правило, в C ++ ви побачите якусь інваріантність класу, наприклад, член x повинен бути менше 25 і більше 0. Це інваріант. Будь-які перевірки проти цього інваріанта є твердженнями. У наведеному вище прикладі мій інваріант - це якщо pParentAccountUserName NULL або порожній, то це батьківський рахунок. Інваріанти - це розроблені рішення.
Pemdas

Як перевірити, що якщо pParentAccountUserName NULL або порожній, цей об'єкт є батьківським обліковим записом? Ваша заява визначає лише те, що має представляти нульове / порожнє значення. Інваріантним є те, що система відповідає цьому, тобто pParentAccountUserName може бути нульовим або порожнім, лише якщо це батьківський рахунок. Це тонка відмінність.
Камерон

1

Виходячи з фізичного походження, у фізиці у нас є інваріанти, які по суті є величинами, які не змінюються протягом цілого обчислення / моделювання. Наприклад, у фізиці для замкнутої системи зберігається загальна енергія. Або знову ж таки у фізиці, якщо дві частинки стикаються, отримані фрагменти повинні містити саме ту енергію, з якої вони почали, і точно такий же імпульс (величина вектора). Зазвичай недостатньо інваріантів, щоб повністю уточнити результат. Наприклад, при зіткненні 2 частинок у нас є чотири інваріанти, три компоненти імпульсу та енергетична складова, але система має шість ступенів свободи (шість чисел для опису її стану). Інваріанти повинні бути збережені в межах помилки округлення, але їх збереження не доводить, що рішення є правильним.

Тому зазвичай такі речі важливі як перевірка добросовісності, але самі по собі вони не можуть довести правильність.


1
-1 Інваріанти у фізиці різні. Обчислення рішення не те саме, що довести, що алгоритм правильний. Для останнього інваріанти можуть довести правильність.
aaronasterling
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.