Огляд книжки «Чистий код» Роберта Мартіна

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

Книга має три розділи. Спочатку мовиться про загальні практики, рекомендації та підходи до написання коду. Далі йде величезний розділ (майже на 100 сторінок) про те, як автор «чистив» свій код. Доволі цікаво описує, як з одного варіанту коду він перейшов до більш «чистого». Незважаючи на те, що розділ займає десь третину книги, на його читання ви витратите набагато більше часу, ніж на решту. Стежити за ходом думок автора доволі цікаво і дивитися, як він використовує свої рекомендації на практиці, корисно, але якщо ви погано знаєте Java або вам у якийсь момент стане нудно — можете скіпнути цей розділ. Завершує книгу розділ про «запахи» та евристичні правила. Як на мене — це must have для читання у цій книзі. «Запахи» коду будуть дуже тісно переплітатися з рекомендаціями з першої частини, але прочитати їх буде корисно кожному.

Найважливіше з книги

Увага, далі будуть спойлери. Я виділив основне, але однаково дуже рекомендую вам прочитати книгу повністю.

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

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

Далі автор розповідає як краще працювати з іменами.

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

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

Мартін рекомендує обирати ОДНЕ слово для ОДНІЄЇ концепції. Складно читати код, у якому get, fetch та retrieve використовуються для однієї і тієї ж дії.

Надавайте імена в термінах рішення. Усі ми — програмісти і знаємо патерни, принципи , алгоритми. Коли ми бачимо UsersFabric-клас, то відразу розуміємо, для чого цей клас треба. Або AccountVisitor клас — з одного імені вже зрозуміло, що мовитиметься про патерн Відвідувач.

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

Намагайтеся уникати switch-оператора.

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

Причиною передачі тільки одного аргумента є або перевірка певної умови fileExists(‘MyFile’) або оброблення аргументу, його перетворення та повернення fileOpen(‘MyFile’).

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

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

Не повторюйтесь! Просто не повторюйте свій код. Бо це принесе в майбутньому тільки костилі та велосипеди, які не будуть їздити так, як вам треба.

Після цього ми читаємо про коментарі. Насправді є і гарні, і погані коментарі.

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

Поганими автор вважає все інше 😄. Це всілякі надлишкові коментарі, які не несуть сенсу, не є правдою, журнальні коменти, шум (очевидні речі: перед конструктором писати «це конструктор»). Також, якщо у вас в застосунку є закоментований кусок кода, скоріш за все, вам він не треба. Не бійтесь його видалити.

Далі йдеться про форматування. Форматування коду є дуже важливим, адже воно задає краще читання коду. Автор розповідає про концепції вертикального форматування: коли треба робити відстані між рядками, коли рядки треба «стискати», а також про горизонтальне форматування.

Наступний розділ про обʼєкти та структури даних. Тут мовиться про концепт абстракції та її застосування. Чим обʼєкт відрізняється від структури даних.

Ми з вами дізнаємось про те, що таке Закон Деметри: об’єкт повинен взаємодіяти лише зі своїми безпосередніми сусідами і не знати про внутрішню роботу інших об’єктів. І, звісно, про приклади його порушення: «аварія потяга» — коли ви викликаєте метод за методом, утворюючи ланцюжок зчеплених вагонеток.

Ознайомлення з концепцією DTO — обʼєкт передавання даних — класи з відкритими змінними і без функцій, а також з різновидом DTO — активні записи — Active Records — це ті ж DTO, але вони також мають навігаційни методи.

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

Починати писати код з Try-Catch-Finally тоді, коли цей метод буде повертати вийняток.

Не вертайте null! Повертаючи null, ми створюємо для себе зайву роботу, а для викличної сторони — зайві проблеми. Тільки пропустиш одну перевірку на null і все — краш. Повертати null погано, а передавати null під час виклику — ще гірше!

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

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

Також тут вводиться концепт «чистих» тестів. Тести повинні бути читабельними, в одному тесті — одна перевірка (або одна концепція), підтримання принципу FIRST (швидкий, незалежний, повторюваний, очевидний та своєчасний).

Далі автор веде до класів. Класи мають бути компактними та дотримуватися принципів єдиної відповідальності SRP. Також автор нагадує, що таке звʼязність класів та те, що вона повинна бути високою. Знаєте, в одній компанії, де я працював, на вході в туалет було написано правило, щоб кожен програміст його запамʼятав: Low Coupling, High Cohesion 😄. Модулі мають бути максимально незалежними від інших, щоб зміни модуля не сильно впливали на інші модулі. А висока звʼязність коду означає, що методи й змінні класу взаємозалежні та існують як єдине ціле.

Далі йдеться про проєктування системи. Тут ми дізнаємося, навіщо використовувати патерн Фабрика. Дізнаємось, що таке впровадження залежностей та інверсія контролю. Нас ознайомлять з тим, чому системний дизайн — це складно та надважливо. А також з тим, що «чистим» має бути не тільки код, а й архітектура.

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

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

Після цього йде розділ, в якому Мартін послідовно чистить свій код та описує, чому він робить ті чи інші речі. Автор трішки критикує JUnit, а опісля рефакторить клас SerialDate.

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

Розповсюджені «запахи»

А також згадані деякі розповсюджені «запахи», як-от:

  • нереалізація очевидної поведінки;
  • некоректна робота на межах функції (привіт, техніки тест-дизайну);
  • навмисне вимкнення засобів безпеки (копілятор лається не просто так, друже);
  • дублювання коду;
  • код на невірному рівні абстракції (коли ви запихуєте код туди, де він не потрібен);
  • базові класи, залежні від похідних;
  • мервий код (той, що не виконується. Видали його просто й усе);
  • вертикальний розподіл. Змінні та функції слід визначати неподалік від місця їхнього використання;
  • непослідовність. Якщо обрали якусь схему чи позначення — дотримуйтесь цього;
  • баласт. Навіщо ви тримаєте змінні, які не використовуються, методи, які не викликаються, коментарі, які не мають сенсу?
  • штучні привʼязки. Те, що не залежить одне від одного, не треба штучно привʼязувати;
  • аргумент-селектор. Краще мати дві функції, ніж одну, у яку передаєте булеву змінну, яка впливає на результат виклику функції;
  • неправильне розміщення коду. Думайте перед тим, як писати, де саме місце вашому майбутньому коду. Де його буде розмістити більш логічно, а не зручніше, щоб він був «під рукою»;
  • нерозуміння алгоритму. Дуже багато дивного коду пишеться через нерозуміння базових алгоритмів;
  • надмірне використання if/else або switch там, де краще використовувати поліморфізм;
  • конвенції коду. Просто прочитайте та розберіться в них — це справді важливо;
  • ніколи не використовуйте «магічних чисел». У житті щось може змінитися, щось додатися, потім мине кілька місяців, і якщо ви не будете дбати про «магічні числа», то рефакторити це потім буде просто жахіття;
  • інкапсулювати умовні конструкції: if(shouldBeDeleted(timer)) краще, ніж if(time.expired() && !timer.recurrent()));
  • уникайте негативних умов: if(buffer.shouldCompact()) краще, ніж if(!buffer.shouldNotCompact());
  • функція має виконувати лише одну операцію;
  • уникайте транзитивних зверненнь (якраз про закон Деметри).

Про тести:

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

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

Якщо сподобалось, буду радий бачити вас в телеграм-блозі або на ютубчику

👍ПодобаєтьсяСподобалось26
До обраногоВ обраному15
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

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

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

На жаль неадекватний переклад це дуже поширене явище як для художньої так і для технічної літератури

Це не догма, а всього на всього поради.

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

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

Ознайомлення з концепцією DTO — обʼєкт передавання даних — класи з відкритими змінними і без функцій, а також з різновидом DTO — активні записи — Active Records — це ті ж DTO, але вони також мають навігаційни методи.

Active Records це взагалі антіпатерн про який треба як най скоріше забути.

Починати писати код з Try-Catch-Finally тоді, коли цей метод буде повертати вийняток.

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

нерозуміння алгоритму. Дуже багато дивного коду пишеться через нерозуміння базових алгоритмів;

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

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

В agile проекті вимоги можуть постійно мінятися і початкова схема може змінитися 100500 разів.

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

Ага це дуже очевидно коли люба функція/метод може викинути любий exception. Монада Either/Result набагато очевидніше.

Все вірно. Я так в кінці огляду і писав що

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

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

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

очередной тестировщик решил всех поучить программированию...

А ви такий токсичний через психологічні проблеми чи розумові?

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

Вашу позицію зрозумів. Скажіть, як давно ви були у психолога і спілкувались про ваші проблеми? Чи обговорювали ви це ще з кимось? Як давно ви помітили що всі навколо неправі і ви молодець єдиний?
у відпуску можно ще сходити, доречі, та здати аналізи на вітаміни. Кажуть токсичність ще буває через депресію часто. А депресію треба лікувати. Лікуйтесь. бажаю швидкого одужання.

Думав пройти мимо, але тон яким Ви спілкуєтесь не дозволив. Мені здається ми живемо в доволі вільному світі, якщо можна так сказати, і погляд на одні і ті самі речі може бути в кожного різним, але щоб так спілкуватися, це треба бути рідкісним мудаком. Я так розумію Ви завзятий читач «Хорошого ІТ», бо там таких повно. Як можна глянути, ще один всезнаючий архітектор в голові в якого є тільки його думка і нічия інша. Так от всезнаючий архітекторе, у світі дорослих людей — ти якщо маєш щось розумне і конструктивне сказати то говориш, а якщо не маєш то просто мовчиш, щоб ніхто не зрозумів який ти «всезнаючий».

Думав пройти мимо, але тон яким Ви спілкуєтесь не дозволив

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

Я так розумію Ви завзятий читач "Хорошого ІТ"

нет

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

с чего ты это взял?

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

ну так попроси своего дружка не выражать свое мнение с компетенцией в программировании ниже чем у таксиста, а 50 долларов можно заработать более простым способом чем от компании за статью на доу

А ти все ніяк не заспокоїшся))) тобі хтось плате за токсичні коментарі та за токсичність?
До психолога записався вже? :)

Судячі з коментарів — розробники не люблять патерни 😅

Скоріше вони принести їм багато болю. Це як героїн: спочатку кайф, але потім не знаєш як злізти.

Намагайтеся уникати switch-оператора.

Чому? Що в ньому поганого?
Якщо це огляд книги, то авто міг би дописати щось і від себе. Якщо просто викласти переклад то це не «огляд книги».

світ по можливості краще заміняти поліморфізмом але без фанатизма :D ну і також може порушувати принцип open-closed . як приклад можете читнути ось тут sourcemaking.com/...​itional-with-polymorphism

свіч — чудова і архікорисна конструкція, викидувати її з мови це дурість.

Є тільки 2 правила — пам’ятати про break і завжди реалізовувати default.

ви дивились посилання що я скинув? і чому саме в тому випадку краще поліфморфізм аніж світч ? хіба там сказано ВИКИНУТИ світч взагалі? чому люди коли читають шось сприймають все радикально? :D

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

А щодо самого свіча — в його оригінальному вигляді він корявий. Якщо для якоїсь конструкції мови виникає формулювання «використовуйте, але пам’ятайте про...», то це поганий знак, бо обов’язково знайдуться ті, хто або не буде пам’ятати, або через банальну спішку вчасно не згадають. Я писав статтю на тему поганих рішень в мовах, і про свіч там є — blog.devgenius.io/...​anguages-32487008796#9db1. Але знов наголошу, що проблема не в свічі як такому, а деяких його особливостях, які не в усіх сучасних мовах виправили.

Це якщо мова підтримує класи. Є алгебраїчні типи даних, де нема наслідування, по суті сума типів як раз умовами розрулюється (Haskell, частково Rust) і цей підхід має своїх прихильників. Це не кажучи про те, що у C# додали паттерн матчінг.

світ по можливості краще заміняти поліморфізмом але без фанатизма

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

ну і також може порушувати принцип open-closed

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

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

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

компілюєш, дивишся де скаржиться компілятор

та ніде він не буде скаржитися, бо скоріш за все скрізь натикані default кейси

коли наслідувати крилату ракету від літака

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

та ніде він не буде скаржитися, бо скоріш за все скрізь натикані default кейси

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

Тоді треба обізвати світч стейт-машиною та винести в окремий клас.

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

Дякую за статтю.
Що таке Agile, а саме його кореневий сенс, відкрив для мене саме Роберт Мартін. Хто не читав «Clean Agile» (можна і не купувати), ніде не знайде відповідь що таке справжній Agile для програміста. Дам лише натяк: «Це алгоритм....»

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

Не можу зрозуміти, який сенс у такому пості?
Таких оглядів в Інтернеті вагон і маленький візок, «Clean Code» — дуже популярна книга.
Ось якби автор, написав як він ВИКОРИСТОВУВАВ поради з цієї книги у своїх проектах, то це було цікаво, тому що досвід кожної людини унікальний.
А так це схоже на переказ документального фільму.

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

Якщо почати вживати всі ці рекомендації то буде стадартний Golang код.
Golang зроблений так, щоб по іншому писати було важко. Тому там код найчистіший

Якщо так, то чому люди досі вивчають та використовують інші мови, а в рейтингу мов програмування на 2023 рік на DOU Go займає 11-місце зі скромними 1.9 %?

Сподіваюсь це сарказм :)

Golang зроблений так, щоб по іншому писати було важко.

Вся «Clean code» — самопротирічна безграмотна нісенітниця. Оглядів цього достатньо в інтернеті, можна згадати хоча б це або це.

Роберт Мартін — «інфоциган», що продає софт-скілли під виглядом хард-скіллів. Те ж стосується TDD і «банди чотирьох» з їх 25 патернами. Їх ідеї гарні самі по собі, але випʼячування саме їх і в такому вигляді, як це роблять Мартін або GoF, не має жодного сенсу, крім створення шару термінології, якою можна жонглювати для досягнення своїх цілей в корпоративному середовищі.

Я здивований підписом автора статті. Я б ще зрозумів таку статтю від якогось PМа зі стажем над групою джунів, якому до дупи, що розробляти — бухгалтерію чи мобільний застосунок для шейпінгу. Але при підпису «Head of QA Department» незрозуміло, який звʼязок QA з темою.

Як конструктив рекомендую почати хоча б з «Code complete» і принципів GRASP замість SOLID. В них теж є спірні твердження, але нема такого, що корисні речі треба викопувати з-під товстого шару сміття, як вся «творчість» Мартіна.

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

доволі цікава думка — а можете більш детально розвивинути чому ви думаєте що патрени GoF — це інфоциганство?

п.с. відповім на питання

який звʼязок QA з темою

дуже жаль, що вам не пощастило працювати з автотестувальниками, QA та SDETами але тим не менш. робота QA/TAE/SDET це написання фреймворків для тестування та тестів на різних рівнях -юніт, інтеграційному та e2e, написання сервісів-заглушок, написання бібліотек для тестування, написання генераторів тестових даних, тощо. Без знання як це зробити достатньо масштабовано, гнучко, читабельно, розширяємо — рішення будуть виходити доволі осередкові.

так той, принципи GRASP в книгах Роберта Мартіна так само розглядаються і рекомендуються — навіть уц цьому огляді про них згадується -low coupling and hight cohesion, також використання поліморфізма :D ви точно читали ці книжки що критикуєте Роберта так яро? :D бо виглядає шо просто накидуєте на вентілятор :D

а можете більш детально розвивинути чому ви думаєте що патрени GoF — це інфоциганство?

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

дуже жаль, що вам не пощастило працювати з автотестувальниками

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

так той, принципи GRASP в книгах Роберта Мартіна так само розглядаються і рекомендуються

Але сховані під шаром SOLID-подібного сміття (хоча і він постійно порушується).

ви точно читали ці книжки що критикуєте Роберта так яро?

Читав.

а які на вашу думку патерни не є ідеальними? або які не можна використовувати у програмуванні?

цілі QA не мають проєцуватись напряму на конкретні рішення дизайну коду.

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

ну насправді так і є — принципи солід можуть конфліктувати з патернами гоф. той же патрен сінглтон -він є порушенням майже всіх принципів солід. але тим не менш інколи без нього ніяк :) в житті завжди треба шукати баланс :D

а які на вашу думку патерни не є ідеальними?

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

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

Деталі — наприклад, кто хоч раз бачив Builder? Я бачив, але не External, як у GoF, а Internal і Builder Context. External — жодного разу за майже 30 років у галузі. Чому вони вибрали саме цей варіант?

Зазвичай архітектурний вибір рішення проходить в інших місцях. Які ми бачимо спори? Чи не найбільше всього — анемічна чи багата (насичена?) доменна модель. Потім — фреймворки і мови.

То, може, книга GoF має якусь дидактичну цінність, як ті сортування, які ми всі вчимо бо це легко навчити алгоритмам на масиві, але потім 99% забуває? Може, і так, але знову — хто довів цінність вибору саме цих 25 патернів? Де порівняння?

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

По пунктах. Саме для кода тестування, не цільового коду.

ООП? Хіба що для «вбудування» у фреймворки типу pytest, і тільки тому, що вони так написані. Замість цього могло б бути написання на шаблонній мові з автогенерацією з неї (як і робиться в деяких BDD-фреймворках).

SOLID? Скільки випадків де реально треба подумати про S і це за межами звичного визначення здорового глузду? Де O в автотестуванні? Де L за межами сидіння в ООП бо туди вже засунули? Чи багато реального D в тестових фреймворках? До біса дверцяти.

GRASP в цілому? Найбазовіші принципи на зразок Information Expert — да. Але я б подивився на того, хто спробував би зробити інакше.

Скільки яких реально патернів з GoF набору ви бачите у автотестах? Де Adapter? Де Bridge? Реально хоч один випадок Interpreter? Куди всунути Command? Де Visitor? Про що ви взагалі?

чи ви про щось інше кажете?

Саме так.

90+% роботи в автотестуванні це вибір, що має бути в тесті, а що — не має. Треба думати про зовсім інші матерії.

ви написали що

цей набір 25 патернів є, мʼяко кажучи, не ідеальним.

от мені цікаво які на вашу думку кращі за ці 25 якщо ви їх так не полюбляєте

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

90+% роботи в автотестуванні це вибір, що має бути в тесті, а що — не має

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

п.с. стосовно

Замість цього могло б бути написання на шаблонній мові з автогенерацією з неї

чи писали ви тести в форматі BDD? чи помітили ви використання патернів GoF в BDD? чи помітили ви НЕДОЛІКИ використання такого підходу? чи знаєте ви межі коли його можна використовувати а коли це зайвий прошарок абстракції? :) питання риторичне

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

Варіант не використовувати патерн не розглядається?

цитата

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

якщо доречність використання = 0 то використовувати не треба :) ізі катка :)

брідж може бути використан
інтерпретатор може бути використан
адаптер може бути використан
команд може бути використан

Це реальні випадки чи уява про те, що «если кто-то кое-где у нас порой» © може щось використати?
Здається, «питання риторичне» ([Артур Шев]) :)

треба піклуватись про інфрастуктуру в якій ви запускаєте тести, піклуватись про фреймворк

Це ніяк не протирічить тому, що я написав.

чи писали ви тести в форматі BDD?

Писав. Це була побочна робота, але була. У простих випадках — нічого складного і дивного, якщо мова зафіксована, немає.

чи помітили ви використання патернів GoF в BDD?

Ні, бо вони не використовувались. Сам фреймворк не враховую, але про нього особлива мова (PropEr і власна надбудова над ним).

питання риторичне

Ви повністю промазали зі свою згадкою :))

приклади реальні :)
вашу точку зору зрозумів, питань більше не маю :)

100%. я саме це і пишу у коменті вище :)

враховуючи всі фактори треба приймати рішення щодо доречності використання того чи іншого патерна.
якщо доречність використання = 0 то використовувати не треба :) ізі катка :)

Більшість патернів є у десктопних аплікухах, наприклад якщо писати плагіни мов чи підтримки фреймворків в Eclipse або Idea. Мабуть автори прийшли з десктоп програмування :)
Зважайте на те, що цій книжці більше 20 років.

якщо писати плагіни мов чи підтримки фреймворків в Eclipse або Idea

Це надто специфічні випадки.

Зважайте на те, що цій книжці більше 20 років.

Скоріше, тут специфіка орієнтації авторів.

яким боком забезпечення якості до тестування? самим я би сказав прямим :D це як спитати яким боком девелопмент до написання коду :D

ну взагалі-то юніт тест — це зона респонсібіліті девів. і невідʼємна частина їх сучасної роботи. В ідеальному світі єдинорогів взагалі ТДД.

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

компанії типу MANGA з вами би не погодились :) в більшості компаній і проєктів — так, юніт тести лежать на девах, але якшо мова розробки спільна з мовою яку знає автоматизатор то це дуже дає буст у покритті тестами вашого продукту

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

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

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

А з приводу ГоФ — не згоден. Класифікація найбільш загальних патернів дуже непогана, і сформульована в практичному стилі «вам треба зробити ось таке при таких-от умовах — використовуйте такий паттерн». Хочу також звернути увагу, що всі описані патерни не дуже прив’язані до прикладної специфіки задачі: пишете ви взаємодію з базою, ЮАй, рендерінг репортів чи гру — майже будь-який з них може стати в нагоді. Хіба що Interpreter вибивається своєю специфічністю, я б його в цей ряд не додавав. Ще можна поскаржитись на Flyweight та Prototype що це непотрібна екзотика. Також можна сказати що Builder сформульований в переускладненому вигляді. Але все інше зустрічаеться регулярно — щось рідше, щось частіше. Для книги написаної в 1994 році так зовсім успіх.

Що дійсно напрягає, так це карго-культ, який на базі таких книг виникає: штучне розрізання коду на мільярд функцій (бо дядя Боб так казав робити), створення 100500 класів для простої задачі (бо інакше «порушиш сінгл респонсібіліті»), всі ці намагання використати якомога більше патернів на сторінку коду, питання на співбесідах «назви три категорії паттернів», «а які біхевіорал паттернс ти знаєш», і т.д. Але то далеко не завжди проблема книги :).

А з приводу ГоФ — не згоден. Класифікація найбільш загальних патернів дуже непогана, і сформульована в практичному стилі «вам треба зробити ось таке при таких-от умовах — використовуйте такий паттерн»

Проблема не в класифікації, а в відборі. Чому саме ці 25 серед сотень.

Але все інше зустрічаеться регулярно — щось рідше, щось частіше.

Що у вас за робота, що вони регулярно зустрічаються?
І коли зустрічався, наприклад, Visitor?

Що дійсно напрягає, так це карго-культ, який на базі таких книг виникає

...

Але то далеко не завжди проблема книги :).

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

Проблема не в класифікації, а в відборі. Чому саме ці 25 серед сотень.

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

Що у вас за робота, що вони регулярно зустрічаються?
І коли зустрічався, наприклад, Visitor?

В будь-якому проекті, де доводиться парсити кастомні вирази, Visitor з’являється. Такі проекти в мене були. Також пару разів доводилося кастомізувати поведінку LINQ виразів при взаємодії з БД, там для цього є клас ExpressionVisitor. Ще був проект, де користувачі конфігурували бізнес-процеси за допомогою діаграм — там здається теж використовували при візуалізації поточного стану процесу.

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

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

Для мене є принципова різниця в подачі «дяді Боба» і GoF. У випадку Clean Code, він досить категорично напирає «робіть саме так і буде вам щастя». GoF дає книгу рецептів із серії «ось для такого кейсу можна використати ось це», а на мою думку, книга рецептів сама по собі карго-культ не породжує.

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

Clean code — нормальна книжка на 4 з 5 балів, особливо на момент її виходу. В ній досить багато корисних порад, що й вказано в обох відгуках. Також другий відгук на російський переклад, який додав проблем порівняно з англійським оригіналом. Тому читати треба в оригіналі. Цікаво чи вийде 2, 3, ... видання з доробками :)

«...якщо чесно, тут доволі поверхнево описуються ці два принципа»
:)
Дякую автору за корисний огляд. Сподіваюсь, він дозволить додати посилання на курс Роберта Мартіна, в якому поглиблено розглядаються SOLID принципи та багато інших цікавих речей ;)
Clean Code Fundamentals by Robert C. Martin
www.oreilly.com/...​ndamentals/9780134661742

да, доречі у нього є на ютубчику ціла серія безкоштовних відосів можна знайти тут в секції Talks cleancoder.com/products

«автор не рекомендує префікс I до інтерфейсів»
Так, тому що сучасна IDE може підказати, чи є тип класом або інтерфейсом. Досить складно розшифровувати hungarian notation, легше сприймаються звичайні слова і змістовні фрази.

«...а також з різновидом DTO — активні записи — Active Records — це ті ж DTO, але вони також мають навігаційни методи.»
Ні, це різні патерни для вирішення різних задач.
DTO для передачі даних, Active Record для доступу до запису в БД та ORM.
«This pattern is commonly used by object persistence tools and in object—relational mapping (ORM).» en.wikipedia.org/...​iki/Active_record_pattern

хм. цкава думка. Роберт Вважає їх саме підвидом ДТО(та і я теж в принципі) ось вставлю цитату з книжки сторінка 101:

thixalongmy.haugiang.gov.vn/...​media/1175/clean_code.pdf

Active Record
Active Records are special forms of DTOs. They are data structures with public (or beanaccessed) variables; but they typically have navigational methods like save and find. Typically these Active Records are direct translations from database tables, or other data
sources

Не погоджуюсь. Класи сутностей JPA (entity classes) реалізують патерн Active Record на рівні репозиторія. Основна задача — відображення реляційної моделі на об’єктну. DTO використовуються для передачі даних від сервіса контролеру і далі клієнту. Можуть мати багато різновидів навіть для однієї сутності в залежності від характеру запиту. Це різні задачі, які вирішуються в різних частинах застосунку. Навіть якщо структури даних зовні схожі, це не є підставою вважати Active Record різновидом DTO.

інкапсулювати умовні конструкції: if(shouldBeDeleted(timer)) краще, ніж if(time.expired() && !timer.recurrent()));

А тестувати це як? Ну, напишу я натомість timer.expired() || timer.recurrent().
Як потім

якщо знайшли баг у функції — напишіть тест на це;

...?
Дивують ці переходи від наче ООП на процедурний стиль.

Але добре, що автор принаймі реагує на статтю, на відміну від колеги.

о!дуже класне питання як тестувати та навіщо саме так робити як радить автор.
відповім питанням яке допоможе у відповіді — а як вважаєш шо легче тестувати дві маленькі функції чи одну спагетті-функцію з іфами і вкладеностями? чи знайомий ти з поняттям цикломатична складність фуцнкції? і як її зменшувати? :)
відповіді на ці питання як раз і будуть служити рекомендаціями авторів(не тільки дядки Боба) до того чому саме так краще писати а не інакше :)
але знову ж таки, НЕ ЗАВЖДИ ))) бо є контекст проєкта, мови, задачі, тощо

а як вважаєш шо легче тестувати дві маленькі функції

У мене, як то кажуть, ООП композиція головного мозоку.
Я пишу (і закликаю писати) timer.shoudBeDeleted()

Якщо чужий final class, то

class MyTimer implements Timer {
    @Delegate
    private final Timer timer;

    boolean shouldBeDeleted() {
          return time.expired() && !timer.recurrent());  // може правильно, а може і ні. В тестах розберемось
    }
}

Тепер ти, будь ласка, розкажи, як тестувати

цикломатичну складність функції

shouldBeDeleted(timer)?

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

давай я поясню трішки моїх думок. як на мене поінт в тому що коли в тебе

...код.. код ... код... потім

if(time.expired() && !timer.recurrent()))

і після знову ...код ...код.... код.
— то ти тестуєш функцію вищого рівня. і тобі треба придумувати умови тестування самої функції ПЛЮС того шо в ній знаходиться(коду шо знаходиться в іф блоці — оця фігня з умовою коли таймер ексайпер і таймер не рекарент) .
яка може бути проблема тут? що методи таймера ексайред і рекарент працюють не так як ти очікуєш і агрегований результат виконання буде не тим шо ти очікуєш.
натомість автор пропонує винести умову в if в окрему функцію. що це тобі дасть?
по-перше — читабельність. коли ти читаєш складну умову в в if — подекуди с першого разу складно зрозуміти шо мав автор цього куска коду і набагато простіше якшо буде одразу пояснююча назва функції — не важливо це буде shouldBeDeleted(timer) чи timer.shoudBeDeleted() — з самої назви вже будуть очікування до того що відбуваєтсья в цьому куску кода і які очікування від його виконання.
по-друге, декомпозувавши таким чином — тестувати буде легше тому шо у тебе окремо вже будуть тести на перевірку самого кондішена if(твоєї нової функції коли вона вертає тру а коли фолс), і окремо перевірки на зовнішню функцію при цьому вже не думаючи про внутрішню реалізацію блоків іф.
ну а потім коли зʼвяляється більш легкий запис то уже може і прийти ідея як знизити цикломатичну складність фцункції зовнішньої шляхов введення або поліморфізму, або використовуючи патерн стратегію як приклад.

але я ще раз наголошу — ДУЖЕ ВСЕ ЗАЛЕЖИТЬ ВІД КОНТЕКСТУ ЗАДАЧІ.
чисто візуально — читати shouldBeDeleted(timer) чи timer.shoudBeDeleted() зручніше ніж
time.expired() && !timer.recurrent()

Капець, скільки слів замість коду!

по-перше — читабельність

Так, читабельність дасть. Для цього достатньо  boolean shouldBeDeleted = timer.expired() && !timer.recurrent() написати, але проблему це не вирішить.

тести на перевірку самого кондішена if(твоєї нової функції коли вона вертає тру а коли фолс)

Ну? І де ці тести? Як вони будуть виглядати? От я в цих закарлючках &&, ||, ! помилився і написав

boolean shouldBeDeleted() {
          return timer.expired() || timer.recurrent();  
}
і агрегований результат виконання буде не тим шо ти очікуєш.

Як цю приватну функцію тестувати? Вона не приватна? Ще цікавіше.

P.S. У книзі було щось на тему, що об’єкт у скоупі одного методу смикати більше одного разу — це «запах»?

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

А ще почитайте Code Complete, там про тести ще краще. Особливо оце:

«В тестах теж можуть бути помилки»

ухти! дякую . ще не читав. оця я так розумію? www.amazon.com/...​onstruction/dp/0735619670

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

прикольно! дякую за рекомендацію. додав у списочок собі після xUnit patterns читну

Авторе, хоч би через ChatGPT прогнав, читати неможливо.

вам цей комент теж ChatGPT допоміг написати? :D

а чого так категорично? :)

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

Також не читав. Книги це взагалі доволі специфічна література не для всіх. Купу авторської нудятини та води. Засипаєш через 2 хвилини читання.

Не бійтесь використовувати винятки замість повернення кодів помилок.

Про що тут йдеться?

Невже на доу битимуть, якщо вставити лінку на книжку, про яку йде мова?

прям в назві ж є ))) вона на ринку одна ))

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

Просто треба зрозуміти, що це суб’єктивна точка зору автора, у якого експертиза це розробка бізнес-рішень на Java. Наприклад, null може бути відсутнім у деяких мовах (Rust, Haskell). В деяких взагалі немає ООП, в embedded часто змістовне ім’я дати неможливо, бо треба писати коротеньке оповідання, ...

100% кожну рекомендацію слід розглядати враховуючи контекст мови, проєкту та задачі

Колись і я напишу свою книгу «Чистий код». Тут така ж схема як і для Скраму (прочитав гайд і пишеш книгу в стилі «я художник — я так бачу»)

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

— один тест = одна перевірка. Теж не завжди підходить. Для юніт-тестів можливо, для UI системних тестів — майже ніколи, занадто довго і дорого вийде.

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

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

— Хороший код это тот, который зарабатывает деньги. Плохой тот, который не выстрелил.

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

загальні запахи? :)

не підібрав іншого слова) ну тіпа загальні, common , general )) є варіант як краще написати? :)

розповсюджені

я мав на увазі «запахи», я не розумію причому вони тут :(

Коли код «воняє», code smells. По коду можна визначити, хто писав, як собака людину по унікальному запаху.

змінив на

розповсюджені

. вроде норм звучить. дякую за допомогу :)

звичайні вади. напевно, не завжди слід перекладати дослівно.

ну слово «запахи» по відношенню до коду використовується доволі часто на конфах та статтях різних. тому думаю саме слово «запахи» доречно вживати :) доречі, знайшов також в самій книзі в україньскому варіанті також вживається це слово — запахи :)

Підписатись на коментарі