Мій начальник просить мене припинити писати дрібні функції і робити все в одному циклі


209

Я прочитав книгу під назвою « Чистий код » Роберта К. Мартіна. У цій книзі я бачив багато методів очищення коду, наприклад, написання невеликих функцій, ретельний вибір імен і т. Д. Це, здається, найцікавіша книга про чистий код, який я читав. Однак сьогодні моєму начальнику не сподобалось те, як я написав код, прочитавши цю книгу.

Його аргументи були

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

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

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

Один із прикладів:

// The way I would write it
if (isApplicationInProduction(headers)) {
  phoneNumber = headers.resourceId;
} else {
  phoneNumber = DEV_PHONE_NUMBER;
}

function isApplicationInProduction(headers) {
   return _.has(headers, 'resourceId');
}

// The way he would write it
// Take the right resourceId if application is in production
phoneNumber = headers.resourceId ? headers.resourceId : DEV_PHONE_NUMBER;

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

Ну, я хотів би поради, який спосіб / практику краще написати чистий код?



4
phoneNumber = headers.resourceId?: DEV_PHONE_NUMBER;
Джошуа

10
Підтвердьте, що ви хочете працювати на місці, де керівництво сказало вам, ЯК робити свою роботу, а не ЩО потрібно вирішувати.
Костянтин Петрухнов

8
@rjmunro Якщо ви не любите свою роботу, майте на увазі, що розробників менше, ніж робочих місць. Отже, цитуйте Мартіна Фаулера: "Якщо ви не можете змінити свою організацію, змініть свою організацію!" і Боси, які розповідають мені про те, як кодувати, - це те, що я радив вам змінити.
Нільс ван Реймерсдал

10
НІКОЛИ не мають isApplicationInProduction()функції! У вас повинні бути тести, і тести марні, якщо ваш код поводиться на щось інше, ніж при виробництві. Це як свідомо мати перевірений / неприхований код у виробництві: це не має сенсу.
Ронан Пайшао

Відповіді:


215

Спершу беручи приклади коду. Ви надаєте перевагу:

if (isApplicationInProduction(headers)) {
  phoneNumber = headers.resourceId;
} else {
  phoneNumber = DEV_PHONE_NUMBER;
}

function isApplicationInProduction(headers) {
   return _.has(headers, 'resourceId');
}

І ваш начальник написав би це:

// Take the right resourceId if application is in production
phoneNumber = headers.resourceId ? headers.resourceId : DEV_PHONE_NUMBER;

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

Я б сказав, що оптимальний код знаходиться між двома:

phoneNumber = isApplicationInProduction(headers) ? headers.resourceId : DEV_PHONE_NUMBER;

function isApplicationInProduction(headers) {
   return _.has(headers, 'resourceId');
}

Це дає вам найкраще з обох світів: спрощений тестовий вираз та коментар замінено тестовим кодом.

Щодо поглядів вашого начальника на дизайн коду:

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

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

Покладіть все в основний великий цикл, навіть якщо основний цикл перевищує 300 рядків, його швидше читати

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

Пишіть лише невеликі функції, якщо вам доведеться дублювати код

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

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

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

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

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


26
Невеликі фукстуції легко перевірені
Mawg

13
Quoth @ ExpertBeginner1 : «Я втомився бачити тонни маленьких методів всюди в нашому коді, так що звідси вперед, є 15 LOC мінімум по всім методам.»
Грег Бекон

34
"Коментарі мають фундаментальний недолік: вони не компілюються / інтерпретуються, тому їх не можна перевірити одиницею" Граючи тут захисника диявола, це справедливо також, якщо ви заміните "коментарі" на "імена функцій".
mattecapu

11
@mattecapu, я прийму вашу адвокатську діяльність і подвоїть її прямо у вас. Будь-який старий розробник сміття може заробляти ваші коментарі, намагаючись описати, що робить код. Коротко описуючи цей фрагмент коду з хорошою назвою функції, має досвідчений комунікатор. Найкращі розробники - це кваліфіковані комунікатори, оскільки письмовий код в першу чергу стосується спілкування з іншими розробниками та компілятора як другорядної проблеми. Ерго, хороший розробник використовуватиме добре названі функції та не буде коментарів; бідні розробники ховають свої погані навички за виправданнями для використання коментарів.
Девід Арно

4
@DavidArno Усі функції мають до- та постумови, питання полягає в тому, чи документуєте ви їх чи ні. Якщо моя функція приймає параметр, який є відстань у вимірюваних футах, ви повинні подавати її в футах, а не в кілометрах. Це передумова.
Jørgen Fogh

223

Є й інші проблеми

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

phoneNumber = DEV_PHONE_NUMBER_WHICH_CAUSED_PROBLEMS_FOR_CUSTOMERS;

або

phoneNumber = DEV_PHONE_NUMBER_FROM_OTHER_COUNTRY;

Ви хочете додати ще більше гілок?

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

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

Ін'єкційна залежність

Ось як повинен виглядати ваш код:

phoneNumber = headers.resourceId;

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

Якщо ви хочете мати DEV_PHONE_NUMBER_FROM_OTHER_COUNTRYрезультат, вам слід це вкласти headers.resourceId. Один із способів зробити це - просто ввести інший headersоб’єкт для тестових випадків (вибачте, якщо це неправильний код, мої навички JavaScript трохи іржаві):

function foo(headers){
    phoneNumber = headers.resourceId;
}

// Creating the test case
foo({resourceId: DEV_PHONE_NUMBER_FROM_OTHER_COUNTRY});

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


11
Я розглядав цю саму тему у власній відповіді, але вважав, що це вже досить довго. Тож +1 вам за це :)
Девід Арно

5
@DavidArno Я збирався додати це як коментар до вашої відповіді, тому що питання все-таки було заблоковано, коли я його вперше прочитав, на моє здивування виявив, що воно знову відкрите, і тому додав це як відповідь. Можливо, слід додати, що є десятки фреймворків / інструментів для автоматизованого тестування. Тим більше, що у JS, здається, щодня виходить нове. Однак це може бути важко продати начальнику.
null

56
@DavidArno Можливо, ви мали б розділити свою відповідь на менші відповіді. ;)
крильгар

2
@ user949300 Використання побітових АБО не було б розумним;)
curiousdannii

1
@curiousdannii Так, помітили, що занадто пізно для редагування ...
user949300

59

На це немає «правильної» чи «неправильної» відповіді. Однак я запропоную свою думку на основі 36-річного досвіду проектування та розробки програмних систем ...

  1. Не існує такого поняття, як "код, який самодокументує". Чому? Тому що це твердження повністю суб'єктивне.
  2. Коментарі ніколи не бувають невдач. Що таке збій - це код, який взагалі неможливо зрозуміти без коментарів.
  3. 300 безперебійних рядків коду в одному кодовому блоці - кошмар технічного обслуговування і дуже схильний до помилок. Такий блок вкрай свідчить про поганий дизайн та планування.

Говорячи безпосередньо на прикладі, який ви надали ... Розміщення isApplicationInProduction()у власну рутину - це розумна справа. Сьогодні цей тест - це просто перевірка "заголовків" і може бути оброблений у потрійному ( ?:) операторі. Завтра тест може бути набагато складнішим. Крім того, "headers.resourceId" не має чіткого відношення до програми "у виробничому статусі;" Я заперечую, що тест на такий статус потрібно від'єднати від базових даних; підпрограма зробить це, а потрійний не буде. Крім того, корисним коментарем може стати те, чому resourceId є тестом на "виробництво".

Будьте обережні, щоб не перебирати за борт з "малими чітко названими функціями". Звичайна ідея повинна інкапсулювати ідею більше, ніж «просто код». Я підтримую пропозицію Амона phoneNumber = getPhoneNumber(headers)і додаю, що getPhoneNumber()слід зробити тест на "виробничий статус"isApplicationInProduction()


25
Є таке поняття, як хороші коментарі, які не є невдачею. Однак коментарі, які є майже дослівними кодом, які вони нібито пояснюють, або є просто порожніми блоками коментарів, що передують методу / класу / тощо. безумовно є невдачею.
jpmc26

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

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

7
@Timbo Ви маєте на увазі "... принаймні один з них невірний". ;)
jpmc26

5
@immibis Якщо ви не можете зрозуміти, що робить код без коментарів, код, ймовірно, недостатньо зрозумілий. Основна мета коментарів - з’ясувати, чому код робить те, що робить. Це кодер, який пояснює свою конструкцію майбутнім обслуговувачам. Код ніколи не може надати такого пояснення, тому коментарі заповнюють ці прогалини в розумінні.
Грехем

47

"Сутності не повинні примножуватися поза необхідністю".

- Бритва Оккама

Код повинен бути максимально простим. Клопи люблять ховатися між складністю, тому що їх важко помітити. То що робить код простим?

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

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

Але кожен виклик функції має когнітивний наклад : мені не просто потрібно розуміти код у моєму поточному фрагменті коду, я також повинен розуміти, як він взаємодіє з кодом зовні . Я думаю, що справедливо сказати, що функція, яку ви видобули, вносить більше складності у функцію, ніж вона витягує . Це те, що ваш начальник мав на увазі під « дрібними функціями [це] біль, тому що він змушує вас перейти до кожної невеликої функції, щоб побачити, що робить код» "

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

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

Дуже важливою метрикою є цикломатична складність (складність МакКейба). Він вимірює кількість незалежних шляхів через фрагмент коду. Це число зростає експоненціально з кожним умовним. Кожен умовний або цикл подвоює кількість шляхів. Існують докази того, що оцінка більшої ніж 10 балів є надто складною. Це означає, що дуже довга функція, яка, можливо, має бал 5, можливо, краще, ніж дуже коротка і щільна функція з балом 25. Ми можемо зменшити складність, витягнувши контрольний потік в окремі функції.

Ваш умовний приклад - це приклад складності, який можна отримати повністю:

function bigFatFunction(...) {
  ...
  phoneNumber = getPhoneNumber(headers);
  ...
}

...

function getPhoneNumber(headers) {
  return headers.resourceId ? headers.resourceId : DEV_PHONE_NUMBER;
}

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


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

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

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

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


Я генерал Мені сподобалась ваша відповідь. Я, однак, приймаю питання щодо міри циклічної складності mcabes. З того, що я бачив, це не представляє справжньої міри складності.
Роберт Барон

27

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

Не слухайте їх!

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

Автоматичні інструменти рефакторингу дозволяють легко, просто та безпечно витягувати методи. І будь ласка, не сприймайте людей серйозно, які рекомендують писати функції з> 300 рядків - такі люди, безумовно, не кваліфіковані, щоб розповісти, як слід кодувати.


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

10
@ArseniMourzenko: Не всім з нас доводиться прямувати перед своїм начальником. Я сподіваюся, що ОП достатньо стара, щоб знати, коли він повинен зробити правильно, або коли він повинен робити те, що говорить його начальник. І так, я не збирався детально на прикладі навмисно, є достатньо інших відповідей, які вже обговорювали ці деталі.
Док Браун

8
@DocBrown Погодився. 300 рядків сумнівні для цілого класу. Функція 300 ліній нецензурна.
JimmyJames

30
Я бачив багато класів, довжиною яких більше 300 рядків, які є абсолютно хорошими класами. Java настільки багатослівний, що майже не можеш зробити нічого значимого на уроці без такого великого коду. Отже, "кількість рядків коду в класі", сама по собі, не є значущою метрикою, більше ніж ми би вважали SLOC значущим показником для продуктивності програміста.
Роберт Харві

9
Крім того, я бачив поради мудреця дядька Боба, що так неправильно трактували та зловживали, що сумніваюся, що вона корисна для всіх, крім досвідчених програмістів.
Роберт Харві

23

У вашому випадку: Ви хочете номер телефону. Або очевидно, як ви отримаєте номер телефону, тоді ви пишете очевидний код. Або не очевидно, як ви отримали б номер телефону, тоді ви пишете метод для цього.

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

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

Не пишіть маленькі функції. Напишіть функції, які мають чітко визначене призначення та відповідають цьому чітко визначеному призначенню.

PS. Мені не подобається реалізація взагалі. Це передбачає, що відсутність номера телефону означає, що це версія для розробників. Тож якщо у виробництві телефонний номер відсутній, ви не тільки не впораєтеся з ним, але заміните випадковий номер телефону. Уявіть, що у вас є 10 000 клієнтів, а у 17 немає номера телефону, і у вас проблеми у виробництві. Незалежно від того, чи є ви у виробництві чи розробці, слід перевіряти безпосередньо, а не виходити з чогось іншого.


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

16

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

Кількість рядків не є корисною метрикою у більшості випадків.

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

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

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

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

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


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

15

Не кладіть все в один великий цикл, але також не робіть цього занадто часто:

function isApplicationInProduction(headers) {
   return _.has(headers, 'resourceId');
}

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

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

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


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

14

Здається, що ви насправді хочете, це таке:

phoneNumber = headers.resourceId || DEV_PHONE_NUMBER

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

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


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

14

Дозвольте сказати прямо: мені здається, що ваше оточення (мова / рамки / дизайн класу тощо) не дуже підходить для "чистого" коду. Ви змішуєте всі можливі речі в кількох рядках коду, які насправді не повинні бути близько один від одного. Який бізнес має одну функцію, знаючи, що це resourceId==undefозначає, що ви не на виробництві, що ви використовуєте номер телефону за замовчуванням у невиробничих системах, що resourceId зберігається в деяких "заголовках" тощо? Я припускаю, що headersце заголовки HTTP, тож ви навіть залишаєте рішення про те, в якому середовищі ви знаходитесь, кінцевому користувачеві?

Розбиття окремих фрагментів на функції не допоможе вам у вирішенні цієї основної проблеми.

Деякі ключові слова, які потрібно шукати:

  • розв'язка
  • згуртованості
  • ін'єкційна залежність

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

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

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


1
Я погоджуюсь на 100%, що тут є неявні компоненти, які слід розділити, але, не знаючи більше про мову / структуру, важко зрозуміти, чи є підхід ОО доцільним. Розв'язка та принцип єдиної відповідальності важливі в будь-якій мові, від чисто функціонального (наприклад, Haskell) до чистого імперативу (наприклад, C.) Моїм першим кроком - якщо начальник дозволить це - було б перетворення основної функції в функцію диспетчера ( як контур або зміст), які читали б декларативним стилем (описуючи політику, а не алгоритми) та обробляють роботу іншими функціями.
Девід Леппік

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

13

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


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

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

6

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

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

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

// NOTE "resourceId" not present in dev build, use test data
let isProduction = 'resourceId' in headers;
let phoneNumber = isProduction ? headers.resourceId : DEV_PHONE_NUMBER;

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


5

Приклад коду, який ви дали, ваш начальник ПРАВИЛЬНИЙ. Єдина чітка лінія в цьому випадку краще.

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

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


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

Відповіді були більш орієнтовані на переваги, я хотів також лише вказати на недоліки. Виклик такої функції, як sum (a, b), завжди буде дорожчим, ніж "a + b" (якщо компілятор не позначає цю функцію). Решта недоліків демонструють, що надскладність може призвести до власного набору проблем. Неправильний код - це поганий код, і лише тому, що його розбитий на менші байти (або зберігається в циклі 300 рядків) не означає, що його простіше ковтати.
Філ М

2

Я можу придумати щонайменше два аргументи на користь довгих функцій:

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

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

Існують також аргументи проти довгих функцій - тестування одиниць спадає на думку. Використовуйте t̶h̶e choosing choosingf̶o̶r̶c̶e̶ свій досвід при виборі між одним і іншим.

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


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


Коментуючи відповідь Девіда Арно :

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

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

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

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

Покладіть все в основний великий цикл, навіть якщо основний цикл перевищує 300 рядків, його швидше читати

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

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

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

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

(*) Принаймні, я чесний з цього приводу ;-)

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

Пишіть лише невеликі функції, якщо вам доведеться дублювати код

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

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

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

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

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

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


-1

На мою думку, правильний код потрібної вам функції:

phoneNumber = headers.resourceId || DEV_PHONE_NUMBER;

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

phoneNumber = getPhoneNumber(headers);

function getPhoneNumber(headers) {
  return headers.resourceId || DEV_PHONE_NUMBER
}

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

function isApplicationInProduction() {
  process.env.NODE_ENV === 'production';
}

Тоді ви можете отримати номер телефону за допомогою:

phoneNumber = isApplicationInProduction() ? headers.resourceId : DEV_PHONE_NUMBER;

-2

Просто коментар до двох пунктів кулі

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

Багато редакторів (наприклад, IntelliJ) дозволять вам перейти до функції / класу просто клавішею Ctrl-Клацання використання. Крім того, вам часто не потрібно знати деталі реалізації функції для читання коду, тим самим прискорюючи читання коду.

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

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