• Как начать программировать с использованием DirectX?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    ISBN 978-1-56881-720-0
    Jason Zink, Matt Pettineo, Jack Hoxley: "Practical rendering and computation with Direct3D 11".

    На русском книги нет. Книга легко читается и в оригинале. Уже с первых глав дает понимание современного пайплайна графики, стадий инициализации систем DirectX 11, способов загрузки и представления в памяти требуемых для отрисовки данных. В книге хорошо описан и язык HLSL.
    Книга хорошо подходит для холодного старта в работе с DirectX 11.
    Ответ написан
    Комментировать
  • Какие есть доки по сборке APK/AAB с использованием Android NDK?

    @MarkusD
    все время мелю чепуху :)
    Для изучения всего пайплайна сборки Android-приложения достаточно просто прочитать официальную документацию.
    Сборка кода низкого уровня выполняется через ndk-build и cmake.
    Сборка APK выполняется через Gradle напрямую.

    Android Studio ничего от разработчика не скрывает. Gradle по умолчанию встраивается в каждый проект Android-приложения. Gradle всегда настраивается руками, прямо в сценариях самого проекта. Для сборки APK требуется только сам проект, SDK и NDK. Студия не требуется, Gradle запускается из консоли. Сборку низкого уровня можно как встроить отдельным этапом в сценарий Gradle, так и сделать выделенным этапом, также запуская cmake/ndk-build из консоли.
    И все это детально описано в документации разработчика по ссылкам выше.

    Практически все вопросы решаются или через примеры в SDK, или через примеры в NDK.
    Все продвинутые вопросы решаются или в документации Gradle-плагина для Android, или в документации к NDK-Build.
    Ответ написан
    2 комментария
  • Что стоит учить с или c++ или c#?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Начинать нужно всегда с того языка, на котором ты думаешь, разговариваешь в быту и пишешь. С Русского языка. У тебя с ним, видно, все вполне хорошо. Пишешь понятно, мысли за словами прослеживаются.
    Значит нужно двигаться дальше. Следующим языком для тебя должен быть интернациональный. На этом языке идет общение в сообществах и передаются знания, как через книги, так и напрямую. Английским нужно владеть на уровне способности читать без запинки и словарей, также на английском нужно уметь внятно писать. Навыки устного общения будут плюсом, но на этапе обучения сильно не требуются.

    Языки программирования - это инструменты. А инструменты всегда надо выбирать от условий задачи. У тебя не выйдет решать все задачи только каким-то одним языком. Знать во всех тонкостях и уметь использовать строго по назначению Python, Java, C#, C++, Lua, TypeScript и, например, PHP не просто нормально, а важно.
    Бьерн Страуструп один раз говорил о том, что для настоящего инженера важно знать порядка 5 разных языков и разбираться в их особенностях, чтобы считать себя настоящим специалистом.

    Поэтому вообще не важно что ты выберешь на свой первый раз. Качество инженера раскрывается в его умении полноценно владеть множеством инструментов.
    Ответ написан
    Комментировать
  • В какого типа переменных хранить адреса?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Для подобных целей уже давно заведены intptr_t и uintptr_t [?], а еще ptrdiff_t [?].
    Именно эти типы и стоит использовать для прямой работы с адресами.

    Теги C и C++ смешивать не совсем уместно. Это мешает выбору более подходящего варианта ответа.
    В C++, например, если нужно только хранить адрес и позволять с ним только определенные операции, лучше мог бы подойти enum class MemoryAddress : uintptr_t;. Пустое перечисление с достаточной шириной и выравниванием избавит от возможности случайно что-то куда-то прибавить или умножить, да и от неявных преобразований убережет. А перегрузка операторов поможет разрешить только определенные операции.
    Но в C так не получится.
    Ответ написан
    Комментировать
  • Ошибка при ручном высчитывании перспективной проекции и точки на экране?

    @MarkusD
    все время мелю чепуху :)
    Для начала тут нужно разобраться с пространствами.
    В каком пространстве рисуется серая платформа? В каком пространстве задана область обзора камеры для платформы? В каком пространстве задана область проекции камеры для платформы?
    В каком пространстве рисуется красный квадрат? В каком пространстве задана область обзора камеры для квадрата? В каком пространстве задана область проекции камеры для квадрата?

    А следом надо разобраться с порядком переходов между этими пространствами.
    У тебя используется два пространства мировых координат и две камеры, результат проекции которых сводится в одну поверхность презентации. Чтобы из точки мирового пространства плоскости получить точку мирового пространства квадрата, тебе нужно взять прямую матрицу WVP пространства плоскости и умножить ее на обратную матрицу WVP пространства квадрата.
    Результирующая матрица будет матрицей перехода из мирового пространства плоскости в мировое пространство квадрата. Умножая вершины плоскости на эту матрицу, ты будешь переводить их координаты в пространство, в котором тебе в этих вершинах надо рисовать красные квадраты.
    Ответ написан
    Комментировать
  • C++ CMake Как исправить ошибку?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Тут проблема не в CMake и не в CLion. Это проблема MinGW и кодировки файлов с исходным кодом.

    Файлы сейчас сохранены в какой-то другой кодировке, когда GCC в составе MinGW по умолчанию ожидает кодировку UTF-8.
    Достаточно будет сконвертировать файлы в кодировку UTF-8 и MinGW начнет их переваривать.
    Ответ написан
    Комментировать
  • Что за странная запись в С++?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Для обращения к любому выражению по его имени в C++ имеется механизм поиска имен. Это довольно сложный и многоэтапный механизм, в результате которого исходя из контекста обращения и самого имени транслятор силится понять что же там такое прекрасное имел в виду писатель кода.

    Насколько видно, базово механизм делится на две ветви: поиск квалифицированных имен и поиск неквалифицированных имен.
    И именно в этот момент речь заходит о т.н. квалификации имени выражения.
    Квалификация имени выражения тем полнее, чем точнее от самого корневого пространства имен (т.е. от ::), через все пространства имен и пространства составных типов, написано имя выражения.

    Имя Process::WaitForExit, хоть и является уже квалифицированным за счет указания пространства типа, в котором метод объявлен, все еще остается недостаточно квалифицированным чтобы считаться полностью квалифицированным.
    Вызов метода по его полной квалификации выглядел бы так:
    process.::base::Process::WaitForExit(&exit_code);

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

    Для чего нужно было писать полную квалификацию конкретно в приведенном коде?
    А кто его знает. Метод base::Process::WaitForExit[?] не является виртуальным чтобы сделать предположение о невиртуальном вызове.
    Просто автору так захотелось, наверное.
    Ответ написан
    2 комментария
  • Как получить данные из буфера глубины при включенном multisample в OpenGL?

    @MarkusD
    все время мелю чепуху :)
    Я конечно могу найти луч и потом искать пересечения со всеми объектами на сцене и рассчитывать координату z уже из этого, но мне кажется это слишком ресурсозатратно

    Однократное использование glReadPixels в сотни раз более ресурсозатратнее обычного оптимизированного алгоритма проверки на пересечение луча с объектами сцены.

    В разработке игр на мобильных устройствах алгоритм проверки пересечений запускается даже не на каждые 10 пикселей экрана, а просто постоянно в каждом кадре, пока игрок не поднимет палец с экрана. И у людей при этом проблем с производительностью нет даже на 96+ FPS.
    В продуктовых решениях мы никогда не используем glReadPixels в покадровых рутинах. Ни в мобильной разработке, ни в десктопной, ни под консоли.

    Вот ответ, который тебе поможет разобраться с переводом координат мышки в 3D пространство сцены.
    Чтобы снизить трудоемкость проверки на пересечение с объектами сцены, используй или Q-Tree, или Oc-Tree, или BSP, или Агломерацию.
    Чтобы снизить трудоемкость проверки на пересечение конкретного объекта, используй AABB, OOBB и все те же Q-Tree/Oc-Tree или BSP для отсечения лишних полигонов модели.

    Это все позволит определять объект сцены под мышкой значительно быстрее чем всего один вызов glReadPixels с проходом по матрице пикселей.
    Ответ написан
    1 комментарий
  • Ошибка в Visual studio opengl. Как решить?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Ошибка LNK2019 относится к стадии линковки и означает что среди всех объектов линковки так и не нашлось определение обозначенной в ошибке функции.

    __imp_glClear и __imp_glDrawArrays - это стандартные функции OpenGL, определены они в библиотеке opengl32.lib, которую и требуется подключить как внешнюю зависимость к твоему проекту.

    Зависимости в проект подключаются через свойства проекта Visual Studio.
    Ответ написан
    5 комментариев
  • Почему в Java изменение интерфейса базового класса посредством модификации сигнатуры разрешено?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Чтобы понять поведение кода в C++, нужно сперва разобраться с поиском имен в C++.
    Коротко, поиск имен запускается всегда для каждого имени, которое встречается в коде, и связывает использованное в коде имя с конкретным, единственным из всех прочих, объявлением этого имени.

    cat1 -> sound();
    Тут по пунктам. Сперва UNL определит имя cat1 как переменную в локальном пространстве с типом Cat*.
    Далее UNL же определит что в пространстве класса Cat есть метод sound.
    Далее для определения перегрузки Cat::sound запустится ADL и найдет в пространстве только одну перегрузку - метод без параметров. ADL вернет объявление этой перегрузки, т.к. она удовлетворяет условиям вызова метода.

    Отдельно отметить стоит то, что это именно писатель определил в пространстве класса только одну перегрузку метода sound. Алгоритм ADL довольно строг и не будет искать объявления где-либо еще.
    Именно поэтому при данном определении класса Cat код cat1 -> sound(1); трансляцию никогда не пройдет. Просто потому что в пространстве класса написана только одна перегрузка метода.

    Что делать, когда перегрузки из родительского класса нужны все, но замещать их все в классе дочернем нужды нет?
    Тут на помощь приходит ключевое слово using[?].
    Это слово нужно использовать в контексте делегирования из пространства родительского класса. Определение класса Cat должно быть таким.
    class Cat : public Animal
    {
    public:
    	// Delegate all overloads of `sound`.
    	using Animal::sound;
    	
    	void sound () override
    	{
    		std::cout << "Cat.sound()" << '\n';
    	}
    };


    В этом случае код cat1 -> sound(1); пройдет трансляцию и приведет к вызову void Animal::sound(int i).
    Ответ написан
    2 комментария
  • При добавлении рисунка с прозрачной областью неправильная цветопередача?

    @MarkusD
    все время мелю чепуху :)
    Изображение у тебя в файле хранится в формате BGRA, а в видеопамять ты его передаешь как RGBA.
    В результате каналы R и B оказываются поменяны местами. Поэтому и вывод такой.

    Тебе нужно или в видеопамять изображение передавать как BGRA, или руками поменять R и B каналы каждого пикселя после загрузки изображения из файла.
    Идеальный вариант - это сменить формат файла так, чтобы изображение в нем хранилось в формате RGBA.
    Ответ написан
  • Почему утверждается, что int32_t имеет ширину точно 32 бита, если он является всего лишь псевдонимом int, который может быть больше 32 бит??

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    В рассуждениях есть ошибка.

    Да, стандарт утверждает что тип int может иметь размер не меньше 16 бит.
    Таблица имеет название "Minimum width", т.е. минимальный размер. Минимальный - это значит что int может иметь размер в 16 бит. А может иметь и 64 бита.

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

    Ошибка в рассуждениях заключается в том, что ты проводишь нить только от int32_t к int и дальше к стандарту. В то время как стандарт определяет и требования к типу int32_t тоже.
    Тип int32_t всегда и для любой модели памяти выбирается таким образом чтобы гарантировать размер в 32 бита.

    То что где-то int32_t является псевдонимом int - это не более чем временное совпадение.
    Ответ написан
    1 комментарий
  • Почему не работает метод clone для класса Test1?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Типом результата Test::clone является std::shared_ptr<Test>.
    Строчка Test1 asd = v[1]->clone(); эквивалентна строчке Test1 asd = std::shared_ptr<Test>{ ... };.
    Оператора или конструктора преобразования из std::shared_ptr<Test> у типа Test1 нет. Трансляцию строчка Test1 asd = v[1]->clone(); не пройдет.

    Идиома Cloneable служит для того, чтобы позволить копирование действительных объектов не выходя за рамки общего интерфейса и не зная действительного типа клонируемого объекта.
    Или ты неправильно понял идиому, или пробуешь неправильно ее использовать.

    Когда ты заранее знаешь тип, тебе незачем пользоваться клонированием, потому что ты можешь просто скопировать стандартным способом.
    Правильно твоя строчка должна выглядеть так: std::shared_ptr<Test> asd = v[1]->clone();.
    Или так:
    Test1 asd{ *std::static_pointer_cast<Test1>( v[1] ) };
    .
    Ответ написан
    Комментировать
  • Использование шаблона в многофайловом проекте, как реализовано в vector например?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Основным постулатом работы с шаблонами является то, что определение шаблона должно быть достижимо из места его инстанцирования.

    Что это означает на практике. После 4й стадии трансляции формируется модуль трансляции, содержимое которого и компилируется в объектный файл. Код модуля трансляции должен быть исчерпывающим, в нем должны находиться все необходимые для компиляции определения. А определение шаблона относится именно к таким требуемым.
    Значит определение используемого в модуле шаблона должно быть внесено в модуль трансляции. А это значит что или определение должно быть вписано в тот .cpp файл, в котором шаблон инстанцируется, или определение должно быть добавлено через директиву #include.

    В первом случае, когда используемое в .cpp файле определение шаблона вписано в том же .cpp файле, определение шаблона будет достижимо только в данном .cpp файле и больше нигде. В этом случае попытка инстанцировать шаблон в другом модуле трансляции приведет к ошибке трансляции, т.к. там определние шаблона будет уже недостижимо.
    Файлы исходного кода включать через директиву #include - это дурной тон и прямой путь к нарушению ODR.

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

    Широкой практикой является разделение кода шаблонов на несколько типов файлов. В .h файлах обычно делают или объявления шаблонов функций, или определения шаблонов классов. В .hpp/.inl файлах обычно делают определения шаблонов функций и шаблонов методов.
    При этом .hpp/.inl файл очень часто включается в самом низу .h файла с его объявлениями.
    Моя личная рекомендация: использовать в таких случаях расширение .inl (от слова inline), т.к. для .hpp столь же широко закреплено взаимоисключающее с .h значение заголовка C++ кода. И видеть эти два расширения в одном проекте обычно бывает странно.

    Вектор же, например в проекте LLVM, реализован так, что часть определений в нем сделаны по месту объявления, а часть - как внешние определения сразу после определения шаблона вектора. Все это сделано прямо в одном заголовке.
    Ответ написан
    Комментировать
  • Актуальны ли книги Александреску, Майерса и Саттера?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Да, книги Андрея Александреску, Скотта Майерса, Герба Саттера, Николая Джосаттиса, и, например, Девида Вандервуда имеют актуальность и по сей день вне зависимости от года издания или перевода.

    C++ развивается вот уже 38 лет. Новые стандарты сегодня приходят с достойной одобрения частотой, но начиная с C++11 изменения в стандартах до сих пор ничего кардинально не ломают. Даже новые возможности концептуально связаны с опытом прошлых стандартов.
    С другой стороны, трансляторы. Новые стандарты языка не приходят сразу, сперва требуется дождаться их поддержки в современных трансляторах. А это происходит не в одно время и не сразу по выходу нового стандарта. Да и когда появляется версия с поддержкой нового стандарта, эта поддержка не лишена ошибок, опознать которые способен только опытный инженер с экспертизой в новом стандарте.
    Разработка же и вовсе не поспевает за трендами. На собеседованиях я то и дело слышу как где-то кто-то еще только вчера и еще только решил перейти на C++11. В 23-м году.
    Самым широко используемым стандартом сейчас является C++17, большинство функций которого многими компаниями так до сих пор и не используется. Люди до сих пор еще только привыкают к нему.
    Книги представленных авторов в понятной форме передают читателю ценный базовый опыт, который можно применять вообще не привязываясь к стандарту языка. Главное - это не брать в рассмотрение книги до 2011 года.

    Чтобы быть на острие развития языка, нужно не книги читать, а быть сильным энтузиастом и иметь изначально глубокую экспертизу в стандартах языка. Авангард развивается за счет самостоятельных экспериментов и исследования пределов возможностей последних стандартов C++. Не за счет ожидания и чтения книг.
    Ответ написан
  • Почему доступ к элементам vector-а O(1)?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    А с вектором дела обстоят иначе. Каждый элемент хранит указатель на следующий после него.

    Такое поведение имеет список, односвязный или двусвязный.

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

    Именно поэтому доступ к любому элементу вектора имеет сложность O(1). Это просто смещение на индекс элемента относительно начала блока памяти с помощью адресной арифметики.
    Ответ написан
    Комментировать
  • Для чего использование Hydrator pattern вместо создания объекта?

    @MarkusD
    все время мелю чепуху :)
    Гидрация, или насыщение, как термин используется для обозначения процесса наполнения данными уже доступной пользователю сущности. Когда сущность уже создана и доступна для использования, но данными заполнена не до конца или еще вовсе не заполнена.
    Как правило, гидрация производится полностью в обход интерфейса сущности, предполагая изначальное соответствие неполной сущности инварианту своего типа. Этот процесс может напоминать сериализацию, но отличается от нее своей протяженностью во времени. В то время как сериализация предоставляет только полностью заполненную данными сущность, гидрация производится уже во время использования сущности пользователем.

    Изначально гидратор являлся маркетинговым шаблоном в дизайне Hibernate ORM. Маркетинговым - это потому что нового ничего шаблон не вносит, просто имеющееся называет броским новым термином ради привлечения внимания. Шаблон дизайна - это потому что гидратор явно в коде не представлен, будучи именно высокоуровневым описанием поведения некоторого кода.
    И самое интересное в том, что маркетинговая задумка удалась, хоть и немного в другом смысле. Люди начали понимать гидратор по-своему. В результате часто получается так, что гидратором называются обычные билдер или фабрика, а то и вовсе сериализатор. Термин людям понравился, просто.

    Декоратор для гидратора из приведенного по ссылке кода - это просто еще одна попытка использовать любимый термин. Гидратор в этом коде гидратором не является по всем своим признакам. Это - сериализатор.
    Ответ написан
    Комментировать
  • Где взять заголовочные файлы и библиотеки opengl?

    @MarkusD
    все время мелю чепуху :)
    Khronos Group не предоставляет сами заголовки для OpenGL. Группа занимается только разработкой и поддержкой спецификаций API, а также предоставляет универсальные схемы для генерации биндингов библиотеки на любой требуемый язык.

    Файлы g.xml, wgl.xml и glx.xml предоставляют все необходимые описания для генерации биндингов OpenGL.
    Файл genheaders.py позволяет сгенерировать обычные заголовки с биндингами на C.

    Но на самом деле это все не нужно, потому что есть GLAD. Этот проект позволяет получить заголовки с биндингами вообще не напрягаясь, используя общедоступный сайт-генератор. Более того, GLAD может добавить свой код загрузчика в генерируемые заголовки, что может сильно упростить подключение сгенерированных заголовочных файлов.
    Поэтому самым верным способом будет воспользоваться функционалом GLAD для получения заголовков с биндингами OpenGL на C.
    Ответ написан
    Комментировать
  • Как сделать обработчик коллизий в OpenGL?

    @MarkusD
    все время мелю чепуху :)
    OpenGL - Open Graphics Library.
    Это - открытая библиотека работы с графикой. И в ее API содержатся только функции работы с графикой.
    Для обработки коллизий нужна отдельная библиотека обработки коллизий, в зависимости от мировой системы координат. Простую библиотеку можно и самому сделать.
    Для обработки коллизий используют библиотеки коллизий и физические движки.
    Ответ написан
    3 комментария
  • Вопрос по оформлению кода C++?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Каждый вопрос здесь подразумевает довольно большой объем информации при обосновании ответов. В общем смысле весь вопрос сводится к выбору одного из стилей написания кода.
    Обычно стиль кода закреплен стандартом языка, но в стандарте C++ такого нет. Поэтому стиль кода в C++ является предметом выбора каждого. Опять же, обычно для C++ стиль кодирования выбирают наобум, просто потому что понравилось так или потому что в этом стиле пишет любимая компания (GCS - хороший пример выбора на эмоциях и яркий пример очень плохого стиля для C++). Но обоснование своего выбора является очень важным.
    В C++ Core Guidelines есть отдельная секция с описанием стиля кодирования стандартной библиотеки.
    И тем не менее.

    1) Как называть переменные:

    Зависит от того, как будут называться функции и константы.
    И вот почему
    Книжки читают быстро, а код - еще быстрее. При беглом чтении всегда нужно уметь разделять переменные, константы и функции. C++ итак сложный, а если все будет написано в одной манере, то код на C++ будет только еще сложнее.
    Моей рекомендацией будет переменные и локальные константы писать в lower_cast_snake_style, а глобальные константы, макроопределения и элементы нестрогих перечислений писать в UPPER_CAST_SNAKE_STYLE.
    Таким образом достигается единообразие. Стиль змейки во всех своих видах сразу отходит под описание данных, создавая акцент для читателя. Таким образом данные будут читаться легче.
    Свои типы, имена элементов строгих перечислений, имена пространств и имена функций с методами, при этом, стоит писать в UpperCamelCase. Почему все эти и только в одном стиле. А потому что они и концептуально связаны, и разделены настолько, что не перемешиваются при чтении.
    Все составные типы формируют свои пространства имен для вложенных объявлений. Поэтому строгое перечисление, структура, класс или пространство имен разумно называть в едином стиле.
    Функции являются точками входа в подпрограмму, их стилистически неверно было бы писать, например, в lowerCamelCase. Первая заглавная буква много значит при чтении, она является акцентом для читателя.


    2) Что лучше присваивать булевым переменным:

    Литералы 0 и 1 имеют тип int. Если тип переменной - bool, то с какой стати справа от типа должны присутствовать значения с типом int?
    Следует использовать только литералы с типом bool: true и false.
    И вот почему
    При написании кода самым важным является не отражение алгоритма или формальное соответствие стандарту, а именно не вызывать вопросов у читателя. Нужно всегда понимать, что у читателя свой контекст, читатель решает свою задачу, здесь у тебя в коде он только для сбора информации. Его не должны сбивать с толку никакие изыски в написанном коде. Когда читатель видит слева тип bool, а справа значение с типом int, у него появляются вопросы, закрадывается подозрение в достоверности прочитанного, он выпадает из своего контекста. Это - очень плохо.


    3) Как лучше называть переменые итераторы во вложенных циклах:

    Абсолютно каждое имя должно отвечать на вопрос: "Зачем ты тут существуешь?"
    Могут ли однобуквенные имена ответить на этот вопрос внятно через всего одну свою букву? Нет.
    Имя - это смысл. Имя - это причина существования. Имя - это цель использования.
    И вот почему
    Чтение кода вынуждает читателя создавать и поддерживать некоторый контекст читаемого кода. Чем сложнее читателю дается поддержка такого контекста, тем менее понятен читаемый код и тем больше времени уйдет на его изучение. Если же в результате читателя выкинет из контекста решаемой им задачи, то это будет совсем плохо и виноват в этом будет именно плохо написанный код.
    Написанное в коде имя создает отметку в контексте для читателя. Чем более это имя понятно и отвечает общему изложению кода, тем легче читателю дается поддержка контекста читаемого кода.
    Существует масса концепций именования, море семантических пар имен, гора ярких и кучи общих имен. Важным остается только одно - переменная должна своим именем говорить о том, что она хранит, а функция - что делает. Тип должен в своем имени раскрывать природу существования своих объектов.
    Имя должно быть обязательно конкретным. Data, Interface, Iterator - это общие имена, которые не несут никакой конкретики. Общие имена допускаются только в абстрактном коде, т.е. в коде интерфейсов, шаблонов, макросов. Между именем вызываемой функции и именем переменной, принимающей результат вызываемой функции должна быть семантическая связь. И разрыв этой связи допускается только при переходе от общего имени к конкретному. Например так: auto hosts = local_network.GetIterator();. И ведь тут с полпинка все становится понятно, даже думать не надо.


    4) Очень локальный вопрос стоит ли писать else, если ниже нет другого кода ниже:

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


    6) Писать ли пробел между стандартными функциями и скобками:

    Пробелы нужны для разделения связанных цепочек символов - слов. Код - это запись рассуждений автора о том, что должна делать программа. Код должен читаться как рассказ, в котором слова правильно разделены между собой и правильно расставлены смысловые акценты.
    И вот почему
    Пробелы нужны чтобы отделить одно от другого. С какой целью? Наверное с целью обратить внимание читателя на то, что пробелами отделено. Пробелы сами не являются акцентами, но позволяют акцентировать внимание читателя, в то время как любые другие символы только забирают на себя внимание потому что читателю надо понять смысл присутствия символов в месте их присутствия.
    a==5 - никаких акцентов, ничего не видно. Даже с подсветкой синтаксиса 5 и == читаются плохо и практически неотличимы от a=5 при беглом чтении. В такие моменты у читателя в контекст вносится ошибка или, как минимум, неопределенность ошибки. Но основная цель писателя кода - это написать понятный для чтения код. Поэтому через пробелы надо акцентировать внимание читателя именно на символе эквивалентности - a == 5, позволяя ему правильно прочитать написанное при беглом чтении.
    if(a == 5){ - в этом коде видно только акцент на знаке эквивалентности, но не на выражении условия. if (a == 5) { - уже лучше, но скобки требуют от читателя понять природу их нахождения, что это именно условие, а также вчитаться в левый и правый аргументы условия. if( a == 5 ){ - здесь для читателя акцент поставлен именно на всем условии, теряется только знак начала области видимости - {. И именно поэтому египетские скобки - это плохо. Область видимости должна начинаться на своей строке, потому что для нее нужно создать максимально заметный акцент.
    for (int a = 0; a < 10; a++) { - тут акценты созданы, но не так, чтобы читатель легко прочитал тип счетчика или операцию шага. for( int a = 0; a < 10; a++ ) - а вот тут внимание читателя акцентируется именно на выражении счетчика. И читателю уже не надо выискивать глазами условия, инициализацию и шаг. Это все выделено пробелами и подано для самого комфортного чтения.


    7) Тот же вопрос только про функции, что я сам написал:

    С этого момента тебе должно стать понятно, на чем именно нужно делать акценты чтобы не выводить читателя из себя. Главное - это при написании кода всегда помнить, что возможно читать его будет натуральный маньяк-психопат, который точно знает где ты живешь. И ты точно не хочешь разгневать его своим кодом. :)
    И вот почему
    Код всегда пишется для читателя. Не для транслятора, не для чего-то еще. Транслятору важно только формальное соответствие кода стандарту. Читателю важно понять логику кода, а для этого код надо читать и разбираться в его логических связях. Поэтому, когда пишешь код, всегда нужно думать о том, как его будут читать, не будет ли вопросов к конкретным строчкам, понятны ли имена и отражает ли написанное вложенную в этот код логику.
    Ответ написан
    2 комментария