...

суббота, 15 июля 2017 г.

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

[Из песочницы] Рекомендации по безопасности при работе с Docker

А был ли взлом «Госуслуг»? Расследование расследования от ИБ Яндекса

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

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

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

Такие фрагменты попадают в код страницы на стороне клиента, если у него установлено расширение. И здесь напрашивается вопрос: «как же тогда этот код оказался на стороне сервера?» И у нас есть гипотеза.

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

А теперь про механизм защиты. У порядка процента пользователей Яндекса возникает аналогичная проблема с посторонним контентом в посещаемых страницах. И это очень много. Поэтому мы давно и активно боремся с подобной угрозой. Прежде всего, мы защищаем пользователей Яндекс.Браузера с помощью механизма, который препятствует подгрузке данных с выявленных сомнительных адресов. Информация о новых угрозах оперативно попадает в базу SafeBrowsing, которая доступна Браузеру, Поиску и другим нашим сервисам при помощи специального API. Это позволяет нам быстро пресекать распространение эпидемий без необходимости выпускать обновление для самого приложения.

К счастью, в случае с Госуслугами пользователи Яндекс.Браузера по большей части уже были защищены: 15 найденных фрагментов из 16 уже были внесены в базу SafeBrowsing еще до ситуации с сайтом в рамках обычного процесса поиска угроз.

И еще кое-что. Иметь щит на стороне клиента хорошо, но и о защите на стороне сервиса тоже не стоит забывать. Мы рекомендуем всем крупным сервисам, работающим с приватными данными пользователей, внедрять поддержку Content Security Policy. Это позволит заблокировать подгрузку стороннего контента и защитить ваших пользователей. Сервисы Яндекса уже давно используют CSP для предотвращения загрузки постороннего кода.

Комментарии (0)

    Let's block ads! (Why?)

    [Из песочницы] История создания библиотеки для группового общения андроид-устройств через Wi-Fi Peer-to-Peer соединение

    Акции Яндекса взлетели после сделки с Uber

    Сервис Яндекс.Такси сообщил об объединении с Uber. Совместное предприятие, которое создадут компании, будет работать в нескольких странах СНГ. В результате заявления Яндекса стоимость его акций резко выросла как в России, так и на зарубежном рынке.

    Подробности сделки


    13 июля в блоге Яндекса появилась запись об объединении сервиса такси, принадлежащего компании, с Uber. Генеральный директор Яндекс.Такси Тигран Худавердян сообщил, что в результате слияния появится новая компания. Она будет работать в 127 городах России, Беларуси, Казахстана, Грузии, Армении и Азербайджана.

    В июне Яндекс и Uber осуществили суммарно 35 млн поездок в этих регионах. Их общая стоимость составила 7,9 млрд рублей. Это позволяет новому игроку занять 5-6% рынка легальных перевозок на такси. Общий объем этого сегмента в 2016 году составил 502 млрд рублей.

    В создание нового игрока на рынке Яндекс и Uber вложили $100 млн и $225 млн соответственно. Стоимость объединённой компании оценивается в $3,725 млрд. При этом 59,3% объединения будет владеть Яндекс, 36,6% — Uber. Ещё 4,1% достанется сотрудникам новой компании.

    Стоит отметить, что Яндекс.Такси в России работает в убыток по EBITDA, хоть и появился на местном рынке в 2011 году. Эксперты прогнозировали, что компания станет прибыльной к 2021 году. В апреле сообщалось, что Яндекс.Такси ищет инвестора.

    Что касается Uber, компания не раскрывает финансовых данных по России, но в целом за 2016 год её выручка составила $6,5 млрд, а чистый убыток — $2,8 млрд. До этого Uber также был убыточным.

    Изображение:joiseyshowaa, CC BY-SA 2.0

    Сделка с Яндекс.Такси — не первый подобный опыт для Uber. Годом ранее китайское подразделение компании было объединено местным лидером Didi Chuxing. Тогда Uber получил 17,5% новой компании, общая стоимость которо оценивалась в $35 млрд. Избавиться от китайского подразделения Uber решил, за 1,5 года работы в стране потерял более $2 млрд.

    Слияние Яндекс.Такси и Uber стало неожиданностью. Компании не объявляли о ведении переговоров, а в деловых СМИ обсуждалась разве что возможность покупки Яндексом служб такси «Максим» и InDriver.

    Сделку о слиянии планируется закрыть в четвертом квартале 2017 года. До этого соглашение должно быть одобрено ФАС. Антимонопольная служба опасается, что новый игрок рынка создаст угрозу конкуренции. Однако ходатайств об объединении бизнеса компаний в ФАС пока не получили. Как бы то ни было, сделка уже оказала влияние и на сами компании, и на рынок в целом. В частности, цена акций Яндекса побила исторический рекорд.

    Взлёт акций Яндекса


    После объявления о слиянии с Uber цена акций Яндекса поползла вверх. В ходе торгов на фондовой бирже NASDAQ она выросла на 16%, составив $31,70. Таким образом, всего за день рыночная стоимость компании на международном рынке выросла на $1,36 млрд, составив $10,73 млрд.

    На российском рынке новость о слиянии двух игроков также наделала шума. О сделке стало известно 13 июля в 13.00, а уже в 13.40 стоимость ценных бумаг Яндекса на Московской бирже поднялась на 17%, составив 1915 руб.

    К 14.47 МСК торговлю пришлось перевести в режим дискретного аукциона, поскольку рост цены перевалил за 20%. Максимальная стоимость акция Яндекса 13 июля составила 2039,5 руб., что на 24,97% выше цены закрытия предыдущих торгов.

    Более того, это рекордный показатель для Яндекса. Цена акций компании не поднималась выше 2000 руб. с того момента, как они начали торговаться на Московской бирже в июне 2014 года.

    В этом нашем материале мы подробно рассказывали о том, как купить акции «Яндекса» на зарубежных биржевых площадках и Московской бирже. Открыть брокерский счет для этого можно дистанционно через «Госуслуги» (подробнее об этом в нашей статье).

    Комментарии (0)

      Let's block ads! (Why?)

      Security Week 28: а Petya сложно открывался, в Android закрыли баг чипсета Broadcomm, Copycat заразил 14 млн девайсов

      [Перевод] AWS DeepLearning AMI — почему (и как) его стоит использовать

      Иногда хорошие вещи приходят бесплатно ...


      Что такое AMI?


      Для тех из вас, кто не знает, что такое AMI, позвольте мне процитировать официальную документацию по этому вопросу:


      Amazon Machine Image (AMI) предоставляет данные, необходимые для запуска экземпляра виртуального сервера в облаке. Вы настраиваете AMI при запуске экземпляра, и вы можете запустить столько экземпляров из AMI, сколько вам нужно. Вы также можете запускать экземпляры виртуальных машин из множества различных AMI, сколько вам нужно.

      Этого должно быть достаточно, чтобы понять остальную часть статьи, однако я бы посоветовал потратить некоторое время на официальную документацию об AMI.


      Если вдруг не слышали термин Deep learning ранее...

      Глубокое обучение или глубинное (англ. Deep learning) — набор алгоритмов машинного обучения, которые пытаются моделировать высокоуровневые абстракции в данных, используя архитектуры, состоящие из множества нелинейных преобразований
      Глубокое обучение является частью более широкого семейства методов машинного обучения, которые подбирают представление данных. Наблюдение (например, изображение) может быть представлено многими способами, такими как вектор интенсивности значений на пиксель, или (в более абстрактной форме) как множество примитивов, областей определенной формы, и т. д.
      С другой стороны есть и мнения, что глубокое обучение — не что иное, как модное слово или ребрендинг для нейронных сетей. Wiki.


      Что такое AWS DeepLearning AMI (а.к.а. DLAMI) и почему его нужно использовать?


      Обучение (тренировку) нейронных сетей можно делать 2-мя путями: с использование CPU или с использованием GPU. Думаю ни для кого не секрет что обучение с помощью GPU показывает лучшие результаты, с точки зрения скорости(а как следствие и затрат), чем обучение с помощью CPU, поэтому все современные системы машинного обучения поддерживают GPU. Однако, чтобы использовать все преимущества GPU мало просто иметь этот самый GPU, вам необходимо еще "по приседать":


      1. Получить сам GPU.
      2. Настроить его драйвера.
      3. Найти библиотеки, которые могут использовать все возможности именно вашего GPU. Библиотеки должны быть совместимы с драйверами и оборудованием из 1 и 2.
      4. Вам необходимо иметь нейронную сеть, которая была скомпилирована с библиотеками, которые были найдены ранее.

      Так что же нужно сделать что бы решить все эти 4 незадачи? Есть 2 варианта:


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

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


      Как DLAMI может решить эту проблему? Да легко, дело в том, что DLAMI, это первое бесплатное решение, включающее все, что необходимо прямо из коробки:


      • Драйвера для новейшего графического процессора от Nvidia;
      • Последние библиотеки CUDA и CuDNN;
      • Предварительно собранные фреймворки с поддержкой GPU (и собранные с теми версиями CUDA и CuDNN которые доступны в AMI).

      Аль, к слову, список фреймворков, которые работают из коробки:



      DLAMI можно использовать с GPU-совместимым машинами на AWS, например P2 или G2:


      image


      Можете, кстати, попробовать поиграться со свеже выпущенными G3


      Надеюсь, теперь у нас есть ответ на вопрос: почему и кому нужно использовать DLAMI. Теперь давайте обсудим ответ на следующий вопрос ...


      Как именно можно создать машину с DLAMI?


      Для этого сначала нужно выбрать, какой вариант DLAMI более предпочтителен:


      • на базе Ubuntu (может использоваться с любыми пакетами Ubuntu).
      • на базе Amazon Linux (включает все программы AWS, такие как awscli, из коробки).

      Если с типом DLAMI определились то перейдем с способам создания машин на базе DLAMI:


      • С помощью AWS EC2 Marketplace:
      • С помощью консоли EC2.

      Консоль EC2 фактически предоставляет два способа ее создания, обычное создание:



      И ускоренное создание консоли EC2, применяя конфигурацию по умолчанию:



      Проблемы с обновлениями


      Существует одна оговорка, которую необходимо обсудить. Поскольку все фреймворки построены с нуля, вы не можете просто так взять и обновить их до последней версии, есть риск получить версию фреймворка, которая не собрана с поддержкой GPU (или не совместима с версией CUDA). Так что обновляйте пакеты на свой страх и риск!


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


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

      Комментарии (0)

        Let's block ads! (Why?)

        [Из песочницы] Как крупные компании следят за работниками

        IBM и ВВС США разрабатывают нейроморфный суперкомпьютер нового поколения

        Чемпионы мира — о спортивном программировании

        DataArt давно дружит с командой ИТМО по спортивному программированию и помогает ей. Этим летом в гости в наш петербургский центр разработки пришли Илья Збань, Иван Белоногов и Владимир Смыкалов. Чемпионы мира 2017 года рассказали о том, как именно программисты соревнуются между собой, о тренировочных сборах, любимых задачах и сильнейших соперниках.

        Олимпиада по программированию


        Главное соревнование программистов — международная студенческая олимпиада под эгидой ACM (ACM-ICPC, или просто ICPC) — проходит с 1970-х, а в виде, близком к сегодняшнему, оформилась в 1989 году. Олимпиада предназначена для студентов и аспирантов, за редким исключением к соревнованиям не допускают программистов старше 24-х лет. К тому же, испытывать силы в финале можно только дважды, а в региональных отборах разрешается участвовать всего пять раз. На ранних этапах, проходящих по всему миру, соревнуются тысячи команд. Около сотни лучших доходят до финала.

        Основные правила


        Команды состоят из трех человек, при этом в распоряжении каждой команды — только один компьютер. Перед началом соревнования всем выдаются конверты с задачами алгоритмического или математического характера — от восьми до 13 штук — которые нужно решить за пять часов. Решение задачи — программа, считывающая текстовый запрос и выдающая текстовый ответ. Для проверки решение прогоняется примерно на сотне тестов, заранее подготовленных жюри, — верным оно признается, только если ответ получается правильным в каждом из тестов.

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

        Языки и среда


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

        На разных соревнованиях набор языков может быть различным. Например, на онлайн-платформе Codeforces допускается около 20 языков: от C++ и Java до Haskell и Perl.

        Большинство команд в финалах пишет на C++, поскольку на первый план выходит скорость. В качестве среды разработки многие команды используют VIM (в нем, например, работали Иван и Илья) или Gina (в ней работал Владимир). Те, кто все же пишет на Java, как правило пользуются средой вроде Eclipse, поскольку писать на Java без автокомплита гораздо сложнее.

        В ближайшее время можно ждать изменений, поскольку финалы теперь будет спонсировать JetBrains (20 лет до конца мая 2017 года спонсором ICPC был IBM). Это значит, что на них появится и продукция спонсора: IDEA для Java и CLion для С++. Возможно, после этого команды начнут широко пользоваться отладчиками, хотя пока чаще справляются без них.

        Эволюция задач


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

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

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

        Примеры задач


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

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

        Процесс решения


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

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

        Особенности кода


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

        Еще одна особенность спортивного программирования в том, что тестовой системой никак не оценивается освобождение памяти — решение работает всего несколько секунд.

        Алгоритмы


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

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

        Объем кода напрямую не влияет на итоговую оценку, другое дело, что 1000 строк написать за отведенное время сложно. А придумав красивое лаконичное решение, можно уложиться всего в 10–15 минут. Именно под поиск таких изящных путей и заточены большинство условий: средний объем решения — 100-200 строк кода, хотя в некоторых случаях он может доходить до 300. В обычной жизни 300 строк не так уж много, но здесь у тебя есть всего пять часов на решение всех задач. Писать нужно быстро, а если в трех сотнях строк будет допущена ошибка — задача не пройдет, значит, все время на ее решение будет попросту потеряно. К тому же, чем длиннее код, тем труднее найти ошибку в распечатанной версии.

        Другие турниры и тренировки



        Денежные призы далеко не основная мотивация участников турниров. На фото: Иван Белоногов и Илья Збань — призеры VK Cup 2015 (источник — страница Ивана Белоногова). В 2017-м призером VK Cup стал третий участник чемпионской команды ИТМО Владимир Смыкалов.

        Мы постоянно участвуем в индивидуальных турнирах — их проводится очень много. Например, соревнования на российском сайте Codeforces регулярно собирают по несколько тысяч человек, из которых россиян обычно около 20 %. Стандартный тур здесь состоит из пяти алгоритмических задач, которые нужно решить за два часа. Самое главное в сложившемся вокруг этого ресурса сообществе — личный рейтинг, рассчитанный по системе Эло, как в шахматах. Успешно выступая на турнирах, программисты получают очки — их определенное количество автоматически меняет цвет ника. Те, у кого ники красные, получают не только просьбы о помощи, но и предложения от работодателей. А главное, как любые спортсмены-чемпионы, пользуются всеобщим уважением — для многих участников «красный ник» сам по себе служит достаточным стимулом для борьбы.


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

        Крупные соревнования проводят Mail.ru, Яндекс, Facebook, Google и другие компании. Например, в первом раунде текущего турнира Google Code Jam участвовало 20 тысяч человек. Тысяча лучших получили фирменные футболки, 25 — поедут на финал, который в этом году пройдет в Дублине.

        Помимо Google Code Jam, Google проводил еще один турнир — Hash Code, финал которого в проходил в головном офисе компании. Участникам, в частности, выдавались планы зданий, которые нужно было максимально покрыть сетью Wi-Fi-точек, используя как можно меньше роутеров и проводов. Оптимального решения у такой задачи не существует, но решить ее лучше других, конечно, возможно.


        Одним из зданий, в котором организаторы Google Hash Code предлагали расставить роутеры, была парижская Гранд-Опера.

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


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

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

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

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

        Тренировки


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

        Несколько раз в год проходят сборы, на которые собираются команды из разных стран. В частности, в Петрозаводск в этом году приезжали сильные поляки, а в МФТИ в Долгопрудном — программисты из Китая и Австралии. На сборах мы полторы недели разбираем новые, специально подготовленные задачи, в том числе, привезенные другими командами.

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

        Соперники


        Самые сильные команды стабильно собирают вузы из России, Польши, Китая, Кореи и Японии. Временами удачные составы подбираются у одного из западно-европейских или американских университетов, но в целом там спортивным программированием явно интересуются меньше. Восточная Европа и Азия доминируют и в индивидуальных турнирах, например, на Codeforces. По количеству участников на многих соревнованиях первой постоянно оказывается Индия, однако чаще всего результаты их представители показывают не самые высокие. Хотя в решении проблем дизайна индийцы как раз сильны — это проявляется на cпециальных турнирах на Topcoder.


        Школьная сборная США сезона 2017/18. Успеха в спортивном программировании в Америке в основном достигают молодые люди с азиатскими корнями.

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


        В этом году в десятку лучших на международной Олимпиаде вошла всего одна команда из Западной Европы — студенты Королевского технологического института из Стокгольма.

        Успехи России выглядят вполне объяснимыми, поскольку здесь — и очень сильная математическая школа, и сложившаяся тусовка олимпиадников, готовая помочь начинающим. В России, помимо ИТМО, очень сильные команды представляют СПбГУ (чемпионы прошлого года), МГУ, московский Физтех, вузы Екатеринбурга и Саратова, хотя время от времени хорошие составы удается собрать и другим университетам.


        На фото еще одна команда ИТМО: Артем Васильев и Бориса Минаев и Геннадий Короткевич — чемпионы мира 2015 года. Кубки Международной олимпиады по программированию не переходящие — теперь в ИТМО хранится уже семь.

        Комментарии (0)

          Let's block ads! (Why?)

          пятница, 14 июля 2017 г.

          [Перевод] Реверс-инжиниринг одной строчки JavaScript

          Несколько месяцев назад я получил от друга такое письмо:

          Тема: Можешь развернуть и объяснить мне эту одну строчку кода?

          Текст:Считай меня тупым, но… я не понимаю её и буду благодарен, если растолкуешь подробно. Это трассировщик лучей в 128 символах. Мне кажется, он восхитительный.

          
          



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


          Вызов принят!

          Часть I. Извлекаем читаемый код


          Первым делом я оставил HTML в HTML, код JavaScript перенёс в файл code.js, а p закавычил в id="p".
          index.html

          Я заметил, что там переменная k — просто константа, так что убрал её из строчки и переименовал в delay.
          code.js
          var delay = 64;
          var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
          var n = setInterval(draw, delay);
          

          Далее, var draw был просто строкой, которая исполнялась как функция eval с периодичностью setInterval, поскольку setInterval может принимать и функции, и строки. Я перенёс var draw в явную функцию, но сохранил изначальную строку для справки на всякий случай.
          Ещё я заметил, что элемент p в действительности ссылался на элемент DOM с идентификатором p, объявленным в HTML, который я недавно закавычил. Оказывается, на элементы в JavaScript можно ссылаться по их идентификатору, если id состоит только из букв и цифр. Я добавил document.getElementById("p"), чтобы сделать код понятнее.
          var delay = 64;
          var p = document.getElementById("p"); // < --------------
          // var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
          var draw = function() {
              for (n += 7, i = delay, P = 'p.\n'; i -= 1 / delay; P += P[i % 2 ? (i % 2 * j - j + n / delay ^ j) & 1 : 2]) {
                  j = delay / i; p.innerHTML = P;
              }
          };
          var n = setInterval(draw, delay);
          

          Затем я объявил переменные i, p и j и перенёс их в начало функции.
          var delay = 64;
          var p = document.getElementById("p");
          // var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
          var draw = function() {
              var i = delay; // < ---------------
              var P ='p.\n';
              var j;
              for (n += 7; i > 0 ;P += P[i % 2 ? (i % 2 * j - j + n / delay ^ j) & 1 : 2]) {
                  j = delay / i; p.innerHTML = P;
                  i -= 1 / delay;
              }
          };
          var n = setInterval(draw, delay);
          

          Я разложил цикл for и преобразовал его в цикл while. Из трёх частей прежнего for осталась только одна часть CHECK_EVERY_LOOP, а всё остальное (RUNS_ONCE_ON_INIT; DO_EVERY_LOOP) перенёс за пределы цикла.
          var delay = 64;
          var p = document.getElementById("p");
          // var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
          var draw = function() {
              var i = delay;
              var P ='p.\n';
              var j;
              n += 7;
              while (i > 0) { // <----------------------
                  //Update HTML
                  p.innerHTML = P;
           
                  j = delay / i;
                  i -= 1 / delay;
                  P += P[i % 2 ? (i % 2 * j - j + n / delay ^ j) & 1 : 2];
              }
          };
          var n = setInterval(draw, delay);
          

          Здесь я развернул троичный оператор ( condition ? do if true : do if false) in P += P[i % 2 ? (i % 2 * j - j + n / delay ^ j) & 1 : 2];.
          i%2 проверяет, является переменная i чётной или нечётной. Если она четная, то просто возвращает 2. Если нечётная, то возвращает «магическое» значение magic (i % 2 * j - j + n / delay ^ j) & 1; (подробнее об этом чуть позже).
          Это значение (индекс) используется для сдвига строки P, так что назовём index и превратим строку в P += P[index];.
          var delay = 64;
          var p = document.getElementById("p");
          // var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
          var draw = function() {
              var i = delay;
              var P ='p.\n';
              var j;
              n += 7;
              while (i > 0) {
                  //Update HTML
                  p.innerHTML = P;
           
                  j = delay / i;
                  i -= 1 / delay;
           
                  let index;
                  let iIsOdd = (i % 2 != 0); // <---------------
           
                  if (iIsOdd) { // <---------------
                      index = (i % 2 * j - j + n / delay ^ j) & 1;
                  } else {
                      index = 2;
                  }
           
                  P += P[index];
              }
          };
          var n = setInterval(draw, delay);
          

          Я разложил & 1 из значения index = (i % 2 * j - j + n / delay ^ j) & 1 в ещё один оператор if.
          Здесь хитрый способ проверки на чётность результата в круглых скобках, когда для чётного значения возвращается 0, а для нечётного — 1. & — это побитовый оператор AND. Он работает так:
          • 1 & 1 = 1
          • 0 & 1 = 0

          Следовательно, something & 1 преобразует "something" в двоичное представление, а также добивает перед единицей необходимое количество нулей, чтобы соответствовать размеру "something", и возвращает просто результат AND последнего бита. Например, 5 в двоичном формате равняется 101, так что если мы применим на ней логическую операцию AND с единицей, то получится следующее:
              101
          AND 001
              001
          

          Другими словами, пятёрка — нечётное число, а результатом 5 AND 1 (5 & 1) будет 1. В консоли JavaScript легко проверить соблюдение этой логики.
          0 & 1 // 0 - even return 0
          1 & 1 // 1 - odd return 1
          2 & 1 // 0 - even return 0
          3 & 1 // 1 - odd return 1
          4 & 1 // 0 - even return 0
          5 & 1 // 1 - odd return 1
          

          Обратите внимание, что я также переименовал остальную часть index в magic, так что код с развёрнутым &1 будет выглядеть следующим образом:
          var delay = 64;
          var p = document.getElementById("p");
          // var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
          var draw = function() {
              var i = delay;
              var P ='p.\n';
              var j;
              n += 7;
              while (i > 0) {
                  //Update HTML
                  p.innerHTML = P;
           
                  j = delay / i;
                  i -= 1 / delay;
           
                  let index;
                  let iIsOdd = (i % 2 != 0);
           
                  if (iIsOdd) {
                      let magic = (i % 2 * j - j + n / delay ^ j);
                      let magicIsOdd = (magic % 2 != 0); // &1 < --------------------------
                      if (magicIsOdd) { // &1 <--------------------------
                          index = 1;
                      } else {
                          index = 0;
                      }
                  } else {
                      index = 2;
                  }
           
                  P += P[index];
              }
          };
          var n = setInterval(draw, delay);
          

          Далее я развернул P += P[index]; в оператор switch. К этому моменту стало понятно, что index может принимать только одно из трёх значений — 0, 1 или 2. Также понятно, что переменная P всегда инициализируется со значениями var P ='p.\n';, где 0 указывает на p, 1 указывает на ., а 2 указывает на \n — символ новой строки
          var delay = 64;
          var p = document.getElementById("p");
          // var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
          var draw = function() {
              var i = delay;
              var P ='p.\n';
              var j;
              n += 7;
              while (i > 0) {
                  //Update HTML
                  p.innerHTML = P;
           
                  j = delay / i;
                  i -= 1 / delay;
           
                  let index;
                  let iIsOdd = (i % 2 != 0);
           
                  if (iIsOdd) {
                      let magic = (i % 2 * j - j + n / delay ^ j);
                      let magicIsOdd = (magic % 2 != 0); // &1
                      if (magicIsOdd) { // &1
                          index = 1;
                      } else {
                          index = 0;
                      }
                  } else {
                      index = 2;
                  }
           
                  switch (index) { // P += P[index]; <-----------------------
                      case 0:
                          P += "p"; // aka P[0]
                          break;
                      case 1:
                          P += "."; // aka P[1]
                          break;
                      case 2:
                          P += "\n"; // aka P[2]
                  }
              }
          };
           
          var n = setInterval(draw, delay);
          

          Я разобрался с оператором var n = setInterval(draw, delay);. Метод setInterval возвращает целые числа, начиная с единицы, увеличивая значение при каждом вызове. Это целое число может использоваться для clearInterval (то есть для отмены). В нашем случае setInterval вызывается всего один раз, а переменная n просто установилась в значение 1.
          Я также переименовал delay в DELAY для напоминания, что это всего лишь константа.
          И последнее, но не менее важное, я поместил круглые скобки в i % 2 * j - j + n / DELAY ^ j для указания, что у ^ (побитового XOR) меньший приоритет, чем у операторов %, *, , + и /. Другими словами, сначала выполнятся все вышеупомянутые вычисления, а уже потом ^. То есть получается (i % 2 * j - j + n / DELAY) ^ j).
          Уточнение: Мне указали, что я ошибочно поместил p.innerHTML = P; //Update HTML в цикл, так что я убрал его оттуда.
          const DELAY = 64; // approximately 15 frames per second 15 frames per second * 64 seconds = 960 frames
          var n = 1;
          var p = document.getElementById("p");
          // var draw = "for(n+=7,i=delay,P='p.\\n';i-=1/delay;P+=P[i%2?(i%2*j-j+n/delay^j)&1:2])j=delay/i;p.innerHTML=P";
           
          /**
           * Draws a picture
           * 128 chars by 32 chars = total 4096 chars
           */
          var draw = function() {
              var i = DELAY; // 64
              var P ='p.\n'; // First line, reference for chars to use
              var j;
           
              n += 7;
           
              while (i > 0) {
           
                  j = DELAY / i;
                  i -= 1 / DELAY;
           
                  let index;
                  let iIsOdd = (i % 2 != 0);
           
                  if (iIsOdd) {
                      let magic = ((i % 2 * j - j + n / DELAY) ^ j); // < ------------------
                      let magicIsOdd = (magic % 2 != 0); // &1
                      if (magicIsOdd) { // &1
                          index = 1;
                      } else {
                          index = 0;
                      }
                  } else {
                      index = 2;
                  }
           
                  switch (index) { // P += P[index];
                      case 0:
                          P += "p"; // aka P[0]
                          break;
                      case 1:
                          P += "."; // aka P[1]
                          break;
                      case 2:
                          P += "\n"; // aka P[2]
                  }
              }
              //Update HTML
              p.innerHTML = P;
          };
           
          setInterval(draw, 64);
          

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

          Часть 2. Понимание кода


          Так что здесь происходит? Давайте разберёмся.
          Изначально значение i установлено на 64 посредством var i = DELAY;, а затем каждый цикл оно уменьшается на 1/64 (0,015625) через i -= 1 / DELAY;. Цикл продолжается, пока i больше нуля (код while (i > 0) {). Поскольку за каждый проход i уменьшается на 1/64, то требуется 64 цикла, прежде чем оно уменьшится на единицу (64/64 = 1). В целом уменьшение i произойдёт 64×64 = 4096 раз, чтобы уменьшиться до нуля.
          Изображение состоит из 32 строк, со 128 символами в каждой. Очень удобно, что 64 × 64 = 32 ×128 = 4096. Значение i может быть чётным (не нечётным let iIsOdd = (i % 2 != 0);), если i является строго чётным числом. Такое произойдёт 32 раза, когда оно равняется 64, 62, 60 и т. д. Эти 32 раза index примет значение 2 index = 2;, а к строке добавится символ новой строки: P += "\n"; // aka P[2]. Остальные 127 символов в строке примут значения p или ..
          Но когда устанавливать p, а когда .?
          Ну, для начала нам точно известно, что следует установить . при нечётном значении let magic = ((i % 2 * j - j + n / DELAY) ^ j);, или установить p, если «магия» чётная.
          var P ='p.\n';
           
          ...
           
          if (magicIsOdd) { // &1
              index = 1; // second char in P - .
          } else {
              index = 0; // first char in P - p
          }
          

          Но когда magic чётное, а когда нечётное? Это вопрос на миллион долларов. Перед тем как перейти к нему, давайте определим ещё кое-что.
          Если убрать + n/DELAY из let magic = ((i % 2 * j - j + n / DELAY) ^ j);, то получится статическая картинка, на которой вообще ничего не двигается:

          Теперь посмотрим на magic без + n/DELAY. Как получилась эта красивая картинка?
          (i % 2 * j - j) ^ j
          Обратите внимание, что получается в каждом цикле:
          j = DELAY / i;
          i -= 1 / DELAY;
          

          Другими словами, мы может выразить j через конечное i как j = DELAY/ (i + 1/DELAY). Но поскольку 1/DELAY слишком малое число, то для этого примера можно отбросить + 1/DELAY и упростить выражение до j = DELAY/i = 64/i.
          В таком случае мы можем переписать (i % 2 * j - j) ^ j как i % 2 * 64/i - 64/i) ^ 64/i.
          Используем онлайновый графический калькулятор для отрисовки графиков некоторых из этих функций.
          Прежде всего, отрисуем i%2.
          Выходит симпатичный график со значениями y от 0 до 2.

          Если отрисовать 64/i, то получим такой график:

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

          В конце концов, если мы отрисуем две функции рядом друг с другом, то увидим следующее.

          О чём говорят эти графики?


          Давайте припомним вопрос, на который мы пытаемся ответить, то есть каким образом получилась такая красивая статическая картинка:

          Мы знаем, что если «магия» (i % 2 * j - j) ^ j принимает чётное значение, то нужно добавить p, а для нечётного числа нужно добавить ..
          Увеличим первые 16 строк нашего графика, где i имеет значения от 64 до 32.

          Побитовый XOR в JavaScript отбросит все значения справа от запятой, так что это равнозначно применению метода Math.floor, который округляет число в меньшую сторону.
          Он вернёт 0, если оба бита равны 1 или оба равны 0.
          Наша j начинается с единицы и медленно продвигается к двойке, останавливаясь прямо около неё, так что можем считать её всегда единицей при округлении в меньшую сторону (Math.floor(1.9999) === 1), и нам нужна ещё одна единица с левой стороны, чтобы получить в результате ноль и дать нам p.
          Другими словами, каждая зелёная диагональ представляет собой один ряд в нашем графике. Поскольку для первых 16 рядов значение j всегда больше 1, но меньше 2, то мы можем получить нечётное значение только в том случае, если левая сторона выражения (i % 2 * j - j) ^ j, она же i % 2 * i/64 — i/64, то есть зелёная диагональ, тоже будет выше 1 или ниже −1.
          Вот некоторые результаты из консоли JavaScript, чтобы посмотреть результаты вычислений: 0 или −2 означают, что результат чётный, а 1 соответствует нечётному числу.
          1 ^ 1 // 0 - even p
          1.1 ^ 1.1 // 0 - even p
          0.9 ^ 1 // 1 - odd .
          0 ^ 1 // 1 - odd .
          -1 ^ 1 // -2 - even p
          -1.1 ^ 1.1 // -2 - even p
          

          Если посмотреть на наш график, то там самая правая диагональная линия едва выходит выше 1 и ниже −1 (мало чётных чисел — мало символов p). Следующая выходит чуть дальше за эти границы, третья — ещё чуть дальше и т. д. Линия номер 16 едва удерживается в границах между 2 и −2. После линии 16 мы видим, что наш статический график меняет свой характер.

          После 16-й строки значение j пересекает лимит 2, так что меняется ожидаемый результат. Теперь мы получим чётное число, если зелёная диагональная линия выше 2 или ниже −2, или внутри рамок 1 и −1, но не соприкасается с ними. Вот почему мы видим на картинке две или больше групп символов p начиная с 17-й строки.
          Если присмотреться к нескольким самым нижним линиям в анимированной картинке, то вы заметите, что они не следуют одному и тому же шаблону из-за большой флуктуации графика.
          Теперь вернёмся к + n/DELAY. В коде мы видим, что значение n начинается с 8 (1 от setInteval и плюс 7 на каждый вызов метода). Затем оно увеличивается на 7 при каждом срабатывании setInteval.
          После достижения значения 64 график изменяется следующим образом.

          Обратите внимание, что j по-прежнему находится около единицы, но теперь левая половина красной диагонали в пределах примерно 62-63 находится примерно около нуля, а правая половина в пределах примерно 63-64 — около единицы. Поскольку наши символы появляются в убывающем порядке от 64 к 62, то можно ожидать, что правая половина диагонали в районе 63-64 (1 ^ 1 = 0 // even) добавит кучку символов p, а левая половина диагонали в районе 62-63 (1 ^ 0 = 1 // odd) добавит кучку точек. Всё это будет нарастать слева направо, как обычный текст.
          Рендеринг HTML для такого условия выглядит следующим образом (вы можете жёстко вбить значение n в редакторе CodePen и посмотреть). Это совпадает с нашими ожиданиями.

          К этому моменту количество символов p выросло до постоянной величины. Например, в первом ряду половина всех значений всегда будут чётными. Теперь символы p и . будут только меняться местами.
          Для примера, когда n увеличивается на 7 на следующем вызове setInterval, график немного изменится.

          Обратите внимание, что диагональ для первого ряда (около отметки 64) сдвинулась примерно на один маленький квадратик вверх. Поскольку четыре больших квадратов представляют собой 128 символов, в одном большом квадрате будет 32 символа, а в одном маленьком квадрате 32/5 = 6,4 сивола (примерно). Если посмотрим на рендеринг HTML, то там первый ряд действительно сдвинулся вправо на 7 символов.

          И один последний пример. Вот что происходит, если вызвать setInterval ещё семь раз, а n будет равняться 64+9×7.

          Для первого ряда j по-прежнему равняется 1. Теперь верхняя половина красной диагонали около отметки 64 примерно упирается в два, а нижний конец около единицы. Это переворачивает картинку в другую сторону, поскольку теперь 1^2 = 3 // odd - . и 1 ^ 1 = 0 //even - p. Так что можно ожидать кучу точек, за которыми пойдут символы p.
          Выглядеть это будет так.

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

          Комментарии (0)

            Let's block ads! (Why?)

            Цикл стартапа: как (в общем) работает венчурное инвестирование

            [Из песочницы] Измеряя Telegram

            «Пока что возможности по полноценной аналитике каналов
            ограничены, в первую очередь, возможностями BotAPI Telegram»

            канал «Телеграм-маркетинг», 28 июня 2016

            Всё хорошо c каналами в Телеграме, кроме одного — их слишком сложно искать. Ссылки есть практически везде,…
            Например:
            В интернете:

            1. С помощью роботов, индексирующих одни каналы в поисках других каналов (1.1, 1.2)
            2. В каталогах каналов, пополняемых владельцами каналов (2.1, 2.2)
            3. На биржах каналов (3.1, 3.2)
            4. В тематических подборках каналов (сюда тоже залетало: 4.1, 4.2)
            5. В списках каналов (5.1)
            6. В Гугл Доке с каналами о каналах (взял из @raskruti: 6.1)

            В мобильном приложении:

            7. Скачав приложение с каталогом каналов (под iOS: TeleBots)

            В самом Телеграме:

            8. На каналах о каналах (8.1)
            9. Используя ботов для каналов (9.1)
            10. На каналах о каналах о каналах (10.1)


            … но процесс поиска остаётся далек от совершенства. Без единого источника данных и нормального аналитического инструментария сложно не просто найти, но даже понять:
            1. Насколько каналы распространены в России?
            2. Насколько каналы пользуются популярностью и что такое «популярность» в числах?

            На такие вопросы нужно отвечать цифрами. В интернете удаётся разыскать только разрозненные данные. Что-то есть на Rusbase (тут), в Ведомостях (тут), в Твиттере (тут), но вся эта информация получена не систематически и сложно верифицируема.

            Данная статья посвящена анализу рынка русскоязычных Телеграм-каналов. Проделана работа от сбора обобщенного (не обязательно полного) списка русскоязычных каналов до кроулинга их содержимого и построения метрик. Анализировались только те каналы, которые продвигаются в большом интернете, указывая ссылки на себя. Такое поведение – индикатор каналов, создаваемых для большой аудитории.


            Шаг 1. Сбор списка каналов


            Для начала создадим единую базу данных методами партизанской аналитики. В качестве источников данных были выбраны два онлайн-каталога, в которые пользователи заносят свои каналы вручную: tlgrm.ru и tchannels.me, и два самопополняющихся каталога: tsear.ch и inten.to (оба, кстати, созданные нашими соотечественниками (одни и вторые -> раздел «О нас» внизу страницы), один переиндексируется вручную, другой – в реальном времени. На всех сайтах будем искать каналы, явно заявленные как «на русском языке». Короткая таблица для сравнения источников внизу.

            image

            Что позволит нам быть уверенными, что найденные каналы именно на русском?

            1. tlgrm.ru по умолчанию создан для каналов на русском языке
            2. tchannels.me состоит из каналов, добавленных вручную и явно указанных как русские (два языка для канала там указать нельзя by design)
            3. tsear.ch определяет язык канала на основе анализа его содержимого с помощью API Яндекс.Переводчика
            4. inten.to – это изначально unified API для переводческих сервисов, который как раз используется для автоматического определения языка каналов, скорее всего ребята шарят

            Нам понадобятся: Chrome Developer Tools, cURL to Python конвертер, сам Python, общая эрудиция.

            Найдены более 10,000 каналов, результаты — одной картинкой.

            image

            Немного технических деталей о том, как достать данные.

            Каталоги, пополняемые вручную

            1. tlgrm.ru Целевой раздел: /channels/. Для получения полного списка нужно обойти все релевантные категории и «списать» IDшники каналов, они будут прямо в теле страницы.

            2. tchannels.me Можно выбрать русский в настройках каталога и проскроллить 27 категорий одну за другой. А можно использовать API сервиса в своих интересах, немного изменив параметры: http://ift.tt/2tTxogn

            Автоматически пополняемые каталоги

            3. tsear.ch Целевой раздел: /list/ru/. Раз за разом кликая на кнопку Next можно записать каналы с каждой страницы и составить общий список.

            4. inten.to Целевой раздел: /telegram/channels/russian/. Эти ребята очень беспокоятся за сохранность своей базы: канал можно найти либо по полному совпадению запроса c ID канала, либо по частичному совпадению с текстом его описания, при этом поиск выдаёт не более 100 первых совпадений, и то не сразу всю сотню, а по 10 штук. Испробованы метод «в лоб», и поиск перебором сочетаний из трёх букв, но таким образом было найдено всего 1722 канала. Это точно не все, так как вручную собранный по этим данным рейтинг с оригинальным рейтингом inten.to не совпал.
            Можно было бы плюнуть, но перед уходом решил кое-что проверить: собрал список уникальных каналов из остальных трёх источников (8283 канала) и постарался найти их в inten.to (по точному совпадению ведь работает, не так ли?). Результат: найдены 3325 из 8283 (~40%).

            Шаг 2. Разработка инструмента сбора статистики


            Никто в здравом уме не будет руками обходить все 10,000 каналов – нет времени. К тому же, каталоги постоянно обновляются – тут нужен инструмент для регулярного использования. Инженер всегда выбирает то, с чем привык работать, и так как у меня уже были какие-то наработки в «прокачке» веб-браузера, я решил пойти мне известным, но не самым тривиальным путём – автоматизировать веб-клиент Телеграма с помощью … расширения к браузеру. Да, да, расширения к браузеру.

            Мои аргументы в защиту этого способа:

            1. Достаточность для решения задачи: расширение позволяют делать javascript инъекции и даёт удобный доступ к коду страницы с помощью JQuery
            2. Наглядность работы: скрипт, который за тебя кликает и вбивает текст на твоих глазах понятно как допиливать
            3. Принципиальная масштабируемость: установив кроулер-агент на пять виртуальных машин и развернув где-то сервер, раздающий агентам «задания» на кроулинг, можно получить ботнет (в расширениях есть возможность отправлять реквесты наружу, раздачу можно сделать через webhook)
            4. Простая и быстрая установка: кроулер устанавливается на компьютер любой домохозяйки, код можно оставить в наследство внукам
            5. Кросс-платформенность: Гугл Хром есть под все распространенные операционные системы
            6. VPN за три копейки: благодаря распространенности соответствующих расширений к браузеру (Hola, frigate, и т.д.)

            Такой подход по сути превращает кроулинг в извращенное (без Selenium’а) написание GUI автотестов. Своими глазами посмотреть процесс кроулинга можно на видео:

            Забегая вперёд скажу, что подход оказался не универсальный, так как у веб-клиента Телеграма течёт память. Когда я пытался загрузить всю историю канала с флудом (скроллил вверх), все посты кешировались, и браузер начинал ужасно тормозить. Вместо того, чтобы фиксить клиент (код до webpack’а), поднимать свой инстанс и там кроулить, я решил не идти в гору и просто исключил каналы с флудом из рассмотрения.

            Шаг 3. Масштабирование процесса


            Чтобы попасть в Телеграм нужно пройти авторизацию по СМС. В наше время это, казалось бы, уже не требует человека: можно купить номеров на twilio и использовать SMS API, но у меня не сработало. Поэтому я сделал в лоб: отправился на Белорусский вокзал и с рук купил 15 сим-карт. Зарядив все ненужные телефоны, что были в квартире, и подняв пару виртуалок на домашней тачке, я приступил к сбору данных.

            Шаг 4. Сбор статистики изнутри Телеграма, обсуждение результатов


            Основные закономерности рассмотрим на выборке каналов с сайта tlgrm.ru. Мои аргументы в пользу этого источника:

            1. Всего 13% уникальных каналов, то есть большинство из них содержатся ещё где-то – владельцы каналов озабочены раскруткой и заняты делом
            2. Наличие категорий в каталоге – будет более интересная и точная аналитика
            3. Русский домен + популярность в рунете = каналы будут русские в 99% случаев, можно не перепроверять.

            Для исследования выберем почти все категории каналов, кроме каналов с контентом 18+ и каналов на узбекском – первые ни о чём и один флуд, а вторые в среднем более раскрученные, поэтому их присутствие «сдвинет» нам статистику вверх.

            Далее, разбивая список всех каналов на кванты по ~500 штук (после 500 Телеграм забанит за слишком частый вызов метода search) и запуская в разных виртуалках кроулеры (обожаю high performance computing), соберем статистику за период от последнего поста на канале вглубь как минимум на неделю. Если на канале не было постов уже семь дней – считаем канал «мёртвым». Если посты идут чаще, чем раз в три часа – это флудильня. Далее следуют слайды с результатами и обсуждение в формате F.A.Q.

            image

            Каков размер моей выборки?
            Я делал три замера: 3 мая, 4 июня и 23-го июня. Каналов в интересующих нас категориях, присутствовавших всё это время в базе tlgrm.ru, насчитал почти 1,889 штук.

            Создаются ли новые каналы?
            Да, постоянно. Если судить по датам создания каналов, каждый день появляется как минимум 3-4 канала.

            Читают ли эти каналы и как активно?
            Да, читают, достаточно активно. Более 70% каналов «выросли» за последние 2 месяца, суммарный прирост в подписках – почти 900 тысяч (я отфильтровал от этой цифры подозрительно быстро растущие каналы, о них далее), при этом всего 60 тысяч «отписались» от каналов, на которых однажды побывали.

            Сколько каналов являются «нормальными»?
            65% каналов регулярно обновляются и не похоже, чтобы они сильно флудили. С другой стороны, каждый третий произвольно взятый канал либо давно не обновлялся, либо с флудом.

            image

            Что такое «успех» в Телеграме?
            Как следует из графика выше успех — это когда твой канал растёт со скоростью 160 человек в день или больше. Даже лучшие каналы не смогли показать среднюю скорость выше, чем это значение. Советую обратить внимание на музыкальные каналы: их сравнительно мало, но у них много подписчиков. Можно попробовать подумать в этом направлении. При этом особой популярностью пользуются каналы с гифками и смешными видосами: если верить статистике, то за два месяца они выросли больше любого другого канала минимум в четыре раза.

            Резюме


            Цифры указаны на графиках, качественные выводы привожу списком ниже.

            По этапу сбора списка каналов

            1. Каналов на русском языке более десяти тысяч – это очень много, за жизнь не перечитать
            2. Каждый из каталогов ведёт свой маленький список и не обменивается им с «соседями», что для пользователя означает необходимость искать сразу во многих местах – это очень неудобно

            По этапу анализа каналов

            1. Каналы как явление продолжают развиваться – их становится всё больше и больше каждый день
            2. Число подписок на каналы растёт чудовищными темпами, почти на полмиллиона каждый месяц
            3. Введение более сложных метрик позволяет генерировать автоматические подборки каналов, которые тем не менее нуждаются в человеческом review

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

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

            1. Обновил бы свой классификатор каналов – web.archive подсказывает, что он почти ни у кого не изменялся с момента создания. Сейчас лето, нанял бы студентов.
            2. Изменил бы интерфейс сайта, сделал бы «листалку» каналов в духе Яндекс.Музыки: для выбора нового канала понадобился бы максимум один клик.
            3. Начал бы следить за тем, как люди ищут каналы и мерить, сколько они могут просмотреть за сессию, девелоперам давал бы премии за превышение показателей.
            4. Связался бы с ребятами из inten.to и предложил бы им интеграцию – они на каналах всё равно не зарабатывают в прямую, а мне движок писать не хочется. Платил бы PRом и уважухой.

            Материалы, использованные в статье
            Все результаты (презентации, таблицы, код) выкладываю в открытый доступ.

            1. Презентация, спредшиты, списки каналов – архив на Гугл.Драйве
            2. Код расширения – репозиторий на Гитхабе
            3. Контактные данные в Телеграме: devrazdev

            Спасибо.

            P.S. Напоследок короткая смешная история про русских и Telegraph

            1. Идёте на telegra.ph
            2. Осваиваете механику: вбиваете во все поля test, нажимаете publish. У меня в своё время получилось так. Это значит, что мой пост был шестнадцатым постом с заголовком test в тот день (14-го июня).
            3. У вас случается прозрение
            4. Вы начинаете путешествовать во времени и подглядывать за другими такими же, как вы, кто тоже написал test в заголовке, просто меняя цифры. Можете, к примеру, перенестись в 20 января и случайно встретить родную душу.

            Комментарии (0)

              Let's block ads! (Why?)

              [Из песочницы] Решето Эратосфена, попытка минимизировать память

              Куда пойти, что читать, с кем общаться на профессиональные темы: дорожная карта для iOS-разработчика

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


              В целом: если вы хотите получать самые оперативные новости об iOS, подпишитесь на iOS Good Reads. Начинка телеграм-канала: архитектура, процессы, инструменты, новости, события, машинное обучение, холивары, безопасность. И все это — про нашу любимую ось. Не больше трех материалов в день, всегда в кармане. А теперь — об источниках, которые я изучаю, чтобы его наполнять.

              Email-рассылки


              MBLTdev Digest
              Единственная живая русскоязычная рассылка для iOS-разработчиков. Захватывают основные новости, статьи по разработке, иногда мелькает дизайн и бизнес. Уже 124 выпуска сделали.
              Авторы: Саша Черный, Руслан Гуменный, Иван Козлов.
              Периодичность: еженедельно, по пятницам.

              iOS Dev Weekly
              Самая известная iOS-рассылка в мире. Обязательна для подписки любому уважающему себя разработчику.
              Авторы: Dave Verwer и компания.
              Периодичность: еженедельно, по пятницам.

              This Week in Swift
              Все, что как-то связано со Swift — и мобильная разработка, и back-end, и вакансии.
              Автор: Natasha Murashev.
              Периодичность: еженедельно, по понедельникам.

              mokacoding
              Про различные виды тестирования в iOS и автоматизацию процессов. Много авторских материалов.
              Автор: Giovanni Lodi.
              Периодичность: еженедельно, по вторникам.

              Swift Weekly Brief
              Для тех, кому лень самостоятельно следить за развитием Swift на GitHub и в mailing list’е. Каждую неделю составляется подборка самых интересных предложений по изменениям, коммитов и pull request’ов в репозиторий языка.
              Автор: Jesse Squires.
              Периодичность: еженедельно, по четвергам.

              iOS Dev Tools Weekly
              Обзоры различных инструментов для разработки, дизайна, маркетинга, аналитики.
              Автор: Adam Swinden.
              Периодичность: нерегулярно.

              Чаты


              iOS Good Talks
              Обсуждение последних интересных статей и новостей, холивары, code review. Запрещены стикеры, мат и stackoverflow-style вопросы по разработке.
              Количество участников: 700+.

              Cocoa Developers Club
              Русскоязычное slack-коммьюнити iOS-разработчиков.
              Количество участников: 3.000.

              iOS Developers HQ
              Огромный slack англоговорящих разработчиков. Можно встретить много известных членов iOS-сообщества.
              Количество участников: 15.000+.

              Темы для холиваров


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

              Подкасты


              Podlodka Podcast
              Обсуждаются различные темы, так или иначе связанные с мобильной разработкой — архитектура, паттерны, библиотеки, процессы разработки в различных компаниях. Практически каждый выпуск — новый гость, известный в сообществе. Уже приходили Алексей Скутаренко, Алекс Денисов, Роман Бусыгин, Ксения Покровская.
              Авторы: Егор Толстой, Стас Цыганов, Глеб Новик.
              Периодичность: еженедельно, по понедельникам.

              Fatal Error
              Изменения в Swift, конференции, инструменты, библиотеки, архитектура. Хороший подкаст с хорошими ведущими.
              Авторы: Soroush Khanlou и Chris Dzombak.
              Периодичность: еженедельно, по понедельникам.

              Swift Unwrapped
              Подкаст, целиком и полностью посвященный Swift. Каждый выпуск подробно разбирается какая-то одна тема — обработка ошибок, тестирование, SourceKit.
              Авторы: JP Simmard, Jesse Squires.
              Периодичность: еженедельно, по понедельникам.

              События


              CocoaHeads Russia
              Ежемесячные митапы по iOS-разработке. В основном проходят в Москве, иногда захватывают Питер, Новосибирск, Екатеринбург и другие города. Темы абсолютно разные — начиная от неочевидных возможностей Swift, заканчивая менеджерскими рассказами. Уровень докладов тоже очень разный, но в целом качество доставляет.
              Организаторы: Александр Зимин, Станислав Жуковский.
              Периодичность: в Москве ежемесячно, в других городах нерегулярно.

              Avito.iOS
              Тематические митапы от компании Avito. Последний прошел в июне, были доклады про архитектуру, тестирование, рабочие процессы.
              Организаторы: Avito.
              Периодичность: 2-3 раза в год.

              Rambler.iOS
              Традиционно одни из лучших митапов Москвы, где подавляющая часть докладов готовится сотрудниками Rambler&Co. Много практики, мало воды.
              Организаторы: Rambler&Co.
              Периодичность: 2-3 раза в год.

              Mobius
              Серия питерских конференций по мобильной разработке. Осенью этого года решили перебраться в Москву. Если вы уже откладываете деньги с обедов, чтобы куда-нибудь поехать, Mobius в ноябре — отличный выбор. Качество докладов обычно на высоте, уровень сложности — выше среднего.
              Организаторы: JUG.ru.
              Периодичность: 2 раза в год.

              MBLTdev
              Еще одна осенняя конференция, проводиться будет уже в четвертый раз. Отбор спикеров не такой серьезный, как на Mobius, но всегда есть несколько отличных докладов, которые оправдывают цену за билет. Кстати, она обычно небольшая по сравнению с другими подобными событиями.
              Организаторы: E-Legion.
              Периодичность: раз в год.

              AppsConf
              Это, по сути, не отдельная конференция, а одна из секций РИТ. В этом году я на ней не был, но отзывы посетителей достаточно неплохие — было много крутых докладов, до этого не засветившихся нигде.
              Организатор: Олег Бунин.
              Периодичность: раз в год.

              Twitter-аккаунты


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

              Это все источники, о которых я хотел рассказать сегодня. Надеюсь, вы узнали что-то полезное для себя в эту пятницу. Если у читателей возникнет интерес — сделаю такую же дорожную карту по источникам об Android-разработке. Напоминаю, что анонсы тематических выступлений об iOS сотрудников Avito, мероприятий и конкурсов публикуются также в телеграм-канале AvitoTech, в одноименном твиттере и в группе на Facebook.

              Комментарии (0)

                Let's block ads! (Why?)

                [Перевод] На пути к Go 2

                Перевод блог поста и доклада Russ Cox с GopherCon 2017, с обращением ко всему Go сообществу помочь в обсуждении и планировании Go 2. Видео доклада будет добавлено сразу после опубликования.

                25 сентября 2007 года, после того как Роб Пайк, Роберт Грисмайер и Кен Томпсон несколько дней обсуждали идею создания нового языка, Роб предложил имя "Go".



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



                В следующие два года, с помощью нового open-source сообщества гоферов, мы экспериментировали и пробовали различные идеи, улучшая Go и ведя его к запланированному релизу Go 1, предложенному 5 октября 2011.



                С ещё более активной помощью Go сообщества, мы пересмотрели и реализовали этот план, в итоге выпустив Go 1 28 марта 2012.



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


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


                После того, как Go 1 был выпущен, мы знали, что мы должны провести какое-то время в реальном использовании Go в продакшн среде, для которой он и был создан. Мы явно перешли от изменений языка к использованию Go в наших проектах и улучшения реализации: мы портировали Go на множество новых систем, мы переписали практически все критичные к производительности части, чтобы сделать Go ещё более эффективным и добавили ключевые инструменты вроде race-детектора.


                На сегодня у нас есть 5 лет реального опыта использования Go для создания огромных, качественных продакшн-систем. Это дало нам чувство того, что работает, а что нет. И сейчас самое время начать новый этап в эволюции и развитии Go. Сегодня я прошу вас всех, сообщество Go разработчиков, будь вы сейчас тут в зале GopherCon или смотрите видео или читаете это в Go блоге, работать вместе с нами по мере того, как мы будем планировать и реализовывать Go 2.


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


                Задачи


                Задачи перед Go сегодня стоят точно такие же, какими были в 2007 году. Мы хотим сделать программистов более эффективными в управлении двумя видами масштабируемости: масштабируемости систем, особенно многопоточных(concurrent) систем, взаимодействующих со многими другими серверами — широко представленными в виде серверов для облака, и масштабируемость разработки, особенно большие кодовые базы, над которыми работают множество программистов, часто удалённо — как, например, современная open-source модель разработки.


                Эти виды масштабируемости сегодня присутствуют в компаниях всех размеров. Даже стартап из 5 человек может использовать большие облачные API сервисы, предоставленные другими компаниями и использовать больше open-source софта, чем софта, который они пишут сами. Масштабируемость систем и масштабируемость разработки также актуальны для стартапа, как и для Google.


                Наша цель для Go 2 — исправить основные недочёты в Go, мешающие масштабируемости.


                (Если вы хотите больше узнать про эти задачи, посмотрите статью Роба Пайка 2012 года “Go at Google: Language Design in the Service of Software Engineering” и мой доклад с GopherCon 2015 “Go, Open Source, Community”.)


                Препятствия


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


                Go 2 должен способствовать всем этим разработчикам. Мы должны просить их разучить старые привычки и выучить новые только если выгода от этого действительно того стоит. Например, перед Go 1, метод интерфейсного типа error назывался String. В Go 1 мы переименовали его в Error, чтобы отличить типы для ошибок от других типов, который просто могут иметь отформатированное строчное представление. Однажды я реализовывал тип, удовлетворяющий error интерфейсу, и, не думая, называл метод String, вместо Error, что, конечно же, не скомпилировалось. Даже через 5 лет я всё ещё не до конца разучил старый способ. Этот пример проясняющего переименования было важным и полезным изменением для Go 1, но был бы слишком разрушительным для Go 2 без действительно очень весомой причины.


                Go 2 должен также хорошо дружить с существующим Go 1 кодом. Мы не должны расколоть Go экосистему. Смешанные программы, в которых пакеты написаны на Go 2 и импортируют пакеты на Go 1 или наоборот, должны безпрепятственно работать в течении переходного периода в несколько лет. Нам ещё предстоит придумать, как именно этого достичь; инструментарий для автоматического исправления и статического анализа вроде go fix определённо сыграют тут свою роль.


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


                При этом я не считаю мелкие вспомогательные изменения, как, возможно, разрешение идентификаторов на большем количестве натуральных языков или добавления литералов для чисел в двоичной форме. Подобные мелкие изменения также важны, но их гораздо проще сделать правильно. Сегодня я буду концентрироваться на возможных крупных изменениях, таких как дополнительная поддержка обработки ошибок или добавление неизменяемых (immutable) или read-only значений, или добавления какой-нибудь формы generics, или ещё какой-нибудь важной пока не озвученной темы. Мы сможем сделать только несколько таких крупных изменений. И мы должны будем выбрать их очень внимательно.


                Процесс


                Это поднимает важный вопрос. Какой процесс разработки Go в целом?


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


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



                Первый шаг — использовать Go, чтобы наработать опыт работы с ним.


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


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


                Четвертый шаг — реализовать решение, проверить его и улучшить, основываясь на результатах проверки.


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


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


                И хотя я не думаю, что мы когда-либо рассказывали про этот процесс целиком, но мы объясняли его по частям. В 2012, когда мы выпустили Go 1 и сказали, что настало время начать использовать Go и перестать изменять, мы объясняли первый шаг. В 2015, когда мы представили изменения в процесс предложений (proposals) для Go, мы объясняли шаги 3, 4 и 5. Но мы никогда не объясняли второй шаг подробно, и я бы хотел сделать это сейчас.


                (Более подробно про разработку Go 1 и про прекращение изменений в языке, посмотрите доклад Роба Пайка и Эндрю Герранда на OSCON в 2012 году “The Path to Go 1.”. Более детально про процесс предложений можно посмотреть в докладе Эндрю Герранда на GopherCon в 2015 “How Go was Made” и в документации к самому процессу)


                Объяснение проблем



                Объяснение проблемы состоит из двух частей. Первая часть — лёгкая — это просто озвучить, в чём, собственно, проблема заключается. Мы, разработчики, в целом достаточно хорошо это умеем. В конце концов, каждый тест, который мы пишем это формулировка проблемы, которая должна быть решена, причём написанная на таком точном языке, который поймёт даже компьютер. Вторая часть — сложная — заключается в том, чтобы описать важность проблемы достаточно хорошо, чтобы все остальные поняли, почему мы должны тратить время на её решение и его поддержку. В отличие от точной формулировки проблемы, мы не так часто описываем их важность и мы не слишком это хорошо умеем. Компьютер никогда нас не спросит “Почему этот случай для теста важен? А ты уверен, что это именно та проблема, которую ты должен решать? Точно ли решение этой проблемы это самая важная задача, которой ты должен заниматься?”. Возможно, однажды так и будет, но точно не сегодня.


                Давайте взглянем на старый пример из 2011. Вот, что я написал про переименование os.Error в error.Value, когда мы планировали Go 1.


                error.Value
                (rsc) Проблема, которую мы имеем в низкоуровневых библиотеках заключается в том, что всё зависит от “os” из-за os.Error, поэтому сложно делать вещи, которые пакет os сам мог бы использовать (как пример с time.Nano ниже). Если бы не os.Error, не было было бы столько других пакетов, которые зависят от пакета os. Сугубо вычислительные пакеты вроде hash/* или strconv или strings или bytes могли бы обойтись без него, к примеру. Я планирую исследовать (пока что ничего не предлагая) определить пакет error примерно с таким API:

                package error
                type Value interface { String() string }
                func New(s string) Value

                Он начинается с краткой однострочной формулировки проблемы: в низкоуровневых библиотеках всё импортирует “os” ради os.Error. Далее идут 5 строк, которые я подчеркнул, описывающие значимость проблемы: пакеты, которые “os” использует не могут использовать тип error в своих API, и другие пакеты зависят от os по причинам никак не связанным с работой операционной системы.


                Убедят ли вас эти 5 строк, что проблема стоит внимания? Это зависит от того, насколько хорошо вы можете заполнить контекст, который я оставил за рамками: чтобы быть понятым, нужно уметь предугадать, что другие люди знают. Для моей аудитории в то время — десять других людей в команде Google работающей над Go, которые читали этот документ — этих 50 слов было достаточно. Чтобы представить ту же самую проблему аудитории на конференции GothamGo прошлой осенью — аудитории с гораздо более разнообразным опытом — я должен быть предоставить больше контекста, и я использовал уже 200 слов, плюс примеры реального кода и диаграмму. И это факт, что современное Go сообщество, которое пытается объяснить важность какой-либо проблемы, должно добавлять контекст, причём проиллюстрированный конкретными примерами, который можно было бы исключить в беседе с вашими коллегами, например.


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


                Пример: високосные секунды


                Мой первый пример связан с временем.


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


                    start := time.Now()       // 3:04:05.000
                    event()
                    end := time.Now()         // 3:04:05.010
                
                    elapsed := end.Sub(start) // 10 ms
                

                Эта очевидная процедура может не сработать во время “високосной секунды” (leap second). Когда наши часы не совсем точно синхронизированы с дневным вращением Земли, специальная високосная секунда — официально это секунды 23:59 и 60 — вставляется прямо перед полуночью. В отличие от високосного года, у високосных секунд нет легко предсказуемого паттерна, что затрудняет автоматизацию их учета в программах и API. Вместо того, чтобы ввести специальную, 61-ю, секунду, операционные системы обычно реализуют високосную секунду переводя часы на секунду назад аккурат перед полуночью, так что при этом 23:59 происходит дважды. Такой сдвиг часов выглядит, как поворот времени вспять, и наш замер 10-миллисекундного события теперь может оказаться отрицательным значением в 990 миллисекунд.


                    start := time.Now()       // 11:59:59.995
                    event()
                    end := time.Now()         // 11:59:59.005 (really 11:59:60.005)
                
                    elapsed := end.Sub(start) // –990 ms
                

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


                Только при этом нестандартном сдвиге часов, монотонные часы не особо лучше обычных часов, которые, в отличие от монотонных, умеют показывать текущее время. Поэтому, ради простоты API пакета time в Go 1 доступ есть только к обычным часам компьютера.


                В октябре 2015 появился баг-репорт о том, что Go программы некорректно возвращают длительность событий во время подобных сдвигов часов, особенно в случае с високосной секундой. Предложенное решение было также и заголовком репорта: “Добавить новый API для доступа к монотонным часам”. Тогда я утверждал, что проблема не была достаточно значима, чтобы ради неё создавать новый API. Несколько месяцев перед этим, для високосной секунды в середине 2015 года, Akamai, Amazon и Google научились замедлять свои часы таким образом, что эта дополнительная секунда “размазывалась” по всему дню и не нужно было переводить часы назад. Всё шло к тому, что повсеместное использовать этого подхода “размазывания секунды” позволило бы избавиться от перевода часов вообще и проблема исчезнет сама собой. Для контраста, добавление нового API в Go добавило бы две новые проблемы: мы должны были бы объяснять про эти два типа часов, обучать пользователей когда использовать какой из них и конвертировать массу существующего кода, и всё лишь для ситуации, когда очень редка и, скорее всего, вообще исчезнет сама.


                Мы поступили так, как делаем всегда, когда решение проблемы не очевидно — мы стали ждать. Ожидание даёт нам больше времени, чтобы накопить больше опыта и углубить понимание проблемы, плюс больше времени на поиски хорошего решения. В этом случае, ожидание добавило понимание серьёзности проблемы, в виде сбоя в работе Cloudflare, к счастью незначительного. Их Go код замеряющий длительность DNS запросов во время високосной секунды в конце 2016 года возвращал негативное значение, подобное примеру с -990 миллисекундами выше, и это приводило к панике на их серверах, поломав около 0.2% всех запросов в самом пике проблемы.


                Cloudflare это именно тот тип облачных систем, для которых Go и создавался, и у них случился сбой в продакшене из-за того, что Go не мог замерять время правильно. Дальше, и это ключевой момент тут, Cloudflare написали про свой опыт — Джон Грэхем-Камминг опубликовал блог-пост “Как и почему високосная секунда повлияла на DNS Cloudflare”. Рассказав конкретные детали и подробности инцидента и их опыт работы с Go, Джон и Cloudflare помогли нам понять, что проблема неточного замера во время високосной секунды была слишком важной, чтобы оставлять её не решенной. Через два месяца после публикации статьи, мы разработали и реализовали решение, которое появится в Go 1.9 (и, кстати, мы сделали это без добавления нового API).


                Пример: алиасы


                Мой второй пример о поддержке алиасов в Go.


                За последние несколько лет, Google собрал команду, сфокусированную на крупномасштабных изменениях в коде, вроде миграций API и исправлений багов по всей кодовой в базе, состоящей из миллионов файлов исходных кодов и миллиардов строк кода, написанных на C++, Go, Java, Python и других языках. Одна из вещей, которую я усвоил из их трудов, было то, что при замене в API старого имени на новое, важно иметь возможность делать изменения шаг за шагом, а не всё за один раз. Чтобы это сделать, должна быть возможность задекларировать, что под старым именем, подразумевается новое. В C++ есть #define, typedef и использование деклараций позволяют это сделать, но в Go такого механизма не было. И поскольку одной из главных задача перед Go стоит умение масштабироваться в больших кодовых базах, было очевидно, что нам нужен какой-то механизм перехода от старых имён к новым во время рефакторинга, и что другие компании также упрутся в эту проблему по мере роста их кодовых баз на Go.


                В марте 2016 я начал обсуждать с Робертом Грисмайером и Робом Пайком то, как Go мог бы справляться с многошаговым рефакторингом кодовых баз, и мы пришли к идее алиасов (alias declarations), которые были именно тем механизмом, что нужно. В тот момент я был очень доволен тем, как Go развивался. Мы обсуждали идею алиасов ещё с ранних дней Go — на самом деле первый черновик спецификации Go содержит пример, использующий алиасы — но, каждый раз при обсуждении алиасов, и, чуть позже, алиасов типов, мы не сильно понимали для чего они могут быть важны, поэтому мы отложили идею. Теперь же мы предлагали добавить алиасы в язык не потому что они были прямо элегентным концептом, а потому что они решали очень серьезную практическую проблему, к тому же помогающая Go лучше решать поставленную перед ним задачу масштабируемости разработки. Я искренне надеюсь это послужит хорошей моделью для будущих изменений в Go.


                Чуть позднее той же весной Роберт и Роб написали предложение, и Роберт предоставил его на коротком докладе (lightning talk) на GopherCon 2016. Следующие несколько месяцев были достаточно смутными, и точно не могут быть примером того, как делать изменения в Go. Один из многих уроков, который мы тогда вынесли была важность описания значимости проблемы.


                Минуту назад я объяснил вам суть проблемы, дав некоторую минимальную информацию о том, как и почему эта проблема может возникнуть, но не дав конкретных примеров о том, как вам вообще решить, коснётся ли эта проблема вас когда-нибудь или нет. То предложение и доклад оперировали абстрактными примерами, включающими пакеты C, L, L1 и C1..Cn, но ничего конкретного, с чем программисты могли ассоциировать проблему. В результате, большая часть ответной реакции от сообщества была основана на идее того, что алиасы решают проблему Google, и которая не актуальна для остальных.


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


                Осенью мы начали заново. Я выступил с докладом и написал статью, подробно объясняющую проблему, используя множество конкретных примеров из реальных open-source проектов, показывающих, что эта проблема актуальна для всех, а не только для Google. Теперь, после того как больше людей поняли проблему и могли оценить её важность, мы смогли начать продуктивное обсуждение о том, какое решение подойдёт лучше всего. Результатом этого стало то, что алиасы типов будут включены в Go 1.9 и помогут Go лучше масштабироваться во всё более крупных кодовых базах.


                Рассказы об опыте использования


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


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


                К примеру, недавно я изучал проблему дженериков (generics), и пока что я не вижу в голове чёткой картины подробного и детального примера проблемы, для решения которой пользователям Go нужны дженерики. Как результат, я не могу чётко ответить на вопрос о возможном дизайне дженериков — например, стоит ли поддерживать generic-методы, тоесть методы, которые параметризованы отдельно от получателя (receiver). Если бы у нас был большой набор реальных практических проблем, мы бы смогли отвечать на подобные вопросы, отталкиваясь от них.


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


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


                Эти статьи и рассказы будут служить сырым материалом для процесса подачи предложений для Go 2, и мы нуждаемся в вас, чтобы помочь понять нам ваш опыт с Go. Вас около полумиллиона, работающих в разных окружениях, и совсем немного нас. Напишите блог пост в своем блоге или на Medium, или Github Gist (добавив расширение .md для Markdown), или в Google doc, или любым другим удобным вам способом. Написав пост, пожалуйста, добавьте его в эту новую страницу Wiki: http://ift.tt/2tMZJ9F


                Решения



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


                Одна из проблем, которую мы, возможно, будем решать, это то, что компьютеры часто при базовых арифметических вычислениях выдают дополнительные результаты, но в Go нет прямого доступа к этим результатам. В 2013 Роберт предложил, что мы можем расширить идею двойных выражений (“comma-ok”) на арифметические операции. Например, если x и y, скажем, uint32 значения, lo, hi = x * y вернет не только обычные нижние 32 бита, но и верхние 32 бита умножения. Эта проблема не выглядела особо важной, поэтому мы записали потенциальное решение, но не реализовывали его. Мы ждали.


                Совсем недавно, мы разработали для Go 1.9 новый пакет math/bits, в котором находятся различные функции для манипулирования битами:


                    package bits // import "math/bits"
                
                    func LeadingZeros32(x uint32) int
                    func Len32(x uint32) int
                    func OnesCount32(x uint32) int
                    func Reverse32(x uint32) uint32
                    func ReverseBytes32(x uint32) uint32
                    func RotateLeft32(x uint32, k int) uint32
                    func TrailingZeros32(x uint32) int
                    ...
                

                Пакет содержит качественные реализации каждой функции, и компилятор задействует специальные инструкции процессоров, там где это возможно. Основываясь на опыте с math/bits мы оба, Роберт и я, пришли к выводу, что делать дополнительные результаты арифметических операций в виде изменения языка это не лучший путь, и, взамен, мы должны оформить их в виде функций в пакете вроде math/bits. Лучшим решением тут будет изменение в библиотеке, а не в языке.


                Другая проблема, которую мы могли бы хотеть решить после выхода Go 1 был факт того, что горутины и разделённая (shared) память позволяли слишком легко создать ситуацию гонки (races) в Go программах, приводящих к падениям и прочим проблемам в работе. Решение, основанное на изменении языка, могло бы заключаться в том, чтобы найти какой-то способ гарантировать отсутствие ситуаций гонки, сделать так, чтобы такие программы не компилировались, например. Как это сделать для такого языка, как Go пока что остается открытым вопросом в мире языков программирования. Вместо этого мы добавили базовый инструмент, который очень просто использовать — этот инструмент, детектор гонок (race detector) стал неотъемлемой частью опыта работы с Go. В этом случае наилучшим решением оказалось изменение в runtime и в инструментарии, а не изменение языка.


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


                Выпуск Go 2



                В заключение, как же мы будем выпускать Go 2?


                Я думаю, наилучший план будет выпускать обратно-совместимые части Go 2 постепенно, шаг за шагом, по ходу обычного плана релизов Go 1. У такого подхода есть несколько важных свойств. Во-первых, это сохраняет привычный график релизов Go 1, позволяя своевременно планировать исправления ошибок и улучшения, от которых зависят пользователи. Во-вторых, это позволяет избежать разделения усилий на Go 1 и Go 2. В-третьих, это спасает от расхождения между Go 1 и Go 2, облегчая в итоге всем жизнь при миграции. В-четвертых, это позволяет нам сконцентрироваться на работе над одним изменением за раз, что позволит сохранять качество. В-пятых, это будет заставлять нас выбирать решения, которые обратно-совместимы.


                Нам потребуется время и планирование перед тем, как какие-либо изменения вообще начнут попадать в релизы Go 1, но, вполне вероятно, что мы можем увидеть мелкие изменения уже где-то через год, в Go 1.12 или около того. Это также даст нам время завершить сначала проект с менеджментом зависимостей.


                Когда все обратно-совместимые изменения будут внедрены, допустим в Go 1.20, тогда мы сможем приступить к обратно-несовместимым изменениям в Go 2. Если окажется так, что не будет обратно-несовместимых изменений, то мы просто объявим, что Go 1.20 это и есть Go 2. В любом случае, на том этапе мы плавно перейдем от работы над Go 1.X релизами к Go 2.X, возможно с более продлённым окном поддержки для финальных Go 1.X релизов.


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


                Вы можете нам помочь


                Нам нужна ваша помощь


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


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


                Спасибо.


                Russ Cox, 13 июля 2017

                Комментарии (0)

                  Let's block ads! (Why?)