Применяем стандарты кодирования NASA к JavaScript
Jet Propulsion Laboratory — научное учреждение выполняющее большой объем разработок и исследований для NASA. На счету JPL такие известные миссии, как марсоход Curiosity и недавно покинувший пределы солнечной системы зонд Voyager. JPL разрабатывала программное обеспечение для большинства беспилотных миссий по изучению дальнего космоса и других планет при этом высокая степень автоматизации зондов и длительность миссий обусловили бескомпромиссные требования к качеству программ. В результате сформировались рекомендации по написанию кода которые впитали фантастический опыт инженеров NASA и недавно были опубликованы. Так как требования к программам выполняемым на web — платформе постоянно растут и ей доверяются все более критические задачи давайте применим стандарты кодирования NASA для JavaScript / HTML приложений ради увеличения производительности, надежности и во имя лучшего мира.
1.Функции должны быть достаточно короткими что бы при печати уместиться на стандартном листе. Обычно это значит не более 60 строк на функцию.
Эта рекомендация отлично подходит и для JavaScript. Декомпозированный код гораздо проще воспринимать, тестировать и поддерживать.
2.Ограничьтесь простыми управляющими конструкциями. Не используйте goto setjmp, longjmp, а так же прямую или косвенную рекурсию.
Cовет из мира C заставляет задуматься. Мы определенно не будем использовать goto или setjmp в JS, но как быть с рекусией? Почему NASA выступает против простейшей техники преподаваемой еще в школе? Причина кроется в статических анализаторах применяемых NASA для уменьшения вероятности ошибок. Для этих анализаторов рекурсии делают код менее предсказуемым. В JS другая ситуация и существующие анализаторы не предписывают уклоняться от использования рекурсий.Что мы можем вынести из этого совета?
- Используйте оправданно простые конструкции. Если хотите писать очень надежный код — бросьте писать очень хитрый код и начните писать предсказуемый. Установите стандарт кодирования и соблюдайте его;
- Используйте анализаторы позволяющие уменьшить вероятность ошибок: JSHint/JSLint/Google Closure Tools;
- Поддерживайте репозиторий в здоровом состоянии с помощью метрик: Plato;
- Анализируйте типы с помощью Flow/Google Closure Tools.
3.Не используйте динамическое выделение памяти после инициализации модуля.
На первый взгляд JavaScript движок сам управляет памятью, а проходящий время от времени garbage collection решает все наши проблемы. Это не так. Память в JS имеет свойство течь, garbage collection не управляем и уменьшает производительность системы во время работы, а избалованные веб — программисты не имеют культуры управления памятью. В результате из этого правила можно выделить три рекомендации. Первые две будут вполне разумной практикой в любом проекте, третий только в проектах требующих предсказуемого расхода памяти и максимальной производительности.
- С уважением относитесь к переменным, упорядочивайте их объявление. Если позволяет стандарт кодирования — объявляйте переменные в начале изолированного блока, это улучшит видимость использования.
- Следите за утечками памяти, очищайте ссылки на переменные, убирайте слушатели, избегайте создания замыканий где это возможно. Классическая статья.
- Можно заставить JS работать в режиме статического выделения памяти и сделать garbage collection управляемым (а значит избежать падения производительности и sawtooth pattern) с помощью пула объектов.
4.Все циклы должны иметь четко заданный верхний предел.
По объяснению JPL это делает статический анализ еще более эффективным и позволяет избежать бесконечного выполнения цикла. В случае если лимит на повторение превышен — функция возвращает ошибку и таким образом выводит систему из неожиданного состояния. Что же, вполне логично для программы с up-time 20+ лет! Проверка на превышение лимита осуществляется с помощью утверждений (assertions) подробнее о которых в пункте пятом. Если вы полностью примете парадигму assertions — ставьте уже и лимиты на циклы, вам это понравится.
5.Устанавливайте не меньше двух проверок (assertions) на функцию.
Тут нужно немного сказать о проверках. Проще всего представить их, как юнит — тесты, выполняемые по ходу исполнения программы.
if (!c_assert(altitude > MAX_POSSIBLE_ALTITUDE) == true) { return ERROR; }
Этот раздел стандарта буквально гласит следующее:
«Статистически на каждые 10 — 100 строк кода приходится один дефект найденный с помощью юнит тестов. Чем больше плотность тестов — тем больше дефектов мы можем обнаружить автоматически.»
Отлично, значит мы можем расценивать это правило, как «Пишите юнит тесты!«? И да и нет.
Особенностью проверок и их приемуществом является постоянный контроль за состоянием системы по ходу выполнения. Самым близким аналогом этой практики для JavaScript будет совмещение юнит тестов и run-time контроля данных с генерацией ошибок при отклонениях.
- Чем выше плотность тестов, тем меньше дефектов. Минимальное количество тестов — 2 на функцию;
- Следите за состоянием системы в run-time, генерируйте ошибки в случае неполадок и обрабатывайте их.
6.Переменные должны располагаться в самом нижнем скоупе из возможных.
Правило рождено желанием поместить данные в приватное пространство и оградить их от записи извне. Разумная и простая практика.
7.Возвращаемое значение любой не — void функции должно проверяться вызывающей функцией. Все входящие аргументы функций должны проверяться.
По заверению авторов рекомендаций это правило нарушается чаще остальных, ведь в самой строгой интерпретации оно требует проверки результата даже встроенных функций.
Мое мнение — стоит проверять возвращаемые значения third — party библиотек, а так же входящие аргументы функций на существование и соответствие ожидаемому типу.
8.Использование препроцессора C должно быть ограничено включением заголовков и простыми определениями. Препроцессор — мощный инструмент, однако приводящий к уменьшению читабельности и дополнительным затратам на тестирование.
Вынужден согласиться — использование препроцессоров должно быть ограниченно в любых языках. Они не нужны пока есть стандартный, понятный и надежный синтаксис для ввода команд в машину, который к тому же активно развивается. Надежный и быстрый JavaScript стоит писать на JavaScript… Интересный материал о стоимости транспайлинга JS
9.Использование указателей должно быть ограниченно. Указатели на функции запрещены.
Это то правило из которого JavaScript разработчик не сможет вынести пользы.
10.С самого первого дня разработки код должен успешно проходить тесты и статический анализ с самыми строгими, педантичными параметрами проверки. При этом не должен вызывать преудпреждающих сообщений и тем более сообщений об ошибках.
Мы все это знаем… Не копите синтаксические замечания, не откладывайте фиксы. Держите код чистым, а перфекциониста в себе живым!
«Эти десять правил были экспериментально использованы в JPL при написании критического ПО с воодушевляющими результатами. После преодоления здорового сопротивления столь строгим правилам разработчики находят их полезными, приводящими к лучшей надежности, читабельности и тестируемости кода. Если сначала правила выглядят драконовскими, то вы должны понимать — они позволяют писать лучший код там, где от него могут зависеть жизни людей: ПО для контроля самолетов, ядерных электростанций находящихся в нескольких километрах от вашего дома или космических кораблей доставляющих космонавтов на орбиту. Правила работают, как ремень безопасности в вашей машине — сначала они мешают, но со временем становятся вторым Я которое невообразимо игнорировать.»(с) Gerard J. Holzmann, автор «The Power of Ten — Rules for Developing Safety Critical Code»
Заключение
Начиная писать эту статью, я не представлял как много мы можем перенять у NASA. Сравнение виделось нелепым сродни сравнению опытного инженера и школьника — самоучки, но это не так. Открытием стала зрелость web — платформы: существующий уровень проектной инфраструктуры, уже устоявшееся использование лучших практик из «старших» языков и изученность возможностей поражают. Хотя так много еще можно достичь…