Чи перевіряються надмірні умови відповідності кращим практикам?


16

Я розробляю програмне забезпечення протягом останніх трьох років, але я нещодавно прокинувся, наскільки я невідомий щодо передового досвіду. Це змусило мене почати читати книгу « Чистий кодекс» , яка перевертає моє життя на краще, але я намагаюсь зрозуміти деякі найкращі підходи до написання моїх програм.

У мене програма Python, в якій я ...

  1. використовуйте argparse required=Trueдля забезпечення двох аргументів, які є обома іменами файлів. перше - ім'я вхідного файлу, друге - ім'я вихідного файлу
  2. мають функцію, readFromInputFileяка спочатку перевіряє, чи було введено ім'я вхідного файлу
  3. мають функцію, writeToOutputFileяка спочатку перевіряє, чи було введено ім'я вихідного файлу

Моя програма є достатньо малою, що я вважаю, що перевірка в №2 та №3 є зайвою, і її слід зняти, тим самим звільняючи обидві функції від зайвих ifумов. Однак мене також припустили, що "подвійна перевірка нормальна" і може бути правильним рішенням у програмі, де функції можна було б викликати з іншого місця, де розбір аргументів не відбувається.

(Крім того, якщо читання чи запис не вдається, я маю try exceptв кожній функції піднімати відповідне повідомлення про помилку.)

Моє запитання: чи найкраще уникати перевірки надмірних умов? Чи повинна логіка програми бути такою міцною, що перевірки потрібно робити лише один раз? Чи є якісь хороші приклади, які ілюструють це чи навпаки?

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


Ось сильно узагальнена версія вашого питання: softwareengineering.stackexchange.com/questions/19549/… . Я б не сказав, що це дублікат, оскільки він має досить велику увагу, але, можливо, це допомагає.
Док Браун

Відповіді:


15

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

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

У вашому випадку ви самі повинні вирішити, який життєвий цикл ви очікуєте для вашої програми. Це програма, яку ви очікуєте використовувати та підтримувати протягом багатьох років? Тоді додавання зайвої перевірки, ймовірно, краще, оскільки це не буде малоймовірно, що ваш код в майбутньому буде відновлено, і ваші readта writeфункції можуть використовуватися в іншому контексті.

Або це невелика програма тільки для навчальних чи цікавих цілей? Тоді ці подвійні перевірки не знадобляться.

У контексті "чистого коду" можна запитати, чи подвійна перевірка порушує принцип DRY. Насправді іноді це відбувається, принаймні в незначній мірі: перевірка вводу може бути інтерпретована як частина ділової логіки програми, і наявність цього в двох місцях може призвести до звичайних проблем з технічним обслуговуванням, викликаних порушенням DRY. Міцність та сухість часто є компромісом - стійкість вимагає надмірності коду, тоді як DRY намагається мінімізувати надмірність. Зі збільшенням складності програми надійність стає все більш важливою, ніж бути СУХОМ у валідації.

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

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

Це означає, що вам потрібно змінити подвійну перевірку в двох місцях? Напевно, ні, така вимога призводить до однієї зміни при виклику argparse, але ніякої зміни в writeToOutputFile: ця функція все одно вимагатиме імені файлу. Тож у вашому випадку я би проголосував за те, щоб зробити перевірку вхідних даних двічі, ризик отримати проблеми з технічним обслуговуванням через те, що потрібно змінити два місця, IMHO набагато нижчий, ніж ризик отримати проблеми з технічним обслуговуванням через замасковані помилки, спричинені занадто малою кількістю перевірок.


"... межі між компонентами у формі загальнодоступних API ..." Я зауважу, що "класи скачують межі" так би мовити. Отже, потрібен клас; узгоджений клас бізнес-домену. Я випливаю з цієї ОП, що тут діє всюдисущий принцип "це просто, тому не потрібен клас". Тут може бути простий клас, який обгортає "первинний об'єкт", застосовуючи бізнес-правила, такі як "файл повинен мати ім'я", який не лише сушить існуючий код, але і зберігає його ДУХО в майбутньому.
radarbob

@radarbob: те, що я написав, не обмежується OOP або компонентами у формі класів. Це стосується також довільних бібліотек із відкритим API, орієнтованим на об'єкти чи ні.
Док Браун

5

Надмірність не гріх. Зайве надмірність є.

  1. Якщо readFromInputFile()і writeToOutputFile()є загальнодоступними функціями (а умовами називання Python вони є, оскільки їхні імена не починалися з двох підкреслень), то ці функції колись можуть використовуватися тим, хто взагалі уникає аргументації. Це означає, що, коли вони залишають аргументи, вони не можуть побачити ваше власне повідомлення про помилку argparse.

  2. Якщо readFromInputFile()і writeToOutputFile()перевірити самі параметри, вам знову з’явиться спеціальне повідомлення про помилку, яке пояснює необхідність імен файлів.

  3. Якщо readFromInputFile()і writeToOutputFile()не перевіряти параметри самі, не повідомлення про помилку не відображається. Користувачеві доведеться самостійно розібратися в отриманому винятку.

Все зводиться до 3. Напишіть якийсь код, який фактично використовує ці функції, уникаючи argparse та видавати повідомлення про помилку. Уявіть, що ви взагалі не заглянули в ці функції і просто довіряєте їх іменам, щоб забезпечити достатнє розуміння для використання. Коли це все, що ви знаєте, чи є якийсь спосіб заплутати виняток? Чи є потреба у персоналізованому повідомленні про помилку?

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


4

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

Якщо програміст має звичку писати методи, які роблять припущення про те, що було передано, виходячи з ідей на кшталт "якщо це порушено, ми маємо турбуватися про більші речі" або "параметр X не може мати значення Y, оскільки решта код перешкоджає цьому ", то раптом у вас вже немає незалежних, нероз'єднаних компонентів. Ваші компоненти по суті залежать від ширшої системи. Це свого роду тонке тісне з'єднання і призводить до експоненціально збільшення загальної вартості власності у міру збільшення складності системи.

Зауважте, що це може означати, що ви перевіряєте ту саму інформацію не раз. Але це нормально. Кожен компонент відповідає за свою перевірку по-своєму . Це не є порушенням DRY, оскільки перевірки проводяться роз'єднаними незалежними компонентами, і зміна валідації в одному не обов'язково повинна повторюватися саме в іншому. Тут немає надмірності. Компанія X несе відповідальність перевіряти вхідні дані на власні потреби та передавати їх Y. Y має власну відповідальність перевіряти, чи є власні матеріали для його потреб .


1

Припустимо, у вас є функція (в С)

void readInputFile (const char* path);

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

void readInputFile (const char* path)
{
    assert (path != NULL && strlen (path) > 0);

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


0

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

  • Наскільки велика програма? Чим вона менша, тим очевидніше те, що абонент робить правильно. Коли ваша програма зростає, стає важливіше конкретизувати, що саме є передумовами та постумовами кожної програми.
  • аргументи вже перевірені argparseмодулем. Часто погана ідея користуватися бібліотекою, а потім виконувати свою роботу самостійно. Навіщо тоді використовувати бібліотеку?
  • Наскільки ймовірно, що ваш метод буде повторно використаний у контексті, коли абонент не перевіряє аргументи? Чим це ймовірніше, тим важливіше перевірити аргументи.
  • Що станеться , якщо аргумент дійсно пропадає? Не знайшовши вхідного файлу, ймовірно, перестане оброблятись прямо. Це, мабуть, очевидний режим відмови, який легко виправити. Підступними видами помилок є ті, коли програма весело продовжує працювати і дає неправильні результати, не помічаючи .

0

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

Занадто багато чеків не зашкодить, один занадто менше може.

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


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

0

Можливо, ви могли б змінити свою точку зору:

Якщо щось піде не так, який результат? Чи завдасть шкоди вашій програмі / користувачеві?

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

З контексту, який ви надаєте:

  • один вхідний файл A
  • один вихідний файл B

Я припускаю , що ви робите перетворення з A в B . Якщо A і B малі, а перетворення - малі, які наслідки?

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

2) Ви забули вказати вихідний файл. Це призводить до різних сценаріїв:

а) Вхід читається одразу. Тоді починається трансформація, і слід записувати результат, але замість цього ви отримуєте помилку. Залежно від часу, який потрібно користувачеві чекати (залежно від маси даних, які можуть оброблятися), це може дратувати.

б) Вхід читається поетапно. Потім процес запису негайно припиняється, як у (1), і користувач починається заново.

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

Додатково: Вам слід уникати параної і не робити занадто багато подвійних перевірок.


0

Я б заперечував, що тести не є зайвими.

  • У вас є дві загальнодоступні функції, для яких потрібне ім'я файлу як вхідного параметра. Доцільно перевірити їх параметри. Функції потенційно можуть використовуватися в будь-якій програмі, яка потребує їх функціональності.
  • У вас є програма, яка вимагає двох аргументів, які мають бути іменами. Трапляється використовувати функції. Програма доречна перевірити її параметри.

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

Більш надійне рішення матиме один чи два валідатори імені файлів.

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

Я використовую два правила, коли потрібно виконувати дії:

  • Робіть їх якомога раніше. Це добре працює для речей, які завжди будуть потрібні. З точки зору цієї програми, це перевірка значень argv, і подальша перевірка логіки програм була б зайвою. Якщо функції переміщені до бібліотеки, вони більше не будуть зайвими, оскільки бібліотека не може вірити, що всі абоненти перевірили параметри.
  • Робіть їх якомога пізніше. Це надзвичайно добре працює для речей, які рідко знадобляться. З точки зору цієї програми, це перевірка параметрів функції.

0

Чек зайвий. Однак для виправлення цього потрібно видалити readFromInputFile та writeToOutputFile та замінити їх на readFromStream та writeToStream.

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

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

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