Команда Rust рада сообщить о выпуске новой версии — 1.57.0. Rust — это язык программирования, позволяющий каждому создавать надёжное и эффективное программное обеспечение.
Если вы установили предыдущую версию Rust средствами rustup, то для обновления до версии 1.57.0 вам достаточно выполнить следующую команду:
Rust 1.57 привносит panic! в константные контексты, добавляет поддержку настраиваемых профилей в Cargo и стабилизирует ошибочные API резервирования.
panic! в константных контекстах
С предыдущими версиями Rust макрос panic! не использовался в const fn и других контекстах времени компиляции. Теперь это стабилизировано. Вместе со стабилизацией panic! в const теперь можно использовать несколько других стандартных библиотечных API, например assert!.
Эта стабилизация ещё не включает в себя полную инфраструктуру форматирования, так что макрос panic! должен вызываться либо со статической строкой ( panic!("...") ), либо с одним интерполированным значением &str (panic!("{}", a)), которое должно использоваться с {} (без формата спецификаторов или других типажей).
Ожидается, что в будущем эта поддержка будет расширяться, но такая минимальная стабилизация уже позволяет проверять простые утверждения во время компиляции, например проверки размера типа:
Cargo уже давно поддерживает четыре профиля: dev, release, test и bench. В Rust 1.57 добавлена поддержка профилей с произвольными именами.
Например, если вы хотите включить оптимизацию времени компоновки (LTO) только при создании окончательной производственной сборки, добавление следующего фрагмента в Cargo.toml включает lto, когда этот профиль выбран, но избегает включения его для сборок с профилем release.
Обратите внимание, что пользовательские профили должны указывать профиль, от которого они наследуют настройки по умолчанию. После того, как профиль определён, у команд Cargo, которые строят код, можно попросить использовать его с --profile production. В настоящее время артефакты будут собираться в отдельном каталоге (target/production). Это означает, что артефакты не используются совместно между каталогами.
Обработка ошибок при выделении памяти
Rust 1.57 стабилизирует try_reserve для Vec, String, HashMap, HashSet и VecDeque. Этот API позволяет вызывающему коду обрабатывать ошибку выделения памяти для этих типов.
Rust обычно прерывает процесс, если глобальный распределитель памяти даёт сбой, что не всегда желательно. Этот API предоставляет метод, позволяющий избежать такого прерывания при работе с коллекциями из стандартной библиотеки. Однако Rust не гарантирует, что возвращаемая память фактически выделена ядром: например, если в Linux включён overcommit, память может быть недоступна при попытке её использования.
Стабилизированные API
Стабилизированы следующие методы и реализации типажей.
Следующие ранее стабилизированные API стали const:
Сегодня мы сделаем одно из самых бесполезных устройств из тех что можно собрать, но как показывает жизнь, лучше сделать что-то чем не сделать ничего тем не менее в защиту этой бесполезности можно сказать только что-то вроде: много ли интересных дел которыми мы занимаемся являются хоть сколько бы полезными?
Мы будем делать часы, таймер и игру в одном устройстве.
Некоторое вступление
Спустя много лет я решил вернуться снова к написанию статей, с новыми знаниями и силами. Знаете, интернет научил меня всему, что я знаю и даже больше чем просто всему. Интернет стал не просто учением в котором тяжело, но и боем в котором легко. И я благодарен всем кто так или иначе принял участие в моем обучении, через статьи, описание каких-то технологий, видео на YouTube и просто критику моих работ. Это герои моего времени, только благодаря им я сейчас являюсь неплохим специалистом. Ведь я не учился в этих ваших институтах и образований не получал да и всего у меня 9 классов. Спасибо тем кто пишет интернет.
И еще
В детстве, когда я только начинал гуглить какие-то схемы, я любил статьи с картинками, больше всего мне нравилось как нагляден процесс сборки, как процесс обучения реализован через картинки. Буквы придумали не для меня и вообще не для детей которые хотят заниматься электроникой. Поэтому я приложил грандиозное количество усилий чтобы эта статья могла стать для кого-то первой ступенью. Я знаю как сложно сделать первый шаг. Мое соприкосновение с контроллером случилось только в 2016 году, хотя я был знаком с ними и заочно задолго до 2016 года.
Компоненты
Приступим. Первое, что нужно для разработки любого устройства – это, подготовить все необходимые радиокомпоненты или хотя бы основные.
Не все компоненты были куплены мой, некоторые лежали без дела или появились прямо за часы перед разработкой этого устройства :)
Резисторы 150 ом 0.25 Ватт — 12 шт.
Конденсаторы 50 вольт 10 микрофарад — 4 шт.
Тактовая кнопка 6x6mm — 3 шт.
Светодиод 75x3mm — 1 шт.
Пьезо зуммер — 1 шт.
Кварцевый резонатор 16 мгц — 1 шт.
Разъём типа гребёнка — 7 шт.
Джампер (перемычка) — 1 шт.
Четырех разрядный семи сегментный индикатор (Sm56425bsr3 или аналоги) — 1 шт.
Сдвиговый регистр 74ch595 корпус DIP — 1 шт.
Панель под микросхему 74ch595 корпус DIP (16 ножек) — 1 шт.
Микроконтроллер ATmega328p корпус DIP — 1 шт.
Панель под микросхему ATmega328p корпус DIP (28 ножек) — 1 шт.
Монтажная плата 40x60mm — 2 шт.
Батарейный отсек cr2032 — 2 шт.
Батарейка cr2032 — 2 шт.
Втулка 5x8x0mm (Не точно) — 4 шт.
Болт 3x6mm (Не точно) — 4 шт.
Шайба 5mm (Не точно)— 4 шт.
Гайка 3mm (Не точно) — 4 шт.
Преобразователь USB-UART CP2102 — 1 шт.
Также рекомендую при необходимости купить флюс, припой и паяльник.
Я намеренно не указываю марку проводов, которая вам подойдет, так как совсем не владею информацией об их параметрах. Могу посоветовать МГТФ, вполне возможно, что очень хорошо подойдут. Если вы знаете, какие провода точно оптимальны, оставьте информацию в комментариях или напишите мне в личные сообщения @prohetamine.
Сдвиговый регистр 74ch595
Наверное, многим новичкам станет не по себе от понимания принципов работы микросхемы 74ch595 вне этой статьи и пропустить этот этап я просто не хочу. Сейчас я попробую максимально доступно объяснить, как она работает и чем будет полезна в конкретном случае с моим устройством.
Проще говоря, микросхема предназначена для расширения количества цифровых выходов.
Распиновка. Внимание! Рисунок имеет незначительные неточности в маркировке контактов, это сделано для более простого усвоения и понимания работы.
Самые загадочные контакты управления, которые вызывают интерес:
output pin * — контакты вывода
DS — (Serial Data Input) контакт, который определяет состояние напряжения на контактах вывода
SH — (Shift Register Clock Input) контакт, который записывает состояние которое определенно в DS
ST — (Storage Register Clock Input) контакт, который открывает микросхему для записи и закрывает, устанавливая на контакты вывода нужные состояния определенные DS
Уверен, визуальный пример поможет вам понять происходящее лучше.
Теперь, когда вы овладели работой с микросхемой, можно приступить к следующему пункту.
Тонкости
Внимание! Чтоб ничего не перепутать и ничего не испортить в том числе настроение. Не ждем, а готовимся! Просто оставлю это здесь для самых маленьких. Я конечно понимаю что всех тонкостей в рамках и без того длинной статьи мне обозначить не удастся и у вас все же могут возникнуть ошибки, пускай хотя бы не самые очевидные.
Когда мы программируем контроллер очень важно не путать rx и tx иначе контроллер просто не прошьется.
Это странная шутка, но работает очень просто, каждый разряд имеет 8 сегментов, у каждого разряда есть минус и восемь плюсов, по сути это те же светодиоды только в общем корпусе.
Каждая микросхема имеет ключ, то есть небольшую метку на корпусе, это признак помогает определить положение установки.
Я смотрю на эту схему каждый раз когда вспоминаю как припаял более 671 кнопоку не в ту сторону.. Не совершай ошибку.
Плюсик у всех новых электронных компонентов которые имеют полярность выглядит как хромоног.
Цветной хромоног.
Батарейный отсек тоже имеет свою не очевидную полярность.
Монтажная схема соединений
Так выглядит схема нашего устройства.
Но не спешите собирать, ведь собирать мы будем на плате, а не на коленке. Но сначала поговорим о некоторых спорных конструкциях. Также я буду апеллировать к своему детству: Я искренни не понимал зачем нужна обвязка, мне казались не нужными эти резисторы и конденсаторы, ведь блок питания может работать и на диодном мосте, а светодиод светится и без понижающего резистора.
Пока пин кнопки состояние которого мы читаем не притянут к плюсу или минусу он выдает случайные (101010000101010) результаты и кнопка не может работать нормально, чтобы «Стабилизировать» состояние кнопки нам нужно притянуть наш пин через резистор к минусу или плюсу, принято к минусу. Тогда при нажатии у нас будет 1 иначе 0.
В ходе первых экспериментов с высокочастотной перерисовкой индикатора появлялись ужасные гличи. Из общих соображений я решил использовать конденсаторы чтобы их сгладить и да помогло, оставляем.
Резисторы предназначены для ограничения тока исходящего от ATmega328p, а именно 5 вольт мы ограничиваем до 3-х вольт, так как почти все светодиоды ограничены напряжением в 3 вольта и привыкли работать за еду, более высокое напряжение приведет к деградации, насколько быстрой зависит от тока, хоть у ATmega328p он не большой примерно 20-40 миллиампер, деградацию и сгорание не будет видно сразу, но оно случится явно намного раньше положенного.
О нем говорят все, но никто не знает зачем он. На самом деле все просто. Предельно просто. Эта микросхема умножает количество контактов с условных трех до N. Мой максимум 265+ выводов, но возможно и больше. В этом месте мог бы возникнуть хороший вопрос, по сути ты ведь делаешь из трех контактов четыре, а остальные четыре не используешь.. ? На эту тему можно конечно рассуждать, зачем и почему, правильный ответ только один, это возможность развиваться устройству.
Монтаж компонентов
Внимание! Контакты компонентов, помеченные красным и черным маркерами, имеют полярность, будьте внимательны при монтаже, придерживайтесь рисунка.
Внимание! Соблюдайте порядок установки микросхем по ключам.
Устанавливаем панели.
Устанавливаем конденсаторы и кнопки.
Устанавливаем разъемы и пьезо зуммер.
Устанавливаем микросхемы и резисторы.
Устанавливаем индикатор, светодиод и резонатор.
Устанавливаем батарейные отсеки.
Прототип
Теперь когда у нас есть не просто бесполезная безделушка, но еще и не рабочая, нужно сделать её рабочей, поэтому добавим много магических проводков.
Объединенная схема
Соединим основные линии питания и необходимую обвязку первой платы.
Соединим кнопки, светодиод индикатор прошивки и пьезо зуммер.
Соединим конденсаторы и семисегментный индикатор с сдвиговым регистром 74ch595.
Соединим семисегментный индикатор с микроконтроллером.
В финале первая плата у вас получится такой.
Вторая плата, но тут все совсем просто. Соединим последовательно элементы питания.
Соединим все вместе.
Устройство
Программирование
Подключаем так как на картинке и можно начинать прошивать микроконтроллер
Бесспорно, абсолютно, однозначно. Мой код на C++ далек от идеала, но я, как всегда, пытался. Я пишу на JS ну вы поняли.. И тем не менее, я все равно собой доволен, хотя бы потому что не притрагиваясь и без того к незнакомому мне языку больше года, мне как-то удалось организовать не только структуру с своими правилами, а также создать богатый функционал: часы, игру и два таймера c разными уровнями точности. Можешь сделать лучше, есть что дополнить? GitHub
Основной файл проекта к которому я подключаю все остальные файлы и библиотеку AsyncDelay с которой управлять синхронным потоком становится проще чем обычно имхо. Изначально в процессе написания кода я обозначил для себя два компонента это actionDriver и actionContoller. где первый переводя на JavaScript-тянский является почти как Event Loop, то есть выполняет стек задач только не событийных, а перманентных, а второй выполняет роль Setter'a.
// Подключаем библиотеку
#include <AsyncDelay.h>
// Назначаем имена прерываний
AsyncDelay delayRenderRowFirst;
AsyncDelay delayRenderRowTwo;
AsyncDelay delayRenderRowThree;
AsyncDelay delayRenderRowFour;
AsyncDelay delayAnimaton;
AsyncDelay delayButtonHandle;
// Определяем пины кнопок
const int BTN_SET_TOP = A5
, BTN_SET_MIDDLE = A4
, BTN_SET_BOTTOM = A3;
// Определяем пины сдвигового регистра
const int DS = 11
, ST_CP = 10
, SH_CP = 9;
// Определяем пины семисигментного индикатора
const int A = 2
, B = 4
, C = 7
, D = 5
, E = 1
, F = 3
, G = 8
, DP = 6;
// Определяем пины светодиода и зуммера
const int BLINK = 13;
const int SIGNAL = 12;
// подключаем модули проекта
#include "viewer.h"
#include "animation.h"
#include "time.h"
#include "mtimer.h"
#include "timer.h"
#include "gameUnLocker.h"
#include "button.h"
void setup () {
// назнчаем интервалы прерываний
delayRenderRowFirst.start(1, AsyncDelay::MILLIS);
delayRenderRowTwo.start(1, AsyncDelay::MILLIS);
delayRenderRowThree.start(1, AsyncDelay::MILLIS);
delayRenderRowFour.start(1, AsyncDelay::MILLIS);
delayButtonHandle.start(1000, AsyncDelay::MILLIS);
delayAnimaton.start(500, AsyncDelay::MILLIS);
// Устанавливаем пины на выход
pinMode(A, OUTPUT);
pinMode(B, OUTPUT);
pinMode(C, OUTPUT);
pinMode(D, OUTPUT);
pinMode(E, OUTPUT);
pinMode(F, OUTPUT);
pinMode(G, OUTPUT);
pinMode(DP, OUTPUT);
pinMode(BLINK, OUTPUT);
pinMode(SIGNAL, OUTPUT);
pinMode(DS, OUTPUT);
pinMode(ST_CP, OUTPUT);
pinMode(SH_CP, OUTPUT);
pinMode(BTN_SET_TOP, INPUT);
pinMode(BTN_SET_MIDDLE, INPUT);
pinMode(BTN_SET_BOTTOM, INPUT);
// Сбрасываем все значения на пинах
digitalWrite(A, 0);
digitalWrite(B, 0);
digitalWrite(C, 0);
digitalWrite(D, 0);
digitalWrite(E, 0);
digitalWrite(F, 0);
digitalWrite(G, 0);
digitalWrite(DP, 0);
// Сбрасываем значения на сдвиговом регистре
digitalWrite(ST_CP, 0);
for (int i = 0; i < 8; i++) {
digitalWrite(SH_CP, 0);
digitalWrite(DS, 1);
digitalWrite(SH_CP, 1);
}
digitalWrite(ST_CP, 1);
}
// Активируем анимацию
boolean aminationStartActive = true;
void loop () {
// Устанавливаем драйвера в общик поток
viewDriver();
buttonDriver();
timeDriver();
mTimerDriver();
timerDriver();
gameUnLockerDriver();
animationDriver();
// Останавливаем анимацию и запускам виджет времени
if (millis() > 3000 && aminationStartActive) {
aminationStartActive = false;
timeShow = true;
}
// Запускаем анимацию
if (millis() < 1) {
animationController(false, "hey ");
}
}
Управление устройством
// Режим работы
int modeId = -1;
// Перемещение между режимами
int selectId = 0;
// Премещение между символами
int carret = 0;
/*
* Режимы работ
* 0 - время
* 1 - таймер
* 2 - минутный таймер
* 3 - игра
*/
// Состояния кнопок
int clickedFirstButton = 0;
int clickedMiddleButton = 0;
int clickedLastButton = 0;
// Управление состоянием кнопок
void buttonController (int first, int middle, int last) {
if (first != -1) {
clickedFirstButton = first;
}
if (middle != -1) {
clickedMiddleButton = middle;
}
if (last != -1) {
clickedLastButton = last;
}
}
// Прекращает работу всех виджетов
void mainOffControllers () {
timeController(false);
mTimerController(false);
timerController(false);
gameUnLockerController(false);
}
// Меню
void menuList () {
if (selectId == 0) {
viewController(0, String('c'));
viewController(1, String(' '));
viewController(2, String(' '));
viewController(3, String(' '));
}
if (selectId == 1) {
viewController(0, String('t'));
viewController(1, String(' '));
viewController(2, String(' '));
viewController(3, String(' '));
}
if (selectId == 2) {
viewController(0, String('m'));
viewController(1, String('t'));
viewController(2, String(' '));
viewController(3, String(' '));
}
if (selectId == 3) {
viewController(0, String('g'));
viewController(1, String(' '));
viewController(2, String(' '));
viewController(3, String(' '));
}
}
// Обработчик первой кнопки
void buttonFristEvent (int clickedFirst, int clickedMiddle, int clickedLast) {
if (modeId == -1) {
selectId++;
if (selectId > 3) {
selectId = 0;
}
mainOffControllers();
menuList();
}
if (clickedLast == 1 || clickedLast == 2 || clickedLast == 3 || clickedLast == 4) {
if (modeId == 0) {
for (int i = 0; i < 4; i++) {
if (carret == i) {
timeTickerController(i, 1);
viewController(carret, String(timeTicker[i]));
}
}
}
if (modeId == 1) {
for (int i = 0; i < 4; i++) {
if (carret == i) {
timerTickerController(i, 1);
viewController(carret, String(timerTicker[i]));
}
}
}
if (modeId == 2) {
for (int i = 0; i < 4; i++) {
if (carret == i) {
mTimerTickerController(i, 1);
viewController(carret, String(mTimerTicker[i]));
}
}
}
if (modeId == 3) {
for (int i = 0; i < 4; i++) {
if (carret == i) {
gameUnLockerPlayerController(i, 1);
viewController(carret, String(gameUnLockerData[i]));
}
}
}
}
}
// Обработчик второй кнопки
void buttonMiddleEvent (int clickedFirst, int clickedMiddle, int clickedLast) {
if (modeId == -1) {
selectId--;
if (selectId < 0) {
selectId = 3;
}
mainOffControllers();
menuList();
}
if (clickedLast == 1 || clickedLast == 2 || clickedLast == 3 || clickedLast == 4) {
if (modeId == 0) {
for (int i = 0; i < 4; i++) {
if (carret == i) {
timeTickerController(i, -1);
viewController(carret, String(timeTicker[i]));
}
}
}
if (modeId == 1) {
for (int i = 0; i < 4; i++) {
if (carret == i) {
timerTickerController(i, -1);
viewController(carret, String(timerTicker[i]));
}
}
}
if (modeId == 2) {
for (int i = 0; i < 4; i++) {
if (carret == i) {
mTimerTickerController(i, -1);
viewController(carret, String(mTimerTicker[i]));
}
}
}
if (modeId == 3) {
for (int i = 0; i < 4; i++) {
if (carret == i) {
gameUnLockerPlayerController(i, -1);
viewController(carret, String(gameUnLockerData[i]));
}
}
}
}
}
// Обработчик третьей кнопки
void buttonLastEvent (int clickedFirst, int clickedMiddle, int clickedLast) {
if (modeId == -1) {
modeId = selectId;
buttonController(0, 0, 0);
if (modeId == 0) {
timeController(true);
}
if (modeId == 1) {
timerController(true);
}
if (modeId == 2) {
mTimerController(true);
}
if (modeId == 3) {
gameUnLockerController(true);
}
return;
}
if (clickedLast == 1 && modeId == 0) {
mainOffControllers();
for (int i = 0; i < 4; i++) {
viewController(i, String(timeTicker[i]));
}
viewController(carret, String('_'));
animationController(false, "edit");
return;
}
if (modeId == 0) {
for (int i = 0; i < 4; i++) {
viewController(i, String(timeTicker[i]));
}
if (carret < 3) {
carret++;
viewController(carret, String('_'));
return;
} else {
carret = 0;
modeId = -1;
timeController(true);
buttonController(0, 0, 0);
}
}
if (clickedLast == 1 && modeId == 1) {
mainOffControllers();
for (int i = 0; i < 4; i++) {
viewController(i, String(timerTicker[i]));
}
viewController(carret, String('_'));
animationController(false, "edit");
return;
}
if (modeId == 1) {
for (int i = 0; i < 4; i++) {
viewController(i, String(timerTicker[i]));
}
if (carret < 3) {
carret++;
viewController(carret, String('_'));
return;
} else {
carret = 0;
modeId = -1;
timerController(true);
buttonController(0, 0, 0);
}
}
if (clickedLast == 1 && modeId == 2) {
mainOffControllers();
for (int i = 0; i < 4; i++) {
viewController(i, String(mTimerTicker[i]));
}
viewController(carret, String('_'));
animationController(false, "edit");
return;
}
if (modeId == 2) {
for (int i = 0; i < 4; i++) {
viewController(i, String(mTimerTicker[i]));
}
if (carret < 3) {
carret++;
viewController(carret, String('_'));
return;
} else {
carret = 0;
modeId = -1;
mTimerController(true);
buttonController(0, 0, 0);
}
}
if (clickedLast == 1 && modeId == 3) {
mainOffControllers();
for (int i = 0; i < 4; i++) {
viewController(i, String(gameUnLockerData[i]));
}
viewController(carret, String('_'));
animationController(false, "play");
return;
}
if (modeId == 3) {
for (int i = 0; i < 4; i++) {
viewController(i, String(gameUnLockerData[i]));
}
if (carret < 3) {
carret++;
viewController(carret, String('_'));
return;
} else {
carret = 0;
modeId = -1;
gameUnLockerController(true);
buttonController(0, 0, 0);
}
}
}
// Отслеживает состояние кнопок
boolean buttonDriverFlag = false;
// Ловит нажатия кнопок
void buttonDriver () {
if (delayButtonHandle.isExpired()) {
if (analogRead(BTN_SET_TOP) >= 128) {
if (!buttonDriverFlag) {
tone(SIGNAL, 1700, 50);
clickedFirstButton++;
buttonFristEvent(clickedFirstButton, clickedMiddleButton, clickedLastButton);
}
buttonDriverFlag = true;
return;
}
if (analogRead(BTN_SET_MIDDLE) >= 128) {
if (!buttonDriverFlag) {
tone(SIGNAL, 1700, 50);
clickedMiddleButton++;
buttonMiddleEvent(clickedFirstButton, clickedMiddleButton, clickedLastButton);
}
buttonDriverFlag = true;
return;
}
if (analogRead(BTN_SET_BOTTOM) >= 128) {
if (!buttonDriverFlag) {
tone(SIGNAL, 1700, 50);
clickedLastButton++;
buttonLastEvent(clickedFirstButton, clickedMiddleButton, clickedLastButton);
}
buttonDriverFlag = true;
return;
}
buttonDriverFlag = false;
}
}
Отрисовка на семисигментном индикаторе
/*
* A
* @@@@@@@@@@@@@@@@@
* @@@@@@@@@@@@@@@@@
* @@ @@
* @@@ @@@
* @@@ @@@
* @@@ @@@
* @@@ F @@@ B
* @@@ @@@
* @@@ @@
* @@@ @@@
* @@ G @@@
* @@@@@@@@@@@@@@@@@@
* @@@@@@@@@@@@@@@@@
* @@ @@@
* @@@ @@@
* @@@ @@@
* @@ @@@
* @@@ E @@@ C
* @@@ @@@
* @@ @@@
* @@@ @@@
* @@@ D @@@
* @@@@@@@@@@@@@@@@@@ @@
* @@@@@@@@@@@@@@@@@ @@ DP
*
*
*/
// Состояние
int valueRenderRow[4][8] = {
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 }
};
// Состояние двоеточия
boolean dotShow = false;
// Отрисовывет отдельный символ
void view (int* symbol, int offset) {
digitalWrite(A, 0);
digitalWrite(B, 0);
digitalWrite(C, 0);
digitalWrite(D, 0);
digitalWrite(E, 0);
digitalWrite(F, 0);
digitalWrite(G, 0);
digitalWrite(DP, 0);
digitalWrite(ST_CP, 0);
for (int i = 0; i < 8; i++) {
digitalWrite(SH_CP, 0);
digitalWrite(DS, 1);
digitalWrite(SH_CP, 1);
}
digitalWrite(ST_CP, 1);
delayMicroseconds(1000);
for (int r = 0; r < 10; r++) {
digitalWrite(A, symbol[0]);
digitalWrite(B, symbol[1]);
digitalWrite(C, symbol[2]);
digitalWrite(D, symbol[3]);
digitalWrite(E, symbol[4]);
digitalWrite(F, symbol[5]);
digitalWrite(G, symbol[6]);
digitalWrite(DP, symbol[7]);
if (dotShow && offset == 4) {
digitalWrite(DP, 1);
} else {
digitalWrite(DP, 0);
}
digitalWrite(ST_CP, 0);
for (int i = 0; i < 8; i++) {
digitalWrite(SH_CP, 0);
digitalWrite(DS, i != offset);
digitalWrite(SH_CP, 1);
}
digitalWrite(ST_CP, 1);
}
delayMicroseconds(1000);
}
// Отрисовывет символы
void viewDriver () {
int OFFSET_0 = 6;
int OFFSET_1 = 4;
int OFFSET_2 = 3;
int OFFSET_3 = 5;
if (delayRenderRowFirst.isExpired()) {
view(valueRenderRow[0], OFFSET_0);
}
if (delayRenderRowTwo.isExpired()) {
view(valueRenderRow[1], OFFSET_1);
}
if (delayRenderRowThree.isExpired()) {
view(valueRenderRow[2], OFFSET_2);
}
if (delayRenderRowFour.isExpired()) {
view(valueRenderRow[3], OFFSET_3);
}
}
// Контроллер управляющий символами
void viewController (int offset, String symbol) {
String viewSymbol = "00000000";
if (symbol[0] == '0') { viewSymbol = "11111100"; }
if (symbol[0] == '1') { viewSymbol = "01100000"; }
if (symbol[0] == '2') { viewSymbol = "11011010"; }
if (symbol[0] == '3') { viewSymbol = "11110010"; }
if (symbol[0] == '4') { viewSymbol = "01100110"; }
if (symbol[0] == '5') { viewSymbol = "10110110"; }
if (symbol[0] == '6') { viewSymbol = "10111110"; }
if (symbol[0] == '7') { viewSymbol = "11100000"; }
if (symbol[0] == '8') { viewSymbol = "11111110"; }
if (symbol[0] == '9') { viewSymbol = "11110110"; }
if (symbol[0] == ' ') { viewSymbol = "00000000"; }
if (symbol[0] == '_') { viewSymbol = "00010000"; }
if (symbol[0] == 'a') { viewSymbol = "11101110"; }
if (symbol[0] == 'b') { viewSymbol = "00111110"; }
if (symbol[0] == 'c') { viewSymbol = "00011010"; }
if (symbol[0] == 'd') { viewSymbol = "01111010"; }
if (symbol[0] == 't') { viewSymbol = "00011110"; }
if (symbol[0] == 'e') { viewSymbol = "10011110"; }
if (symbol[0] == 'f') { viewSymbol = "10001110"; }
if (symbol[0] == 'g') { viewSymbol = "11110110"; }
if (symbol[0] == 'h') { viewSymbol = "00101110"; }
if (symbol[0] == 'i') { viewSymbol = "00001100"; }
if (symbol[0] == 'j') { viewSymbol = "01111000"; }
if (symbol[0] == 'k') { viewSymbol = "01101110"; }
if (symbol[0] == 'l') { viewSymbol = "00011100"; }
if (symbol[0] == 'm') { viewSymbol = "00101010"; }
if (symbol[0] == 'n') { viewSymbol = "00101010"; }
if (symbol[0] == 'o') { viewSymbol = "00111010"; }
if (symbol[0] == 'p') { viewSymbol = "11001110"; }
if (symbol[0] == 'q') { viewSymbol = "11100110"; }
if (symbol[0] == 'r') { viewSymbol = "11001100"; }
if (symbol[0] == 's') { viewSymbol = "10110110"; }
if (symbol[0] == 't') { viewSymbol = "00011110"; }
if (symbol[0] == 'u') { viewSymbol = "00111000"; }
if (symbol[0] == 'v') { viewSymbol = "01111100"; }
if (symbol[0] == 'w') { viewSymbol = "00111000"; }
if (symbol[0] == 'x') { viewSymbol = "01101110"; }
if (symbol[0] == 'x') { viewSymbol = "01101110"; }
if (symbol[0] == 'y') { viewSymbol = "01110110"; }
if (symbol[0] == 'z') { viewSymbol = "11011010"; }
for (int i = 0; i < 8; i++) {
valueRenderRow[offset][i] = String(viewSymbol[i]).toInt();
}
}
// Контроллер отвечающий за двоеточие
void viewControllerDot (boolean isShow) {
dotShow = isShow;
}
Анимации переходов
// Состояние переходов анимации
float animationTicker = -1;
// Звук в анимации
int animationSound = true;
// Сообщение анимации
String animationMessage = "";
// Последнее отрисовоное состояние, так как я не понял как
// работают коллбеки и есть ли они вообще, я придумал свой способ
// тут я храню то что было отрисованно в основном стейте чтобы показать его после анимации
int animationSaveValueRow[4][8] = {
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0 }
};
// Сохраняем основное состояние
void animationSaveState () {
for (int y = 0; y < 4; y++) {
for (int x = 0; x < 8; x++) {
animationSaveValueRow[y][x] = valueRenderRow[y][x];
}
}
}
// Восстанавливаем состояние
void animationPushState () {
for (int y = 0; y < 4; y++) {
for (int x = 0; x < 8; x++) {
valueRenderRow[y][x] = animationSaveValueRow[y][x];
}
}
}
// Отрисовывем анимацию
void animationDriver () {
if (delayAnimaton.isExpired()) {
if (animationTicker != -1) {
animationTicker += 0.2;
}
if (animationTicker > 32 || (animationMessage.length() == 0 && animationTicker > 16)) {
animationPushState();
animationTicker = -1;
return;
}
if (animationTicker == -1) {
return;
}
viewController(0, String(' '));
viewController(1, String(' '));
viewController(2, String(' '));
viewController(3, String(' '));
if (animationSound) {
if (int(animationTicker) > 23) {
tone(SIGNAL, (100 * (int(animationTicker) + 1) + 500), 50);
} else {
if (int(animationTicker) % 23) {
tone(SIGNAL, (20 * (int(animationTicker) + 1) + 500), 50);
} else {
noTone(SIGNAL);
}
}
}
if (animationMessage.length() != 0 && int(animationTicker) > 8 && int(animationTicker) < 24) {
for (int x = 0; x < 4; x++) {
viewController(x, String(animationMessage[x]));
}
return;
} else {
if (int(animationTicker) % 8 < 4) {
viewController(int(animationTicker) % 4, String('0'));
return;
} else {
viewController(int(animationTicker) % 4, String(' '));
return;
}
}
}
}
// Контроллируем состояние анимации
void animationController (boolean isSound, String message) {
animationSaveState();
animationMessage = message;
animationSound = isSound;
animationTicker = 0;
}
Виджеты
Время
// Изначательное время
int timeTicker[4] = { 1,2,4,8 };
// Счетчик секунд
int timeSecond = 0;
// Последнее время внутреннего счетчика микроконтроллера
uint32_t timeDelta;
// Флаг включающий и отключающий виджет
boolean timeShow = false;
// Управление виджетом
void timeController (boolean isShow) {
timeShow = isShow;
viewControllerDot(isShow);
}
// Управление состоянием виджета
void timeTickerController (int offset, int n) {
if (offset == 0) {
timeTicker[0] += n;
if (timeTicker[0] > 2) {
timeTicker[0] = 0;
}
if (timeTicker[0] < 0) {
timeTicker[0] = 2;
}
}
if (offset == 1) {
timeTicker[1] += n;
if (timeTicker[0] < 2) {
if (timeTicker[1] < 0) {
timeTicker[1] = 9;
}
if (timeTicker[1] > 9) {
timeTicker[1] = 0;
}
} else {
if (timeTicker[1] < 0) {
timeTicker[1] = 3;
}
if (timeTicker[1] > 3) {
timeTicker[1] = 0;
}
}
}
if (offset == 2) {
timeTicker[2] += n;
if (timeTicker[2] > 5) {
timeTicker[2] = 0;
}
if (timeTicker[2] < 0) {
timeTicker[2] = 5;
}
}
if (offset == 3) {
timeTicker[3] += n;
if (timeTicker[3] > 9) {
timeTicker[3] = 0;
}
if (timeTicker[3] < 0) {
timeTicker[3] = 9;
}
}
}
// Отвечает за ход времени
void timeDriver () {
if (timeShow && millis() - timeDelta >= 1000) {
timeDelta = millis();
timeSecond++;
viewControllerDot(timeSecond % 2 == 0);
if (timeSecond > 59) {
timeSecond = 0;
timeTicker[3]++;
if (timeTicker[3] > 9) {
timeTicker[3] = 0;
timeTicker[2]++;
if (timeTicker[2] > 5) {
timeTicker[2] = 0;
timeTicker[1]++;
if ((timeTicker[0] < 2 && timeTicker[1] > 9) || (timeTicker[0] == 2 && timeTicker[1] > 3)) {
timeTicker[1] = 0;
timeTicker[0]++;
if (timeTicker[0] > 2) {
timeTicker[0] = 0;
}
}
}
}
}
viewController(0, String(timeTicker[0]));
viewController(1, String(timeTicker[1]));
viewController(2, String(timeTicker[2]));
viewController(3, String(timeTicker[3]));
}
}
Таймер
// Изначальное время
int timerTicker[4] = { 0, 0, 3, 0 };
// Счетчик секунд
int timerSecond = 59;
// Последнее время внутреннего счетчика микроконтроллера
uint32_t timerDelta;
// Флаг включающий и отключающий виджет
boolean timerShow = false;
// Управление виджетом
void timerController (boolean isShow) {
timerShow = isShow;
viewControllerDot(isShow);
}
// Управление состоянием виджета
void timerTickerController (int offset, int n) {
if (offset == 0) {
timerTicker[0] += n;
if (timerTicker[0] > 2) {
timerTicker[0] = 0;
}
if (timerTicker[0] < 0) {
timerTicker[0] = 2;
}
}
if (offset == 1) {
timerTicker[1] += n;
if (timerTicker[0] < 2) {
if (timerTicker[1] < 0) {
timerTicker[1] = 9;
}
if (timerTicker[1] > 9) {
timerTicker[1] = 0;
}
} else {
if (timerTicker[1] < 0) {
timerTicker[1] = 3;
}
if (timerTicker[1] > 3) {
timerTicker[1] = 0;
}
}
}
if (offset == 2) {
timerTicker[2] += n;
if (timerTicker[2] > 5) {
timerTicker[2] = 0;
}
if (timerTicker[2] < 0) {
timerTicker[2] = 5;
}
}
if (offset == 3) {
timerTicker[3] += n;
if (timerTicker[3] > 9) {
timerTicker[3] = 0;
}
if (timerTicker[3] < 0) {
timerTicker[3] = 9;
}
}
}
// Отвечает за обратный отсчет
void timerDriver () {
if (timerShow && millis() - timerDelta >= 1000) {
timerDelta = millis();
timerSecond--;
viewControllerDot(timerSecond % 2 == 0);
if (timerSecond < 0) {
timerSecond = 60;
timerTicker[3]--;
if (timerTicker[3] < 0) {
timerTicker[3] = 9;
timerTicker[2]--;
if (timerTicker[2] < 0) {
timerTicker[2] = 5;
timerTicker[1]--;
if (timerTicker[1] < 0) {
timerTicker[1] = 9;
timerTicker[0]--;
if (timerTicker[0] < 0) {
timerTicker[0] = 0;
}
}
}
}
}
viewController(0, String(timerTicker[0]));
viewController(1, String(timerTicker[1]));
viewController(2, String(timerTicker[2]));
viewController(3, String(timerTicker[3]));
if (
timerTicker[0] == 0 &&
timerTicker[1] == 0 &&
timerTicker[2] == 0 &&
timerTicker[3] == 0
) {
timerController(false);
viewControllerDot(false);
animationController(true, "end");
}
}
}
Минутный таймер
// Изначальное время
int mTimerTicker[4] = { 0, 0, 0, 5 };
// Последнее время внутреннего счетчика микроконтроллера
uint32_t mTimerDelta;
// Флаг включающий и отключающий виджет
boolean mTimerShow = false;
// Управление виджетом
void mTimerController (boolean isShow) {
mTimerShow = isShow;
viewControllerDot(isShow);
}
// Управление состоянием виджета
void mTimerTickerController (int offset, int n) {
if (offset == 0) {
mTimerTicker[0] += n;
if (mTimerTicker[0] > 5) {
mTimerTicker[0] = 0;
}
if (mTimerTicker[0] < 0) {
mTimerTicker[0] = 5;
}
}
if (offset == 1) {
mTimerTicker[1] += n;
if (mTimerTicker[1] > 9) {
mTimerTicker[1] = 0;
}
if (mTimerTicker[1] < 0) {
mTimerTicker[1] = 9;
}
}
if (offset == 2) {
mTimerTicker[2] += n;
if (mTimerTicker[2] > 5) {
mTimerTicker[2] = 0;
}
if (mTimerTicker[2] < 0) {
mTimerTicker[2] = 5;
}
}
if (offset == 3) {
mTimerTicker[3] += n;
if (mTimerTicker[3] > 9) {
mTimerTicker[3] = 0;
}
if (mTimerTicker[3] < 0) {
mTimerTicker[3] = 9;
}
}
}
// Отвечает за обратный отсчет
void mTimerDriver () {
if (mTimerShow && millis() - mTimerDelta >= 1000) {
mTimerDelta = millis();
mTimerTicker[3]--;
viewControllerDot(mTimerTicker[3] % 2 == 0);
if (mTimerTicker[3] < 0) {
mTimerTicker[3] = 9;
mTimerTicker[2]--;
if (mTimerTicker[2] < 0) {
mTimerTicker[2] = 5;
mTimerTicker[1]--;
if (mTimerTicker[1] < 0) {
mTimerTicker[1] = 9;
mTimerTicker[0]--;
if (mTimerTicker[0] < 0) {
mTimerTicker[0] = 5;
}
}
}
}
viewController(0, String(mTimerTicker[0]));
viewController(1, String(mTimerTicker[1]));
viewController(2, String(mTimerTicker[2]));
viewController(3, String(mTimerTicker[3]));
if (
mTimerTicker[0] == 0 &&
mTimerTicker[1] == 0 &&
mTimerTicker[2] == 0 &&
mTimerTicker[3] == 0
) {
mTimerController(false);
viewControllerDot(false);
animationController(true, "end");
}
}
}
Игра
// Изначальное состояние
int gameUnLockerData[4] = { 0,0,0,0 };
// Состояние измененное случайным образом
int gameUnLockerHiddenData[4] = {
random(0, 9),
random(0, 9),
random(0, 9),
random(0, 9)
};
// Флаг включающий и отключающий виджет
boolean gameUnLockerShow = false;
// Управление виджетом
void gameUnLockerController (boolean isShow) {
gameUnLockerShow = isShow;
}
// Управление состояние виджета
void gameUnLockerPlayerController (int offset, int n) {
if (offset == 0) {
gameUnLockerData[0] += n;
if (gameUnLockerData[0] > 9) {
gameUnLockerData[0] = 0;
}
if (gameUnLockerData[0] < 0) {
gameUnLockerData[0] = 9;
}
}
if (offset == 1) {
gameUnLockerData[1] += n;
if (gameUnLockerData[1] > 9) {
gameUnLockerData[1] = 0;
}
if (gameUnLockerData[1] < 0) {
gameUnLockerData[1] = 9;
}
}
if (offset == 2) {
gameUnLockerData[2] += n;
if (gameUnLockerData[2] > 9) {
gameUnLockerData[2] = 0;
}
if (gameUnLockerData[2] < 0) {
gameUnLockerData[2] = 9;
}
}
if (offset == 3) {
gameUnLockerData[3] += n;
if (gameUnLockerData[3] > 9) {
gameUnLockerData[3] = 0;
}
if (gameUnLockerData[3] < 0) {
gameUnLockerData[3] = 9;
}
}
}
// Обработка состояния виджета
void gameUnLockerDriver () {
if (gameUnLockerShow) {
if (
gameUnLockerData[0] == gameUnLockerHiddenData[0] &&
gameUnLockerData[1] == gameUnLockerHiddenData[1] &&
gameUnLockerData[2] == gameUnLockerHiddenData[2] &&
gameUnLockerData[3] == gameUnLockerHiddenData[3]
) {
viewController(0, String('g'));
viewController(1, String('o'));
viewController(2, String('o'));
viewController(3, String('d'));
} else {
viewController(0, String('b'));
viewController(1, String('a'));
viewController(2, String('d'));
viewController(3, String(' '));
}
}
}
Демонстрационная версия
Я искренне надеюсь, что это получился хороший материал для начинающих пробовать себя в электронике и программировании. Полный код проекта вы также можете найти на GitHub.
Также пользуясь случаем передаю привет своему другу Каро, в гараже которого было собранно это устройство осенью 2019 года.
По информации российского издания Forbes, 1 декабря Apple подала в суд на Федеральную антимонопольную службу (ФАС) России. Американская компания хочет отменить предписание регулятора по поводу обязательного внедрения в App Store возможности альтернативных способов оплаты в приложениях для iOS. Технически Apple должна была исправить это нарушение до 30 сентября, но ответный иск по оспариванию решения ФАС теперь отодвинет эту дату на время нового разбирательства.
Помимо заявления Apple о признании ненормативных правовых актов ФАС недействительными, юристы американской компании передали в российский суд ходатайство о приобщении к делу дополнительных документов, которые должны дополнительно повлиять на процесс и показать, что Apple действует в рамках законодательных норм страны.
27 октября 2021 года ФАС объявила о возбуждении дела в отношении Apple в связи со злоупотреблением доминирующим положением на рынке приложений для iOS. Компании может грозить миллиардный оборотный штраф от суммы выручки на российском рынке за невыполнение в установленный срок предупреждения антимонопольной службы.
ФАС пояснила, что в некоторых случаях купить, например, электронную книгу дешевле на сайте продавца, так как у Apple предусмотрена комиссия 15–30% с каждого платежа в App Store. Также компания запрещает разработчикам информировать в своих программах пользователей магазина приложений о наличии альтернативного способа оплаты товаров или услуг любым способом.
Надзорное ведомство выяснило, что подобные действия Apple ограничивают самостоятельность разработчиков, что негативно сказывается на конкуренции и может привести к росту цен на их продукцию или услуги. ФАС считает, что Apple нарушила пункт 3 части 1 статьи 10 ФЗ «О защите конкуренции».
9 октября федеральный судья в Окленде (США, штат Калифорния) отклонил апелляцию Apple на отсрочку введения альтернативных способов оплаты в App Store. Предыдущее судебное предписание, обязывающее компанию позволять разработчикам предоставлять пользователям ссылки на сторонние сервисы для оплаты покупок вне Apple Pay, вступит в силу в декабре этого года. Компания утверждает, что ей нужно больше времени, чтобы осуществить техническую поддержку нововведения.
В начале сентября Apple пояснила, что разрешит приложениям с музыкой, видео и книгами не платить комиссию в AppStore. Apple пообещала разрешить разработчикам приложений с музыкой, видео и книгами не платить 30% комиссию в AppStore. Разработчики смогут добавлять в свои приложения прямые ссылки на собственные сайты, где пользователи самостоятельно смогут управлять профилем и платежами. При этом продавать подписки в обход Apple всё равно нельзя. Изменения вступят в силу в начале следующего года.
30 августа 2021 года Федеральная антимонопольная служба РФ вынесла предупреждение Apple из-за злоупотребления доминирующим положением на рынке приложений для iOS. ФАС потребовала от Apple разрешить разработчикам информировать покупателей приложений в App Store об альтернативных способах оплаты. Регулятор дала компании срок до 30 сентября на исправление ситуации.
6 августа Apple согласилась поменять несколько правил использования App Store в ответ на коллективные судебные иски от разработчиков из США. Они жаловались, что Apple не дает развиваться малому бизнесу и просили разрешить альтернативные платежи в магазине приложений. Компания разрешила разработчикам сообщать пользователям о возможности альтернативных платежей за подписку, но только не внутри своих приложений в App Store. Если покупатель приложения согласится передать автору данные о своей электронной почте, то разработчик приложения может отправлять пользователям информацию о возможности оплаты подписки за пределами App Store.
27 апреля ФАС наложила оборотный штраф на Apple в размере 906 млн рублей (более $12 млн) за нарушение антимонопольного законодательства РФ. Регулятор подверг Apple такому административному наказанию в рамках антимонопольного дела, возбужденного после заявления «Лаборатории Касперского» в марте 2019 года, по части 2 статьи 14.31 КоАП РФ. В августе 2020 года ведомство завершило рассмотрение этого дела и установило, что Apple действительно злоупотребляла своим доминирующим положением на рынке. Apple также оспаривает этот штраф.
Арахнофобия – одна из самых распространённых фобий, обладатели которой панически боятся представителей класса паукообразных: пауков, скорпионов и клещей. По данным Американской психологической ассоциации, примерно 6-8 % жителей Западного мира и около 2-4% людей в странах Азии, Африки и Латинской Америки страдают как минимум одной специфической фобией, и почти в 40% случаев иррациональные страхи связаны со всевозможными ползучими тварями. Почему же боязнь пауков и их восьминогих собратьев так глубоко укоренилась в человеческом сознании?
По словам Алана Манавитца – клинического психиатра из нью-йоркского госпиталя Ленокс-Хилл, благодаря жизненному опыту, науке, СМИ и жизни в обществе мы знаем, что многие пауки ядовиты. Это знание провоцирует естественную реакция в виде страха, когда человек видит паука. Впрочем, на протяжении многих лет в научном сообществе идёт спор о происхождении этого страха. Согласно одной из наиболее популярных теорий, виной иррациональной боязни паукообразных является окружение человека. В частности, развитие арахнофобии значительно более вероятно в пределах Западного мира, чем, например, в Камбодже, где жаренные тарантулы и скорпионы считаются деликатесом.
Тем временем сторонники природной теории считают, что страх пауков заложен в нас как механизм выживания, сохранившийся со времён первого знакомства наших предков с восьминогими тварями. Наблюдая за поведением паукообразных, древние люди пришли к выводу о том, что многие из них ядовиты. Однако прародители человечества не догадывались, что далеко не все пауки имеют в своём распоряжении достаточно мощные зубы-хелицеры, чтобы прокусить человеческую кожу. Исходя из актуальных научных данных, среди более чем 35 тысяч видов пауков менее 20 представляют для людей реальную угрозу. Эволюционные психологи предполагают, что арахнофобия стала следствием неспособности определить, какие именно пауки могут причинить реальный вред здоровью человека.
К слову, эволюционную гипотезу подтверждают многочисленные исследования. К примеру, в 2017 году команда учёных из Института мозга человека и когнитивных наук им. Макса Планка провела эксперимент с участием 6-месячных детей. Испытуемым показывали изображения различных животных и растений, наблюдая за состоянием их зрачков. Исследование показало, что зрачки младенцев расширялись больше всего при взгляде на змей и пауков, что в сочетании с другими физическими признаками служило свидетельством сильного страха. Такие итоги эксперимента позволили учёным предположить, что боязнь может быть врождённой и не обусловленной каким-либо неприятным ассоциативным опытом.
По мнению экспертов издания Psychology Today, за развитием арахнофобии может стоять интенсивное отвращение к объекту фобии. Несколько исследований показали, что образ паукообразных полон атрибутов, которые вызывают у людей ощущение неприязни и даже омерзения: ядовитость, волосяной покров, количество ног или глаз, непредсказуемость движений и т. д. Учёные считают, что нередко люди не в состоянии отличить физические проявления отвращения от признаков страха, вследствие чего возникает иррациональная боязнь. В частности, оба состояния имеют схожие симптомы: расширенные зрачки, ускоренный пульс, тошнота. Кроме того, при встрече с объектом фобии люди нередко хмурятся и морщат нос. С точки зрения эволюционной нейробиологии, отвращение, как и страх, играет важную роль для самосохранения живых организмов.
Многолетний опыт поведенческих психотерапевтов показывает, что арахнофобия наиболее успешно поддаётся лечению при помощи экспозиционной терапии. Данная техника подразумевает прямое столкновение обладателя фобии с её источником в безопасной, контролируемой обстановке. Суть метода заключается в образовании новых нейронных связей при помощи создания позитивных ассоциаций и воспоминаний.
Неплохие результаты также показало экспериментальное применение пропранолола в сочетании с экспозиционной терапией. В 2016 году исследование, опубликованное в журнале Biological Psychiatry, отметило заметный прогресс в преодолении иррационального страха спустя всего несколько дней приёма препарата, сопряжённого с взаимодействием с тарантулом в течение всего двух минут. Через 3 месяца участники эксперимента могли держать паука в руках, а спустя год арахнофобия полностью излечилась. По словам исследователей, такой эффект связан со способностью пропранолола уменьшать эмоциональное воздействие сформированных негативных воспоминаний.
В 2014 году нейробиологи поделились с миром удивительным примером радикального избавления от арахнофобии. Согласно описанному клиническому случаю, пациент получил хирургическую помощь для облегчения приступов судорог, спровоцированных саркоидозом. Хирург удалил участок тканей миндалевидного тела – части мозга, которая, среди прочего, отвечает за формирование эмоций, в частности страха. После данной процедуры арахнофобия пациента невероятным образом обратилась очарованием пауками.
Ещё одно необычное исследование, проведённое в 2019 году, продемонстрировало исцеляющий эффект просмотра фильмов «Человек-Паук» и «Человек-Муравей». До и после просмотра отдельных фрагментов этих фильмов участники эксперимента заполняли небольшие опросники. Как ни странно, даже семисекундного отрывка было достаточно, чтобы у некоторых испытуемых уменьшилась интенсивность симптомов. К слову, учёные видят в современных технологиях перспективный инструмент для борьбы с фобиями. В частности, специалисты уже работают над способами применения технологий виртуальной и дополненной реальности для смягчения или даже полного устранения арахнофобии.
В разработке цветет культ Карго. Многие полагаются на слова, которые сказал уважаемый автор десятки лет назад. Продолжают разрабатывать код, опираясь на подходы, которые либо не актуальны, либо сам автор поменял свою точку зрения. И сегодня мы поговорим о некоторых распространенных принципах программирования, которые не так однозначны как может показаться.
Меня зовут Кирилл Мокевнин и я — сооснователь школы программирования Хекслет. За последние пару лет я провел собеседования с более чем 400 человек, потенциальными наставниками по совершенно разным направлениям в разработке. В результате у меня собралась большая выборка наблюдений, которые мы разберем ниже.
Редакторы
Для развития экосистемы не очень круто, что каждая IDE сама реализует все фичи под каждый язык. Вендоры не хотят делиться этим с сообществом и разрабатывают механизмы для анализа и работы с кодом внутри себя. И если кто-то делает свой собственный редактор, то для поддержки языков ему приходится писать всё практически с нуля, особенно тяжелые фичи.
Не все знают, что недавно в мире редакторов произошла революция. Раньше было четкое разделение на IDE (которые дают рефакторинг, autocomplete, подсказки, документацию и запуск кода) и обычные редакторы, которые дают подсветку и небольшой набор базовых возможностей, типа перехода по файлам. Но какое-то время назад всё изменилось.
LSP (Language Server Protocol)
По-другому, это, как ни странно, реализовала Microsoft. Хотя миф, что Microsoft — ребята не очень, существует до сих пор. Но с точки зрения Open Source они — номер один в мире по количеству продуктов. Я уж не говорю о том, что им принадлежит GitHub.
Microsoft сделала LSP — спецификацию, про которую знает пока не так много людей. Понятно, зачем в Microsoft это сделали — они хотят, чтобы их среда и технологии распространялись, поэтому им выгодно делать это бесплатно. И тем не менее.
LSP — это стандарт, который определяет, как должен быть написан сервер, отвечающий за анализ и изменение кода. Такой сервер не связан с редактором, он пишется независимо. Единственное, что требуется от редакторов — встроить возможность общаться с таким сервером.
Если раньше поддержку языков писали под каждый редактор, то сейчас на GitHub есть открытые имплементации LSP и они работают абсолютно со всеми редакторами. На самом деле это серьезная вещь и она полностью изменила ландшафт. Например, теперь очень просто создать собственный редактор с сумасшедшими возможностями.
Более того, для современных языков (Rust, Swift, TypeScript) уже не делают специализированных IDE и даже плагинов к существующим. На том же GitHub разработчики этих языков сразу декларируют, что будут делать реализацию LSP, чтобы вы сами могли подключить поддержку языка в ваш любимый редактор.
Это уже поддерживают Apple, Microsoft и Mozilla — и рано или поздно мы получим все возможности IDE в любом редакторе. Для примера — мой редактор Vim.
Видно, что здесь есть умный autocomplete: он знает не только про класс и стандартную Java, но и Lombok. Сверху есть аннотации, Vim всё подсказывает и подсвечивает. Кроме того, есть еще рефакторинг, организация импортов, проваливание, дизассемблирование и много других вещей. Может быть, это не так симпатично, как в IDE, но очень эффективно. А достигается всё это благодаря LSP.
Благодаря этому сейчас начинается узкоспециализированный бум: на GitHub можно увидеть множество новых редакторов, потому что они из коробки получают поддержку всех популярных языков.
Разработка
В разработке существуют не то чтобы мифы, но многие думают, что можно делать только так, а не по-другому.
Ритуалы
Я специально назвал это ритуалами — люди часто что-то выполняют, не задумываясь, потому что другого просто не видели. Например, синхронные митинги, когда надо всем в одно и то же время созвониться и рассказать, кто и что сделал. Подобные митинги часто превращаются в обычную отчетность.
То же самое происходит со спринтами и деплоями. Например, я знаю людей, у которых спринт идет две недели, и деплой почему-то тоже раз в две недели. При этом в скраме нет требования привязывать деплой к концу спринта. В идеале, нужно деплоиться каждый день, независимо ни от чего, потому что с точки зрения бизнеса Time To Market — это самая главная вещь. Когда вы выкатываете сразу множество фич, сложно оценить, что на что повлияло и к чему все это приведет. При этом всем очень важно считать — все хотят понять, что происходит и оценивают, оценивают и оценивают...
Все подобные ритуалы приводят к проблемам, которых без них бы не было. Укладываться в спринтах в срок не то чтобы невозможно, а чаще всего не нужно. Это выдуманная проблема, которая не только приводит разработчиков к стрессу, но даже разборки после спринта провоцирует. «Почему мы не успеваем, давайте на 3,14 умножать! У нас есть коэффициенты? А, их же нельзя, персональной ответственности нет — мы, как команда, зафейлили…»
Но всё возможно совсем по-другому:
Деплой в любой момент, хоть 5 раз в день;
Задачи по мере поступления;
Асинхронные дейли;
Отсутствие оценок.
Всё это применяется и у нас в Хекслете. При этом сейчас мы стали использовать множество ботов для тех же дейли.
Код ревью и фича-ветки
Все сталкиваются с долгими фича-ветками и блокирующими код ревью. Все не упускают случая пожаловаться, что фича-ветку ребейзят и мержат в мастер пять дней и всей командой, а на код ревью надо пятерых уговаривать, и у них все равно нет времени.
Хотя на самом деле возможно по-другому:
Неблокирующее ревью;
Короткие ветки;
Прямо в мастер (хотя сейчас уже надо говорить main).
Делать неблокирующее ревью через маленькие изменения позволяет подход Trunk-based Development. Многие программисты не всегда осознают, что изменения могут быть небольшими — они уверены, что их фича не бьется. Но если проверить, то почти всегда это возможно: ведь глобальные изменения, которые затрагивают всё, и их нельзя поэтапно накатывать, бывают достаточно редко.
Благодаря маленьким изменениям практически всё можно делать сразу в main. При таком подходе даже если вы что-то напишете не так, то быстро и легко сможете это поправить.
Есть еще дополнительный бонус от такого подхода. Когда программист что-то долго пишет и где-то в начале уходит не туда, он может идти не туда целую неделю и даже больше. В результате накопленный негативный эффект будет слишком большой. Но если бы он вливал в main очень маленькими кусочками, то вы бы намного раньше увидели, что он пошел не в ту сторону, и поправили это.
Понятно, что это работает не везде, и что это связано с культурой компании. Но у нас это работает — и мы требуем ветки только от новичков.
Качество
В некоторых командах количество тестировщиков зашкаливает: их больше, чем программистов. Есть компании у которых по 5 стейджингов, и иногда это действительно бывает оправдано. Но на самом деле в современном мире может быть по-другому:
От стейджинга отказываются из-за того, что существуют флаги включения фич, канареечные релизы и другие подходы, которые позволяют минимизировать ошибки и сделать так, чтобы они проявлялись на небольшом количестве людей, либо только внутри продукта. Года 2-3 назад было много статей на тему, как большие компании типа Airbnb и Spotify отказались от стейджингов и почему.
В Booking работают несколько тысяч разработчиков и у них вообще нет отдела тестирования. Да, они используют жесткие маркетинговые приемы, можно их за это не любить. Но с точки зрения инженерной культуры они довольно продвинутые ребята. Например, они очень хорошо считают деньги и, если у них появляются баги, то они сразу же понимаю, сколько теряют.
Однажды они решили, что могут потерять $100 тыс. в месяц. Назвали это RND — как бы деньги на исследования. И если в конце месяца они не были потеряны из-за ошибок программистов, CEO писал письмо типа: «Ребята, мы были слишком консервативны в этом месяце и замедлили движение вперед. Давайте в следующем месяце сделаем все поинтересней, потому что иначе мы остановимся в развитии». Но когда они переходили через эту цифру, их просили немного успокоиться и замедлиться, не так сильно экспериментировать.
Если мы делаем баги, то в нормальной разработке это означает, что мы на что-то не обращаем внимания, зато двигаемся и развиваемся очень быстро. Если у нас багов нет, то почти всегда это связано с консервативным подходом к разработке — то есть она очень длительная.
В Booking поменяли негатив на позитив. Подход интересный, и, с моей точки зрения, за ним скрывается очень важная вещь: самое главное — это мониторинг. Ведь вы никогда в жизни не добьетесь того, чтобы у вас совсем не было ошибок. Но если у вас отличный мониторинг, разделение и все эти подходы, о которых я говорю, то не принципиально, как и что вы делаете. Вы всегда можете откатиться, поправить всё, и продолжить двигаться с высокой скоростью.
Пирамида тестирования
Это моя любимая тема. Все знают про пирамиду тестирования Фаулера: если спросить про тесты, большинство сразу говорит, что пишут юнит-тесты.
Это очень фанатическая история, относящаяся к тому, что надо писать. Особенно часто я это встречаю, когда собеседую Java-разработчиков. В книгах, в документации Spring, в статьях, везде — только и разговоров, что о unit-тестах.
Я сам в тестировании много чего повидал и много как тестировал. Но вы же знаете, что мир меняется? Например, в 2008 году Kent Beck сказал: «I get paid for code that works, not for tests, so my philosophy is to test as little as possible to reach a given level of confidence...». В 2019 году он же написал про принципы тестирования, но многие люди, которые высказываются за юнит-тесты, до сих пор на него ссылаются. Хотя он уже давно переосмыслил tdd, и даже написал в Фейсбуке, что попробовал Haskell и изменил своё отношение к тестам.
Бережливое тестирование
К счастью, подходы все-таки меняются, это хорошо видно во фронтенде. Например, появилось новое веяние — бережливое тестирование.
Основная цель тестов — сделать так, чтобы наше приложение работало, а не заниматься тестами. В этом я абсолютно солидарен с Кентом Беком 2019 года, который предложил писать как можно меньше тестов, но так, чтобы мы покрывали как можно больше функциональности.
Понятно, что E2E тесты всё это могут, но они сложные и дорогие в написании. Это не ежедневные тесты, которые надо писать разработчикам. Их чаще пишут тестировщики — и это правильно.
Но если посмотреть на фронтенд, теперь всё крутится вокруг интеграционных тестов. Благодаря Kent C. Dodds и Guillermo Rauch появилась концепция «Testing Trophy», в которой этот факт подчеркивается. Тот же React Testing Library придерживается жесткой позиции, что хватит уже лазить внутрь, всё должно работать независимо от того, React у вас или нет.
Понятно, что иногда нужны юнит-тесты, а иногда — какие-то другие тесты. Но идеальный тест — если функционально ваш код не поменялся, то и тест не сломается. Если тесты ломаются — значит вы увлекаетесь мокингом или слишком глубоко лезете в кишки. Я много раз видел, как люди сначала всё покрывали юнит-тестами, а когда начинался рефакторинг — выкидывали их, потому что эти тесты ничем не могли им помочь.
С бэкендом похожая история. Тестируем интеграцию, а детали реализации сейчас мало кого волнуют. Главное чтобы у вас системы друг с другом работали.
В заключение темы разработки я задам короткий вопрос: «Замедляют ли тесты разработку?». Я даже провел небольшой опрос в Твиттере — половина опрошенных ответили, что нет, а в комментариях объяснили, что тесты наоборот, разработку ускоряют.
Все зависит от того, про какие тесты мы говорим. Чаще всего это упирается в умение писать тесты, а, как ни странно, их умеют писать далеко не все. Например, все ли понимают хотя бы разницу между моками и стабами, если моками называют то, что ими не является, или их пишут там, где не надо? Посмотрите на моем канале видео, я там рассказывал про эту тему.
ООП
Это отдельная большая тема, поэтому сегодня остановимся только на некоторых интересных особенностях.
Полиморфизм
Когда я разговариваю с людьми об этом, то очень многие удивляются, что существует несколько видов полиморфизма (а их вообще много). Во-вторых, когда начинаешь копать, то выясняется, что все хорошо знают определение, но довольно слабо понимают, где это можно и нужно использовать, почему это надо и какие проблемы решает.
Я провел еще один опрос в Твиттере:
Только половина ответила правильно. Это грустно, потому что полиморфизм в ООП наиболее сильно влияет на структуру кода (Бенджамин Пирс в ТАПЛ выделяет полиморфизм подтипов как основную фишку современного мейнстримового ООП).
Если взять навскидку несколько паттернов — Null Object, Strategy, Adapter, Decorator, Composite, Proxy, State — все это просто разновидности применения полиморфизма. Но если вы знаете полиморфизм и понимаете его, вам эти паттерны и знать не надо. Вы и так будете понимать, в какой момент и как их можно применить.
Принципы SOLID
Еще одна прекрасная вещь в ООП — SOLID. Тут вообще надо знать историю, откуда SOLID взялся и почему эти принципы так получились.
Самый забавный принцип здесь — Liskov. Как часто в жизни вы про него вспоминаете? Как он вообще на архитектуру вашего кода влияет? Этот принцип абсолютно правильный, но он нужен для редких кейсов, я бы даже сказал — для специфических языков. Но армия программистов следует за Робертом Мартином, который не только сейчас на Clojure пишет, но и давным-давно отдал предпочтение динамической типизации, пересмотрев свое отношение к возможностям языков. Почитайте его блог. Он писал на Ruby, перешел на Clojure, и вообще на днях написал, что это будет его последний язык.
Про Лисков. В своей статье про SOLID он сказал: «People (including me) have made the mistake that this is about inheritance. It is not. It is about sub-typing». В принципе в Liskov нет ни слова про классы или наследование, там всё связано с типами, которые обычно выражаются интерфейсами.
Если вы действительно хотите узнать про архитектуру, то послушайте Андрея Аксенова, который сделал Sphinx. «Снесите это немедленно», — один из лучших докладов про архитектуру. Кроме всего прочего, он говорит, что принцип Liskov понадобился ему только один раз — на собеседовании.
На самом деле есть гораздо более важные принципы. Например, Single level of abstraction и вообще барьер абстракции — это одна из ключевых частей разработки. А с Command-Query Separation мы вообще сталкиваемся каждый день. Law of Demeter — попроще, но он встречается гораздо чаще, чем принцип Liskov.
Микросервисы
Я прекрасно понимаю джавистов, когда на собеседовании они начинают со слова «микросервис». Но мне не очень понятно, когда об этом говорят ребята из других языков, особенно когда в их команде разработки три человека.
Например, человек мне рассказывал, что они переходят на микросервисы, потому что у них тормозит ORM в Django. И что у Django плохая ORM, потому что там нельзя писать рекурсивные запросы. Я решил докопаться до сути и начал его расспрашивать.
Выяснилось, что у ребят древовидный каталог, они пишут рекурсивные запросы прямо на SQL, и они тормозят. Как это матчится на микросервисы, не очень понятно, но суть оказалась в другом. Человек просто не знал, как хранить деревья в базе, потому что когда они делали каталог, то не посмотрели, какие есть способы хранения, кроме adjust set. Когда я рассказал ему про материализованный путь, он ушел исправлять проект. Такое у нас было собеседование.
Другая история связана с Python. Парень сказал, что он реализовал микросервисы именно потому, что у него было много зависимостей в одном проекте, и они долго устанавливались. Я попытался поговорить с ним, что это не совсем связанные вещи, но он был твердо уверен, что всё сделал правильно.
О чем я хочу сказать. Микросервисы, безусловно, решают проблемы, и есть проекты, в которых они нужны. Но существует миф, что если где бы то ни было внедрить микросервисы, то станет лучше. Часто говорят: у нас код так себе, поддерживать невозможно, а вот если мы его сейчас разделим на микросервисы, то он сразу станет классным.
Но вообще-то микросервисы сложнее, потому что логика никуда не денется — система останется такой же сложной, но при этом станет еще и распределенной.
С микросервисами вы сразу упираетесь в:
Асинхронность/Обработка ошибок;
Трафик/Кэши;
Распределенные транзакции/Согласованность;
Логи/Отладка;
Версионирование;
Люди/Шаринг/Рефакторинг.
То есть микросервисы не спасают от плохого кода — при разделении он станет еще хуже, потому что в том месте, в котором он все равно собирается, вместо синхронных вызовов будут асинхронные вызовы, и т.д. Я уж не говорю про логирование и всякие другие штуки. Плохой код в тысячу раз проще сделать хорошим в рамках системы, где вы работаете. А микросервисы нужны для другого и решают другие проблемы.
Расскажу историю. В одной компании пишут на Java, и у них всё в микросервисах. При этом их архитектор не любит Spring, и они реализовали пять разных алгоритмов в одном зашифрованном файле. И чтобы достать из этого файла алгоритм, у них реализовано семь микросервисов: один берет данные, другой складывает в базу, плюс под каждый алгоритм написан свой собственный микросервис (еще пять штук).
Все микросервисы одновременно берут этот файл, потому что они одновременно срабатывают. Внутри каждого есть проверка на свой файл. Сам алгоритм на 200 строк, и еще обвязка на пару тысяч, чтобы все это запускалось. Это явный перебор, но так работают многие компании, особенно в аутсорсе. Хотя, на самом деле, это решается классическим паттерном — обычной стратегией, которая делается с помощью простого свитча.
Это прекраснейший доклад. В нем помимо практики есть и философия — от просмотра оторваться нельзя, как от хорошей книжки.
Заключение
Есть фундаментальные правила проектирования кода:
Менеджмент состояния. Как только у нас появляется shared state, то возникает определенный класс проблем, и это нужно понимать. Это связано с concurrency и распределенными системами.
Изоляция побочных эффектов. Если вы писали или будете писать на функциональных языках, вы это хорошо поймете, потому что они к этому приучают.
Семантическое именование имеет невероятно важное значение, потому что именовать все равно никто не умеет.
Что делать?
Читайте книги! Есть огромное количество книг, в которых давно написана вся правда. А читая про ОС, вы поймете, как работает все остальное — в операционках много готовых архитектурных решений.
Если вы не писали на языках с совершенно другой парадигмой, то потратьте время на них. У меня есть набор языков, которые я всегда рекомендую, чтобы захватить как можно большее количество разных парадигм и подходов:
Haskell
Clojure, и вообще любой Lisp в принципе.
Kotlin из языков уровня C#, Java и подобных.
С
Ruby, Python, JS — не сильно принципиально какой, любой из них.
Elixir или Erlang.
И последнее: работайте среди тех, кто сильнее вас.
Видео моего выступления на эту тему на конференции TechLead Conf 2021:
В 2022 году конференция TechLead пройдет на одной площадке с конференцией DevOps Conf 2022 — 9 и 10 июня в Москве, в Крокус-Экспо. Подать заявку на выступление для техлидов вы можете здесь, а для devops-коммьюнити — здесь.
Если у вас есть идеи и мысли по выступлению, но есть и много вопросов, то можно встретиться в прямом эфире с Программным комитетом и расспросить их обо всем. На сайтах обеих конференций смотрите информацию о встречах.