Чистий функціонал проти кажіть, не питайте?


14

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

( Джерело: коментар @David Arno під іншим питанням на цьому сайті )

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

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

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

Однак із цих 133 результатів я міркую, що в даний час чисте функціональне програмування "виграє", оскільки стає консенсусом, що йому краще сказати, не питайте. Це правильно?

Тому на прикладі цієї антипатернської гри я намагаюся зробити : Якби я хотів привести її у відповідність до чистої функціональної парадигми - ЯК ?!

Мені здається розумним мати бойовий стан. Оскільки це покрокова гра, я зберігаю бойові стани у словнику (мультиплеєр - може бути багато битв, які грають багато гравців одночасно). Щоразу, коли гравець робить свою чергу, я називаю відповідний метод бойового стану, який (a) відповідно змінює стан і (b) повертає оновлення гравцям, які серіалізуються в JSON і в основному просто повідомляють їм, що щойно сталося на дошки. Це, мабуть, є грубим порушенням принципів BOTH і водночас.

Гаразд - я міг би зробити метод ПОВЕРНЕННЯ бойового стану замість того, щоб змінювати його на місці, якби дуже хотів. Але! Чи доведеться мені потім копіювати все, що знаходиться у бойовому стані, лише для повернення абсолютно нового стану замість того, щоб змінювати його на місці?

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

Мені здається набагато простіше просто змінити стан на місці і повернути оновлення ...

Але як би досвідчений інженер вирішив це?


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

1
У мене ніколи не було запитань тут про щось, про що я говорив раніше. Мені честь. :)
Девід Арно

Відповіді:


14

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

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

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


Дякуємо, що правильно пояснили "Скажи, не питай".
user949300

13

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

Перший: Урок дядька Боба

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

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

Як сказав Альберт Ейнштейн, "все повинно бути настільки просто, наскільки це може бути, але не простіше".

Другий: Урок Девіда Арно

Описаний спосіб розробки Девіда Арно - це більш функціональний розвиток стилю, ніж орієнтований на об'єкти . Однак функціональний код масштабується набагато краще, ніж традиційне об'єктно-орієнтоване програмування. Чому? Через блокування. У будь-який момент, коли стан об'єкта є незмінним, ви ризикуєте переговорити умови або заблокувати суперечку.

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

Розробка - це низка компромісів

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

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

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

Хто прав? Що ж, Девід має рацію, ніж дядько Боб у цьому випадку. Однак, тут я хочу підкреслити, що метод повинен мати стільки аргументів, скільки має сенс.


Є паралелізм. Паралельно можна обробляти різні битви. Однак так: один бій, поки він обробляється, потрібно заблокувати.
gaazkam

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

8

Гаразд - я міг би зробити метод ПОВЕРНЕННЯ бойового стану замість того, щоб змінювати його на місці, якби дуже хотів.

Так, це ідея.

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

Ні. Ваш "бойовий стан" може бути змодельований як незмінна структура даних, яка містить інші незмінні структури даних як будівельні блоки, можливо, вкладені в деякі ієрархії змінних структур даних.

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

Google для "Ефективних змінних структур даних", і ви неодмінно знайдете деякі посилання, як це працює в цілому.

Мені здається набагато простіше просто змінити стан на місці та повернути оновлення.

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


8

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

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

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

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

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

Вітер трохи вперед. Зараз у нас є об'єкти та методи. Ці дані більше не повинні бути глобальними, вони можуть передаватися через конструктор і міститися в об'єкті. І тоді у нас є методи, які впливають на ці дані. Отже, тепер у нас є "скажи, не питай", як описує Фоулер. Об'єкт повідомляє свої дані. Ці методи більше не повинні задавати глобальний обсяг для своїх даних. Але ось те, що, на мій погляд, все ще не відповідає "скажіть, не запитуйте", оскільки ці методи все-таки повинні задавати область об'єкта. Це скоріше сценарій "скажи, а потім запитай".

Тож вітер вперед до сучасного дня, скиньте підхід "це все до кінця" і запозичіть деякі принципи функціонального програмування. Тепер, коли метод викликається, всі дані йому надходять через його параметри. Можна (і було) стверджувати, "який сенс, це просто ускладнює код?" І так, передаючи через параметри, дані, доступні через область об’єкта, додають складності коду. Але зберігання цих даних в об'єкті, а не зробити їх доступними в усьому світі, також додає складності. Однак мало хто стверджує, що глобальні змінні завжди кращі, оскільки вони простіші. Справа в тому, що переваги, які "говорить, не питай", переважає над складністю зменшення сфери застосування. Це більше стосується передачі через параметри, ніж обмеження сфери дії на об'єкт.private staticі передайте все, що потрібно, через параметри, і тепер цьому методу можна довіряти, щоб він не мав доступ до речей, до яких він не повинен. Далі, це заохочує збереження методу невеликим, інакше список параметрів не виходить з рук. І це заохочує методи написання, які відповідають критеріям "чистої функції".

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

Але важливо пам’ятати, що ця «повна реалізація кажи не запитуй» справді є ідеалом, тобто прагматизм повинен брати участь у меншій мірі, ми стаємо ідеалістичним і, таким чином, трактуємо його помилково як єдино можливий правильний підхід коли-небудь. Дуже мало додатків можуть бути навіть близькими до того, що на 100% є побічним ефектом безкоштовно з тієї простої причини, що вони не зробили б нічого корисного, якби вони справді не мали побічних ефектів. Нам потрібна зміна стану, нам потрібен IO тощо, щоб програма була корисною. І в таких випадках методи повинні викликати побічні ефекти і тому не можуть бути чистими. Але головне правило полягає в тому, щоб мінімізувати ці "нечисті" методи; тільки вони мають побічні ефекти, тому що вони потребують, а не як норма.

Мені здається розумним мати бойовий стан. Оскільки це покрокова гра, я зберігаю бойові стани у словнику (мультиплеєр - може бути багато битв, які грають багато гравців одночасно). Щоразу, коли гравець робить свою чергу, я закликаю відповідний метод бойового стану, який (a) відповідно змінює стан і (b) повертає оновлення гравцям, які серіалізуються в JSON і в основному просто повідомляють їм, що щойно сталося на дошки.

Мені здається більш ніж розумним мати бойовий стан; це здається суттєвим. Вся мета такого коду - обробляти запити на зміну стану, керувати цими змінами стану та звітувати про них. Ви могли б обробляти цей стан у всьому світі, ви могли утримувати його в окремих об'єктах програвача або можете передавати його навколо набору чистих функцій. Вибір, який ви обираєте, підходить до того, який найкраще підходить для вашого конкретного сценарію. Глобальний стан спрощує розробку коду і є швидким, що є ключовою вимогою більшості ігор. Але це робить спосіб коду складнішим у підтримці, тестуванні та налагодженні. Набір чистих функцій зробить код складнішим для реалізації та ризикує бути занадто повільним через надмірне копіювання даних. Але це буде найпростіше тестувати і підтримувати. "Підхід до ОО" сидить на півдорозі.

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

І на завершення, лише ще раз зазначити: чисті функції та "скажи, не питай" - це зовсім не протилежності.


5

Будь-що, коли-небудь сказане, існує контекст, в якому ви можете викласти це твердження, що зробить це абсурдом.

введіть тут опис зображення

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

Наприклад pi(), це ідеально функція, яка є. Чому? Тому що мені байдуже, як і навіть якщо це було розраховано. Або якщо воно використовувало е, або sin (), щоб отримати число, яке воно повертає. Я з цим добре, бо ім’я говорить мені все, що мені потрібно знати.

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

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

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

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

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

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

Обидва надають хороші функції. Можна зробити і те, і інше. Якщо ви досить сміливі, щоб змішати їх, то будь ласка, чітко позначте їх.


0

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

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

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

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

Речі стають плутаними, коли ви починаєте визначати незмінні об'єкти (ті, команди яких повертають змінену копію, а не мутацію). Ви кажете собі: "Це OOP" і "я визначаю поведінку об'єкта". Ви повертаєтесь до випробуваного принципу Tell, Don't Ask. Проблема в тому, що ти застосовуєш її до неправильної сфери.

Сфери абсолютно різні і дотримуються різних правил. Функціональна область будується до того моменту, коли вона хоче випустити побічні ефекти у світ. Для того, щоб ці ефекти були випущені, усі дані, які були б інкапсульовані в імперативному об'єкті (якби це було написано саме так!), Повинні бути на користь обов`язкової оболонки. Без доступу до цих даних, які в іншому світі були б приховані за допомогою інкапсуляції, він не може зробити свою роботу. Це обчислювально неможливо.

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

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