Огляд книжки «Чистий код» Роберта Мартіна
Вітаю! З вами Артур і у цій статті я зробив огляд книжки, яка повинна бути у бібліотеці кожного програміста — «Чистий код» (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())
; - функція має виконувати лише одну операцію;
- уникайте транзитивних зверненнь (якраз про закон Деметри).
Про тести:
- брак тестів — завжди погано;
- використовуйте засоби для аналізу покриття коду;
- не пропускайте тривіальні тести;
- коли ми вимикаємо тест, ми повинні задати питання, а чи правильно розуміємо вимоги;
- якщо знайшли баг у функції — напишіть тест на це;
- тести повинні бути швидкими. Повільні тести — це тести, які ніхто ніколи не буде запускати.
Отож тепер ваш код точно стане трішки кращим, навіть просто після прочитання цієї статті. Сподіваюсь, ви знайшли щось корисне, і не забувайте, що все це думки автора, а не правила, яких треба сліпо дотримувати 🙂
Якщо сподобалось, буду радий бачити вас в телеграм-блозі або на ютубчику
117 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів