Как правильно оформлять код
Глеб Летушов, редактор-фрилансер, написал статью специально для блога Нетологии о том, как правильно оформлять программный код. Статья для конкурса блога.
Один из важных моментов в разработке — качественное написание кода. С правильным оформлением удобно работать, потому что тратишь меньше времени на чтение и понимание кода.
Разбираем правила, которые подходят для разных языков программирования и помогают сделать код более понятным и чистым.
Используйте горизонтальные и вертикальные отступы
Комбинирование горизонтальных и вертикальных отступов — хороший тон в разработке. С их помощью выделяются отдельные циклы, функции и другие элементы.
Горизонтальный отступ проставляется с помощью двух или четырех пробелов для того, чтобы показать вложенность и выровнять элементы, например, переменную внутри условия.
var browser = prompt(«ваш браузер», «»);
alert( ‘Хороший браузер’ );
Вертикальный отступ помогает наглядно разбить код на части. С помощью перевода строки можно разделить блоки внутри функции.
return confirm(‘Номер маленький’);
Восемь-девять строк подряд делают код менее читаемым, поэтому различные части лучше отделять помощью отступов.
Не превышайте оптимальную длину строки
Максимальная длина строки — 80 символов. Если их больше, то читать и понимать код становится тяжелее.
Когда количество символов превышает оптимальное, код лучше разбить на несколько строк.
Правильно используйте фигурные скобки
Так же, как и отступы, важно правильно использовать фигурные скобки. Есть два общепринятых метода.
- Ставить скобки сразу после кода:
var password = prompt («Введите Ваш пароль», «отмена»);
if (pass == «qwerty»)
alert («Успешная авторизация»);
> else if (prompt == null)
alert («Авторизация не удалась»);
alert («Пароль неверный»);>
- Ставить скобки параллельно друг с другом:
var password = prompt («Введите Ваш пароль», «отмена»);
if (pass == «qwerty»)
alert («Успешная авторизация»);
else if (prompt == null)
alert («Авторизация не удалась»);
alert («Пароль неверный»);
Начинающие программисты часто пропускают фигурные скобки, а если пользоваться одним из этих наглядных методов, то место с пропуском найти легко.
Называйте переменные и функции на английском
Английские названия короче и их легче писать. В больших компаниях могут работать люди из разных стран, и названия на английском будут понятны всем.
Названия, написанные транслитом, будут вносить путанницу в код. Одно и то же слово можно по-разному написать транслитом: ssilka, ssylka.
Составляйте названия из несколько слов
Названия должны быть понятными. Чтобы этого добиться, иногда лучше использовать несколько слов.
Слова в названиях нельзя разделять пробелом, поэтому существуют другие способы разделения.
- Новое слово пишется слитно с предыдущим и начинается с большой буквы:
Такой стиль написания называется CamelCase (верблюжья нотация).
- Слово соединяется через знак нижнего подчеркивания:
Имя переменной — существительное
Название переменной описывает данные, которые в ней хранятся. Поэтому переменные удобно называть существительными, которые отвечают на вопрос «что?».
Название функции — глагол
Так как функция выполняет действие, то ее обозначают глаголом.
Для обозначения сложной функции удобно использовать несколько слов. Первое — простой глагол, который показывает характер действия, второе уточняет это действие.
Одна функция должна выполнять одно действие, которое указано в названии. Если действие в функции сложное, лучше разделить его на несколько функций.
Комментарии к коду
Комментарии — это важная часть программирования. Правильно написанный комментарий делает код более понятным и объясняет, почему задача решена так, а не иначе.

Комментировать лучше по мере разработки, а не на последнем этапе. В конце сложнее вспомнить, как работает нужный алгоритм и что повлияло на его выбор.
Если отдельная строка кода непонятна без комментария, ее лучше переписать — оптимизировать код и сделать его очевидней.
Заключение
Не всегда эти правила работают так, как нужно. Но они помогают сделать код читабельным и легким для понимания. Это хороший тон в программировании.
Мнение автора и редакции может не совпадать. Хотите написать колонку для «Нетологии»? Читайте наши условия публикации.
Средняя оценка 4.6 / 5. Всего проголосовало 5
Как правильно писать код?
На протяжении свой карьеры программиста, я неоднократно сталкивался с тем, что программисты не умеют писать код. Причем это может касаться как начинающих так уже и очень опытных людей. Честно говоря, по моему мнению существуют единицы, которые действительно умеют это делать. Я не претендую на полноту освещение проблемы и на то что мое мнение правильное, а рассмотрю ее со своей точки зрения.
На мой взгляд не существует и не может существовать единого стандарта и каждый человек волен выбирать и адаптировать свои собственные подходы к программированию. Но есть некоторый набор практик, который помогает в подавляющем большинстве случаев.
Прежде всего хочется обратится к первоистокам проблемы. Что вообще не так и зачем надо что-то менять. Я думаю, что мечтой каждого программиста, является писать код максимально быстро и максимально красиво, причем чтоб все четко работало с первого раза. Это позволит чаще читать фишки и пить кофе с тестировщицами, для особо ярых даст больше времени для для развития себя как специалиста.
Одно из основных отличий профессионального программиста от новичка является система приоритетов. Как правило сделать код работающим очень не сложно, сделать его понятным намного сложнее (желательно конечно чтоб при этом он остался работающим). Поэтому профессионал зачастую больше времени проводит за рефакторингом чем за дебагом, что само по себе уже хорошо, однако хотелось бы и момент рефакторинга сократить до минимума.
Итак основные идеи, которые могут помочь писать код лучше
Имей идею
В любой вашей реализации должна быть идея, это как линия которая проходит через всю функциональность и связывает ее воедино. До того как сесть что-то писать, вы должны более менее представлять как это все будет работать, какие есть блоки, как они друг с другом взаимодействуют. Не садитесь писать просто так, придумайте концепцию, так и интереснее и зачастую результирующий код станет понятнее. Естественно уже придумав что-то стоит максимально оставаться в рамках этой концепции, не стоит менять все при первых проблемах, проблемы будут всегда. Также не стоит лениться и со словами, “да ладно и так затащит” втыкать какуюто затычку. Это к добру не приведет.
Идеала нет
А хочется конечно, чтоб он был, но его в 99.(9)% нет. Не стоит пытаться сделать код идеальным, получится еще хуже потратится намного больше времени. Это просто борьба с ветряными мельницами, все же нам платят за то что наше приложение работает а на за то, как оно шикарно написано. Часто поиски идеала приводят к постоянным сменам концепций, бесконечным переписыванием одного и того же, в конечном итоге надоедает, человек все бросает, затыкает все затычкам и “да ладно и так затащит”. Должно быть хорошо и удобно, идеал это не удел инженеров это удел поэтов, а программисты все таки инженеры.
Сохраняй фокус
Одной из основных проблем особенно у новичков является копание в деталях. Надо фокусироваться на 1 задаче в единицу времени. Что я хочу сказать, вы пишете какую-то функцию, натыкаетесь на какой-то не простой момент с которым надо разобраться. В большинстве случаев вы знаете, что эта проблема решается 100%, но не знаете как. Если переключиться на ее решение, вы собьетесь с фокуса текущей проблемы, потом для того, чтоб вернуться придется потратить время, переключение контекста никогда не было бесплатной операцией. Эта идея так же распространяется на детали реализации. Предположим вам надо написать функцию которая читает какие-то данные из базы и записывает их в файл. Вот то и есть вашим контекстом. Решите эту проблему потом решайте остальные (тоесть чтение из базы и запись в файл). Изначально мы пишем следующее:
var data = Read(); Write(data);
и генерируем 2 заглушки для Read и Write, благо вижуал студия имеет магическую комбинацию клавиш alt+shift+f10, которую я прожимаю чаще всего (перебиндить ее на f1, просто f1 вместо поиска, мешает только то что это надо делать у всех). Что нам дает такой подход. Во-первых, мы пишем быстрее ибо остаемся в контексте. Во вторых мы пишем лучше ибо в один момент решаем одну задачу, да, ошибок будет меньше. В третьих мы получаем лучше код, он изначально формируется правильно. Функции называются правильно и по контексту, они получаются маленькие и простые. На мой взгляд это самая важная практика из всех перечисленных здесь.
Чувствуй запах
Как писал Фаулер в своем бессмертном произведении, у кода есть запах. Не буду сильно повторяться, скажу кратко. Если вы ощущаете, что с кодом что-то не так, подумайте как его улучшить. Такое чувство возникает как правило с опытом, однако если вы все время будете анализировать то, что пишете, после написания, оно у вас будет развиваться намного быстрее. Однако помните, идеала нет!
Лучше безобразно но единообразно
Выработайте свою систему именования переменных, функций и тп. старайтесь ее максимально придерживаться. Я вот например возвращаемое значение функции всегда называю result, может это не правильно и не отражает смысл, отсылка во времена делфи, но я так делаю и мне это нравится, мне так удобно. Также я всегда использую var никогда не использую явное типизирование (ну только когда выхода нет). Я так же стремлюсь всегда давать очень короткие имена переменным i,d,v,k для меня не проблема, ибо функции маленькие все понятно из контекста. Зачем писать currentNode если можно написать просто n и при это все равно все ясно? Более того, длинные имена переменных часто только усложняю изложение. Про стандарты кодирования я тут молчу это другая тема, их просто надо придерживаться.
Что? Где? Когда?
Задавайте себе больше вопросов по тому коду который вы пишете. Обрабатываются ли исключения? Правильно ли сделано управление ресурсами? Очищаются ли у вас кеши в нужный момент? Все ли хорошо с потокобезопасностью? Правильно ли вы используете библиотеки? В том ли месте находится функция которую вы пишете? Существует очень много вопросов которые стоит рассмотреть даже после того как код уже работает. Надо задаваться этими вопросами постоянно, тогда это войдет в привычку и все это будет делаться уже автоматом.
Будьте проще и люди к вам потянутся
Шаблоны проектирования, иерархии классов, элементы функционального программирования это все замечательные вещи. Однако стоит по возможности стараться все делать как можно проще. Чем проще код, тем он понятнее и тем легче его сопровождать. Глубокие иерархии классов очень сложно поддерживать и понимать. Зачастую наследования вообще стоит избегать, старайтесь заменять его реализацией интерфейсов. Шаблоны проектирования тоже дают великолепные решения, но подумайте, может стоит все сделать просто? Возможно оно так будет понятнее? Зачастую так оно и есть.
И на последок еще несколько замечаний
— Если вы дебажите свой код, который только что написали, что-то пошло не так.
— Стоит реже компилировать свой код, вообще стоит больше писать меньше собирать. Если вы конечно активно используете юнит тесты, то этот пункт в принципе отпадает, там и правда лучше чаще собирать и запускать тесты. Однако все равно не стоит это делать слишком уж часто.
— Изучите все возможности которые дает среда разработки, выучите горячие клавиши и используйте их, это очень сильно экономит время. Однако не советую сильно настраивать под себя среду, будет очень сложно если придется помогать коллеге за его компьютером.
Я здесь умышленно не привожу примеры кода, сейчас я пишу на C# (как пытливый читатель уже наверно догадался), но дело в том, что не существует принципиальных отличий в написании кода на PHP, C++, Delphi, C# и тп. даже на сильно отличающихся языках (например функциональных).
Хочу отметить, что простое прочтение каких-то правил и советов ничего не дает, надо отрабатывать. И вот это последняя мысль которую я хочу выразить. Не пишете просто код, всегда отрабатывайте и улучшайте свои навыки. “Сейчас просто напишу, а дома на кошках потренируюсь” ничего вам не даст совершенно. Можно было бы еще продолжить и расширить мой список, но я считаю изложенные моменты основными.
Рекомендации по стилю для проектов из Google с открытым исходным кодом
Есть несколько охватывающих всё принципов, которые резюмируют представления о том, как писать читаемый код на языке Go. Ниже перечислены признаки читаемого кода в порядке их важности:
- Ясность: Назначение и обоснованность кода должны быть понятны читателю.
- Простота: Код должен выполнять свою задачу самым простым способом.
- Лаконичность: Код должен содержать как можно меньше воды.
- Сопровождаемость: Код должен быть написан так, чтобы его легко было поддерживать.
- Согласованность: Код должен согласоваться с более масштабной кодовой базой Google.
Ясность
Основная цель читаемости — сделать код понятным читателю.
Ясность достигается, в первую очередь, созданием эффективной системы имён, написанием полезных комментариев, а также эффективной организацией кода.
Ясность нужно рассматривать с точки зрения читателя кода, а не его автора. А легко читается, как правило, то, что легко пишется. Два аспекта ясности кода:
Что делает код? (назначение)
Go разработан так, чтобы быть относительно простым в том смысле, чтобы видеть, что делает код. В случае неясности или когда для понимания нужно «быть в теме», автору стоит потратить время, чтобы сделать назначение кода понятнее будущим читателям. В частности, этому поспособствуют:
- более описательные имена переменных;
- дополнительные комментарии;
- разбиение кода пробельными символами и комментариями;
- модульный подход с рефакторингом по методам/функциям.
Ни один подход не универсален, но при разработке кода в Go ясность всегда должна быть в приоритете.
Почему код делает это? (Обоснованность)
Часто обоснование кода достаточно передать именами переменных, функций, методов или пакетов. Где этого нет, важно добавить комментарий. Вопрос «Почему?» особенно важен, когда в коде есть нюансы, с которыми читатель может быть не знаком:
- нюанс в языке: замыкание будет захватывать переменную цикла, но само оно расположено на расстоянии многих строк;
- нюансы в бизнес-логике: специальная защита при идентификации пользователя;
API может требовать внимания, чтобы правильно его использовать. Например, оптимизированный фрагмент кода может казаться сложным и запутанным, а сложная последовательность математических операций может включать неожиданные конверсии типов. Эти и многие другие случаи требуют от автора разъяснений в комментариях и сопроводительной документации, которые уберегут сопровождающих код людей от ошибки, а читателей кода — от непонимания и необходимости в реверс-инжиниринге.
Важно осознавать, что некоторые попытки внести ясность (например, дополнительные комментарии) могут скрыть цель кода. Такое возможно, когда комментарии делают код беспорядочным, повторяют то, что предельно ясно из самого кода, или противоречат коду по своей сути. Проблемы вызывают и комментарии, которые нужно постоянно обновлять, чтобы их содержание соответствовало действительности. Такие комментарии затрудняют сопровождение. Пусть код говорит сам за себя, где это возможно (для этого, например, полезно давать объектам «говорящие» имена). Это лучше, чем писать избыточные комментарии. Кроме того, часто полезнее писать комментарии, объясняющие не что делает код, а зачем и почему он это делает.
В общем и целом кодовая база Google стремится к единообразию и согласованности. Но часто бывает, что отдельно взятый код в ней не соответствуют требованиям (например, содержит малознакомые паттерны). Таким решениям есть веские причины. Как правило, эти причины связаны с оптимизацией производительности. При этом важно указать, на что обратить внимание в новом фрагменте кода.
Стандартная библиотека содержит много примеров применения такого подхода на практике. В их числе:
- комментарии мейнтейнера в package sort ;
- полезные рабочие примеры в рамках одного пакета, которые удобны как для пользователей (просматриваются в godoc), так и для мейнтейнеров (запускаются в рамках тестов).
- примеры strings.Cut всего из 4 строк кода, которые вносят ясность и придают точкам вызова правильный вид.
Простота
Ваш код в Go должен быть простым в применении, чтении и сопровождении.
Код Go должен быть написан самым простым способом, который достигает поставленных целей, как с точки зрения поведения, так и с точки зрения производительности. В кодовой базе Google простой код Go:
- легко читается от начала и до конца;
- не предполагает, что читатель знаком с принципом работы кода;
- не требует держать в памяти всё прочитанное;
- не имеет лишних уровней абстракции;
- не имеет имён, привлекающих внимание к чему-то слишком очевидному;
- объясняет логику решений и изменений значений;
- содержит комментарии, объясняющие не что делает код, а зачем и почему он это делает, не уводя от темы;
- имеет самостоятельную документацию;
- содержит полезные ошибки и проверки сбоев;
- часто исключает «умный» код;
Между простотой кода и простотой использования API возможны компромиссы. К примеру, усложнить код может быть целесообразно, чтобы конечному пользователю было проще правильно вызвать API. И наоборот, может быть целесообразно немного усложнить действия конечного пользователя API, чтобы код оставался простым и понятным.
Если сложность в коде необходима, её нужно вносить осознанно. Как правило, это бывает необходимо для повышения производительности или при наличии множества разрозненных клиентов опредёленной библиотеки или сервиса. Сложность может быть оправдана, но в любом случае она должна объясняться сопроводительной документацией, чтобы клиенты и будущие мейнтейнеры могли ориентироваться в этой сложности. Сложные фрагменты должны быть дополнены тестами и примерами, которые покажут, что использовать сложный код разумно, особенно когда одну и ту же задачу решает и «простой», и «сложный» код.
Принцип простоты кода не означает, что сложный код не может и не должен создаваться в Go. Мы стремимся лишь к тому, чтобы кодовая база не содержала избыточно сложного кода. Если код становится сложным, это означает, что его будет сложнее понимать и сопровождать. В идеале такая ситуация требует сопроводительного комментария с обоснованием решения и указанием того, на что обратить особое внимание. Код часто становится сложнее для понимания человеком, когда оптимизируется его производительность с точки зрения машины. Повышение производительности часто требует более сложного подхода, например, предварительного выделения буфера и последующего его использование на протяжении всего времени жизни «гоурутины» (сопрограммы). Когда это видит человек, сопровождающий код, он понимает, что производительность кода критична, и это нужно учитывать при внесении любых изменений. Однако же, если сложность искусственная, без явной необходимости в ней, то она ложится бременем на тех, кто будет читать и сопровождать его.
Если код оказывается очень сложным, хотя его цель должна быть простой, это часто является сигналом к пересмотру реализации, к тому, чтобы посмотреть, есть ли способ сделать то же самое проще.
Принцип простейшей механики
Если одну идею можно выразить по-разному, предпочтение отдаётся решению с самыми стандартными средствами. К сложным алгоритмам не стоит обращаться без веских причин. Усложнить код просто. Гораздо сложнее упростить его, если сложность оказалась избыточной.
- Стремитесь обходиться базовыми конструкциями языка, если их достаточно для достижения вашей цели. К таким конструкциям относятся, например, канал (channel), слайс (slice), карта, цикл и структура.
- Если их недостаточно, ищите языковые средства в стандартной библиотеке. Это может быть, например, гипертекстовый клиент или движок шаблонизатора.
- Поищите библиотеку ядра в кодовой базе Google. Только если найти её не удалось, вводите новую зависимость или создавайте свою.
Рассмотрим, например, рабочий код, где есть переменная с привязкой к флагу. Эта переменная имеет значение по умолчанию, которое должно переопределяться в ходе тестирования. Если мы не намерены тестировать программу в режиме командной строки (допустим, с помощью os/exec ), проще и разумнее переопределить связанное значение напрямую, чем использовать flag.Set .
Аналогично, если фрагменту кода нужна проверка существования элемента во множестве, часто достаточно карты логических значений. Например, это может быть map[string]bool . Библиотеки с типами и функционалом наподобие множеств используются только там, где необходимы сложные операции, где карты неприменимы или заметно усложняют задачу.
Лаконичность
Лаконичный код Go содержит минимум «воды». В нём легко выделить самые важные части, а структура кода и система именования служат подсказками.
Многие аспекты могут оттенять важные части кода в разные моменты чтения. Среди них:
- дублируемый код;
- аутентичный синтаксис;
- неинтуитивные имена;
- лишние уровни абстракции;
- пробельные символы;
Когда код дублируется, очень трудно понять разницу между почти одинаковыми фрагментами. Читателю приходится сравнивать похожие строки кода и выискивать различия. Хорошим примером кода, где важные детали выделены на фоне повторений, являются Table-Driven Tests. При этом выбор того, что включать в таблицу, а что нет, напрямую влияет на понятность этой таблицы.
Когда код можно структурировать по-разному, стоит подумать о том, какой способ лучше выделит важные фрагменты.
Понимание и применение стандартных конструкций и выражений важно для лаконичности кода. Например, этот фрагмент кода — привычный способ обработки ошибок, который быстро поймёт любой читатель.
// Хорошо: if err := doSomething(); err != nil < // . >
И если фрагмент кода очень похож на приведённый выше, читатель может и не заметить, что есть небольшое отличие. В подобном случае разумно «забустить» сигнал проверки ошибки, добавив привлекающий внимание комментарий.
// Хорошо: if err := doSomething(); err == nil < // if NO error // . >
Сопровождаемость
Код редактируют гораздо чаще, чем пишут или переписывают. Поэтому читаемость кода важна не только для тех, кто хочет понять принцип его работы, но и для тех, кому предстоит его редактировать. Ясность чрезвычайно важна.
Хорошо сопровождаемый код:
- легко поддаётся корректному редактированию;
- имеет структурированные API, допускающие элегантное развитие;
- чётко описывает принятые допущения и выбирает абстракции, отображающие структуру задачи, а не самого кода;
- не имеет избыточной связности и неиспользуемых особенностей;
- имеет исчерпывающие тесты, позволяющие проверить факт поддержки заявленного поведения и корректности важной логики;
- должен предоставить чёткую и действенную диагностику в случае сбоя в рамках того же набора тестов;
При использовании таких абстракций, как интерфейсы и типы, которые по определению вырывают информацию из контекста, важно убедиться, что они достаточно полезны. Мейнтейнеры и IDE могут напрямую обращаться к определению метода и отображать соответствующую документацию, если применяется конкретный тип. Если же это не так, они могут обратиться только к определению интерфейса. Интерфейсы — мощный инструмент, но за него приходится платить. Для правильного применения интерфейса при сопровождении может потребоваться знание специфики нижележащей реализации. Эту специфику необходимо объяснять в документации интерфейса или в точке вызова.
Сопровождаемость кода также предполагает, что важные детали кода не «завуалированы» там, где их легко пропустить. К примеру, для понимания приведённых ниже строк кода нужно подметить, есть ли всего один символ, или его нет:
// Плохо: // The use of = instead of := can change this line completely. if user, err = db.UserByID(userID); err != nil < // . >
// Плохо: // The ! in the middle of this line is very easy to miss. leap := (year%4 == 0) && (!(year%100 == 0) || (year%400 == 0))
Оба фрагмента верны по сути, но их можно написать чётче или снабдить комментариями, которые привлекут внимание к важным особенностям поведения кода:
// Хорошо: u, err := db.UserByID(userID) if err != nil < return fmt.Errorf("invalid origin user: %s", err) >user = u
// Хорошо: // Gregorian leap years aren't just year%4 == 0. // See https://en.wikipedia.org/wiki/Leap_year#Algorithm. var ( leap4 = year%4 == 0 leap100 = year%100 == 0 leap400 = year%400 == 0 ) leap := leap4 && (!leap100 || leap400)
Таким образом, вспомогательная функция, скрывающая критичную логическую структуру кода или важный крайний случай, может привести к тому, что при сопровождении их попросту не заметят.
Ещё одна особенность хорошо сопровождаемого кода — прогнозируемость имён. При использовании пакета или сопровождении фрагмента кода приходится прогнозировать имена переменных, методов или функций в конкретном контексте. Параметры функций и имена адресатов в одинаковых понятиях должны называться одинаково, чтобы документация была понятной, а рефакторинг обходился минимум накладных расходов.
Хорошо сопровождаемый код минимизируем явные и скрытые зависимости. Когда код зависит от меньшего числа пакетов, для поддержания определённого поведения требуется меньше строк кода. Отсутствие зависимости от внутреннего или незадокументированного поведения снижает вероятность проблем, когда поведение программы придётся менять.
При структурировании кода стоит задуматься о том, как этот код может измениться со временем. Если при каком-то из возможных подходов вносить изменения проще и безопаснее, это будет хороший компромисс, даже если он несколько усложнит конструкцию кода.
Согласованность
Согласованный код должен выглядеть, ощущаться и вести себя так же, как аналогичный код в более масштабной кодовой базе, в рамках рабочей группы, пакета и даже отдельного файла.
Согласованность не должна вноситься в код в ущерб любому из приведённых выше принципов. Однако стоит сделать код согласованным, если он останется ясным, простым, лаконичным и сопровождаемым.
Прежде всего согласованность нужна в рамках пакета (package). Когда одну задачу в одном пакете решают по-разному, а понятие в одном файле называют по-разному, это создаёт значительные неудобства. Но даже это не отменяет необходимости следовать задокументированным принципам стиля и стремиться к глобальной согласованности.
Ключевые рекомендации
Эти рекомендации объединяют самые важные аспекты стиля Go, которым должен соответствовать любой код Go. Ожидается, что эти принципы будут усвоены и соблюдены к моменту, когда код станет читаемым. Не предполагается, что эти принципы будут часто меняться, и новые дополнения должны будут соответствовать высокому уровню.
Ниже приведена расширенная версия рекомендаций Effective Go, признанные всемирным комьюнити базовыми для языка Go.
Форматирование
Все файлы исходного кода должны соответствовать формату вывода инструмента форматирования gofmt . Этот формат поддерживается в кодовой базе Google при проверке перед отправкой кода. Как правило, генерируемый код должен иметь тот же формат (который задаётся, например, через format.Source ), поскольку и такой код индексируется системой Code Search.
Смешанные регистры
В исходном коде принято слитное написание без подчёркиваний ( MixedCaps или mixedCaps ). Иными словами, если имя состоит из нескольких слов, программисты Go используют «верблюжий», а не «змеиный» регистр.
Это правило действует даже тогда, когда оно противоречит правилам синтаксиса других языков программирования. К примеру, экспортируемая константа записывается как MaxLength (а не MAX_LENGTH ); неэкспортируемая — как maxLength (вместо max_length ).
Запись локальных переменных соответствует образцу записи неэкспортируемых для выбора начального регистра.
Длина строки
Длина строки не ограничена. Если строка выглядит слишком длинной, вместо обычного разбиения следует провести рефракторинг этой строки. Если же длина строки целесообразна, строке можно позволить остаться длинной.
Кроме того, не разбивайте строку:
- перед структурным изменением (indentation change), таким как объявление функции или условный оператор
- если это разделит на части строковую переменную (например, URL-адрес)
Именование
Именование больше похоже на искусство, чем на науку. В Go имена обычно короче, чем во многих других языках, однако применимы действующие для других языков универсальные правила.
Имена не должны:
- ощущаться бесполезным повторением;
- выбираться без учёта контекста;
- повторять понятия, которое уже используется.
Подробнее о выборе имён — в документе «Решения по стилю».
Локальная согласованность
Если в «Руководстве по стилю» ничего не сказано о конкретном аспекте стиля, авторы кода вольны выбирать стиль по вкусу. Однако локальный код (код в одном файле или пакете, а иногда — принадлежащий одной группе или директории) должен придерживаться согласованного подхода к решению каждой отдельной задачи.
Примеры правильного выбора локального стиля:
- применение %s или %v при форматированном выводе ошибок;
- применение буферизированных каналов (buffered channels) вместо мьютексов.
Примеры неправильного выбора локального стиля:
- ограничение кода по длине строки;
- применение тестовых библиотек на основе утверждений (ассертов).
Если локальный стиль нарушает требования «Руководства по стилю», но это влияет на читаемость только одного файла, это обычно выявляется при код-ревью. Согласованные правки при этом остаются за пределами рассматриваемого списка изменений (CL). В этом случае разумно отправить сообщение об ошибке и следить за ходом исправлений.
Если предложенные изменения ещё сильнее нарушают к стилю, распространяют его по поверхностям API, увеличивают число файлов с нарушениями или приводят к багам, то локальная согласованность не может оправдать нарушения требований в новом коде. В этом случае автору следует привести существующую кодовую базу в порядок в рамках того же CL, провести рефакторинг с опережением CL или найти альтернативу, которая, по меньшей мере, не усугубляет локальные проблемы.

- Профессия Backend-разработчик на Go (12 месяцев)
- Профессия Data Scientist (24 месяца)
Краткий каталог курсов
Data Science и Machine Learning
- Профессия Data Scientist
- Профессия Data Analyst
- [Курс «Математика для Data Science»](https://skillfactory.ru/matematika-dlya-data-science#syllabus? utm_source=habr&utm_medium=habr&utm_campaign=article&utm_content=data-science_mat_010123&utm_term=cat)
- Курс «Математика и Machine Learning для Data Science»
- Курс по Data Engineering
- Курс «Machine Learning и Deep Learning»
- Курс по Machine Learning
Python, веб-разработка
- Профессия Fullstack-разработчик на Python
- Курс «Python для веб-разработки»
- Профессия Frontend-разработчик
- Профессия Веб-разработчик
Мобильная разработка
- Профессия iOS-разработчик
- Профессия Android-разработчик
Java и C#
- Профессия Java-разработчик
- Профессия QA-инженер на JAVA
- Профессия C#-разработчик
- Профессия Разработчик игр на Unity
От основ — в глубину
- Курс «Алгоритмы и структуры данных»
- Профессия C++-разработчик
- Профессия «Белый хакер»
А также
Как писать читаемый код
Бывает, что посмотрев на старый код, мы говорим: «Его проще переписать, чем поменять». Печально, если речь идет о нашем собственном коде, с такой любовь написанном несколько лет назад. Head of Developer Relations в Evrone Григорий Петров в своем докладе на TechLead Conf 2020 разобрал проблемы, которые приводят к такой ситуации, и рассказал, как бороться с Software complexity problem.
В этой статье пересекаются, казалось бы, непересекаемые вещи: нейрофизиология, проклятие нулевой цены копирования, когнитивная и социальная интуиция. И, конечно же, в ней поднимается тема сложности кода. Вы узнаете о том, откуда она берется, почему ее нельзя убрать и как с ней жить.

В компании Evrone занимаются заказной разработкой сложного софта. Поэтому ее сотрудникам важно писать читаемый код, чтобы клиенты могли поддерживать его сами и благодарить компанию за хорошо сделанную работу.
Но несмотря на двадцатилетний опыт в программировании, Григорий признается: писать читаемый код до сих пор тяжело. И в этой статье мы обсудим все предполагаемые сложности.
Художник рисует мазками
Когда художник рисует картину, он делает это мазками: берет кисть, краски и начинает накладывать по одному мазку. Но нельзя забывать о том, что он всегда может отойти на шаг, чтобы посмотреть на свое творение целиком.

Можно сказать, что программисты тоже пишут код своеобразными «мазками»: идентификатор за идентификатором, оператор за оператором, expression, statement, строчка за строчкой получаются творения в 10, в 100, в 1000 строк кода.

Но, в отличие от художника, программисты не могут «отойти на шаг назад». Художник использует машинерию зрительной коры, у которой есть механизм зума. А когда пишется код, используются механизмы кратковременной и долговременной памяти, у которых механизм зума, к сожалению, архитектурно не предусмотрен.
Поэтому одна из главных проблем, существующих в разработке софта — это то, что мы называем Software complexity problem, что можно перевести на русский как «проблема сложности программ».
Но ведь есть огромное количество других областей, где точно также накапливается сложность. Например, ракетостроение. У людей, которые делают космические корабли, немало сложностей, правда?

Но есть у них и годы обучения, которые начинаются прямо с детского сада. Будущие ракетостроители учат счет, вначале устный, потом письменный, потом приходят в школу, там изучают физику, математику. Проходят годы, они поступают в институт, где узнают, собственно, о строительстве ракет. У них есть устоявшиеся практики о том, как создавать ракеты, а навыки закрепляются повторением. Будущий ракетостроитель проектирует, экспериментирует, совершенствуется, и пятнадцатая по счету ракета таки выйдет за пределы атмосферы.
Проблемы программирования
- Нулевая цена копирования; Если мы уже «построили ракету» — написали софт, у нас нет необходимости писать точно такой же еще раз, чтобы сделать его чуть-чуть лучше. Если разработчик работает над кодом, значит раньше подобного им написано не было. Иначе код был бы скопирован.
- Нет понимания «как правильно»; Индустрия программирования очень молода, мы еще не успели подготовить лучшие практики и не знаем, как «правильно» писать софт. Прямо сейчас можно наблюдать, как монолит объектно-ориентированного программирования, который последние 20 лет был незыблемым, сдает позиции функциональному программированию. Многие топовые разработчики сейчас говорят о том, что наследование — это не очень хороший способ декомпозиции кода. А в языках программирования последнего десятилетия (например, в Rust) в принципе нет классов, как таковых. И они неплохо себя чувствуют.
- Отсутствие фундаментального образования; Из-за молодости индустрии и нулевой цены копирования, в среде программистов отсутствует фундаментальное образование. Есть computer science, но это science. Она про науку, и имеет примерно такое же отношение к прикладной разработке софта как астрономия — к разработке телескопов. Программист, который в университете 6 лет учил алгоритмы и структуры данных, почти ничего не знает про систему управления версиями, идентификаторы, про то, как писать читаемый код и рассказывать этим кодом истории.
- Сложно посмотреть, «как делают другие». Художник может прийти в картинную галерею, посмотреть на разные топовые картины и сказать: «Вот это круто нарисовано. Я сейчас повторю и буду рисовать так же хорошо!». Для этого у него есть интуитивное мышление.
Интуитивное мышление, когнитивные искажения
Наш мозг, конечно, не «чистый лист» с рождения, но и не компьютер с предустановленным софтом. Считается, что мы можем думать ровно те мысли и тем способом, которому обучились за свою жизнь. Интуитивное мышление неплохо справляется на бытовом уровне при оценке диапазонов: оценить насколько красива картина, насколько хорошо сделан ремонт, насколько талантливо выступает артист.
Но если мы попробуем применить интуитивное мышление к чужому коду, наш мозг автоматически выдает результат: «Этот код плохой, ведь его писал не ты. Перепиши все».
У нас нет интуитивного способа оценить «качество кода». Программирование — это принципиально новая область, и наш мозг не может интуитивно применить к нему жизненный опыт из реального мира.
Кроме того, программистам, в отличие от художников, трудно обучаться у мастеров. Мы, конечно, можем прийти в наш аналог картинной галереи — GitHub — и посмотреть там на большие проекты. Но если сделать чекаут проекта с GitHub, там может оказаться полмиллиона строк кода. Это очень много, а у нас нет оптического зума, чтобы просто окинуть код взглядом, не вникая. Поэтому обучаться на примере программистам очень тяжело. Про то, что GitHub это скорее склад строительного материала, а не картинная галерея, я даже говорить не буду.
Так же тяжело заказчикам софта, которым интуиция не помогает понять, что такое технический долг и рефакторинг, и почему команда хочет много денег, чтобы, казалось бы, не сделать ничего особенного.
Так что возвращаясь к вопросу о накоплении сложности, в программировании все то же самое, что и в ракетостроении. Но, из-за отсутствия фундамента, сложность копится намного быстрее, а накопление сложности делает код нечитаемым.

Борьба со сложностью
К сожалению для нас, сложность из кода нельзя убрать. Ведь она — это та польза, которую приносит написанная нами программа.
Но сложность можно перераспределить! Именно об этом пойдет речь в статье.
Гиппокамп — это часть мозга, которая, предположительно, имеет отношение к формированию памяти. Известно, что когда выходит из строя гиппокамп, ломается память.
Как это происходит, не совсем ясно. Но существует такая закономерность, как «Кошелек Миллера»: когда человек смотрит на новые для себя объекты, в среднем, он может удержать в фокусе внимания от 5 до 9 из них.
Современные нейрофизиологи сделали вывод, что Миллер был большим оптимистом, и в реальности число удерживаемых в фокусе объектов ближе к 5. Именно столько новых штук может находиться в кратковременной памяти, прежде чем она начнет давать сбои.
Но у нас есть еще и долговременная память, объемы которой довольно велики. Однако помещение чего-либо в долговременную память занимает немало времени.

Когда человек только учится играть в шахматы, он медленно сканирует шахматную доску, вспоминая правила и пытаясь нащупать некие комбинации. Окно его внимания, содержащее 5 элементов, неспешно ползет по доске.
Но если речь идет об игроке, который сидит за шахматной доской 10-15 лет, то его мозг в автоматическом режиме пользуется привычными паттернами: комбинациями фигур, типичными атаками и защитами.
Когда опытный игрок в шахматы смотрит на доску, новой информации для него очень мало. Именно эта новая информация — то, что он держит в кратковременной памяти, и речь обычно идет всего о 2-3 элементах. Все остальное уже есть в долговременной памяти. Но для такой подготовки требуются годы.
Библиотеки и фреймворки для языков программирования могут стать способом перевести информацию из кратковременной памяти в долговременную.
Если программист много лет пишет на Python и использует requests, он к ним привыкает. Типичные конструкции использования requests — как сделать запрос, как передать и получить JSON, как решать вопросы скорости и задержек — ему привычны. Код, который использует библиотеку, становится для программиста читаемым. В таком коде больше нет сложности. По крайней мере, для этого конкретного программиста.
Если же программист начинает использовать другую библиотеку, читаемость кода для него падает. Поэтому иногда выбор не оптимальной, с точки зрения скоростных или usability характеристик, библиотеки или фреймворка, которые, тем не менее, мега популярны, может быть разумным. Такой код будет намного читаемее для большого количества программистов.
Точно также работает стандарт кодирования, «coding style». Код разработчиков может быть читаемым друг для друга, но только если они хотя бы несколько месяцев поживут с ним. Нейрофизиология утверждает, что несколько месяцев и несколько сотен повторений нужны нашей памяти, чтобы выстроить долговременные связи long-term potentiation, чем бы они ни были.
Все это сейчас очень удобно упаковывается в линтеры. Так что если мы хотим сделать так, чтобы код, который пишут программисты в нашей команде, был читаемым в первую очередь для них самих, мы запаковываем стандарт кодирования в линтеры и настраиваем линтеры в их IDE.
Но память — это долго. Это самый простой, но и самый длительный по времени способ борьбы со сложностью.
Второй по популярности способ — это декомпозиция на части по 5 элементов.

Посмотрим на эволюцию типичной сферической программы в вакууме.
Как правило, она начинается с одного файла, который реализует минимум функциональности. Затем, по мере добавления строк кода, программист интуитивно начинает разделять программу на файлы поменьше. Еще через некоторое время, когда файлов становится несколько десятков, более-менее опытный программист выделяет модули, которые дает язык программирования.
Чуть позже программист начинает использовать абстракции языка. Обычно это классы, миксины, интерфейсы, протоколы, синтаксический сахар. Современные языки программирования, как правило, позволяют программисту бороться со сложностью путем добавления высокоуровневых абстракций и синтаксического сахара.
Через некоторое время, когда абстракции языка программирования исчерпывают себя, и строчек становится несколько десятков тысяч, разработчики начинают с интересом смотреть в сторону DSL: YAML, JSON, Ruby, XML и т.д.
Особенно большой интерес проявляется в Java, где XML-конфиги к программам — просто стандарт де-факто. Но даже если команда не пишет на Java, она с большим удовольствием выкладывает и перераспределяет избыточную сложность в JSON, YAML и в другие места, которые сможет найти.
Наконец, когда строк кода становится очень много, программы начинают делить на модные сейчас микросервисы.
Вспоминается анекдот о том, что любую архитектурную проблему можно решить путем ввода дополнительного слоя абстракции. Кроме проблемы слишком большого количества дополнительных слоев абстракции.
Хорошо, что у нас есть и другие инструменты для того, чтобы писать читаемый код.

- Метаинформация.
Прежде всего метаинформация нужна не компилятору и языку программирования, а людям.
Она как дорожные указатели, которые расставлены по коду. Там, где сложности скопилось слишком много, ее разделяют на части с указанием того, что в этих частях находится. Основная часть при этом одна, все такая же огромная, но внешние «дорожные указатели» позволяют посмотреть на нее под разными углами.
Главный, основной, фундаментальный дорожный указатель — идентификаторы.
Идентификаторы — это переменные, константы, названия функций и классов — все те имена, которые мы даем сущностям в коде.
Недокументированное свойство нашего мозга заключается в том, что часть коры, которая занимается распознаванием слов (зоны Брока и Вернике) очень хорошо умеет их склеивать вместе. Поэтому каким бы длинным ни было слово, с точки зрения нашей рабочей памяти это практически всегда будет одна сущность (в разумных пределах).
竜宮の乙姫の元結の切り外し или Мосгорводоканалстрой — это одна сущность для нашего мозга.
Идентификатор здорово помогает писать читаемый код, если отвечает на вопрос «что это?». Программист пришел в проект, посмотрел на идентификатор, и ему сразу понятно, что. В современных языках программирования для этого есть PascalCase, camelCase, snake_case. Выбирая конкретный стиль, мы выбираем то, что привычнее нашей команде.
В Go все очень тяжело со сложностью, потому что язык практически не предоставляет синтаксического сахара. В книге «Как писать на языке программирования Go» есть параграф про эволюцию идентификаторов. Там написано о том, как бороться с когнитивной сложностью в коде. Выбирая имя для идентификатора, авторы предлагают смотреть на то, что находится рядом с этим идентификатором. Если там что-то очень простое (функция, 2-3 строчки кода, которые очевидны), то идентификатор может быть i или v:
v => users => admin_users
Но по мере увеличения сложности и количества кода, мы хотим увеличивать длину идентификатора, чтобы он лучше отвечал на вопрос «что это?», если такая информация непонятна из контекста.
После идентификаторов идут комментарии, которые отвечают уже на вопрос «зачем это?».
Худший комментарий в коде тот, который пересказывает, что происходит в коде. Но это и так можно увидеть, прочитав код! А вот информация зачем это происходит, как правило, содержится только в голове разработчика.
Топовые мировые программисты нередко пишут код вообще без комментариев. Идентификаторы, которые они используют для переменных, констант, функций, классов, и то, как они разбивают код на части с помощью предоставляемых языком программирования средств, рассказывают историю лучше самых удачных комментариев. Лучшим комментарием является сам код.
Но писать так, как делают это лучшие программисты, тяжело. Поэтому, мы можем добавить в код комментарии, отвечающие на вопрос «зачем?».

Точно также комментарии в коммитах могут давать понимание, зачем сделан этот коммит. А если в таком коммите есть ссылка на тикет, то сложность перераспределяется и туда, давая дополнительные точки опоры при чтении кода через много лет и отвечая на вопрос «зачем это было сделано?».
Документацию же можно рассматривать в качестве последнего бастиона. Если не удалось сделать код, который отвечает на вопрос «зачем?», не получилось добавить в него комментарии, которые отвечают на этот вопрос, и с комментариями в коммитах и тикетах тоже не сложилось, открываем readme.md и пишем там большой архитектурный абзац.
У документации есть огромные риски рассинхронизироваться с кодом, поэтому, когда мы пишем читаемый код, нужно постараться выносить что-то в документацию только в том случае, если выбора нет. Например, когда у нас очень большой проект.
Автогенерация документации — это отдельная история. Многие примеры хорошего кода, которые мы видим: фреймворки и библиотеки. При их изготовлении нам важна документация, поэтому мы документируем каждый метод, а потом автогенерим документацию. Но это нужно делать с умом.
В роли документации могут выступать и тесты. Поскольку они показывают пути выполнения.
В последние 5-10 лет в динамические языки программирования пришли типы. Они выполняют роль своеобразных «капканов» для ошибок. Когда программист пишет код, он может воспользоваться Gradual подходом современных языков. И там, где сложность повышена, добавить типы, чтобы в коде расставились несколько «капканов».
Если программист через какое-то время (например, спустя полгода) воспользуется этим кодом неправильно, «капкан» сработает, подчеркнет ему строчку в IDE красным, и все сразу станет понятно.

Gradual подход к перераспределению сложности
Gradual подход к написанию читаемого кода по больше части вращается вокруг цифры 5.
Есть несколько способов перераспределения сложности:
- Gradual decomposition; Если в нашем коде собралось больше пяти элементов, мы пробуем распределить их декомпозицией по файлам, по модулям, по классам, по функциям: в зависимости от того, что у нас есть в языке программирования.
- Gradual meta information; Если мы понимаем, что у нас уже есть распределение на множество частей, начинаем добавлять метаинформацию: давать описательные имена идентификаторам, чтобы они отвечали на вопросы «что это?» и «зачем это?».
- Gradual typing. Наконец, когда сложность продолжает скапливаться, мы добавляем типы, как «капканы» на будущее. Чтобы по возвращению к этому коду через какое-то время, «капканы» сработали и защитили нас.
Gradual подход работы со сложностью можно сформулировать в одном предложении: если количество новых вещей в коде намного превышает цифру 5, нужно использовать один из способов перераспределения сложности из списка выше.
Вопрос о том, что такое «новая вещь», остается немного за кадром. Это зависит от бэкграунда разработчика: сколько лет он пишет код, какие языки программирования, фреймворки, подходы знает.
Если в команде есть разработчики разного уровня (например джуниоры и сеньоры), они не смогут писать код, который будет читаем друг для друга. То, что не является новинкой для сеньора, который 20 лет пишет код, для джуниора ею будет. Поэтому код, который напишет сеньор, будет очень простой, понятный, хорошо читаемый — но для сеньоров. А для джуниоров количество «нового» и, соответственно, сложности в таком коде будет зашкаливать.
Практика показывает: если мы хотим, чтобы код, который пишут наши разработчики, был читаемый в первую очередь для них самих, квалификация тех, кто занимается этим в одной команде, должна быть примерно одинакова.
Писать читаемый код сложно. И Gradual поход, о котором шла речь в статье, не всегда применим. Разработка софта очень разная: есть разработка микроконтроллеров, есть разработка игр, есть бизнес-автоматизация по спецификациям, и там правила игры совершенно другие.
Но в большинстве случаев Gradual подход, который крутится вокруг цифры 5, является неплохой стартовой точкой.
Конференция, полностью посвященная инженерным процессам и практикам, TechLead Conf 2021 пройдет 12 и 13 апреля. Билеты можно приобрести здесь. Вы можете успеть купить их до повышения цены!
А пока мы все ждем апреля, приглашаем вас на Quality Assurance Webinar. На нем поговорим о пирамиде тестирования, узнаем, как найти UI тесты, которые легко могут быть перенесены на более низкие уровни, и разберемся в инфраструктуре тестирования в браузерах.
Мероприятие начнется 21 января в 18:00 мск. До встречи!
- Блог компании Конференции Олега Бунина (Онтико)
- Совершенный код
- Управление разработкой