Як ви виправдовуєте написання ще коду, дотримуючись чистих практик кодування?


106

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

Я дотримувався деяких практик, рекомендованих у книзі "Чистий код" Роберта Мартіна, особливо тих, які стосуються типу програмного забезпечення, з яким я працюю, і тих, які мають для мене сенс (я не дотримуюся цього як догми) .

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

  • Інкапсуляція умовних умов

Тож замість

if(contact.email != null && contact.emails.contains('@')

Я міг написати такий маленький метод

private Boolean isEmailValid(String email){...}
  • Заміна вбудованого коментаря іншим приватним методом, так що ім'я методу описує себе, а не мати вбудований коментар поверх нього
  • Клас повинен мати лише одну причину зміни

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

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

Конкретне питання, на яке я шукаю відповідь:

Це прийнятний побічний продукт написання чистого коду? Якщо так, то які аргументи я можу використати для обґрунтування того, що було написано більше LOC?

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

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

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


98
Якщо ваша організація використовує лише LOC в якості метрики для ваших кодових баз, то виправдовувати чистий код не можна спочатку.
Кіліан Фот

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

29
Це не відповідь, а пункт, який слід зробити: Існує ціла підкомора щодо написання коду з якомога менше рядків / символів. codegolf.stackexchange.com Можна стверджувати, що більшість відповідей там не такі читабельні, як могли б бути.
Антитей

14
Вивчіть причини, що стоять за кожною найкращою практикою, а не лише самі правила. Дотримуватися правил без причин - вантажний культ. Кожне правило має свою причину.
Герман

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

Відповіді:


130

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

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

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

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


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

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

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

31
@JackAidley, я повністю не згоден з тобою. Тести полегшують зміну коду. Я визнаю, хоча той погано розроблений код, який занадто щільно пов'язаний і, таким чином, щільно пов'язаний з тестами, важко змінити, але добре структурований і добре перевірений код просто змінити в моєму досвіді.
Девід Арно

22
@JackAidley Ви можете багато чого рефакторувати, не змінюючи API чи інтерфейс. Це означає, що ви можете зійти з розуму, змінюючи код, не змінюючи жодного рядка в одиничному чи функціональному тесті. Тобто, якщо ваші тести не перевіряють конкретну реалізацію.
Ерік Думініл

155

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

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

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


10
набагато краща відповідь, ніж стомлююче "тестування одиниць вирішує всі ваші проблеми"
Дірк Бур,

13
Ця відповідь відповідає ключовому моменту, який дядько Боб та друзі, як видається, завжди пропускають - переробляння на маленькі методи допомагає лише, якщо вам не доведеться читати всі маленькі методи, щоб зрозуміти, що робить ваш код. Створення окремого класу для перевірки адрес електронної пошти є розумним. Втягування коду iterations < _maxIterationsв метод, який називається ShouldContinueToIterate, нерозумно .
BJ Майєрс

4
@DavidArno: "бути корисним"! = "Вирішує всі ваші проблеми"
Крістіан Хакл

2
@DavidArno: Коли хтось скаржиться на те, що люди мають на увазі, що тестування блоку "вирішує всі ваші проблеми", вони, очевидно, мають на увазі людей, які мають на увазі, що тестування підрозділу вирішує або принаймні сприяє вирішенню майже всіх проблем в інженерії програмного забезпечення. Я думаю, що ніхто не звинувачує когось у тому, що він запропонував тестування, як спосіб припинити війну, бідність та захворювання. Ще один спосіб сказати, що надзвичайне завищення одиничного тестування в багатьох відповідях, не тільки на це питання, але і на ІП загалом, піддається критиці.
Крістіан Хакл

2
Привіт @DavidArno, мій коментар явно був гіперболою, а не солом'яком;) Для мене це так: я запитую, як зафіксувати свою машину, а релігійні люди приїжджають і кажуть, що я повинен жити менш грішним життям. Теоретично щось варто обговорити, але це не дуже допомагає мені покращитись у фіксації автомобілів.
Дірк Бур

34

Білл Гейтс, як відомо, пояснював: "Вимірювання прогресу програмування за допомогою рядків коду - це як вимірювання прогресу будівництва літака за вагою".

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

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

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

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


1
Однак вага літака є важливою метрикою. І під час проектування очікувана вага слідкує уважно. Не як знак прогресу, а як обмеження. Рядки моніторингу коду підказують, що більше краще, тоді як в конструкції літаків менша вага - краще. Тому я думаю, що містер Гейтс міг обрати кращу ілюстрацію для своєї точки зору.
Джос

21
@jos в конкретній команді, з якою працює ОП, здається, що менше LOC вважається "кращим". Справа, яку робив Білл Гейтс, полягає в тому, що LOC не пов'язаний з прогресом жодним значущим чином, так само, як у вагомобудівництві вага не пов'язаний з прогресом змістовно. Літак, що будується, може мати 95% своєї кінцевої ваги порівняно швидко, але це була б просто порожня оболонка без систем управління, вона не на 95% повна. Те ж саме в програмному забезпеченні, якщо програма має 100k рядків коду, це не означає, що кожна 1000 рядків забезпечує 1% функціональності.
Містер Міндор

7
Моніторинг прогресу - важка робота, чи не так? Бідні менеджери.
Джос

@jos: у коді також краще мати менше рядків для тієї ж функціональності, якщо всі інші рівні.
RemcoGerlich

@jos Уважно читайте. Гейтс нічого не говорить про те, чи є вага важливим показником для самого літального апарату. Він каже, що вага - жахлива міра для прогресу будівництва літака. Зрештою, за допомогою цієї міри, як тільки весь корпус буде кинутий на землю, ви в основному зробите, оскільки це, ймовірно, становить 9x% від ваги всього літака.
Ву

23

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

Це питання втрати зору на фактичну мету.

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

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


Ведення журналів

Візьміть додаток (A), в якому немає реєстрації. Тепер створіть додаток B, яке є тим же додатком A, але з веденням журналу. У B завжди буде більше рядків коду, і, таким чином, вам потрібно написати більше коду.

Але багато часу заглибиться на розслідування проблем та помилок та з'ясування того, що пішло не так.

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

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

Це може бути лічені хвилини, години чи дні; залежно від розміру та складності бази даних.


Регресії

Візьміть додаток A, який зовсім не є ДУХОМ.
Візьміть додаток B, яке сухий, але в результаті потребують більше рядків через додаткові абстракції.

Подається запит на зміну, який вимагає зміни логіки.

Для програми B розробник змінює (унікальну, спільну) логіку відповідно до запиту на зміну.

Для програми A розробник повинен змінити всі екземпляри цієї логіки там, де він пам’ятає, що вона використовується.

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

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


Взаємозамінність розробника

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

Розробник А відсутній протягом місяця через свята. Подається запит на екстрену зміну. Не можна чекати ще три тижні, коли Dev A повернеться.

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

У той же час програма B (яку створив розробник B) має надзвичайну ситуацію. Dev B зайнятий, але Dev C доступний, хоча він не знає кодової бази. Що ми робимо?

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

Дев А повертається зі свого свята і бачить, що Б не зрозумів код, і тому погано його реалізував. Це не вина Б, оскільки він використовував усі доступні ресурси, вихідний код просто не був достатньо читабельним. Чи потрібно зараз витрачати час на встановлення читабельності коду?


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

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

Якщо так, то які аргументи я можу використати для обґрунтування того, що було написано більше LOC?

Моє роз'яснення goto запитує керівництво, що вони віддадуть перевагу: додаток із кодовою базою 100KLOC, який можна розробити за три місяці, або кодову базу коду 50KLOC, яку можна розробити за півроку.

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


23

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

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

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

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

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

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


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

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

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

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

2
@PeterMortensen: Це також можливо в C # та JavaScript. І це чудово! Але суть залишається, функція, навіть локальна функція, видно в більшому обсязі, ніж фрагмент вбудованого коду.
ЖакБ

9

Я зазначу, що в цьому немає нічого поганого:

if(contact.email != null && contact.email.contains('@')

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

У мене могли бути проблеми з цим дуже легко:

private Boolean isEmailValid(String email){
   return email != null && email.contains('@');
}

Кілька речей, на які я буду дивитися:

  1. Чому він приватний? Це схоже на потенційно корисну заглушку. Чи достатньо корисний приватний метод і немає шансів на його ширше використання?
  2. Я б не назвав метод IsValidEmail особисто, можливо, ContainsAtSign або LooksVaguelyLikeEmailAddress, оскільки він майже не має реальної перевірки, що, можливо, добре, можливо, не те, що виконується.
  3. Він використовується не раз?

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

З іншого боку, я бачив методи, які роблять щось подібне:

if (contact.email != null && contact.email.contains('@')) { ... }
else if (contact.email != null && contact.email.contains('@') && contact.email.contains("@mydomain.com")) { //headquarters email }
else if (contact.email != null && contact.email.contains('@') && (contact.email.contains("@news.mydomain.com") || contact.email.contains("@design.mydomain.com") ) { //internal contract teams }

Цей приклад, очевидно, НЕ СУХИЙ.

Або навіть саме це останнє твердження може дати ще один приклад:

if (contact.email != null && contact.email.contains('@') && (contact.email.contains("@news.mydomain.com") || contact.email.contains("@design.mydomain.com") )

Метою має бути зробити код більш читабельним:

if (LooksSortaLikeAnEmail(contact.Email)) { ... }
else if (LooksLikeFromHeadquarters(contact.Email)) { ... }
else if (LooksLikeInternalEmail(contact.Email)) { ... }

Інший сценарій:

У вас може бути такий метод, як:

public void SaveContact(Contact contact){
   if (contact.email != null && contact.email.contains('@'))
   {
       contacts.Add(contact);
       contacts.Save();
   }
}

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

Але коли хтось запитує "Чому" @ "збережено, тому що це не так!" і ви вирішите додати якусь фактичну перевірку, а потім витягніть її!

Ви будете раді, що зробили це, коли вам також доведеться зареєструвати другий обліковий запис електронної пошти президентів Pr3 $ sid3nt @ h0m3! @ Mydomain.com і вирішите просто все випробувати та спробувати підтримати RFC 2822.

Щодо читабельності:

// If there is an email property and it contains an @ sign then process
if (contact.email != null && contact.email.contains('@'))

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

// The UI passes '@' by default, the DBA's made this column non-nullable but 
// marketing is currently more concerned with other fields and '@' default is OK
if (contact.email != null && contact.email.contains('@'))

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

Підсумовуючи це: Не вимірюйте ці речі; Зосередьтеся на принципах, з яких побудований текст (DRY, SOLID, KISS).

// A valid class that does nothing
public class Nothing 
{

}

3
Whether the comments above an if statement or inside a tiny method is to me, pedantic.Це проблема "соломинка, яка зламала верблюду спину". Ви маєте рацію, що це одне не особливо важко читати прямо. Але якщо у вас є великий метод (наприклад , великий імпорт) , який має десятки цих дрібних оцінок, маючи ці інкапсульовані в читаних імен методів ( IsUserActive, GetAverageIncome, MustBeDeleted...) буде помітне поліпшення при читанні коду. Проблема з прикладом полягає в тому, що він спостерігає лише одну соломинку, а не весь пучок, який ламає верблюжою спину.
Flater

@Flater, і я сподіваюся, що це дух, який читач бере від цього.
AthomSfere

1
Ця «інкапсуляція» є антидіаграмою, і відповідь насправді це демонструє. Ми повертаємось до читання коду для налагодження та для розширення коду. В обох випадках важливим є розуміння того, що насправді робить код. Запуск кодового блоку if (contact.email != null && contact.email.contains('@'))- помилка. Якщо "if" - "false", більше нічого, якщо рядки можуть бути "true". Це зовсім не видно в LooksSortaLikeAnEmailблоці. Функція, що містить один рядок коду, не набагато краща за коментар, що пояснює, як працює лінія.
Вигадка

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

@quirk Я думаю, ви згодні з моєю загальною точкою? А з клеєм у вас йде зовсім інша проблема. Я фактично використовую карти коду під час перегляду нового коду команд. Страшно погано, що я бачив, коли я робив деякі великі методи, що викликають ряд великих методів навіть на рівні шаблону mvc.
AthomSfere

6

Чистий Кодекс - це відмінна книга, і її варто прочитати, але це не остаточний авторитет у таких питаннях.

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

Один з варіантів, коли не варто створювати абсолютно нову функцію, - це просто використовувати проміжну змінну:

boolean isEmailValid = (contact.email != null && contact.emails.contains('@');

if (isEmailValid) {
...

Це допомагає легко дотримуватися код без необхідності багато стрибати по файлу.

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


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

5

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

Рядки коду як метрики безглузді. Важливими питаннями в інженерії програмного забезпечення не є:

  • У вас занадто багато коду?
  • У вас занадто мало коду?

Важливі питання:

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

Я ніколи не думав перед ЛоК, коли писав код з будь-якою метою, крім Code Golf. Я запитав себе "Чи можна це написати більш лаконічно?", Але з метою читабельності, ремонтопридатності та ефективності, а не просто довжини.

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

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


3

На одному рівні вони праві - менше коду краще. Ще одну відповідь, цитуючи ворота, я вважаю за краще:

"Якщо налагодження - це процес видалення програмних помилок, програмування повинно бути процесом їх введення". - Edsger Dijkstra

"Під час налагодження новачки вставляють коригувальний код; експерти видаляють несправний код ". - Річард Паттіс

Найдешевші, найшвидші та найнадійніші компоненти - це ті, яких там немає. - Гордон Белл

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

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

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

Кінцева мета - мати читабельний код, але не зайвих додатків. Два принципи не повинні діяти один проти одного.

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


2

В існуючих відповідях є багато мудрості, але я хотів би додати ще один фактор: мова .

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

Наприклад, в Java може легко взяти 50 рядків, щоб написати новий клас з трьома властивостями, кожен з getter і setter, і один або кілька конструкторів - в той час як ви можете виконати саме те саме в одному рядку Kotlin * або Scala. (Ще більше економії , якщо ви також хотіли відповідні equals(), hashCode()і toString()методи.)

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

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

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

(* Це насправді не місце для штекера, але Котлін варто переглянути IMHO.)


1

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

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


Наступним підтвердженням того, що ваш код є злиттям Contactта Emailкласом, є те, що ви не зможете легко перевірити код перевірки електронної пошти. Для отримання коду перевірки електронної пошти у великому методі з правильними значеннями знадобиться багато маневрів. Метод див. Нижче.

private void LargeMethod() {
    //A lot of code which modifies a lot of values. You do all sorts of tricks here.
    //Code.
    //Code..
    //Code...

    //Email validation code becoming very difficult to test as it will be difficult to ensure 
    //that you have the right data till you reach here in the method
    ValidateEmail();

    //Another whole lot of code that modifies all sorts of values.
    //Extra work to preserve the result of ValidateEmail() for your asserts later.
}

З іншого боку, якби у вас був окремий клас електронної пошти із методом перевірки електронної пошти, то для блоку перевірки коду перевірки ви просто зробили б один простий дзвінок Email.Validation()зі своїми тестовими даними.


Зміст бонусу: розмова MFeather про глибоку синергію між довіреністю та хорошим дизайном.


1

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

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

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

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


1

Ви визначили дійсну угоду

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

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

Жадібний алгоритм може вийти з ладу в цих випадках

Складність також зробила код у певному сенсі менш читабельним, ніж більше. У попередньому завданні я розглядав HTTP API, який був дуже ретельно та точно структурований у кілька шарів, кожна кінцева точка визначається контролером, який перевіряє форму вхідного повідомлення та передає його якомусь менеджеру "бізнес-логічного рівня" , який потім зробив деякий запит деякого "рівня даних", який відповідав за внесення декількох запитів до деякого рівня "об'єкта доступу до даних", який відповідав за створення декількох делегатів SQL, які насправді відповіли б на ваше запитання. Перше, що я можу сказати про це: щось на зразок 90% коду було копіювально-вставним котлом, іншими словами, це не було. Тож у багатьох випадках читання будь-якого проходу коду було дуже "простим", оскільки "о, цей менеджер просто пересилає запит до цього об'єкта доступу до даних".багато контекстної комутації та пошуку файлів і намагаються відслідковувати інформацію, яку ви ніколи не мали б відслідковувати, "це називається X на цьому шарі, воно називається X" на цьому іншому шарі, тоді воно називається X '"в цьому іншому інший шар. "

Я думаю, що коли я припинив, цей простий API CRUD був на етапі, коли, якщо ви надрукували його по 30 рядків на сторінку, це займе 10-20 підручників з п'ятисот сторінок на полиці: це була ціла повторювана енциклопедія код. Щодо суттєвої складності, я не впевнений, що там було навіть половина підручника суттєвої складності; У нас було лише 5-6 діаграм баз даних, щоб обробити це. Внести будь-які незначні зміни до цього, це було завдання мамонта, навчившись це мамонта, додавання нової функціональності стало настільки болючим, що ми насправді мали файли шаблонів шаблонів, які ми використали для додання нової функціональності.

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

Найкраща практика: використовуйте DRY та довжину для здійснення дзвінка

(Примітка. Цей заголовок розділу дещо жартує; я часто кажу своїм друзям, що коли хтось каже "ми повинні робити X, оскільки найкращі практики говорять так ", вони 90% часу не говорять про щось на зразок ін'єкції SQL або хешування паролів або що завгодно - односторонні найкращі практики - і тому заява може бути переведена за те, що 90% часу "ми повинні робити X, бо я так кажу ". Начебто у них може бути стаття в блозі від якогось бізнесу, який зробив кращу роботу з X, а не з X, але, як правило, немає гарантії, що ваш бізнес схожий на цей бізнес, і, як правило, є якась інша стаття з іншого бізнесу, яка зробила кращу роботу з X ', а не X. Тому, будь ласка, не приймайте заголовок теж серйозно.)

Те, що я рекомендував би, ґрунтується на розмові Джека Дідеріха під назвою « Написати класи» (youtube.com) . У цій розмові він зазначає кілька чудових моментів: наприклад, те, що ви можете знати, що клас насправді є лише функцією, коли він має лише два загальнодоступних методу, і один з них - конструктор / ініціалізатор. Але в одному випадку він говорить про те, як гіпотетична бібліотека, яку він замінив на рядок для розмови, як "Muffin" оголосила власний клас "MuffinHash", який був підкласом вбудованого dictтипу, який має Python. Реалізація була абсолютно порожньою - хтось тільки подумав: "Можливо, нам доведеться пізніше додати спеціальну функціональність до словників Python; давайте введемо абстракцію прямо на всякий випадок".

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

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

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

  1. Refactor перевірка функції / методу, коли ви хочете скопіювати її та вставити. Можливо, є випадкові поважні причини копіювання та вставки, але ви завжди повинні відчувати себе брудно. Справжні автори не змушують вас читати велике довге речення 50 разів протягом усієї розповіді, якщо вони справді не намагаються висвітлити тему.
  2. Функція / метод в ідеалі має бути "абзацом". Більшість функцій мають становити близько половини сторінки або 1–15 рядків коду, і лише, можливо, 10% ваших функцій повинні бути дозволені до діапазону до половини сторінки, 45 рядків і більше. Отримавши 120+ рядків коду та коментарів, цю річ потрібно розбити на частини.
  3. Файл в ідеалі повинен бути "главою". Більшість файлів мають бути довжиною 12 сторінок або менше, тому 360 рядків коду та коментарів. Лише, можливо, 10% ваших файлів повинні мати доступ до 50 сторінок або 1500 рядків коду та коментарів.
  4. В ідеалі більшість вашого коду має бути з відступом базовою лінією функції або глибиною на один рівень. Виходячи з деяких евристик щодо вихідного дерева Linux, якщо ви релігійні щодо цього, лише, можливо, 10% вашого коду має бути з відступом 2 рівня або більше в межах базової лінії, менше 5% з відступом 3 рівня або більше. Це означає, зокрема, що речі, які потребують "обговорення" якоїсь іншої проблеми, як, наприклад, обробка помилок під час великої спроби / лову, повинні бути виведені з фактичної логіки.

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

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