Гарний стиль коду для впровадження перевірок даних скрізь?


10

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

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

Щоб завжди не з'ясовувати, що звідки вийшло, я почав трактувати кожну функцію та клас як "відокремлену сутність", в тому сенсі, що він не може покладатися на зовнішній код, даючи йому правильний вхід і повинен сам перевіряти вхід (або, у деяких випадках переробляють дані, якщо дані наведені у неправильній формі).

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

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

(FYI: Я все ще новачок, тому, вибачте, якщо це питання було наївним; мій проект знаходиться в Python.)


1
Можливий дублікат того, наскільки нам слід захищати?
гнат

3
@gnat Це аналогічно, але замість того, щоб відповісти на моє запитання, там він дає поради ("будьте настільки захисними, як ви можете бути") для конкретного випадку, про який згадував ОП, який відрізняється від мого більш загального запиту.
користувач7088941

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

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

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

Відповіді:


4

Краще рішення - скоріше скористатися можливостями мови та інструментами Python.

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

Цю проблему усувають за допомогою namedtuple. Він легкий і надає легкий смисловий сенс членам вашого масиву.

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

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

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


+1 за вказівку на мене namedtupleта на всі інші приємні речі. Я не про це namedtuple- і, хоча я знав про автоматизоване тестування, я ніколи насправді не використовував його багато і не усвідомлював, наскільки це допоможе мені в цьому випадку. Все це, здається, так само добре, як і статичний аналіз. (Автоматизоване тестування може бути навіть кращим, оскільки я можу впіймати всі найтонші речі, які не потрапили б у статичний аналіз!) Якщо ви знаєте будь-яке інше, будь ласка, дайте мені знати. Я буду тримати питання довше відкритим, але якщо інших відповідей не буде, я прийму ваші.
user7088941

9

Гаразд, актуальна проблема описана в коментарі під цією відповіддю:

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

Проблема тут полягає у використанні списку рядків, де порядок означає семантику. Це дійсно схильний до помилок підхід. Натомість слід створити спеціальний клас із двома полями з ім'ям titleта bibliographical_reference. Таким чином, ви не збираєтесь їх змішувати, і уникнете цієї проблеми в майбутньому. Звичайно, це вимагає певного рефакторингу, якщо ви вже використовуєте списки рядків у багатьох місцях, але повірте, з часом це буде дешевше.

Поширений підхід у динамічно типових мовах - це "набирання качок", а це означає, що ви дійсно не переймаєтесь "типом" переданого об'єкта, вас дбає лише, якщо він підтримує методи, які ви викликаєте на ньому. У вашому випадку ви просто прочитаєте поле, яке називається, bibliographical_referenceколи вам це потрібно. Якщо цього поля не існує на переданому об'єкті, ви отримаєте помилку, і це вказує на те, що неправильний тип передається функції. Це так само добре, як і будь-яка перевірка.


Іноді проблема є ще більш тонкою: я передаю правильний тип, але "внутрішня структура" мого вводу змінює функцію вгору: Наприклад, у функції 1 очікуваний вхід - це масив рядків, де перший рядок позначає заголовок чогось, а другий - бібліографічний довідник. У функції 2 очікуваний вхід все ще є масивом рядків, але тепер ролі рядків перевернуті: Перший рядок повинен бути бібліографічним посиланням, а другий - бібліографічним посиланням. Думаю, для цього перевірки підходять?
user7088941

1
@ user7088941: Проблему, яку ви описуєте, можна легко вирішити, маючи клас із двома полями: "title" та "bibliographical_reference". Ви не змішаєте це. Покладаючись на порядок у списку рядків, схоже, дуже схильні помилки. Може це основна проблема?
ЖакB

3
Це відповідь. Python - це об'єктно-орієнтована мова, а не перелік словників-рядків-цілих чисел (або будь-яка інша) мова, орієнтована на список. Отже, використовуйте предмети. Об'єкти несуть відповідальність за керування власною державою та примушення їх власних інваріантів, інші об'єкти не можуть їх пошкодити, коли-небудь (якщо правильно розроблено). Якщо неструктуровані або напівструктуровані дані надходять у вашу систему ззовні, ви перевіряєте та аналізуєте один раз на межі системи та якнайшвидше перетворюєте на багаті об’єкти.
Йорг W Міттаг

3
"Я б справді уникав постійного рефакторингу" - цей ментальний блок - ваша проблема. Хороший код виникає лише при рефакторингу. Багато рефакторингу. Підтримується одиничними тестами. Особливо, коли компоненти потрібно розширювати або розвивати.
Док Браун

2
Я зараз це отримав. +1 за всі приємні відомості та коментарі. І дякую всім за неймовірно корисні коментарі! (Під час використання якихось класів / об’єктів я переплутав їх із згаданими списками, що, як я зараз бачу, не було гарною ідеєю. Залишалося питання, як найкраще це реалізувати, де я використав конкретні пропозиції відповіді JETM , що дійсно
змінило

3

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

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

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

(або, в деяких випадках, переробляти дані, якщо дані наведені в неправильній формі).

Це може бути менш гарна ідея. Якщо ви помітили, що частина вашої програми викликає функцію з неправильно відформатованими даними, ВИПРАВЛІТЬ ЦІЙ ЧАСТІ , не змінюйте викликану функцію, щоб мати можливість перетравлювати погані дані все одно.

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

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

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


1

Нічого страшного. Я раніше кодував у FoxPro, де у мене були блоки TRY..CATCH майже у кожній великій функції. Тепер я кодую в JavaScript / LiveScript і рідко перевіряю параметри у "внутрішніх" чи "приватних" функціях.

"Скільки перевірити" залежить від обраного проекту / мови більше, ніж це залежить від вашої майстерності коду.


1
Я здогадуюсь, це було ДОПОМОГАТИ ... ЗЛАТИ ... ІГНОРЕ. Ви робили навпаки того, чого просить ОП. ІМХО їх суть у тому, щоб уникнути неузгодженості, тоді як ваша гарантувала, що програма не підірветься при натисканні на неї.
maaartinus

1
@maaartinus це правильно. Мови програмування зазвичай дають нам конструкції, які прості у використанні для запобігання застосуванню, але мови програмування дають нам запобігти невідповідності, здається, набагато складніше: наскільки мені відомо, постійно переробляють все та використовують класи, які найкраще використовують контейнери інформаційний потік у вашій програмі. Це саме те, про що я запитую - чи є простіший спосіб виправити це.
user7088941

@ user7088941 Тому я уникаю слабко набраних мов. Python просто фантастичний, але для чогось більшого я не можу відслідковувати те, що робив в іншому місці. Тому я віддаю перевагу Java, який є досить багатослівним (не так вже й багато можливостей Lombok та Java 8), має суворий тип та інструменти для статичного аналізу. Я б запропонував вам спробувати кілька строго типів мови, оскільки я не знаю, як це вирішити інакше.
maaartinus

Йдеться не про суворий / вільний параметр набраного тексту. Йдеться про те, щоб знати, що параметр правильний. Навіть якщо ви використовуєте (ціле 4 байти), можливо, вам доведеться перевірити, чи він знаходиться в деякому діапазоні, наприклад, 0..10. Якщо ви знаєте, що параметр завжди 0..10, вам не потрібно його перевіряти. Наприклад, FoxPro не має асоціативних масивів, дуже важко працювати з його змінними, їх областю тощо. Тому вам доведеться перевірити чек ..
Michael Quad

1
@ user7088941 Це не OO, але є правило "невдало швидко". Кожен неприватний метод повинен перевірити свої аргументи і кинути, коли щось не так. Немає спроб лову, жодної спроби виправити це, просто підірвіть його до неба. Впевнені, що на якомусь вищому рівні виняток отримують реєстрацію та обробку. Оскільки ваші тести заздалегідь знаходять більшість проблем, і жодні проблеми не приховуються, код переходить до рішення, що не містить помилок, набагато швидше, ніж коли є помилковими.
maaartinus
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.