Programming Guidelines

Материал из Русский WINE
Перейти к: навигация, поиск

Руководство по программированию

Перейдя на http://www.microsoft.com/resources/practices/ вы можете просмотреть лучшие практики программирования от Microsoft.

Здесь приведены подсказки, изначально собранные в рамках проекта ReactOS и позволяющие написать хороший код.

Для получения консультации по написанию безопасного кода см. статью Безопасное Программирование.

Всегда помните следующие требования:

Гораздо важнее быть правильным, чем быстрым

Преждевременная оптимизация есть корень всех зол.

Со всем уважением к эффективно использующим память и быстро написанным программам, в первую очередь хотелось бы сказать, что не нужно стараться достичь максимальной скорости исполнения кода при его написании. Эти вопросы, в основном, станут пустой тратой времени. Создавайте программу, которая будет надежной и легко обслуживаемой, а уже потом, если её производительность будет неприемлема, просмотрите её и улучшите только те разделы, которые наиболее всего требуют оптимизации. -RexJolliff

Пусть вас не стыдит небольшое и умное решение проблемы

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

Создавайте потокобезопасный код

Будьте особенно внимательны и при написании библиотеки; ваша библиотека может быть использована мультипоточным (MT) приложением. В любом случае это относится к ядру, поскольку ядро является библиотекой, которая должна быть поточно-безопасной.

Отличием потокобезопасного кода от небезопасного является использование глобальных переменных. Потокобезопасный код избегает использования практически всех глобальных переменных, включающих в себя также локальные статические переменные и переменные класса. Необходимо решить, какая переменная или структура данных будут локальными, глобальными в рамках потока или глобальными в рамках процесса. Постарайтесь удалить глобальные переменные. Если это невозможно, и вам необходима переменная, глобальная для всего потока, то правильным решением станет использование локальной памяти потока (TLS). Если вам нужна переменная, глобальная для процесса, правильным решением станет использование классической глобальной переменной. Тем не менее, не забудьте объявить её, как изменяемую. Если вы не объявите её как изменяемую, может случиться так, что два потока будут держать копию переменной в своём контексте. Поэтому согласованность данных не может быть гарантирована.

Глобальные структуры данных немного сложнее. Структура, глобальная для потока, такая например, как список, не проблема, потому, что только владеющий им поток знает о нём. Однако структура, глобальная для процесса и получающая доступ к нескольким потокам, требует больше внимания. Вы должны синхронизировать доступ к потокам с так называемыми объектами синхронизации, предоставляемыми ядром. Это поможет гарантировать вашим потокам взаимный эксклюзивный доступ к наиболее часто используемым структурам данных. Наиболее часто для этой цели используется мьютекс. Один мьютекс на список, это, в основном, нормально. Однако, если к структуре сложно получить доступ, то нужно рассмотреть возможность использования более чем одного мьютекса. Один мьютекс на элемент - это пустая трата ресурсов. Таким образом, использование мьютексов на несколько диапазонов, является хорошим компромиссным решением.

То же самое касается режима ядра. Просто в нём это сложнее сделать, и необходимо использовать спин-блокировки, чтобы синхронизировать потоки нескольких процессоров. volatile int really_global_i; DWORD tlsi = TlsAlloc(); TlsSetValue( tlsi, 95 ); Вкратце:

  • Избегайте глобальных переменных.
  • Используйте локальную память потока (TLS) для переменных, глобальных для потока.
  • Найдите правильные детали для синхронизации глобальных структур.
  • Создавайте код, который сможет работать на нескольких процессорах.

Используйте многопоточность везде, где это возможно и полезно, но в разумных пределах

Многопоточность - не святой Грааль. Тем не менее существуют многочисленные примеры, где использование многопоточности было бы уместно. Приложения Win32 GUI, как правило, имеют один GUI-поток и множество рабочих потоков. Кроме того, можно иметь много GUI-потоков, но очень быстро это становится сложным, а если не принять меры предосторожности, то станет невозможно получить доступ к GUI из более чем одного потока.

Не так-то просто писать MT-GUI-приложения. Однако, некоторые вещи просто интуитивно разделяются на потоки, так что стоит попробовать.

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

Использование нескольких потоков, которые выполняют один и тот же код параллельно, ведёт к другой проблеме. Если программа запускается на системах, поддерживающих SMP и SMT, происходит так называемое совмещение кэша. Кэши процессора организованы в так называемые строки кэш-памяти, каждая из которых состоит из 32, 64 или более байт. Эти строки вкруговую отображаются на адреса памяти. Результатом является то, что адреса, указывающие на одну и ту же строку кэша, повторяют все 8Мб или около того.

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

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

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

Это означает также и SMT и совмещение кэш-памяти.

Попробуйте открывать общие файлы (в особенности SHAREMODE_DELETE)

Создавайте программы, поддерживающие запуск нескольких копий

Избегайте повреждения данных при критических ошибках

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

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

Создавайте упреждающий код

Используйте спин-блокировки редко, но там, где это необходимо

Возбуждайте IRQL как можно короче - подумайте о написании DPC

Используйте Unicode и не-Unicode

Не используйте в своих программах только UNICODE или только ANSI. Подключите и используйте TCHAR вместо char, macro _T() для строк текста, а также макросы _tcscpy(), _tcscmp(), ... или lstrcpy(), lstrcmp(), ... для обработки строк. Для подсчёта длины строки TCHAR массива TCHAR, используйте sizeof( массив ) / sizeof( массив[0] ) или sizeof( массив ) / sizeof( TCHAR ).

Не прописывайте англоязычные фразы жёстко в исходном коде - лучше соберите их в одном месте (для облегчения локализации)

1 - Отыщите изначально жёстко прописанные в тексте фразы: это должно стать первым шагом при работе с плохо написанным кодом

сейчас можно забыть о unicode/nounicode printf("моя жёстко прописанная строка");

2 - Сделайте для них макрос:

define HARDSTRING "моя жёстко прописанная строка"

printf(HARDSTRING);

3 - и, наконец:

char* AllStrings[NUMSTRINGS];

define HARDSTRING AllStrings[1]

printf(HARDSTRING); // <--- к этой строке можно больше не обращаться

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

Не создавайте поток для подключенного пользователя, лучше используйте порты завершения ввода/вывода

Использование одного потока для обслуживания всех пользователей является плохой идеей. Пользователям приходится ждать некоторое время, что приводит к замедлению работы. Решением является создание по одному потоку для каждого пользователя. Однако, это тоже не лучшая идея. Это нормально, если вы обслуживаете определённый максимум запросов. Но если точно не известно, сколько придёт запросов, то лучше использовать удобные порты завершения ввода/вывода. Проблема с созданием большого количества серверных потоков состоит в следующем: в большинстве случаев ваш поток выполняет операции ввода/вывода (доступ к жёсткому диску). Вызов слишком большого количества потоков не повредит вашей ОС, но замедлит работу подсистемы ввода/вывода, что повлечёт за собой произойдет снижение пропускной способности. Каждый из потоков будет мешать закончить работу остальным, и общая производительность упадёт.

Так ограничение на количество серверных потоков - это лучшая идея. Их количество для программы можно задавать вручную или воспользоваться портами завершения ввода/вывода. Пример:

Порты завершения ввода/вывода - это большая, быстрая и эффективная многопоточная модель. Вот ей и воспользуйтесь. Вы создаёте потоки единожды, и они ожидают до тех пор, пока им не будет необходимо выполнить какую-либо работу. Они не будут расходовать процессорное время при ожидании, и активируются лишь тогда, когда это необходимо. С другой стороны, они получают преимущества многопоточного кода, который не приведет к задержкам, когда какая-либо операции блокируется, и вы можете обслуживать все эти потоки используя лишь единственный поток процессора. Это означает, что они не будут медленнее, чем код, имеющий только один поток, более того, ваш код, вероятно, будет работать быстрее в SMP системах. Такой код, конечно, сложнее в использовании, чем однопоточный код, создание потока для каждого действия или наличие пула потоков, которые после время от времени проверяют необходимость работы для себя, но на самом деле такое усложнение с лихвой окупается.

Подумайте о возможности смены направления написания текста

Порядок чтения символов слева направо является не единственным направлением чтения и написания текста на земном шаре. Также существуют языки, в которых используется порядок чтения справа налево, как, например, в иврите, и сверху вниз, как, например, в японском языке. В то же время, в японском языке также существует порядок чтения слева направо. Так что вам необходимо учитывать эти два порядка. Это означает, что

Создавайте GUI, использующие диспетчер компоновки, а не пиксельное позиционирование элементов управления

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

Создавайте быстрые и эффективно использующие память программы

Это противоречит ранее сказанному про оптимизацию. Имейте ввиду, что это заявление не означает, что вы должны тратить память или процессорные такты впустую.

Размеры оперативной памяти становится всё больше, а процессоры - всё быстрее. Но программы всегда становятся все менее эффективны. Ну да ладно, хватит об этом. Разумеется, это всего лишь совет, поскольку все принимают решения самостоятельно и разрабатывают приложения сами по себе. Помните хотя бы эти подсказки: Не забывайте, что вы можете использовать файлы, отображенные в память. Это обеспечивает вам более простой доступ к файлам данных, и при этом вам не надо перестраивать все пространство данных в памяти. Это то, чего всегда необходимо избегать: так, например, некоторые игры занимают 1 Гб на жестком диске и, будучи запущенными, занимают такой же объем в пространстве свопа. Если вы пишете в объектно-ориентированном стиле, применяйте ссылки для параметров объектов. Это позволяет избежать двойного вызова конструктора копии. Один раз для временного объекта, один раз для переменной более высокого уровня. Избегайте копирования избыточных данных через иерархию объекта. То есть не передавайте набор переменных следующему объекту более низкого уровня и так далее, но используйте вместо этого "умный" дизайн с указателями.

Избегайте утечек памяти

Проще сказать, чем сделать. Утечки памяти всегда неожиданны. Нельзя сделать так, чтобы они никогда не появлялись. Тем не менее, воспользуйтесь приведёнными здесь советами. Вы можете использовать язык со сборкой мусора, например, Eiffel. Используя С, вы должны быть более осторожным и всегда соблюдать парность функций выделения и освобождения памяти или использовать подсчет ссылок. Если вы используете С++, то вам доступны техники автоматических и интеллектуальных указателей. О подробностях их использования читайте в соответствующей литературе.

-- Также существуют сборщики мусора для C и C++ - Jakov

Вставляйте достаточно комментариев в ваш код

  • Помните о блочных комментариях, которые описывают целый модуль и его назначение.

Найдите разумный баланс между абстракцией и реализацией

Абстракция - полезная концепция программирования. Тем не менее, легко переборщить с ее использованием.

Не создавайте лишних копий программных данных

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

  • продолжение следует...

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

ReactOS уже является или станет мультиплатформенной ОС. Быстрые ассемблерные вставки для критических операций это хорошо. Тем не менее всегда нужно иметь и альтернативу, написанную на С. Только это гарантированно обеспечит возможность сборки ROS на каждой из целевых платформ. Напоследок одна подсказка - основная задача сделать так, чтобы код был работоспособен. Если мы определим, что данный участок является узким местом в производительности, лишь тогда мы будем его оптимизировать и, возможно, перепишем на ассемблере.

Сохраняйте метаданные файлов, которые вы используете

В файловых системах ReactOS, файлы могут иметь много дополнительной информации. Она включает в себя метки времени, атрибуты, списки управления доступом, множественные файловые потоки и расширенные атрибуты. Не каждая файловая система дает доступ ко всем этим возможностям. Но если же они доступны, то пользователь будет раздражен, если программа уничтожит метаданные, которые он использовал. Чаще всего это происходит при копировании, архивировании или сохранении файлов. Подобные программы просто создают новый файл, сохраняют содержимое из старого файла в новый, а затем удаляют старый файл и переименовывают новый в старый. Оптимальным путем в подобной ситуации было бы отображение в память оригинального файла и непосредственный доступ к отображённым данным в режиме "только чтение". Если в файле необходимо произвести изменения, связанные с работой пользователя в программе, то вступает в действие механизм копирования при записи и создает рабочую копию. Если пользователь захочет сохранить внесенные изменения, то изменённые структуры записываются обратно в файл через отображённые в память данные.

Если подобный вариант вам не подходит, тогда не забывайте использовать параметр hTemplateFile вызова CreateFile.

Не используйте абсолютные пути; не делайте предположений о именовании путей

Вам приходилось когда-либо пользоваться одной из тех программ, которые могут быть установлены только на диск C? Хотя это и очевидно, но крайне раздражает, не так ли?

Как вы знаете, буквы дисков (какая боль) могут отличаться в разных системах. Таким образом, включение букв дисков в пути - не лучший выбор. Лучше используйте относительные пути, или локальные пути. Кроме того, в различных системах семейства windows пути могут быть различны. Размещение каталога "Program Files", например, в разных локализациях различно. В Win32 существуют функции GetWindowsDirectory, GetSystemDirectory, SHGetPathFromIDList, SHGetSpecialFolderPath и SHGetFolderPath. С помощью этих функций можно устранить различия между отдельными установками и локализациями.

Также, вы можете воспользоваться переменными окружения.

Избегайте непосредственного назначения выделенной памяти указателю

Избегайте использования кода, подобного этому:

char *ptr; ptr = (char*)malloc(size);

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

Одним из возможных решений может стать вызов функции, которая всегда проверяет нулевое значение перед вызовом функций, подобных malloc, при этом исключение возвращается обратно, поэтому вы должны помнить о необходимости проверки его возникновения. Конечно теперь вам придётся написать обработчик исключений, но таким образом вы покрываете большие объёмы кода, уменьшая свою работу, и заставляете компилятор использовать меньше условных переходов. Таким образом вы пишете меньше кода, который с большей вероятностью будет правильным, а также более быстрым!

Опасайтесь синтаксиса Си

Одна из возможностей синтаксиса С, которая никогда вам не потребуется, это возможность написать вот такой код: if ( a = 5 ) { ... } Что это означает? Программисту, который использует С, очень просто ответить на этот вопрос, но новичка это может ввести в ступор.

Но худшая сторона, в том что вы скорее всего просто хотели проверить равняется ли а пяти, и просто опечатались. if ( a == 5 ) { ... } моим вариантом написания этого кода будет: if ( 5 == a ) { ... } Теперь вы можете спать немного спокойнее, зная что код написан правильно. Если вы сделаете опечатку, компилятор сразу же сообщит об ошибке, ведь if ( 5 = a ) не корректно.

ReactOS
Search.png
Доклады
О ReactOSARWINSSЧеЗа
Информация Новости Выпуски новостейПереводы блоговНовости проектаВидеоReactOS на ХабреUSB от Вадима Галянта
Разработка Руководство по программированиюОтсутствующая функциональностьВетви разработкиКомпоненты системыReactOS и WineПлан работRoadmap ядра by VgalРазработчикиСовместимость с dll WindowsНаиболее значимые изменения за годИспользуемые проектыGoogle Summer of CodeИзвестные проблемы
Порты AMD64ARMXboxPowerPC
Компоненты Файловые системыРежим совместимостиОтчеты об ошибкахПечатьUSBЯдро
Загрузчик Восстановление MBRЗагрузка из GRUBПараметры загрузки
Прочее ARWINSSПриложения в ReactOSОформление ReactOSКоординаторы"Пасхальные яйца"Монетизация
Другое Типы ядерFreeWin95
Помощь
RAM-диск ReactOS по PXEс жесткого диска
Разработка Стиль написания кодаСтандарты RC-файловРабота с документациейВенгерская нотацияGNU Indent • [ Subversion : ветвислияниеиспользование TortoiseSVN ] • Основы переводаОтправка патчей
Репорты Отладка в VirtualBoxОтладка на экранДобавление программы в менеджер приложенийОтправка отчетов
Отладка Com0comGDBKdbgRossym.gdbRoswin.gdbWinDBGРуководство по WinDBGВключение трассировки ядраКоды DPRINTУдалённый отладчик ReactOS
Сборка CMakeRBuildФайлы RBuildАвтоматическое копирование файловСборка MINGW-w64Сборка модулейСреда сборки
Тестирование VirtualBoxVMwareQEMUHyper-VНеобходимый объём дискаПеренос файлов на виртуальный дискУстановка ReactOSУстановка драйверов
Сеть Общие папкиSambaNFS
Игры Установка DirectPlay
Обновление ReactOSЗагрузочная флешкаЧем можно помочь проектуСоздание нового пользователяЗвук и сеть в VirtualBoxСъемка и публикация видеоIRC-каналСторонние компонентыFAQReactOS как рабочая станцияReactOS и UEFI
Обзоры ОболочкаNTVDMWOWCommunity EditionИстория сайтаReactOS ServerКриптографияПО времен XP