суббота, 13 ноября 2010 г.

Обратная совместимость является отцом NLS функций

Это перевод Backcompat is the father of the NLS APIs. Автор: Майкл Каплан.

Некоторое время назад у меня был разговор с бесстрашным директором нашей группы Джули Беннетт. Вообще-то, она могла быть тогда менеджером нашей группы. Я завёл с ней разговор, потому что один наш клиент жаловался на то, как LCMapString работает с ключами сортировки.

Эта функция API была спроектирована так, что если вы передавали ей строку и флаг LCMAP_SORTKEY, то она возвращала вам двоичное представление строки (подходящее для использования в качестве индекса).

Один из принципов, согласно которому работает эта функция при создании ключей сортировки, звучит так: функция не трогает целевой буфер, если только
  • буфер, вообще-то, задан и существует. И...
  • функция завершается успешно.
В отличие от других API функций NLS (вроде WideCharToMultiByte и MultiByteToWideChar), LCMapString не использует целевой буфер для своей работы, а просто копирует в него результат в самом конце. Поэтому, если функция завершается неудачей, то буфер будет оставлен в том виде, в котором он был до входа в функцию. Буфер используется только если был получен успешный результат. Это же поведение используют и другие NLS функции вроде GetLocaleInfo, GetNumberFormat и GetDateFormat.

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

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

Я был очень удивлён реакцией Джули.

Не забывайте, что она не занималась разработкой в этой области годы. В тот момент она управляла людьми, которые управляли людьми, которые управляли людьми, которые делали эту работу. Чтобы этот разговор состоялся, я был вынужден встретиться с ней вечером после рабочего дня, полного совещаний, которые включали архитектора из команды Оболочки и вице-президента чего-то ещё. Я видел фильм Head Office, где председатель компании любил лично рассматривать индивидуальные запросы клиентов по телефону, чтобы почувствовать, что он как бы остаётся на связи с клиентом, но она (к счастью!) не была таким микро-менеджером.

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

И она его имела, уж поверьте!

Она отметила, что смутное чувство беспокойства, вероятно, возникло в связи с неявным изменением поведения, которое будет вызвано такой правкой. Когда вы имеете дело с Windows API, у вас могут быть сотни тысяч и более клиентов с миллионами индивидуальных вызовов функций. Никто не может предсказать, как все они могли использовать вашу API функцию, и поведение функции (будь оно документированное или нет) всегда ломает клиентский код, если вы его меняете. Ну, это не делает изменение вообще невозможным, но вы должны минимизировать его, насколько возможно. Даже когда люди вызывают функцию неправильно, вы должны тщательно взвесить пользу от любого изменения в отношении как ожидаемым/разумном, так и непредсказуемым/неразумном.

Например, прогнозируемые изменения в данном API включают в себя обновления для новых языков и, иногда, исправления ошибок для старых языков. К счастью, последнее встречается редко, а первое - часто (надеюсь). Но в любом случае, для того, чтобы не нарушать код клиентов, которые должны зависеть от ключей сортировки, в Windows Server 2003 были добавлены новые API функции (IsNLSDefinedString и GetNLSVersion). Таким образом, любой клиент может использовать механизм (вроде того, что я предложил в Что делает строку осмысленной?), позволяющий обновлениям работать корректно. Таким образом, даже если изменение явно оправдано и достойно реализации, вам нужно решить множество проблем, где, когда и как делать это изменение.

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

Я вышел с этой встречи со следующими мыслями:
  • Я снова вспомнил, почему я работаю в этой группе - наш бесстрашный лидер невероятно крута (я уже говорил об этом);
  • Мне нужно доверять своим чувствам - моё подсознание гораздо умнее меня;
  • Даже если старое поведение неверно (ну, в этом случае это не так), то его изменение ("исправление") не всегда будет наилучшей идеей;
  • Возможно, это просто глупость под вдохновением от сцены с Архитектором в Matrix Reloaded, но я должен сказать, что если Джули - мать NLS API, то обратная совместимость - их отец;
  • Обратная совместимость бьёт козырем значительные изменения шесть дней в неделю и дважды в воскресенье.
Так как вы можете делать изменения, вроде предлагаемого, без нарушения обратной совместимости? Ну, есть несколько вариантов. Вот два примера:
  1. Новое API - очевидно, что у вас будет гораздо меньше беспокойства об обратной совместимости, потому что у нового API нет истории старого поведения. Хотя даже в этом случае важно указать в документации различия, иначе люди просто перенесут ожидания со старого API на новое, потому что у них нет причин этого не делать;
  2. Новый флаг к старому API (известно (и документировано), что если вы передадите в функцию недопустимый набор флагов, то API функция завершится ошибкой, верную в GetLastError значение ERROR_INVALID_PARAMETER). Очевидно, что этот подход требует аккуратной синхронизации с авторами документации.
Те же правила применимы и к миру .NET Framework.

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

Вам, быть может, интересно, что станет с тем конкретным запросом? Ну, пока рано говорить, но вы же знаете меня - когда мне есть что сказать, я скажу.

Но пока что следующее поколение владельцев NLS API выучило уроки, которые предыдущее поколение и так уже знало. И это приносит уверенность в будущем NLS API :-)

This post brought to you by "8" and "∞" (U+0038 and U+221e, a.k.a. DIGIT EIGHT and INFINITY)
Два символа, являющиеся лучшими друзьями!

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

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

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

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

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

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

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