воскресенье, 29 мая 2011 г.

Основные негласные правила программирования: параметры функций

Это перевод Basic ground rules for programming - function parameters and how they are used. Автор: Реймонд Чен.

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

Таким же образом при построении маршрута вы даже не рассматриваете вопрос о срезании пути через чей-то двор или езды не в ту сторону по улице с односторонним движением, так же, как опытный шахматист даже не рассматривают недопустимые ходы при принятии решения, что делать дальше, опытный программист даже не рассматривает нарушения следующих правил - без явного их разрешения в документации:
  • Всё, что не определено - неопределённо. Это может звучать как тавтология, но, на самом деле, это очень полезная тавтология. Многие правила ниже являются просто частным случаем этого правила.
  • Все параметры должны быть допустимы. Контракт функции может применяться только если вызывающий выполнил все свои обязательства, и одно из них - все параметры должны быть тем, чем они заявлены. Это частный случай правила "Всё, что не определено - неопределённо":
    • Указатели должны быть не равны nil, если только противное не указано явно.
    • Указатели указывают на то, на что они должны указывать. Если функция принимает указатель на CRITICAL_SECTION, то вам нужно передавать указатель на корректную CRITICAL_SECTION.
    • Указатели корректно выровнены. Выравнивание указателей - это фундаментальное требование архитектуры, хотя многие люди и пренебрегают им из-за всепрощающей архитектуры.
    • Вызывающий имеет право использовать память, на которую ему указывают. Это означает отсутствие вещей вроде указателей на освобождённую память или на память, над которой у вызывающего нет контроля.
    • Все буфера должны быть доступны и корректны вплоть до задекларированного или подразумеваемого размера. Если вы передаёте указатель на буфер и говорите, что он 10 байтов в длину, то буфер действительно должен иметь размер в 10 байт.
    • Описатели должны ссылаться на корректные объекты, которые не были уничтожены. Если функция хочет описатель окна, то вам нужно дать ей описатель настоящего окна.
  • Все параметры стабильны:
    • Вы не можете менять параметр, пока работает функция, в которую он передан.
    • Если вы передали указатель, то память, на которую он указывает, не должна модифицироваться другим потоком в течение вызова функции.
    • Вы также не можете освобождать эту память во время вызова функции.
  • Передаётся правильное число параметров и в правильном соглашении вызова. Это ещё один особый случай правила "Всё, что не определено - неопределённо".
    • Слава богу, современные компиляторы отказываются передавать неверное число параметров, хотя вы будете удивлены, узнав, скольким людям удаётся обмануть компилятор, чтобы передать неверные параметры, обычно используя изощрённые приведения типов.
    • Когда вы вызываете метод объекта, то Self должен быть объектом. Опять-таки, это - то, что автоматически отслеживает современный компилятор, хотя людям, использующим COM из C (и, да, такие бывают), приходится передавать его вручную, так что иногда они лажают.
  • Время жизни параметров функций:
    • Вызываемая функция может использовать параметры во время своего выполнения.
    • Вызываемая функция не может использовать параметры после возврата управления. Конечно же, если вызывающий и вызываемый устанавливают отдельное соглашение о продлении времени жизни параметров, то применимы и такие правила:
      • Время жизни параметра, который является интерфейсом (указателем на COM объект), может быть увеличено вызовом метода IUnknown.AddRef.
      • Многие параметры передаются в функции с подразумеваемым контекстом, что они будут использованы после выхода из функции. Если это так, то гарантия продления времени жизни параметра на срок, который нужен вызываемой функции, является ответственностью вызывающего. К примеру, если вы регистрируете функцию обратного вызова (callback), то она должна быть допустима, пока вы не отмените её регистрацию.
  • Входные буфера:
      Функции разрешено читать буфер от начала до указанной или подразумеваемой границы буфера, даже если для определения результата выполнения функции требуется только часть информации из буфера.
  • Выходные буфера:
    • Выходной буфер не может пересекаться с входным буфером или любым другим выходным буфером.
    • Функции разрешено писать в выходной буфер в границах, указанными вызывающим, даже если для записи результата нужен не весь буфер.
    • Если функции нужна только часть буфера для хранения результата вызова, то содержимое неиспользованной части буфера не определено.
    • Если функция завершается неудачей и документация не указывает состояние выходных буферов при неудаче, то содержимое выходных буферов не определено. Это частный случай правила "Всё, что не определено - неопределённо".
    • Заметьте, что COM устанавливает дополнительные правила для выходных буферов. COM требует, чтобы все выходные буферы находились бы в маршаллируемом состоянии даже при завершении функции с ошибкой. Для объектов, которые требуют нетривиального маршаллинга (наиболее яркие примеры: интерфейсы и BSTR (WideString)), это означает, что выходные буфера должны быть равны nil при ошибке.
(Запомните, каждый пункт здесь является базовым правилом, а не непреложной истиной. Считайте, что перед каждым пунктом написано "Если явно не указано обратное, то...". Если вызывающий и вызываемый соглашаются на исключении из правила, то это исключение будет работать. К примеру, указатель, помеченный как volatile (прим.пер.: операция не поддерживается в Delphi), явно говорит нам, что "Это значение может быть изменено другим потоком", так что правило про модификацию параметров функций не применимо к такому указателю.)

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

Удобное правило для оценки того, что вы можете и не можете делать: спросите себя, "Как бы мне понравилось, если бы кто-то сделал это со мной?" (это особый случай теста "представьте, если бы это было возможным").

Комментариев нет:

Отправить комментарий

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

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