Правила гарного смаку від Лінуса Торвальдса. Робимо код швидше, простіше і зрозуміліше

«Смак — це здатність судити про прекрасне»
І. Кант
Дірк Хондел, один з тих, хто стояв біля витоків Linux, одного разу сказав про творця Linux Линусе Торвальдсе: «Лінус не тільки блискучий програміст: у нього гарний смак. Торвальдс знаходить прості і розумні шляхи вирішення проблем, вміє все «розкласти по поличках». Складні речі він робить простими. По-моєму, це і є головна відмінність чудового програміста від просто хорошого».

image
недавньому інтерв'ю, приблизно на 14-й хвилині, Лінус Торвальдс торкнувся теми «доброго смаку в програмуванні». Хороший смак? Ведучий попросив його зупинитися на цьому детальніше, і Лінус, що прийшов не з порожніми руками, показав кілька слайдів.

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


Приклад поганого смаку в програмуванні

Це – функція, написана на C, яка займається видаленням об'єктів із зв'язаного списку. Вона складається з 10 рядків коду.

Лінус привернув увагу до керуючої конструкції if в нижній частині функції. Саме цим фрагментом він був особливо незадоволений.

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

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

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

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


Приклад гарного смаку в програмуванні

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

Лінус пояснив новий код, сказав, що найголовніше полягає в усуненні прикордонного випадку, після чого розмова перейшла на іншу тему.

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

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

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

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

Ініціалізація країв сітки зі смаком
Нижче показаний алгоритм, який я написав для того, щоб ініціалізувати елементи уздовж країв сітки, яка представлена у вигляді багатовимірного масиву:
grid[rows][cols]
.

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

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

for (r = 0; r < GRID_SIZE; ++r) {
for (c = 0; c < GRID_SIZE; c++) {
// Верхній край
if (r == 0)
grid[r][c] = 0;
// Лівий край
if (c == 0)
grid[r][c] = 0;
// Правий край
if (c == GRID_SIZE - 1)
grid[r][c] = 0;
// Нижній край
if (r == GRID_SIZE - 1)
grid[r][c] = 0;
}
}

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

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

  2. Код неефективний. За умови, що змінна GRID_SIZE встановлена в значення 64, тіло внутрішнього циклу виконається 4096 раз тільки для того, щоб знайти 256 елементів на краях.
Лінус, мабуть, погодився б з тим, що цей код можна віднести до зразків гарного смаку.

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

for (i = 0; i < GRID_SIZE * 4; i++) {
// Верхній край
if (i < GRID_SIZE)
grid[0][i] = 0;
// Правий край
else if (i < GRID_SIZE * 2)
grid[i - GRID_SIZE][GRID_SIZE - 1] = 0;
// Лівий край
else if (i < GRID_SIZE * 3)
grid[i - (GRID_SIZE * 2)][0] = 0;
// Нижній край
else
grid[GRID_SIZE - 1][i - (GRID_SIZE * 3)] = 0;
}

Стало краще? Так. Але виглядає це просто огидно. Цей код не з тих, які можна зрозуміти з першого погляду. Тільки одне це змусило мене рухатися далі.

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

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

for (i = 0; i < GRID_SIZE; ++i) {
// Верхній край
grid[0][i] = 0;
// Нижній край
grid[GRID_SIZE - 1][i] = 0;
// Лівий край
grid[i][0] = 0;
// Правий край
grid[i][GRID_SIZE - 1] = 0;
}

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

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

Є у мене смак до програмування?
І що ж, тепер я програміст, код якого не відповідає правилам гарного смаку?

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

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

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

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

А як ви можете застосувати концепцію «гарного смаку» у своїх проектах?
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.