Случайны выбор дневника Раскрыть/свернуть полный список возможностей


Найдено 20835 сообщений
Cообщения с меткой

программирование - Самое интересное в блогах

Следующие 30  »
rss_rss_hh_new

Видео о статическом анализе кода

Среда, 26 Апреля 2017 г. 10:15 (ссылка)

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



Русскоязычные видео



Статический анализ кода



Picture 19




https://youtu.be/nz1ZmHArPX4



Автор: Артём Кошелев



Составляющие качественного кода — понятность, простота, архитектура. В видео рассказывается, что из себя представляет статический анализ, какого его устройство и какие инструменты он использует. Поднимаются вопросы об особенностях, достоинствах и недостатках динамического и статического анализа. Рассматриваются популярные инструменты статического анализа, в частности SonarQube.



Статический анализ, как гигиена кода



Picture 20




https://youtu.be/YNrr_mMgEDU



Автор: Андрей Карпов



Видео с конференции DotNext 2015 Moscow. В докладе рассказывается о способах обнаружения ошибок, методологии статического анализа, правильном и неправильном использовании инструментов анализа кода. Автор также приводит мифы о статическом анализе, которые могут ввести в заблуждение разработчиков. Демонстрируются примеры ошибок в Open Source проектах, выявленных с помощью таких инструментов, как ReSharper, PVS-Studio, Visual Studio SCA.



Статический анализ в C++ и анализ производительности



Picture 10




https://youtu.be/6m1KcWxWDNg



Авторы: Александр Нежельский, Евгений Буштырёв, Никита Какуев, Николай Дьяконов.



Запись доклада, озвученного в рамках события CoLaboratory в штаб-квартире «Лаборатории Касперского». В видео обсуждается статический анализ C++ кода и анализ производительности программ. В числе прочего авторы рассказывают, как сделать код пригодным для статического анализа, как бороться с ложными срабатываниями и как расширять функциональность Clang Static Analyzer за счет собственных проверок.



Современный статический анализ кода: что умеет он, чего не умели линтеры



Picture 25




https://youtu.be/aQaJs7PcJao



Автор: Павел Беликов



Видео с конференции C++ CoreHard Winter 2017. Автор показывает, чему со времени появления по сегодняшний день научились статические анализаторы. Рассматриваются различные методики анализа, как они появлялись и какие ошибки можно с их помощью найти. В видео проводится разбор ошибок, найденных в Open Source проектах и рассказывается, чем статический анализатор отличается от «линтеров» и некоторых других инструментов, а также какие проблемы, помимо анализа кода, он решает.



Сценарии использования статического анализатора



Picture 11




https://youtu.be/k9zjmg7spEA



Автор: Валерий Игнатьев



Доклад со встречи SPB .NET, где автор рассматривает сценарии использования статического анализатора и предостерегает от возможных ошибок. Так же вкратце рассматриваются коммерческие инструменты статического анализа.



Сlang-Tidy путешествие внутрь C++ Abstract Syntax Tree



Picture 24




https://youtu.be/6azelG0PbAs



Автор: Юрий Ефимочев



Видео с конференции CoreHard Summer Conf 2016. Семейство библиотек Clang предоставляет разработчикам широчайшие возможности по реализации различных инструментов, основанных на разборе и анализе абстрактного синтаксического дерева (AST). В частности, авторы Clang выпускают такой инструмент, как Clang-Tidy, который является мощным статическим анализатором кода. В видео рассматривается, как этот инструмент применяется в процессе разработки для С++ и как дополнить его собственными проверками. Попутно идет разбор некоторых занимательных особенностей AST для С++.



Не все статические анализаторы одинаково полезны



Picture 1




https://youtu.be/axwoBjT3R-M



Автор: Владимир Кошелев



Видео с конференции DotNext 2016 Spb. В докладе речь пойдет о популярных инструментах, ищущих нарушения Guidelines, ошибки copy-paste и опечатки в исходном коде. Обсуждаются результаты работы этих инструментов на наборе Open Source проектов, а также об используемой при сравнении методике. Далее рассматриваются более сложные ошибки, такие как возникновение NullReferenceException или утечка ресурсов, и способы их обнаружения. Помочь обнаружить такие ошибки может как чисто статический анализ, например, Coverity Prevent, так и статико-динамический, такой как IntelliTest(Pex).



Статический анализ кода в контексте SSDL



Picture 3




https://youtu.be/30re90uJSjc



Автор: Иван Ёлкин



Видео с форума PHDays VI. Ведущий фаст-трека рассказывает об опыте внедрения Static Analysis Security Tool в QIWI, о сложностях, с которыми сталкивались разработчики. Разбирает такие вопросы, как писать «костыли» или рефакторить код, а также что делать, когда мнения клиента и разработчика расходятся. Расскажет, сколько строк кода пришлось прочитать и написать до и после запуска сканера, и предложит краткий обзор найденных и упущенных уязвимостей.



Статический анализ кода JS



Picture 9




https://youtu.be/F7FtAxUHd54



Автор: Антон Хлыновский



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



Англоязычные видео



Что такое статический анализ?



Picture 2




https://youtu.be/POvX4hYIoxg



Автор: Matt Might



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



Статический анализ кода: просканируйте весь свой код на ошибки



Picture 12




https://youtu.be/Heor8BVa4A0



Автор: Jared DeMott



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

Статический анализ кода: предотвращение ошибок и замедлений до того, как они случаются



Picture 6




https://youtu.be/VxeC7WFfg3Q



Автор: Vinny DaSilva



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



Сделаем код более безопасным! — Обзор жизненного цикла безопасной разработки и статический анализ кода



Picture 13




https://youtu.be/DyWpRmhGnRI



Автор: Jason Cohen



Несмотря на экспоненциальный рост продуктов, спецслужб, компаний, сертификатов безопасности и общего интереса к этой теме, до сих пор каждый день обнародуются все новые уязвимости безопасности. Внедрение передового опыта, принципов жизненного цикла разработки, а также статического анализа кода в этой области, может значительно снизить вероятность возникновения общих вопросов безопасности. Обработка входных данных, Cross-Site-Scripting, переполнение буфера и другие задачи по-прежнему представляют основную проблему безопасности. Статический анализ кода может помочь найти многие из этих незаметных уязвимостей до того, как код выйдет из рук разработчика. В видео рассматриваются общие передовые практики развития жизненного цикла безопасности и как интегрировать их в современные схемы разработки кода.



Охота на баги с использованием статического анализа кода



Picture 27




https://youtu.be/Sb011qfbMkQ



Автор: Nick Jones



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



Текущее состояние (бесплатного) статического анализа



Picture 4




https://youtu.be/sn1Vg8A_MPU



Автор: Jason Turner



Видео с конференции CPPCON2015. Обсуждаются имеющиеся в настоящее время бесплатные статические анализаторы, доступные для C++. Автор рассказывает какие ошибки могут поймать эти инструменты, а какие пропускают и почему статический анализ должен быть частью обычного процесса сборки.



Статический анализ и С++: больше чем Lint



Picture 14




https://youtu.be/rKlHvAw1z50



Автор: Neil MacIntosh



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



Подружимся с инструментами статического анализа Clang



Picture 5




https://youtu.be/AQF6hjLKsnM



Автор: Gabor Horvath



Видео с конференции CPPCON2016. В этом докладе представлен обзор инструментов статического анализа для C++ с открытым исходным кодом. Акцент делается на инструментах на основе Clang. Понимание различных методов разработки таких инструментов может быть очень полезным, поскольку это помогает создавать более дружественный статический анализатор кода и понимать причину ложных срабатываний, а также показывает ограниченность имеющихся в настоящее время инструментов. Автор дает краткий учебный материал о том, как использовать и интегрировать их в поток работы.



Поиск багов при помощи Clang на этапе компиляции и во время выполнения



Picture 22




https://youtu.be/kkokHPKlhzQ



Автор: Bernhard Merkle



Видео с конференции ACCU 2016. Анализ кода и проверка приобретают все большее значение в программировании и обеспечении качества программных проектов. Особенно в таких языках, как С/С++, ошибки могут вызвать неопределенное поведение и утечки памяти. Инструменты статического анализа хорошо в этом помогают, но бывает трудно обнаружить проблемы, которые возникают во время выполнения программы. В этом докладе показывается использование возможностей Clang для поиска багов, как во время компиляции (с помощью статических анализаторов), так и во время выполнения (с помощью санитайзеров). Сочетание обоих подходов позволяет повысить качество программного обеспечения.



Статический анализ исходного кода. Следующее поколение



Picture 26




https://youtu.be/W_xIm5Djnpk



Автор: James Croall



Видео с конференции Devoxx 2016. Прошли времена «линтеров» и прославленных проверок орфографии. Сегодня статический анализ исходного кода является точным и надежным, и может найти сложные дефекты кодирования в параллельных программах, которые незаметны человеческим взглядом. В докладе рассказывается, как в Open Source проектах разработчики, используя программное обеспечение Coverity, нашли и устранили критические ошибки, приводящие к падениям программ, и дефекты в области безопасности в языке Java.



Статический анализ сегодня спас мой код



Picture 8




https://youtu.be/TelqURPdQmQ



Автор: Damien Seguy



Видео с конференции PHP UK Conference 2017. Инструменты статического анализа проверяют PHP-код без его запуска. Полностью автоматизированные, они привносят опыт, чтобы просмотреть код, обеспечить соблюдение передовых методов при программировании, поддерживать готовность кода для следующей версии PHP. PHP 7 значительно расширил возможности по аудиту кода — благодаря AST и возвращаемым типам, можно провести более глубокий анализ и предотвратить большее количество ошибок. В этом видео автор рассмотрит текущее состояние инструментов статического анализа, покажет, что они могут найти, а также продемонстрирует, как интегрировать их в цикл разработки.



Статический анализ кода для Python



Picture 28




https://youtu.be/mfXIJ-Fu5Fw



Автор: Andrew Wolfe



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



Дополнение статического анализа с использованием Ablation



Picture 17




https://youtu.be/wHIlNRK_HiQ



Автор: Paul Mehta



Конференция BH USA 2016. Ablation является дополнительным инструментом к статическому анализу, созданным для извлечения информации из выполняемого процесса. Эта информация затем импортируется в среду дизассемблирования, где она используется для разрешения виртуальных вызовов, выделения области исполняемого кода и визуально различия сценариев. Цель Ablation заключается в расширении статического анализа с минимальными расходами или вовлечением пользователя. Ablation позволяет легко сравнивать сценарии и выделить различающиеся. Это достигается путем сравнения исполняемого кода, а не просто сопоставления данных. Также в видео рассматривается сравнение запутанного сценария падения и исходного сценария.



Заключение



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



Другие материалы



Вы можете прислать ссылки на видеоматериалы о статическом анализе кода, которые показались вам интересными, и мы добавим их в конец этой статьи.







Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Ekaterina Milovidova. Videos about static code analysis



Прочитали статью и есть вопрос?
Часто к нашим статьям задают одни и те же вопросы. Ответы на них мы собрали здесь: Ответы на вопросы читателей статей про PVS-Studio, версия 2015. Пожалуйста, ознакомьтесь со списком.

Original source: habrahabr.ru.

https://habrahabr.ru/post/327388/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

Второй вебинар по SAP Cloud Platform: детальный обзор сервисов

Среда, 26 Апреля 2017 г. 08:48 (ссылка)

Мы продолжаем серию вебинаров по SAP Cloud Platform и сегодня в 11:00 приглашаем всех желающих познакомиться с некоторыми сервисами для платформы. Эксперты SAP расскажут о следующих темах:


  • как связать воедино разные программные приложения на уровне процессов и данных — создать единый бизнес-процесс, состоящий из бизнес-сервисов, с помощью Integration Service

  • как «принести» и использовать свой собственный готовый программный код для запуска на платформе SCP (с помощью Cloud Foundry)

  • как превратить свой программный сервис в продаваемый продукт с помощью API Management

  • сервис геймификации, позволяющий превратить скучное бизнес-приложение в захватывающее приключение

  • WebIDE



Для участия в вебинаре необходимо зарегистрироваться.



Напоминаем, что серия вебинаров приурочена к проведению онлайн-конкурса по разработке приложений SAP Кодер.



Следующий вебинар состоится 28 апреля и будет посвящен детальному обзору дополнительных сервисов, которые можно использовать для решения конкурсных задач, а также ответам на вопросы по задачам конкурса.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/327384/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Перевод] Почему Apache Ignite — хорошая платформа для микросервисов

Среда, 26 Апреля 2017 г. 07:57 (ссылка)





Прим. Переводчика. Статья может быть интересна архитекторам и разработчикам, планирующим построение решения на основе микросервисов, либо ищущим способы оптимизации текущего решения, особенно если работа идет с большими объемами данных. Перевод сделан на основе части 1 и части 2 цикла статей о микросервисах на Apache Ignite. Предполагается общее знакомство с экосистемой Java (Apache Ignite работает также с .NET, C++, а через REST и с другими языками, но примеры в статье будут апеллировать к Java), рекомендуется наличие базового знания Spring.



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



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


  • дисковые базы данных не справляются с нарастающими объемами информации, которую необходимо хранить и обрабатывать; базы данных становятся узким местом, в которое «упирается» ваше решение;

  • требования к высокой доступности, которые когда-то были «вишенкой на торте», теперь являются необходимым минимумом для продукта, который хочет успешно конкурировать на рынке.



Цель этой статьи — рассказать, как вы можете решить эти проблемы в своем продукте, используя Apache Ignite (или GridGain In-Memory Data Fabric), чтобы построить отказоустойчивое и масштабируемое решение в микросервисной парадигме.



Разбор решения на базе Apache Ignite



Давайте рассмотрим типичную архитектуру микросервисного решения, которое использует Apache Ignite, приведенную на рисунке ниже.







Слой кластера Apache Ignite



Кластер Apache Ignite используется для достижения 2 целей.



Во-первых, это основное хранилище данных, в котором они содержатся в оперативной памяти. Поскольку данные находятся в памяти, микросервису не нужно опосредованно через СУБД выполнять множество дорогостоящих обращений к диску, что значительно повышает общую производительность системы.



«Узлы данных» (data nodes) — особая группа в кластере, которая отвечает за хранение данных. Это «серверные» узлы Apache Ignite, которые хранят участки данных и позволяют исполнение на этих участках запросов получения данных и вычислений. При этом не требуется разворачивать классы данных и вычислений: Apache Ignite полагается на собственный кросс-платформенный бинарный формат, а также имеет механизмы обмена классами с логикой между узлами кластера (peer class loading).



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



Во-вторых, кластер управляет жизненным циклом микросервисов и предоставляет все необходимые для взаимодействия между ними и узлами данных API.



Для этого решения на основе кластера Apache Ignite используют Узлы сервисов (service nodes, сервисные узлы). Это узлы, где развернут код микросервисов, содержащий необходимую бизнес-логику. Отдельный узел может содержать один и более микросервис, в зависимости от особенностей конкретного решения.



Каждый микросервис реализует интерфейс Service, а также регистрируется в узле Apache Ignite, после чего внутренние механизмы кластера позволяют обеспечить отказоустойчивость и дают удобный механизм для вызова микросервиса из других частей кластера. Apache Ignite берет на себя заботу по развертыванию одной и более копий микросервиса на сервисных узлах. Внутренняя механика кластера также обеспечивает балансировку нагрузки и отказоустойчивость.



На вышеприведенном рисунке микросервисы помечены как MS (MS1, MS2 и т.д.). За счет разнесения логики между сервисными узлами и узлами данных исчезает необходимость перезапускать весь кластер, если микросервис MS1 необходимо обновить. Все, что необходимо сделать — обновить классы MS1 на сервисных узлах, где он развернут. Далее необходимо будет перезапустить только некоторое подмножество узлов, минимизируя потенциальное влияние на систему.



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



Слой постоянного хранения данных



Этот слой не обязателен и может быть использован в сценариях, где:


  • нет смысла либо невозможно держать все данные в памяти;

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



Чтобы использовать слой постоянного хранения данных, необходимо указать для Apache Ignite реализацию интерфейса CacheStore. Среди реализаций по-умолчанию можно найти различные реляционные СУБД, а также MongoDB, Cassandra и т.д.



Слой взаимодействия с внешними приложениями



Это слой «клиентов» ваших микросервисов, с этого слоя инициируются различные ветки исполнения путем вызова одного или более микросервисов.



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



Данная архитектура обеспечивает возможность горизонтального масштабирования, позволяет хранить данные в памяти и гарантирует высокую доступность (high availability) микросервисов.



Пример реализации



Далее рассмотрена возможная реализация первого слоя кластера Apache Ignite. Код, с которым будет происходить работа, можно посмотреть на: https://github.com/dmagda/MicroServicesExample.



В частности, будет показано, как можно:


  • настраивать и запускать узлы данных;

  • реализовывать сервисы с использованием Apache Ignite Service Grid API;

  • настраивать и запускать сервисные узлы;

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



Узлы данных



Как было написано выше, узел данных — это серверный узел Apache Ignite, который содержит часть данных и позволяет выполнять запросы и вычисления, инициируемые со стороны бизнес-логики приложения. Узлы данных отвязаны от бизнес-логики и инкапсулируют в себе только механизмы хранения и обработки данных, принимая конкретные запросы по ним извне.



Давайте рассмотрим создание таких узлов на примере. Для этого вам потребуется скачать GitHub-проект, который упоминался ранее: https://github.com/dmagda/MicroServicesExample.



Найдите в проекте файл data-node-config.xml. Этот файл используется для запуска новых узлов данных. В нем можно увидеть определение кешей, которые должны быть развернуты на кластере, а также иные настройки, специфичные для узлов данных. Рассмотрим основные из них.



Прим. переводчика. XML-конфигурация Apache Ignite использует Spring для построения дерева объектов. Если читатель не знаком с конфигурацией Spring, необходимую информацию можно получить, например, из официальной документации (англ.) либо из каких-либо обучающих материалов. Естественно, возможно создание IgniteConfiguration напрямую, а также, поскольку используются механизмы Spring, построение конфигурации на основе аннотаций или диалекта Groovy.



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




...







Во-вторых, реализуется класс фильтра, который мы определили выше. В данном примере применяется один из наиболее простых подходов, когда критерием того, будет ли узел отвечать за хранение данных, является атрибут узла “data.node”. Если этот атрибут установлен и равен true, узел будет считаться узлом данных и содержать на сете кеши. В ином случае, узел будет игнорироваться при распределении данных по кластеру.



public boolean apply(ClusterNode node) {
Boolean dataNode = node.attribute("data.node");
return dataNode != null && dataNode;
}




В третьих, в конфигурации определяется значение параметра “data.node” для каждого узла, который будет использовать эту конфигурацию при запуске.











Попробуйте использовать класс DataNodeStartup, чтобы запустить узел данных, либо используйте ignite.sh или ignite.bat-скрипты, передав им как аргумент конфигурацию, определенную в data-node-config.xml. В последнем случае, не забудьте предварительно собрать JAR-файл, который будет содержать классы из java/app/common, и положить этот JAR-файл на classpath каждого узла данных.



Сервисные узлы



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



Для начала необходимо определить микросервис, используя Apache Ignite Service Grid API. В рамках статьи будет рассмотрен пример MaintenanceService, приложенный в репозитории на GitHub.



Интерфейс сервиса выглядит следующим образом:



public interface MaintenanceService extends Service {
public Date scheduleVehicleMaintenance(int vehicleId);
public List getMaintenanceRecords(int vehicleId);
}




Сервис позволяет планировать техническое обслуживание машины, а также получать список назначенных обслуживаний. Реализация, помимо бизнес-логики, содержит определение специфических для Service Grid методов, таких как init(…), execute(…) и cancel(…).



Есть несколько способов опубликовать данный микросервис на подмножестве кластера. Один из них, который будет использоваться в примере, — определить конфигурационный файл maintenance-service-node-config.xml, и запускать сервисные узлы с этим конфигурационным файлом и необходимыми классами на classpath. В таком случае конфигурация будет выглядеть следующим образом.



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











Реализация фильтра выглядит следующим образом:



public boolean apply(ClusterNode node) {
Boolean dataNode = node.attribute("maintenance.service.node");
return dataNode != null && dataNode;
}




В данной реализации критерием служит наличие у узла атрибута “maintenance.service.node”, установленного в значение true.



Наконец, узлы будут получать этот атрибут за счет следующего участка конфигурации maintenance-service-node-config.xml:











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



Свойство name сервиса отвечает за уникальное в рамках кластера наименование, по которому, например, к сервису можно будет обращаться через сервис-прокси, свойство service ссылается на класс, реализующий логику сервиса, а свойства totalCount и maxPerNodeCount обозначают общее количество экземпляров, которые необходимо поддерживать и максимально допустимое количество экземпляров на конкретном узле соответственно. В данном случае, сконфигурирован кластер-синглтон: в рамках несегментированного кластера всегда будет только 1 экземпляр сервиса.





























Попробуйте запустить несколько экземпляров сервисных узлов используя MaintenanceServiceNodeStartup либо передавая maintenance-service-node-config.xml в ignite.sh или ignite.bat, предварительно положим все необходимые классы из java/app/common и java/services/maintenance на classpath каждого из узлов.



В репозитории на GitHub также можно найти пример сервиса VehicleService. Запустить экземпляры этого сервиса можно используя класс VehicleServiceNodeStartup, либо передав файл vehicle-service-node-config.xml в ignite.sh или ignite.bat, предварительно поместив все необходимые классы на classpath.



Пример приложения



Как только у нас настроены и готовы к запуску узлы данных, а также сервисные узлы с сервисами MaintenanceService и VehicleService, можно запустить наше первое приложение, которое будет использовать инфраструктуру распределенных микросервисов.



Для этого запустите файл TestAppStartup из репозитория GitHub. Приложение присоединится к кластеру, заполнит кеши данными и выполнит операции на развернутых микросервисах.



Код для выполнения операций на сервисе выглядит следующим образом:



MaintenanceService maintenanceService =
ignite.services().serviceProxy(
MaintenanceService.SERVICE_NAME,
MaintenanceService.class, false);

int vehicleId = rand.nextInt(maxVehicles);
Date date = maintenanceService.scheduleVehicleMaintenance(vehicleId);




Как можно заметить, приложение работает с сервисами используя сервис-прокси. Прелесть данного подхода в том, что узел, играющий роль клиента, не обязан иметь классы реализации сервиса, развернутые локально на classpath — достаточно только интерфейсных классов, — а также не обязан иметь запущенным локально сам сервис: прокси обеспечит прозрачное взаимодействие с удаленной реализацией.



Заключение



В данной статье была рассмотрена архитектура, предполагающая использование микросервисов поверх Apache Ignite, а также был рассмотрен пример приложения, реализующего данную архитектуру. В следующих статьях будет рассмотрено, как связать кластер со слоем постоянного хранения данных, а также как выделить слой взаимодействия с внешними приложениями.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/327380/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Перевод] Простые числа Мерсенна и тест Люка-Лемера

Вторник, 25 Апреля 2017 г. 16:17 (ссылка)



Перевод поста Джона Макги (John McGee) "Mersenne Primes and the Lucas–Lehmer Test".

Код, приведенный в статье, можно скачать здесь.

Выражаю огромную благодарность Полине Сологуб за помощь в переводе и подготовке публикации



Содержание



Введение.

Теорема множителей Эйлера и Мерсенна

Люка и Лемер

От ${M_{13}}$ до ${M_{20}}$

Совершенные числа

21-е, 22-е и 23-е числа Мерсенна

24-е, 25-е и 26-е числа Мерсенна.

27-е и 28-е числа Мерсенна

29-е число Мерсенна

30-е и 31-е числа Мерсенна

Великий интернет-поиск чисел Мерсенна

Факторизация чисел Мерсенна


Введение.



Простое число Мерсенна — простое число вида ${M_p} = {2^p} - 1$ (значение степени р также должно быть простым). Эти простые числа получили свое название от имени французского математика и религиозного ученого Мерсенна, который и составил данный список простых чисел этой формы в первой половине семнадцатого века. Первые четыре из них были известны уже давно: ${M_2} = 3$, ${M_3} = 7$, ${M_5} = 31$ и ${M_7} = 127$.



Мерсенн утверждал, что значение ${2^p} - 1$ будет простым для простых чисел $p \leqslant 257$, принадлежащих множеству $p \in \left\{ {{\text{2}}{\text{,3}}{\text{,5}}{\text{,7}}{\text{,13}}{\text{,17}}{\text{,19}}{\text{,31}}{\text{,67}}{\text{,127}}{\text{,257}}} \right\}$. Во всем ли он был прав, можно проверить с помощью функции Wolfram LanguagePrimeQ, в которой используются современные методы тестирования чисел на простоту, для которых не требуется поиска конкретного множителя, чтобы доказать, что число составное.











Вполне возможно, что его утверждение о том, что ${M_{67}}$ — простое число, просто опечатка, и на самом деле он имел в виду ${M_{61}}$. Несложно понять, что проверка на простоту была при жизни Мерсенна делом затруднительным, поскольку проверка делением была одним из немногих доступных инструментов. Например, для ${M_{257}}$ наименьшим множителем оказывается 15-значное число, так что даже с современными методами факторизации его не так-то легко найти. В функции FactorInteger используются наиболее совершенные методы, которые позволяют применять метод факторизации в отношении больших целых чисел.













Теорема множителей Эйлера и Мерсенна



Первые важные достижения в области проверки на простоту принадлежат великому математику Леонарду Эйлеру, который незадолго до 1772 года уточнил, что ${M_{31}}$ является простым. Он сделал это, продемонстрировав, что любой простой делитель ${M_{31}}$ должен быть равен 1 или 62 (mod 248).











Такой относительно короткий список даже во времена Эйлера мог быть проверен с помощью пробного деления (вручную). Ему принадлежит применение теоремы Мерсенна, в которой говорится, что если $q$ является делителем ${M_p}$, то $q \equiv 1 \vee - 1\left( {\bmod 8} \right)$, $q \equiv 1\left( {\bmod p} \right)$ и $q \equiv 2kp + 1$ для некоторого целого положительного числа $k$. Эти факты значительно ограничивают количество возможных делителей ${M_p}$. С помощью функций, представленных ниже, демонстрируется использование этой теоремы с целью предоставления списка возможных делителей ${M_p}$, меньших, чем $\sqrt {{2^p} - 1} $.



















Мы используем эти функции, чтобы быстро найти делитель ${2^{41}} - 1$. Обратите внимание, что $q$ является делителем ${2^{p}} - 1$ тогда и только тогда, когда ${2^p} \equiv 1\left( {\bmod q} \right)$. Это дает возможность использовать функцию PowerMod, что обеспечивает эффективное возведение в степень по модулю.







































Ниже приводится число Мерсенна с 161649 знаками:.





































Люка и Лемер



Следующим важным шагом стало открытие Эдуардом Люка метода для проверки простоты чисел данной формы. Он использовал свой метод в 1876 году для проверки, является ли ${M_{127}}$ (самое большое число Мерсенна "докомпьютерной" эпохи) простым. В начале двадцатого века, когда основы двоичной арифметики и алгебры стали широко известны, Дерек Генри Лемер усовершенствовал метод Люка. Полученный в результате тест простоты чисел Люка-Лемера обеспечивал эффективную проверку, если число данной формы являлось простым. Проверка проводилась с помощью сравнения по модулю:







Это означает, что $k$ идентично числу, представленному его $p$ битами низшего порядка, плюс — числами, представленными остальными битами. Это соотношение может применяться рекурсивно до тех пор, пока $k < {2^p} - 1$.



Рассмотрим следующий пример. Здесь мы покажем, что для $k = {\text{1234567891}}$. Обратите внимание, что (23 бита низшего порядка) и — означает, что остальные биты сдвинуты в крайнее нижнее положение.















































Функция ниже задает этот метод для вычисления $k\bmod \left( {{2^p} - 1} \right)$ с использованием только битовых операций (без деления). Обратите внимание на то, что ${2^n} - 1$ имеет двоичную форму ${111...111_2}$, при этом нет ни одного 0, и поэтому она также служит в качестве маски для $p$ битов низшего порядка числа $k$.







Следующая функция реализует тест простоты Люка-Лемера (LLT). Определим последовательность ${s_0} = 4$, ${s_i} = s_{i - 1}^2 - 2;i > 0$. Тогда ${M_p} = {2^p} - 1$ является простым тогда и только тогда, когда ${s_{p - 1}} \equiv 0\left( {\bmod {M_p}} \right)$.







Опыт показывает, что основное время выполнения этих функций тратится на целочисленную арифметику.



Чтобы проверить, является ли ${2^p} - 1$ простым, лучше сначала проверять простоту на небольших простых делителях и выполнять другие проверки простоты. Сначала мы используем теорему делителей простых чисел Мерсенна, закодированную в checkMPDivisors, а затем функцию PrimeQ. Если это не сработает, применим тест Люка-Лемера.







Здесь мы представляем расширенную версию функции PrimeQ, которая применяет тест Люка-Лемера для больших чисел вида ${2^p} - 1$.







От ${M_{13}}$ до ${M_{20}}$



Первым простым числом Мерсенна, обнаруженным на компьютере с помощью теста Люка-Лемера, стало ${M_{521}}$, найденное Рафаэлем Робинсоном 30 января 1952 года на базе лампового компьютера SWAC (Standards Western Automatic Computer). Ниже вы видите блок памяти этого компьютера, содержащий 256 слов по 37 бит каждое.







20-е простое число Мерсенна было обнаружено Александром Гурвицем в ноябре 1961 года в результате проведения 50-минутного теста Люка-Лемера на IBM 7090. Ниже мы воспроизводим эти результаты (на это потребовалось около 151 секунд машинного времени на современном одноядерном ноутбуке).































Одной из особенностей Wolfram Language, делающей его пригодным для такого рода работы, является его быстрая целочисленная арифметика. Поиск простых чисел Мерсенна стал настоящим вызовом на рассвете эпохи компьютеризированного поиска. Исследователи адаптировали методы быстрого преобразования Фурье для преобразования задачи умножения двух больших целых чисел в простое поэлементное произведение трансформированных цифр. Быстрое умножение целых чисел необходимо для шага возведения в квадрат в тесте Люка-Лемера. В Wolfram Language используются новейшие алгоритмы, оптимизированые для работы с точными целыми числами с миллиардами символов. Например, убедимся, что последнее из них, — ${M_{4423}}$, — на самом деле простое число Мерсенна, и продемонстрируем все его цифры.















Совершенные числа



Существует интересная связь между простыми числами Мерсенна и совершенными числами. Совершенное число — это число, равное сумме всех своих делителей (отличных от самого числа). Евклид предполагал, а Эйлер доказал, что все четные совершенные числа, имеют вид $P = {2^{p - 1}}\left( {{2^p} - 1} \right) = {2^{p - 1}}{M_p}$. Функция Wolfram Language PerfectNumberQ проверяет, является ли число совершенным. Продемонстрируем это свойство на ${M_{31}}$.



































21-е, 22-е и 23-е числа Мерсенна



Перейдем к переоткрытию 21-го, 22-го и 23-го чисел Мерсенна (будем обозначать их далее в форме вида $\# 21$): $\# 21 = {M_{9689}}$, $\# 22 = {M_{9941}}$, $\# 23 = {M_{11213}}$. Все они были обнаружены Дональдом Гиллисом, который запускал LLT на ILLIAC II всю весну 1963 года (см. здесь). Для проверки всех чисел вида ${2^p} - 1$ для простых чисел в промежутке $7927 \leqslant p \leqslant 17389$ нам понадобилось около 6 минут.















































24-е, 25-е и 26-е числа Мерсенна



Далее мы расширяем поиск, чтобы найти $\# 24 = {M_{19937}}$, $\# 25 = {M_{21701}}$, $\# 26 = {M_{23209}}$. Последнее из них было обнаружено в феврале 1979 г. Лэндоном Куртом Ноллом и Лорой Никель. Они искали в диапазоне от ${M_{21001}}$ до ${M_{24499}}$ на суперкомпьютере CDC Cyber 174 (почитать об этом можно здесь). Наши расчеты становятся более долгими, так что мы начинаем использовать параллельную обработку. Поскольку тесты независимы, для ускорения работы мы можем использовать ParallelMap. Проверка диапазона $17393 \leqslant p \leqslant 27449$ занимает приблизительно три с половиной минуты (используются 4 ядра).











































Обратите внимание на то, что специализированный тест Люка-Лемера значительно быстрее, чем более общая функция PrimeQ (для данных чисел Мерсенна).



















27-е и 28-е числа Мерсенна



Далее мы проверили диапазон $27457 \leqslant p \leqslant 48611$, чтобы найти число $\# 27 = {M_{44497}}$. Оно было обнаружено в апреле 1979 года Гарри Нельсоном и его командой (они использовали суперкомпьютер Cray-1). Наш поиск завершился за 15 минут.























habrastorage.org/files/e0e/9b8/1d8/e0e9b81d89c04ee0a3c22d05cb204e8d.png»/ width=«425»>











Следующее число Мерсенна, $\# 28 = {M_{86243}}$. Оно был открыто в сентябре 1982 года Дэвидом Словински — также на Cray-1. Этот суперкомпьютер весил около 5 тонн и потреблял около 115 киловатт электроэнергии, а его вычислительная производительность достигала 160 мегафлопс. Он поставлялся с 1 миллионом 64-разрядных слов памяти (8 мегабайт), а стоил около 16000000$ в сегодняшних ценах. Ниже показана деталь его системы охлаждения. Для сравнения: Raspberry Pi весит несколько унций, работает на 4 Вт, обеспечивает около 410 мегафлопс и снабжен 1Гб оперативной памяти, и это все — за 40$. А еще он поставляется сразу с системой Mathematica.











Число $\# 28 = {M_{86243}}$ содержит 25962 цифры. Это значение мы нашли за 1 час и 14 минут (на моем ноутбуке, а не на Raspberry Pi), проводя испытания в диапазоне $48619 \leqslant p \leqslant 87533$.













































29-е число Мерсенна



Поскольку теперь нам требуется более серьезное компьютерное время, мы также производим отметку времени для каждого прогона. Теперь мы проверяем диапазон $87557 \leqslant p \leqslant 110597$. Через 1 час и 44 минут компьютер показал: $\# 29 = {M_{110503}}$. Это число впервые было обнаружено 29 января 1988 года Уокер Колкуиттом и Люком Уэлшем (суперкомпьютер NEC DX-2; статью можно найти здесь).

































































30-е и 31-е числа Мерсенна



Следующие два простых числа Мерсенна: ${M_{132049}}$ и ${M_{216091}}$, — были на самом деле обнаружены еще до 29-го (той же командой, которая обнаружила и 28-е). Они использовали Cray X-MP, чтобы найти 30-е число Мерсенна в сентябре 1983 года и 31-е — в сентябре 1985 года. Проверим $\# 30 $ с помощью функции поиска в диапазоне $110603 \leqslant p \leqslant 139901$. Для проверки каждого ${M_p}$ в этом диапазоне понадобилось 4 часа и 8 минут.































































Великий интернет-поиск чисел Мерсенна



С открытием 34-го простого числа Мерсенна — ${M_1257787}$ — в сентябре 1996 года закончилась эпоха суперкомпьютеров для поиска простых чисел Мерсенна. Следующие 15 были найдены добровольцами Великого интернет-поиска простых чисел Мерсенна (GIMPS), в рамках которого проводится вариант теста Люка-Лемера (в качестве фонового процесса) на персональных компьютерах. Этот масштабный проект распределенных вычислений в настоящее время достигает уровня производительности, эквивалентного приблизительно 300 терафлопс в секунду, причем задействуется время простоя более чем 1,3 миллиона компьютеров.







Проверим 34-е число Мерсенна с помощью теста Люка-Лемера. На этом этапе мы достигли предела возможностей личного компьютера. На тестирование тысячи чисел Мерсенна в этом диапазоне потребуется много дней. Интересно отметить, что тест Люка-Лемера часто используется в качестве стресс-теста надежности компьютерного оборудования и программного обеспечения, так как даже одна арифметическая ошибка среди миллиардов вычислений, необходимых для тестирования одного большого простого числа, повлечет за собой неправильный вывод, что будет означать потерю числа Мерсенна или ложный ответ. Тот факт, что мы проверили каждое ${M_p}$ для простых чисел в промежутке между 2 и 139901, убедительно доказывает надежность арифметики больших чисел и бинарных операций в системе Mathematica и языке Wolfram Language.

















Факторизация чисел Мерсенна



Как мы уже видели, количество возможных множителей в разложении чисел вида ${2^p} - 1$ согласно теореме множителей Мерсенна ограниченно. Это позволило провести эффективный компьютеризированный поиск делителей больших чисел этой формы. На момент написания этой статьи все числа Мерсенна (до ${2^{1201}} - 1$) были полностью разложены на множители (иллюстрации можно найти здесь). Проект GIMPS также привел к открытию крупных множителей многих чисел Мерсенна в качестве побочного продукта проверки простоты чисел. Новую статью, которая представляет собой современный подход к решению этой проблемы (наряду с 17 новыми факторизациями), можно найти здесь. Наибольшее факторизованное число составило ${2^{1199}} - 1$; его наименьший из найденных делителей имеет 76 десятичных цифр. Его наименьший делитель равен 23. Общее время, необходимое для того, чтобы получить этот результат составляет 7500 лет (речь о компьютерном времени).



Мы можем быстро найти несколько первых множителей ${2^{1201}} - 1$ с помощью функции FactorInteger.











Все простые числа Мерсенна, обнаруженные на сегодняшний день, каталогизированы в Wolfram Language (до 44-го). Доступ к этой информации обеспечивается функциями MersennePrimeExponent и MersennePrimeExponentQ.



























Если вас заинтересовала эта тема, вы можете найти более подробную информацию на следующих веб-сайтах:




Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/327342/

Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

The Better Parts: доклад Дугласа Крокфорда о JavaScript с конференции .concat() 2015

Вторник, 25 Апреля 2017 г. 13:45 (ссылка)

Кто знает о JS больше, чем один из его «отцов»? На HolyJS 2017 Piter приедет легендарный Дуглас Крокфорд, создатель JSON и автор множества инструментов JavaScript. В преддверии его выступлений в Петербурге публикуем перевод его выступления на .concat() 2015: The Better Parts — о том как использовать существующие языки программирования более эффективно и каким будет язык программирования будущего. Хотя с момента выступления прошло более года, доклад коснулся ряда «вечных» вопросов программирования, которые, мы уверены, будут актуальны и через 3-5 лет.











Путь к совершенству



Этот человек — известный авиатор и писатель Антуан де Сент-Экзюпери. Большинство его помнит за то, что он является автором замечательной детской книги «Маленький принц» (хотя книга эта не совсем детская). Помимо нее он написал много других книг, и в одной из них есть удивительная фраза: «Как видно, совершенство достигается не тогда, когда уже нечего прибавить, но когда уже ничего нельзя отнять».



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



Он говорил о конструкции самолетов. Но, кажется, мысль на самом деле шире. Я думаю, что она лучшим образом подходит к компьютерной программе. Поскольку у нас особые отношения с совершенством, отсутствующие в других дисциплинах: то, что мы пишем, должно быть совершенным, или оно будет вести себя неправильно. И автор дает нам некоторое представление о том, как мы достигаем совершенства — через вычитание.



Я думаю, что это относится и к языкам программирования.



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



Это удивительно противоречивое заявление. Есть множество людей, не желающих использовать лучший вариант. И происходит это из-за непонимания одной вещи: нам не платят за использование каждой функции языка. В конце проекта нет менеджера с портфелем, проверяющего: «А использовали ли вы двойное равенство»? Никто не заботится об этом. Нам платят за то, чтобы мы писали программы, которые хорошо работают и не содержат ошибок.



Когда «отсутствие ошибок» стало частью сделки? Оно всегда была ее частью. Просто мы очень редко достигаем этого. Легко забыть, что на самом деле отсутствие ошибок было первым требованием. Поэтому хороший язык программирования должен научить вас этому.



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



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



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



Я написал книгу о своем опыте использования JavaScript и кода JSLint — «JavaScript — сильные стороны». Возможно, вы слышали о ней. Она по-прежнему бестселлер, что редкость для книг по программному обеспечению. Большинство книг по программному обеспечению устаревают еще до выпуска, а эта по-прежнему актуальна. Все потому, что «сильные стороны» по-прежнему остаются сильными сторонами. Что хорошо, язык не изменился вообще.



Контраргументы



Теперь аргументы против использования «сильных сторон». Я бы хотел изложить их для вас:


  • Первый звучит так: «что хорошо, а что плохо — это просто вопрос мнения». Это неправда. В рамках поддержки JSLint я получаю сообщения об ошибках от людей со всего мира. Недавно я получил письмо от одной компании, которая провела две недели, пытаясь решить некую проблему. Выяснилось, что в одном месте была случайно добавлена точка перед знаком равенства. Никто не видел ее, потому что она выглядела корректно, но результат был плохим. Поэтому они спросили меня, могу ли я модифицировать JSLint, чтобы никто больше не страдал от подобной проблемы. Почему нет? Я делаю это уже много лет. Так что если вы используете JSLint, вы никогда не потратите впустую две недели на проблему такого рода. И это не мнение, это факт.


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


  • Я имею право использовать каждую функцию. Тема разговора изменилась. Неужели мы больше не говорим о том, как лучше всего писать программы. Мы сейчас говорим о наших правах? На этом аргументация, в конечном итоге, заканчивается. «Я имею право писать дерьмо», правда? Это не важно. Важно, что мы обязаны писать очень хорошо.


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


  • Мне нужно уменьшить количество нажатий клавиш. Мы предполагаем, что большую часть времени печатаем. И если бы мы могли найти способ компоновки программ с помощью нескольких нажатий клавиш, это сделало бы нас намного эффективнее. Но дело обстоит с точностью до наоборот. Главный растратчик времени — не набор символов, а взгляд в бездну с возгласом: «мой бог, что я сделал, почему это не работает». Вот куда мы тратим большую часть времени. Если бы я мог предложить вам схему, согласно которой увеличение количества нажатий клавиш в 10 раз могло бы сократить ошибки вдвое, это было бы огромной победой (к сожалению, у меня нет такой схемы).


  • «Это оскорбление предполагать, что я когда-либо ошибусь, используя опасную функцию». «Я знаю, что можно ошибиться, но я настолько искусен, что это спасает меня. Думать иначе — оскорбление для меня, личная травма». Тут все понятно.


  • «Есть причина, почему эти функции были добавлены в язык». Это абсолютно неверно. Существует множество причин, почему вещи попадают в язык, но большинство из них не являются вескими.

    Например, JavaScript был разработан и реализован всего за 10 дней. Это удивительно. И блестящий человек, который это сделал, Брендан Айк, за эти 10 дней совершил несколько ошибок. Одна из них — оператор двойного равенства, который он заставил делать преобразование типа, прежде чем сравнивать элементы. Это вызывает ложные срабатывания, которые путают. Брендан Айк признал, что оператор был реализован неправильно. В то время многие другие языки (например, PHP) имели ту же ошибку. Брендан хотел исправить это. Поэтому, когда началась работа над стандартом, он решил, что сейчас самое время внести исправления. Он пошел к ECMA и сказал: «Такое поведение неверно, давайте сделаем правильно». Но в ECMA отказали, хотя и предложили компромисс: ввести оператор тройного равенства, который будет работать правильно. При этом двойное равенство осталось в языке для людей, которые уже начали использовать неправильную конструкцию. В итоге мы имеем оператор без веских на то оснований.

    Брендан называет подобные особенности «выстрелом в ногу» (footgun — в дословном переводе пистолет для выстрела в ногу, — прим. ред.). И он непреднамеренно поместил их в JavaScript. Их использовать не рекомендуется.






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



Раньше мы думали, что писать хорошие программы на JavaScript просто невозможно, потому что это был такой хрупкий язык. Но оказывается, что писать хорошие программы на JavaScript не только невозможно, но и не нужно. Как раз из-за своей «хрупкости» JavaScript требует большей дисциплины, чем любой другой язык программирования. И вы действительно должны ее придерживаться, чтобы писать нечто, что будет работать.



Есть две вещи, которые работают против этого. Во-первых, это фантазия о непогрешимости. В особенности молодые программисты считают, что их навыки настолько развиты, что они могут делать удивительные вещи, которые будут работать. Во-вторых, есть бесполезность безупречности, которую вы видите особенно у старых парней, которые делали годами то, что никогда не работало. Эти два очень разных взгляда приводят к одному и тому же: danger driven development. И это плохо.



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



Время B должно быть равно 0, верно? Вы пишете код — он должен работать. Но время В иногда становится больше времени А. Иногда оно бесконечно. Подобное происходит, когда у вас есть проект по разработке ПО, который заканчивается, но затем от ПО отказываются, прежде чем оно начинает работать.



Все, что вы делаете в рамках времени A, увеличивающее время B, неправильно. Вы должны пытаться сократить время B до нуля.



Новые сильные стороны ES6



Есть ряд дополнений к JavaScript, которые будут отправлены на ECMA General Assembly в июне этого года (эти нововведения действительно прошли через процедуру сертификации и стали частью новой спецификации — ECMAScript 2015 Language Specification, — прим. ред.).

Рад сообщить, что там есть некоторые новые сильные стороны. И сегодня я бы хотел поделиться ими с вами.



Хвостовая рекурсия



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



Например, мы можем использовать стиль передачи продолжений и другие виды программирования, которые мы не могли реализовать на старом языке. Так что с этой функцией JavaScript, наконец, становится реальным функциональным языком программирования, и это здорово.



Многоточие



Также мы получили оператор многоточия. Если мы поместим его в список параметров или аргументов, сможем иметь дело с переменным количеством аргументов, что действительно приятно.

Здесь у нас есть две версии функции curry. Первая — в соответствии с ES6:



function curry(func, ...first) {
return function (...second) {
return func(...first, ...second);
};
}




Вторая — как это реализовывалось ранее:



function curry(func) {
var slice = Array.prototype.slice, args = slice.call(arguments, 1);
return function () {
return func.apply(null, args.concat(slice.call(arguments, 0)));
};
}




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



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



Модули



Теперь у нас появились модули. Вы можете импортировать и экспортировать значения из разных файлов; и это наконец-то имеет поддержку в языке. Раньше все это реализовывалось через глобальные переменные, и это было ужасно.



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



Константы



У нас появилось два новых способа определения переменных: let и const, что решает проблему области видимости блока. Оказывается, в хорошей программе вам не нужна область видимости блока. Но синтаксис JavaScript ранее выглядел так, будто область видимости блока есть (с его синтаксической конструкцией var), хотя это было не так, что смущало людей. Всякий раз из-за путанницы возникали ошибки. Let и Const позволяют этого избежать.



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



Деструктуризация



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



Например, здесь у меня есть объект. И я хочу создать некоторые переменные, инициализировав эти переменные из свойств объекта.



let {that, other} = some_object;
let that = some_object.that, other = some_object.other;




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



WeakMaps



У нас появился WeakMap. WeakMap работает так, как должны были бы работать объекты. В объектах JavaScript ключи — это строки, что является ошибкой. Было бы лучше, если бы вместо них использовались какие-то значения. Но, тем не менее, это строки. WeakMap решает эту проблему. Здесь можно взять любое значение и использовать его в качестве ключа.



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

С помощью WeakMap вы можете писать программы, которые невозможно было создать на языке ранее.



Шаблонные строки



И наконец мы нечто, что я назвал мегастроковым литералом (в языке это называется шаблонными строками, но мне не нравится это имя; но ранее их называли квази-литералами, что еще больше сбивало с толку).



Это регулярные выражения, которое соответствует нескольким строкам в ES6. Объявление этого регулярного выражения просто ужасно; я надеюсь, когда-нибудь мы сделаем что-нибудь получше. Но в ES6 у нас нет лучшей альтернативы.



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



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



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



Стрелочные функции



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



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



Недостатки ES6



Помимо сильных сторон, у ES6 есть и откровенно плохие элементы.



Классы



Худшее нововведение — это классы. Класс был самой востребованной новой функцией, причем большая часть запросов поступала от Java-программистов, которые теперь должны писать на JavaScript и они действительно возмущены этим. Хочется писать на Java, но задачи и деньги есть в JavaScript, поэтому им приходится это делать. Переучиваться они не хотят, поэтому новый синтаксис упростит их жизнь.



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



Object.Create



Я пересматриваю то, что было написано в «Сильных сторонах», учитывая новые знания и необходимость отражения нововведений ES6. Когда я писал «сильные стороны», я рекомендовал вместо классов использовать object.create.



Фактически, именно мне удалось добавить object.create к языку, чтобы именно я мог его использовать. Получилось неплохо. Но как же я был удивлен, когда заметил, что прекратил использовать object.create. Я добавил его туда для себя, но даже я его не использую. И причина в том, что я прекратил использовать this.



This



Я перестал использовать this из-за того, что в 2007 году сделал проект под названием ADSafe. В то время существовало несколько исследовательских групп, таких как fbjs в Facebook, Caja Google, Web Sandbox в Microsoft, кроме того, был мой собственный проект — ADSafe и другие. Все мы пытались выяснить, как сделать JavaScript безопасным языком, чтобы можно было добавить сторонний код в приложение и быть уверенным, что он не породит проблем с безопасностью. И в JavaScript это оказалось очень сложной задачей.



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



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



Мой подход в ADSafe был намного проще. Я просто сделал это незаконным, побудив отказаться от программ, где это используется. Это работает. Единственная проблема в том, что отказаться приходится от львиной доли программ, потому что все использовали this.



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



Null / undefined



Я перестал использовать null. JavaScript имеет два минимальных значения — null и undefined. В некоторых языках считается, что у вас и одного не должно быть. JavaScript — это единственный язык, который у которого таких значений более одного. Но совершенно точно, что глупо иметь два сразу.



Некоторые фреймворки пытаются рассматривать их как взаимозаменяемые, но они таковыми не являются. В зависимости от ситуации я должен использовать только один из них. Я использовал undefined, потому что именно его использует сам язык: вы получаете undefined, если, к примеру, обращаетесь к отсутствующему свойству.



Неопределенное значение порождает целый круг проблем: совершенно неправильный тип отсутствующего объекта. Мы надеялись, что это будет исправлено в ES6, но этого не произошло. Наверное, так будет всегда. Но если вы не используете null, то не столкнетесь с этой проблемой. И я перестал использовать ошибочные значения, решив, что само их существование в JavaScript — плохая идея.



Изначально эта идея появилась из C, который использует 0 для представления no, false и другие аналогичные значения. JavaScript попытался сделать то же самое. Но это сбивает с толку.



For



Я перестал использовать for. В ES5 мы ввели новый набор методов работы с массивами — for each, map и другие, поэтому теперь я использую эти инструменты. Если у меня есть массив и мне нужно его обработать, я использую один из этих методов. И я могу объединить их вместе удобным образом. Это действительно выразительно.



For Each



Я не использую for each. В ES5 мы получили object .keys(object). Эта конструкция дает вам хороший массив строк. И он не включает в себя вещи из цепочки прототипов, поэтому вам не нужно фильтровать результаты. Очень приятно, что я могу взять массив и выполнить for each таким образом, это действительно выразительный способ.



Ранее я уже упоминал, что в ES6 появились правильная хвостовая рекурсия. И сейчас пора перестать использовать циклы вовсе (в частности, оператор while) — время использовать только рекурсивные функции.



Например, это рекурсивная функция. Вы вызываете эту функцию до тех пор, пока она не вернет undefined. Первая версия кода написана с использованием цикла while.



function repeat(func) {
while (func() !== undefined) {
}
}




Вторая версия написана с использованием хвостовой рекурсии.



function repeat(func) {
if (func() !== undefined) {
return repeat(func);
}
}




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



Язык следующего поколения



Я много думал о языке программирования следующего поколения. Каким он будет? Вероятно, это должен быть язык, которым мы заменим JavaScript, потому что было бы очень грустно, если бы оказалось, что JavaScript является последним языком программирования. Было невыносимо. Мы должны сделать мир лучше, по крайней мере для наших детей, не так ли?



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

В одной вещи я уверен: когда он, наконец, появится, мы отвергнем его. И причина этого в том, что программисты — эмоциональные и иррациональные ненормальные люди.



Мы считаем, что это не так. Мы думаем, что являемся ультра-рациональными, потому что исполняем роль послов людей в мире компьютеров, а компьютеры вполне рациональны. Соответственно, мы предполагаем, что и сами также рациональны. Многие из нас лишились социальных навыков, но выходит так, что иррациональность и эмоциональность никуда не делась. Давайте посмотрим на доказательства в поддержку этого тезиса:




  • Потребовалось поколение, чтобы признать языки высокого уровня хорошей идеей. Когда среди множества ассемблерных языков появился Fortran, разработчики (на тот момент они все были ассемблерными программистами) отказались на него переходить. Потому что Fortran отнимал у них контроль взаимодействия с машиной, чего они не могли допустить. Переход сделал бы их жизнь намного лучше, но они отказались от него.


  • Потребовалось целое поколение, чтобы согласиться с тем, что go to был плохой идеей. Десятилетиями мы вели эмоциональные споры о том, следует ли нам использовать go to или нет. И приводились как глубоко продуманные аргументы от очень умных людей, так и заявления в стиле: «Это мой способ самовыражения», «Мне нужна эффективность», «Умру, но не отдам». Оказалось, что все эти доводы были совершенно неправильными.


  • Потребовалось поколение, чтобы согласиться, что объекты были хорошей идеей. В 1967 году в Норвегии появился язык, названный Simula. Насколько я могу судить, только один человек в мире — Алана Кей из Университета штата Юта (США) — признал, что этот язык содержал важные идеи. Все остальные пропустили это. Он думал, что объекты, которые появились в Simula были настолько выразительными, что он мог бы разработать язык программирования для детей. И дети могли бы писать удивительные программы, используя эту парадигму. Поэтому он начал изучать этот язык и потратил почти десять лет на разработку и совершенствование собственного. В 1980 он наконец был опубликован. И это был лучший из разработанных языков программирования в истории — С++. Алан Кей взял идеи Simula, не совсем понял их, но перевел на C.

    Итак, у индустрии появился выбор: мы могли пойти вслед за Simula или за C++. Итоговое решение принимали люди, которые в корне не понимали, чем объекты были для программирования. Но они сделали выбор в пользу C ++, потому что там вам не нужно было разбираться в объектно-ориентированном программировании, чтобы начать работу с языком. И с тех пор почти во всех языках больше заимствовано от C++, нежели от Simula. И поэтому мы до сих пор ошибаемся.


  • И, наконец, потребовалось два поколения, чтобы согласиться с тем, что лямбды были хорошей идеей. Лямбды появились в языке под названием Squeak в MIT в начале семидесятых. И индустрия вообще не обратила на это внимания. Потребовалось даже не два, а четыре поколения, чтобы наконец лямбды добрались до мейнстрима. На это потребовалось так много времени, что некоторые считали это доказательством несостоятельности идеи. Но оказалось, что функциональное программирование с лямбдами очень эффективно при работе  с асинхронностью и распределенными системами, с которыми мы имеем дело сегодня. Первый язык, принявший эту идею, — JavaScript. После этого лямбды появились в Python и Ruby, и, в конечном итоге, C#. Не так давно наконец в Java. Но JavaScript был первым.






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



Я помню, когда появился go to. Спор вокруг него не утихал годами, а потом вдруг стало тихо. Можем ли мы избавиться от него сейчас? Да, мы уже просто бросили его. Разве кто-нибудь страдает от отсутствия go to? Все споры оказались бессмысленным, произошел сдвиг парадигмы.



Людям действительно сложно сдвинуть эту парадигму. История с go to — это просто избавление от одной конструкции и изменение способа, структурирования программы. Оказалось, что отсутствие go to облегчает программирование.



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



О классификации языков



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



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



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



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

Но моя работа сосредоточена на прикладных языках — именно с ними большинство из нас и будут иметь дело.



Прикладные языки также можно разделить на две группы: классическую школу (к которой относятся почти все языки) и группу языков для прототипного программирования, которая включает JavaScript.



И я думаю, JavaScript правильно выбрал направление. Вызывая много критики, JavaScript содержит инновации.



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



Все это не является обязательной частью объектно-ориентированного программирования. Это просто классы. Если вы займетесь объектно-ориентированным программированием без классов, вам не придется ничего делать. Поэтому в JavaScript, если вы просто делаете прототипы (и делаете их правильно), вам не нужно делать таксономию и выполнять рефакторинг. Это намного проще.



Я постоянно пытаюсь объяснить это Java-разработчикам. Но они так и не осознают, что таксономию делать не надо. Здесь нужен сдвиг парадигмы — они просто не могут осознать, что таксономия делает их несчастными, не могут представить другого способа.



Раньше я был сторонником прототипического наследования. Его основное преимущество заключается в сохранении памяти. В этом случае мы копируем объект, запоминая только реальное различие между оригиналом и копией, т.е. памяти выделяем меньше. И, возможно, это было самым важным фактором в 1995 году. Но не сегодня. Объем доступной памяти постоянно растет в соответствии с Законом Мура. Теперь у вас есть гигабайты оперативной памяти буквально в вашем кармане. Поэтому беспокоиться о том, сколько битов выделяется на каждый объект сегодня — просто потеря времени. Но мы по-прежнему делаем это.



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



Все это также снижает производительность. Современные движки JavaScript работают очень быстро, делая предположения о параметрах объектов. Но они вынуждены с пессимизмом относиться к цепочкам прототипов, поскольку прототип может быть в любое время изменен. Таким образом, наличие такого уровня косвенности в языке фактически замедляет процесс.



Поэтому, хотя я и был сторонником прототипического наследования, теперь я придерживаюсь бесклассового объектно-ориентированного программирования. Я думаю, такой подход — дар JavaScript человечеству.



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



{
let a;
{
let b;
... a …
... b …
}
... a ...
}




JavaScript всегда был в состоянии так работать с функциями, поскольку функция — это просто блок, связанный с объектом. И поэтому функции имеют те же взаимоотношения.



function green() {
let a;
function yellow() {
let b;
... a …
... b …
}
... a ...
}




Множество переменных, которые видит внешняя функция, включается в множество переменных, видимых для внутренней функции (поэтому мы такие взаимоотношения называем замкнутыми — это понятие из теории множеств).



Но что делать, если внутренняя функция живет дольше, чем внешняя? В этом случае при вызове внешней функции в стеке выделяется А. Когда внешняя функция возвращает значение, А из стека удаляется. Как сделать, чтобы теперь внутренняя функция могла использовать это A?



Понадобилась пара поколений, чтобы понять, как это сделать. Это тривиально — просто не использовать стек, а выделять место в heap, имея при этом хороший сборщик мусора. Именно так мы это делаем сейчас.



С учетом всего этого вот, как я создаю объекты, в соответствии с ES6.

Итак, у меня есть функция constructor:

function constructor(spec) {
let {member} = spec,
{other} = other_constructor(spec),
method = function () {
// member, other, method
};
return Object.freeze({
method,
other
});
}



Здесь я задаю спецификацию объекта. И мне это нравится гораздо больше, чем задание некого количества переменных.



Несколько лет назад я спроектировал ужасный конструктор, в котором было множество параметров, и никто не мог вспомнить, в каком порядке они шли. В какой-то момент мы осознали, что никто никогда не использует третий параметр. Но мы не могли ничего изменить, и это было ужасно. Вместо этого я создал один объект, описывающий то, что мы пытаемся сделать. Получилось более гибко — мы могли иметь значения по умолчанию, а также добавлять или удалять вещи со временем.



Но вернемся к нашему примеру. Я использую деструктуризацию, чтобы получить значения из объекта и инициализировать локальные переменные.



Далее вызываю другой конструктор. Я могу передать ту же спецификацию объекта, и это будет преобразование объекта. Затем я беру методы и помещаю их в локальные переменные. Я могу повторять вызов столько раз, сколько захочу, так что если мне необходимо множественное наследование или что-то вроде того, я могу реализовать это очень легко. Затем я создаю свои методы.



После этого я выполняю return. В ES6 у нас есть еще одна сокращенная нотация: вместо вызова метода можно просто написать method. Что приятно, это больше похоже на объявление, чем на литерал.



Далее я собираюсь его заморозить. Теперь это объект, который нельзя изменить, что правильно определяет его локальное состояние. Это лучший способ реализовать подобное на JavaScript. И я думаю, что это действительно важное свойство для объектов. В начале объектно-ориентированного программирования мы стартовали с идеи, реализованной в Pascal, согласно которой у нас есть запись, а затем мы связываем с этой записью функции, которые являются методами, действующими на элементы записи. И ранее я не мог выйти за эти рамки. Теперь же у меня теперь есть два совершенно разных набора объектов. У меня есть замороженные объекты, которые содержат только функции, и у меня есть значимые объекты, которые просто содержат данные. И я не смешиваю их. Я использую их друг с другом, чтобы защитить данные и обеспечить соблюдение дисциплины.



Единственный численный тип





История одной ошибки



У меня есть еще немного времени, чтобы рассказать вам историю одной ошибки.

В 2001 году я писал на Java один из первых парсеров JSON. И он содержал конструкцию this. Я создавал локальную переменную с именем index, она была int. Переменная измеряла, сколько символов было в файле или потоке, без парсинга. При обнаружении синтаксической ошибки переменная могла сообщить вам, в каком именно месте она произошла ошибка.



Напомню, я написал это в 2001 году. Я хорошо подумал, что int дает мне примерно 2 миллиарда байт — это два гигабайта. В то время это был довольно большой диск. Я не мог себе представить, что текст JSON окажется больше. Самый большой текстовый файл из тех, что я создавал, был пару килобайт.



Я думал, что все хорошо, пока в прошлом году не получил сообщение об ошибке от кого-то, у кого текст JSON был 7 Gb. И он содержал синтаксическую ошибку за пределами первых двух миллиардов символов, поэтому переменная давала в корне неверное значение. А все потому, что я выбрал int. Это оказалось ошибкой.



Я ненавижу int. У int есть ужасное свойство — он переполняется без предупреждения. Что должно произойти, если у вас есть число, которое слишком велико для помещения в int? Считается, что программа не должна вылетать. В итоге значимые биты выкидываются, а вы получаете неверные результаты. Лучше было бы бросить эксепшн, который скажет, что «что-то не так».



Альтернативный вариант — замена: когда вы попробуете получить значение, вам сообщат, что его больше здесь нет. Но существующий вариант — худшее, что можно было придумать; это путь максимизировать ошибки.  Более того, эти ошибки никогда не выявляются в тестах, поскольку тесты изначально предполагают, что данные соответствуют диапазонам.



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



DEC64



Причина всех проблем с типами данных родом из пятидесятых годов, когда компьютеры были ламповыми. Чем больше ламп, тем больше требуется энергии и косвенных затрат (например, тем быстрее выгорают лампы).



Кроме того, задействовать дополнительную память было действительно страшно. У машины могла быть только пара килобайт. К примеру, память Atari 2600 была всего 128 байтов. И поэтому не хотелось выделять 64 бита на то, что могло бы быть представлено 4 битами.



И кто-то догадался в целях экономии использовать дополнительную арифметику. Догадка была действительно великолепной. Но, спустя 50 лет, мы должны спросить себя — почему мы все еще делаем это?



Аналогичная ситуация сохраняется на других языках. Например, в Java у вас есть byte, char, short, int, long, flot, double и т.п. Каждый раз, когда вам нужно создать параметр, переменную или элемент, вы должны спросить себя, к какому типу будет относится новая структура. Значение сэкономленной при этом памяти настолько мало, что говорить о нем нет смысла. Нет никакой пользы в том, чтобы сделать правильный выбор. Фактически время, которое вы потратили на размышления, стоит бесконечно больше, чем сэкономленный ресурс. Но если вы ошибетесь, все перестанет работать, и тесты это не выявят. Это плохо.



С другой стороны, JavaScript имеет только один численный тип. Здесь вы не можете ошибиться. Единственная проблема с JavaScript заключается в том, что это неправильный тип. При его использовании 0.1 + 0.2 не равно 0.3. Это фундаментальная вещь. Проблема в двоичной плавающей запятой — это снова нечто, что имело смысл в пятидесятые годы.



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



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



По сей день мы продолжаем это делать. И это неправильно, поскольку вызывает проблемы. Это было так же неправильно и в пятидесятые годы: бизнесмены возражали, что не могут пользоваться такими вычислениями. Вместо этого они использовали BCD (двоично-десятичный код). Таким образом языки программирования разделились. Вы могли использовать Fortran с плавающей запятой или COBOL с BCD. В конце концов, ситуация стала совсем запутанной. Java стала наследником COBOL. Но Java не была предназначена для ведения бизнес-процессов. Так что этот функционал здесь просто не используется.



Я предлагаю решение этой ужасной проблемы. Я назвал его DEC64. Это 64-битное значение, которое на самом деле очень похоже на то, что было сделано в сороковых годах.







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



Я написал реализацию DEC64 на ассемблере Intel x64 — используйте ее, если хотите опробовать тип в аппаратной реализации (dec64.com, https://github.com/douglascrockford/DEC64/).

Этот тип избавит от необходимости иметь отдельный int. Кроме того, он правильно считает деньги и выполняет арифметические операции так, как нас учили это делать в средней школе.



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



У меня есть немного опыта в сфере убеждения всего мира в том, как поступать правильно. Пример — мой любимый формат обмена данными JSON.



Это статистика Google, которая показывает относительную популярность XML (красным) и JSON (синим). Вы можете видеть, что мир неуклонно теряет интерес к XML с 2005 года, и он очень медленно растет интерес к JSON.



Стоит отметить, что на рост популярности JSON не повлияла никакая отрасль. Нет крупных корпораций, которые продают большие инструменты JSON, потому что вам не нужны большие инструменты для работы с JSON. Вот почему вам он нравится. Это просто работает.



Похоже, скоро будет пересечение. Я не могу предсказать, когда это произойдет. Но Google может. Согласно прогнозам Google Trends это произойдет в этом году (2015 — прим. ред.). JSON наконец станет официально более интересным, чем XML.






Если вам интересна оригинальная видеозапись доклада на английском, с радостью ее предоставляем:










На грядущем HolyJS Piter вы сможете прослушать два доклада Дугласа Крокфорда:



Кроме них в течение двух дней вы услышите еще более 20 докладов о JavaScript: от новых фронтенд фреймворков до разбора серверного перфоманса и десктопной разработки. Будут доклады от Lea Verou, Martin Splitt, Anjana Vakil, Claudia Hern'andez и много кого еще. Детали смотрите на сайте конференции.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/327320/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

[Перевод] Всё плохо

Вторник, 25 Апреля 2017 г. 12:50 (ссылка)

image



Что ж, всё плохо. Немного забавно так говорить: на конференции (Web `a Qu'ebec) было много разговоров об удивительном будущем и вещах, возможных благодаря новым технологиям. О новых средствах и устройствах, которые должны сделать нашу жизнь проще. Мои знакомые знают, что у меня обычно очень циничный взгляд на технологии; лично я боюсь всех этих умных устройств, которые реагируют на мои слова, чем восхищались другие спикеры.



В основном потому, что чем больше времени я трачу на программирование и провожу в этой отрасли, тем больше узнаю, как всё работает изнутри, и тем меньше доверия всё это мне внушает. Я подобрал изображение для слайда. Это картина «Триумф смерти» Питера Брейгеля. В некоторой степени она раскрывает моё отношение к «умному дому».



image



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



image



Чтобы раскрыть свою точку зрения, я начну с базового приложения, знакомого любому разработчику в этом зале. Здесь у меня маленькое веб-приложение. Справа — пользователи, они с помощью своих устройств подключаются к моему фронтенду, исполняющему код, написанный на некотором языке и отвечающий за логику приложения. Там происходит подключение к базе данных, где хранится информация. Далее — cron, он нужен для ряда фоновых задач, возможно, не особо сложных. Затем идёт облако, которое, вероятно, используется множеством других составляющих системы. Может быть, вы храните изображения в S3 или что-то ещё.



Итак, это разумное приложение, но в нём без труда найдутся проблемы: если какой-либо механизм выходит из строя — вся система перестаёт корректно функционировать. Вместо этого мы рассмотрим следующее:



image



Такая схема безопаснее. Теперь у меня есть дополнительный фронт, и я могу подключать или отключать любой из них и по-прежнему оставаться доступным для клиентов. Второй узел базы данных даёт нам возможность аварийного переключения в случае возникновения проблем, но это не резервное решение. Достаточно всего одного человека, DROP TABLE users и репликацией в реальном времени, чтобы удалить таблицу «Пользователи» и потерять этот горячий резерв. Так что давайте скажем, что мы поступили разумно и создали резервную копию в этом облаке.



Таким образом, необходим только один сервер с cron, потому что асинхронные задачи можно решить и чуть позже. Так-то лучше. Наша архитектура достаточно надёжна и удобна, и теперь будем беспокоиться только о коде.



image



Вот несколько абстракций.



В левом верхнем углу вы видите структуры данных: деревья, карты, массивы, словари, наборы, списки и т. д. Они позволяют нам структурировать информацию во что-то большее, чем биты памяти. Очевидно, что это хорошо.



Дальше у нас идут идентификаторы. Я считаю их абстракциями. Они могут быть вашими автоинкрементными идентификаторами в базе данных, UUID или GUID, а также переменными, указателями, URL или URI. Они в основном позволяют ссылаться на элемент, часть данных или объект без необходимости его полного описания. Обычно идентификаторы связаны контекстом, который придает им смысл. Я могу сказать: «Ты Джон», и люди, которые знают конкретного Джона, будут иметь представление о том, кто такой Джон, основываясь на своём знакомом, на таких показателях, как его рост, профессия, возраст и т. д. Идентификаторы позволяют нам определить то, что поддерживает весь истинный элемент.



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



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



Далее идёт время. И здесь я не буду его описывать. Философы, метафизики и учёные тысячелетиями спорили, чтобы приблизиться к тому, что мы сегодня понимаем под этим словом.



И далее у нас строки, формат для всего остального, что мы не знаем, как изобразить.



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



image



Начнём с простых чисел с плавающей запятой. Если вы когда-нибудь видели сумму 0,1 + 0,2, равную 0,30000000000000004, вы знаете, что это такое. Существует бесконечность чисел от 0,1 до 0,2, но у нас есть ограниченное количество байтов для их представления. Некоторые цифры не делятся нацело, и без работы с дробной частью будет очень сложно не потерять точность, когда компьютер начнёт подменять цифры, чтобы они приобрели смысл.



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



Я слышал об одном проекте, в котором банк пытался транслировать свои старые базы кода с помощью node.js. К несчастью для команды, никто не сказал им о том, что в JavaScript есть только числа с плавающей запятой, и команде пришлось отказаться от проекта.



Кстати говоря, языки с флоатами типа JavaScript имеют верхний предел точности целых чисел, в данном случае 253.



image



С целыми числами немного проще. Люди по большей части лучше понимают ограничения, что помогает. Но этого недостаточно: нужна правильная структура и тип использования, поскольку не все целые числа означают одно и то же. Вспомните о Mars Climate Orbiter, который потерпел неудачу, потому что одни части кода работали в имперских единицах, а другие — в метрической системе. Упс.



На изображении показан особый взгляд на эти запутанные единицы. Спросите Siri или Wolfram|Alpha, сколько китайских королей на одном квадратном метре, и вы получите 1,628 x 10–4. Не совсем уверен, что это значит, но каждый раз это достоверно определяется.



image



Ещё интереснее, когда добавляется языковая обработка. Я не понимаю почему, но на вопрос «Кто король Соединенных Штатов?» получаем ответ «1,57 миллиарда китайских королей».



Как правило, подобную ошибку довольно просто предотвратить. Будьте осторожны и, если имеете дело с системой причудливого типа, используйте её в полной мере. Не гонитесь за «целым» для всех своих типов; укажите, что представляют собой целые числа, какова их единица.



image



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



UUID — это всего лишь группа битов, которые вы сравниваете. Если они все сошлись, то идентификатор прошёл проверку. Если нет, тогда он другой. Там множество разных вариантов, какие-то случайные, какие-то основаны на времени или адресе.



Как мы можем видеть, UUID — это то, что расположено на вершине слайда, с шестнадцатеричными числами, разделёнными дефисами. Это немного рискованно.



image



В зависимости от языка или стека, к которым вы прибегаете, у вас могут быть библиотеки, которые позволяют хранить UUID как двоичные блобы в памяти. Или же строки, где хранятся их абстрактные представления. В некоторых случаях вам придётся использовать строки в качестве промежуточного формата, например при передаче информации по различным системам, не поддерживающим необработанные двоичные данные, таким как SQL-запросы или JSON.



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



image



И действительно, различные компоненты, не использующие одинаковое точное представление, могут стать серьёзной головной болью. Ваши пользователи, front-end, back-end, базы данных и облачные сервисы должны либо быть нечувствительными к регистру, либо не противоречить общему представлению.



Для определённых языков требуются особые библиотеки, одна часть которых всё сделает правильно, а от другой нельзя ждать того же. Базы данных могут быть сложными:




  • Используя PostgreSQL, вы получаете нечувствительное к регистру сравнение, но представление в нижнем регистре.

  • MySQL поддерживает создание UUID верхнего регистра, но не имеет формата хранения для них. Рекомендуется выбирать чувствительный к регистру VARCHAR.

  • MSSQL может хранить GUID, но подходит для представления в верхнем регистре.

  • Oracle может хранить необработанные данные, так что здесь не должно возникнуть проблем.

  • Redis не поддерживает UUID, и, скорее всего, вам придётся использовать строку с учётом регистра.

  • MongoDB всё делает правильно, с двоичным представлением.



Тем не менее даже если вы со своей стороны делаете всё безошибочно, нет никакой гарантии, что система будет работать. Достаточно всего одной подсистемы, чтобы всё разрушить. Например, внешние службы, такие как некоторые сервисы Amazon, имеют множество различных предложений, которые не всегда согласуются друг с другом (например, DynamoDB и SimpleDB чувствительны к регистру, поэтому третий сервис AWS, основывающийся на них, скорее всего, унаследует эти свойства). Если подобное произойдёт с вами, если вы, скажем, используете PostgreSQL локально и храните UUID в качестве UUID, то ничего хорошего из этого не выйдет. Теоретически объекты могут начать конфликтовать друг с другом или просто исчезнуть, поскольку ваше локальное представление нижнего регистра не существует для сервиса верхнего регистра.



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



image



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



Если я сравниваю хеши паролей, причём самый верхний из них — правильный, а второй и третий — это другие попытки, то, когда мой оператор '==' достигает разницы, он обнуляется и выдаёт false.



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



Собственно говоря, это атака по времени. Хотя существует гораздо больше их разновидностей.



image



Эту проблему можно решить, заменив оператор сравнения строгой дизъюнкцией. Установите значение false, а затем проведите побитное или побайтное сравнение с XOR. Каждый раз, когда оба значения одинаковы, выдаётся 0 (false), и каждый раз, когда значения отличаются, выдаётся 1 (true). ИЛИ приводит к исходному значению, и если в итоге это не 0, то вы знаете, что есть разница.



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



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



image



Конечно, некоторые устройства принимают это слишком близко к сердцу, и их медлительность на самом деле необязательна!



image



Итак, у нас есть это замечательное приложение. Теперь оно прекрасно справляется с избыточностью, целыми числами, плавающими запятыми, UUID и паролями. У меня появляется намного больше пользователей, и производительность относительно времени отклика начинает снижаться. Из сообщений в блоге я узнал, что теряю важный процент пользователей, не получающих ответ каждую десятую секунду потраченного времени. Это сигнал к действию!



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



image



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



image



И что же? Все мои действия возвращены. Чёрт, это даже быстрее, чем раньше!



Тем не менее есть некоторая проблема.



image



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



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



image



На самом деле, если работа не выполняется тщательно, события могут повторяться. Вот пользователь Reddit, который приобрёл игру за 12,74 доллара. Он купил её однократно, но получил запрос об оплате множество раз, пока на его банковском счёте не образовалось превышение кредита в –93 тыс. долларов.



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



image



Но это ещё не всё. Причина, из-за которой моё приложение стало медленно работать: оно было перегружено. Так что сейчас я полностью отделил воспринимаемую производительность от фактической нагрузки в системе.



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



image



В итоге очередь переполняется и вылетает. Конечно, выходит из строя не только она.



image



Ошибка добирается до интерфейсных узлов, и в итоге мы получаем катастрофические сбои.



Как же нам быть? Кто-нибудь устраивает собрание, говорит, что такое недопустимо. И что делают разработчики?



image



Увеличивают очередь. Но это ещё не всё. Так как во время аварии все данные в очереди были потеряны, все участники собрания также соглашаются сделать её постоянной. Это её замедляет, и, поскольку мы любим избыточность, почему бы не добавить ещё одну очередь?



image



В следующий раз, когда произойдёт ошибка, мы сможем сделать то же самое.



image



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



image



И это микросервисы!



image



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



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



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



image



Мы могли бы выбрать что-то вроде JSON. Это диаграмма из seriot.ch, отслеживающая около 50 реализаций библиотек JSON на более чем десяти языках.



Почти каждая из них обрабатывает информацию по-своему.



image



Это не очень хорошо.



image



Стандарт JSON очень мал, но это, вероятно, означает, что он предоставляет пространство для интерпретации и, следовательно, для особого нестандартного поведения.



Самое интересное: если запустить несколько микросервисов и отправить всем им одинаково тщательно обработанный набор данных, это может привести к противоречивому поведению. Возьмём, к примеру, неверный JSON со множеством одинаковых ключей, имеющих разные значения. Скажем, у меня есть объект «человек», где атрибут «имя» появляется дважды, первый раз это «Марк», второй раз — «Джон».



Скорее всего, анализатор поведёт себя одним из трех способов:




  1. Откажется от разбора записи, как и должно быть.

  2. Сохранит первое имя (Марк).

  3. Сохранит второе имя (Джон).



Выбор последних двух способов зависит от порядка анализа (с использованием стека или по очереди).



Предположим, у меня есть три сервиса, у каждого из них — своя версия поведения. Тогда один сервис остановится и откажется обрабатывать запись, а два остальных увидят разные значения для одного и того же объекта. Необходимо обращаться с ними с осторожностью.



image



Ещё одна проблема связана со временем. Различные часы работают на разных скоростях. В прошлом году я проводил эксперимент для презентации по календарям (нет, не смейтесь, календари на самом деле очень интересны), во время которого я отключил синхронизацию часов на своём компьютере. Затем я проделал то же самое с духовкой и микроволновой печью, чтобы посмотреть, где будет наибольшее отклонение. Где-то через три-четыре недели время на микроволновке сдвинулось примерно на 3 минуты, на ноутбуке — примерно на 2 минуты и 16 секунд, а на духовке — не изменилось.



Часы отклоняются в зависимости от аппаратного обеспечения, температуры, напряжения, влажности и т. д. Они не могут оставаться точными долгое время.



image



Значит, без синхронизации временные метки для одного и того же события могут разделиться и привести к разрозненным результатам. Чтобы этого избежать, необходим NTP. Вы можете проследить за временем и убедиться, что оно незначительно отклоняется. Тем не менее для событий, которые происходят очень быстро, даже у NTP может появиться достаточно вариаций, чтобы вызвать проблемы.



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



В судьбоносный день, когда транзакция выполнялась особенно быстро, NTP-синхронизация компьютерных часов привела к отклонению на какие-то доли секунды: вполне достаточно, чтобы поставить вторую временную метку перед первой. Для полного счастья это случилось примерно в полночь в последний день месяца или в какой-то другой оборот цикла.



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



Хитрость здесь состоит в том, чтобы создать значительное различие между монотонным и системным временем.



image



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



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



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



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



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



image



На самом деле логические ошибки весьма забавны. Они позволяют нам совершать путешествия во времени.



image



Но это ещё не всё. Присутствующие здесь наверняка знакомы с часовыми поясами. Но кто-нибудь здесь слышал про пояса в полчаса?



(Большинство людей поднимают руки.)



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



Но опять же, кто знает о существовании часового пояса в четверть часа? Подобный находился в Новой Зеландии. Или что в Либерии был зафиксирован 44-минутный? Может быть, некоторые здесь слышали, что в 1927 году в часовых поясах в Китае произошли отклонения в пять минут и несколько секунд?



Но и это ещё не всё. Не всегда изменения в часовых поясах незначительны. Например, острова Самоа существуют между UTC –11 и UTC +13, полностью переключая дни. Хитрость в том, что они находятся на линии изменения даты. В 1892 году острова решили согласовать свои календари с США, исходя из рыночных соображений. В том году у них дважды было 4 июля. Первое июля, второе июля, третье июля, четвёртое июля, четвёртое июля, пятое июля...



И опять же в 2011 году (совсем недавно!) они согласовали календари с Китаем, и на этот раз на островах Самоа не было 31 декабря.



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



image



Далее, високосные секунды. Проблемы часовых поясов решит просто UTC. Но это имеет весьма интересные последствия, поскольку каждый тип всё ещё использует временные метки UNIX и свободно конвертируется между UTC и эпохой UNIX, которая начинается 1 января 1970 года.



Это связано с тем, что UTC учитывает високосные секунды, которые часто корректируются: добавляются или удаляются, основываясь на вращении по земной орбите, чтобы синхронизировать часы с реальным миром. С 1970 года мы находимся в сети 27-секундных смещений. Эпоха UNIX в наших системах не учитывает эти секунды.



Изображение, которое вы можете видеть на слайде, появилось пару лет назад из-за ошибки в iPhone, в результате которой перемотка даты на телефоне до 1 января 1970 года приводила к его поломке. Устройство считало программное обеспечение недействительным и отказывалось что-либо воспроизводить. В результате в руках оказывался девайс, который было невозможно ни отремонтировать, ни перезагрузить.



Моё личное предположение, почему это произошло (и я надеюсь, что я прав), связано с несоответствием временных представлений. Если вы используете целое число без знака — опять эти надоедливые целые числа — для представления времени, начинающегося 1 января 1970 года, в качестве эпохи, но с помощью преобразования UTC для установки даты и времени, вы получите 27, что невозможно считать целым числом без знака. Вместо желаемого результата произойдёт потеря значимости. При 32 битах устройство выдаст нам 2106 год, которого может просто не существовать в данном программном обеспечении, и это опять приведёт к поломке.



image



Но это ещё не всё. Далеко не всё. Здесь, в Квебеке и Северной Америке, мы используем григорианское время. Кто-нибудь из присутствующих допускал ошибки в коде, связанные с високосными годами? (Многие поднимают руки.)



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




  • Бенгальский календарь. Солнечный, разделён на шесть сезонов по два месяца в каждом.

  • Китайский календарь. Довольно запутанный лунно-солнечный календарь. Официально используется в Китае (где живёт более миллиарда человек) наряду с григорианским.

  • Эфиопский календарь. Косвенно происходит от старого египетского, первого солнечного календаря, который у нас есть. Технически его високосные дни не являются частью каждого месяца. Полночь в стране начинается тогда, когда в соседних странах шесть часов утра. Местные жители гордится тем, что Эфиопию никогда не колонизировали, поэтому они, как правило, не заинтересованы в принятии других систем.

  • Еврейский календарь. Один из самых старых. Одновременно и лунный, и солнечный. В нём самые сложные правила, связанные с выбором дат, которые я когда-либо видел. Простые правила выбора первого дня года могут занимать несколько страниц. Он официально используется в Израиле и имеет тот же статус, что и григорианский календарь.

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

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

  • И наконец, исламский календарь, который используется в Саудовской Аравии официально, а в других странах — в культурных или религиозных целях. Интересно, что он основан на наблюдениях. То есть каждый новый лунный цикл какой-то уполномоченный человек должен выйти, посмотреть на небо и сказать: «Это новолуние, у нас официально новый месяц». Раньше, когда этот календарь использовался во многих странах, месяцы могли начинаться с разных дат, поскольку в зависимости от местоположения наблюдения отличаются.



Так что да. Здесь нужно многое иметь в виду. Давайте оставим всё это сложное время и пойдём дальше.



image



О нет. Юникод. Осмелюсь предположить: многие в этом зале сталкивались с тем, что различные системы коверкали ваши имена. Поднимите руки. (Более половины зала поднимают руки.)



Да. Стало быть, вы знаете, как это происходит. Проблема заключается в конфликте кодировки между Latin-1 (или ISO-8859-1) и UTF-8.



image



Выходит так, что в нижних символах UTF-8 и ISO-8859-1 абсолютно одинаковы, а самые нижние символы разделены с ASCII.



Поэтому для получения одной и той же кодировки нам необходимо настроить каждый шаг. Сначала пользователь, затем передача данных с помощью HTTP (включая надоедливые метатеги), сами экземпляры, затем языки программирования (некоторые функции по своей сути не являются UTF-8), далее соединение с базой данных (поскольку SQL основан на тексте, он чувствителен к кодировке), а затем и сама база данных. Если вам повезёт, то ваша БД также позволит настроить конфигурацию для таблицы и для столбца.



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



image



В Юникоде длина — это также переменная. Это можно объяснить четырьмя способами. Используем небольшую строку с а и лошадью в ней:




  • Имеется длина в байтах, которая даёт нам 14 байтов в UTF-8, 12 в UTF-16 и 20 в UTF-32.

  • В кодовой единице содержится длина, которая говорит о том, сколько комбинаций битов используется для каждой кодировки строки. В данном случае 14 для UTF-8, 6 для UTF-16 и 5 для UTF-32.

  • Длина кода основана на количестве логических символов Юникода. У нас длина равна 5, так как второй символ здесь сделан с комбинированной меткой, придающей ему маленькую кривую сверху.

  • Кроме того, есть кластеры графем, которые мы, люди, считаем символами. Мне нравится описывать это как «сколько раз нужно нажимать клавишу Delete, пока строка не станет пустой». Длина равна 4.



Длина может изменяться от языка к языку, когда речь заходит о кластерах графем, поэтому важна локализация.



Это весело. На прошлой работе мы один раз удалили целый кластер на 40+ минут. Бинарный протокол запросил длину байта по переданным ему данным, но одна из двух точек связи использовала длину кода для работы. Это продолжалось годами, пока мы не начали отправлять произвольные данные, передаваемые клиентами. Потребовалось около 30 секунд, чтобы ввести с Юникод данные, которые десинхронизовали оба протокола и всё исправили.



Несмотря на то что мы сделали это оперативно, нам понадобилось ещё около трёх дней, чтобы решить проблему окончательно. Подходящее время для отпуска.



image



Этот опрос касается используемых языков. Не знаю, как были получены результаты, но мне они кажутся правильными.



image



А, как забавно. Я получил это в результате одной интересной ошибки Spotify, которая произошла несколько лет назад. Я не буду повторять, что я тогда сказал, а просто дам ссылку на сообщение в блоге.



image



На этом слайде объясняется нормализация Юникода. Он использовался в качестве поддержки ошибки Юникод в Spotify, так что вот ссылка на сообщение в блоге.



image



И теперь мы добрались до по-настоящему интересного материала! Безопасность! Это из статьи 2012 года «Самый опасный код в мире: проверка SSL-сертификатов вне браузера», которую я советую почитать.



Когда дело дошло до плохих абстракций, безопасность оказалась на высоте. Она обнаружила, что почти всё обследованное программное обеспечение было вынуждено проверять сертификаты вне веб-браузеров. Это привело к критической ошибке, в результате которой могли быть пропущены атаки «человек посередине» и перехвачены или изменены данные всего сеанса. Весёлые времена.



image



Вот хороший отрывок из библиотеки OpenSSL. С таким плохим интерфейсом многие люди пропустили критические биты проверки.



image



И вот один о GnuTLS с таким же ужасным интерфейсом.



image



Но API cURL в PHP мой любимый. По умолчанию настройки правильные и корректные, но если вы прочтёте документ, то сможете установить для параметра CURLOPT_SSL_VERIFYHOST значение true. Проблема заключается в том, что в PHP (так же, как в C и C++) true — почти то же самое, что 1. Однако значение 1 для CURLOPT_SSL_VERIFYHOST отключает проверку. Правильное значение — 2.



Упс.



image



Итак, мы подошли к концепции class breaks, о которой я впервые узнал из блога Брюса Шнайера.



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



В случае с программным обеспечением у нас есть концепция class breaks, согласно которой слабое место может быть использовано для всего сразу. Это относительно новое и особенно страшное явление.



image



«Интернет вещей», который был воспринят как нечто поразительное на этой конференции, вызывает особое беспокойство. Реализации просто ужасны. Чем больше вы работаете с кодом, тем меньше вы верите в то, что разработчики IoT хорошо делают своё дело.



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



image



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



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



Применяем ли мы адекватные средства для обеспечения безопасности своих творений? Оставим ли мы всё как есть только потому, что ещё не убили достаточно людей? Может, мы все знаем о проблеме, но просто отрицаем свою личную ответственность, пока что-нибудь не заставит нас действовать как единое целое, чтобы добиться большего?



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


Original source: habrahabr.ru.

https://habrahabr.ru/post/327264/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best

Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

В поисках быстрого локального хранилища

Вторник, 25 Апреля 2017 г. 12:16 (ссылка)

Текст этой статьи также доступен на английском.



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







Контекст задачи



Я работаю в команде, которая занимается разработкой средств разработки для разработчиков реляционных баз данных (SqlServer, MySql, Oracle), среди них как отдельные приложения, так и те, что встраиваются в такой 32 битный “дредноут” как Microsoft Management Studio.



Задача



Восстановить документы открытые в IDE на момент закрытия при следующем запуске.



Usecase



Быстро закрыть IDE перед уходом домой, не думая о том, какие документы сохранены, а какие нет. При следующем запуске среды получить тоже окружение, что было на момент закрытии и продолжить работу.

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



Разбор задачи



Похожая функция есть в браузерах. Там им, с технической точки зрения, живется куда проще: нужно всего-то сохранить пачку URL адресов, а даже у меня редко бывает более сотни вкладок, сам URL это в среднем всего две сотни символов. Таким образом мы хотим получить поведение подобное тому, что есть в браузере, но хранить нам нужно содержимое документа целиком. Получается, что нам нужно куда-то часто и быстро сохранять все документы пользователя. Усложнял задачу и тот факт, что люди иногда работают с SQL не так как с другими языками. Если я, как разработчик на С#, напишу класс более чем на тысячу строк кода, то я поеду в лес в багажнике и по частям это вызовет много вопросов и недовольства, в мире же SQL наряду с крошечными запросами на 10-20 строк существуют монструозные дампы баз, редактировать которые очень трудоемко, а значит пользователи захотят, чтобы их правки были в безопасности.



Требования к хранилищу



Проанализировав задачу сформулировали следующие требования к хранилищу:


  1. Это должно быть встраиваемое легковесное решение.

  2. Скорость записи.

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



Претенденты на роль



Первый лобовой и топорный вариант: хранить все в папке, где-нибудь в AppData.

Очевидный вариант это SQLite. Стандарт в области встраиваемых баз данных. Очень обстоятельный и популярный проект.

Третьим стала база LiteDB. Первый ответ гугла на вопрос: “embedded database for .net”



Первый взгляд



Файловая система — файлы есть файлы. За ними придется следить, им придется придумывать имена. Помимо содержимого файла нужно хранить и небольшой набор свойств(оригинальный путь на диске, строка соединения, версия IDE в которой он был открыт), а значит придется или создавать по два файла на один документ или придумывать формат, дабы отделить свойства от содержимого.



SQLite — классическая реляционная база данных. База представлена одним файлом на диске. На этот файл накатывается схема данных, после чего с ней придется взаимодействовать средствами SQL. Можно будет создать две таблицы: одну для свойств, другую для контента, на случай если будут задачи где понадобится одно без другого.



LiteDB — нереляционная база. Как и в SQLite база представлена одним файлом. Полностью написана на С#. Подкупающая простота использования: всего лишь нужно отдать библиотеке объект, а сериализацию она уже берет на себя.



Performance тест



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

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

В файлы писал через FileStream со стандартным размером буфера.

В SQLite был один нюанс на котором считаю нужным заострить внимание. Мы не могли складывать всё содержимое документа(выше писал, что они могут быть действительно большими) в одну ячейку базы данных. Дело в том, что в целях оптимизации мы храним текст документа построчно, а это значит, что для того чтобы сложить текст в одну ячейку нам нужно было бы слить весь текст в одну строку, чем удвоить, количество используемой оперативной памяти. Другую сторону той же проблемы получили бы и на чтении данных из базы. Поэтому в SQLite была отдельная табличка, где данные хранились построчно и были связаны по внешнему ключу с таблицей, где лежали лишь свойства документов. Кроме того, получилось немного ускорить базу, вставляя данные пачками по несколько тысяч строк в режиме синхронизации OFF, без журналирования и в рамках одной транзакции(этот трюк я подсмотрел здесь и здесь).

В LiteDB просто отдавался объект, у которого одним из свойств был List

и библиотека сама это сохраняла на диск.



Еще во время разработки тестового приложения я понял, что мне больше нравится LiteDB, дело в том, что тестовый код для SQLite занимал более 120 строк, а код решающий ту же задачу для LiteDB менее 20.
Генерация тестовых данных
FileStrings.cs

    internal class FileStrings {

private static readonly Random random = new Random();

public List Strings {
get;
set;
} = new List();

public int SomeInfo {
get;
set;
}

public FileStrings() {
}

public FileStrings(int id, int minLines, decimal lineIncrement) {

SomeInfo = id;
int lines = minLines + (int)(id * lineIncrement);
for (int i = 0; i < lines; i++) {

Strings.Add(GetString());
}
}

private string GetString() {

int length = 250;
StringBuilder builder = new StringBuilder(length);
for (int i = 0; i < length; i++) {

builder.Append(random.Next((int)'a', (int)'z'));
}
return builder.ToString();
}
}


Program.cs

            List files = Enumerable.Range(1, NUM_FILES + 1)
.Select(f => new FileStrings(f, MIN_NUM_LINES, (MAX_NUM_LINES - MIN_NUM_LINES) / (decimal)NUM_FILES))
.ToList();


SQLite
 private static void SaveToDb(List files) {

using (var connection = new SQLiteConnection()) {
connection.ConnectionString = @"Data Source=data\database.db;FailIfMissing=False;";
connection.Open();
var command = connection.CreateCommand();
command.CommandText = @"CREATE TABLE files
(
id INTEGER PRIMARY KEY,
file_name TEXT
);
CREATE TABLE strings
(
id INTEGER PRIMARY KEY,
string TEXT,
file_id INTEGER,
line_number INTEGER
);
CREATE UNIQUE INDEX strings_file_id_line_number_uindex ON strings(file_id,line_number);
PRAGMA synchronous = OFF;
PRAGMA journal_mode = OFF";
command.ExecuteNonQuery();

var insertFilecommand = connection.CreateCommand();
insertFilecommand.CommandText = "INSERT INTO files(file_name) VALUES(?); SELECT last_insert_rowid();";
insertFilecommand.Parameters.Add(insertFilecommand.CreateParameter());
insertFilecommand.Prepare();

var insertLineCommand = connection.CreateCommand();
insertLineCommand.CommandText = "INSERT INTO strings(string, file_id, line_number) VALUES(?, ?, ?);";
insertLineCommand.Parameters.Add(insertLineCommand.CreateParameter());
insertLineCommand.Parameters.Add(insertLineCommand.CreateParameter());
insertLineCommand.Parameters.Add(insertLineCommand.CreateParameter());
insertLineCommand.Prepare();

foreach (var item in files) {
using (var tr = connection.BeginTransaction()) {
SaveToDb(item, insertFilecommand, insertLineCommand);
tr.Commit();
}
}
}
}

private static void SaveToDb(FileStrings item, SQLiteCommand insertFileCommand, SQLiteCommand insertLinesCommand) {

string fileName = Path.Combine("data", item.SomeInfo + ".sql");

insertFileCommand.Parameters[0].Value = fileName;


var fileId = insertFileCommand.ExecuteScalar();

int lineIndex = 0;
foreach (var line in item.Strings) {

insertLinesCommand.Parameters[0].Value = line;
insertLinesCommand.Parameters[1].Value = fileId;
insertLinesCommand.Parameters[2].Value = lineIndex++;
insertLinesCommand.ExecuteNonQuery();
}
}


LiteDB
        private static void SaveToNoSql(List item) {

using (var db = new LiteDatabase("data\\litedb.db")) {
var data = db.GetCollection("files");
data.EnsureIndex(f => f.SomeInfo);
data.Insert(item);
}
}




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





Нас не удивила победа LiteDB над SQLite, хоть и удивил порядок этой победы. В полный шок меня повергла победа LiteDB над файлами. Немного исследовав репозиторий библиотеки я, например, нашел очень грамотно реализованную постраничную запись на диск, на этом и успокоился, хоть и уверен это лишь один из многих performance-tricks, которые там используются. Еще хочу обратить внимание на то, как быстро деградирует скорость доступа к файловой системе, когда файлов в папке становится действительно много.



Для разработки это feature была выбрана LiteDB, о чем в дальнейшем мы жалели довольно редко. Спасало, что библиотека написана на родном для всех c# и если что-то было не до конца ясно, то всегда можно было почитать в исходниках.



Недостатки



Помимо выше приведенных преимуществ LiteDB над конкурентами по мере разработки стали всплывать и недостатки, большинство из которых можно списать на молодость библиотеки. Начав использовать библиотеку слегка за рамками “обычного” сценария нашли несколько проблем(#419, #420, #483, #496) Автор библиотеки всегда очень быстро отвечал на вопросы, большинство проблем очень быстро исправлялись. Сейчас осталась только одна(и пусть статус closed вас не смущает). Это проблема конкурентного доступа. По всей видимости где-то в глубине библиотеки спрятался очень противный race-condition. Для себя мы этот баг обошли довольно интересным способом, о чем я планирую написать отдельно.



Еще стоит упомянуть об отсутствии удобного редактора и просмотрщика. Есть LiteDBShell, но это для фанатов консоли.

UPD: недавно нашелся инструмент



Резюме



Мы построили большую и важную функциональность поверх LiteDB, а сейчас ведется разработка еще одной крупной feature где тоже будем использовать эту библиотеку. Для тех, кто сейчас ищет in-process базу для своих нужд предлагаю посмотреть в сторону LiteDB и того как она ляжет на ваши задачи, ведь нет никакой гарантии, что то, что сработало для одного, так же хорошо сработает для чего-то совершенно другого.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/326990/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество
rss_rss_hh_new

Тестирование параллельных процессов

Вторник, 25 Апреля 2017 г. 09:07 (ссылка)

image



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



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



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





Пример номер один. Параллельное добавление одного и того же



Задача. У нас есть приложение с базой данных (PostgreSQL) и нам надо наладить импорт данных из сторонней системы. Допустим, есть таблица account (id, name) и связи идентификаторов с внешней системой в таблице account_import (id, external_id). Давайте набросаем простой механизм приема сообщений.



При приеме сообщения будем сперва проверять — есть ли такие записи у нас в базе. Если есть, то будем обновлять имеющиеся. Если нет, то будем добавлять в базу.



$data = json_decode($jsonInput, true); // '{"id":1,"name":"account1"}'

try {
$connection->beginTransaction();

// Проверим, есть ли такая запись в базе
$stmt = $connection->prepare("SELECT id FROM account_import
WHERE external_id = :external_id");
$stmt->execute([
':external_id' => $data['id'],
]);
$row = $stmt->fetch();

usleep(100000); // 0.1 sec

// Если импортируемая запись в базе есть, то обновим ее
if ($row) {
$stmt = $connection->prepare("UPDATE account SET name = :name WHERE id = (
SELECT id FROM account_import WHERE external_id = :external_id
)");
$stmt->execute([
':name' => $data['name'],
':external_id' => $data['id'],
]);
$accountId = $row['id'];
}
// Иначе создадим новую запись
else {
$stmt = $connection->prepare("INSERT INTO account (name) VALUES (:name)");
$stmt->execute([
':name' => $data['name'],
]);
$accountId = $connection->lastInsertId();

$stmt = $connection->prepare("INSERT INTO account_import (id, external_id)
VALUES (:id, :external_id)");
$stmt->execute([
':id' => $accountId,
':external_id' => $data['id'],
]);
}

$connection->commit();
}
catch (\Throwable $e) {
$connection->rollBack();
throw $e;
}




С первого взгляда выглядит хорошо. Но если данные в нашу систему могут передаваться не строго последовательно, тут можем столкнуться с проблемой. Задержка 0.1 секунды в этом примере нам нужна, чтобы гарантированно воспроизвести проблему. Что будет, если выполнить импорт одних и тех же данных параллельно? Вероятно, вместо того, чтобы данные были добавлены, а потом обновлены, будет попытка повторной вставки данных и, как следствие, ошибка нарушения первичного ключа в account_import.



Чтобы исправить ошибку, ее хорошо бы сперва воспроизвести. А лучше всего — написать тест, который воспроизводит ошибку. Я решил для этого запускать команды асинхронно с помощью bash и написал простой скрипт для этого, который можно использовать не только в связке с PHP.



Идея проста — запускаем в фоне несколько экземпляров команд, потом ждем когда все они завершатся, и проверяем коды выполнения. Если среди кодов выполнения есть отличные от нуля, значит мы нашли баг. В упрощенном виде скрипт будет выглядеть так:



# Команда, которую будем проверять
COMMAND=”echo -e '{\"id\":1,\"name\":\"account1\"}' | ./cli app:import”

# PID-ы запущенных фоновых процессов
pids=()

# Результаты выполнения фоновых процессов
results=()

# Ожидаемые результаты выполнения фоновых процессов (нули)
expects=()

# Запустим процессы в фоне и перенаправим вывод в stderr
for i in $(seq 2)
do
eval $COMMAND 1>&2 & pids+=($!) ; echo -e '>>>' Process ${pids[i-1]} started 1>&2
done

# Ожидаем завершения каждого процесса и сохраняем результаты в $results
for pid in "${pids[@]}"
do
wait $pid
results+=($?)
expects+=(0)
echo -e '<<<' Process $pid finished 1>&2
done

# Сравним полученные результаты с ожидаемыми
result=`( IFS=$', '; echo "${results[]}" )`
expect=`( IFS=$', '; echo "${expects[]}" )`
if [ "$result" != "$expect" ]
then
exit 1
fi




Полную версию скрипта выложил на github.



На основе этой команды мы можем дописать к PHPUnit новые assert-ы. Тут уже все проще и я не буду подробно останавливаться на этом. Скажу только, что в вышеупомянутом проекте они реализованы. Чтобы их использовать достаточно подключить трейт AsyncTrait к вашему тесту.



Напишем такой тест.



use App\Command\Initializer;
use Mnvx\PProcess\AsyncTrait;
use Mnvx\PProcess\Command\Command;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Tester\CommandTester;

class ImportCommandTest extends TestCase
{
use AsyncTrait;

public function testImport()
{
$cli = Initializer::create();
$command = $cli->find('app:delete');

// Удаляем запись c external_id = 1,
// чтобы проверить случай параллельного добавления одной и той же записи
$commandTester = new CommandTester($command);
$commandTester->execute([
'externalId' => 1,
]);

$asnycCommand = new Command(
'echo -e '{"id":1,"name":"account1"}' | ./cli app:import', // Тестируемая команда
dirname(__DIR__), // Каталог, из которого будет запускаться команда
2 // Количество запускаемых экземпляров команд
);
// Запуск проверки
$this->assertAsyncCommand($asnycCommand);
}
}




В результате запуска теста получим такой вывод.



$ ./vendor/bin/phpunit
PHPUnit 6.1.1 by Sebastian Bergmann and contributors.

F 1 / 1 (100%)

Time: 230 ms, Memory: 6.00MB

There was 1 failure:

1) ImportCommandTest::testImport
Failed asserting that command
echo -e '{"id":1,"name":"account1"}' | ./cli app:import (path: /var/www/pprocess-playground, count: 2)
executed in parallel.
Output:


>>> Process 18143 started
>>> Process 18144 started
Account 25 imported correctly


[Doctrine\DBAL\Exception\UniqueConstraintViolationException]
An exception occurred while executing 'INSERT INTO account_import (id, exte
rnal_id) VALUES (:id, :external_id)' with params ["26", 1]:
SQLSTATE[23505]: Unique violation: 7 ОШИБКА: повторяющееся значение ключа
нарушает ограничение уникальности "account_import_pkey"
DETAIL: Ключ "(external_id)=(1)" уже существует.

-------

app:import

<<< Process 18143 finished
<<< Process 18144 finished

.

/var/www/pprocess-playground/vendor/mnvx/pprocess/src/AsyncTrait.php:19
/var/www/pprocess-playground/tests/ImportCommandTest.php:30

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.




Причину мы уже обсудили. Теперь попробуем добавить принудительную блокировку параллельного выполнения фрагмента нашего скрипта (тут используется malkusch/lock).



$mutex = new FlockMutex(fopen(__FILE__, 'r'));
$mutex->synchronized(function () use ($connection, $data) {
// наш код из блока try
});


Тест пройден:



$ ./vendor/bin/phpunit
PHPUnit 6.1.1 by Sebastian Bergmann and contributors.

. 1 / 1 (100%)

Time: 361 ms, Memory: 6.00MB

OK (1 test, 1 assertion)




Этот и другие примеры я выложил на github, если вдруг кому-то понадобится.



Пример номер два. Подготовка данных в таблице



Этот пример будет немного интереснее. Допустим, у нас есть таблица пользователей users (id, name) и мы желаем хранить в таблице users_active (id) список активных в настоящий момент пользователей.



У нас будет команда, которая каждый раз будет удалять все записи из таблицы users_acitve и добавлять туда данные заново.



try {
$connection->beginTransaction();

$connection->prepare("DELETE FROM users_active")->execute();

usleep(100000); // 0.1 sec

$connection->prepare("INSERT INTO users_active (id) VALUES (3), (5), (6), (10)")->execute();

$connection->commit();
$output->writeln('users_active refreshed');
}
catch (\Throwable $e) {
$connection->rollBack();
throw $e;
}




Тут только с первого взгляда все хорошо. На самом же деле при параллельном запуске снова получим ошибку.



Напишем тест, чтобы ее воспроизвести.



use Mnvx\PProcess\AsyncTrait;
use Mnvx\PProcess\Command\Command;
use PHPUnit\Framework\TestCase;

class DetectActiveUsersCommandTest extends TestCase
{
use AsyncTrait;

public function testImport()
{
$asnycCommand = new Command(
'./cli app:detect-active-users', // Тестируемая команда
dirname(__DIR__), // Каталог, из которого будет запускаться команда
2 // Количество запускаемых экземпляров команд
);
// Запуск проверки
$this->assertAsyncCommand($asnycCommand);
}
}




Запускаем тест и видим текст ошибки:



$ ./vendor/bin/phpunit tests/DetectActiveUsersCommandTest.php
PHPUnit 6.1.1 by Sebastian Bergmann and contributors.

F 1 / 1 (100%)

Time: 287 ms, Memory: 4.00MB

There was 1 failure:

1) DetectActiveUsersCommandTest::testImport
Failed asserting that command
./cli app:detect-active-users (path: /var/www/pprocess-playground, count: 2)
executed in parallel.
Output:


>>> Process 24717 started
>>> Process 24718 started
users_active refreshed
<<< Process 24717 finished


[Doctrine\DBAL\Exception\UniqueConstraintViolationException]
An exception occurred while executing 'INSERT INTO users_active (id) VALUES
(3), (5), (6), (10)':
SQLSTATE[23505]: Unique violation: 7 ОШИБКА: повторяющееся значение ключа
нарушает ограничение уникальности "users_active_pkey"
DETAIL: Ключ "(id)=(3)" уже существует.

-------

app:detect-active-users

<<< Process 24718 finished

.

/var/www/pprocess-playground/vendor/mnvx/pprocess/src/AsyncTrait.php:19
/var/www/pprocess-playground/tests/DetectActiveUsersCommandTest.php:19

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.




По тексту ошибки понятно, что снова INSERT выполняется параллельно и это приводит к нежелательным последствиям. Попробуем сделать блокировку на уровне записей — добавим строчку после старта транзакции:



$connection->prepare("SELECT id FROM users_active FOR UPDATE")->execute();




Запускаем тест — ошибка ушла. Наш тест запускает два экземпляра процесса. Давайте увеличим в нашем тесте количество экземпляров до 3-х и посмотрим, что будет.



$asnycCommand = new Command(
'./cli app:detect-active-users', // Тестируемая команда
dirname(__DIR__), // Каталог, из которого будет запускаться команда
3 // Количество запускаемых экземпляров команд
);




И снова имеем ту же ошибку. В чем дело, мы же добавили блокировку?! Немного подумав, можно догадаться, что такая блокировка поможет только если в таблице users_active есть записи. В случае же, когда работают 3 процесса одновременно, получается картина такая — первый процесс получает блокировку. Второй и третий процесс ждут завершения транзакции первого процесса. Как только транзакция будет завершена, продолжат выполняться параллельно и второй и третий процесс, что приведет к нежелательным последствиям.



Чтобы починить, сделаем блокировку более общую. Например,



$connection->prepare("SELECT id FROM users WHERE id IN (3, 5, 6, 10) FOR UPDATE")->execute();




Либо вместо DELETE мы могли просто воспользоваться TRUNCATE, которая блокирует всю таблицу.



Пример номер три. Deadlock



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



Напишем пару таких команд. Это классический случай, когда возникает взаимная блокировка.



Первая команда сперва обновляет запись с id=1, потом с id=2.



try {
$connection->beginTransaction();

$connection->prepare("UPDATE deadlock SET value = value + 1 WHERE id = 1")->execute();

usleep(100000); // 0.1 sec

$connection->prepare("UPDATE deadlock SET value = value + 1 WHERE id = 2")->execute();

$connection->commit();
$output->writeln('Completed without deadlocks');
}
catch (\Throwable $e) {
$connection->rollBack();
throw $e;
}




Вторая команда сперва обновляет запись с id=2, потом с id=1.



try {
$connection->beginTransaction();

$connection->prepare("UPDATE deadlock SET value = value + 1 WHERE id = 2")->execute();

usleep(100000); // 0.1 sec

$connection->prepare("UPDATE deadlock SET value = value + 1 WHERE id = 1")->execute();

$connection->commit();
$output->writeln('Completed without deadlocks');
}
catch (\Throwable $e) {
$connection->rollBack();
throw $e;
}




Тест будет выглядеть так.



use Mnvx\PProcess\AsyncTrait;
use Mnvx\PProcess\Command\CommandSet;
use PHPUnit\Framework\TestCase;

class DeadlockCommandTest extends TestCase
{
use AsyncTrait;

public function testImport()
{
$asnycCommand = new CommandSet(
[ // Тестируемые команды
'./cli app:deadlock-one',
'./cli app:deadlock-two'
],
dirname(__DIR__), // Каталог, из которого будет запускаться команда
1 // Количество запускаемых экземпляров команд
);
// Запуск проверки
$this->assertAsyncCommands($asnycCommand);
}
}




В результате запуска теста увидим причину ошибки:



$ ./vendor/bin/phpunit tests/DeadlockCommandTest.php
PHPUnit 6.1.1 by Sebastian Bergmann and contributors.

F 1 / 1 (100%)

Time: 1.19 seconds, Memory: 4.00MB

There was 1 failure:

1) DeadlockCommandTest::testImport
Failed asserting that commands
./cli app:deadlock-one, ./cli app:deadlock-two (path: /var/www/pprocess-playground, count: 1)
executed in parallel.
Output:


>>> Process 5481 started: ./cli app:deadlock-one
>>> Process 5481 started: ./cli app:deadlock-two


[Doctrine\DBAL\Exception\DriverException]
An exception occurred while executing 'UPDATE deadlock SET value = value +
1 WHERE id = 1':
SQLSTATE[40P01]: Deadlock detected: 7 ОШИБКА: обнаружена взаимоблокировка
DETAIL: Процесс 5498 ожидает в режиме ShareLock блокировку "транзакция 294
738"; заблокирован процессом 5499.
Процесс 5499 ожидает в режиме ShareLock блокировку "транзакция 294737"; заб
локирован процессом 5498.
HINT: Подробности запроса смотрите в протоколе сервера.
CONTEXT: при изменении кортежа (0,48) в отношении "deadlock"

-------

app:deadlock-two

Completed without deadlocks
<<< Process 5481 finished
<<< Process 5484 finished

.

/var/www/pprocess-playground/vendor/mnvx/pprocess/src/AsyncTrait.php:39
/var/www/pprocess-playground/tests/DeadlockCommandTest.php:22

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.




Проблема лечится добавлением блокировки по аналогии с первым примером. Либо пересмотром структуры базы или алгоритма работы с данными.



Резюмируем



При параллельном исполнении кода могут возникать неожиданные ситуации, при исправлении которых полезно написать тесты. Мы рассмотрели несколько таких ситуаций и написали тесты, воспользовавшись pprocess.
Original source: habrahabr.ru (comments, light).

https://habrahabr.ru/post/327292/

Метки:   Комментарии (0)КомментироватьВ цитатник или сообщество

Следующие 30  »

<программирование - Самое интересное в блогах

Страницы: [1] 2 3 ..
.. 10

LiveInternet.Ru Ссылки: на главную|почта|знакомства|одноклассники|фото|открытки|тесты|чат
О проекте: помощь|контакты|разместить рекламу|версия для pda