Проблема: мертвый код в крупном проекте
Решение: Knip, статический анализатор для TypeScript
В нашем Angular-проекте с Nx workspace несколько приложений и более 100 библиотек. С ростом кодовой базы там накапливался неиспользуемый код: зависимости, экспорты, целые файлы. Очистить все это вручную невозможно: разработчики не успевают проверять каждый файл, а при рефакторинге старые сущности остаются.
Он находит неиспользуемые зависимости, файлы (.ts, .less, .html), экспорты, а также отсутствующие и неразрешенные импорты.
Мы настроили автоматическую генерацию workspaces, интеграцию в pre-commit и CI/CD. В итоге сократили число пакетов с 158 до 148, удалили сотни файлов и сделали разработку быстрее.
Вначале необходимо установить Knip как dev-зависимости:
npm install --save-dev knip
import type { KnipConfig } from "knip";
export default {
nx: true,
workspaces: {
"libs/core": { entry: ["src/index.ts"] },
"projects/project1": {
angular: true,
entry: ["src/main.ts", "src/app/app.module.ts"],
},
},
ignore: [
"**/*.d.ts",
"**/polyfills*.ts",
"**/environments/environment*.ts",
"**/*.spec.ts",
"**/*.test.ts",
],
ignoreDependencies: ["package-name-1"],
rules: {
files: "error",
dependencies: "error",
devDependencies: "error",
unresolved: "error",
exports: "error",
types: "error",
},
} satisfies KnipConfig;А далее — настроить базовую конфигурацию в knip.config.ts:
Ручное поддержание списка workspaces не работает: разработчики забывают добавлять новые библиотеки, старые записи остаются. Поэтому мы создали скрипт, который сканирует директорию libs/, находит папки с src/index.ts и генерирует конфигурацию.
function generateConfig() {
const workspaces = scanDirectory('libs', ''); // Аналогично для Angular-проектов
const newConfig = `import type {KnipConfig} from 'knip';
${formatWorkspacesObject(workspaces)}
export default {
nx: true,
workspaces: workspacesConfig,
...DEFAULT_KNIP_CONFIG (базовая конфигурация правил, которая описана выше)
} satisfies KnipConfig;
`;
writeFileSync('knip.config.ts', newConfig, 'utf-8');
}
function scanDirectory(
dir: string,
basePath: string,
) {
const result = {};
const entries = readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = join(dir, entry.name);
const workspacePath = basePath ? ${basePath}/${entry.name} : entry.name;
if (entry.isDirectory()) {
if (hasIndexTs(fullPath)) {
result[libs/${workspacePath}] = { entry: ["src/index.ts"] };
} else {
const nested = scanDirectory(fullPath, workspacePath);
Object.assign(result, nested);
}
}
}
return result;
}
function formatWorkspacesObject(
workspaces: Record<string, any>,
) {
const lines: string[] = [];
lines.push("const workspacesConfig: KnipConfig['workspaces'] = {");
for (const key of Object.keys(workspaces)) {
const config = workspaces[key];
const entryStr = entry: ['src/index.ts'];
lines.push('${key}': {${entryStr}},);
}
lines.push('};');
return lines.join('\n');
}
Ключевые функции скрипта, который рекурсивно сканирует директорию и собирает workspace конфигурации:
function validate() {
const knipConfig = readFileSync('knip.config.ts', "utf-8");
const workspacesInConfig = extractWorkspacesByRegex(knipConfig);
const actualWorkspaces = scanDirectory('libs', ""); // Аналогично для Angular-проектов
const missingWorkspaces = actualWorkspaces.filter(
(workspace) => !workspacesInConfig.has(workspace),
);
if (missingWorkspaces.length > 0) {
console.error("❌ Следующие workspace отсутствуют в knip.config.ts:");
missingWorkspaces.forEach((workspace) =>
console.error( - ${workspace}),
);
process.exit(1);
}
}
function scanDirectory(dir: string, basePath: string) {
const result: string[] = [];
if (!existsSync(dir)) return result;
const entries = readdirSync(dir, {withFileTypes: true});
for (const entry of entries) {
const fullPath = join(dir, entry.name);
const workspacePath = basePath ? ${basePath}/${entry.name} : entry.name;
if (entry.isDirectory()) {
if (hasIndexTs(fullPath)) {
result.push(libs/${workspacePath});
} else {
result.push(...scanDirectory(fullPath, workspacePath));
}
}
}
return result;
}
Второй скрипт scripts/knip/validate-workspaces.ts проверяет, что все библиотеки присутствуют в конфиге:
{
"scripts": {
"knip": "knip --tsConfig tsconfig.base.json 2>&1 | tee knip-report.txt",
"knip:fix": "knip --tsConfig tsconfig.base.json --fix",
"knip:generate": "ts-node scripts/knip/generate-workspaces.ts",
"knip:validate": "ts-node scripts/knip/validate-workspaces.ts"
}
}В package.json добавлены скрипты для работы с Knip:
Скрипт npm run knip запускает анализ с сохранением отчета, knip:fix автоматически исправляет проблемы, knip:generate автогенерирует конфигурацию workspaces, knip:validate проверяет актуальность конфигурации.
knip-validate:
stage: affected
script:
- npm run knip:validate
knip-report:
stage: affected
script:
- npm run knip:report
- |
if [ -s knip-report.txt ]; then
echo "⚠️ Есть рекомендации от Knip"
exit 1
fi
needs:
- knip-validate
Так мертвый код не попадает в основную ветку.Пойдем дальше и добавим CI-джобу, которая запустит эти проверки в пайплайне:
Мы сократили общее число пакетов с 158 до 148. Это ускорило установку node_modules примерно на 10% и уменьшило размер кэша в CI на несколько сотен мегабайт.
Knip помог удалить сотни файлов: старые моки, сервисы, утилитарные классы. Они не влияли на бандл (tree-shaking их исключал), но мешали разработке.
Автоматическая генерация и pre-commit исключили человеческий фактор — никто не забывает добавить новую библиотеку в конфиг.
Pre-commit и CI дают мгновенную обратную связь — мертвый код не попадает в основную ветку.
*️⃣ Почему React перестал лагать на больших списках? Все дело в новой архитектуре
Бывало такое, что печатаете в input, а интерфейс начинает лагать на большом списке? Это классическая проблема старого React из-за синхронного рекурсивного обхода дерева: он не мог прерваться, чтобы отдать управление браузеру.
Чтобы исправить это, в React переписали архитектуру. Отказались от рекурсивного стека, изобрели новую структуру данных (Fiber), научились прерывать рендер и расставлять приоритеты.
В карточках разбираемся, как устроен Fiber, почему will*-методы стали unsafe, как работает планировщик и как эволюционировала система приоритетов от ExpirationTime до Lanes. А больше подробностей с примерами кода ищите на Хабре.
⚡️ Как мы сократили потребление памяти на 18% и CPU на 31% в Kubernetes
Переезд автоматизированной банковской системы в K8s не прошел гладко. Приложения долго стартовали, поды умирали с OOMKill, heap рос неделями, а утилизация ресурсов была низкой. Мы перепробовали все: настраивали JVM, меняли GC, пересматривали количество реплик и даже заменили аллокатор памяти.
В итоге сократили потребление памяти на 18%, CPU — на 31%, а приложения стали работать быстрее.
🙂 В карточках наш руководитель направления Костя Никитин поделился, что делали, какие ключи JVM использовали, как считали память и почему два пода работают быстрее шести. А больше подробностей читайте на Хабре.
Так теперь умеет говорить наш бот поддержки. Мы обучили его английскому, узбекскому, таджикскому и китайскому, чтобы он мог отвечать на иностранные сообщения клиентов без переключения на сотрудника.
⠀
⚡️ В карточках — как устроен бот, как мы контролируем качество перевода и какие результаты получили.
#ai #llm #translation
2006 — первый банк без отделений
2009 — дебетовые карты с кэшбэком рублями
2011 — мобильное приложение
2026 — экосистема с 55 млн клиентов
За 20 лет Т-Банк превратился из смелой идеи банка без отделений в финтех мирового уровня с собственной экосистемой, десятками продуктов и более чем 55 млн клиентов.
⠀
Мы строим платформы, которые соответствуют амбициям большой экосистемы. Разрабатываем инструменты, которые повышают продуктивность команд. Создаем агентскую среду, в которой искусственный интеллект становится полноценным участником разработки.
⠀
В карточках наши технические директора поделились, как выглядел технологический стек Т-Банка тогда и сейчас, какие инженерные принципы появились еще на старте и что сохранилось до сих пор. Читайте и слушайте про наш путь 👇
☑️ Как заставить камеру на Android делать четкие фото для биометрии
Представьте: клиент хочет перевести крупную сумму и мобильный банк запрашивает подтверждение по селфи. Человек наводит камеру, делает фото, а на бэкенд уходит размытый кадр потолка с кусочком лица. Клиент получает отказ, звонит в поддержку, злится и теряет доверие к банку.
Чтобы не допускать таких случаев, мы сделали Face-Shot — библиотеку для Android, которая анализирует 30 кадров в секунду, ищет лицо, смотрящее в камеру, и передает лучший кадр на бэкенд.
🙂 В карточках наш ведущий инженер-программист Евгений Шувагин поделился, как команда работала с CameraX, настраивала фокус на лицо, боролась с шумоподавлением, ускоряла инициализацию и делала превью мгновенным. А больше подробностей и примеры кода ищите на Хабре.