TGViewer
Channel Public Channel
YeaHub Tech

YeaHub Tech

@yeahub_tech

Новые технологии, советы и обучающие материалы

YeaHub — это платформа для IT-специалистов, объединяющая обучение, карьерный рост, развитие и сообщество единомышленников.

Платформа: https://yeahub.ru

Для связи: @ruslan_kuyanets
Subscribers
412
Photos
343
Videos
15
Links
450

Showing posts older than #296 · Back to latest

Older Posts 20 shown
Post #295 82
🔖 Spring Boot + Redis + PostgreSQL: кэширование за 15 минут

Ускорение API в 300 раз!


📎 Результат

Без кэша: 3000 мс
С Redis: 10 мс


🔸 Что нужно?

Зависимости:
spring-boot-starter-data-redis + jedis

Конфигурация:
RedisTemplate + CacheManager + @EnableCaching


📝 5 аннотаций

@Cacheable — кэширует результат (GET)
@CacheEvict — очищает кэш (POST/PUT/DELETE)
@CachePut — обновляет кэш
@Caching — группирует операции
@CacheConfig — настройки класса


🔸 Как работает?

1-й запрос: Redis пусто → БД → сохраняем в кэш → 3000 мс
2-й запрос: Redis → 10 мс
Изменение: @CacheEvict очищает кэш


💡 3 стратегии

Cache-Aside: @Cacheable (популярная)
Write-Through: @CachePut (актуальные данные)
Invalidation: @CacheEvict (сложная)


🔸 Best Practices

✅ Кэшируйте медленные запросы (> 100 мс)
✅ Очищайте при изменениях
✅ Используйте TTL
✅ Мониторьте hit rate (> 70%)

❌ Не забывайте инвалидацию


🔸 Подводные камни

Thundering Herd: все в БД → cache warming
Memory Limits: переполнение → eviction policies
Serialization: объекты Serializable


🔸 Результаты

Без кэша: 100 запросов = 300 сек, нагрузка 100%
С кэшем: 100 запросов = 4 сек, нагрузка 1%

Ускорение в 75 раз!


Итог ⬇️

5 аннотаций + простая настройка = прирост в 75-300 раз. Must-have для production.


📎 Статья

🎙 Новости

📝 База вопросов
Post #294 81
🔖 Контейнеризация: почему это невозможно остановить

Изолированная среда для запуска приложений!


📎 Что такое контейнер?

Изолированная «песочница», где процессы работают отдельно от ОС.

Внутри: все зависимости, библиотеки, конфигурации.

Результат: запускаешь везде — ничего устанавливать не нужно.


🔸 Как работает?

Правило: 1 контейнер = 1 процесс

Цепочка:
Dockerfile → Image (шаблон) → Container (экземпляр)

Аналогия: Image = класс, Container = объект


💡 Контейнер vs ВМ

ВМ: гипервизор + полная ОС + много ресурсов
Контейнер: только нужное + разделяемая ОС + минимум ресурсов


🔸 Преимущества

✅ Легковесные (МБ vs ГБ)
✅ Портативные (работают везде)
✅ Воспроизводимые (1 образ = тысячи копий)
✅ Изолированные (не мешают друг другу)
✅ Быстрые (секунды vs минуты)


🔸 Применение

• Микросервисы
• CI/CD
• Масштабирование
• Разработка ("Works on my machine" решено)
• Облако (AWS, Google, Azure)


🔸 Docker

docker pull node:18-alpine
docker run -p 3000:3000 my-app
docker stop container_id



🔸 Без vs С контейнерами

Без: установка → зависимости → настройка → молитва
С: docker run my-app


🔸 Почему невозможно остановить?

✅ "Works on my machine" → Works everywhere
✅ Сложный деплой → docker run
✅ Долгая настройка → Уже готово
✅ Конфликты → Изоляция
✅ Медленно → Мгновенно


Итог ⬇️

Контейнеры: легкие, быстрые, портативные, эффективные, изолированные.


📎 Статья

🎙 Новости

📝 База вопросов
  • 👍 1
Post #293 110
🔖 Тайм-трекер в Telegram за вечер

Забываете отмечать рабочее время? Делаем простой бот на Node.js + TypeScript!


🔍 История

Жене нужен был отчёт за 3 месяца работы, но инструмента для учёта времени не было. Сначала писала "начало"/"конец" в WhatsApp группу. Решил автоматизировать это через Telegram Bot!


💡 Что получилось:

✅ 4 простые команды
✅ Автоподсчёт времени
✅ Экспорт в CSV
✅ Работает везде (телефон всегда под рукой!)


🔸Команды:

🔸 /work [задача] — начать работу
🔸 /done — завершить задачу
🔸 /state — текущий статус
🔸 /print — вывести CSV


🔸Стек:

• Node.js + TypeScript
• node-telegram-bot-api
• CSV для хранения
• Docker для деплоя


🔸 Быстрый старт:

1️⃣ Установка:
npm i --save node-telegram-bot-api
npm i --save @types/node-telegram-bot-api


2️⃣ Создать бота:
Идём к @BotFather → /newbot → получаем токен

3️⃣ Базовый код:
import TelegramBot from 'node-telegram-bot-api';

const token = 'YOUR_TOKEN';
const bot = new TelegramBot(token, { polling: true });

// Начать задачу
bot.onText(/\/work (.+)/, async (msg, match) => {
const chatId = msg.chat.id;
const work = match[1];
const startDate = new Date();

const entries = await getEntries();
entries.push({
work,
startDate: startDate.toJSON(),
endDate: null,
duration: null,
});
writeFile(entries);

bot.sendMessage(chatId,
`Started ${work} at ${startDate.toLocaleString()}`);
});

// Завершить задачу
bot.onText(/\/done(.*)/, async (msg) => {
const chatId = msg.chat.id;
const endDate = new Date();

const entries = await getEntries();
const targetEntry = entries[entries.length - 1];
targetEntry.endDate = endDate.toJSON();

const startDate = new Date(targetEntry.startDate);
const diff = endDate - startDate;
targetEntry.duration = formatDuration(diff);

writeFile(entries);

bot.sendMessage(chatId,
`Finished ${targetEntry.work}. Took ${diff}!`);
});


🔸 Работа с CSV:
const getEntries = async () => {
const val = fs.readFileSync(csvFilePath).toString();
const entries = [];

val.split("\n").forEach((row, index) => {
if (row === "" || index == 0) return;
const [work, startDate, endDate, duration] =
row.split(';');
entries.push({ work, startDate, endDate, duration });
});

return entries;
}

const writeFile = (entries) => {
let str = "work;startDate;endDate;duration;\n";
entries.forEach(e => {
str += Object.keys(e).map(k => e[k]).join(';') + '\n';
});
fs.writeFileSync(csvFilePath, str);
};


🔸 Использование:

/work Написать статью
→ Started at 9:00 AM

/done
→ Finished. Took 3h 25m!

/print
→ work;startDate;endDate;duration;
Статья;2025-01-20T09:00...;3h 25m


🔸 Деплой:
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm install && npm run build
CMD ["node", "build/main/index.js"]


docker build -t timetracker .
docker run -ti timetracker



🔸Преимущества:

✅ Просто — 4 команды
✅ Быстро — 2 секунды на команду
✅ Везде — Telegram на всех устройствах
✅ Бесплатно — без подписок
✅ CSV — легко открыть в Excel


🔸 Идеи для улучшения:

🔸 Категории задач (#важное, #срочное)
🔸 Статистика по дням/неделям
🔸 Напоминания о перерывах
🔸 Экспорт в JSON/PDF
🔸 Интеграция с календарём


❗️ Важно:

Не коммитьте токен! Используйте .env:
import dotenv from 'dotenv';
dotenv.config();

const token = process.env.TELEGRAM_BOT_TOKEN;



Итог ⬇️

За один вечер — полнофункциональный тайм-трекер! ~200 строк кода, работает везде, данные в удобном формате.


📎 Статья

🎙 Новости

📝 База вопросов
Post #292 95
Best Practices:
✅ Один тест = одна проверка
✅ Тесты должны быть быстрыми (<1 сек)
✅ Понятные названия: test_add_negative_numbers
✅ Не тестируйте сторонние библиотеки
✅ Покрытие кода 70-80% — отличный результат
✅ Коммитьте тесты вместе с кодом

❌ Не делайте тесты зависимыми друг от друга
❌ Не используйте случайные данные без seed
❌ Не игнорируйте упавшие тесты


🔸Workflow разработки:

1. Пишете функцию
2. Сразу пишете тест
3. Запускаете тест
4. Если упал → чините код
5. Повторяете до зелёных тестов
6. Добавляете edge cases
7. Профит! 💫

🔸 Альтернативы unittest:

🔸 pytest — более простой синтаксис
🔸 nose2 — расширение unittest
🔸 doctest — тесты в docstrings

Но unittest — это стандарт Python!

💡 Золотое правило:

«Если нашли баг — напишите тест, который его воспроизводит. Потом чините код. Так этот баг никогда не вернётся!»

Реальная статистика ⬇️

🔸 Проекты с тестами имеют на 40% меньше багов
🔸 Рефакторинг с тестами = безопасность
🔸 Тесты = документация вашего кода
🔸 Код с тестами легче поддерживать


📎 Статья

🎙 Новости

📝 База вопросов
  • 👍 1
Post #291 83
🔖 Python: Модульное тестирование для чайников и профи

Пишете код без тестов? Готовьтесь часами искать баги! Разбираем, как unittest спасет ваше время и нервы.


❓Что такое тестирование?

Простыми словами: это проверка, что ваш код работает правильно. Большинство делает это через print() и запуск в терминале. Но есть способ лучше!

💡 Зачем вообще нужны тесты?

🔸 Экономия времени
Вместо часов отладки — мгновенное обнаружение проблем

🔸 Лучший код
Заставляет продумывать входы, выходы и граничные случаи

🔸 Переиспользование
Копируете функцию? Копируйте и тесты!

🔸 TDD возможен
Можно даже писать тесты ДО написания кода (test-driven development)


Типы тестов:

🔸 Модульное (Unit)
Проверка отдельных функций/компонентов

🔸 Интеграционное
Проверка взаимодействия всех частей

Сегодня фокус на модульном!

📝 Простейший пример:

def add(x, y):
return x + y

# Базовый тест
assert add(2, 4) == 6, "Should be 6"


Если результат не 6 → получите AssertionError ❌

Unittest: профессиональный подход

Встроенный в Python тестировщик. Без установки пакетов!

Структура проекта:
project/
├── functions.py
└── tests.py


functions.py:
def add(x, y):
return x + y


tests.py:
import unittest
from functions import add

class TestAdd(unittest.TestCase):
def test_add(self):
self.assertEqual(add(2, 4), 6)
self.assertEqual(add(-1, 1), 0)
self.assertEqual(add(-1, -1), -2)

if __name__ == '__main__':
unittest.main()


Запуск:
python tests.py


Результат:
...
-------------------------------
Ran 1 test in 0.001s

OK


Каждая точка (.) = успешный тест ✅
E = ошибка ❌
F = провал теста ❌


🔸Основные assert методы:

🔸 assertEqual(a, b) — a == b
🔸 assertNotEqual(a, b) — a != b
🔸 assertTrue(x) — x is True
🔸 assertFalse(x) — x is False
🔸 assertIn(a, b) — a in b
🔸 assertIsNone(x) — x is None
🔸 assertRaises(Exception) — проверка исключений
🔸 assertAlmostEqual(a, b) — для float чисел

🔸Правила хороших тестов:

1️⃣ Правильные имена
# ✅ Работает
def test_add():
pass

# ❌ НЕ работает (не начинается с test_)
def add_test():
pass


Название ДОЛЖНО начинаться с test_!

2️⃣ От простого к сложному

Сначала очевидные случаи:
def test_add_positive():
self.assertEqual(add(2, 3), 5)


Потом граничные:
def test_add_zero():
self.assertEqual(add(0, 5), 5)
self.assertEqual(add(5, 0), 5)


3️⃣ Тестируйте edge cases

Что может сломать ваш код?
🔸Отрицательные числа
🔸Нули
🔸Очень большие значения
🔸 Пустые строки
🔸 None значения

def test_edge_cases():
self.assertEqual(add(-1, 1), 0)
self.assertEqual(add(0, 0), 0)
self.assertEqual(add(-5, -3), -8)


4️⃣ Независимость тестов

Каждый тест — отдельная вселенная!

class TestCalculator(unittest.TestCase):
def setUp(self):
# Выполняется ПЕРЕД каждым тестом
self.calc = Calculator()

def tearDown(self):
# Выполняется ПОСЛЕ каждого теста
self.calc = None


5️⃣ Избегайте assertTrue

❌ Плохо:
self.assertTrue(add(2, 3) == 5)
# Вывод: Expected True, but was False


✅ Хорошо:
self.assertEqual(add(2, 3), 5)
# Вывод: AssertionError: 5 != 6


Второй вариант даёт НАМНОГО больше информации!


🔸Реальный пример бага:

def round_plus_ten(x):
return round(x) + 10

class TestRound(unittest.TestCase):
def test_round(self):
self.assertEqual(round_plus_ten(4.3), 14) # ✅
self.assertEqual(round_plus_ten(4.7), 15) # ✅
self.assertEqual(round_plus_ten(4.5), 15) # ❌ FAIL!


Почему упало?
round(4.5) = 4, а не 5! Python округляет к ближайшему чётному.

🟧Урок: тесты находят неочевидные баги!


🔸 Продвинутые фишки:

Группировка тестов:
class TestAddition(unittest.TestCase):
def test_positive(self): ...
def test_negative(self): ...

class TestMultiplication(unittest.TestCase):
def test_basic(self): ...


Пропуск тестов:
@unittest.skip("Пока не работает")
def test_future_feature(self):
pass


Проверка исключений:
def test_divide_by_zero(self):
with self.assertRaises(ZeroDivisionError):
divide(10, 0)
  • 👍 1
Post #290 74
🔖 C++: Switch со строками — миссия выполнима!

В C++ нет нативной поддержки switch для строк, но есть 4 мощных способа это обойти! Разбираем от простого к продвинутому.


Проблема:
// ❌ Так нельзя в C++
switch(str) {
case "hello": ...
case "quit": ...
}


1️⃣ Решение: Хеширование через enum
Самый простой и быстрый способ:

enum class StringCode {
hello, quit, help, unknown
};

StringCode hashString(const string& str) {
if (str == "hello") return StringCode::hello;
if (str == "quit") return StringCode::quit;
if (str == "help") return StringCode::help;
return StringCode::unknown;
}

switch (hashString(command)) {
case StringCode::hello:
cout << "Hello there!" << endl;
break;
case StringCode::quit:
cout << "Goodbye!" << endl;
break;
}


✅ Плюсы: быстро, экономно по памяти
🔸 Когда: CLI интерфейсы, конфиги

2️⃣ Решение: unordered_map + функторы
Для динамической обработки:

class CommandProcessor {
unordered_map<string, function<void()>> commands;

public:
CommandProcessor() {
commands["help"] = []() {
cout << "Help info" << endl;
};
commands["add"] = []() {
cout << "Adding..." << endl;
};
}

void execute(const string& cmd) {
auto it = commands.find(cmd);
if (it != commands.end()) {
it->second();
}
}
};


✅ Плюсы: гибкость, можно менять во runtime
🔸 Когда: плагины, динамические команды

3️⃣Решение: Constexpr хеширование (C++17)
Хеширование на этапе компиляции:

constexpr uint64_t hash(string_view str) {
uint64_t hash = 0;
for (char c : str) {
hash = (hash * 131) + c;
}
return hash;
}

constexpr uint64_t operator""_hash(const char* str, size_t len) {
return hash(string_view(str, len));
}

switch (hash(action)) {
case "save"_hash:
cout << "Saving..." << endl;
break;
case "load"_hash:
cout << "Loading..." << endl;
break;
}


✅ Плюсы: производительность как у int switch
🔸 Когда: нужна скорость + compile-time проверки

4️⃣Решение: String Pool (advanced)
Для высоконагруженных систем:

class StringPool {
unordered_set<string> pool;
public:
const string* intern(const string& str) {
auto [it, inserted] = pool.insert(str);
return &(*it);
}
};

// Сравниваем указатели, а не строки!
if (str == pool.intern("start")) {
// быстрее обычного сравнения строк
}


✅ Плюсы: O(1) сравнение вместо O(n)
🔸 Когда: частые повторяющиеся строки

💡 Практический пример: Текстовый редактор
class TextEditor {
enum class CommandType {
insert, delete_line, move, print, unknown
};

CommandType getCommandType(const string& cmd) {
if (cmd == "insert" || cmd == "i")
return CommandType::insert;
if (cmd == "delete" || cmd == "d")
return CommandType::delete_line;
// ...
}

void executeCommand(const string& cmdLine) {
switch (getCommandType(cmd)) {
case CommandType::insert: /* ... */ break;
case CommandType::delete_line: /* ... */ break;
}
}
};


Сравнение производительности:
🔸 Enum хеширование: O(1) после предобработки
🔸 unordered_map: O(1) в среднем, гибко
🔸 Constexpr hash: O(1), compile-time
🔸 String Pool: O(1), лучшее для частых операций

Выбор метода:
Небольшой фикс. набор строк → Enum hash
Динамические команды → unordered_map
Критична скорость → Constexpr hash
Много повторений → String Pool


Важные моменты ⬇️

🔸Enum подход требует exhaustive списка
🔸unordered_map имеет overhead на lookup
🔸constexpr работает только с литералами
🔸String Pool нужен для действительно частых операций

Pro-tip:
Комбинируйте подходы! Например:
🔸constexpr hash для горячих путей
🔸 unordered_map для редких команд
🔸 String pool для логирования

Реальные кейсы применения:
✅Парсеры конфигов
✅CLI приложения
✅Обработка HTTP методов
✅Игровые движки (команды консоли)
✅Компиляторы (обработка токенов)


📎 Статья

🎙 Новости

📝 База вопросов
  • 👍 1
Post #289 83
🔖 10 мощных трюков со строками в JavaScript

Строки в JS — это не просто текст, а швейцарский нож для разработчика! Вот 10 приемов, которые сделают ваш код чище и быстрее.


1️⃣ Переворот строки в одну строку
const reverse = str => str.split('').reverse().join('');
reverse("hello"); // "olleh"


2️⃣ Проверка вхождения подстроки
"hello world".includes("world"); // true

✳️Быстро и читаемо!

3️⃣ Capitalize каждого слова
const toTitleCase = str => str.split(' ')
.map(word => word[0].toUpperCase() + word.slice(1).toLowerCase())
.join(' ');

toTitleCase("javascript tricks");
// "Javascript Tricks"


4️⃣ Удаление пробелов
"  hello world  ".trim(); 
// "hello world"


5️⃣ Повторение строки
"hello ".repeat(3); 
// "hello hello hello "

✳️Забудьте про циклы!

6️⃣ Подсчет вхождений
const count = (str, sub) => str.split(sub).length - 1;
count("hello world, hello JS", "hello"); // 2


7️⃣ Дополнение до нужной длины
"42".padStart(5, "0"); // "00042"
"7".padEnd(3, "0"); // "700"

✳️Идеально для ID и номеров!

8️⃣ Замена всех вхождений
"banana".replaceAll("a", "o"); // "bonono"

// Или через regex:
"banana".replace(/a/g, "o");


9️⃣ Извлечение только букв
const onlyLetters = str => str.replace(/[^a-zA-Z]/g, '');
onlyLetters("a1b2c3"); // "abc"

✳️Regex — ваш друг!

1️⃣0️⃣ Строка в массив символов
[..."hello"]; 
// ['h', 'e', 'l', 'l', 'o']

✳️Spread operator творит магию!

🟧Бонус-комбо:
// Подсчет уникальных символов
const uniqueChars = str => [...new Set(str)].length;
uniqueChars("hello"); // 4

// Удаление дубликатов
const removeDupes = str => [...new Set(str)].join('');
removeDupes("hello"); // "helo"


Почему это важно⬇️

✅ Код становится короче и читабельнее
✅ Меньше багов и edge cases
✅ Лучшая производительность
✅ Проще поддержка и рефакторинг


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


Частые ошибки:
❌ Забывают, что строки иммутабельны
❌ Используют циклы там, где есть методы
❌ Не знают про replaceAll и padStart
❌ Переусложняют regex там, где можно проще


📎 Статья

🎙 Новости

📝 База вопросов
  • 👍 2
Post #288 91
🔖 Java: Работа с большими данными без боли

Обрабатываете миллионы записей и приложение тормозит? 11 проверенных техник оптимизации Java Spring для работы с большими данными!


Проблема:
Приложение должно обработать 10 000+ транзакций по каждому счету. Как не упасть в OutOfMemoryError?


✳️Топ-11 техник оптимизации:

1️⃣ Оптимизация циклов
// ❌ Плохо
for (int i = 0; i < list.size(); i++)

// ✅ Хорошо
int size = list.size();
for (int i = 0; i < size; i++)

Экономия: вызов size() выносится за цикл

2️⃣ Пакетная обработка
Делим данные на батчи по 100-1000 записей
for (int i = 0; i < size; i += 100) {
List<T> batch = data.subList(i, min(i+100, size));
repository.saveAll(batch);
repository.flush(); // освобождаем память
}


3️⃣ Stream API с умом
// ❌ Создает промежуточные коллекции
.collect(Collectors.toList())

// ✅ Прямая обработка
.forEach(System.out::println)


4️⃣ Правильные структуры данных
• ArrayList вместо LinkedList (меньше памяти)
• int вместо Integer (без автобоксинга)
• HashMap с начальной емкостью

5️⃣ Параллельные потоки
// Используем все ядра процессора
list.parallelStream().forEach(this::process);


6️⃣ Избегаем создания объектов
// ❌ В цикле
new String("Result")

// ✅ Переиспользование
String result = "Result";


7️⃣ Optional осторожно
Не используйте Optional в полях класса — это лишние объекты-обертки!

8️⃣ Настройка GC
java -Xms1g -Xmx2g -XX:+UseG1GC \
-XX:MaxGCPauseMillis=200


9️⃣ Логи сборщика мусора
Включите на проде для мониторинга:
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps


1️⃣0️⃣ Слабые ссылки для кэша
// Предотвращаем утечки памяти
WeakHashMap<String, T> cache = new WeakHashMap<>();


1️⃣1️⃣ Transient для сериализации
Не сериализуйте лишние поля:
private transient int salary;



💡 Практический кейс:
Банковское приложение обрабатывало остатки по счетам с 10К+ транзакций. После применения этих техник:
🔸Память: -60%
🔸Скорость: +250%
🔸GC паузы: -80%


Частые ошибки:
❌ list.size() в условии цикла
❌ Создание объектов в циклах
❌ Неправильный начальный размер HashMap
❌ Переиспользование Optional
❌ Игнорирование настроек GC


🔸Главное правило:
Измеряйте производительность! Используйте профайлеры (JProfiler, VisualVM) перед оптимизацией.


Чек-лист перед продом ⬇️

✅ Пакетная обработка настроена
✅ Циклы оптимизированы
✅ GC параметры подобраны
✅ Логи GC включены
✅ Структуры данных выбраны правильно
✅ Утечки памяти проверены


📎 Статья

🎙 Новости

📝 База вопросов
  • 👍 1
Post #287 103
🔖 C++: полное руководство по mutable

Ключевое слово, которое часто упускают из виду, но оно критически важно для элегантного кода!

Что такое mutable?

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

class Example {
public:
void setValue(int val) const {
value = val; // Работает благодаря mutable!
}
private:
mutable int value;
};


Четыре основных сценария использования:

1️⃣ Кэширование

Сохраняйте результаты дорогостоящих вычислений:

class DataProcessor {
public:
int getSum() const {
if (!cachedSum_) {
cachedSum_ = std::accumulate(data_.begin(),
data_.end(), 0);
}
return *cachedSum_;
}
private:
std::vector<int> data_;
mutable std::optional<int> cachedSum_;
};


Преимущество: Вычисляем один раз, используем многократно!

2️⃣ Отложенные вычисления (Lazy Evaluation)

class LazyString {
public:
const std::string& getUpperCase() const {
if (!upperCase_) {
upperCase_ = original_;
std::transform(upperCase_->begin(),
upperCase_->end(),
upperCase_->begin(),
::toupper);
}
return *upperCase_;
}
private:
std::string original_;
mutable std::optional<std::string> upperCase_;
};


Экономия: Вычисляем только когда действительно нужно!

3️⃣ Потокобезопасность

class ThreadSafeCounter {
public:
int getValue() const {
std::lock_guard<std::mutex> lock(mutex_);
return value_;
}
private:
int value_ = 0;
mutable std::mutex mutex_; // Можем блокировать в const методах
};


4️⃣ Логическая неизменяемость

class BigData {
public:
int get(size_t index) const {
++accessCount_; // Не меняет логическое состояние
return data_[index];
}

size_t getAccessCount() const {
return accessCount_;
}
private:
std::vector<int> data_;
mutable size_t accessCount_ = 0;
};


💡 Практический пример: отложенная загрузка конфигурации

class Configuration {
public:
Configuration(const std::string& filename)
: filename_(filename) {}

std::string getValue(const std::string& key) const {
loadConfigIfNeeded(); // Загружаем только при первом обращении
return config_[key];
}

private:
void loadConfigIfNeeded() const {
if (!isLoaded_) {
// Загружаем конфигурацию из файла
isLoaded_ = true;
}
}

std::string filename_;
mutable bool isLoaded_ = false;
mutable std::unordered_map<std::string, std::string> config_;
};


Использование:
const Configuration config("settings.conf");
// Файл загрузится только здесь ↓
std::cout << config.getValue("log_level");


❗️Потенциальные ошибки:

1. Потокобезопасность
// ❌ НЕ потокобезопасно!
mutable int count_ = 0;
void increment() const { ++count_; }

// ✅ Потокобезопасно
mutable int count_ = 0;
mutable std::mutex mutex_;
void increment() const {
std::lock_guard<std::mutex> lock(mutex_);
++count_;
}


2. Не нарушайте логическую неизменяемость
// ❌ Плохо - меняет логическое состояние
class Bad {
void setData(int d) const { data_ = d; }
mutable int data_;
};

// ✅ Хорошо - только внутренняя оптимизация
class Good {
int getData() const {
++accessCount_; // Метаданные, не логика
return data_;
}
int data_;
mutable size_t accessCount_ = 0;
};


3. Документируйте использование
class Example {
private:
// mutable используется для кэширования
// с целью повышения производительности
mutable std::optional<int> cachedValue_;
};



Итог ⬇️

mutable — мощный инструмент для:
✅ Кэширования результатов
✅ Отложенных вычислений
✅ Потокобезопасности в const методах
✅ Сохранения логической неизменяемости


📎 Статья

🎙 Новости

📝 База вопросов
  • 👍 3
Post #286 82
🔖 TypeScript: 5 рекомендаций для профессионального кода

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


1️⃣ Используйте `unknown` вместо `any`

any отключает проверку типов — это как @ts-ignore для всей переменной. Опасно!

// ❌ Плохо
function process(data: any) {
return data.someMethod(); // Никаких проверок!
}

// ✅ Хорошо
function process(data: unknown) {
if (typeof data === 'object' && data !== null) {
// Безопасная проверка перед использованием
}
}


Правило: unknown заставляет вас явно проверить тип перед использованием. Это безопаснее!

2️⃣ Избегайте объектов-оберток непримитивов

// ❌ Плохо
let str: String = new String("hello");
let num: Number = new Number(42);
let bool: Boolean = new Boolean(true);

// ✅ Хорошо
let str: string = "hello";
let num: number = 42;
let bool: boolean = true;


Используйте примитивные типы: string, number, boolean, bigint, symbol.

3️⃣ Применяйте константные перечисления

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

// ❌ Генерирует runtime код
enum Status {
Active = "ACTIVE",
Inactive = "INACTIVE"
}

// ✅ Встраивается при компиляции
const enum Status {
Active = "ACTIVE",
Inactive = "INACTIVE"
}


Результат: Меньше кода, быстрее выполнение!

4️⃣ Используйте современные возможности JavaScript

TypeScript поддерживает опциональную цепочку (?.) и нулевое слияние (??):

// ❌ Старый способ
const name = user && user.profile && user.profile.name || 'Guest';

// ✅ Современный способ
const name = user?.profile?.name ?? 'Guest';


Преимущества:
🔸 Код короче и читабельнее
🔸 Меньше вероятность ошибок
🔸 Явная обработка null/undefined

5️⃣ Комбинируйте встроенные utility types

TypeScript предоставляет мощные инструменты для преобразования типов:

interface User {
id: number;
name: string;
email: string;
password: string;
}

// Все поля опциональны
type PartialUser = Partial<User>;

// Все поля только для чтения
type ReadonlyUser = Readonly<User>;

// Выбрать определенные поля
type PublicUser = Pick<User, 'id' | 'name' | 'email'>;

// Исключить определенные поля
type UserWithoutPassword = Omit<User, 'password'>;

// Сделать определенные поля обязательными
type RequiredName = Required<Pick<User, 'name'>>;


Комбинируйте их:
type SafeUser = Readonly<Omit<User, 'password'>>;


Итог ⬇️

TypeScript — мощный инструмент, но важно использовать его правильно:
✅ unknown безопаснее any
✅ Примитивные типы вместо оберток
✅ const enum для производительности
✅ Современный синтаксис JS
✅ Utility types для гибкости

Следуя этим рекомендациям, вы пишете не просто типизированный код, а надежный и поддерживаемый TypeScript!


📎 Статья

🎙 Новости

📝 База вопросов
  • ❤ 2
Post #285 71
🔖 C++: полное руководство по «приведению вверх»

Разбираем фундаментальную концепцию ООП, которая делает ваш C++ код гибким и расширяемым!


Что такое Upcasting?

Преобразование указателя/ссылки производного класса в указатель/ссылку базового класса — движение вверх по иерархии наследования.

Dog* dog = new Dog();
Animal* animal = dog; // Приведение вверх
animal->makeSound(); // "The dog barks"


Два способа приведения:

1️⃣ Неявное (рекомендуется)
Animal* animal1 = dog;


2️⃣ Явное
Animal* animal2 = static_cast<Animal*>(dog);


🔸 Приведение вверх + Полиморфизм

Пишите код для базового класса, работайте с любыми производными:

std::vector<Shape*> shapes;
shapes.push_back(new Circle(5));
shapes.push_back(new Rectangle(4, 6));

for (const auto& shape : shapes) {
shape->draw(); // Вызов правильного метода
cout << shape->area();
}


❗️Важно про деструкторы:

class Base {
public:
virtual ~Base() {} // ОБЯЗАТЕЛЬНО virtual!
};


Без виртуального деструктора не вызовется деструктор производного класса при удалении через базовый указатель → утечки памяти!

🔸 Практический пример: игровой движок

class GameEngine {
std::vector<std::unique_ptr<GameObject>> objects;
public:
void addObject(std::unique_ptr<GameObject> obj);
void updateAll(); // Обновляет всё единообразно
void renderAll(); // Рисует всё единообразно
};

engine.addObject(std::make_unique<Player>());
engine.addObject(std::make_unique<Enemy>());
engine.addObject(std::make_unique<Projectile>());


Храним Player, Enemy, Projectile в одном контейнере и работаем с ними единообразно!


💡 Upcasting vs Downcasting:

🔸 Upcasting — всегда безопасно, обычно неявно
🔸 Downcasting — может быть опасно, используйте dynamic_cast

Derived1* d1 = dynamic_cast<Derived1*>(basePtr);
if (d1) {
d1->derived1Method(); // Безопасно!
}


🔸Множественное наследование:

class C : public A, public B {};

C* c = new C();
A* a = c; // Приведение к A
B* b = c; // Приведение к B
// Оба работают корректно!



Итог ⬇️

Приведение вверх — основа полиморфизма в C++. Используйте его для:
✅ Единообразной работы с разными типами объектов
✅ Гибкого и расширяемого кода
✅ Реализации паттернов проектирования
✅ Создания универсальных контейнеров

Помните: всегда делайте базовый деструктор виртуальным при использовании полиморфизма!


📎 Статья

🎙 Новости

📝 База вопросов
Post #284 92
🔖 Развертывание Cloud Functions в GCP с помощью Terraform

Автоматизируйте развертывание serverless функций с Infrastructure as Code!


Что будем делать:

Создадим полностью автоматизированную инфраструктуру:
🔸Bucket для загрузки файлов
🔸Bucket для хранения исходного кода
🔸Cloud Function, которая триггерится при загрузке файла


Структура проекта:

cloud_function_project/
├── terraform/
│ ├── backend.tf
│ ├── function.tf
│ ├── main.tf
│ ├── storage.tf
│ └── variables.tf
└── src/
├── main.py
└── requirements.txt


Ключевые шаги:

1️⃣ Создаем Python функцию
def hello_gcs(event, context):
print('Event ID:', context.event_id)
print('Bucket:', event['bucket'])
print('File:', event['name'])


2️⃣ Настраиваем Terraform
Объявляем провайдера, переменные и ресурсы GCP

3️⃣ Упаковываем и деплоим
data "archive_file" "source" {
type = "zip"
source_dir = "../src"
}

resource "google_cloudfunctions_function" "function" {
runtime = "python37"
entry_point = "hello_gcs"
event_trigger {
event_type = "google.storage.object.finalize"
}
}


🔸Развертывание:

cd ./terraform
terraform init
terraform plan
terraform apply


💡 Преимущества подхода:

✅ Воспроизводимость инфраструктуры
✅ Версионирование через Git
✅ Простое масштабирование
✅ Автоматизация CI/CD
✅ Безопасность через код


Итог ⬇️

Terraform + Cloud Functions = идеальная комбинация для serverless архитектуры. Описываете инфраструктуру декларативно, а Terraform заботится о развертывании и управлении состоянием. Код становится единственным источником истины для вашей инфраструктуры.

Бонус: Поддержка Python, Node.js, Go, Java, .NET, Ruby и PHP!


📎 Статья

🎙 Новости

📝 База вопросов
  • ❤ 1
Post #283 82
🔖 Слоты в Angular: делаем компоненты по-настоящему гибкими

При создании UI-библиотек один из главных вызовов — найти баланс между гибкостью и простотой API. Разберём, как правильно работать с проекцией контента в Angular.


Два основных подхода:

1️⃣ ng-content — для простых случаев

Отлично подходит, когда:
• Слоты всегда определены
• Не нужен условный рендеринг
• Контекст не требуется

<app-field>
<span slot="label">Label</span>
<input>
</app-field>



2️⃣ ng-template — для сложных сценариев

Используйте, когда нужно:
• Передать контекст в слот
• Ленивая инициализация контента
• Условный рендеринг

<ng-template #suffix let-control>
<button [class.bg-red]="control.invalid">
Search
</button>
</ng-template>



💡 Практический совет:

Создайте универсальную директиву для унификации работы со слотами:

@Directive({
selector: 'ng-template[slot]'
})
export class Slot {
readonly template = inject(TemplateRef);
readonly name = input.required<string>({ alias: 'slot' });
}


Это даёт:
✅ Единообразный синтаксис
✅ Простую документацию
✅ Чистую идентификацию контента

🔸Реальный пример:

Компонент Accordion может комбинировать оба подхода:
🔸title — через ng-content (статичный текст)
🔸icon — через ng-template (нужен контекст открыт/закрыт)
🔸content — оба варианта (eager/lazy загрузка)


Итог ⬇️

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


📎 Статья

🎙 Новости

📝 База вопросов
Post #282 92
🔖 C++: руководство по сортировке строк

Сортировка строк — фундаментальная операция в программировании. Разбираем все методы от базовых до продвинутых.


1️⃣ Базовая сортировка: std::sort

#include <algorithm>
#include <string>

std::string str = "zycbwvutsrqponmlkjihgfedcba";
std::sort(str.begin(), str.end());
// Результат: "abcdefghijklmnopqrstuvwxyz"


Символы сортируются по значениям ASCII в порядке возрастания.

2️⃣ Сортировка вектора строк

std::vector<std::string> words = {"banana", "apple", "cherry", "date"};
std::sort(words.begin(), words.end());

// Результат:
// apple
// banana
// cherry
// date


Лексикографическая сортировка (словарный порядок).

3️⃣ Сортировка без учёта регистра

bool case_insensitive_compare(const std::string& a, const std::string& b) {
return std::lexicographical_compare(
a.begin(), a.end(),
b.begin(), b.end(),
[](char a, char b) {
return std::tolower(a) < std::tolower(b);
}
);
}

std::vector<std::string> words = {"Banana", "apple", "Cherry", "date"};
std::sort(words.begin(), words.end(), case_insensitive_compare);

// Результат:
// apple
// Banana
// Cherry
// date


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

4️⃣ Сортировка по длине

std::vector<std::string> words = {"short", "very long string", "medium"};

std::sort(words.begin(), words.end(),
[](const std::string& a, const std::string& b) {
return a.length() < b.length();
});

// Результат:
// short
// medium
// very long string


Лямбда-функция сравнивает длину строк.

5️⃣ Практика: сортировка имён

struct Name {
std::string first;
std::string last;
};

std::sort(names.begin(), names.end(),
[](const Name& a, const Name& b) {
if (a.last != b.last) {
return a.last < b.last;
}
return a.first < b.first;
});

// Входные данные:
// John Doe, Jane Doe, Alice Smith, Bob Johnson

// Результат:
// Jane Doe
// John Doe
// Bob Johnson
// Alice Smith


Многокритериальная сортировка: сначала по фамилии, затем по имени.

6️⃣ Unicode с библиотекой ICU

#include <unicode/coll.h>

bool compare_unicode(const std::string& a, const std::string& b) {
UErrorCode status = U_ZERO_ERROR;
icu::Collator* collator = icu::Collator::createInstance(icu::Locale(""), status);

icu::UnicodeString ua = icu::UnicodeString::fromUTF8(a);
icu::UnicodeString ub = icu::UnicodeString::fromUTF8(b);

UCollationResult result = collator->compare(ua, ub, status);
delete collator;

return result == UCOL_LESS;
}

std::vector<std::string> words = {"café", "cafe", "résumé", "resume"};
std::sort(words.begin(), words.end(), compare_unicode);


Корректная обработка диакритических знаков.

🔸Оптимизация производительности

1. std::string_view
Избегайте копирования для строк только для чтения.

2. std::stable_sort
Сохраняет относительный порядок эквивалентных элементов.

3. Параллельная сортировка (C++17+)
Для больших наборов данных.

Бенчмарк:
100,000 строк по 10 символов:
• std::sort: 0.245 сек
• std::stable_sort: 0.298 сек


🔸Выбор метода

Простая сортировка:
→ std::sort

Без учёта регистра: → Кастомный компаратор

По длине: → Лямбда-функция

Unicode: → ICU библиотека

Сохранить порядок: → std::stable_sort

Большие данные: → Параллельная сортировка (C++17+)


Итог⬇️

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

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


📎 Статья

🎙 Новости

📝 База вопросов
Post #281 84
🔖 Поддержка нескольких модальных окон в React Native

Команда Whitespectre создала библиотеку rn-modal-presenter для решения проблем с множественными модальными окнами в React Native.


❌ Проблемы стандартного подхода

1. Управление состояниями — нужна переменная для каждого модального окна
2. iOS ограничение — нельзя показать >1 модального окна одновременно
3. Размонтирование — при навигации модалки исчезают
4. Тихие ошибки — вторая модалка не появляется без предупреждений

Пример проблемы:

const App = () => {
const [showRateAppModal, setShowRateAppModal] = useState(false);
const [showAppRatedPositiveModal, setShowAppRatedPositiveModal] = useState(false);
const [showAppRatedNegativeModal, setShowAppRatedNegativeModal] = useState(false);
const [showActivateGadgetModal, setShowActivateGadgetModal] = useState(false);

return (
<SafeAreaView>
{showActivateGadgetModal && <ActivateGadgetModal />}
{showRateAppModal && <RateAppModal />}
{showAppRatedPositiveModal && <PositiveFeedbackModal />}
{showAppRatedNegativeModal && <NegativeFeedbackModal />}
</SafeAreaView>
);
};


Проблемы:
🔸 4 переменные состояния для 4 модалок
🔸 Сложная логика управления
🔸 При одновременном показе двух модалок — молчаливая ошибка на iOS

Почему сторонние библиотеки не помогли

react-native-modal:
🔸 Расширяет стандартный Modal
🔸 Те же ограничения iOS
🔸 Не решает проблему множественных окон

react-native-modalfy:
🔸 Поддерживает множественные модалки (JS-реализация)
❌ Много boilerplate кода
❌ Нельзя показать несколько экземпляров одной модалки

✅ Решение: rn-modal-presenter

Преимущества:

1. Императивный API — вызов из любого места
2. Без состояний — не нужны useState
3. Множественные модалки — из коробки
4. Гибкость — любой компонент как модалка
5. 100% JavaScript — работает кроссплатформенно

Установка:

yarn add @whitespectre/rn-modal-presenter


Использование:

import { ModalPresenterParent, showModal } from '@whitespectre/rn-modal-presenter';

const App = () => (
<ModalPresenterParent>
<SafeAreaView>
<Button
title="Activate Gadget"
onPress={() => showModal(ActivateGadgetModal, {})}
/>
</SafeAreaView>
</ModalPresenterParent>
);


Модалка управляет собой:

const ActivateGadgetModal = ({dismiss}: ModalContentProps) => (
<View style={styles.modal}>
<Text>Your Gadget has been activated</Text>
<Button
title="Close"
onPress={() => {
dismiss();
showModal(RateAppModal, {});
}}
/>
</View>
);


Сравнение подходов

Standard Modal:
❌ Много состояний
❌ Нет множественных модалок
❌ iOS ограничения
❌ Нет императивного API

react-native-modal:
❌ Много состояний
❌ Нет множественных модалок
❌ iOS ограничения
❌ Нет императивного API

react-native-modalfy:
✅ Без состояний
✅ Множественные модалки
✅ Императивный API
🔸Много boilerplate

rn-modal-presenter:
✅ Без состояний
✅ Множественные модалки
✅ Императивный API
✅ Минимум кода
✅ Несколько экземпляров одной модалки

Планы развития

🔸 Очередь модальных окон
🔸 Система приоритетов
🔸 Показ поверх Native вьюх
🔸 Кастомная анимация


Итог⬇️

rn-modal-presenter упрощает работу с множественными модальными окнами, убирая управление состояниями и предоставляя императивный API. Идеально для сложных флоу с несколькими последовательными модалками.


📎 Статья

🎙 Новости

📝 База вопросов
  • ❤ 1
Post #280 79
🔖 9 полезных инструментов CSS для веб-разработчиков

Написание CSS с нуля отнимает много времени. Эти бесплатные инструменты помогут ускорить разработку и повысить продуктивность.


1️⃣ Neumorphism

Генератор модного стиля "мягкого UI". Создаёт плавные тени и объёмные элементы.

Настройки:
🔸 Цвет
🔸 Размер
🔸 Радиус
🔸 Расстояние
🔸 Интенсивность теней

Автоматическая генерация CSS-кода для неоморфного дизайна.

2️⃣ CSS Clip-path Maker

Создание сложных фигур без знания свойства clip-path:
🔸 Многоугольники
🔸 Круги
🔸 Эллипсы
🔸 Кастомные формы

Визуальный редактор + готовый CSS-код.

3️⃣ Animista

Коллекция готовой CSS-анимации:
🔸 Входы/выходы элементов
🔸 Трансформации
🔸 Эффекты наведения
🔸 Текстовая анимация

Выбираете анимацию → редактируете → копируете код.

4️⃣ PurgeCSS

Удаление неиспользуемого CSS-кода.

Зачем нужен:
🔸 Фреймворки содержат много лишнего кода
🔸 Уменьшение размера файлов
🔸 Повышение производительности

Особенно полезен с Bootstrap, Tailwind и другими фреймворками.

5️⃣ Shadow Brumm

Генератор плавных теней.

Настройки:
🔸 Количество слоёв
🔸 Размытие
🔸 Смещение
🔸 Прозрачность

Создаёт реалистичные многослойные тени одним кликом.

6️⃣ CSS Gradient

Создание градиентных фонов:
🔸 Linear градиенты
🔸 Radial градиенты
🔸 Conic градиенты
🔸 Множественные цвета

Визуальный редактор + копирование кода.

7️⃣ CSS Scan

Премиум-инструмент для просмотра CSS любого элемента.

Возможности:
🔸 Наведите курсор → увидите все стили
🔸 Копирование кода одним кликом
🔸 Инспекция любого сайта

Идеально для изучения чужого кода и поиска вдохновения.

8️⃣ CSS Grid Generator

Генератор макетов CSS Grid.

Настройки:
🔸 Количество столбцов
🔸 Количество строк
🔸 Единицы измерения (fr, px, %)
🔸 Gap между элементами

Получаете CSS + HTML-код сетки.

9️⃣ Get Waves

Создание SVG-волн для фона.

Параметры:
🔸 Высота волн
🔸 Количество слоёв
🔸 Цвета
🔸 Инверсия

Готовый SVG + CSS для вставки в проект.


Итог ⬇️

Эти инструменты:
✅ Экономят время разработки
✅ Не требуют глубоких знаний CSS
✅ Генерируют чистый код
✅ Повышают продуктивность

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


📎 Статья

🎙 Новости

📝 База вопросов
  • ❤ 1
Post #279 76
🔖 C++: подробное руководство по выводу массива

Массивы — фундаментальные структуры данных C++. Разбираем эффективные способы их вывода для отладки и визуализации.


🔸 Классический цикл for

void printArray(int arr[], int size) {
for (int i = 0; i < size; i++) {
std::cout << arr[i];
if (i < size - 1) std::cout << ", ";
}
}


✅ Полный контроль над форматированием
✅ Легко менять разделители


🔸 Range-based for (C++11)

for (int i : std::array<int, 5>{arr, arr + size}) {
std::cout << i << " ";
}


✅ Лаконичнее
✅ Меньше ошибок с индексами


🔸STL: std::copy + std::ostream_iterator

std::copy(arr, arr + N, 
std::ostream_iterator<T>(std::cout, ", "));


✅ STL-подход
✅ Работает с любым типом


🔸Многомерные массивы

void print2DArray(int arr[][3], int rows, int cols) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
std::cout << arr[i][j] << " ";
}
std::cout << std::endl;
}
}


Вложенные циклы для матриц.


🔸std::array — современный подход

template<typename T, size_t N>
void printArray(const std::array<T, N>& arr) {
for (size_t i = 0; i < N; ++i) {
std::cout << arr[i];
if (i < N - 1) std::cout << ", ";
}
}


✅ Размер известен на компиляции
✅ Безопасность + удобство


🔸 Пользовательские типы

struct Person {
std::string name;
int age;

friend std::ostream& operator<<(
std::ostream& os, const Person& p) {
return os << p.name << " (" << p.age << ")";
}
};


Переопределяем operator<< для кастомных объектов.


🔸 Практика: анализ сенсоров

struct SensorReading {
double temperature;
double humidity;

friend std::ostream& operator<<(
std::ostream& os, const SensorReading& sr) {
return os << "Temp: " << sr.temperature
<< "°C, Humidity: "
<< sr.humidity << "%";
}
};


🔸 Когда что использовать?

• Простой for — полный контроль
• Range-based — современный код
• std::copy — STL-стиль
• std::array — фиксированный размер
• operator<< — свои типы


⬇️Итог

Эффективный вывод массива — это представление данных в понятном формате для отладки, анализа и работы с пользователем.


📎 Статья

🎙 Новости

📝 База вопросов
Post #278 106
🔖 JavaScript vs Gleam: пишем Todo CLI

Создаём одинаковое приложение на двух языках и сравниваем подходы.


🔸 Что делаем

CLI-приложение с 4 командами:
- list — показать все задачи
- add — добавить задачу
- done — отметить выполненные
- clear — удалить все

Формат хранения:
[ ] заголовок задачи
[*] выполненная задача


🔸JavaScript: Setup

pnpm init


Node v20, ESM модули, без сторонних библиотек. Файл todo.mjs.

Добавить задачу:
async function addTodo(filePath, todoTitle) {
return await useTodoFile(filePath, async (fd) => {
fd.appendFile(`[ ] ${todoTitle}\n`)
})
}


Форматированный список:
async function formattedTodos(filePath) {
return await useTodoFile(filePath, async (fd) => {
const content = await fd.readFile('utf8')
return content
.split('\n')
.filter((title) => title)
.map((title, index) => {
const isDone = /^\[\*\]/.test(title)
const rawTitle = title.match(/^\[[\s*]\]\s(.+)/)?.[1]
return `${index + 1} ${isDone ? strikethrough(rawTitle) : rawTitle}`
})
.join('\n')
})
}


Отметить выполненной:
async function markDone(filePath, ids) {
return await useTodoFile(filePath, async (fd) => {
const content = await fd.readFile('utf8')
const lines = content.split('\n')
ids.forEach((id) => {
lines[id - 1] = lines[id - 1].replace(/^\[\s\]/, '[*]')
})
await fd.truncate()
await fd.writeFile(lines.join('\n'))
})
}


🔸 Gleam: Setup

gleam new gleam_todos
gleam add argv simplifile gleam_community_ansi


Добавить задачу:
pub fn add_todo(file_path: String, title: String) -> Result(Nil, Nil) {
simplifile.append(file_path, "[ ] " <> title <> "\n")
|> result.replace(Nil)
|> result.nil_error
}


Форматированный список:
pub fn formatted_todos(file_path: String) -> Result(String, Nil) {
simplifile.read(file_path)
|> result.map(split_to_lines)
|> result.map(format_todo_lines)
|> result.map(join_lines)
|> result.nil_error
}

fn format_todo_lines(lines: List(String)) -> List(String) {
list.index_map(lines, fn(line, index) {
case line {
"[ ] " <> rest -> int.to_string(index + 1) <> " " <> rest
"[*] " <> rest -> int.to_string(index + 1) <> " " <> ansi.strikethrough(rest)
_ -> line
}
})
}


Отметить выполненной:
pub fn mark_done(file_path: String, ids: List(Int)) -> Result(Nil, Nil) {
simplifile.read(file_path)
|> result.map(split_to_lines)
|> result.map(fn(lines) { mark_todo_done(lines, ids) })
|> result.map(join_lines)
|> result.try(fn(content) { simplifile.write(file_path, content) })
|> result.nil_error
}


🔸 Запуск

JavaScript:
node todo.mjs add "first todo"
node todo.mjs list


Gleam:
gleam run add "first todo"
gleam run list



Итог ⬇️

JavaScript:
- ✅ Знакомый синтаксис
- ✅ Нативная работа с файлами
- ❌ Многословный код
- ❌ Ручная обработка ошибок

Gleam:
- ✅ Чистый, лаконичный синтаксис
- ✅ Result для обработки ошибок
- ✅ Pattern matching вместо регулярок
- ✅ Pipes |> для цепочек операций
- ❌ Менее знакомый синтаксис

Автор предпочитает Gleam за красивый код, мощную обработку ошибок и простые зависимости.

Gleam код читается как описание того, что делает программа, а не как она это делает.


📎 Статья

🎙 Новости

📝 База вопросов
  • ❤ 1
Post #277 130
🔖 Моделирование данных в современном стеке данных 2.0

Зачем моделировать данные, если Snowflake всё обработает сам? Разбираемся на примере.


🔸 Задача

Проанализировать элементы, которые пользователи видят в списке (товары, результаты поиска, посты в ленте).

Аналитикам нужно:
- Оценить CTR
- Определить влияние позиции (1-я vs 10-я)


🔸Современный стек данных

Этап 1: Сбор данных

Фронтенд отправляет JSON-событие:
{
"user_id": 3299223,
"event_time": "2022-04-21 12:31:55 UTC",
"items_shown": [1037287139, 1727228887, ..., 1665899805]
}


Этап 2: Доставка

API → Kafka/Airbyte/Fivetran → Snowflake → таблица RAW_Events

Этап 3: Готово!

Преимущества:
- Не нужно думать о масштабируемости
- Не нужно преобразовывать данные
- JSON работает из коробки


🔸 Анализ без моделирования

Запрос 1: Развернуть массив
SELECT 
user_id,
value::NUMBER as item_id,
index as position
FROM RAW_Events,
LATERAL FLATTEN(input => items_shown)


Запрос 2: Сколько пользователей видели item на позиции 1?
SELECT COUNT(DISTINCT user_id)
FROM (
SELECT user_id, value, index
FROM RAW_Events,
LATERAL FLATTEN(input => items_shown)
)
WHERE value = 1037287139
AND index = 0
AND DATE(event_time) = CURRENT_DATE


🔸 Проблемы:
- Сложный синтаксис
- Читает ВСЕ JSON из таблицы
- Распаковывает и разворачивает каждый раз
- Медленно и дорого
- При JOIN'ах всё повторяется


🔸 Решение: моделирование

Создаём таблицу Events:
CREATE TABLE Events AS
SELECT
user_id,
event_time,
value::NUMBER as item_id,
index as position
FROM RAW_Events,
LATERAL FLATTEN(input => items_shown)


Обновляем инкрементально (ежедневно/ежечасно).

Запрос 4: Тот же вопрос, но проще
SELECT COUNT(DISTINCT user_id)
FROM Events
WHERE item_id = 1037287139
AND position = 0
AND DATE(event_time) = CURRENT_DATE



🔸 Результаты (20M JSON)

Запрос 2 (без модели) | 47 сек | X-Small |
Запрос 4 (с моделью) | 0.5 сек | X-Small |

Разница: 94x быстрее!

При миллиардах JSON разница в 1000+ раз по времени и деньгам.


🔸 Когда нужно моделирование?

1. Данные достаточно большие
2. Запросы достаточно частые
3. Количество аналитиков ограничено
4. Нужна консистентность результатов


🔸 Итог

10-20 лет назад: аналитик не мог работать с сотнями миллионов строк без инженера данных.

Сегодня: современный стек позволяет исследовать данные самостоятельно. Моделирование не обязательно, но:
- Ускоряет запросы
- Упрощает SQL
- Снижает затраты
- Повышает производительность

Современный стек даёт свободу. Моделирование даёт скорость.


📎 Статья

🎙 Новости

📝 База вопросов
  • ❤ 1
Post #276 126
🔖 6 лайфхаков для улучшения JavaScript-кода

Современный JavaScript развивается стремительно. Разбираем 6 фич, которые делают код короче и чище.


1. Опциональные цепочки (?.)

Безопасная навигация по объектам без множественных проверок:

const someObject = {
profile: {
firstName: 'Nicky',
lastName: 'Christensen'
}
}

// ✅ С optional chaining
if (someObject?.profile?.firstName) {
console.log(someObject.profile.firstName)
}

// ❌ Старый способ
if (someObject && someObject.profile && someObject.profile.firstName) {
console.log(someObject.profile.firstName)
}

// Не выполнится, если свойства нет
if (someObject?.profile?.name) {
console.log('Not executed')
}


2. Оператор нулевого слияния (??)

Возвращает правый операнд только если левый null или undefined:

const falsy = false;
const emptyString = '';
const nullish = null;

console.log(falsy ?? 'Default'); // false
console.log(emptyString ?? 'Default'); // "" (не заменяется!)
console.log(nullish ?? 'Default'); // "Default"

// VS оператор ||
console.log(falsy || 'Default'); // "Default"
console.log(emptyString || 'Default'); // "Default"
console.log(nullish || 'Default'); // "Default"


Преимущество `??`: не заменяет false, 0 или "" — только null/undefined.

3. Динамический импорт

Асинхронная загрузка модулей (code splitting):

let someAsyncModule = await import('/modules/my-module.ts');


4. Promise.allSettled()

Узнать результат всех промисов, даже отклонённых:

const promise1 = Promise.resolve("OK");
const promise2 = Promise.reject("Error");
const promise3 = Promise.resolve("Done");

Promise.allSettled([promise1, promise2, promise3])
.then((results) => console.log(results));

// Результат:
// [
// { status: 'fulfilled', value: 'OK' },
// { status: 'rejected', reason: 'Error' },
// { status: 'fulfilled', value: 'Done' }
// ]


Отличие от `Promise.all()`: не падает при reject, показывает статус каждого промиса.

5. Spread-оператор (...)

Для массивов:
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const arr3 = [...arr1, ...arr2]; // [1,2,3,4,5,6]


Для объектов:
const basePerson = {
name: 'Nicky C',
country: 'DK'
}

const footballer = {
...basePerson,
team: 'Man UTD',
shirtNumber: '11'
}
// { name: 'Nicky C', country: 'DK', team: 'Man UTD', shirtNumber: '11' }


6. Деструктуризация объектов

Распаковка значений из объекта в переменные:

const footballer = {
name: 'Nicky C',
team: 'Man UTD',
shirtNumber: '11'
}

const { team, shirtNumber } = footballer;
console.log(team, shirtNumber); // "Man UTD" "11"



🔸Итог

- Сокращают код
- Улучшают читаемость
- Снижают количество багов
- Стандарт в современном JavaScript


Используйте уже сегодня!


📎 Статья

🎙 Новости

📝 База вопросов
  • ❤ 2
Older posts →
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →