Принципи написання коду

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

Стандарти кодування

Типові проблеми багатьох таких стайл-гайдів:
1. Погано обґрунтовано їх доцільність
2. Вони декларують конкретні правила, замість принципів.
3. Ці правила погано обґрунтовані і нерідко побудовані на які суперечать принципах.

У згаданій статті все обґрунтування необхідності стандартизації полягає в:
Хороше керівництво з оформлення коду дозволить домогтися наступного:
1. Встановлення стандарту якості коду для всіх джерел;
2. Забезпечення узгодженості між першоджерелом;
3. Дотримання стандартів усіма розробниками;
4. Збільшення продуктивності.
1. [тут розташовується картинка про ще один стандарт] «Стандарт потрібен для того, щоб був стандарт» — не обґрунтовує його наявність.
2. В будь-якому більш-менш великому проекті завжди буде купа коду не відповідає поточним тенденціям моди оформлення: зміни стайл-гайда з часом, легасі код, код сторонніх бібліотек, автогенерированный код. Це неминуче і не так вже й погано, як на перший погляд може здатися.
3. Те ж що і перший пункт.
4. Вже тепліше, але знову ж таки, не обґрунтовується чому продуктивність від цього має зрости, і головне — на скільки.

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

Як написати грамотний стандарт:
1. Визначився з цілями
2. Сформулювати принципи і провалидировать їх на відповідність цілям
3. Сформулювати мінімум правил, для реалізації цих принципів

Отже, спробуємо

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

У чому полягає підтримка:
1. написання нового коду
2. зміна існуючого, в тому числі і автоматична
3. пошук потрібного ділянки коду
4. аналіз логіки роботи код
5. пошук джерела неправильного поведінки
6. порівняння різних версій одного код
7. перенесення коду між гілками

Які принципи допоможуть досягти поставленої мети:

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

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

.icon--home { background-position: 0 0 ; }
.icon--person { background-position: -16px 0 ; }
.icon--files { background-position: 0-16px; }
.icon--settings { background-position: -16px-16px; }

Вертикальне вирівнювання — це може бути і красиво, але не практично з наступних причин:
1. Додавання рядка з більш довгим ім'ям (наприклад, icon--person-premium) призведе до зміни всіх рядків у групі.
2. Автоматичне перейменування в більшості випадків зіб'є вирівнювання (наприклад, при зміні icon--person на icon--user в більшості инструметов).
3. Іноді прогалини стають невиправдано довгим, від чого сприймати код стає складніше.

Також тут можна помітити зайвий семиколон (крапку з комою). Єдина причина його появи в цьому місці — сліпе слідування правилам стайл-гайда, не розуміючи його принципів.
У багаторядкових правилах, це дійсно необхідно, щоб додаються в кінець рядка не приводили до зміни вже існуючих. Наприклад:

.icon {
display: inline-block;
width: 16px;
height: 16px
}

.icon {
display: inline-block;
width: 16px;
height: 16px; // додали семиколон
background-image: url(/img/sprite.svg) // корисне зміна
}

Якщо ви пишете на javascript і можете дозволити собі відмовитися від ie8, то можете використовувати хвостову пунктуацію і в литералах:

var MainThroubles = [
'fools',
'roads',
'fools on roads',
]

var GodEnum = {
father : 0,
son: 1,
holySpirit : 2,
}

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

.icon {
background: url(/img/sprite.svg) 10px 0 black;
}

.icon {
background: url(/img/sprite.svg) 10px 0; /* зміщення в спрайте жорстко пов'язані з самим спрайтом */
background-color: black; /* фоновий колір змінюється незалежно від картинки */
}

Ще один яскравий приклад порушення цього принципу — ланцюжка викликів методів:

messageProto
.clone()
.text( text )
.appendTo( document.body )
.fadeIn()

Тут ми постаралися розмістити кожна ланка на окремому рядку, що дозволяє додавати/видаляти/змінювати ланки не зачіпаючи сусідні рядки, але між ними все одно залишається сильна зв'язок із-за якої ми не можемо, наприклад, написати так:

messageProto
.clone()
.text( text )
if( onTop ){
.appendTo( document.body )
} else {
.prependTo( document.body )
}
.fadeIn()

Щоб додати таку логіку доведеться розбити ланцюжок на дві і виправити їх початку:

var message = messageProto
.clone()
.text( text )
if( onTop ){
message.appendTo( document.body )
} else {
message.prependTo( document.body )
}
message.fadeIn()

А ось при такому записі ми маємо повну свободу дій:

var message = messageProto.clone()
message.text( text )
message.appendTo( document.body )
message.fadeIn()

var message = messageProto.clone()
message.text( text )
if( onTop ){
message.appendTo( document.body )
} else {
message.prependTo( document.body )
}
message.fadeIn()

2. Не звалювати всі яйця (код) в одну корзину (файл/теку).
Якщо вам здається, що в коді не вистачає так званих «секцій», то швидше за все ви підібралися до верхнього порогу сприйняття, коли, вже складно знаходити потрібні його ділянки. У цьому випадку природним бажанням є створення змісту. Але зміст у вигляді коментарів на початку файлу не зрівняється з змістом у вигляді списку файлів у директорії. Маючи код в ієрархії файлової системи ви досить не погано можете впорядковувати всі прибуває число сутностей. Приблизно те ж саме ви швидше за все робите і в рантайме, розташовуючи код в ієрархії неймспейсов, так що немає ніякої причини мати для однієї сутності різні простори імен: в рантайме і у файловій системі. Простіше кажучи, імена та ієрархія директорій повинна відповідати іменам і ієрархії просторів імен.

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

img/
header/
logo.jpg
menu_normal.png
menu_hover.png
main/
title-bullet.png
css/
header/
logo.css
menu.css
main/
typo.css
title.css
js/
menu.js
spoiler.js
tests/
menu.js
spoiler.js

header/
logo/
header-logo.jpg
header-logo.css
menu/
menu.css
menu.js
menu.test.js
menu_normal.png
menu_hover.png
main/
title/
title.css
title-bullet.png
spoiler/
spoiler.js
spoiler.test.js

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

3. Мова програмування — принципово не природний мову.
На відміну від письмовій промови, яка читається строго послідовно, програмний код на сучасних мовах програмування являє собою двомірну структуру. У них часто немає, наприклад, необхідність ставити крапки (семиколоны) в кінці речень, що:

.icon--settings { background-position: -16px-16px; }

.icon--settings { background-position: -16px-16px }

JS частково розуміє двомірність коду, тому в ньому семиколоны в кінці рядків є тавтологиями:

function run() {
setup();
test();
teardown();
}

function run() {
setup()
test()
teardown()
}

А ось CSS не розуміє, тому в ньому, без них не обійтися:

.icon {
display: inline-block;
width: 16px;
height: 16px;
}

Для поліпшення сприйняття токенів мови, прогалини можуть бути розставлені зовсім не за правилами писемного мовлення:

say({message: concat("привіт", worldName.get())})

say({ message : concat( "привіт" , worldName.get() ) })

say( document.body , { message : concat( "привіт" , worldName.get() ) } )

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

view.getTopOffset()

view.offsetTop_get()

view.offset.top.get()

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

for( man of mans ) man.say( 'Hello, Mary!' )

Але це менше зло порівняно з вимогою від кожного програміста хорошого знання всіх англійських словоформ.

5. Повні і однакові імена однієї сутності в різних місцях
Пошук по імені — досить часта операція при роботі з незнайомим кодом, тому важливо писати код так, щоб по імені можна було легко знайти місце, де воно визначається. Наприклад, ви відкрили сторінку і виявили там клас «b-user__compact». Вам потрібно дізнатися, як він там з'явився. Пошук по рядку «b-user__compact» нічого не видає, тому, що ім'я цього класу ніде цілком не зустрічається — воно склеюється з шматочків. А все тому, що хтось вирішив зменшити копипасту ціною усложения дебага:

//my/block/block.js
My.Block.prototype.getSkinModClass = function(){
return 'b-' + this.blockId + '__' + this.skinId
}
My.Block.prototype.skinId = 'compact'

//my/user/user.js
My.User.prototype.blockId = 'user'

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

//my/block/block.js
My.Block.prototype.getSkinModClass = function(){
return this.blockId + '__' + this.skinId
}

My.Block.prototype.skinId = 'my-block-compact'

//my/user/user.js
My.User.prototype.blockId = 'my-user'

За отриманим класу «my-user__my-block-compact» відразу видно, що він склеєний з двох шматків: один визначений в модулі «my/block», а інший «my/user» і обидва легко знаходяться за відповідним підрядками. Аналогічна логіка можлива і при використанні css-препроцесорів, де ми зустрічаємося з тими ж проблемами:

//my/block/block.styl
.b-block {
&__compact {
zoom: .75;
}
}

//my/user/user.styl
.b-user {
@extends .b-block;
color: red;
}

//my/block/block.styl
. My-block {
&__my-block-compact {
zoom: .75;
}
}

//my/user/user.styl
. My-user {
@extends. my-block;
color: red;
}

Якщо ж не використовуєте css-препроцессоры, то тим більше:

//my/block/block.css
.m-compact {
zoom: .75;
}

//my/user/user.css
.b-user {
color: red;
}

//my/block/block.css
. My-block-compact {
zoom: .75;
}

//my/user/user.css
. My-user {
color: red;
}

Резюме

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

Джерело: Хабрахабр

0 коментарів

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