Уймин - про разработку
190 subscribers
3 photos
1 file
40 links
Авторский канал про backend-разработку. Подробнее - в закрепллённом сообщении.

Личиный аккаунт: @maksimuimin
Download Telegram
Паттерн early exit

Какие задачи стоят перед программистом, когда он реализует какую-то функцию? Обеспечить корректность алгоритма, при минимальной сложности кода. Именно в этом помогает паттерн early exit.

📍 В чём суть: когда в вашем алгоритме появляется оператор ветвления (if, switch и т.п.), короткую ветвь вычислений надо обработать перед длинной ветвью.

ℹ️ Пример:
- Без early exit
if user.isAuthorized() {
// Do
// some
// business
// logic
} else {
return errors.New("403 Anauthorized")
}

- То же самое, но с early exit:
if !user.isAuthorized() {
return errors.New("403 Anauthorized")
}
// Do
// some
// business
// logic


У early exit подхода есть ряд преимуществ:

1️⃣ Снижается вложенность операторов - это упрощает чтение кода.

2️⃣ Повышается фокус на основной путь выполнения функции - часто, когда дочитываешь длинную (вероятно, главную) ветвь выполнения кода, уже забываешь, зачем выше было ветвление и в чём смысл короткой ветви. Early exit позволяет быстро прочитать короткую ветвь и забыть о ней, сфокусировавшись на длинной ветви. В итоге, программисту нужно меньше “оперативной памяти” чтобы прочитать код 😊

3️⃣ Повышается корректность алгоритма - все граничные условия можно рассмотреть в самом начале функции, по моему опыту такой подход снижает количество ошибок.

4️⃣ Если в вашем языке нет defer'ов и сборки мусора, разновидность early exit упрощает корректную деаллокацию ресурсов. Пример:
void foo() {
void *a = malloc(1024);
if (!a)
goto out_a;

void *b = malloc(1024);
if (!b)
goto out_b;

void *c = malloc(1024);
if (!c)
goto out_c;

/*
* Метки позволяют деаллоцировать только те ресурсы,
* которые были успешно аллоцированы
*/

free(c);

out_c:
free(b);

out_b:
free(a);

out_a:
return;
}

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

💡 Итого: при использовании операторов ветвления короткие ветви алгоритма следует обрабатывать перед длинными. Этот подход называется early exit. Он позволяет писать более простой и корректный код.

Ставь огонёк, если используешь early exit в своей работе 🔥

#hardskills #coding #pattern #bestpractice #codereading
Синхронные vs асинхронные операции

Любую операцию (вызов функции, запись в файл, сетевой поход, запрос к БД и т.д.) можно выполнить двумя способами: синхронно или асинхронно. Рассмотрим разницу между двумя подходами.

➡️ Синхронно - значит “с ожиданием завершения операции”. Обычно, это поведение по-умолчанию: программист пишет инструкции в программе, они выполняются процессором в том порядке, в котором написаны. Процессор не начинает выполнение следующей инструкции, пока не закончит предыдущую.

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

🤔 Зачем вообще нужно асинхронное выполнение? Давайте сравним два подхода и определим, чем отличаются их свойства.

1️⃣ Асинхронное выполнение тесно связано с таким свойством ПО, как многозадачность. Это значит, что несколько задач должны выполняться параллельно. С точки зрения программиста, для этого нужно запустить несколько операций без ожидания их заверщения, то есть асинхронно. Так что, когда ОС одновременно запускает несколько программ или web-браузер скачивает параллельно несколько ресурсов, под капотом там работает асинхронный код.

2️⃣ Асинхронный код может эффективнее обрабатывать блокировки. Для быстрых операций вроде сложения или доступа к оперативной памяти асинхронное выполнение часто не имеет смысла. Но есть операции медленные: сетевые походы, работа с жёстким диском, работа с мьютексами. В этих операциях часто нужно подождать, пока система не будет готова к выполнению операции. При этом происходит блокировка выполнения - система простаивает во время ожидания. В случае синхронного выполнения простой неизбежен, а в случае асинхронного - из множества параллельных задач можно выполнить любую, готовую к выполнению, и избежать простоя.

3️⃣ Как следствие, асинхронного ПО использует аппаратное обеспечение более эффективно, т.к. снижается время простоя в ожидании блокировок.

4️⃣ При всех преимуществах асинхронного ПО, у него есть один существенный недостаток: код становится сложнее. Программисту надо решать:
- Какие задачи надо выполнять асинхронно?
- Какие механизмы ЯП и ОС для этого выбрать?
- Как обеспечить синхронизацию задач (переход от асинхронного обратно к синхронному выполнению)?
Всё это усложняет чтение асинхронного кода, делает его поддержку более дорогой и может приводить к появлению целых классов новых ошибок.

⚖️ Вот такой вот интересный трейдоф: производительность vs простота. Что надо запомнить:
- Синхронное и асинхронное выполнение - это фундаментальная концепция. С ней можно встретиться в разных областях IT и за пределами отрасли.
- Асинхронное выполнение имеет смысл для тяжёлых операций. Для них асинхронность может повысить эффективность использования ресурсов.
- Повышение производительности не бесплатное: за него мы платим сложностью системы.

Ставь огонёк, если интересна тема асинхронщины и параллельного выполнения кода 🔥

#theory #concurrency #pattern
Потоки vs процессы: масштабирование по ядрам CPU

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

🤔 Сколько процессора утилизирует этот код?
while (true) {}

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

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

📖 Доступом к процессору, как и ко всем другим аппаратным ресурсам, управляет операционная система. Современные процессоры - многоядерные, ядра процессора могут выполнять вычисления параллельно, независимо друг от друга. Чтобы приложения могли использовать эту возможность, ОС предоставляет асинхронный API для параллельных вычислений, в основе которого лежит 2 концепции: потоки и процессы.

▶️ Процесс (process) - это запущенное приложение. У каждого процесса есть эксклюзивный доступ к ресурсам: аллоцированной памяти, файловым дескрипторам и т.д. Процессы максимально изолированы: они не имеют доступа к ресурсам друг друга и обладают независимым жизненным циклом. Любое взаимодействие между процессами требует написания кода с использованием специальных механизмов IPC (inter-process communication) - файлов, пайпов, сокетов, сигналов, и других.

➡️ Поток (thread) - это последовательность вычислений. У каждого процесса под капотом по-умолчанию есть 1 поток, все вычисления выполняются в нём последовательно. Ресурсы внутри процесса общие для всех потоков. Если кто-то открыл файл или аллоцировал память, все потоки могут этот ресурс прочитать и записать без необходимости использовать какие-то дополнительные механизмы.

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

📈 Максимальной производительности можно достичь, когда кол-во запущенных потоков в системе равняется кол-ву потоков в архитектуре CPU. В этом случае каждый поток может выполняться непрерывно.

🤔 Зачем знать низкоуровневые API в 2024? У нас же есть языки программирования высокого уровня со всевозможными async/await, go func () { } и другими высокоуровневыми асинхронными API.

⚠️ Дело в том, что ОС кроме процессов и тредов других асинхронных моделей не знает. Поэтому, если вам нужно больше 1 ядра CPU, понимание и использование низкоуровневых API необходимо. Например:
- В go есть GOMAXPROCS
- В Node.js есть --v8-pool-size
- В Python есть threading и multiprocessing
- А в C/C++ можно использовать fork(2) и pthread_create(3) напрямую

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

#theory #Linux #concurrency #tools #coding #pattern #highload
Паттерн middleware

Нужен для организации цепочек действий. Мидлвара оборачивает функцию дополнительной логикой:

package main

import "fmt"

type MyFunc func(string)
type Middleware func(next MyFunc) MyFunc

func Hello(name string) {
fmt.Printf("Hello, %s\n", name)
}

func GentleMiddleware(next MyFunc) MyFunc {
return func(name string) {
fmt.Println("Greetings!")

next(name)

fmt.Println("Goodbye, see you soon")
}
}

func main() {
GentleMiddleware(Hello)("world")
/* Greetings!
* Hello, world
* Goodbye, see you soon
*/
}


Мидлвары умеют выстраиваться в цепочки. Попробуй поиграть с кодом из примера, вот так выглядит:
GentleMiddleware(GentleMiddleware(Hello))("world")
Когда цепочка становится длинной, мидлвары можно хранить в списке и применять к функциям по запросу.

Патерн middleware улучшает модульность кода, с ним дополнительную логику просто подключить и использовать повторно. Такой подход популярен в HTTP-серверах - там у обработчиков запросов одинаковая сигнатура, это позволяет использовать одну мидлвару для всех обработчиков. Такой подход применим везде, где много функций с одинаковой сигнатурой.

Выноси в мидлвары:
- проверку авторизации;
- логирование запросов и ответов;
- замеры метрик latency, throughput, errors rate;
- обработку исключений;
- проверку ключей идемпотентости;
- ретраи и фейловеры.

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

#hardskills #coding #pattern