Чи завжди найкращою практикою є написання функції для всього, що потрібно повторити двічі?


131

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

Для коду, якому потрібно більше двох рядків, я напишу функцію. Але стикаючись з такими речами, як:

print "Hi, Tom"
print "Hi, Mary"

Я вагаюся написати:

def greeting(name):
    print "Hi, " + name

greeting('Tom')
greeting('Mary')

Другий здається занадто великим, чи не так?


Але що робити:

for name in vip_list:
    print name
for name in guest_list:
    print name

І ось альтернатива:

def print_name(name_list):
    for name in name_list:
        print name

print_name(vip_list)
print_name(guest_list)

Речі стають складними, ні? Зараз важко визначитися.

Яка ваша думка з цього приводу?


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

7
У вашому останньому прикладі ви можете зробити: from itertools import chain; for name in chain(vip_list, guest_list): print(name).
Бакуріу

14
@ user16547 Ми називаємо цих розробників програмного забезпечення!
Брайан

6
Із Дзен Пітона Тіма Пітерса:Special cases aren't special enough to break the rules. Although practicality beats purity.
Зенон

3
Також врахуйте, чи хочете ви "вологий код" або "сухий код", див. Stackoverflow.com/questions/6453235/…
Іван

Відповіді:


201

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

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

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


Цей момент я сумую у відповіді Роберта Гарвея.
Док Браун

1
Справді. Я часто розбиваю складні функції на вбудовані підпрограми - не перф, але чисто. Анонімні сфери теж хороші.
imallett

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

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

1
+1 просто для Часто має сенс створити функцію для чогось, що виконується лише один раз. Мене одного разу запитали, як розробити тести на функцію, яка моделює поведінку ракетного двигуна. Цикломатична складність цієї функції була в 90-х роках, і це не було твердженням про перемикання з 90 деякими випадками (потворними, але перевіреними). Натомість це був заплутаний безлад, написаний інженерами, і був абсолютно незаперечний. Моя відповідь полягала саме в тому, що вона була непереборною і її потрібно було переписати. Вони дотримувались моєї поради!
Девід Хаммен

104

Тільки якщо дублювання є навмисним, а не випадковим .

Або, по-іншому:

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


Ось чому:

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

Практичне правило:

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


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

2
Це рубін, але ця екранна трансляція про спільне копіювання Авді Грімм все ще стосується питання: youtube.com/watch?v=E4FluFOC1zM
DGM

60

Ні, це не завжди найкраща практика.

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

То чому тоді код переробляється на окремі методи? Для поліпшення модульності. Зібрати значну функціональність за окремим методом і дати йому змістовну назву. Якщо ви цього не досягаєте, вам не потрібні ці окремі методи.


58
Читання коду є найважливішим.
Том Робінсон

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

17
Я погоджуюсь з Док Браун, код не обов’язково читати по черзі за рядком, ніж при правильному рефераті. Хороші назви функцій роблять функції високого рівня ДУЖЕ легкими для читання (тому що ви читаєте наміри, а не реалізацію), а функції низького рівня легше читати, оскільки ви виконуєте одне завдання, саме одне завдання, і нічого, крім цього завдання. Це робить функцію стислою та точною.
Історія Джона

19
Why take the performance hit of a function call when you don't get any significant benefit by doing so?Який показник вдарив? У загальному випадку малі функції вбудовуються, а для великих функцій накладні витрати незначні. CPython, ймовірно, не вбудований, але ніхто не використовує його для продуктивності. Я також сумніваюся в питанні line by line code.... Це простіше, якщо ви намагаєтеся імітувати виконання в своєму мозку. Але налагоджувач зробить кращу роботу, і намагатися довести щось про цикл, слідуючи за його виконанням, - це ніби намагатися довести твердження про цілі числа, повторивши його.
Doval

6
@Doval піднімає ДУЖЕ дійсну точку - коли компілюється код, функція розміщується в рядку (і дублюється), а отже, НЕ БУТИ ефективність виконання, хоча у компіляції є невеликий. Це не вірно для інтерпретованих мов, але навіть тому виклик функції - це хвилинна частка часу виконання.
Історія Джон

25

У вашому конкретному прикладі створення функції може здатися непосильним; натомість я б задав питання: чи можливо, що саме це привітання зміниться в майбутньому? Як так?

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

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

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


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

22

Особисто я прийняв правило трьох - яке я виправдаю, називаючи YAGNI . Якщо мені потрібно щось робити, колись я напишу цей код, двічі я можу просто скопіювати / вставити (так, я щойно допустив, щоб скопіювати / вставити!), Тому що мені це більше не знадобиться, але якщо мені потрібно зробити те саме Знову я знову рефактор і витягну цей шматок у свій власний метод, я продемонстрував собі, що мені це буде потрібно знову.

Я погоджуюся з тим, що сказали Карл Білефельдт та Роберт Харві, і моє тлумачення того, що вони говорять, полягає в тому, що головне правило - це читабельність. Якщо це полегшує читання мого коду, тоді подумайте про створення функції, майте на увазі такі речі, як DRY та SLAP . Якщо я можу уникнути зміни рівня абстракції у своїй функції, то мені здається, що в моїй голові легше керувати, тому не стрибати між функціями (якщо я не можу зрозуміти, що ця функція робить, просто прочитавши її ім'я), означає менше перемикачів у моїй функції психічні процеси.
Так само не потрібно перемикати контекст між функціями та вбудованим кодом, таким як print "Hi, Tom"працює для мене, в цьому випадку я можу отримати функцію PrintNames() iff решта моєї загальної функції були переважно функціональними дзвінками.


3
У вікі c2 є хороша сторінка у Правилі трьох: c2.com/cgi/wiki?RuleOfThree
pimlottc

13

Це рідко чітко, тому вам потрібно зважити варіанти:

  • Кінцевий термін (виправити спалення серверної кімнати якнайшвидше)
  • Читання коду (може впливати на вибір будь-яким способом)
  • Рівень абстракції спільної логіки (пов'язаний з вище)
  • Вимога повторного використання (тобто важлива точно така ж логіка, або просто зручна зараз)
  • Складність спільного використання (ось як світить пітон, див. Нижче)

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

Однак у Python досить легкою є повторна використання, а не повторення логіки. Більше, ніж у багатьох інших мовах (принаймні історично) ІМО.

Отже, подумайте просто повторно використовувати логіку локально, наприклад, створивши список з локальних аргументів:

def foo():
    for name_list in (vip_list, guest_list): # can be list of tuples, for many args
        for name in name_list:
            print name

Можна скористатися кортежем кортежів і розділити його безпосередньо на цикл for, якщо вам потрібно кілька аргументів:

def foo2():
    for header, name_list in (('vips': vip_list), ('people': guest_list)): 
        print header + ": "
        for name in name_list:
            print name

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

def foo():
    def print_name(name_list):
        for name in name_list:
            print name

    print_name(vip_list)
    print_name(guest_list)

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

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


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

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

@PatrickCollins: Дякую за голос! :) Я додав кілька міркувань, щоб зробити відповідь більш повною.
Макке

9

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

Отже, у вашому прикладі: Якщо ви припускаєте, що у вас стандартне привітання і ви хочете переконатися, що воно однакове для всіх людей, тоді напишіть

def std_greeting(name):
    print "Hi, " + name

for name in ["Tom", "Mary"]:
    std_greeting(name)   # even the function call should be written only once

В іншому випадку вам доведеться звернути увагу і змінити стандартне привітання в двох місцях, якщо воно трапиться на зміну.

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

print "Hi, Tom"
print "Hello, Mary"

З другого боку, залежно від двох факторів, залежно від того, скільки потрібно "упакувати" у функції, залежить:

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

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


Просто нерозумне підказки з огляду на ваш цикл - якщо насправді потрібно лише кілька чітко зашифрованих імен, щоб повторити, і ми не очікуємо, що вони зміниться, я б стверджував, що трохи зручніше не використовувати список. for name in "Tom", "Mary": std_greeting(name)
yoniLavi

Ну, щоб продовжити, можна сказати, що жорсткий зашифрований список не з’явиться посередині коду і все одно буде змінною :) Але правильно, я фактично забув, що ви можете пропустити дужки там.
Геренюк

5

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

  1. Необхідно змінити обидва вітання таким же чином, [наприклад , до Bonjour, Tomі Bonjour, Mary].

  2. Потрібно змінити одне привітання, а інше залишити таким, яким воно є [наприклад, Hi, Tomі Guten Tag, Mary].

  3. Потрібно змінити обидва привітання по-різному [наприклад, Hello, Tomі Howdy, Mary].

  4. Ні привітання ніколи не потрібно змінювати.

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

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


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

@RubberDuck: Я думаю, що надто мало уваги зазвичай приділяється питанню того, що слід чи не слід "відчужувати" - багато в чому, і можна було б очікувати, що дві найпоширеніші причини помилок (1) вважають, що це речі відчужуються / приєднуються, коли їх немає, або (2) щось змінюють, не розуміючи, що до нього додаються інші речі. Такі проблеми виникають при розробці структури коду та даних, але я не пам'ятаю, щоб коли-небудь багато уваги приділяв цій концепції. Як правило, псевдонім не має значення, коли є лише щось, або якщо воно ніколи не зміниться, але ...
supercat

@RubberDuck: ... якщо щось із двох є, і вони відповідають, важливо знати, чи зміна однієї повинна залишати значення іншої постійною, чи слід підтримувати відносини постійними (тобто значення іншого змінюється). Я часто бачу велику вагу, надану перевагам постійного зберігання значень , але набагато меншу вагу, надану важливості стосунків .
supercat

Я повністю згоден. Я просто розмірковував над тим, як звичайна мудрість говорить НАДУВАТИ це, але статистично є більша ймовірність, що повторний код пізніше не повториться кодом. У вас в основному шанси 2 на 1.
RubberDuck

@RubberDuck: Те, що я даю два сценарії, коли важлива відстороненість, і той, де краще вкладення, нічого не говорить про відносну ймовірність виникнення різних сценаріїв. Важливо визнати, коли два фрагменти коду збігаються «за збігом обставин» або тому, що вони мають однакове фундаментальне значення. Крім того, коли є більше двох предметів, виникають додаткові сценарії, коли один кінець, який хоче зробити одне або більше, відрізняється від інших, але все-таки змінити два і більше однаковим чином; також, навіть якщо дія використовується лише в одному місці ...
supercat

5

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

  1. абстракція
  2. модульність
  3. навігація кодом
  4. оновлення узгодженості
  5. рекурсія
  6. тестування

Абстракція

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

Модульність

Процедури можна використовувати для організації коду в модулі. Модулі часто можна обробляти окремо. Наприклад, побудований і випробуваний окремо.

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

Навігація за кодом

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

Оновити узгодженість

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

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

Рекурсія

Використання рекурсії вимагає створення процедур

Тестування

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

Висновок

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


4

Жоден із випадків, які ви надаєте, не здається рефакторним.

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

Я рекомендую завжди шукати способи виділення коду, де:

  1. Є ідентифіковане, змістовне завдання, яке ви могли б відокремити, і

  2. Або

    а. завдання складно виразити (з урахуванням доступних вам функцій) або

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

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

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

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

Однак найчастіше у вас буде суміш 2a та 2b. І вам доведеться використовувати ваше судження, беручи до уваги ділову цінність коду, частоту його використання, незалежно від того, чи добре це зафіксовано та зрозуміло, стан вашого тестового набору тощо.

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

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


3

Загалом я згоден з Робертом Харві, але хотів додати справу, щоб розділити функціонал на функції. Для поліпшення читабельності. Розглянемо випадок:

def doIt(smth,smthElse)
    for x in getDataFromSomething(smth,smthElse):
        if not check(x,smth):
            continue
        process(x,smthElse)
        store(x) 

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


2

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

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


2

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

Я був безжальним рефакторером і твердим віруючим в DRY ще до появи термінів. Переважно тому, що у мене проблеми із збереженням великої бази коду в голові, а почасти тому, що мені подобається кодування DRY, і мені нічого не подобається щодо кодування C&P, насправді це боляче і страшенно повільно для мене.

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

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

Menu m=new Menu("File");
MenuItem save=new MenuItem("Save")
save.addAction(saveAction); // I forget the syntax, but you get the idea
m.add(save);
MenuItem load=new MenuItem("Load")
load.addAction(loadAction)

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

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

class Menu {
    @MenuItem("File|Load")
    public void fileLoad(){...}
    @MenuItem("File|Save")
    public void fileSave(){...}
    @MenuItem("Edit|Copy")
    public void editCopy(){...}...

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

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

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

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

Тож мої моменти:

  • Копіювати та вставляти коштує більше часу, якщо ви не знаєте, як правильно перефактурувати.
  • Ви навчитесь добре рефакфікувати, роблячи це - наполягаючи на DRY навіть у найскладніших і тривіальних випадках.
  • Ви програміст, якщо вам потрібен невеликий інструмент, щоб зробити свій код ДУХОМИ, побудуйте його.

1

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


1

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

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

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

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


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

@pllee: Я представив причини, за якими факторинг на функції стає контрпродуктивним, коли його доводять до крайнощів (зауважте, що питання полягає саме в тому, щоб довести його до крайнощів, а не в загальному випадку.) Ви не запропонували проти аргументів цих питань, просто стверджувати, що "повинно" бути іншим. Дати , що проблема іменування «ймовірно» поганий знак не є аргументом , що це можна подолати в розглянутих тут випадках (мій аргумент, звичайно, є те , що воно є , по крайней мере попередження - що ви , можливо, досягли точки просто абстрагування заради себе.)
sdenham

Я згадав про 3 негативні думки, не впевнені, де я просто стверджую, що "має бути інакше". Все, що я говорю, - це те, що якщо ви не можете придумати ім’я, ваш інлайнер, ймовірно, робить занадто багато. Крім того, я думаю, що вам не вистачає великого сенсу щодо навіть крихітних колись використаних методів. Існують крихітні добре названі методи, тому вам не потрібно знати, що намагається зробити код (не потрібно стрибати IDE). Наприклад, навіть простий вислів `if (day == 0 || day == 6)` vs `if (isWeekend (day))` стає простішим для читання та розумового відображення. Тепер, якщо вам потрібно повторити, це твердження isWeekendстає не мозком.
pllee

@pllee Ви стверджуєте, що це "ймовірно поганий знак", якщо ви не можете знайти значно коротше, чіткіше ім'я майже для кожного повторного фрагмента коду, але це, здається, є справою віри - ви не надаєте жодних підтверджуючих аргументів (щоб зробити загальний У випадку, вам потрібно більше, ніж прикладів.) isWeekend має значущу назву, тому єдиний спосіб, коли він може не виконати моїх критеріїв, це якщо він ні значно коротший, ні значно чіткіший за його реалізацію. Стверджуючи, що ти думаєш, що це останнє, ти стверджуєш, що це не протилежний приклад моїй позиції (FWIW, я сам використовував isWeekend.)
sdenham

Я наводив приклад, коли навіть окремий рядок вираз може бути більш читабельним, і я дав негативи вкладеного коду. Це моя скромна думка, і все, що я кажу, і ні, я не сперечаюся або не кажу про "безіменний метод". Я не дуже впевнений, що настільки заплутане з цього приводу чи чому ви вважаєте, що мені потрібен "теоретичний доказ" для моєї думки. Я прикладом покращила читабельність. Технічне обслуговування складніше, тому що якщо змінити кодовий пакет, він повинен мати N кількість плям (читайте про DRY). Очевидно важче повторно використовувати, якщо ви не вважаєте, що скопіювати пасту, якщо життєздатний спосіб повторного використання коду.
pllee
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.