JavaRush /Java блог /Random UA /Кава-брейк #64. Як писати чистий код Чому Java краще, ніж...

Кава-брейк #64. Як писати чистий код Чому Java краще, ніж C++ для систем із низьким значенням затримки

Стаття з групи Random UA

Як писати чистий код

Джерело: Dev.to Написання чистого коду схоже на написання поезій. Це поезія, яка має бути лаконічною, зрозумілою та доступною для зміни. Чистий код передбачає масштабовану організацію. Це означає, що внесення змін не призводить до хаосу. Вміння писати такий код – одна з ключових якостей досвідченого розробника. Після того, як кілька людей порадабо мені прочитати книгу «Чистий код», я нарешті набралася хоробрості і взялася за читання. Виявилося, що це одна з тих книг, де обкладинка повністю виправдовує ажіотаж навколо неї. Рекомендації в книзі чіткі, конкретні, практичні та ще й подані з гумором. Сьогодні я хочу поділитися з вами основними висновками цієї книги.Кава-брейк #64.  Як писати чистий код  Чому Java краще, ніж C++ для систем з низьким значенням затримки - 1

1. Код має бути не тільки робочим, а й читаним

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

2. Пізніше - значить ніколи

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

3. Функції мають бути невеликими

Перше правило написання функцій - вони мають бути маленькими, приблизно до 20 рядків . Чим менша функція і чим більше вона сфокусована на якомусь одному завданні, тим легше підібрати для неї гарне ім'я. Щодо аргументів функції, їхня ідеальна кількість — 0. Далі йде 1, 2, але треба намагатися, щоб аргументів було не більше 3. Як цього досягти? Функції потрібно писати відповідно до принципів єдиної відповідальності та відкритості/закритості.

4. Дублювання коду – це погано

Дублювання - ворог добре організованої системи. Це додаткова робота, додатковий ризик та додаткова непотрібна складність. Що з цим робити? Слідкуйте за тим, щоб код писався відповідно до принципу DRY, був ізольованим та модульним.

5. Єдиний добрий коментар - це той, який ви знайшли спосіб не писати

«Немає нічого кориснішого за хороший коментар у потрібному місці. Але коментарі навіть у найкращому разі неминуче зло». Коментарі мають компенсувати нашу нездатність висловити свою думку в коді. Тобто це спочатку визнання поразки. Так, нам доводиться їх використовувати, тому що ми не завжди можемо донести свої наміри за допомогою коду, але це не привід для радості. Справа в тому, що коментарі часто брешуть. Не завжди і не спеціально, але надто часто. Чим старший коментар і що далі він розташований від коду, який описує, тим ймовірніше, що він є помилковим. Причина цього проста: програмісти не можуть по-справжньому добре підтримувати код і всі коментарі. Тому дуже часто коментарі відокремлюються від коду, до якого відносяться, і стають безхазяйними анотаціями з мінімальною точністю. Що з цим робити? Потрібно використовувати методи описового найменування. Прочитавши ім'я змінної, ви повинні відразу зрозуміти, що вона є. Також потрібні тести, щоб інші розробники розуміли, який функціонал є основним.

6. Об'єкт розкриває поведінку, але не дані

Модуль не повинен знати про начинки об'єктів, якими він маніпулює. Об'єкти приховують свої дані та розкривають операції. Це означає, що об'єкт повинен розкривати свою внутрішню структуру через методи доступу. Не обов'язково, щоб усі бачабо тебе голим. Що з цим робити? Область видимості змінних повинна бути якомога локальнішою, щоб не розкривати більше необхідного.

7. Тестування

Код тестів так само важливий, як і той, що йде у виробництво. Тому він повинен змінюватись і зростати у міру розвитку проекту. Завдяки тестам ваш код залишається гнучким, підтримуваним та придатним для багаторазового використання. Без них будь-яка зміна може спричинити появу багів. Тести дозволяють чистити код, не побоюючись, що щось поламається. Тому підтримка чистоти тестів має велике значення. Чистоту тестів забезпечує їх читаність. Тести — можливість пояснити іншим розробникам простою мовою наміри автора коду. Тому в кожній тестовій функції тестуємо лише одну концепцію. Так тест виходить описовим, він легше читається, і якщо провалюється — легше відстежити причину цього. Як цього досягти? Потрібно дотримуватися принципів чистих тестів FIRST . Тести мають бути:
  • Швидкі (Fast). Тести мають виконуватися швидко. Якщо вам доводиться дуже довго чекати на виконання тесту, ви навряд чи станете запускати його частіше.
  • Незалежними/ізольованими (Independent). Тести повинні бути якомога ізольованішими і не залежать один від одного.
  • Повторювані (Repeatable). Тести мають бути повторюваними у будь-якому середовищі — у розробці, стейджингу та продакшені.
  • Очевидними (Self-Validating). Результатом виконання тесту має бути булеве значення. Тест має бути або пройдено, або провалено.
  • Вичерпними (Thorough). Потрібно прагнути охопити тестами всі крайні випадки, всі проблеми безпеки, кожен use case (варіант використання) та happy path (найсприятливіший сценарій роботи коду).

8. Обробка помилок та винятків

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

9. Класи

Класи мають бути невеликими. Але рахувати треба не рядки коду, а відповідальність. Імена класів - ключ до опису того, за що вони відповідають. Наші системи повинні складатися з великої кількості невеликих класів, а не з кількох великих. Кожен такий маленький клас має інкапсулювати єдину відповідальність. Для існування кожного класу має бути лише одна конкретна причина, а для досягнення бажаної поведінки системи кожен клас має «співпрацювати» з кількома іншими класами. Рідко буває поважна причина створення загальнодоступної змінної. Послаблення інкапсуляції – це крайня міра. Крім того, змінних екземпляра має бути небагато. Хороший дизайн програмного забезпечення дозволяє вносити зміни без великих вкладень та переробок. Звуження діапазону змінних спрощує це завдання. Як цього досягти? Поділ проблем — один із найстаріших та найважливіших прийомів проектування. Класи повинні бути відкриті для розширення, але закриті для модифікації. В ідеальній системі ми включаємо нові функції шляхом розширення системи, а не шляхом внесення змін до існуючого коду.

10. Форматування

Кожен порожній рядок — візуальна підказка, яка допомагає визначити, що почалася нова, окрема концепція. Локальні змінні мають з'являтися нагорі функції. Змінні екземпляри мають декларуватися вгорі класу. Короткі рядки краще ніж довгі. Зазвичай межа - 100-120 символів, довше вже не варто робити. Як цього досягти? Більшість параметрів можна передати лінтеру у вашому CI або текстовому редакторі. Користуйтеся цими інструментами, щоб зробити свій код якомога чистішим.

Принципи розробки програм

Застосовуйте такі прийоми, і ваш код завжди буде чистим: Іменування змінних. Підбір відповідних імен (хороший неймінг) має вирішальне значення задля забезпечення читабельності коду і, отже, його підтримуваності. "Вибирати ім'я для змінної слід так само відповідально, як і для свого первістка". Вибір добрих імен часто викликає труднощі у розробників. Для цього потрібна хороша навичка опису предметів і культурний бекграунд. Чистий код — код, який читають та покращують зовсім різні розробники. Ім'я змінної, функції чи класу має відповідати всі основні питання: чому ця сутність існує, навіщо і як використовується. Якщо ім'я вимагає коментаря, значить воно недостатньо розкриває суть того, що описує. Більш довгі імена важливіші за короткі, а будь-яке доступне для пошуку ім'я краще, ніж константа. Однолітерні імена можуть використовуватися тільки як локальні змінні всередині коротких методів: довжина імені повинна відповідати області видимості. Імена методів мають бути дієсловами чи дієслівними фразами; ім'я класу має бути дієсловом. Залежності мають бути зведені до мінімуму. Краще покладатися на те, що ви контролюєте, ніж те, що не можете контролювати. В іншому випадку ці речі будуть контролювати вас. Акуратність. Кожен окремий фрагмент коду повинен бути там, де читач очікує його знайти. Навігація по кодовій базі має бути інтуїтивною, а наміри розробника зрозумілими. Очищення. Не залишайте в кодовій базі марний код (старий і вже не використовується або створений "про всяк випадок"). Зменшуйте кількість дублікатів та створюйте прості абстракції на ранніх етапах. Стандартизація. При написанні коду слід дотримуватись стилю та практик, встановлених для репозиторію. Самодисципліна. У міру розвитку використаних технологій та появи нових у розробників часто виникає прагнення щось змінити та покращити у вже існуючому коді. Не піддавайтеся хайпу надто швидко: вивчайте нові стеки ґрунтовно і лише з конкретною метою. Підтримка чистоти кодової бази — це щось більше, ніж ввічливість до справжніх і майбутніх колег. Вона потрібна для виживання програми у довгостроковій перспективі. Чим чистіший ваш код, тим щасливішими є розробники, тим кращий продукт і тим довше він проіснує.

Чому Java краще, ніж C++ для систем з низьким значенням затримки

Джерело: StackOverflow Як розробники, ми всі знаємо, що є два способи щось робити: вручну, повільно та з роздратуванням, або автоматично, складно та швидко. Я міг би використати штучний інтелект, щоб він написав за мене цю статтю. Це могло б заощадити мені купу часу — ІІ здатний генерувати тисячі статей за секунду, але мій редактор навряд чи зрадіє, дізнавшись, що генерація першої статті триватиме два роки. Кава-брейк #64.  Як писати чистий код  Чому Java краще, ніж C++ для систем з низьким значенням затримки - 2Аналогічна ситуація виникає розробки програмних систем з низьким значенням затримки. Вважають, що було б божевіллям використовувати щось, крім C++, тому що все інше має занадто велику затримку. Але я тут, щоб переконати вас у протилежному інтуїції, що суперечить, майже єретичному понятті: коли справа доходить до досягнення низької затримки в програмних системах, краще використовувати Java. У цій статті хочу взяти конкретний приклад програмного забезпечення, для якого цінується низька затримка: торговельні системи. Однак наведені аргументи можуть застосовуватися практично до будь-яких обставин, в яких потрібна або бажана низька затримка. Просто це легше обговорювати стосовно тієї галузі розробки, в якій я маю досвід. І справді в тому, що затримку складно виміряти. Все зводиться до того що, що ви розумієте під визначенням «низька затримка». Давайте зараз у цьому розберемося.

Набута мудрість

Оскільки C++ набагато ближче до заліза, більшість розробників скажуть вам, що кодування цією мовою дає перевагу в швидкості. У ситуаціях з малою затримкою, таких як високошвидкісна торгівля, коли мілісекунди можуть мати значення між життєздатною частиною програмного забезпечення та застарілою тратою дискового простору, C++ вважається золотим стандартом. Принаймні колись так було. Але реальність така, що в даний час багато великих банків та брокерів використовують системи, написані на Java. І я маю на увазі спочатку написане на Java, а не написане на Java, а потім інтерпретоване на C++ для зменшення затримки. Ці системи стають стандартними навіть для інвестиційних банків першого рівня, незважаючи на те, що вони (імовірно) повільніші. Отже, що відбувається? Так, C++ може мати «низьку затримку», коли справа доходить до виконання коду, але це безперечно не низька затримка, коли справа доходить до розгортання нових функцій або навіть пошуку розробників, які можуть його написати.

(Реальні) відмінності між Java та C++

Питання часу розробки — це лише початок, коли йдеться про відмінності між Java та C++ у реальних системах. Щоб зрозуміти справжню цінність кожної мови в цьому контексті, давайте трохи заглибимося. По-перше, важливо пам'ятати справжню причину, через яку C++ у більшості ситуацій швидше за Java: покажчик C++ є адресаою змінної в пам'яті. Це означає, що програмне забезпечення може безпосередньо звертатися до окремих змінних, і йому не потрібно переглядати таблиці, що вимагають великих обчислювальних ресурсів, для їх пошуку. Або принаймні може звертатися, якщо буде вказано, де вони знаходяться, тому що з C++ вам часто доведеться явно управляти часом життя та володінням об'єктами. В результаті, якщо ви дійсно не дуже хороші в написанні коду (навичка, на освоєння якого можуть знадобитися десятиліття), C++ вимагатиме годин (або тижнів) налагодження. І, як вам скаже будь-хто, хто намагався налагодити Monte Carlo engine або інструментальний засіб перевірки PDE, спроба налагодження доступу до пам'яті на фундаментальному рівні може зайняти багато часу. Один лише несправний покажчик може легко вивести з ладу всю систему, тому випуск нової версії, написаної на C++, може бути справді жахливим. Звісно, ​​це ще не все. Люди, яким подобається програмувати на C++, вкажуть на те, що збирач сміття Java страждає від нелінійних сплесків затримки. Це особливо актуально при роботі зі застарілими системами, тому надсилання оновлень коду Java, не порушуючи роботу клієнтських систем, може зробити їх настільки повільними, що їх не можна буде використовувати. У відповідь я хотів би відзначити, що за останнє десятиліття було зроблено велику роботу зі скорочення затримки, створюваної Java GC. LMAX Disruptor, наприклад, є торговельну платформу з низьким значенням затримки, написану на Java, також побудовану як фреймворк, який має «механічну взаємодію» з обладнанням, на якому він працює, і який не вимагає блокування. Проблеми можуть бути додатково пом'якшені, якщо ви створюєте систему, яка використовує процес безперервної інтеграції та доставки (CI/CD), оскільки CI/CD допускає автоматичне розгортання перевірених змін коду. Це пов'язано з тим, що CI/CD забезпечує ітеративний підхід до зменшення затримок складання сміття, при якому Java може поступово покращуватися та адаптуватися до конкретних апаратних середовищ без ресурсомісткого процесу підготовки коду для різних специфікацій обладнання перед його відправкою. Оскільки підтримка Java в IDE набагато ширша, ніж у C++, більшість середовищ (Eclipse, IntelliJ IDEA) дозволяють виконувати рефакторинг Java. Це означає, що IDE можуть оптимізувати код для роботи з низьким значенням затримки, хоча ця можливість ще обмежена при роботі з C++. Навіть якщо код Java не зовсім відповідає C++ за швидкістю, більшості розробників все одно легше досягти прийнятної продуктивності Java, ніж C++.

Що ми маємо на увазі під словом «швидше»?

Фактично, є вагома причина засумніватися в тому, що C++ дійсно «швидший» або взагалі має «меншу затримку», ніж Java. Я усвідомлюю, що потрапляю в досить "каламутні води", і що багато розробників почнуть сумніватися в моєму здоровому глузді. Але вислухайте мене. Уявімо таку ситуацію: у вас є два розробники — один пише на C++, а інший на Java, і ви просите їх написати платформу для високошвидкісної торгівлі з нуля. У результаті система, написана на Java, виконуватиме торгові транзакції довше, ніж система на C++. Однак Java має набагато менше випадків невизначеної поведінки, ніж C++. Візьмемо лише один приклад: індексація за межами масиву є помилкою як Java, так і C++. Якщо ви випадково зробите це на C++, у вас може виникнути помилка segfault або (частіше) ви просто отримаєте якесь випадкове число. У Java при виході за кордон завжди виникає помилка ArrayIndexOutOfBoundsException . Це означає, що налагодження Java значно спрощується, тому що помилки, як правило, відразу визначаються, і місцезнаходження помилки легше відстежити. До того ж, принаймні на мій досвід, Java краще розпізнає, які фрагменти коду не потрібно запускати, а які критичні для роботи вашого програмного забезпечення. Ви, звичайно, можете витратити дні на налаштування свого коду C++, щоб він не містив абсолютно ніякого стороннього коду, але в реальному світі кожна частина програмного забезпечення містить певну роздутість, і Java краще розпізнає його автоматично. Це означає, що в реальному світі Java часто швидше, ніж C + +, навіть за стандартними показниками затримки. І навіть там, де це не так, різниця в затримці між мовами часто перекривається іншими факторами, які не настільки великі, щоб мати значення навіть у високошвидкісній торгівлі.

Переваги Java для систем із низькою затримкою

Всі ці фактори, на мій погляд, створюють досить незаперечний аргумент на користь використання Java для написання високошвидкісних торгових платформ (і систем з низьким значенням затримки в цілому, докладніше про це трохи пізніше). Однак, щоб трохи вплинути на ентузіастів C++, розглянемо низку додаткових причин для використання Java:
  • По-перше, будь-яка надмірна затримка, яку Java привносить у ваше програмне забезпечення, ймовірно, буде набагато менше, ніж інші фактори, що впливають на затримку, наприклад, проблеми з інтернетом. Це означає, що будь-який (добре написаний) код Java може легко працювати так само, як і C++, у більшості торгових ситуацій.

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

  • Якщо заглибитись у це, ви побачите, що навіть оптимізація програмного забезпечення Java може бути швидше (якщо розглядати її в рамках всього програмного забезпечення), ніж аналогічне завдання на C++.

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

Висновки

Якщо ви не розробляєте торгові системи з низьким значенням затримки, вам, ймовірно, буде цікаво, чи застосовно щось із вищеперерахованого саме до вас. Відповідь, крім дуже небагатьох винятків, позитивна. Суперечки про те, як досягти низької затримки, не є новими і не унікальними для світу фінансів. З цієї причини з нього можна отримати цінні уроки для інших ситуацій. Зокрема, наведений вище аргумент про те, що Java «краще», тому що вона більш гнучка, більш стійка до відмови і, в кінцевому рахунку, швидше в розробці та обслуговуванні, може застосовуватися в багатьох областях розробки програмного забезпечення. Причини, з яких я (особисто) волію писати системи з низьким значенням затримки на Java, ті ж, що й ті, які зробабо мову такою успішною за останні 25 років. На Java легко писати, компілювати, виконувати налагодження та вивчати. Це означає, що ви можете витрачати менше часу на написання коду і більше часу на його оптимізацію. Насправді це призводить до більш надійним і швидким торговим системам. І це все, що має значення для високошвидкісної торгівлі.
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ