Прочитав очередные вредные советы про стандарты оформления кода (раз, два, тысячи их), я не смог удержаться, чтобы не поделиться своими измышлениями на эту тему. Долгие годы я вынашивал в своём подсознании чувство «что-то тут не так». И вот, пришло время определиться с тем, что не так, и почему. Рискуя быть закиданным тухлыми бананами, я всё же пишу эту статью тут, а не в своём личном блоге, потому, что это очень важная тема и хочется, чтобы как можно большее число разработчиков поняли её суть и, возможно, пересмотрели свои взгляды на жизнь… кода.

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


Типичные проблемы многих таких стайл-гайдов:
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. Иногда пробелы становятся неоправданно длинными, от чего воспринимать код становится сложнее.

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

	.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("hello", worldName.get())})

	say({ message : concat( "hello" , worldName.get() ) })

	say( document.body , { message : concat( "hello" , 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;
	}

Резюме


Продолжать можно было бы долго, но на этом пожалуй закончим. Все проекты разные: где-то нужно быстрое прототипирование, а где-то дол��ая поддержка; где-то используются статически типизированные языки, а где-то динамически. Важно тут одно — прежде чем объявлять какие-то правила единственно верными, сформулируйте цели, принципы, и убедитесь, что правила действительно им соответствуют и отбросьте всё лишнее. Незачем связывать себя по рукам и ногам, боясь оступиться, если достаточно поставить перила в нужных местах.