...

суббота, 8 августа 2020 г.

[Из песочницы] Изобретаем велосипед или пишем персептрон на С++. Часть 1 и 2

Напишем простую библиотеку для реализации персептрона на C++



Вступление


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

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

Итак, поехали.

Немного теории

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

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


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

Теперь рассмотрим работу каждого узла отдельно. Данная картинка как нельзя лучше передаёт смысл каждого нейрона:


Допустим, что в наш нейрон приходят три сигнала(х1, х2, х3), тогда для вычисления значения u нейрон складывает произведения входных сигналов на веса входов (w1, w2, w3), или проще говоря:
u = x1*w1 + x2*w2 + x3*w3
В более общем виде выражение записывается так:

Теперь поговорим об активационной функции нейрона. На рисунке она указана как y(u), где u – уже известная нам величина. Эта функция нужна для вычисления значения, которое будет на выходе нейрона и пойдёт на входы других нейронов.

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

Данное чудо ограничено диапазоном значений (0; 1), поэтому отлично нам подходит. А величина y(u) будет называться выходным значением нейрона.

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

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

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

Объясню идею на картинках:

Пусть у нас есть 8 нейронов в слое (с n1 по n8), а мы хотим хранить значения той самой суммы произведений u, вычисленного из сигмоиды значения «y(u)» и ошибки «err», тогда воспользуемся двумерным массивом (матрицей). Смысл поля «err» поясню в следующей части.

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

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

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

Ну вроде принципы хранения нужных значений в нейронах получилось объяснить. Теперь разберёмся с хранением весов связей между нейронами.

Возьмём для примера вот такую сеть:

.

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

Её структура совсем не сложная: например, значение веса между нейроном N1 и нейроном n1 содержится в ячейке w1-1, аналогично и с другими весами. Но опять же, такая матрица пригодна для хранения весов только между двумя первыми слоями, но ведь в сети есть ещё веса между вторым и третьим слоями. Воспользуемся уже знакомым приёмом – добавим новое измерение в массив, но с оговоркой: пускай названия строчек отображают слой нейронов слева относительно «пучка» весов, а слой нейронов справа вписывается в названия столбцов.

Тогда получим для второго «пучка» весов такую таблицу:

А всё пространство весов теперь будет выглядеть так:

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


И в заключение первой части


А на этом первая часть заканчивается, во второй части разберём программную реализацию.

В этой части программно реализуем идеи, описанные в прошлый раз.


Вступление

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

Итак, поехали!


Оформление header — файла

Дабы наш код можно было использовать в различных проектах, оформим его как библиотеку. Для этого создадим header — файл (пусть называется «neuro.h»). Внутри него опишем класс с основными методами:

class NeuralNet {
public:
    NeuralNet(uint8_t L, uint16_t *n);
    void Do_it(uint16_t size, double *data);
    void getResult(uint16_t size, double* data);
    void learnBackpropagation(double* data, double* ans, double acs, double k);
private:
    vector<vector<vector<double>>> neurons;
    vector<vector<vector<double>>> weights;
    uint8_t numLayers;
    vector<double> neuronsInLayers;
    double Func(double in);
    double Func_p(double in);
    uint32_t MaxEl(uint16_t size, uint16_t *arr);
    void CreateNeurons(uint8_t L, uint16_t *n);
    void CreateWeights(uint8_t L, uint16_t *n);

};

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


//строки ниже нужны, чтобы сказать предпроцессору о компиляции этого файла, если ранее он не был упомянут в коде
#ifndef NEURO_H
#define NEURO_H

#include <vector> //файл для работы с векторами
#include <math.h> //библиотека для работы с математикой, нужна для объявления активационной функции
#include <stdint.h> //эта библиотека позволит использовать более оптимизированные типы данных, что немного сократит объём выделяемой памяти для нашего не самого оптимизированного кода.

Разберёмся с публичными функциями класса:

NeuralNet(uint8_t L, uint16_t *n);

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

void Do_it(uint16_t size, double *data);

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

void getResult(uint16_t size, double* data);

Эта функция позволяет получить выходные данные с последнего слоя сети.

void learnBackpropagation(double* data, double* ans, double acs, double k);

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

На этом публичные методы класса заканчиваются, сейчас остановимся на приватных полях класса:


    vector<vector<vector<double>>> neurons; //трёхмерный вектор с нейронами, который мы описывали ранее
    vector<vector<vector<double>>> weights; //трёхмерный вектор с весами, его мы тоже описали в первой части
    uint8_t numLayers; //количество слоёв сети
    vector<double> neuronsInLayers; //вектор, хранящий количество нейронов на каждом слое
/*
Вообще это поле и предыдущее можно было бы и не объявлять, а брать количество слоёв и нейронов, исходя из размеров пространств весов и нейронов, но в этой статье мы не сильно затрагиваем вопросы оптимизации, этим займёмся позже
*/
    double Func(double in); // та самая активационная функция
    double Func_p(double in); // производная той самой активационной функции
    uint32_t MaxEl(uint16_t size, uint16_t *arr);// простенькая функция для поиска максимума в массиве
    void CreateNeurons(uint8_t L, uint16_t *n);// эту и следующую функции использует конструктор для разметки векторов с весами и нейронами
    void CreateWeights(uint8_t L, uint16_t *n);

Закончим header — файл строкой:

#endif

На этом завершим header и оставим его в покое. Переходим к самому вкусному — source — файлу).


Код внутри source — файла

Ссылка на весь код будет в конце, мы же разберём самые интересные его места.

Конструктор класса изнутри выглядит так:


NeuralNet::NeuralNet(uint8_t L, uint16_t *n) {
        CreateNeurons(L, n); //переразмечаем пространство нейронов
        CreateWeights(L, n); //переразмечаем пространство весов
        this->numLayers = L;
        this->neuronsInLayers.resize(L);
        for (uint8_t l = 0; l < L; l++)this->neuronsInLayers[l] = n[l]; //в последних трёх строках заполняем все переменные класса
}

Про функцию прямого распространения рассказать особо нечего, просто берём и считаем всё от слоя к слою:


void NeuralNet::Do_it(uint16_t size, double *data) {
        for (int n = 0; n < size; n++) { // тут вносим данные в нейроны первого слоя
                neurons[n][0][0] = data[n]; // нулевое место отвечает за хранение входного значения
                neurons[n][1][0] = Func(neurons[n][0][0]); // первое место отвечает за значение функции от входного в нейрон значения
        }
        for (int L = 1; L < numLayers; L++) { // а здесь от слоя к слою считаем входные значения каждого нейрона и значения их активационных функций
                for (int N = 0; N < neuronsInLayers[L]; N++) { 
                        double input = 0;
                        for (int lastN = 0; lastN < neuronsInLayers[L - 1]; lastN++) {// для каждого отдельного нейрона подсчитаем сумму его входов для отправки в активационную функцию
                                input += neurons[lastN][1][L - 1] * weights[lastN][N][L - 1];
                        }
                        neurons[N][0][L] = input;
                        neurons[N][1][L] = Func(input);
                }
        }
}

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


void NeuralNet::getResult(uint16_t size, double* data) {
        for (uint16_t r = 0; r < size; r++) {
                data[r] = neurons[r][1][numLayers - 1];
        }
}

Уход в закат

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

Опять же, жду ваши советы и замечания в комментах.

Спасибо за уделённое внимание к статье, до скорого!

P.S.: Как и обещал — ссылка на исходники: GitHub

Let's block ads! (Why?)

Издеваемся над USB


В очередной раз втыкая скоростную USB флешку в порт USB 3.0, я увидел надпись "Это устройство может работать быстрее...". Но подождите, я и так его воткнул в порт 3.0! Неужели контакт барахлит? И если так, то как флешка определяет, на какой скорости ей работать? Ведь современные ПК поддерживают целых три стандарта соединения — USB 1.1, 2.0 и 3.0. Можно ли «понизить» стандарт USB, насильно заставив устройство работать, к примеру, на USB 1.1? Не на все эти вопросы в сети удалось найти ответ, и я решил разобраться сам, по ходу столкнувшись с довольно неочевидными ситуациями.

Понижаем USB 3.0 до 2.0

Вы сейчас скажете — да что может быть проще, просто возьми USB 2.0 кабель — и будете совершенно правы. Если в кабеле или разъёме нет USB 3.0 контактов, у устройства не будет иного выхода, кроме как завестись на скорости USB 2.0:

Но отключение каких конкретно проводков приведёт к переключению на USB 2.0? Что будет, если отключить только один, или замкнуть соседние? Интересно же выйти за рамки стандарта и поэкспериментировать!
Для экспериментов я спаял USB-«маму» и USB-«папу» проводками на макетной плате:

На фотографии не просто так один проводок находится в воздухе. Выяснилось, что соединение прекрасно работает даже без одного из проводников SS_TX дифф. пары! (для пары SS_RX такой фокус уже не работает)

Более того, если отключить все USB 3.0 контакты, кроме SS_TX, девайс продолжает считать, что он подключен к USB 3.0 и вообще никак не обнаруживается в системе. Честно говоря, я был уверен, что соединение в этом случае переключится на 2.0 режим:
здесь отключены SS_RX-, SS_RX+ и SS_TX+

Итого делаем вывод, что USB 3.0 устройство проверяет наличие SuperSpeed соединения по линии SS_TX, причём трансивер настолько устойчив к ошибкам, что ему плевать на обрыв одной из линий пары. Для гарантированного переключения устройства на USB 2.0 нужно рвать обе линии: SS_TX- и SS_TX+.

Понижаем USB до 1.1

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

Несмотря на то, что интернет пестрит вопросами «как понизить USB 2.0 до 1.1», простого решения я нигде не увидел:

Давайте глянем внимательнее! По стандарту USB, скорость работы согласуется на сигнальном уровне. Устройство поднимает вольтаж D- до 0.8в, а хост отвечает пилообразным сигналом:


То же самое видим на нашем «экспериментальном стенде» на осциллографе:

То есть, нужно сделать так, чтобы хост не увидел этого повышения напряжения. А значит — ставим диод в разрыв линии D- (Шоттки, чтобы минимизировать падение):

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

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

Собираем «даунгрейдер»

Как обобщение вышеописанных экспериментов, я сделал простенький пассивный переключатель USB режимов — 1.1/2.0/3.0

Моё стремление к простоте порой невозможно сдерживать. Захотелось всё реализовать на единственном трёхпозиционном переключателе, вот таком:


Первоначальная идея была — один ряд контактов переключает D- между:

  • «диод» (USB 1.1)
  • «пусто» (USB 3.0)
  • «D-» (USB 2.0)

А другой ряд контактов соединяет SS_TX- только в режиме USB 3.0:
  • «пусто» (USB 1.1)
  • «SS_TX-» (USB 3.0)
  • «пусто» (USB 2.0)
Но эту идею я отбросил из-за сомнений — вряд ли все USB 3.0 устройства смогут работать только на одной линии дифф. пары. Поэтому я переделал выключатель кусачками:

Теперь средний контакт переключается между крайними, а в среднем положении выключатель замыкает две пары независимых контактов. Идеально! Осталось припаять и готово:
TODO: развести печатную платку и сделать красиво

Всё, теперь можно быть уверенным, что флешка работает именно в 3.0 (2.0, 1.1) режиме, а иначе она просто не обнаружится в системе. В заключение, тестируем наш картридер в различных положениях выключателя:
«3.0»:

«2.0»:

почему-то скорость USB 1.1 не понравилась Crystal Disk Mark, и в результате теста он показал нули

Вопрос на засыпку

В USB 3.0 разъёме две пары контактов — USB 2.0 и USB 3.0, мы уже выяснили, что устройство (флешка, картридер) сначала лезет на контакты 3.0, а если не получается, переходит в 2.0 режим.

Что, если к 3.0 контактам подключить одно устройство, а к 2.0 контактам — другое? Какое из устройств увидит компьютер?


Попробуйте ответить в опросе ниже перед тем, как заглядывать под спойлер :)
Ответ
Для этого эксперимента, спаяем вместе USB 3.0 SATA-адаптер и USB 2.0 флешку:


Вставляем в комп и…

Увиделось оба устройства! Да, на самом деле в каждом физическом USB 3.0 порту сразу два независимых порта. По крайней мене, у ПК на чипсетах Intel.

Let's block ads! (Why?)

[Перевод] 6 мощных возможностей CSS, которые позволяют обойтись без JavaScript

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


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

  • CSS, по своей природе, является технологией, устойчивой к отказам. Это значит, что когда CSS-парсер встречает свойство, которое он не понимает, он просто игнорирует его и идёт дальше. Другими словами, используя CSS, программист применяет стили и ожидает, что они окажутся работоспособными.
  • JavaScript отказоустойчивой технологией не является. Единственная синтаксическая ошибка в JS-коде может нарушить работу целого приложения. То есть, управляя стилизацией сайтов с помощью JS, совершенно необходимо проверять работоспособность соответствующих конструкций.

При ответе на вопрос о том, когда CSS лучше использовать вместо JS, можно рассмотреть и многие другие соображения.

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

В этом материале я расскажу о некоторых интереснейших возможностях CSS (некоторые из них — очень свежие), о которых вы, возможно, ещё не знаете. А именно, речь пойдёт о плавном скроллинге, о свойстве position: sticky, и о других возможностях, для реализации которых раньше требовалось написать немало строк хитроумного JS-кода.

1. Плавная прокрутка


Раньше, для оснащения страницы плавной прокруткой, требовалось задействовать несколько строк JS-кода. А теперь эта задача может быть решена исключительно средствами CSS. Ну не замечательно ли это? Теперь воспользоваться плавной прокруткой можно, прибегнув к CSS-свойству scroll-behavior.

Вот как это выглядит:

html {
  scroll-behavior: smooth;
}


Реализация плавной прокрутки

Вот пример на CodePen

Во время написания этого материала свойство scroll-behavior поддерживается лишь в Chrome и Firefox. Его пока не поддерживают Edge, IE и Safari (настольная и мобильная версии). Подробности о поддержке этого свойства можно узнать на Can I Use.

2. Закрепление элементов


Закрепление элементов — это одна из моих любимых возможностей CSS. Речь идёт о том, что соответствующие элементы не исчезают из области просмотра при прокрутке страниц. Теперь для закрепления элементов на страницах нет нужды прибегать к offsetTo и window.scrollY в JS. В наши дни можно просто воспользоваться CSS-свойством position: sticky:
header {
  position: sticky;
  top: 0;
}


Навигационный блок «упирается» в верхнюю границу области просмотра и не исчезает при прокрутке страницы

Вот проект на CodePen, в котором приведён пример использования свойства position: sticky

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

Взглянем на следующий HTML-код:

<main class="container">
  <nav class="nav">
    <ul>
      <li>Home</li>
      <li>About</li>
      <li>Contact</li>
    </ul>
  </nav>
  <div class="main-content">Main Content</div>
  <footer class="footer">Footer</footer>
</main>

Меню (элемент nav из этого примера) можно будет зафиксировать только в области, которую перекрывает его родительский элемент (main в нашем примере). В результате при использовании свойства position: sticky можно выделить два основных класса элементов:
  • Закрепляемый элемент: это тот элемент, к которому мы применяем свойство position: sticky (nav в нашем случае). Этот элемент будет перемещаться в пределах родительского элемента до тех пор, пока не дойдёт до заданной позиции. Например — это может быть top: 0px.
  • Контейнер закрепляемого элемента: это — HTML-элемент, в котором содержится закрепляемый элемент. Это — та область, в пределах которой может перемещаться закрепляемый элемент. Этот «контейнер» определяет границы, в которых может существовать закрепляемый элемент.

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

Данная возможность отличается практически 100% браузерной поддержкой.

3. Обрезка текста


CSS даёт в наше распоряжение два чудесных свойства: text-overflow и line-clamp. Они позволяют обрезать тексты, аккуратно обращаясь со словами, и при этом избавляют нас от необходимости использовать для решения подобных задач JavaScript или какие-то другие сложные методы. Оба свойства не новы, но крайне полезны.

Обрезка текстов

Вот пример на CodePen

Давайте подробнее поговорим о свойствах text-overflow и line-clamp.

▍Свойство text-overflow


Это свойство управляет тем, как текст выводится в ситуациях, когда он, если не помещается в одной строке, должен быть обрезан. Пример такой ситуации показан на вышеприведённом рисунке, в заголовке карточки. Тут можно воспользоваться конструкцией text-overflow: ellipsis, что приведёт к тому, что в конец обрезаемого текста будет добавлен Unicode-символ ().

Для того чтобы свойство text-overflow: ellipsis сделало бы своё дело, необходимо использовать так же свойства white-space: nowrap и overflow: hidden.

.card-title {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

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

▍Свойство line-clamp


Это свойство приходит нам на помощь в тех случаях, когда надо работать не с однострочным, а с многострочным текстом (пример такого текста — это содержимое карточки с вышеприведённого рисунка). Хотя это — часть стандарта CSS Overflow Module Level 3, который сейчас имеет статус «рабочего черновика («Working Draft»), данное свойство уже поддерживают порядка 95% браузеров, правда, с префиксом -webkit-. Перед его использованием важно учитывать то, что оно не даёт возможности управления количеством выводимых символов. Но оно, в любом случае, невероятно полезно.

Нам, чтобы им пользоваться, надо прибегнуть к старой реализации flexbox, применив свойства display: -webkit-box и -webkit-box-orient: vertical. Вот как это выглядит:

.card-description {
  overflow: hidden;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
}

4. Пользовательские CSS-свойства: CSS-переменные


В JavaScript-мире препроцессоры CSS (вроде Sass, Less и Stylus) — это очень полезные и популярные технологии. Препроцессоры расширяют возможности CSS, позволяя пользоваться, например, переменными и функциями. Но современным веб-дизайнерам доступны мощные стандартные возможности CSS, известные как пользовательские CSS-свойства или CSS-переменные.

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

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

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

:root {
  --base: #ffc600;
  --spacing: 10px;
  --blur: 10px;
}

img {
  padding: var(--spacing);
  background: var(--base);
  -webkit-filter: blur(var(--blur));
  filter: blur(var(--blur));
}

.hl {
  color: var(--base);
}

CSS-переменными можно управлять из JavaScript.

Использование CSS-переменных

Вот пример на CodePen, где показано использование CSS-переменных и управление ими из JS-кода

5. Обеспечение поддержки тёмной темы


С тех пор, как компания Apple представила в прошлом году тёмную тему для macOS, и благодаря тому, что CSS дал нам возможность обнаруживать применение этой темы с использованием медиазапроса, многие крупные веб-проекты (например, вроде Twitter и Google Maps) такой темой обзавелись (вот список проектов, которые поддерживают тёмную тему).

Тёмная тема — это не просто способ украшения веб-страниц. Она способна реально помочь некоторым людям работать в интернете.

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

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

Томас Штайнер, Customer Solutions Engineer, Google, Германия.

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

Чарльз Рейнольдс, дизайнер, правительство Великобритании.

Кроме того, из материала Томаса Штайнера можно узнать о том, что использование тёмной темы способствует экономии энергии: «(…) как известно, использование тёмного режима на AMOLED-дисплеях позволяет сэкономить немало энергии. Исследования в сфере Android-устройств, направленные на популярные приложения Google, вроде YouTube, показали, что в некоторых случаях экономия энергии может достигать 60%».

Новая возможность CSS, которая позволяет нам узнавать о том, включена ли у пользователя тёмная тема, представлена медиа-функцией prefers-color-scheme. Она уже совместима с Chrome, Firefox, Safari и Opera.

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

:root {
  --color: #222;
  --background: #eee;
  --text: 'default';
}

body {
  color: var(--color);
  background: var(--background);
}

body:after {
  content: var(--text);
  display: block;
  text-align: center;
  font-size: 3rem;
}

@media (prefers-color-scheme: light) {
  :root {
    --color: #222;
    --background: #eee;
    --text: 'light';
  }
}

@media (prefers-color-scheme: dark) {
  :root {
    --color: #eee;
    --background: #222;
    --text: 'dark';
  }
}


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

→ В данном CodePen-проекте оформление страницы зависит от того, какую тему использует тот, кто просматривает этот пример

6. Директива supports


Долгое время веб-разработчикам приходилось прибегать к сторонним решениям (вроде JS-инструмента Modernizr) для выяснения того, поддерживаются ли некие возможности CSS текущим браузером. Например, настраивая свойство элемента -webkit-line-clamp, можно проверить, поддерживается ли это свойство в браузере, и, если это не так, можно воспользоваться каким-то запасным вариантом.

После того, как в CSS появилась директива @supports, проверять возможности браузеров стало можно прямо из CSS-кода.

Директива @supports очень похожа на медиазапросы. Она поддерживает различные комбинации выражений, построенные с помощью условных операторов AND, OR и NOT:

@supports (-webkit-line-clamp: 2) {
    .el {
        ...
    }
}

Здесь проверяется, поддерживает ли браузер свойство -webkit-line-clamp. Если это так, то есть, если условие оказывается истинным, будет применён стиль, объявленный внутри директивы @supports.

Эту возможность поддерживают все современные браузеры.

Итоги


В этом материале я рассказал о некоторых полезных возможностях CSS. Если вы можете сделать что-то без использования JS, а лишь применяя CSS, то так и поступите.

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

А вам известны какие-нибудь свежие возможности CSS, которые позволяют решать те же задачи, которые раньше решали только с использованием JavaScript?

Let's block ads! (Why?)

[Из песочницы] Компульсивное переедание или как потолстеть на 20 кг

Кто я?


Привет, дорогой читатель! Меня зовут Лера, мне 20 лет и я ненавижу свою жизнь. Пожалуй, именно так можно описать жизнь человека с РПП (расстройством пищевого поведения).
К РПП относят нервную анорексию, нервную булимию, компульсивное переедание, а также ряд других расстройств. Нарушения пищевого поведения относятся к психическим расстройствам.

И смех, и грех, но как раз в период карантина начало усугубляться моё ментальное здоровье, привет депрессия и тревожность.
Я так устала от себя. Чувствую себя очень одинокой, уже ни во что не верю и всего боюсь. Как-будто в тупике: мне так тяжело видеть столько счастья и радости в людях, в то время как у меня на душе пустота и сплошное разочарование. — записки из моего дневника.

Небольшая предистория


В 2018 году я заболела анорексией. За год я потеряла 20 кг и тогда мой вес составлял 42 кг при росте 178 см. Почему я начала худеть? Всё очень просто — манящие мечты о модельной карьере, контракты с Gucci и Dior, обложки на глянцевых журналах и путешествия по всему миру. Всё, что было нужно для этого — убрать пару лишних килограммов. Ну и я тут немножко разгулялась. Вместо 3-х кг сбросила целых 20.
Анорексия — болезнь, которая характеризуется нарушением работы пищевого центра головного мозга и проявляется в виде потери аппетита и отказа от еды. В её основе лежит нервно-психическое расстройство, проявляющееся страхом ожирения и навязчивым стремлением к похудению.
Понятное дело, со временем родители распознали очевидную проблему и отвели в клинику, где мне приписали огромное кол-во ферментов, гепатопротекторов и обследований. Спустя год мучений, я полностью восстановилась. И набрала 20 кг.

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

Компульси́вное перееда́ние (англ. binge-eating disorder, сокращённо BED) — расстройство приёма пищи, представляющее собой чрезмерное употребление еды, приводящее к появлению лишнего веса, и являющееся реакцией на дистресс. Может следовать за утратой близких, несчастными случаями и эмоциональным дистрессом, особенно у лиц, предрасположенных к полноте.
Иногда переедать — это нормально. Однако люди, которые навязчиво переедают, используют пищу как единственный способ для преодоления негативных эмоций и апатии (моя ситуация). В результате ситуация выходит из-под контроля. Все мысли только о еде, а потом вина, стыд и депрессия.
Это не просто переедание как на Новый Год или ДР. Это когда вас поглащает стыд и глубокое чувство вины перед самим собой 24/7.

Как это начинается?


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

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

Компульсивное переедание — это не из-за голода


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

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

Признаки компульсивного переедания


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

Одна из трудностей в определении того, есть ли у вас компульсивное переедание, состоит в том, что никто точно не знает, что именно представляет собой «переедание». Но вот некоторые признаки:
  • едите быстрее, чем обычно;
  • едите до перенасыщения;
  • едите, когда не голодны физически;
  • едите в одиночестве или тайно;
  • чувство вины после переедания;
  • ощущение безысходности, как будто вы одержимы;
  • попытки компенсировать переедание диетой или ограничением пищи.

Описания пострадавших могут включать следующие комментарии:
  1. Я не голоден, я просто зависим — это как наркотик. Чем больше у меня есть еды, тем больше я хочу есть.
  2. Я ем так быстро, как будто завтра не наступит. И всегда в секрете.
  3. Я приношу еду в постель, а утром кладу обертки в мусорное ведро, чтобы никто не знал, что я ел.
  4. Я мало ем в присутствии других, но ем много, когда прихожу домой.
  5. Я постоянно хожу к холодильнику в поисках чего-нибудь, что могло бы меня удовлетворить.
  6. Я постоянно срываюсь на сладкое.
  7. Я ем, когда счастлив, и ем, когда мне грустно, но обычно это происходит не потому, что я голоден.

А чё с тобой не так-то?


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

К чему я это всё веду


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

Так что берегите и любите себя!

P.S.: я сама сейчас в поисках психотеравпевта, так что начало положено.

Let's block ads! (Why?)

[Из песочницы] Реализация offline режима для Yandex.Music

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

Итак, нам понадобится:


  • Относительно свежий python: 3.7 и выше
  • Всякая асинхронщина: aiohttp и aiofile
  • Классический инструмент для работы с html-API: BeautifulSoup
  • Для развлечения пользователя во время процесса: tqdm
  • Для заполнения тэгов: mutagen

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

def resolve_cookie(login: str, password: str) -> str:
    cookies = CookieJar()
    opener = urllib.request.build_opener(
        urllib.request.HTTPCookieProcessor(cookies),
        urllib.request.HTTPRedirectHandler())
    response = opener.open("https://passport.yandex.ru")
    doc = response.read()
    parser = FormParser()
    parser.feed(doc.decode("utf-8"))
    parser.close()
    parser.params["login"] = login
    response = opener.open(parser.url or response.url, urllib.parse.urlencode(parser.params).encode("utf-8"))
    doc = response.read()
    parser = FormParser()
    parser.feed(doc.decode("utf-8"))
    parser.close()
    parser.params["login"] = login
    parser.params["passwd"] = password
    response = opener.open(parser.url or response.url, urllib.parse.urlencode(parser.params).encode("utf-8"))
    cookie_data = {}
    for item in cookies:
        if item.domain == ".yandex.ru":
            cookie_data[item.name] = item.value
    if "yandex_login" not in cookie_data:
        keys = ", ".join(cookie_data.keys())
        raise Exception(f"Invalid cookie_data {keys}")
    return "; ".join(map(lambda v: f"{v[0]}={v[1]}", cookie_data.items()))

По адресу https://passport.yandex.ru нас ожидает первая форма с полем login. Если будет указан актуальный логин, то следующая форма предложит ввести пароль, что мы незамедлительно сделаем. Результат отправки формы нас особо не интересует. Наша цель — кукисы. Если среди полученых кук окажется yandex_login, то авторизация прошла успешно. Вариант с двухфакторной аутентификацией мы пока не рассматриваем.

Следующим нашим шагом будет получение списка избранных пользователем исполнителей. Чтобы показать наш стиль и молодёжность, для запросов применим aiohttp. Полученный html распарсим с помощью BeautifulSoup. Это классический инструмент, который используют ребята, заинтересованные в получении контента с чужих веб-страниц.

class YandexMusicApi:
    host = "music.yandex.ru"
    base_url = f"https://{host}"

    def __init__(self, cookie: str):
        self.headers = Headers(self.host, cookie)

    async def _request(self, end_point: str):
        async with aiohttp.ClientSession() as session:
            url = f"{self.base_url}/{end_point}"
            async with session.request(method="GET", url=url) as response:
                return await response.read()

    async def get_favorite_artists(self, login: str) -> List[Artist]:
        body = await self._request(f"users/{login}/artists")
        soup = BeautifulSoup(body, "lxml")
        artists_soup = soup.find("div", class_="page-users__artists")
        if artists_soup is None:
            caption = soup.find("div", class_="page-users__caption")
            if caption:
                raise Exception(caption.contents[0])
        result = []
        for artist_soup in artists_soup.find_all("div", class_="artist"):
            title_soup = artist_soup.find("div", class_="artist__name")
            title = title_soup.attrs["title"]
            title_href_soup = title_soup.find("a")
            id_ = int(title_href_soup.attrs["href"].split("/")[-1])
            result.append(Artist(id_, title))
        return result

Здесь всё довольно просто, на странице https://music.yandex.ru/users/<login>/artists в блоке page-users__artists отображается требуемый нам список. Имя исполнителя можно обнаружить в аттрибуте title блока artist__name. Id исполнителя лёгким движением split извлекается из адреса ссылки.
Аналогичные действия позволят нам получить список альбомов исполнителя, а также список треков в альбоме.

В этом месте мы уже потираем наши шаловливые ручки в ожидании прослушивания своей музыкальной коллекции. Но если бы всё было так просто, то я бы не писал эту статью. Нас ожидает новый противник — yandex-хранилище. Для проникновения в хранилище потребуется воспроизвести жонглирование ссылками, которое происходит на страницах музыкального ресурса. При внимательном изучении вкладки Network браузерного инструментария выяснилось, что все элементы запроса легко выводятся за исключением одного участка в адресе https://{host}/get-mp3/{sign}/{ts}/{path}, который после непродолжительного гугления получил имя sign. Соль для сигнатуры (XGRlBW9FXlekgbPrRHuSiA) тоже была обнаружена на просторах интернета. Скорее всего не составит большого труда извлечь это значение из исходников страницы, но это уже не требуется.

    async def get_track_url(self, album_id: int, track_id: int) -> str:
        async with aiohttp.ClientSession() as session:
            url = f"{self.base_url}/api/v2.1/handlers/track/{track_id}:{album_id}/" \
                  f"web-album-track-track-main/download/m?hq=0&external-domain={self.host}&overembed=no&__t={timestamp()}"
            page = f"album/{album_id}"
            headers = self.headers.build(page)
            async with session.request(method="GET", url=url, headers=headers) as response:
                body = await response.json()
                src = body["src"]
                src += f"&format=json&external-domain={self.host}&overembed=no&__t={timestamp()}"
                result = parse.urlparse(src)
                headers = self.headers.build(page, {
                    ":authority": "storage.mds.yandex.net",
                    ":method": "GET",
                    ":path": f"{result.path}/{result.query}",
                    ":scheme": "https",
                }, True)
                async with session.request(method="GET", url=src, headers=headers) as response:
                    body = await response.json()
                    host = body["host"]
                    path = body["path"]
                    s = body["s"]
                    ts = body["ts"]
                    sign = md5(f"XGRlBW9FXlekgbPrRHuSiA{path[1::]}{s}".encode("utf-8")).hexdigest()
                    url = f"https://{host}/get-mp3/{sign}/{ts}/{path}"
                    return url

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

На этом этапе уже немного надоедает всё это дело, поэтому берём в одну руку ранее используемое нами aiohttp, в другую — aiofile и реализуем скачивание в лоб.

    async def download_file(cls, url: str, filename: str):
        async with aiohttp.ClientSession() as session:
            async with session.request(method="GET", url=url) as response:
                data = await response.read()
                async with AIOFile(filename, "wb") as afp:
                    await afp.write(data)
                    await afp.fsync()

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

Далее, нам нужно поставить закачку на поток. Методично перебираем исполнителей, альбомы, треки. Создаём директории, скачиваем альбомный арт, скачиваем сами треки. Прописываем тэги (почему-то у полученных mp3 они отсутствовали).

    async def download_artist(self, artist: Artist, depth: Depth = Depth.NORMAL):
        artist_progress = tqdm(total=0, desc=artist.title, position=1, ascii=True)
        albums = await self.api.get_artist_albums(artist.id)
        artist_progress.total = len(albums)
        artist_progress.refresh()
        for album in albums:
            album_dir = os.path.join(self.target_dir, normalize(artist.title), f"{album.year} - {normalize(album.title)}")
            if depth < Depth.ALBUMS and os.path.exists(album_dir):
                artist_progress.update()
                continue
            album_progress = tqdm(total=0, desc=f"> {album.title}", position=0, ascii=True)
            tracks = await self.api.get_album_tracks(album.id)
            album_progress.total = len(tracks)
            album_progress.refresh()
            os.makedirs(album_dir, exist_ok=True)
            if album.cover:
                album_progress.total += 1
                cover_filename = os.path.join(album_dir, "cover.jpg")
                if not os.path.exists(cover_filename):
                    await self.download_file(album.cover, cover_filename)
                album_progress.update()
            for track in tracks:
                target_filename = os.path.join(album_dir, f"{track.num:02d}. {normalize(track.title)}.mp3")
                if depth >= Depth.TRACKS or not os.path.exists(target_filename):
                    url = await self.api.get_track_url(track.album_id, track.id)
                    await self.download_file(url, target_filename)
                    self.write_tags(target_filename, {
                        "title": track.title,
                        "tracknumber": str(track.num),
                        "artist": artist.title,
                        "album": album.title,
                        "date": str(album.year),
                    })
                album_progress.update()
            album_progress.close()
            artist_progress.update()
        artist_progress.close()

После первой загрузки, я с удивлением обнаружил, что любимая мною группа AC/DC оказалась разделена на две директории. Это послужило поводом к реализации метода normalize:

def normalize(name: str) -> str:
    return name.replace("/", "-")

Как несложно заметить, процесс скачивания можно (точнее нужно) распараллелить. Почва для этого подготовлена благодаря использованию асинхронных библиотек для сетевых запросов и записи в файлы. Для этого можно использовать asyncio.Semaphore в комбинации с asyncio.gather.
Но реализация асинхронной очереди не является предметом данной статьи, поэтому пока и так сойдёт.

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

def resolve_cookie() -> str:
    base_dir = os.path.normpath(os.path.join(os.path.dirname(__file__), os.path.pardir))
    cookie_file = os.path.join(base_dir, ".cookie")
    if os.path.exists(cookie_file):
        with open(cookie_file, "rt") as file:
            return file.read()
    credentials_file = os.path.join(base_dir, ".credentials")
    if os.path.exists(credentials_file):
        config = configparser.ConfigParser()
        config.read(credentials_file)
        login = config["yandex"]["login"]
        password = config["yandex"]["password"]
    else:
        raise Exception(f"""Create \"{credentials_file}\" with content

[yandex]
login=<user_login>
password=<user_password>
""")
    cookie = auth.resolve_cookie(login, password)
    with open(cookie_file, "wt") as file:
        file.write(cookie)
    return cookie

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


  • -a (--artist), Id исполнителя, если указан, то скачиваются только треки этого исполнителя
  • -o (--output), директория для хранения музыкальной коллекции, по умолчанию — Music в домашней директории пользователя.
  • -d (--depth), параметр родился как костыль, вызванный возможным прерыванием процесса
    • При значении по-умолчанию 0 (NORMAL) проверяется наличие директории с альбомом, и, если она существует, то загрузка альбома пропускается
    • Значение 1 (ALBUMS) перебирает все треки в альбоме и скачивает недостающие
    • Значение 2 (TRACKS) скачивает и перезаписывает треки, даже если они уже присутствуют в файловой системе
async def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("-a", "--artist", help="Artist ID")
    parser.add_argument("-o", "--output", default=f"{Path.home()}/Music",
                        help=f"Output directory, default {Path.home()}/Music")
    parser.add_argument("-d", "--depth", default=0, type=int,
                        help=f"Exists files check depth, {enum_print(Depth)}")
    args = parser.parse_args()
    cookie = resolve_cookie()
    api = YandexMusicApi(cookie)
    agent = YandexMusicAgent(api, args.output)
    if args.artist:
        artist = await api.get_artist(args.artist)
        await agent.download_artist(artist, args.depth)
    else:
        email = re.compile(".*?yandex_login=(.*?);.*?", re.M).match(cookie).group(1)
        await agent.download_favorites(email, args.depth)

И вот, наконец-то, мы можем всё это запустить:

if __name__ == "__main__":
    asyncio.run(main())

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

Результат можно увидеть в репозитории yandex.music.agent

Let's block ads! (Why?)

Habr vs Medium: сколько можно заработать, опубликовав 9 статей на Medium.com

Привет, Хабр.

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

Для тех кому интересно что получилось, подробности под катом.

Во-первых, почему medium.com? Ответ простой — не знаю. Как-то так получилось, что наибольшее количество полезных материалов я находил именно там, поэтому идея разместить тексты там же появилась сама собой. Во-вторых, стоит ли вообще выкладывать что-либо на английском? Разумеется, мой английский далеко не нативный как, наверное, у 99% авторов Хабра, так что возможно, в этом смысле всё плохо. С другой стороны, не менее 2млрд людей на Земле знают английский, при том что Англия + США это всего лишь 0.4млрд, так что 3/4 людей, способных прочитать английский текст, также не являются нативами, и на эту тему можно просто не париться. Наконец, технический текст это все же не текст художественный, и содержание тут имхо важнее формы. В третьих, я решил выложить те статьи, которые уже были выложены на русскоязычном Хабре и получили высокий рейтинг. В этом плане, материал должен быть более-менее качественный.

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

Бизнес-модель


Для начала, интересно сравнить бизнес-модели Хабра и Medium, благо все это доступно в открытых источниках и ни для кого не секрет. Авторы Хабра получают вознаграждение согласно правилам ППА — если статья набирает +30 или +50 голосов от участников Хабра, она оплачивается, если нет, автор работал бесплатно. У такого правила есть разумеется, и недостатки, например, если статья набрала +30 голосов, но хотя бы один читатель поставил "-", то автор не получает ничего. Имхо не очень справедливо, но сейчас не об этом. В отличие от Хабра, у Medium правила несколько другие. Во-первых, доступ к сайту для читателей платный, бесплатно можно просматривать лишь несколько статей в месяц. Статьи при этом оплачиваются по времени прочтения, которое учитывается только для подписчиков сайта. Бесплатные просмотры, например люди, открывающие статью из поиска Гугла, на оплату не влияют. Хотя если кто-то прочитал несколько статей бесплатно, в итоге ему понравилось, он зарегистрировался и оплатил аккаунт, то прочитанные им статьи «зачтутся» автору. Вторая тонкость в том, что выплаты автору зависят от доли, которую занимают статьи автора в общем времени чтения для этого читателя. Видимо, идея в том, что Medium хочет мотивировать авторов делать «уникальный» контент — если какие-то читатели приходят на сайт, чтобы читать статьи конкретного автора, то его гонорар от таких прочтений будет максимальным. Сложно сказать, насколько это работает, для топовых авторов может смысл в этом и есть, для новичков разницы наверно, не будет. Какого-либо порога, в отличие от +30/+50 у Хабра, в Medium нет — можно получить за статью 0.01$ и сайт честно её выплатит.

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

Статистика и просмотры


Первое, что «бросается в глаза», это число просмотров в целом. На Хабре статья за 2-3 дня легко набирает 5000-10000 просмотров, здесь же 50 — это уже много.

Причин я не знаю, возможно, на Medium публикуется гораздо больше материалов, и шанс что новая статья будет замечена, ничтожно мал. Статистика за каждую статью довольно подробная, и включает в себя как «внутренние», так и «внешние» просмотры (которые, еще раз напомню, не оплачиваются):

Интересен параметр Reading Time, показывающий время чтения. Для некоторых статей он может выглядеть вот так:

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

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

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


Некоторые результаты неожиданны, например последняя статья про Software Defined Radio, которая набрала на Хабре примерно 50К просмотров и рейтинг +55, на Medium вообще никого не заинтересовала, её прочитал 1 человек.

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

Как можно видеть, сумма получилась «астрономическая». За месяц было заработано целых 49 центов. В общем, если бы я решил жить в США и зарабатывать написанием текстов, жить наверно пришлось бы под мостом в картонной коробке.

Тут виден один интересный момент: «науч-поп», в отличие от Хабра, на Medium «не идет» совсем. Причин опять же, я не знаю. Статьи по программированию наоборот, приносят определенную прибыль, причем она не совпадает с рейтингами Хабра. Статья «Панорама-FM или как увидеть все радиостанции сразу с помощью SDR» практически не вызвала интереса на Хабре, но на Medium принесла наибольшую прибыль. Науч-поп весьма интересен на Хабре, на Medium, наоборот, все результаты по нулям. К примеру, статья про гаджеты для наблюдения солнца имеет рейтинг +45 и 16К просмотров на Хабре, на Medium она имеет только 3 просмотра. Конечно можно было бы традиционно пошутить в стиле «ну тупыые», но я точно знаю что это не так, крупнейший производитель солнечных телескопов Coronado как раз расположен в Америке, да и число любителей астрономии там весьма велико. Скорее всего, вся главная страница просто занята «топами», и статьи новичков туда просто не попадают. Возможно, я просто «не попал» в целевую аудиторию, и постоянные читатели Medium ищут статьи про политику, covid или что-то еще, но не про наблюдения Солнца. В общем, вопрос тут тоже открытый, как и вопрос оптимальной стратегии размещения публикаций. К примеру, одна из англоязычных авторов, судя по всему, профессиональный копипастер копирайтер, в своем блоге писала, что она в течении 3х недель публиковала на Medium 1 текст в день, но для серьезных технических текстов это просто невозможно.

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

Заключение


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

Надеюсь, для желающих «попробовать», этот опыт окажется интересным.

И как обычно, всем удачных экспериментов.

Let's block ads! (Why?)

Как создаются Highload проекты на PHP: расшифровка

28 июля в нашем инстаграм-аккаунте и ютубе прошел прямой эфир с Александром Высоцким — ведущим PHP-разработчиком в лондонском офисе Badoo, который работает в команде антиспама. Саша рассказал о том, как создаются Highload проекты на PHP, своей жизни в Лондоне и, конечно, про Badoo.

***
Меня зовут Высоцкий Александр, я работаю в компании Badoo ведущим PHP-разработчиком.
Мы делаем Badoo и Bumble – это онлайн-платформы для знакомств. У нас 500 миллионов пользователей по всему миру. В команде 300 человек, у нас 2 офиса разработки – в Москве и Лондоне, 20 open source-проектов и множество других внутренних инструментов.

Я родом из Саратова, там же получил профильное образование. Я закончил специалитет и аспирантуру на Факультете компьютерных наук и информационных технологий СГУ. К моменту окончания аспирантуры успех поработать на позиции backend-разработчика в разных областях, от туристической сферы до игр. В середине 2016 основной проект завершился, и передо мной возник вопрос: что делать дальше – искать что-то новое в Саратове, переехать в Москву или Санкт-Петербург или податься в зарубежные компании? Тогда я уже знал о Badoo, и сделал apply на открытую позицию в лондонский офис. Правда, мне не хватило опыта и знаний, чтобы получить offer, но параллельно мне пришли предложения о работе из Германии и Нидерландов, и я решил вместе с супругой переехать и работать в немецкой компании. Полтора года жили в Лейпциге – это город в Саксонии, в десятке крупнейших городов Германии. Я там работал над туристическими решениями. Однако желание работать в Badoo не пропало, и я подался на открытую позицию еще раз, через год. После нескольких интервью по телефону и одного on-site мне сделали offer. В начала 2019 года я релоцировался уже в Лондон.

Я хочу рассказать, каково работать на позиции backend-разработчика в Badoo, в условиях двух релизов в день, реального highload и миллиона строк кода, как адаптироваться к жизни и работе за границей и сохранить при этом семью.

Насколько бесшовен deploy в условиях монолитности?


Ответ на этот вопрос можно разделить на две части. Первая – техническая реализация нашего CICD-pipeline, ее хорошо описал мой бывший коллега Юрий Насретдинов в своем докладе на HighLoad («5 способов deploy PHP-кода в условиях highload»). Я его рекомендую посмотреть. Если коротко – у нас есть несколько сотен серверов, которые обслуживают запросы пользователей. При deploy мы раскладываем только изменения в репозитории и атомарно переключаем symlink. Вторая – проверка того, что deploy не разломает нам production. Любой код перед выкладкой проверяется с помощью unit, интеграционного и UI-теста, а также статическим анализатором, на предмет явных проблем. У нас большой и профессиональный QA-отдел, позволяющий успешно релизить 2 раза в день.

Используете ли DDD или другие архитектурные паттерны?


DDD – это Domain Dream Design. Это не архитектурный паттерн скорее, а методология. Я бы не сказал, что у нас используется один конкретный подход, скорее – комбинация из нескольких. Насчет паттернов – в backend для решения задач используется несколько паттернов проектирования, я бы хотел выделить это подробно: наш отдел их активно разрабатывает, и они помогают нам эффективно решать задачи. Мы активно используем реализации, у нас есть очень много очередей, мы отправляем миллионы событий, которые обрабатываются соответствующими консумерами. Также среди активно используемых паттернов есть модуль: большая часть нашего кода разбита на отдельные связанные инстансы, которые взаимодействуют через ограниченный открытый API.

Какие фреймворки используются?


У нас свой, внутренний фреймворк, мы не используем сторонние открытые решения.

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


Объемный вопрос, я его разобью на несколько частей.

Безусловно, PHP – это отличный инструмент, который позволяет нам решать задачи web-разработки. Но отношение к нему в сообществе неоднозначно, и это связано с его репутацией. С самого появления PHP бытовало мнение, что на нем очень легко писать плохой код – но, по моему мнению, низкий порог входа не является недостатком. Наоборот, это позволяет приобщить к разработке широкий круг людей. Кроме того, он действительно позволяет хорошо решать задачу разработки web-приложений, и с каждой новой версией язык улучшается. PHP 7 сделал огромный шаг вперед по части performance и удобства разработки. У нас в блоге на Хабре есть отличная статья о том, как переход на эту версию позволил нам высвободить значительную часть ресурсов.

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

Почему Badoo использует монолит, а не микросервисы?


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

Как релизиться 2 раза в день, когда у тебя 2 продукта на нескольких платформах и сотни версий клиента?


У нас очень короткий релиз-цикл и, действительно, два deploy в день. Нам очень важно поддерживать качество работы нашего продукта на высоком уровне – мы не хотим выкатывать в production баги/фаталы. Поэтому на первое место выходит тестирование фичей, которые катятся на production. Я уже упоминал о том, что у нас есть большой набор инструментов для тестирования каждого релиза, и это позволяет нам выкатывать минимальное количество багов/фаталов на production. Кроме того, у нас есть подход, который связан с тем, что каждый backend-разработчик отвечает и заинтересован в том, чтобы его фича на backend запустилась без каких-либо дополнительных действий со стороны его отдела и со стороны frontend и mobile-команд. Может быть такая ситуация, когда у тебя есть тикет на разработку backend-фичи, ты ее релизишь на production, но она реально начинает использоваться только через какое-то время. И тогда к тебе приходят QA-инженеры и спрашивают, почему она не работает. Поэтому мы на стороне backend при релизе функционала покрываем его максимальным количеством тестов, моков и QAP, чтобы быть на 100% уверенными в том, что все, что мы катим – на 100% рабочее.

Стоит ли идти в крупную компанию из фриланса на меньшую ЗП, если до этого не было опыта работы в крупной компании?


Дисклеймер: вы можете зайти на наш сайт tech.badoo.com, где мы выкладываем текущие вакансии. Может быть, попадется что-то по душе, и вы попробуете сделать apply.

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

Расскажите, были ли такое, что production не выдерживал highload и как с этим боролись?


На моей памяти – не было. У нас опытные инженеры, наш продукт разрабатывается уже более 15 лет, и у компании огромный опыт именно highload-разработки. Мы нацелены на то, чтобы performance наших приложений был на максимуме.

Какие плюшки в Badoo относительно мелких компаний?


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

Чем вы тестируете API?


У нас есть unit-тесты и целый фреймворк для запуска большого количества тестов параллельно, с минимальными затратами времени. Подробно об этом можно прочитать в статье Владимира Янца в нашем блоге, он хорошо и подробно описал эту тему. Если говорить о UI-тестах – мы используем Bash для их запуска, чтобы проверять корректность работы UI.

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


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

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

У нас есть модель для детекта «spam/scam». Мы сделали тулзу для анализа мобильного трафика для параллельной команды. Также у нас в компании используются нейросети – для жестовой фото-верификации и при отправке непристойных фотографий в мессенджере. Недавно наши коллеги запустили т/н «dick pic detector» для защиты от нежелательного контента в личных сообщениях (пользователь может выбирать, хочет ли он получать такой контент).

PHP и MySQL – что делать для оптимизации производительности backend?


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

Насчет стека: благодаря тому, что в Badoo большое количество отделов и команд, мы используем максимально широкий набор технологий – начиная от PHP, MySQL, Nginx, Go, C++ и заканчивая Tarantool, LUA и Scala. Каждая команда выбирает инструмент для эффективного решения поставленной задачи. Так как мы работаем в условиях highload и обрабатываем десятки тысяч запросов в секунду, критичным становится вопрос performance нашего backend.

Теперь стоит упомянуть об инструментах, которые были созданы внутри компании и были выложены в open source. Первый инструмент — это Pinba (PHP is Not a Bottleneck Anymore). Это инструмент для сбора статистики и мониторинга производительности приложения без импакта на его performance, и для представления собранных данных в human-friendly виде. Следующий – Codeisok: инструмент для управления git-репозиториями и проведения code review. Мы активно юзаем нашу внутреннюю наработку, и перед тем, как фича переходит в master, мы применяем лучшие практики code review (о них тоже можно прочитать в нашем блоге), чтобы до production доезжал максимально эффективный код. Еще один инструмент, который позволяет нам трекать performance каждого отдельного участка кода – это LifeProf: он позволяет в автоматическом режиме профилировать все запросы. Все эти инструменты (и даже больше) можно найти в нашем Github-репозитории.

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


Очень широкая тема. У меня есть опыт жизни в Германии и Англии, и он отличается, но я расскажу про Лондон.

Переезд – это испытание. Ты выходишь из привычной среды, рядом с тобой нет друзей, родственников, родителей, с которыми ты привык общаться каждый день. Это определенный удар для тебя и для супруги, для семьи в целом. Тут важно найти выход из этого состояния. Рецепт, который нашли мы – это максимально быстрая интеграция в новое общество. В Германии очевидным барьером был язык – мы всегда учили английский, а тут предстояло влиться в немецкое общество; это требовало усилий, и было стрессово, но за 1.5 года жизни в Германии мы смогли достигнуть высокого уровня языка, уча его каждый день. В Лондоне такой проблемы не стояло, и опыт жизни в иностранном государстве уже был.
Компания Badoo оказывала максимальную поддержку при переезде – в вопросах поиска первой квартиры, в общении с налоговой. Это позволяло легко влиться в жизнь в Лондоне.

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

Моя супруга всегда хотела учиться на дизайнера, и планирует поступать. Здесь мы нашли курсы, которые подошли; она как раз учила язык и сдавала тест IELTS, чтобы поступить на бакалавриат интерьерного дизайна. Очень широкий набор учебных заведений, но нужно помнить, что для иностранцев стоимость обучения в несколько раз выше, чем для местных.

Как контролировать усилие, усидчивость, самомотивацию, прокрастинацию?


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

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

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

Используете ли вы ORM или прямое взаимодействие с хранилищем? Почему?


Я уже упоминал, что у нас свой собственный фреймворк. Мы используем собственную реализацию ORM.

В Штатах офис не планируете?


У нас есть офис в США, там хостится Bumble – в городе Остин, штат Техас. Но там нет инженерной команды, и пока неизвестно, будем ли мы расширяться.

Что помогало адаптироваться каждый раз на новом месте?


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

Как инженеры Badoo движут русскоговорящее PHP-сообщество? Конференции, митапы, блог, неформальные сходки.


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

У нас есть Badoo PHP Meetup: два раза в год, в московском офисе. На последних встречах было около 250 участников. Мой коллега Владимир Янц, о котором я уже говорил, развивает неформальные встречи в Москве – BeerPHP Moscow. У него уже есть последователи в Санкт-Петербурге, Саратове и других городах. Формат, конечно, заимствован у аналогичных митапов BeerJS, но это все равно очень круто – в неформальной обстановке пообщаться с единомышленниками, коллегами и просто чуваками из индустрии.

Инженеры Badoo регулярно входят в состав программного комитета единственной PHP-конференции в России, PHP Russia. В этом году ее онлайн-часть стала международной и бесплатной для всех участников, благодаря нашей компании. Также у нас есть блоги на Хабре и Medium, где мы делимся всеми наработками (не только PHP).

Как скалируются разные куски монолита под нагрузку?


Хороший вопрос в продолжение темы о монолите и микросервисах. У нас в блоге выложена отличная презентация о performance и о том, как, с архитектурной точки зрения, построен наш backend – я расскажу кратко. У нас есть около 600 серверов, которые обрабатывают все запросы от клиентов, и на них возложен наш монорепозиторий. При таком подходе мы обладаем некоторой гибкостью в скейлинге, добавляем новые тачки, вкладываем код – и они готовы к использованию.

Расскажите подробнее о самописном фреймворке Badoo – на основании чего он реализован и на что больше похож?


Этим занимается так называемая команда платформы – очень крутые ребята, которые делают «backend для backend» и поддерживают основную часть backend-разработчиков. Они дают нам много крутых плюшек. Я уже отчасти об этом говорил – например, они реализуют тот самый функционал очередей, которые широко используется в компании; они также делают облачный сервис для наших нужд.

Я бы не сказал, что он на что-то конкретное похож. Я работал с Aravel и Symfony – безусловно, какие-то части есть, и мы можем использовать модули, которые находятся в open source в нашем проекте, но я не думаю, что наш git-репозиторий сильно отличается по подходам от других современных фреймворков. Мы используем пакетные менеджеры, чтобы подтягивать сторонние зависимости, используем autoloading, используем модули, чтобы инкапсулировать части кода.

Какие самые сложные задачи пришлось решать в Badoo?


Не могу выделить конкретную задачу или проект, которые были бы очень сложными. У нас есть интересные проекты, и есть – очень интересные, и они все очень разные. В моей практике это все проекты, связанные с Machine Learning. Когда я учился в аспирантуре, я касался этой темы, и эта область – и задачи из нее – мне импонирует. Я упоминал проект, который мы делали для команды Performance Marketing, связанный с анализом трафика – он был очень крутой, мы нашли и открыли много полезных инсайтов.

Используется ли в Badoo компиляция PHP?


Нет.

Репа одна, все пушат в одно место?


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

Есть ли DDD в Badoo, как вы к нему относитесь?


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

Как Badoo работает со спамом? Простые if или уже есть ML?


Я где-то видел шутку о том, что ML – это просто большая куча if/else. Но, конечно, мы используем что-то

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

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

Как организовано взаимодействие модулей проекта? Класс к классу, или что-то более хитрое?


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

Правда ли, что Badoo не нанимает на работу в Англию? Не могу найти явный ответ.


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

На чем реализуете ML? PHP, другой язык, какой-то фреймворк, полностью своя разработка?


У нас есть очень крутые ребята в data team. В блоге есть крутой доклад от Александра Крашенинникова – к сожалению, он уже мой бывший коллега – в котором рассказано, что это за команда, какие проблемы она решает, как улучшает работу Badoo и помогает нам всем. Эта команда создала свой собственный фреймворк для ML, который очень прост в использовании и доступен всем остальным командам в компании – можно сказать, они уже сделали всю работу за нас. У них очень крутая реализация, отличная документация, очень прямолинейный подход работы с фреймворком.

Какие используется специфичные для разных БД плюшки, используя ORM?


Не до конца понимаю вопрос, но постараюсь ответить.

Я уже говорил, что основная наша БД – это MySQL, в ней хранится большая часть данных. Также мы используем Exasol, Presto, Tarantool, для специфичных задач – Aerospike; то есть, у нас есть большой набор хранилищ под каждую задачу. Мы себя не ограничиваем в выборе инструмента: если использование технологии выгодно, мы ее используем. MySQL – центральное место для нашего приложения, и мы используем разнообразные репликации, шардинги, чтобы эффективно держать нагрузку.

Ваш API – монолит?


Да.

Пришлось работать на удаленке? Сложнее стало? Как взаимодействовали?


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

Что думаете про PHP 8, планируете переходить?


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

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

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


Еще одна ситуация, когда я не могу точно ответить.

Docker мы не используем, у нас есть общее dev-окружение, которые мы используем для разработки. Наша платформа занимается, в том числе поддержанием нашего dev-окружения в рабочем состоянии для разработки, и там мы запускаем все тесты, раскладываем фичи, которые будем катить на production. То есть, у нас есть преднастроенное окружение.

Так и не понял, на чем ML: PHP, Python, что-то другое?


Раньше использовали Python для ML-фреймворка, но сейчас перешли на Spark – это сильно повысило performance.

Как балансируете нагрузку на 600 серверов? Я правильно понимаю, что это – монорепа на каждом сервере, в docker?


Монорепа на каждом сервере, но балансируется не в docker, а с помощью Nginx.

Какие у вас ожидания от кандидата на собеседовании, какие soft/hard-скиллы в среднем достаточны?


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

Очень сложно сказать, какие конкретные ожидания могут быть у компании от кандидата. Я бы сказал: если, читая описание, возникает ощущение, что ты не подходишь на эту вакансию – нужно 100% делать apply. Релевантный опыт или знания можно узнать от человека в ходе нескольких раундов интервью. На мой взгляд, нужно делать apply в любом случае.

Из hardskills – безусловно, нужно иметь опыт и понимание того, как работают PHP и MySQL – это основные технологии, которые мы используем, стек. Это если речь идет о backend-разработке, у других отделов – свой стек.

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

Используете ли автогенераторы кода, для каких задач?


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

Как осуществляется репликация баз данных для MySQL?


Могу ответить только поверхностно.
Ни для кого не секрет, что у нас есть датацентры в Европе и США, и необходимо поддерживать между ними консистентность данных. Мы используем репликацию между основными хранилищами, чтобы поддерживать в каждом месте актуальную информацию. Из-за того, что между датацентрами может быть большой replication lag, мы используем разного рода кэши для тех задач, где не критична актуальность данных.

Junior берете, или минимум mid?


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

Используете исключения, или стараетесь избегать?


Используем. И стараемся избегать.

Test-driven development, когда сначала тесты, потом код – не практикуете?


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

Что было ранее


  1. Илона Папава, Senior Software Engineer в Facebook — как попасть на стажировку, получить оффер и все о работе в компании
  2. Борис Янгель, ML-инженер Яндекса — как не пополнить ряды стремных специалистов, если ты Data Scientist
  3. Александр Калошин, СEO LastBackend — как запустить стартап, выйти на рынок Китая и получить 15 млн инвестиций.
  4. Наталья Теплухина, Vue.js core team member, GoogleDevExpret — как пройти собеседование в GitLab, попасть в команду разработчиков Vue и стать Staff-engineer.
  5. Ашот Оганесян, основатель и технический директор компании DeviceLock — кто ворует и зарабатывает на ваших персональных данных.
  6. Сания Галимова, маркетолог RUVDS — как жить и работать с психиатрическим диагнозом. Часть 1. Часть 2.
  7. Илья Кашлаков, руководитель фронтенд-отдела Яндекс.Денег — как стать тимлидом фронтендеров и как жить после этого.
  8. Влада Рау, Senior Digital Analyst в McKinsey Digital Labs — как попасть на стажировку в Google, уйти в консалтинг и переехать в Лондон.
  9. Ричард «Левелорд» Грей, создатель игр Duke Nukem 3D, SiN, Blood — про личную жизнь, любимые игры и о Москве.
  10. Вячеслав Дреер, гейм-дизайнер и продюсер игр с 12-летним стажем — про игры, их жизненный цикл и монетизацию
  11. Андрей, технический директор GameAcademy — как видеоигры помогают прокачивать реальные навыки и найти работу мечты.

Let's block ads! (Why?)