Исключительная красота исходного кода Doom 3

http://kotaku.com/5975610/?post=56184038
image

Сегодня вас ждет рассказ об исходном коде Doom 3 и о том, насколько он красив.
Да, красив. Позвольте мне объясниться.

После выпуска моей видеоигры Dyad я решил взять небольшой перерыв. Я наконец-то прочитал несколько книг и посмотрел фильмы, которые так долго откладывал в дальний ящик. Тогда я работал над европейской версией Dyad, но все это время я большей частью ждал фидбеков от отдела качества Sony, так что у меня было более чем достаточно времени. После месяца подобного времяпровождения я всерьез задумался над тем, что же мне делать дальше. Я вспомнил, что давно собирался отделить от Dyad куски исходного кода, которые хотел использовать в своем новом проекте.

Когда я только начинал работу над Dyad, то это был «прозрачный» игровой движок с неплохим функционалом, и получился он таким благодаря моему опыту на предыдущих проектах. Ближе к концу разработки игры он превратился в беспросветный бардак.

За последние 6 недель разработки Dyad я дописал 13k строк кода. Один только исходник главного меню MainMenu.cc раздулся до 25 501 строки. Когда-то прекрасный код превратился в настоящий бардак из всяких #ifdef, указателей на функции, уродливых SIMD и ассемблерных вставок — а я открыл новый для себя термин «энтропия кода». С грустью взглянув на все это, я отправился в путешествие по интернету в поисках других проектов, которые помогли бы мне понять, как другие разработчики красиво управляются с сотнями тысяч строк кода. Но после того, как я взглянул на код пары больших игровых движков, я был просто обескуражен; мой «ужасный» исходный код по сравнению с остальными оказался еще чистейшим!

Я продолжил свои поиски, неудовлетворенный таким результатом. В конце концов, мне попался на глаза интересный анализ исходного кода Doom 3 от id Software, написанный Фабианом Санглардом.

Я провел несколько дней в изучении исходников Doom 3 и за чтением статей Фабиана, после чего выдал твит:
Я потратил некоторое время на изучение исходников Doom3. Это, наверное, самый понятный и симпатичный код из всех, что я видел.

И это было правдой. До этого момента я никогда не заботился об исходном коде. Да я, на самом деле, не слишком люблю называть себя «программистом». У меня неплохо получается, но для меня программирование длится ровно до того момента, как все начнет работать. После просмотра исходного кода Doom 3, я действительно научился ценить хороших программистов.

***
Чтобы вы получили какое-то представление: Dyad содержит 193k строк кода, все на С++. Doom 3 — 601k, Quake III — 229k и Quake II — 136k. Это большие проекты.

Когда меня попросили написать эту статью, я использовал это как оправдание для чтения еще некоторого количества исходного кода других игр и статей о стандартах программирования. После нескольких дней своих исследований, я бы смущен своим собственным твитом и это навело меня на мысль — так что же все-таки стоит считать «красивым» исходным кодом? Я спросил нескольких из своих друзей-программистов, что по их мнению это значило. Их ответы были очевидными, но все же имеет смысл их привести здесь:

  • Код должен быть сгруппирован локально и едино-функционален: Одна функция должна делать ровно одну вещь. Должно быть понятно, что делает конкретная функция.
  • Локальный код должен объяснять или хотя бы указывать на архитектуру всей системы.
  • Код должен быть задокументирован «сам по себе». Комментариев следует избегать во всех возможных ситуациях. Комментарии дублируют работу и для чтения, и для написания кода. Если вам требуется что-то прокомментировать, то скорее всего оно должно быть переписано с нуля.

Для idTech 4 стандарты кода выложены в открытый доступ (.doc) и я могу порекомендовать их как достойное чтиво. Я пройдусь по большинству из этих стандартов и попробую разъяснить, каким же образом они делают код Doom 3 настолько прекрасным.

Универсальный парсинг и лексический разбор

Одна из самых умных вещей, которые я увидел в Doom, это использование их лексического анализатора и парсера по всей программе. Все файлы ресурсов представляют собой ascii файлы с единым синтаксисом включая: скрипты, файлы анимации, конфиги и пр.; все одно и то же. Это позволяет читать и обрабатывать все файлы одним и тем же куском кода. Парсер особенно надежен, и поддерживает главное подмножество С++. Приверженность единому парсеру и лексическому анализатору помогает остальным компонентам движка не беспокоиться о сериализации данных, так как код, отвечающий за эту часть приложения, уже написан. Благодаря этому, остальной код становится куда как яснее.

Const и строгие параметры (Rigid Parameters)

Код Doom достаточно строг, но (на мой взгляд) недостаточно строг по отношению к const. Const служит нескольким причинам, которые как я уверен игнорирует слишком много программистов. Мое правило таково: «везде должно использоваться const, кроме тех случаев, где оно не может быть использовано». Я мечтаю о том, чтобы все переменные в C++ по умолчанию были const. Doom почти всегда придерживается политики «без входно-выходных» для параметров; имеется в виду, что все параметры передаваемые в функцию являются или вводом, или выводом, и никогда не совмещают эту роль в одном лице. Этот нехитрый прием позволяет куда как быстрее видеть, что происходит с какой-нибудь переменной, когда вы передаете ее в функцию. К примеру:

image

Одно лишь определение этой функции уже делает меня счастливым!

Лишь из нескольких вещей, которые сразу бросаются мне в глаза, уже становится понятно очень многое:
  • idPlane передается функции как неизменяемый аргумент. Я могу спокойно использовать эту же плоскость после вызова этой функции без проверки на изменение idPlane.
  • Я знаю, что эпсилон не будет изменено внутри функции (несмотря на то, что оно может быть без проблем скопировано в другую переменную и использовано для ее инициализации — такой способ будет непродуктивным)
  • front, back, frontOnPlaneEdges и backOnPlaceEdges — это ВЫХОДНЫЕ переменные. В них будет осуществляться запись.
  • финальный модификатор const после списка параметров — мой самый любимый. Он указывает на то, что idSurface::Split() не сможет изменить саму поверхность (surface). Это одна из моих самых любимых возможностей в С++, по которой я так скучаю в других языках. Она позволяет мне вытворять подобное:
    void f(const idSurface &s) {
    s.Split(....);
    }

    если Split не была бы определена как Split(...) const; этот код бы не скомпилировался. Теперь я всегда буду знать, что при любом вызове f() не изменит поверхность, даже если f() передается поверхности другой функцией или вызывает какой-либо из методов Surface::method(). Const говорит мне о многом насчет этой функции и также дает намеки насчет общей архитектуры системы. Одно чтение объявления этой функции дает понять о том, что поверхности могут разделяться плоскостями динамически. Вместо изменения исходной поверхности, нам будут возвращены новые поверхности — передняя и задняя, а также, возможно, боковые frontOnPlaneEdges and backOnPlaneEdges.


Правило употребления const и отсутствие «входных-выходных» параметров в моей оценке — одна из самых важных вещей, отделяющих хороший код от восхитительного. Подобный подход делает проще не только понимание самой системы, но и ее изменение или рефакторинг.

Минималистичные комментарии

Данный пункт, конечно, все-таки больше касается вопросов стиля написания кода, но тем не менее — в Doom есть такая замечательная вещь, как отсутствие излишнего комментирования. Я в своей практике встречал слишком много кода, весьма похожего на подобный:
image
Такие приемы, на мой взгляд, весьма и весьма раздражающая штука. Почему? Потому что я и так смогу назвать, что делает этот код, — достаточно лишь взглянуть на его имя. Если же мне неясно назначение метода из его названия, то его название должно быть изменено. Если название слишком длинное — сократите его. Если оно не может быть изменено и так уже сокращено — что ж, тогда можно и использовать комментарий. Всех программистов со школьной скамьи учат, что комментирование — это хорошо; но это не совсем так. Комментарии — это плохо, до той поры, пока они не станут необходимы. А необходимы они крайне редко. Создатели Doom проделали ответственную работу для того, чтобы свести число комментариев к минимуму. Используя в качестве примера idSurface::Split(), посмотрим на то, как она закомментирована:

// разделяет поверхность на переднюю и заднюю поверхности, сама поверхность остается неизменной
// frontOnPlaneEdges и backOnPlaneEdges опционально хранят индексы вершин, которые лежат на краях разделяющей плоскости
// возвращает SIDE_?

Первая строка полностью является лишней. Мы уже все это знаем из определения функции. Вторая и третья строки несут некоторую новую информацию. Мы могли бы убрать вторую линию, но это может вызвать потенциальную неоднозначность.

Большей частью код Doom весьма суров по отношению к собственным комментариям, что делает его гораздо проще для восприятия. Я знаю, что это может быть делом стиля для некоторых людей, но мне кажется, что определенно существует «правильный» способ сделать это. Например, что должно приключиться если кто-нибудь изменит функцию и удалит константу в конце? В таком случае, для внешнего кода поменяется вызов функции, и теперь комментарий будет несвязан с кодом. Посторонние комментарии вредят читабельности и аккуратности кода, так что код становится хуже.

Отступы

Doom не склонен тратить понапрасну свободное вертикальное пространство экрана.
Вот пример из t_stencilShadow::R_ChopWinding():

image

Я могу прочитать весь алгоритм без проблем, потому что он помещается на 1/4 моего экрана, оставляя другие 3/4 для того, чтобы понять, какое может этот код иметь отношение к окружающему его. Я за свою жизнь видел слишком много подобного:

image

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

Второй способ занимает 18 строк по отношению к 11 строкам в первом. Почти в два раза больше строк кода при той же самой функциональности. К тому же, следующий кусок кода явно не поместится на моем экране. А что там в нем?

image

Этот код не имеет никакого смысла без предыдущего куска с циклом. Если бы id не экономили вертикальное пространство, то их код стал бы значительно сложнее для чтения, поддержки и сразу бы потерял в красоте.

Другая вещь, которую id решила принять как постоянное правило, я тоже настоятельно поддерживаю — это решение всегда использовать { }, даже когда это не является необходимым. Я повидал слишком много кода подобного этому:

image

Я не смог найти ни одного примера в коде id, где они хоть раз пропустили бы { }. Если опустить дополнительные { }, то разбор блока while() займет в разы больше времени, чем должен был бы. Кроме того, любая правка превращается в настоящее страдание — достаточно представить, что мне потребуется вставить if-условие на пути else if (c > d).

Минимальное использование шаблонов

id нарушили один из величайших запретов в мире С++. Они переписали все потребовавшиеся функции STL. Лично я состою с STL в отношениях «от любви до ненависти один шаг». В Dyad я использовал ее в отладочных билдах для управления динамическими ресурсами. В релизе я запаковал все ресурсы так, что их стало возможным загружать настолько быстро, насколько вообще можно, и они перестали использовать функционал STL. STL довольно удобная вещь благодаря тому, что она дает доступ к основным структурам данных; ее главная беда в том, что ее использование приводит к некрасивому коду и подвержено ошибкам. Например, взгляните на класс std::vector. Скажем, если мне нужно перебрать все элементы:

image

В C++11 то же самое выглядит куда как проще:

image

Лично мне не нравится использование auto, мне кажется, оно делает код проще для написания, но тяжелее для чтения. Я иногда использовал auto в прошлые годы, но сейчас мне кажется, что это было неправильным решением. Я не собираюсь сейчас даже начинать обсуждать нелепость некоторых из алгоритмов на STL, таких как std:for_each или std::remove_if.

Удаление значения из std::vector — тоже тот еще ужас:

image

Представьте, каждый программист каждый раз должен набирать эту строчку правильно!

id убирает всю двусмысленность: они выкатывают свои собственные базовые контейнеры, класс строки и т.д. Они стараются сделать их более конкретными, чем их аналоги в STL, — возможно, для того, чтобы сделать их проще для понимания. Они минимально шаблонизированы и используют свои собственные аллокаторы памяти. А код STL завален постоянным использованием шаблонов настолько, что его просто невозможно читать.

Код на С++ быстро становится неуправляемым и некрасивым, так что программистам постоянно приходится прилагать свои усилия для получения обратного эффекта. А чтобы вы поняли, насколько вещи могут далеко зайти, посмотрите на этот исходный код STL. Имплементация STL у Microsoft и в GCC — один из самых страшных исходных кодов из всех, что мне приходилось видеть. Даже если программист сдувает с кода шаблона любые пылинки, код все равно превращается в полный бардак. Для примера, взгляните на библиотеку Loki от Андрея Александреску, или на библиотеки boost – эти строки написаны одним из самых лучших программистов на С++ в мире, и даже его старания сделать их настолько красивыми, насколько это возможно, смогли выродиться лишь в некрасивый и совершенно нечитаемый код.

Как же решает эту проблемы id? Они просто не пытаются привести все к «общему знаменателю», сверх-обобщая свои функции. У них есть классы HashTable и HashIndex, первый обязывает тип ключа быть const char *, а второй — пару int->int. В случае С++, подобное решение принято считать плохим — «следовало бы» завести единый класс HashTable, и написать в нем две разные обработки для KeyType = const char * и <int, int>. Но то, что сделала id, также корректно, и более того — сделало их код в разы красивее.

Убедиться в этом совсем несложно, достаточно проследить контраст между «хорошим стилем программирования на С++» для генерации хэша и способом, которым с ней разобралась id.

Многим покажется неплохой мыслью создать специальный класс вычислений, который можно передать как параметр в HashTable:

image

это может быть задано как определенный тип:

image

Теперь вы можете передавать ComputeHashForType в качестве HashComputer для HashTable:

image

Похожим образом я сделал у себя. Выглядит умным решением, но… как же некрасиво! Что если мы стоклнемся с большим числом параметров в шаблоне? С аллокатором памяти? С отладкой? Тогда у нас получится что-то вроде этого:

image

Брутальное определение функции, не так ли?

image

Так к чему это все? Я едва ли смог бы найти имя метода без яркой подсветки синтаксиса. Вполне вероятно, что определение функции займет больше места, чем ее тело. Однозначно тяжело для восприятия и не слишком красиво.

Я видел то, как другие движки управляются с подобным беспорядком методом разгрузки задания аргументов функций с помощью миллиардов typedef-ов. Это же еще хуже! Может быть, код «прямо перед носом» и станет понятнее, но между системой и текущим кодом возникнет пропасть еще большая, чем была до этого, и этот код уже не будет указывать на дизайн всей системы — что нарушает наш же принцип красоты. Например, у нас есть код:

image

и

image

и вы использовали их вместе и сделали что-то вроде этого:

image

Возможно, аллокатор памяти StringHashTable под именем StringAllocator не способствует глобальной памяти, что может смутить вас. Вы должны будете просмотреть весь код, обнаружить, что StringHashTable на самом деле typedef из запутаных шаблонов, пройтись по исходному коду шаблона, обнаружить еще один аллокатор, найти его описание… кошмар, просто кошмар.

Doom идет вразрез с принципами логики C++: код написан настолько конкретным, насколько это вообще возможно, использует обобщения только там, где это имеет смысл. Что же делает HashTable из Doom, когда ему требуется сгенерировать хэш или еще что-нибудь? Он вызывает idStr::GetHash(), потому что единственный тип ключей, которые он принимает, это const char *. Что случится, если потребуется другой ключ? Мне кажется, что они шаблонизируют ключ и просто принудительно вызывают key.getHash(), а компилятор обеспечивает, что типы ключей имеют метод int getHash().

Остатки в «наследство» от С

Не знаю точно, сколько из программистов id в 90-ых работает в компании сейчас, но как минимум сам Джон Кармак имеет большой опыт программирования на С. Все игры id до Quake III были написаны на С. Мне встречались программисты С++, которые не имели большого опыта программирования на С, так что их код был слишком С++зирован. Прошлый пример был лишь одним из множества — вот другие, которые встречаются мне довольно часто:

  • слишком частое использование get/set методов
  • использование stringstream
  • чрезмерная перегрузка операторов.

id строго следит за всеми этими случаями.

Часто бывает, что кто-то создает класс подобным образом:

image

Это пустая трата строк кода и последующего времени на его чтение. Такой вариант съест больше вашего времени, чем

image

А что если вам часто приходится увеличивать var на некоторое число n?

image

в сравнении с

image

Первый пример гораздо проще для записи и чтения.

id не использует stringstream. stringstream содержит одну из самых главных «бастардизаций» перегрузки операторов, которую мне доводилось встречать: <<.

К примеру,

image

Это некрасиво. У подобного способа есть сильное преимущество: можно определить эквивалент функции toString() из Java для определенного класса, который затронет переменные класса, но синтаксис станет слишком неудобным, и id принимает решение не использовать данный метод. Выбор в пользу printf() вместо stringstream делает код более простым для восприятия, и я считаю этот выбор правильным.

image

Гораздо лучше!

Синтаксис оператора << для SomeClass доходит до смешного:

image

[Примечание: Джон Кармак однажды заметил, что программы статистического анализа кода помогли выяснить, что их общий баг был вызван некорректным соответствием параметров в printf(), Интересно, перешли ли они на stringstream в Rage из-за этого?.. GCC и clang оба выдают сообщения о подобной ошибке в случае использования флага -Wall, так что вы все можете увидеть сами, не прибегая к дорогостоящим анализаторам для поиска данных ошибок.]

Еще один принцип, который делает код Doom таким красивым, это минимальное использование перегрузки операторов. Это очень популярная и удобная функция, появившаяся в С++, позволяет вам делать что-то вроде этого:

image

Без перегрузки эти операции станут менее очевидными и потребуют больше времени на написание и чтение. Здесь Doom и останавливается. Я видел код, которые идет дальше. Я видел код, который перегружает оператор '%' чтобы обозначить скалярное произведение векторов, или оператор Vector * Vector, который выполняет умножение векторов. Бессмысленно заводить оператор * для подобного действия, которое будет осуществимо только в 3D. Ведь если вам захочется сделать some_2d_vec * some_2d_vec, то что вы прикажете делать? А что вы скажете насчет 4d или больше? Вот почему принцип минимального вмешательства от id правилен — он не оставляет нам разночтений.

Горизонтальные отступы

Одной из самых важных вещей, которые я узнал из исходного кода Doom, стала простая смена стиля. Я привык, что мои классы выглядят примерно так:

image

По стандарту кода для Doom 3, id используют реальную табуляцию, которая соответствует 4 пробелам. Одинаковая табуляция по умолчанию позволяет всем программистам выравнивать определения их классов по горизонтали без лишних раздумий:

image

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

image

Сам я против излишнего набора символов на клавиатуре. Главное, что мне надо — это сделать свою работу настолько быстро, насколько это возможно — но в данной ситуации небольшой перебор с набором текста при определении класса окупается не раз и не два в том случае, когда программисту приходится просматривать определение класса. Есть еще несколько примеров стиля кодинга, которые описаны в документе Doom 3 Coding Standards (.doc), ответственному за все красоты исходного кода Doom 3.

Имена методов

На мой взгляд, правилам именования методов в Doom все-таки чего-то не хватает. Лично мне нравится использовать в своей работе такое правило: все имена методов должны начинаться с глаголов, и исключения составляют только те случаи, когда это не может быть осуществлено.

Например:

image

гораздо лучше, чем:

image

Да, он невероятно красив.

Я рад тому, что эта статья увидела свет, потому что она позволила мне задуматься на тему того, что же мы подразумеваем под красотой кода. Если честно, я не уверен, что я понял хоть что-то. Вполне возможно, что все мои оценки слишком субъективны. Лично для себя я отметил как минимум пару очень важных вещей — стилистику отступов и постоянное использование констант.

Множество из выборов в стилистике кода — это мои личные предпочтения; не сомневаюсь, что у других программистов они окажутся совсем другими. На мой взгляд, груз выбора стиля написания кода полностью лежит на плечах того, кто собирается его писать и читать, но и об этом следует время от времени задумываться.

Я бы хотел посоветовать всем заглянуть в исходный код Doom 3, потому что такой исходный код увидите не каждый день: тут вам полный набор, от дизайна архитектуры системы до табуляции между символами.

Шон МакГраф (Shawn McGrath) — игровой разработчик, проживающий в Торонто, создатель популярной психоделической игры для Playstation 3 — паззла-гонки Dyad. Советуем взглянуть на его игру и подписаться на него в Твиттере.

Примечания

Прим. Джона Кармака

Спасибо! Несколько комментариев:

Я продолжаю думать, что в определенном роде код Quake 3 все же лучше, так как стал вершиной эволюции моего С-стиля — в отличие от первой попытки программирования движка на С++, но это может быть лишь моей иллюзией благодаря небольшому количеству строк в первом, или же благодаря тому, что я уже не заглядывал в него с десяток лет. Я думаю, что «хороший С++» лучше, чем «хороший С» с точки зрения читабельности, при этом в остальном языки эквивалентны.

Я порядком «напетлял» с С++ в Doom 3 — дело в том, что я был опытным программистом на С с умениями в ООП, оставшимися со времен NeXT и Objective-C, так что я начал писать код на С++ без полного изучения всех принципов использования языка. Оглядываясь назад, я могу заметить, что сильно жалею о том, что не прочитал Effective C++ и еще кое-что по этой теме. Пара других программистов имела достаточный опыт на С++, но они большей частью следовали моим стилистическим выборам.

Я не доверял шаблонам много лет, да и сейчас использую их с опаской, но я как-то решил, что прелести строгой типизации перевешивают чашу весов в сторону, противоположную странному коду в заголовочных файлах. Так что споры вокруг STL все еще не утихают у нас в id, и теперь они получили дополнительного «огонька». Возвращаясь к тем дням, когда начиналась разработка Doom 3, я могу практически наверняка сказать, что использование STL определенно стало бы неудачной идеей, но сейчас… появилось много разумных аргументов «за», даже и в случае игр.

Сейчас я стал страшным «const nazi», и я отчитываю любого программиста, который не делает переменную или параметр константой, если они могли бы быть ею.

Касаемо лично меня — моя собственная эволюция направляет меня в сторону более функционального стиля программирования, что подразумевает отучивание от большого числа старых привычек и отход от некоторых приемов ООП.

[www.altdevblogaday.com]

Прим. переводчика

Я сам наткнулся на блог Фабиена около полутора лет тому назад, и могу смело порекомендовать его всем интересующимся — если и не ради вдумчивого чтения, то хотя бы ради вдохновения.
По поводу «чистого» кода — в Твиттере я спрашивал не так давно у Кармака, чего бы он порекомендовал почитать по теме. Он настоятельно советовал книгу «Art of the Readable Code» (Amazon).
Поделиться публикацией
Похожие публикации
Ой, у вас баннер убежал!

Ну. И что?
Реклама
Комментарии 245
  • +87
    Я видел код, который перегружает оператор '%' чтобы обозначить продукт с точкой

    это dot product что ли? вашу ж мать, переводчики…
    • +19
      Скалярное произведение возвращено на законное место, спасибо за замечание.
      Я не сталкивался с английским написанием ранее, my bad.
      • +18
        ну и «программы статистического анализа кода» вдогонку. Статического!
        • 0
          Тогда уж вдогонку: не «умножение векторов», а векторное произведение.
        • 0
          И не «вторую линию», а «вторую строку»
        • +13
          Второй закон кододинамики: энтропия кода не убывает.
          • +86
            Для повышения читаемости кода автору можно только посоветовать включить антиалиасинг, сменить шрифт и цветовую схему.
            • +4
              Так в оригинале статьи.
              Но в целом да — весьма странно писать о читабельности кода, и не позаботиться о нормальных шрифтах. Черные куски кода на светлом фоне режут глаз, фигурная скобка слабо отличается от круглой (особенно чудесно смотрится в K&R стиле).
              • +29
                меня больше радует примеры кода в png
              • +8
                Шрифты — это вообще отдельная тема войны. Я вот все хочу увидить грамотный срач статью на эту тему на хабре :)
                • +1
                  Он тут хотя бы моноширный. У меня есть книга по C++, где автор (издатели?) решили не делать его таким. Через час чтения вставки кода превращаются в не читаемый комок текста.
                  • НЛО прилетело и опубликовало эту надпись здесь
                • +1
            • +14
              Сложилось впечатление, что автор назовет прекрасным любой код, обработанный code beautifier'ом с нужными настройками :)
              • +3
                Дело не в форматировании, а в самой архитектуре проекта.
                • +5
                  В статье про это очень мало.
              • +35
                Интересно было почитать, спасибо за перевод!
                Вообще, при чтении таких статей нужно понимать, что многие вещи субъективны и являются делом вкуса.
                Например, расстановка фигурных скобок в таком виде:

                if(smth) {
                    doSmth();
                } else if(smthElse) {
                    doSmthElse();
                }
                

                Аргумент атвора — экономия вертикального места на экране. Но это также и дело привычки — например, для моих глаз намного читабельнее текст с бОльшими вертикальными отступами:

                if(smth)
                {
                    doSmth();
                }
                else if(smthElse)
                {
                    doSmthElse();
                }
                

                Читать гораздо приятнее. Исключительно ИМХО.

                По поводу написания комментариев в коде и вынесения всего, что только можно, в отдельные функции — если сранивать с более длинными функциями, разделёнными на «подсекции» при помощи комментариев-подзаголовков, то это просто-напросто два разных подхода к организации кода. Мы к такому выводу пришли в нашей команде после пары недель ожесточённых споров :)
                • –11
                  Поддерживаю Shedal. Код 2го вида более читабельный!
                  • +1
                    Когда «плоская» функция разрастается в длину и делится на «секции» комментариями — это нормально. Главное, чтобы глубина циклов-условий не была по пять уровней. :)
                    • +2
                      Конечно более читаемый, мозгу не надо выискивать где какая скобка кончается и начинается ли другая. Автор может не очень богат и не может себе позволить монитор full hd, который поворачивается вертикально и места становится просто завались. да т 1200пх вполне достаточно.
                      • +6
                        Соглашусь с Вами, у нас половина разработчиков сидит на full hd мониторах с вертикальной ориентацией — очень удобно.
                        Еще была шутка: «Вы там говорили, функция должна помещаться на одном экране? У меня помещается». :)
                        • +11
                          Не надо выискивать скобки. Надо выискивать отступы — это гораздо быстрее. А скобки всегда будут располагаться либо в начале либо в конце строки.
                          • +3
                            чувствуется голос питон-адепта :)
                            • 0
                              А вот и нет. Python уважаю, но практически на нём ничего не пишу, в основном Java.

                              Ещё вот что — обычно расцветка синтаксиса в различных редакторах такова, что скобки как можно сильнее выделяются из фона (в этой статье ярко-красные, например). Я, напротив, использую цветовые схемы, где скобки от фона отличаются слабо (например, светлый серо-голубой на белом фоне), именно потому что нет нужды, чтобы глаз на них спотыкался.
                              • –4
                                Тю, такое скажет и лиспер, и хаскелист, и рубист, и ассемблероид… Везде принято делать отступы и ориентироваться по ним. Что касаемо расстановки скобок, то один мой коллега предложил простой и логичный вариант — ставить их тогда, когда тело оператора содержит более одной строки. То есть:

                                foreach (item; list)
                                    process(item);

                                но

                                foreach (item; list)
                                {
                                    if (!item.isProcessed())
                                        process(item);
                                }

                                Соответственно, пример из топика будет выглядеть так:

                                while (a)
                                {
                                    if (b > c)
                                        d = c;
                                    else if (c > d)
                                        e = f;
                                    else
                                    {
                                        if (q)
                                            a = 0;
                                        else
                                            b = 0;
                                    }
                                }
                                • +2
                                  Любители опускать скобки при однострочном теле часто пишут в одну строку вообще, т.е.:
                                  foreach (item; list) process(item);

                                  if (b > c) d = c;
                                  else if (c > d) e = f;

                                  в вашем случае
                                  • –1
                                    Так а я-то тут причём, мне за что минусы? Кто пишет в одну строчку — Страуструп им судья, я так не делаю.
                                    • 0
                                      Я без понятия, за что минусы, более того я вас даже не осуждал, это помоему исключительно вкусовшина… просто констатация :)
                          • +17
                            По итогам голосования на моём комментарии и карме можно видеть, что 2/3 людей любят переносить открывающую фигурную скобку, 1/3 не любит, и 1 человек ОЧЕНЬ не любит :)
                            • +6
                              По итогам голосования на моём комментарии

                              На самом деле сказалось то, что люди охотнее ставят «плюс», чем «минус». Я вот не люблю, а минусовать даже не думал. Скорее всего, если бы любил, то плюсанул бы. На самом деле стронников каждого из вариантов практически равное количество.
                              • 0
                                Мой комментарий о голосовании — полушутка. Понятное дело, что здесь много факторов, и одним из основных является общепринятое форматирование для конкретного языка форматирования.
                                • 0
                                  общепринятое форматирование для конкретного языка форматирования

                                  Не встречал такого ни для одного из языков. Соответствующие стандарты так же молчат на эту тему.

                                  Что касается открывающих скобок, расположенных на строке с выражением, то контраргументом, наверное будет то, что если кому-то мешает открывающая скобка на новой строке, потому как код метода не влезает в монитор, то может стоит пересмотреть код метода и уменьшить лапшеобразность?
                                  • +2
                                    вроде как java code style рекомендует не переносить фигурные скобки, хотя я это не соблюдаю )
                                    • +2
                                      А как же Python и его pep8?
                                      • +4
                                        Для C#, в примерах кода как в конвенциях от MS, так и в MSDN, открывающая фигурная скобка всегда переносится на новую строку. То же самое — во всех проектах на C#, что я видел.
                                        Это я и называю общепринятым форматированием — когда в подавляющем большинстве кода есть какая-то закономерность.
                                  • 0
                                    По итогам голосования на моём комментарии и карме можно видеть, что 2/3 людей любят переносить открывающую фигурную скобку, 1/3 не любит, и 1 человек ОЧЕНЬ не любит :)

                                    Интересно, что «проголосовало» уже втрое больше участников, но соотношение между «остроконечниками» и «тупоконечниками» всё время сохранялось :)
                                  • +3
                                    Почему-то принято считать, что первый стиль — для Джавы, второй — для Си, Си-шарпа. Иногда, к сожалению, доходит до холиваров.
                                    • 0
                                      Наверное, потому, что официальные рекомендации по кодированию на этих языках так написаны?
                                    • +6
                                      Но это также и дело привычки — например, для моих глаз намного читабельнее текст с бОльшими вертикальными отступами Читать гораздо приятнее. Исключительно ИМХО.

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

                                      Менее опытный же требует «пространства», разделения програмы на мелкие кусочки, выделения ключевых слов. Я и сам раньше любил код с бОльшими вертикальными отступами, но чем дальше, чем больше стараюсь «экономить» вертикальное место и, при этом, не расти вширь
                                      • +1
                                        Ну, вы, наверное, судите только по себе. Я знаю людей с 15-летним стажем, которые читают код как книжку, и любят, чтобы он был «свёрстан» эстетично — со смысловыми разносами блоков, и т.п. В том числе, используют и перенос открывающей фигурной скобки на новую строку.
                                        Думаю, что всё-таки, это дело привычки и вкуса.
                                        • +4
                                          которые читают код как книжку, и любят, чтобы он был «свёрстан» эстетично — со смысловыми разносами блоков

                                          Странная манипуляция смыслом. Кто сказал, что компактный код свёрстан не эстетично?)
                                          • +1
                                            Эстетика — тоже субъективное понятие. Всё, что я хочу сказать во всех своих комментариях — что здесь нет объективного критерия. Некоторым нравится так, некоторым так, каждому по своим причинам.
                                            • +1
                                              Аналогично я только предположил, что более опытные программисты могут одновременно оценивать взглядом большие куски кода
                                              • +1
                                                Выходит, что мы с вами не спорим ;-)
                                                • +3
                                                  Просто мирно общаемся)
                                          • –2
                                            Эстетика кода начинается только после его компиляции (или при его выполнении, если это скрипт). Эстетика — в искусстве управления машиной!
                                            Когда через каждые три машинные команды воткнуты call'ы в отладочную процедуру, а каждая пустяковая процедура обернута каскадом из push и pop — это неэстетично.

                                            Еще пример: Регулярные выражения: PHP(POSIX) vs Perl. Ускорение 60-200%
                                            • 0
                                              Ну, есть, конечно, и код, который пишется для машины. Но большая часть кода пишется для людей — разработчиков, которые будут этот код поддерживать.
                                              • +4
                                                «Programs must be written for people to read, and only incidentally for machines to execute.» — Abelson & Sussman, 1985
                                      • –2
                                        Также выскажусь в поддержку второго варианта — симметрично расположенные скобки позволяют глазам «цепляться».
                                        Я использую этот вариант даже в Java, хотя «родным» там все таки считается первый вариант.
                                        Еще скажу кое-что о правой границе: очень маленькая граница в купе с использованием первого варианта делают код почти не читаемым. Для примера посмотрите на исходный код Android. Правая граница у них приблизительно 100 символов (странно что не 80 :D) — это 1/3 ширины моего рабочего стола. Понимаю, что у них наверное стандарт такой, но стандарт должен отвечать текущим требованиям, а не требованиям времен текстового режима.
                                        • +2
                                          Извините, но 80 символов — это очень много. Когда у меня код даже приближается к этой отметке, то я уже думаю, что с ним не так. Сам стараюсь делать не больше 60-ти. Широкий код в большинстве случаев признак плохой архитектуры.
                                          • –9
                                            Не вижу тесной связи между шириной кода и его архитектурой.
                                            А впрочем смотрите сами:
                                            image
                                            Это часть конструктора класса android.view.View
                                            • +12
                                              Рефакторинг по этому куску плачет.
                                              • –2
                                                Ok. Я вижу есть много сторонников такого стиля :)
                                                Оставим рефакторинг в стороне и просто переформатируем код:
                                                image
                                                • +21
                                                  Остался всё таким же отвратительным)
                                              • +4
                                                Я, пожалуй, с вами соглашусь, но C++ здесь явное исключение, для него соблюдать ширину в 80 символов без переносов ну никак не получится. С их-то шаблонами, unordered_map-ами да неймспейсами, а уж, если еще и итераторы сюда добавить.
                                                • +1
                                                  Хотя auto немного облегчает дело.
                                                • +1
                                                  >> Широкий код в большинстве случаев признак плохой архитектуры.
                                                  Не забывайте ставить «ИМХО».

                                                  В C#/.NET с его naming guidelines в 60 ничего не уместится. Чаще вижу ограничение в 120.
                                              • +2
                                                Я пользовался bsd-стилем (видимо, дело в том, что первый язык был паскаль), пока не начал коммитить в проект с более компактной версией. Компактность рулит.
                                                • –6
                                                  а можно же ещё и так, если одна строка

                                                  if(smth) doSmth();
                                                  else if(smthElse) doSmthElse();
                                                  • +6
                                                    А можно еще if(smth) doSmth(); else if(smthElse) doSmthElse();
                                                    • –3
                                                      Возможно…
                                                      if(a<b) a++; else if(a>b) a--; else break;
                                                      Зачем её разматывать на 12 строк? Это же идиома :)

                                                      А еще можно
                                                      smth? doSmth(): smthElse? doSmthElse(): 0;
                                                      — будет короче, чем через if :) Если, конечно, компилятор не будет ругаться на выражение без побочного эффекта :)
                                                  • +4
                                                    да там в каждом абзаце праздник

                                                    с условиями и фигурными скобками автор окончательно запутался, так и не разобравшись, насколько важна экономия места.

                                                    в целом правила «для того, что мне нравится, приводим хорошие примеры, для остального — плохие» придерживается строго.

                                                    ну а на расскаже о прекрасности printf я просто заплакал. я сам стрингстримом не пользуюсь, но рассказывать про великолепие printf — это уже перебор. особенно удобен и прекрасен он, когда начинаешь пересчитывать проценты и сопоставлять их с параметрами.
                                                    • +1
                                                      Я понимаю, что Вы написали про «исключительное ИМХО», но все равно никогда не мог понять, что в этом стиле может быть такого приятного. Открывающаяся фигурная скобка является чистым синтаксическим мусором, поскольку фактически закрывающаяся скобка соответствует самому оператору, а не фигурной скобке. Именно поэтому открывающаяся скобка никому не нужна (кроме удовольствия компилятора) и хочется засунуть ее в самое неприметное место. Т.е. если бы вместо закрывающейся скобки надо было писать fi для if и rof для for, то было бы гораздо логичнее (нотация Гриса).
                                                      • +1
                                                        Дело, наверное, не в самой скобке, а в визуальных отступах между строками. Я даже когда на JS пишу, и открывающую скобку не переношу на новую строку, всё равно следующую строку оставляю пустой.
                                                        Для меня это что-то вроде логического отступа между заголовком блока и его телом.
                                                        Плюс, когда обе скобки находятся на одном уровне, глаз их мгновенно схватывает. Впрочем, это уж точно исключительно дело привычки.
                                                    • 0
                                                      Статья — красота! Спасибо!
                                                      • +5
                                                        Всегда считал, что красота кода заключается не в выборе способа расстановки скобок или форматирования, а в его сруктуре — разбиении на классы, модули, выделение ключевых компонентов, что в результате приводит к простоте и ясности, отсутсвию лишнего кода и быстроте, как следствию. А то о чем написал автор в большой сепени сугубо индивидуально. Особенно то, что касается форматирования. В общем, анализ тут очень поверхностный, имхо.
                                                        • 0
                                                          А я думаю что эти вещи связаны. Пусть и не прямой зависимостью, но она есть.
                                                      • +5
                                                        Как-то поверхностно выглядит статья, например абзац про комментарии.
                                                        С большинством дифирамбов мне как-то трудно согласиться. Например, с хвалой форматированию кода. Мне кажется, что это вкусовщина. Меня, например раздражают открывающие скобки в той же строке или несколько выражений, записанных в одной строке. Но мне не лень нажать две кнопки в IDE и привести форматирование любого исходника к удобному для меня виду.
                                                        На мой взгляд, трюки, вроде ...split(..., const float epsilon, ...) только снижаю читабельность, потому как, подсознание нашептывает, что epsilog передается по ссылке, но не модифицируется, ан нет.
                                                        • 0
                                                          А вообще, зачем это const float? Как будто float может измениться, странно смущает меня это почему-то.
                                                          Про выравнивание. Есть два понятия: indentation и alignment, вот для первого табы — самое оно, а второе делать табами — сомнительно, потому, что кто-то может не любит табы в 4 пробела, сделает в 2, и все поедет к черту.
                                                          • +9
                                                            А вообще, зачем это const float? Как будто float может измениться, странно смущает меня это почему-то.

                                                            Это защита не от изменения переменных по отношению к вызываемой функции, а гарантия того, что программист ничего не перепутает и не начнет пользоваться переменной внутри самой функции кроме как в виде входного параметра. Т.е. таким образом четко разделяются входные параметры от переменных функции, чтобы увеличить читаемость кода.
                                                            • +3
                                                              Понятно. Теперь я тоже подумываю стать const nazi.
                                                              • 0
                                                                Ясно. Интересная практика.
                                                                • 0
                                                                  Однако, если понадобится как-то изменить этот параметр для дальнейших вычислений, то придется заводить новую переменную.
                                                                  • +10
                                                                    Да, но:
                                                                    1. Измененная переменная будет иметь уже какой-то другой смысл, поэтому ей подойдет другое название.
                                                                    2. Программист должен будет сделать это явно (компилятор заставит), он не ошибется случайно.
                                                                    3. Будет доступ и к оригинальному и к измененному значениям, что удобно.
                                                                • +3
                                                                  У них свой стиль кода по всему проекту.
                                                                  Один из пунктов Use ‘const’ as much as possible, я думаю вы поняли.
                                                                  Что значит «кто-то может не любит»? Опять же у них свой стиль…
                                                                • +1
                                                                  финальный модификатор const после списка параметров — мой самый любимый. Он указывает на то, что idSurface::Split() не будет изменен самой поверхностью (surface).
                                                                  Небольшая неточность: не «не будет изменен самой поверхностью», а «не будет изменять саму поверхность».
                                                                  Автор имеет в виду, что const-метод не может менять объект, для которого вызван. Хотя полностью надеяться на это нельзя, поскольку mutable-переменные никто не отменял.


                                                                  За последние 6 недель разработки Dyad я дописал 13k строк кода.
                                                                  Вот это скорость! При стандартном 8-часовом рабочем дне получается примерно 1 строчка кода в минуту.
                                                                  Видимо, проект так нравился автору, что он его и дома не оставлял ;)
                                                                  • 0
                                                                    Вот это скорость!

                                                                    Судя по комментам автора по поводу его кода — там качество не ахти. Так что вполне возможно, что это куча копипасты, а реального кода там треть.
                                                                  • 0
                                                                    > Он указывает на то, что idSurface::Split() не будет изменен самой поверхностью (surface).

                                                                    Ээ, чего? Функция изменяется поверхностью?
                                                                    Может, наоборот, а?
                                                                    • +6
                                                                      Любопытно, почему это геттеры-сеттеры лишние. Понадобится ввести валидацию входных данных — это будет намного проще ввести с сеттером, чем лазить по всему коду и ставить проверки где нужно. Я лично стараюсь вводить геттеры-сеттеры даже тогда, когда уверен что эта переменная может быть свободно модифицирована и считана.
                                                                      • +2
                                                                        А понадобится многопоточный доступ к полю сделать так вообще здорово с сеттерами-геттерами.
                                                                        Еще бывает нужно логгировать доступ к полю. Главным образом для дебага, но все же.
                                                                        • +2
                                                                          Если так подумать то если понадобится валидации или что-то еще, то можно сделать сетер и гетер, когда весь исходный код под контролем не должно быть с этим проблем. Искать все места по коду чаще всего довольно просто и из-за статической типизации компилятор не допустит того что вы где-то забудете поменять обращение к переменной на метод (в отличие например от JavaScript). Ведь действительно очень часто геттеры/сеттеры остаются тривиальными всю жизнь, сколько лишнего кода приходится добавлять из-за принципа, которое прививают С++ программистам чуть ли не с детского сада.
                                                                          • +1
                                                                            Эх, дело-то в том, что по C++ плачет нормальная IDE, способная делать с кодом на С++ всё то, а лучше больше, что может делать с кодом на Java IntelliJ IDEA. Тогда и сеттеры-геттеры будут автоматически в код вставляться (при первом создании класса), и все обращения к полю будут мгновенно корректироваться (при добавлении геттеров-сеттеров впоследствии) — и многое другое. Вряд ли кто-то станет спорить, что код на C++ довольно шумный — и очень многое может сделать IDE автоматически. Надежда вот на Clang — что его инструменты развяжут руки разработчикам плагина к той же IDEA, QtCreator'а, KDevelop'a и пр.
                                                                            • –2
                                                                              Если код написан так, что его поддержка в notepad более затруднительна, чем в специальной IDE — это распространённая и печальная проблема.
                                                                              • 0
                                                                                Что-то мне подсказывает, что поддержка кода любого приложения, для которого имеет смысл понятие «поддержка», в notepad более затруднительна, чем в специальной IDE. Как бы профессионально этот код не был выполнен.
                                                                                • 0
                                                                                  Так обычно и бывает. Но это в первую очередь проблемы средств языка и качества кода, нежели проблемы среды разработки. Программисты слишком часто полагаются на возможности IDE там, где простого текстового редактора в сочетании с grep могло бы хватить. Я имел удовольствие работать с сорцами на несколько миллионов строк plain C, где этого было достаточно.
                                                                                  • 0
                                                                                    Еще немного, и мы выясним, что «простой текстовый редактор» — это vim или emacs, а под «работой с сорцами» и «поддержкой» понимается внесение незначительных изменений в части проекта, надо которыми вы уже давно и много работаете — и знаете их почти наизусть :) Никто не спорит, что хороший код поддерживать легче. Но совершенно ясно, что в первоначальной форме ваше заявление, мягко говоря, абсурдно.
                                                                                    • 0
                                                                                      Примерно столь же абсурдно, как и изначальный посыл, что code bloat, сгенерированный IDE — не code bloat, а очень даже мило и хорошо. Такая формулировка намеренна.

                                                                                      P.S. Да, признаюсь, «простой текстовый редактор» — это vim :)
                                                                                      • 0
                                                                                        Я бы на месте vim обиделся :)
                                                                              • +1
                                                                                Кстати, JetBrains по-тихому уже пилят задел для C++ IDE, в рамках AppCode (C++ она тоже должна поддерживать же)
                                                                                • 0
                                                                                  Мда… Кастомный поиск всегда печальный: C++ (JetBrains), C++ (google, site: JetBrains). У второго по первой же ссылке можно найти пункт «C/C++ Support» для AppCode — и звучит он довольно вкусно. Надо попробовать :)
                                                                                  • 0
                                                                                    Ыыый, угораздило не обратить внимание: только для маков эта IDE. Не попробую…
                                                                                • –1
                                                                                  Процесс превращения явы в ту многословную хрень, которой она является сейчас, во многом произошел из-за введения этих самых IDE-фишечек вместо нормальной эволюции языка. Никакому языку не надо такого счастья, в особенности С++.
                                                                                  • 0
                                                                                    Для С++ нормальная поддержка IDE невозможна, потому что грамматика языка контекстно-зависима. Подробности есть в C++ FQA
                                                                                    • 0
                                                                                      Не совсем так: для нормальной поддержки С++ IDE должна иметь полноценный парсер C++. В частности, одной из целей создания Clang была нормальная поддержка С++ IDE.
                                                                                      Одной из главных задач Clang является поддержка инкрементной компиляции, позволяющей более тесно интегрировать компилятор и графический интерфейс среды разработки, в отличие от GCC, который был создан для работы в классическом цикле «компиляция-линковка-отладка». В отличие от GCC, ориентированного преимущественно на кодогенерацию, Clang стремится предоставить универсальный фреймворк для парсинга, индексации, статического анализа и компиляции языков семейства Си. В частности, Clang не производит упрощений исходного кода на этапе парсинга (как это делает GCC), гарантируя точное воспроизведение исходного текста в AST.
                                                                              • +1
                                                                                Всегда думал что функции типа serVar(..) и getVar() создаются только если надо контролировать значение.
                                                                                Например чтобы переменная никогда не была равна нулю.
                                                                                Поэтому думаю что не всегда следует использовать переменную в public.
                                                                                • +1
                                                                                  Если не изменяет память то вообще не рекомендуется использовать паблик-переменные. Они потенциально опаснее, ибо их модификации неконтролируемы.
                                                                                  • 0
                                                                                    вообще это дань инкапсуляции. Класс не должен показывать никакие свои поля во вне. Общение с объектом класса только через интерфейс. Это полезно не только для проверки переменной на ноль, но и возможностью в любой момент поменять в одном только методе getX() способ получения этого самого X без изменения другого кода программы. Ну и при наследовании всякое может случиться, в том числе и возможная перегрузка сеттеров и геттеров.
                                                                                  • 0
                                                                                    Некоторые моменты весьма спорны. Вот с const и с выравниванием табуляцией имен методов и переменных согласен.
                                                                                    • +9
                                                                                      довольно забавно, автор предлагает сделать все переменные константными, но при этом восхищается «открытыми» полями в классе. вообще мне кажется только в С++ какой-то особый культ поклонения const :)
                                                                                      • 0
                                                                                        Мм, а разве есть противоречие между этими двумя пунктами?
                                                                                        • +1
                                                                                          Есть. Открытые переменные могут быть модифицированы как угодно без заведомо созданной проверки значения. Константы — не могут быть модифицированы. Вот и противоречие.
                                                                                          • +1
                                                                                            Мне кажется автор имел в виду, что следует сделать все переменные константными по умолчанию, и открывать нужные для редактирования вручую. А открывать хоть паблики, хоть обычные переменные это не важно.
                                                                                            • –1
                                                                                              это все понятно, но пока такое не сделано делать поля в классе «открытыми»(при этом трепетно относясь к возможности случайного изменения переменных) нелогично. а вообще мне одному его предложение насчет всеобщей константности кажется бредовой? ошибок от случайной записи неконстанты в моей практики было ничтожно мало, зато утечек памяти и переполнения буфера хоть отбавляй. в других языках к этому относятся проще, зато здесь как будто это корень всех зол
                                                                                            • 0
                                                                                              class ClassType {
                                                                                              public:
                                                                                                  const int var;
                                                                                                  ClassType(int var) : var(var) {}
                                                                                              };
                                                                                              
                                                                                          • +3
                                                                                            не только в C++. Последнее время до всех доходит что иммутабельность — это хорошо :)
                                                                                          • +29
                                                                                            Возможно, я еще пожалею об этом, но напишу:

                                                                                            Я ненавижу else и else if в условиях, и пишу «ленивые» функции и циклы.
                                                                                            Ленивые — потому, что они хотят как можно быстрее завершиться.
                                                                                            оригинал
                                                                                            int check_param_and_do_someting(int param)
                                                                                            {
                                                                                            	if( param > 0 )
                                                                                            	{
                                                                                            		if( param % 3 == 0 )
                                                                                            		{
                                                                                            			//do something
                                                                                            			return 0;
                                                                                            		}else
                                                                                            		{
                                                                                            			return -2;
                                                                                            		}
                                                                                            	}else
                                                                                            	{
                                                                                            		return -1;
                                                                                            	}
                                                                                            }
                                                                                            
                                                                                            
                                                                                            "ленивый" вариант:
                                                                                            int check_param_and_do_someting(int param)
                                                                                            { 
                                                                                            	if( param <= 0 )
                                                                                            	{
                                                                                            		return -1;
                                                                                            	}
                                                                                            
                                                                                            	if( param % 3 != 0 )
                                                                                            	{
                                                                                            		return -2;
                                                                                            	} 
                                                                                            	
                                                                                            	//do something 
                                                                                            	return 0;	
                                                                                            }
                                                                                            

                                                                                            В этом случае нет многоступенчатой вложенности

                                                                                            В циклах — тоже самое. Сперва проверяются условия для break или continue, а потом идет сам код цикла.

                                                                                            Кажется, этому стилю есть какое-то название, но я его не смог выгуглить — придумал «ленивое».
                                                                                            • +5
                                                                                              Ага, но если это си, то все используемые ресурсы придется перед каждым ретурном освобождать руками. Вот вам и дублирование. Так что палка о двух концах.
                                                                                              • +4
                                                                                                А если на С, goto писать.
                                                                                                • +1
                                                                                                  Ну, даже если не вдаваться, насколько goto зло, то по большинству стайлгайдов оно запрещено. То есть велика вероятность, что не дадут.
                                                                                                  • +5
                                                                                                    Если умеешь пользоваться goto, то никакого зла за этим нет. Тем более, что вот именно в этом случае goto был бы к месту, ибо не мешает читаемости и даже наоборот.
                                                                                                    • +6
                                                                                                      На си goto cleanup общепринятый стандарт, который я встречал чаще, чем не встречал.
                                                                                                      • +1
                                                                                                        Про goto cleanup писали выше. Встречал не раз в весьма серьезных проектах (Linux и CPython, к примеру).
                                                                                                        • +1
                                                                                                          Его нужно аккуратно использовать. Не злоупотреблять. Так же как макросы, например. Он много где используется в крупных проектах, в ядре linux, например.
                                                                                                      • 0
                                                                                                        Речь скорее не о выходе из функции, а об инвертировании слишком разветвленных условий — тогда уменьшается степень вложенности.
                                                                                                        • 0
                                                                                                          > если это си, то все используемые ресурсы придется перед каждым ретурном освобождать руками

                                                                                                          Как и в исходном варианте.
                                                                                                        • 0
                                                                                                          я тоже так пишу, сразу видны предусловия функции. ну и, конечно, многоуровневая вложенность серьезно затрудняет чтение кода ИМХО
                                                                                                            • 0
                                                                                                              Тоже заметил за собой «сдвиг» в сторону подобного стиля: выявить «нестандартные» ситуации как можно раньше и выйти.
                                                                                                              И код более плоским и читабельным получается.
                                                                                                              • –1
                                                                                                                +1 Вот — редкий(?) пример пользы лени, в итоге выйдет более красивый машинный код. Только я бы писал if'ы еще ленивее: пишется и читается проще (прокрутка исходника туда-сюда из-за его длины по вертикали ворует время, следовательно — скрытые издержки и не Lean):
                                                                                                                int check_param_and_do_someting(int param)
                                                                                                                { 
                                                                                                                    if( param <= 0 ) return -1;
                                                                                                                    if( param % 3 != 0 ) return -2;
                                                                                                                    //do something
                                                                                                                    return 0;	
                                                                                                                }
                                                                                                                
                                                                                                                • +2
                                                                                                                  По объективным причинам всегда предпочтительнее писать условное выражение под условием. Например, bp на выражении поставить не получится (по крайней мере, в известных мне IDE).
                                                                                                                  • 0
                                                                                                                    Здравый смысл: кто ставит bp на return?

                                                                                                                    В случае сложных условных выражений — да, писать условие строкой выше резон есть, но опять же голос здравого смысла: а зачем тратить по строке на { и }?
                                                                                                                    • +3
                                                                                                                      Если нужно узнать, как и в какой ситуации завершилась функция — самое милое дело поставить breakpoint на все операторы return, и посмотреть, на каком из них она остановится. И разобраться, почему.
                                                                                                                      Кстати, C# позволяет ставить breakpoint на отдельный оператор. А C++ в той же студии — почему-то нет.
                                                                                                                  • 0
                                                                                                                    А можно к вам вопрос? Как часто вы оцениваете «красоту машинного кода», какими инструментами пользуетесь, чтобы до него добраться и/или оценить красоту, и… каковы критерии этой красоты? И еще, позвольте еще один вопрос: скажите, вы не пробовали писать на C++ код, который компилируется в машинный, содержащий стихотворения на hexspeak'е? Это было бы просто потрясающе, особенно если исходный код не содержит таких текстов — а именно получается в процессе компиляции! Заранее спасибо за ваши ответы.
                                                                                                                    • 0
                                                                                                                      Кстати сказать, стиль традиционный для Форта и для Перла. Очень удобный, поскольку позволяет сразу проверить и отсеять ошибочные аргументы. Еще удобнее инвертировать условие проверки, чтобы проверка превратилась в аналог охраняющего выражения (guard).
                                                                                                                    • +2
                                                                                                                      Автор мог бы просто прочесть code convention ftp.idsoftware.com/idstuff/doom3/source/CodeStyleConventions.doc
                                                                                                                      вместо того, что бы пытаться восстановить его по исходникам
                                                                                                                      • +9
                                                                                                                        да, и статья бы у него получилась очень интересная из одного предложения:
                                                                                                                        «почитайте на досуге code style conventions от id software — они классные»
                                                                                                                        • +1
                                                                                                                          Все его время ушло на восстановление code convention, он мог бы потратить его на действительно интересные вещи и написать статью о том, как устроен дум, интересные архитектурные решения, оптимизации и т.д.
                                                                                                                      • +5
                                                                                                                        Странно что программисты используют разделение данных и представления в своих продуктах, но не используют в своих инструментах. ИМХО, следить за отступами, пробельчиками, скобочками и др. вещами не влияющими на семантику кода должна IDE. Заставлять человека следить за этим — очень нерациональная трата ресурсов. Код должен хранить в репозитории в некотором «нормализированном» стиле, а показываться при непосредственной работе в соответствии с индивидуальными предпочтениями конкретного программиста.
                                                                                                                        • +7
                                                                                                                          В Doom 3 действительно очень хороший код, это уже ни раз обсуждалось. Как с точки зрения выбранного стиля, так и архитектурно.
                                                                                                                          Но большая часть того, что описывает автор в статье — результат жесткого придерживания корпоративному стилю, что и породило однообразие и удобочитаемость. Я могу привести другой пример отличного исходного кода, но с другим, во многом противоположным, соглашением — Ogre3D. Его читабельность не хуже, а как по мне — даже лучше.

                                                                                                                          В статье Кармак не зря упоминает«Эффективное использование C++» Скотта Майерса — настоятельно рекомендую к прочтению. Отпадут простые вопросы «зачем везде нужен const?», возможно, научитесь писать безопасные относительно исключений функции и многое другое.

                                                                                                                          Если пробежаться по пунктам статьи, я могу сказать следующее:
                                                                                                                          1) Мало комментариев — плохо. Много комментариев — тоже плохо.
                                                                                                                          Их должно быть ровно столько, чтобы убрать любые неоднозначности. Так же я за достаточно подробные комментарии в объявлении функции (тут, видимо, сказывается общая приверженность к инструменту для генерации документации — Doxygen).
                                                                                                                          Например, прочитав это объявление, даже незнакомый с остальной частью кода программист, сразу все поймет:
                                                                                                                                  /** Find all file or directory names matching a given pattern
                                                                                                                                      in this archive.
                                                                                                                                  @note
                                                                                                                                      This method only returns filenames, you can also retrieve other
                                                                                                                                      information using findFileInfo.
                                                                                                                                  @param pattern The pattern to search for; wildcards (*) are allowed
                                                                                                                                  @param recursive Whether all paths of the archive are searched (if the 
                                                                                                                                      archive has a concept of that)
                                                                                                                                  @param dirs Set to true if you want the directories to be listed
                                                                                                                                      instead of files
                                                                                                                                  @return A list of filenames matching the criteria, all are fully qualified
                                                                                                                                  */
                                                                                                                                  virtual StringVectorPtr find(const String& pattern, bool recursive = true,
                                                                                                                                      bool dirs = false) = 0;
                                                                                                                          


                                                                                                                          2) Отступы — дело привычки, ровно как и расположение открывающейся скобки. Тут даже странно говорить про удобство, профессионализм или что-то еще. Как я уже писал выше, у нас половина разработчиков работают на Full HD мониторах в вертикальной ориентации — все всегда помещается.

                                                                                                                          3) Отказ от шаблонов в основном API — скорее правильный выбор, нежели нет. В конце-концов, хорошо оперируют шаблонами достаточно малое количество программистов.
                                                                                                                          Но на низком уровне убирать шаблоны — странное решение. То, что привел в пример автор, смотрится как минимум странно. Вполне возможно, у него какое-то странное представление о шаблонах.
                                                                                                                          А boost сложен в чтении обычно потому, что он предельно универсален. Они ведь не знают, что будут писать конечные пользователи библиотеки, в отличие от ребят из id, которые четко знали, зачем и для чего они переписывают STL.

                                                                                                                          4) Использование get/set в именах функции — опять же удобство для программиста.
                                                                                                                          Например, используя автодополнения кода в своей IDE, я, даже не сильно зная API, могу просто написать get и получить полный список всех доступных мне функции, возвращаемых данным классом. Аналогично с set.
                                                                                                                          Использование же функции, возвращающих значение без префикса get, иногда просто приводит к неоднозначности.
                                                                                                                          Например:
                                                                                                                          void renderSystem(RenderSystem* system);
                                                                                                                          

                                                                                                                          Что значит этот код? Мы инициализируем систему рендера? Или мы просто устанавливаем систему рендера, но ничего с ней не делаем? А может мы ее добавляем в какой-то список доступных систем рендера? Неоднозначно.
                                                                                                                          Другое дело:
                                                                                                                          void setRenderSystem(RenderSystem* system);
                                                                                                                          

                                                                                                                          Тут все яснее — мы устанавливаем систему рендера. Инициализации или что-то там еще в этой функции проходить не должны.

                                                                                                                          5) stringstream
                                                                                                                          Лучше всего использовать boost::format. Это и безопасно, и понятно. Рекомендовать вместо stringstrean использовать printf — очень, очень странно. printf небезопасен, в местах его использования очень часто вылезают ошибки (о чем, кстати, написано в статье). Я не стал бы слушать человека, который рекомендуют использовать printf, когда рядом есть огромное число куда более хороший инструментов.

                                                                                                                          10) Собственно, не использовать перезагрузку операторов — тоже весьма странный совет. Перезагрузка operator() и operator= — это вообще фундамент C++.

                                                                                                                          11) Горизонтальные отступы — тоже на любителя. Если привыкнуть, я думаю, это нормально.
                                                                                                                          На вертикальный мониторах это будет хрен знает как смотреться. А на больших горизонтальных (у меня 32") — придется мотать головой влево-вправо, чтобы найти соответствие возвращаемому типу функции и самой функции. Можно, конечно, подсвечивать линии, но все же это спорное удобство.

                                                                                                                          Пожалуй, на этом можно закончить. Выбирайте свой стиль, синхронизируйте его с местом работы и в путь! :)
                                                                                                                          • 0
                                                                                                                            10) Собственно, не использовать перезагрузку операторов — тоже весьма странный совет. Перезагрузка operator() и operator= — это вообще фундамент C++.

                                                                                                                            Как я понял имелос в виду злоупотребление перегрузкой операторов, типа как всю векторную алгебру сделать через перегруженные операторы.
                                                                                                                            • 0
                                                                                                                              И что плохого в этом коде?

                                                                                                                                      inline Vector3& operator = ( const Vector3& rkVector )
                                                                                                                                      {
                                                                                                                                          x = rkVector.x;
                                                                                                                                          y = rkVector.y;
                                                                                                                                          z = rkVector.z;
                                                                                                                              
                                                                                                                                          return *this;
                                                                                                                                      }
                                                                                                                              
                                                                                                                              • +3
                                                                                                                                Может быть, то, что он не нужен вообще, поскольку эквивалентен дефолному копированию?
                                                                                                                                • 0
                                                                                                                                  Если x/y/z, в своюю очередь, имеют нетривиальный operator= — не эквивалентны, т.к. умолчательный конструктор копирования побитовый ;) Один раз начав, трудно остановиться, именно тот случай.
                                                                                                                                  • +1
                                                                                                                                    Как ни странно, нет. Я проверил вот такой код:
                                                                                                                                    struct Coord{
                                                                                                                                    	double *CX;
                                                                                                                                    	Coord(){ CX=new double[2]; }
                                                                                                                                    	~Coord(){ delete CX; }
                                                                                                                                    	Coord(Coord const &p){
                                                                                                                                    		CX=new double[2];
                                                                                                                                    		CX[0]=p.CX[0]; CX[1]=p.CX[1];
                                                                                                                                    	}
                                                                                                                                    	Coord &operator=(Coord const &p){
                                                                                                                                    		printf("Coord::operator=(%p): %p\n",&p,this);
                                                                                                                                    		CX[0]=p.CX[0]; CX[1]=p.CX[1];
                                                                                                                                    		return *this;
                                                                                                                                    	}
                                                                                                                                    };
                                                                                                                                    
                                                                                                                                    struct Vector{
                                                                                                                                    	Coord X,Y,Z;
                                                                                                                                    };
                                                                                                                                    

                                                                                                                                    Оказалось, что при копировании структур Vector оператор Coord::operator=() вызывается, несмотря на то, что Vector::operator=() переопределён не был.
                                                                                                                                    • 0
                                                                                                                                      Да, стал проверять стандарт и понял, что ошибся. 15й параграф пункта 12.8 из С++11 говорит о том, что оно должно быть memberwise. Не знаю, откуда в памяти взялось такое заблуждение.
                                                                                                                                      • 0
                                                                                                                                        Опечатка
                                                                                                                                        ~Coord(){ delete [] CX; }
                                                                                                                                        
                                                                                                                                        • 0
                                                                                                                                          И чем же они отличаются в случае double*?
                                                                                                                                          • 0
                                                                                                                                            Я не совсем Вас понял. Вы имеете в виду почему delete[], а не просто delete?
                                                                                                                                            У Вас же CX — массив из 2х элементов, а масивы, если память под них выделена с помощью operator new[] — соответственно и зачищаются с помощью operator delete[].
                                                                                                                                            Вот если бы было как-то так
                                                                                                                                            double* CX = new double(0.5);
                                                                                                                                            

                                                                                                                                            то, соответственно и удаляли бы
                                                                                                                                            delete CX;
                                                                                                                                            CX = NULL;
                                                                                                                                            

                                                                                                                                            Я правильно Вас понял?
                                                                                                                                            • 0
                                                                                                                                              Наверное, это правильно.
                                                                                                                                              Просто еще со времен Borland C++ 1.0 я не доверяю оператору delete[] (там были какие-то баги), и с тех пор никогда не захватываю массивы из объектов с деструкторами. А если массив состоит из чисел или указателей, то для него оба оператора всё равно сводятся к free(), так что delete работает точно так же… Но да, пожалуй, в правильном коде [] действительно нужны.
                                                                                                                                    • 0
                                                                                                                                      Пожалуй, действительно не самый удачный пример, хотя, возможно, на некоторых компиляторах такая запись даст некоторый прирост.

                                                                                                                                      Лучше взять operator+(). Без него делать Vector3 vec4 = vec1 + vec2 + vec3; будет несколько проблематично, а в 3д-графике это используется повсеместно.
                                                                                                                                      • 0
                                                                                                                                        operator +() — да. Но уже для умножения придётся каждый раз вспоминать, какое умножение скаляное, а какое векторное. А при умножении матриц преобразования — лезть в документацию, чтобы вспоминать, какое преобразование первое, а какое второе (если не работаете с этим каждый день). Конечно, они сильно навредили, когда заставили людей работать с векторами-строками, а не столбцами.
                                                                                                                                        • 0
                                                                                                                                          Я, видимо, не очень понял мысли.
                                                                                                                                          Допустим, переопределили мы operator*() для скаляра и для вектора:
                                                                                                                                          Vector3 operator*( const float scalar ) const
                                                                                                                                          Vector3 operator*( const Vector3& rhs ) const

                                                                                                                                          Зачем теперь что-то вспоминать?
                                                                                                                                          • 0
                                                                                                                                            Нет, я определил
                                                                                                                                            Vector3 operator*( const Vector3& rhs ) const;
                                                                                                                                            и
                                                                                                                                            double operator%( const Vector3& arg ) const { return X*arg.X+Y*arg.Y+Z*arg.Z; }
                                                                                                                                            

                                                                                                                                            Как я потом вспомню, кто из них *, а кто %?
                                                                                                                                            Собственно, в следующем комментарии всё это обсуждается.
                                                                                                                                            • 0
                                                                                                                                              А, речь о скалярном произведении векторов. Но тут я согласен с автором статьи, нужно определить функцию dotProduct, которая и обозначает скалярное произведение.

                                                                                                                                              Естественно, нужно переопределять операторы без фанатизма. Я полностью согласен, что в приведенном Вами примере это достаточно серьезная неоднозначность при перемножении векторов.
                                                                                                                                              Но, согласитесь, определить стандартные операторы для умножения, сложения, вычитания и т.д. для векторов можно и нужно.
                                                                                                                                              • 0
                                                                                                                                                Возможно, надо определить как раз CrossProduct, а DotProduct оставить как *. Векторное произведение встречается намного реже, чем скалярное.
                                                                                                                                                • 0
                                                                                                                                                  Либо сделать та