From 629fc6a4abbf94f817d00cdbf98c0ed809fe0209 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=98=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2?= Date: Tue, 28 Jul 2026 20:52:33 +0300 Subject: [PATCH 01/16] docs: expand computer architecture answers --- src/pages/computer-science/index.md | 225 +++++++++++++++++++++------- 1 file changed, 168 insertions(+), 57 deletions(-) diff --git a/src/pages/computer-science/index.md b/src/pages/computer-science/index.md index 3e275c9..e7d8a39 100644 --- a/src/pages/computer-science/index.md +++ b/src/pages/computer-science/index.md @@ -21,16 +21,31 @@ order: 10 **Полный ответ** -Программа и данные хранятся в общей памяти, а процессор читает и выполняет инструкции последовательно, если управление -не изменено переходом. Классическая модель включает память, устройство управления, арифметико-логическое устройство и -ввод/вывод. +Архитектура фон Неймана описывает компьютер как систему, в которой **инструкции программы и обрабатываемые данные лежат в +одной адресуемой памяти**. Процессор получает из нее очередную инструкцию, декодирует ее, выполняет и переходит к следующей, +если сама инструкция не изменила поток управления. + +Этот процесс обычно объясняют циклом **fetch — decode — execute**: + +1. **Fetch** — устройство управления читает инструкцию по адресу из счетчика команд. +2. **Decode** — процессор определяет операцию и необходимые операнды. +3. **Execute** — ALU или другой исполнительный блок выполняет операцию. +4. **Store** — результат сохраняется в регистр или память, после чего обновляется счетчик команд. **Главные принципы**: -- Единое хранилище для программ и данных -- Однородность памяти -- Адресуемость памяти -- Последовательное программное управление +- единое хранилище для программ и данных; +- однородность памяти: ячейки различаются адресами, а не назначением; +- адресуемость: процессор обращается к конкретным ячейкам; +- последовательное программное управление с возможностью переходов. + +Общая память и общий путь к ней создают **бутылочное горлышко фон Неймана**: CPU может выполнять операции быстрее, чем +память успевает поставлять инструкции и данные. Поэтому современные процессоры используют регистры, несколько уровней +кеша, предварительную выборку и параллельное выполнение. Внутри процессора кеш инструкций и кеш данных могут быть +разделены, но на уровне программной модели компьютер все равно часто остается фоннеймановским. + +Например, при выполнении цикла над большим массивом производительность зависит не только от числа арифметических операций, +но и от того, насколько последовательно программа читает память и попадают ли данные в кеш. ![img.png](assets/von-neuman-architecture.png) @@ -44,42 +59,38 @@ order: 10 **Короткий ответ** -Основные части: +Классическая модель включает память, устройство управления, арифметико-логическое устройство и устройства ввода/вывода. +Устройство управления и ALU являются ключевыми частями CPU, но современный процессор содержит и другие блоки. **Полный ответ** -Основные части: +В классической архитектуре фон Неймана выделяют четыре основные части: -- память (RAM) -- устройство управления (Central Unit, CU) -- арифметико-логическое устройство (Arithmetic Logic Unit, ALU) -- устройства ввода и вывода (I/O) +- **память** хранит инструкции программы и данные; +- **устройство управления (CU)** получает и декодирует инструкции, а затем координирует работу остальных блоков; +- **арифметико-логическое устройство (ALU)** выполняет арифметические и логические операции; +- **устройства ввода и вывода (I/O)** связывают компьютер с диском, сетью, клавиатурой, экраном и другими устройствами. CPU объединяет управление и вычисления, RAM хранит активные инструкции и данные, а ввод/вывод связывает программу с -внешним миром. Современные компьютеры сложнее, но эта модель полезна как базовая абстракция. - -Строго говоря, **CU + ALU ≠ CPU**: это важные части процессора, но не весь процессор целиком. +внешним миром. Упрощенно выполнение команды выглядит так: CU читает инструкцию из памяти, подготавливает операнды, +направляет их в исполнительный блок и сохраняет результат. -Помимо устройства управления (CU) и арифметико‑логического устройства (ALU), в современном CPU есть и другие критически -важные компоненты: +Строго говоря, **CU + ALU не равны всему CPU**. Это важные части процессора, но современный CPU дополнительно содержит: -- Регистры. Это сверхбыстрая память прямо внутри ядра. В них временно держат операнды и результаты, чтобы ALU не тянул - данные из медленной RAM. Без регистров цикл «взять данные — посчитать — вернуть» был бы слишком долгим. -- Кэш‑память (L1, L2, иногда L3). Она хранит часто используемые данные и инструкции поближе к ядру. Кэш сильно смягчает - проблему «бутылочного горлышка» фон Неймана. -- Блок предсказания переходов и конвейер. В современных процессорах инструкции не выполняются строго по одной: их - «нанизывают» в конвейер, а блок предсказания заранее загружает нужные данные. Этим всем управляет именно логика внутри - CU, но сам блок — отдельная сложная структура. -- Исполнительные блоки помимо ALU. Например, FPU (для чисел с плавающей точкой) и векторные блоки (SIMD, как SSE/AVX в - x86). Они делают вычисления параллельно и отдельно от «обычного» ALU. -- Интерконнект и контроллеры. Внутри кристалла нужно соединять все эти блоки между собой и с внешней памятью; часто - прямо на кристалле размещают и контроллер памяти, и части PCIe‑интерфейса. +- **регистры** — самую быструю память для текущих операндов, адресов и результатов; +- **кеш L1, L2 и L3** — копии часто используемых данных и инструкций ближе к ядрам; +- **FPU и SIMD-блоки** — вычисления с плавающей точкой и обработку нескольких значений одной инструкцией; +- **конвейер и предсказатель переходов** — параллельную подготовку этапов инструкций и попытку заранее выбрать нужную + ветку; +- **декодеры, планировщики и reorder buffer** — внутреннее управление выполнением инструкций; +- **контроллеры памяти и внутренние шины** — обмен между ядрами, кешами, RAM и периферией. -На уровне базовой архитектуры (учебники, фон Нейман): часто говорят «CPU = CU + ALU», потому что цель — показать самую -суть: есть тот, кто управляет (CU), и тот, кто считает (ALU). Это упрощение, удобное для понимания принципов. +Например, выражение `total += prices[i]` требует не только сложения в ALU: нужно вычислить адрес элемента, получить данные +из регистра, кеша или RAM, выполнить операцию и сохранить результат. Поэтому скорость программы определяется всей +иерархией, а не только частотой ALU. -На уровне реального современного процессора: CPU — это сложная система из десятков и сотен подблоков, где CU и ALU — -важные, но не единственные части. +На интервью полезно сначала назвать учебную модель, а затем уточнить, что реальный CPU значительно сложнее. Это показывает, +что кандидат понимает границу между полезной абстракцией и физической реализацией. @@ -91,18 +102,44 @@ CPU объединяет управление и вычисления, RAM хр **Короткий ответ** -Frontend-разработчику эта модель помогает понимать CPU-bound задачи, main thread (главный поток) и стоимость доступа к -памяти. JavaScript выполняется не в вакууме: вычисления занимают CPU, объекты расходуют RAM, а сеть и диск (I/O) -работают существенно медленнее регистров и кешей процессора. Это помогает объяснять long tasks (тяжелые задачи), лаги -main thread, memory leaks (утечки памяти) и пользу Web Workers. +Frontend-разработчику эта модель помогает понимать CPU-bound задачи, main thread и стоимость доступа к памяти. +JavaScript-вычисления занимают CPU, объекты расходуют RAM, а сеть и диск работают существенно медленнее регистров и кешей. +Это объясняет long tasks, лаги интерфейса, memory leaks и пользу Web Workers. **Полный ответ** -Frontend-разработчику эта модель помогает понимать CPU-bound задачи, main thread (главный поток) и стоимость доступа к -памяти. JavaScript выполняется не в вакууме: вычисления занимают CPU, объекты расходуют RAM, а сеть и диск (I/O) -работают существенно медленнее регистров и кешей процессора. Это помогает объяснять long tasks (тяжелые задачи), лаги -main thread, memory leaks (утечки памяти) и пользу Web Workers. Знание базы позволяет оптимизировать измеряемые узкие -места, а не отдельные строки наугад. +Браузер скрывает детали железа, но производительность интерфейса все равно ограничена процессором, памятью и вводом/выводом. +Базовая модель помогает связать симптомы в DevTools с реальными причинами. + +**CPU-bound работа** занимает вычислительные ресурсы: + +- сортировка или агрегация большого массива; +- `JSON.parse()` большого ответа; +- синхронное преобразование изображений; +- сложный JavaScript во время рендера; +- style calculation, layout и часть paint pipeline. + +Если такая работа надолго занимает main thread, браузер не может вовремя обработать ввод пользователя и отрисовать +следующий кадр. Пользователь видит задержку клика, зависший scroll или пропущенную анимацию. Для тяжелых независимых +вычислений можно использовать Web Worker, но DOM остается доступен только из main thread. + +**Память** важна не только из-за утечек. Большие графы объектов увеличивают давление на garbage collector, а частые +выделения временных объектов могут вызывать заметные паузы. Например, хранение нескольких копий большого API-ответа в +store, кеше и view model повышает расход RAM без пользовательской пользы. + +**I/O-bound работа** чаще ждет сеть, диск или другой внешний ресурс. Здесь помогают HTTP-кеш, дедупликация запросов, +pagination, streaming, prefetch и правильная отмена устаревших операций, а не перенос кода в Worker. + +Практический порядок оптимизации: + +1. воспроизвести проблему и записать Performance profile; +2. определить, что ограничивает сценарий: CPU, память, сеть, layout или GPU; +3. найти самый дорогой участок; +4. изменить архитектуру или алгоритм; +5. повторить измерение и проверить пользовательскую метрику. + +Например, для таблицы на 100 000 строк правильное решение обычно не в микроправке одной функции. Нужны виртуализация, +меньше создаваемых DOM-узлов, стабильные ссылки на данные и перенос необязательных вычислений из критического пути. ![img.png](assets/frontend-mindset.png) @@ -116,13 +153,38 @@ main thread, memory leaks (утечки памяти) и пользу Web Worker **Короткий ответ** -CPU выполняет машинные инструкции и вычисления. RAM быстро хранит данные работающих процессов, но очищается после -выключения питания. Storage, например SSD, хранит файлы долговременно, но обычно имеет большую задержку доступа. +CPU выполняет машинные инструкции и вычисления. RAM быстро хранит данные работающих процессов, но теряет их после +выключения питания. Storage, например SSD, хранит файлы долговременно, однако обычно имеет большую задержку доступа. **Полный ответ** -CPU выполняет машинные инструкции и вычисления. RAM быстро хранит данные работающих процессов, но очищается после -выключения питания. Storage, например SSD, хранит файлы долговременно, но обычно имеет большую задержку доступа. +**CPU** выполняет машинные инструкции. Он читает команды, работает с регистрами и кешами, выполняет арифметические, +логические и управляющие операции. Несколько ядер позволяют действительно выполнять несколько потоков одновременно, хотя +конкретное распределение работы определяют операционная система и runtime. + +**RAM** — рабочая энергозависимая память. В ней находятся код и данные запущенных процессов: heap JavaScript, DOM, +буферы, библиотеки и другие активные структуры. RAM значительно больше кеша процессора, но доступ к ней медленнее. + +**Storage** — долговременное хранилище: SSD, HDD или флеш-память. На нем лежат файлы приложения, операционная система, +браузерный профиль и данные между перезапусками. Перед обработкой файл обычно читается со storage в RAM, после чего CPU +работает с загруженными данными. + +Упрощенная иерархия выглядит так: + +`регистры -> кеш CPU -> RAM -> SSD/HDD -> сеть` + +Чем ближе уровень к CPU, тем обычно меньше объем и ниже задержка. Поэтому последовательный доступ к уже загруженным данным +обычно дешевле случайного чтения большого числа мелких файлов или сетевых ресурсов. + +Frontend-пример: + +- JavaScript bundle хранится на сервере и может кешироваться на storage; +- браузер загружает его по сети и помещает необходимые данные в RAM; +- CPU парсит, компилирует и выполняет код; +- созданные объекты остаются в heap, пока доступны приложению или пока их не удалит garbage collector. + +Важно не смешивать физическую память с Web Storage API. `localStorage` и IndexedDB являются браузерными механизмами +долговременного хранения, а не прямым доступом JavaScript к RAM или SSD. ![img.png](assets/cpu-ram-storage.png) @@ -137,14 +199,32 @@ CPU выполняет машинные инструкции и вычислен **Короткий ответ** RAM быстрее и используется как рабочая память процессов, а диск предназначен для долговременного хранения. Данные с -диска обычно сначала читаются в память, после чего CPU может с ними работать. Недостаток RAM приводит к сборке мусора, -выгрузке страниц памяти и ухудшению отзывчивости. +диска обычно сначала читаются в RAM, после чего CPU может их обрабатывать. **Полный ответ** -RAM быстрее и используется как рабочая память процессов, а диск предназначен для долговременного хранения. Данные с -диска обычно сначала читаются в память, после чего CPU может с ними работать. Недостаток RAM приводит к сборке мусора, -выгрузке страниц памяти и ухудшению отзывчивости. +RAM и диск различаются назначением, скоростью и временем жизни данных. + +| Свойство | RAM | SSD/HDD | +| --- | --- | --- | +| Назначение | Активные данные процессов | Долговременные файлы | +| Сохранение без питания | Нет | Да | +| Задержка доступа | Ниже | Выше | +| Типичный объем | Меньше | Больше | +| Доступ CPU | Через память и кеши | После операции ввода/вывода | + +Когда программа открывает файл, операционная система обычно читает нужные блоки с диска в RAM. После этого процессор +работает с данными в памяти. ОС также использует page cache, поэтому повторное чтение файла иногда обслуживается из RAM и +происходит быстрее. + +Если физической RAM недостаточно, операционная система может выгружать неактивные страницы в swap или page file на диск. +Это помогает продолжить работу, но из-за более высокой задержки storage система может начать заметно тормозить. Сам по +себе недостаток RAM не означает, что garbage collector обязательно запустится: GC управляет памятью конкретного runtime, +а paging управляет операционная система. Эти механизмы связаны давлением на память, но не являются одним и тем же. + +Frontend-пример: загруженное изображение может занимать на диске 2 MB в сжатом JPEG, но после декодирования в RGBA-буфер +размером 4000 x 3000 пикселей потребовать около 48 MB RAM. Поэтому размер сетевого файла и фактическое потребление памяти +интерфейсом могут сильно различаться. ![img.png](assets/ram-vs-hard.png) @@ -158,15 +238,46 @@ RAM быстрее и используется как рабочая памят **Короткий ответ** -Это элементарная команда, которую CPU умеет декодировать и выполнять: загрузить данные, сложить значения, сравнить или -перейти к другому адресу. JavaScript сначала преобразуется движком в промежуточное представление и машинный код. Одна -строка исходника может потребовать много инструкций. +Машинная инструкция — элементарная команда из набора команд процессора: загрузить данные, выполнить арифметическую +операцию, сравнить значения или перейти к другому адресу. Одна строка JavaScript обычно превращается во множество таких +инструкций. **Полный ответ** -Это элементарная команда, которую CPU умеет декодировать и выполнять: загрузить данные, сложить значения, сравнить или -перейти к другому адресу. JavaScript сначала преобразуется движком в промежуточное представление и машинный код. Одна -строка исходника может потребовать много инструкций. +Машинная инструкция — команда, закодированная в формате, который понимает конкретная архитектура процессора, например +x86-64 или ARM64. Набор поддерживаемых команд и правила их кодирования называют **ISA — Instruction Set Architecture**. + +Инструкция обычно содержит: + +- **opcode** — какую операцию выполнить; +- **операнды** — регистры, константы или адреса памяти; +- иногда режим адресации и дополнительные флаги. + +Примеры категорий инструкций: + +- загрузка и сохранение данных между памятью и регистрами; +- сложение, умножение и побитовые операции; +- сравнение значений; +- условные и безусловные переходы; +- вызов функции и возврат; +- SIMD-операции над несколькими значениями. + +JavaScript не содержит прямых машинных инструкций. Движок браузера сначала парсит исходный код, строит внутреннее +представление, интерпретирует его и может JIT-компилировать часто выполняемые участки в машинный код. При изменении +предположений о типах оптимизированный код иногда деоптимизируется. + +Например, выражение: + +```js +const total = price + tax; +``` + +может потребовать проверить типы значений, загрузить их из памяти, выполнить сложение, обработать представление числа и +сохранить результат. Точное число инструкций зависит от движка, архитектуры CPU, контекста и примененных оптимизаций, +поэтому нельзя надежно оценивать производительность по числу строк исходного кода. + +Практический вывод: оптимизировать следует алгоритмы, объем данных и измеренные hot paths, а не пытаться вручную угадать +машинный код обычного JavaScript. ![img.png](assets/machine-command.png) From 889758895aa992a69618b45d0e2c5e9ce7a4c146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=98=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2?= Date: Tue, 28 Jul 2026 20:53:27 +0300 Subject: [PATCH 02/16] chore: add answer depth audit --- scripts/audit-answer-depth.mjs | 117 +++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 scripts/audit-answer-depth.mjs diff --git a/scripts/audit-answer-depth.mjs b/scripts/audit-answer-depth.mjs new file mode 100644 index 0000000..d7f4bc8 --- /dev/null +++ b/scripts/audit-answer-depth.mjs @@ -0,0 +1,117 @@ +import {readdir, readFile} from 'node:fs/promises'; +import {relative, resolve} from 'node:path'; +import process from 'node:process'; + +const ROOT = resolve(process.cwd(), 'src/pages'); +const STRICT = process.argv.includes('--strict'); +const MIN_ADDED_CHARACTERS = 180; +const MIN_LENGTH_RATIO = 1.35; + +async function collectMarkdownFiles(directory) { + const entries = await readdir(directory, {withFileTypes: true}); + const nestedFiles = await Promise.all( + entries.map(async (entry) => { + const path = resolve(directory, entry.name); + + if (entry.isDirectory()) { + return collectMarkdownFiles(path); + } + + return entry.isFile() && entry.name.endsWith('.md') ? [path] : []; + }), + ); + + return nestedFiles.flat(); +} + +function cleanAnswer(value) { + return value + .replace(/!\[[^\]]*]\([^)]*\)/g, ' ') + .replace(/<[^>]+>/g, ' ') + .replace(/[`*_>#|\-]+/g, ' ') + .replace(/\s+/g, ' ') + .trim() + .toLowerCase(); +} + +function extractQuestions(markdown) { + return [...markdown.matchAll(/
[\s\S]*?<\/details>/g)].flatMap(([block]) => { + const question = block.match(/([\s\S]*?)<\/summary>/)?.[1]?.trim(); + const shortMarker = '**Короткий ответ**'; + const fullMarker = '**Полный ответ**'; + const shortStart = block.indexOf(shortMarker); + const fullStart = block.indexOf(fullMarker); + + if (!question || shortStart === -1 || fullStart === -1 || fullStart <= shortStart) { + return []; + } + + const shortAnswer = block.slice(shortStart + shortMarker.length, fullStart); + const fullEnd = block.indexOf('', fullStart); + const fullAnswer = block.slice(fullStart + fullMarker.length, fullEnd); + + return [{question, shortAnswer: cleanAnswer(shortAnswer), fullAnswer: cleanAnswer(fullAnswer)}]; + }); +} + +function diagnose({shortAnswer, fullAnswer}) { + if (!shortAnswer || !fullAnswer) { + return 'empty answer'; + } + + if (shortAnswer === fullAnswer) { + return 'full answer duplicates short answer'; + } + + const addedCharacters = fullAnswer.startsWith(shortAnswer) + ? fullAnswer.length - shortAnswer.length + : Number.POSITIVE_INFINITY; + const lengthRatio = fullAnswer.length / shortAnswer.length; + + if (addedCharacters < MIN_ADDED_CHARACTERS) { + return `full answer only adds ${addedCharacters} characters`; + } + + if (lengthRatio < MIN_LENGTH_RATIO) { + return `full answer is only ${lengthRatio.toFixed(2)}x longer`; + } + + return null; +} + +const files = await collectMarkdownFiles(ROOT); +const findings = []; +let questionCount = 0; + +for (const file of files) { + const markdown = await readFile(file, 'utf8'); + const questions = extractQuestions(markdown); + + questionCount += questions.length; + + for (const question of questions) { + const reason = diagnose(question); + + if (reason) { + findings.push({file: relative(process.cwd(), file), question: question.question, reason}); + } + } +} + +if (findings.length === 0) { + console.log(`Checked ${questionCount} questions in ${files.length} files: no shallow full answers found.`); + process.exit(0); +} + +console.log(`Found ${findings.length} shallow full answers in ${questionCount} questions:\n`); + +for (const finding of findings) { + console.log(`- ${finding.file}: ${finding.question}`); + console.log(` ${finding.reason}`); +} + +console.log('\nA full answer should add reasoning, trade-offs, edge cases or a practical example.'); + +if (STRICT) { + process.exitCode = 1; +} From 3b5aa1df599fab850fd1ca876e3b75334ec77631 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=98=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2?= Date: Tue, 28 Jul 2026 20:53:44 +0300 Subject: [PATCH 03/16] chore: expose answer depth audit --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index f7b9c35..31308d2 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,8 @@ "preview": "astro preview", "debug": "npm run build && astro preview --host 127.0.0.1 --port 4321", "astro": "astro", + "audit:answers": "node scripts/audit-answer-depth.mjs", + "audit:answers:strict": "node scripts/audit-answer-depth.mjs --strict", "format": "prettier \"!package-lock.json\" . --ignore-path .gitignore --write", "format:check": "prettier \"!package-lock.json\" . --ignore-path .gitignore --check" }, From bb4db34d5e5a9ef4ada5f99023e4b798284b10a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=98=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2?= Date: Tue, 28 Jul 2026 20:54:32 +0300 Subject: [PATCH 04/16] docs: add answer writing guidelines --- CONTRIBUTING.md | 98 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..66c50be --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,98 @@ +# Как добавлять и редактировать вопросы + +## Короткий и полный ответы + +Каждый вопрос должен быть полезен в двух режимах: + +- **Короткий ответ** помогает быстро вспомнить основную мысль и ответить на первый вопрос интервьюера. +- **Полный ответ** помогает объяснить тему глубже, привести пример и продолжить разговор после уточняющих вопросов. + +Полный ответ не должен быть копией короткого ответа с одной дополнительной фразой. + +### Короткий ответ + +Обычно достаточно 1–3 предложений. Ответ должен: + +- сразу отвечать на вопрос; +- называть ключевые термины; +- не уходить в историю и второстепенные детали; +- быть понятным без чтения полного ответа. + +### Полный ответ + +Полный ответ должен раскрывать как минимум два дополнительных аспекта: + +- как механизм работает; +- зачем он нужен; +- практический пример; +- ограничения и trade-offs; +- распространенная ошибка или edge case; +- связь с frontend, backend, архитектурой или эксплуатацией; +- что важно сказать на интервью. + +Удобная структура: + +1. определение своими словами; +2. механизм или последовательность работы; +3. пример; +4. ограничения, альтернативы или типичная ошибка; +5. практический вывод. + +Не обязательно использовать все пять пунктов. Структура должна соответствовать вопросу, а не превращаться в формальный шаблон. + +## Пример + +Плохо: + +```md +**Короткий ответ** + +Web Worker выполняет JavaScript в отдельном потоке. + +**Полный ответ** + +Web Worker выполняет JavaScript в отдельном потоке. Он помогает не блокировать main thread. +``` + +Лучше: + +```md +**Короткий ответ** + +Web Worker выполняет JavaScript вне main thread и подходит для тяжелых вычислений, которые иначе блокировали бы интерфейс. + +**Полный ответ** + +Worker работает в отдельном execution context и обменивается данными с main thread через сообщения. У него нет прямого +доступа к DOM, поэтому UI обновляет основной поток. + +Например, в Worker можно вынести парсинг большого файла или расчет сложной агрегации. При этом нужно учитывать стоимость +создания Worker и сериализации данных. Для короткой операции перенос может оказаться дороже выполнения на main thread. +``` + +## Проверка глубины ответов + +Запустите аудит: + +```bash +npm run audit:answers +``` + +Команда находит ответы, которые полностью совпадают или слишком мало отличаются по объему. Это эвристика, а не замена +редакторской проверки: длинный ответ тоже может быть неточным или бесполезным. + +Для строгого режима с ненулевым exit code: + +```bash +npm run audit:answers:strict +``` + +## Checklist перед PR + +- Короткий ответ можно произнести примерно за 20–40 секунд. +- Полный ответ добавляет объяснение, а не только повторяет формулировку. +- Есть конкретный пример, когда он помогает понять тему. +- Указаны ограничения или trade-offs, если они существенны. +- Термины используются последовательно. +- Код и команды можно скопировать и выполнить в указанном контексте. +- Ответ не привязан к устаревшей версии технологии без явной пометки. From 09d4f6856776ddc283933723364eb11f6252a912 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=98=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2?= Date: Tue, 28 Jul 2026 20:56:27 +0300 Subject: [PATCH 05/16] chore: harden answer depth audit --- scripts/audit-answer-depth.mjs | 69 ++++++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/scripts/audit-answer-depth.mjs b/scripts/audit-answer-depth.mjs index d7f4bc8..11d7518 100644 --- a/scripts/audit-answer-depth.mjs +++ b/scripts/audit-answer-depth.mjs @@ -35,23 +35,41 @@ function cleanAnswer(value) { } function extractQuestions(markdown) { - return [...markdown.matchAll(/
[\s\S]*?<\/details>/g)].flatMap(([block]) => { - const question = block.match(/([\s\S]*?)<\/summary>/)?.[1]?.trim(); - const shortMarker = '**Короткий ответ**'; - const fullMarker = '**Полный ответ**'; - const shortStart = block.indexOf(shortMarker); - const fullStart = block.indexOf(fullMarker); - - if (!question || shortStart === -1 || fullStart === -1 || fullStart <= shortStart) { - return []; - } - - const shortAnswer = block.slice(shortStart + shortMarker.length, fullStart); - const fullEnd = block.indexOf('', fullStart); - const fullAnswer = block.slice(fullStart + fullMarker.length, fullEnd); + return [...markdown.matchAll(/
[\s\S]*?<\/details>/g)].flatMap( + ([block]) => { + const question = block + .match(/([\s\S]*?)<\/summary>/)?.[1] + ?.trim(); + const shortMarker = '**Короткий ответ**'; + const fullMarker = '**Полный ответ**'; + const shortStart = block.indexOf(shortMarker); + const fullStart = block.indexOf(fullMarker); + + if ( + !question || + shortStart === -1 || + fullStart === -1 || + fullStart <= shortStart + ) { + return []; + } - return [{question, shortAnswer: cleanAnswer(shortAnswer), fullAnswer: cleanAnswer(fullAnswer)}]; - }); + const shortAnswer = block.slice(shortStart + shortMarker.length, fullStart); + const fullEnd = block.indexOf('', fullStart); + const fullAnswer = block.slice( + fullStart + fullMarker.length, + fullEnd === -1 ? undefined : fullEnd, + ); + + return [ + { + question, + shortAnswer: cleanAnswer(shortAnswer), + fullAnswer: cleanAnswer(fullAnswer), + }, + ]; + }, + ); } function diagnose({shortAnswer, fullAnswer}) { @@ -93,24 +111,35 @@ for (const file of files) { const reason = diagnose(question); if (reason) { - findings.push({file: relative(process.cwd(), file), question: question.question, reason}); + findings.push({ + file: relative(process.cwd(), file), + question: question.question, + reason, + }); } } } if (findings.length === 0) { - console.log(`Checked ${questionCount} questions in ${files.length} files: no shallow full answers found.`); + console.log( + `Checked ${questionCount} questions in ${files.length} files: ` + + 'no shallow full answers found.', + ); process.exit(0); } -console.log(`Found ${findings.length} shallow full answers in ${questionCount} questions:\n`); +console.log( + `Found ${findings.length} shallow full answers in ${questionCount} questions:\n`, +); for (const finding of findings) { console.log(`- ${finding.file}: ${finding.question}`); console.log(` ${finding.reason}`); } -console.log('\nA full answer should add reasoning, trade-offs, edge cases or a practical example.'); +console.log( + '\nA full answer should add reasoning, trade-offs, edge cases or a practical example.', +); if (STRICT) { process.exitCode = 1; From b6ddb4bb252c1ae1ba7c223d88aba40b002b9e04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=98=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2?= Date: Tue, 28 Jul 2026 21:02:28 +0300 Subject: [PATCH 06/16] docs: expand operating system answers --- src/pages/operating-systems/index.md | 606 +++++++++++++++++++++------ 1 file changed, 473 insertions(+), 133 deletions(-) diff --git a/src/pages/operating-systems/index.md b/src/pages/operating-systems/index.md index c3cae96..cc57f87 100644 --- a/src/pages/operating-systems/index.md +++ b/src/pages/operating-systems/index.md @@ -20,11 +20,21 @@ order: 11 **Полный ответ** -Программа - это набор инструкций и данных, описывающих, что должен сделать компьютер. В исходном коде программа понятна -разработчику, а для выполнения она должна быть интерпретирована, скомпилирована или запущена внутри runtime. +Программа - это **пассивное описание вычисления**: исходный код, байткод, исполняемый файл, конфигурация и связанные +ресурсы. Пока программа не запущена, операционная система не выделяет ей отдельное время CPU и рабочую память процесса. + +Способ выполнения зависит от технологии: + +- нативный бинарный файл содержит машинный код для конкретной архитектуры; +- JavaScript загружается и выполняется движком, который может интерпретировать и JIT-компилировать код; +- Java или .NET обычно используют байткод и runtime; +- TypeScript сначала преобразуется в JavaScript, который затем выполняет JS-движок. -Например, файл `main.ts` - это исходный код. После сборки и запуска его результат становится частью работающего -процесса. +Например, `main.ts` сам по себе является исходным текстом. После сборки появляется JavaScript bundle, но работающим +приложением он становится только после загрузки браузером или запуска Node.js. + +Важно различать программу и ее выполнение: один и тот же Chrome, Node.js script или CLI можно запустить несколько раз. +Каждый запуск создаст отдельный процесс или набор процессов со своим состоянием. @@ -41,12 +51,25 @@ order: 11 **Полный ответ** -Программа - это пассивное описание: файлы на диске, исходный код или исполняемый файл. Процесс - это уже запущенный -экземпляр программы в операционной системе. +Программа описывает, **какой код можно выполнить**, а процесс представляет **конкретное выполнение этого кода сейчас**. +Один файл программы может породить несколько независимых процессов. + +У процесса обычно есть: -У процесса есть память, идентификатор процесса (PID), открытые файлы, переменные окружения, права доступа и состояние -выполнения. Одну и ту же программу можно запустить несколько раз, и операционная система создаст несколько разных -процессов. +- PID; +- виртуальное адресное пространство; +- код, heap и stacks потоков; +- открытые файлы и sockets; +- переменные окружения и аргументы запуска; +- текущая директория; +- пользователь, права и ограничения ресурсов; +- состояние выполнения, которое отслеживает scheduler. + +Например, если открыть два независимых процесса браузера или дважды запустить `node server.js`, код программы может быть +одинаковым, но память, открытые соединения и текущие запросы будут разными. + +Изоляция процессов повышает устойчивость: ошибка одного процесса обычно не дает ему напрямую изменить память другого. +Обратная сторона - обмен между процессами требует IPC: pipes, sockets, shared memory или других механизмов ОС. @@ -63,11 +86,21 @@ order: 11 **Полный ответ** -Операционная система находит исполняемый файл или runtime, создает процесс, выделяет ему память, передает аргументы и -переменные окружения, подключает стандартные потоки ввода/вывода и передает управление начальной точке программы. +Точная последовательность зависит от ОС и формата программы, но упрощенно запуск выглядит так: -Дальше программа выполняет инструкции, обращается к памяти, файлам, сети и другим ресурсам через API операционной -системы. +1. Shell, файловый менеджер или другая программа просит ОС запустить файл. +2. ОС проверяет путь, формат файла и права доступа. +3. Создается процесс с PID и виртуальным адресным пространством. +4. Loader отображает в память код, данные и необходимые shared libraries. +5. Процесс получает аргументы, environment variables, working directory и стандартные потоки. +6. Создается основной поток, его registers и stack. +7. Scheduler допускает поток к CPU, и выполнение начинается с entry point. + +В Unix-подобных системах shell часто создает дочерний процесс и вызывает семейство `exec`, а Windows предоставляет +`CreateProcess`. Детали разные, но смысл одинаков: ОС превращает пассивный файл в управляемое выполнение с ресурсами. + +После старта программа обращается к файлам, сети, таймерам и устройствам через runtime и системные вызовы. Даже простой +`console.log()` в итоге приводит к записи в стандартный output, которой управляет ОС. @@ -84,11 +117,30 @@ order: 11 **Полный ответ** -Операционная система - это слой между программами и аппаратным обеспечением. Она управляет процессами, памятью, файлами, -устройствами ввода/вывода, сетью, пользователями и правами доступа. +Операционная система одновременно является **менеджером ресурсов** и **набором абстракций** над аппаратным обеспечением. -Для разработчика ОС важна потому, что любая программа работает не напрямую с железом, а через правила и API операционной -системы. +Как менеджер ресурсов она решает: + +- какой поток и когда получит CPU; +- какие страницы памяти принадлежат процессу; +- кто может открыть файл или порт; +- как разделить устройства между программами; +- как изолировать пользователей и процессы. + +Как слой абстракций ОС дает программам более удобные сущности: + +- процесс вместо ручного управления CPU; +- файл вместо работы с блоками накопителя; +- socket вместо прямого формирования сетевых сигналов; +- virtual memory вместо физических адресов RAM; +- window, input events и device APIs поверх конкретного железа. + +ОС включает не только kernel. В систему также входят drivers, системные службы, библиотеки, shell и пользовательские +утилиты. Например, Linux технически является ядром, а привычная Linux-система дополнена user-space инструментами и +дистрибутивом. + +Для frontend-разработчика ОС проявляется через файловые watchers, лимиты открытых файлов, процессы dev server, переменные +окружения, сертификаты, DNS, sockets и поведение браузера. @@ -105,11 +157,25 @@ order: 11 **Полный ответ** -Ядро - центральная часть операционной системы, которая управляет самыми низкоуровневыми ресурсами: CPU, памятью, -процессами, драйверами, файловой системой и сетевым вводом/выводом. +Kernel работает с повышенными привилегиями и контролирует ресурсы, которым нельзя безопасно доверить обычным приложениям. + +К основным задачам ядра относятся: -Обычная программа не должна напрямую управлять железом. Она обращается к ядру через системные вызовы, например чтобы -прочитать файл, открыть сетевое соединение или создать новый процесс. +- планирование потоков на CPU; +- создание и завершение процессов; +- virtual memory и защита адресных пространств; +- обработка interrupts; +- работа с device drivers; +- файловые системы; +- network stack; +- проверка прав доступа и системных ограничений. + +Пользовательская программа не читает диск и не управляет сетевой картой напрямую. Она вызывает API runtime или системной +библиотеки, а те переходят к system call, например `open`, `read`, `write`, `mmap` или `socket`. Kernel проверяет запрос, +выполняет привилегированную операцию и возвращает результат. + +Ошибка в kernel или driver опаснее ошибки обычного приложения, потому что kernel имеет доступ ко всей системе. Поэтому +минимизация кода с высокими привилегиями и четкая граница user/kernel space важны для безопасности и устойчивости. @@ -126,11 +192,24 @@ Kernel space - область, где работает ядро ОС и код **Полный ответ** -Kernel space - область, где работает ядро ОС и код с максимальными правами. User space - область, где работают обычные -пользовательские программы. +Kernel space и user space разделены уровнями привилегий CPU и механизмами virtual memory. + +Код в **kernel space** может управлять page tables, drivers, interrupts и памятью разных процессов. Код в **user space** +обычно видит только собственное виртуальное адресное пространство и не может выполнить привилегированную инструкцию +напрямую. + +Когда приложению нужен защищенный ресурс, оно делает system call: + +1. CPU переключается в привилегированный режим. +2. Kernel проверяет параметры и права. +3. Операция выполняется или отклоняется. +4. Управление возвращается в user space. -Разделение нужно для безопасности и устойчивости: ошибка в обычной программе не должна ломать всю систему. Если -программе нужен файл, сеть или устройство, она просит об этом ядро через системный вызов. +Это разделение ограничивает последствия ошибок. Если вкладка браузера падает, обычно завершается ее процесс, а не вся ОС. +Если же критическая ошибка происходит в kernel driver, возможен crash всей системы. + +Переход между режимами не бесплатен, но обычно проблема производительности не в одном system call, а в огромном числе +мелких операций. Поэтому I/O часто буферизуют и объединяют в batches. @@ -147,11 +226,22 @@ Kernel space - область, где работает ядро ОС и код **Полный ответ** -Поток выполнения - это последовательность инструкций внутри процесса, которую планировщик ОС может выполнять на CPU. -Процесс может иметь один или несколько потоков. +Thread - минимальная единица выполнения, которую обычно планирует ОС. Каждый поток имеет собственные: + +- instruction pointer; +- registers; +- stack; +- состояние scheduler. -Потоки одного процесса обычно разделяют общую память процесса, поэтому обмениваться данными между ними дешевле, чем -между разными процессами. Но из-за общей памяти появляются риски гонок данных и необходимость синхронизации. +При этом потоки одного процесса обычно разделяют code, heap, открытые файлы и sockets. Благодаря общей памяти обмен +данными дешевле, чем между процессами, но требуется синхронизация. + +Например, browser process может использовать отдельные потоки для UI, I/O и служебной работы, а renderer process - +main thread и дополнительные worker threads. JavaScript страницы обычно выполняется на main thread, но Web Worker дает +отдельный execution context, который может работать параллельно на другом ядре. + +Если два потока изменяют общие данные без координации, появляется data race. Для защиты используют mutexes, atomics, +message passing или проектируют систему так, чтобы mutable state не разделялся. @@ -167,10 +257,26 @@ Kernel space - область, где работает ядро ОС и код **Полный ответ** -Процесс - изолированный экземпляр программы со своей памятью и ресурсами. Поток - линия выполнения внутри процесса. +Главное различие - **изоляция ресурсов**. + +Процесс обычно имеет отдельное виртуальное адресное пространство. Чтобы два процесса обменялись данными, нужен IPC. +Потоки одного процесса видят общий heap и многие общие ресурсы, но имеют собственные stacks и registers. -Разные процессы обычно не могут напрямую читать память друг друга. Потоки одного процесса разделяют память процесса и -часть ресурсов, поэтому они легче, но требуют аккуратной работы с общим состоянием. +Практические trade-offs: + +| Свойство | Процессы | Потоки | +| --- | --- | --- | +| Изоляция | Выше | Ниже | +| Обмен данными | Через IPC | Через общую память | +| Стоимость создания и переключения | Обычно выше | Обычно ниже | +| Последствие crash | Чаще локализовано | Может повредить весь процесс | +| Риск data races | Ниже между процессами | Выше при shared state | + +Браузеры используют multi-process архитектуру, чтобы изолировать вкладки, сайты и привилегированные компоненты. Внутри +каждого процесса при этом могут работать несколько потоков. + +Выбор зависит не только от скорости. Для недоверенного кода или сильной изоляции процесс может быть правильнее, даже если +он тяжелее. Для тесно связанной вычислительной работы могут подойти threads или workers. @@ -188,12 +294,23 @@ Kernel space - область, где работает ядро ОС и код **Полный ответ** -Конкурентность означает, что несколько задач продвигаются во времени независимо: одна ждет сеть, другая обрабатывает -ввод, третья готовится к выполнению. Параллельность означает, что несколько задач действительно выполняются одновременно -на разных ядрах CPU. +**Concurrency** описывает организацию нескольких незавершенных задач. Система переключается между ними или продолжает одну, +пока другая ждет I/O. + +**Parallelism** означает физическое одновременное выполнение как минимум двух операций, например на разных CPU cores. + +Однопоточный JavaScript может быть конкурентным: -Однопоточная программа тоже может быть конкурентной, если она умеет переключаться между задачами во время ожидания I/O. -Но для настоящего одновременного CPU-выполнения нужны несколько потоков, процессов, ядер или рабочих окружений. +1. код начинает `fetch`; +2. запрос обрабатывается сетевой подсистемой; +3. main thread продолжает другую работу; +4. после ответа callback или Promise continuation попадает в очередь. + +Но JavaScript callbacks в одном event loop не выполняются параллельно друг другу. Для CPU-параллелизма в браузере нужны +Web Workers, а в Node.js - worker threads или процессы. + +Важно не говорить, что `async/await` автоматически создает новый поток. Он упрощает управление асинхронным ожиданием, но +CPU-bound цикл по-прежнему блокирует текущий поток. @@ -210,12 +327,27 @@ CPU-bound задача упирается в вычисления процесс **Полный ответ** -CPU-bound задача упирается в вычисления процессора: парсинг большого объема данных, сжатие, криптография, сложная -агрегация. I/O-bound задача больше ждет внешний ресурс: диск, сеть, базу данных или файловую систему. +У CPU-bound и I/O-bound задач разные bottlenecks, поэтому им нужны разные оптимизации. + +**CPU-bound**: + +- CPU долго занят вычислением; +- ускоряется улучшением алгоритма, уменьшением данных, SIMD или параллелизмом; +- асинхронная обертка сама по себе не сокращает вычисления. + +Frontend-примеры: тяжелая сортировка, декодирование данных, image processing, сложный synchronous parser. + +**I/O-bound**: -Это различие важно для оптимизации. CPU-bound работу часто ускоряют параллельным выполнением или алгоритмическими -улучшениями. I/O-bound работу чаще улучшают кешированием, батчингом, асинхронностью и уменьшением числа обращений к -внешним ресурсам. +- большая часть времени уходит на ожидание сети, диска или базы данных; +- помогает concurrency, caching, batching, streaming и сокращение round trips; +- дополнительные CPU cores могут почти не изменить latency одного запроса. + +Например, десять HTTP-запросов можно выполнять конкурентно, если backend допускает это. Но запуск десяти тяжелых +вычислений на одном main thread не сделает их параллельными и может ухудшить responsiveness. + +Сначала нужно измерить, где тратится время. Медленный пользовательский сценарий часто смешивает оба типа: сеть доставляет +большой JSON, после чего main thread долго его парсит и рендерит. @@ -232,10 +364,22 @@ CPU-bound задача упирается в вычисления процесс **Полный ответ** -Терминал - это интерфейс для ввода команд и просмотра текстового вывода программ. В современных системах чаще всего это -приложение-эмулятор терминала: Terminal, iTerm, Windows Terminal или встроенный терминал IDE. +Исторически terminal был физическим устройством с клавиатурой и экраном, подключенным к вычислительной машине. Сегодня +terminal emulator - приложение, которое воспроизводит текстовый интерфейс терминала. + +Он отвечает за: + +- отображение символов, colors и cursor; +- передачу нажатий клавиш; +- размер текстовой области; +- обработку control sequences; +- связь с shell или другой консольной программой через pseudo-terminal. + +Сам terminal обычно не решает, что означает `cd`, `npm` или pipe. Он передает ввод запущенной программе и отображает ее +output. Обычно этой программой является shell, но в terminal можно запустить REPL, editor или другую TUI-программу. -Сам терминал не обязательно понимает команды. Обычно он запускает shell, а уже shell интерпретирует введенную строку. +Поэтому закрытие окна terminal может завершить связанные процессы, но это зависит от того, привязаны ли они к terminal, +запущены ли через background manager, `nohup`, `tmux`, system service или IDE. @@ -252,11 +396,29 @@ Terminal отвечает за окно, ввод, вывод и отображ **Полный ответ** -Terminal отвечает за окно, ввод, вывод и отображение текста. Shell - это программа, которая читает команду, разбирает -аргументы, ищет исполняемые файлы, запускает процессы и связывает их ввод/вывод. +Terminal предоставляет **канал взаимодействия**, а shell предоставляет **язык команд и управление процессами**. + +Shell: + +- разбирает quoting, variables и wildcard expansion; +- выполняет built-in команды вроде `cd`; +- ищет программы по `PATH`; +- создает pipelines; +- настраивает redirections; +- запускает foreground и background jobs; +- возвращает exit code. -Примеры shell: `bash`, `zsh`, `fish`, `PowerShell`. Поэтому одна и та же команда может работать по-разному в разных -shell или на разных операционных системах. +Например, при команде: + +```bash +cat app.log | grep ERROR > errors.log +``` + +terminal передает строку shell. Shell создает процессы `cat` и `grep`, соединяет `stdout` первого со `stdin` второго и +перенаправляет результат в файл. Terminal только отображает ввод и то, что не было перенаправлено. + +Примеры shell: `bash`, `zsh`, `fish`, PowerShell. Их syntax и built-ins отличаются, поэтому команда для Bash не обязана +работать в PowerShell, даже если используется то же приложение terminal. @@ -268,18 +430,32 @@ shell или на разных операционных системах. **Короткий ответ** -Это стандартные потоки процесса: +`stdin` - стандартный ввод процесса, `stdout` - обычный результат, `stderr` - ошибки и диагностика. Shell может соединять +и перенаправлять эти потоки. **Полный ответ** -Это стандартные потоки процесса: +При запуске консольный процесс обычно получает три стандартных stream: + +- `stdin` с file descriptor `0`; +- `stdout` с file descriptor `1`; +- `stderr` с file descriptor `2`. -- `stdin` - стандартный ввод -- `stdout` - стандартный вывод обычного результата -- `stderr` - стандартный вывод ошибок и диагностики +По умолчанию они могут быть связаны с terminal, но shell умеет заменить источник или назначение: -Shell может перенаправлять эти потоки: передавать вывод одной программы на вход другой, писать результат в файл или -разделять обычный output и ошибки. +```bash +node build.mjs < input.json +node build.mjs > result.txt +node build.mjs 2> errors.txt +node build.mjs > all.txt 2>&1 +cat app.log | grep ERROR +``` + +Разделение `stdout` и `stderr` важно для автоматизации. CLI может писать machine-readable результат в `stdout`, а progress +и диагностику - в `stderr`, не повреждая данные pipeline. + +Stream не обязательно является файлом на диске. Он может быть terminal, pipe, socket или специальное устройство. Запись +может буферизоваться, поэтому момент вызова logging API и момент появления текста не всегда совпадают. @@ -292,15 +468,32 @@ Shell может перенаправлять эти потоки: переда **Короткий ответ** Переменные окружения - это пары ключ-значение, которые ОС или shell передают процессу при запуске. Они часто -используются для конфигурации: режим окружения, адрес API, токены, порты, feature flags. +используются для конфигурации: режим окружения, адрес API, токены, порты и feature flags. **Полный ответ** -Переменные окружения - это пары ключ-значение, которые ОС или shell передают процессу при запуске. Они часто -используются для конфигурации: режим окружения, адрес API, токены, порты, feature flags. +Environment variables являются частью окружения процесса. Обычно дочерний процесс получает **копию** набора переменных +родителя при запуске. + +Пример: + +```bash +API_URL=https://api.example.com NODE_ENV=production node server.mjs +``` + +Процесс читает значения через API runtime, например `process.env` в Node.js. Если после запуска изменить переменную в +другом окне shell, работающий процесс обычно не получит обновление автоматически. + +Важные ограничения: + +- значения передаются как strings; +- наличие переменной нужно валидировать при старте; +- environment не является безопасным secret storage сам по себе; +- переменные frontend build часто подставляются в bundle во время сборки и становятся видны пользователю; +- runtime-конфигурация браузерного приложения требует отдельного механизма, например config endpoint или файла. -Процесс получает собственный набор environment variables при старте. Если изменить переменную в терминале после запуска -программы, уже работающий процесс обычно не узнает об этом автоматически. +Хорошая практика - преобразовать сырые variables в типизированный config один раз при старте и завершить процесс с понятной +ошибкой, если обязательное значение отсутствует. @@ -312,16 +505,30 @@ Shell может перенаправлять эти потоки: переда **Короткий ответ** -Exit code - числовой код завершения процесса. Обычно 0 означает успешное завершение, а ненулевое значение означает -ошибку или особое состояние. +Exit code - числовой код завершения процесса. Обычно `0` означает успех, а ненулевое значение - ошибку или другое +неуспешное состояние. **Полный ответ** -Exit code - числовой код завершения процесса. Обычно `0` означает успешное завершение, а ненулевое значение означает -ошибку или особое состояние. +Когда процесс завершается, ОС сохраняет его status, который может прочитать родительский процесс или shell. По соглашению +`0` означает успешное выполнение, а ненулевые значения обозначают разные причины неуспеха. -Exit code важен для shell scripts, CI/CD и package scripts. Например, если тесты завершаются с ненулевым кодом, pipeline -понимает, что проверка не прошла. +Shell использует status для управления командами: + +```bash +npm test && npm run build +npm test || echo "Tests failed" +``` + +В первом случае build запускается только после `0`, во втором правая команда выполняется после ошибки. CI/CD работает по +тому же принципу: job считается failed, если команда вернула ненулевой status. + +CLI полезно выбирать стабильные codes и писать объяснение в `stderr`. При завершении из-за signal отображаемое значение +может кодироваться особым образом и отличаться между shell и ОС, поэтому не стоит считать любое конкретное ненулевое +число универсальным без документации инструмента. + +В Node.js можно задать `process.exitCode`, чтобы runtime корректно завершил pending synchronous cleanup, вместо +немедленного `process.exit()`. @@ -333,16 +540,26 @@ Exit code важен для shell scripts, CI/CD и package scripts. Напри **Короткий ответ** -Порт - это числовой идентификатор сетевого endpoint внутри компьютера. IP-адрес помогает найти машину в сети, а порт -помогает понять, какой программе на этой машине предназначено соединение. +Порт - числовой идентификатор transport endpoint внутри хоста. IP-адрес помогает найти сетевой интерфейс, а порт - +программу или службу, которой предназначены данные. **Полный ответ** -Порт - это числовой идентификатор сетевого endpoint внутри компьютера. IP-адрес помогает найти машину в сети, а порт -помогает понять, какой программе на этой машине предназначено соединение. +TCP и UDP используют номера портов от `0` до `65535`, чтобы несколько сетевых программ могли работать на одном IP-адресе. +TCP port и UDP port являются разными пространствами: процесс может использовать один номер для TCP, а другой - для UDP. + +Server обычно bind-ится к известному порту, например `443`. Client при исходящем соединении получает временный ephemeral +port. TCP-соединение различается набором: + +`protocol + source IP + source port + destination IP + destination port` + +Поэтому тысячи clients могут одновременно обращаться к одному server port: у соединений различаются source endpoints. + +В URL порт указывают после двоеточия: `http://localhost:3000`. Если порт не указан, схема задает default, например `80` +для HTTP и `443` для HTTPS. -Например, в адресе `http://localhost:3000` число `3000` - это порт. На одном компьютере могут одновременно работать -разные серверные процессы на разных портах. +Порт не является физическим разьемом и не гарантирует конкретный protocol приложения. На `3000` может работать HTTP server, +но это определяется программой, а не самим числом. @@ -354,16 +571,28 @@ Exit code важен для shell scripts, CI/CD и package scripts. Напри **Короткий ответ** -Это значит, что программа попросила ОС привязать сетевой socket к конкретному порту и готова принимать входящие -соединения или пакеты. +Это значит, что программа привязала socket к адресу и порту и готова принимать входящие соединения или datagrams. **Полный ответ** -Это значит, что программа попросила ОС привязать сетевой socket к конкретному порту и готова принимать входящие -соединения или пакеты. +Для TCP server последовательность обычно включает: -Например, dev server может слушать `localhost:3000`. Когда браузер открывает этот адрес, ОС направляет сетевое -соединение процессу, который слушает порт `3000`. +1. создать socket; +2. выполнить `bind` к local address и port; +3. перевести socket в состояние `listen`; +4. принимать соединения через `accept`; +5. читать и писать данные через socket каждого соединения. + +Для UDP постоянного соединения может не быть: программа bind-ится к порту и получает отдельные datagrams. + +Адрес binding важен: + +- `127.0.0.1:3000` доступен только через loopback; +- `0.0.0.0:3000` означает все подходящие IPv4 interfaces; +- конкретный LAN IP ограничивает один interface. + +Например, dev server, слушающий только localhost, откроется на ноутбуке разработчика, но обычно не будет доступен с +телефона в той же сети. Чтобы открыть доступ, нужно изменить binding и учесть firewall. @@ -375,16 +604,34 @@ Exit code важен для shell scripts, CI/CD и package scripts. Напри **Короткий ответ** -Обычно один и тот же адрес и порт не могут быть заняты двумя разными процессами одновременно. Если dev server уже -слушает localhost:3000, второй сервер при попытке занять тот же порт получит ошибку вроде EADDRINUSE. +Другой socket уже может быть привязан к несовместимому сочетанию protocol, address и port. Тогда новый server получает +ошибку вроде `EADDRINUSE`. **Полный ответ** -Обычно один и тот же адрес и порт не могут быть заняты двумя разными процессами одновременно. Если dev server уже -слушает `localhost:3000`, второй сервер при попытке занять тот же порт получит ошибку вроде `EADDRINUSE`. +ОС не позволяет двум listening sockets без специальных условий одновременно владеть одним и тем же endpoint. Например, +если процесс слушает `0.0.0.0:3000`, он занимает порт на всех IPv4 interfaces и может конфликтовать с binding к +`127.0.0.1:3000`. + +Причины: + +- старый dev server продолжает работать; +- IDE или terminal оставили background process; +- другой инструмент использует тот же default port; +- предыдущие соединения еще находятся в переходном TCP state; +- настройки контейнера публикуют host port. + +Диагностика зависит от ОС, например: -Решение зависит от ситуации: остановить старый процесс, выбрать другой порт или проверить, не остался ли фоновый процесс -после закрытия терминала или IDE. +```bash +lsof -i :3000 +``` + +После определения процесса можно корректно остановить его или выбрать другой порт. Не следует без проверки завершать +случайные системные процессы. + +Options вроде `SO_REUSEADDR` влияют на повторное использование адреса, но не означают, что любые два servers безопасно +разделят один endpoint. Семантика зависит от ОС и socket options. @@ -396,16 +643,27 @@ Exit code важен для shell scripts, CI/CD и package scripts. Напри **Короткий ответ** -localhost - имя, которое указывает на текущий компьютер. Обычно оно разрешается в loopback-адрес 127.0.0.1 для IPv4 или -::1 для IPv6. +`localhost` - имя текущего хоста, которое обычно разрешается в loopback-адрес `127.0.0.1` для IPv4 или `::1` для IPv6. **Полный ответ** -`localhost` - имя, которое указывает на текущий компьютер. Обычно оно разрешается в loopback-адрес `127.0.0.1` для IPv4 -или `::1` для IPv6. +Loopback interface позволяет сетевым программам на одном компьютере общаться через обычный network stack без отправки +пакетов во внешнюю сеть. + +Когда браузер открывает `http://localhost:3000`, он соединяется с process на той же network namespace, который слушает +подходящий address и port. + +Важно различать: -Когда браузер открывает `http://localhost:3000`, запрос не уходит во внешнюю сеть. Он направляется на программу, -работающую на этом же компьютере и слушающую порт `3000`. +- `localhost` - host name; +- `127.0.0.1` и `::1` - loopback IP addresses; +- `0.0.0.0` - не адрес для обращения из браузера, а специальный bind address "все IPv4 interfaces". + +В container или virtual machine `localhost` относится к самой изолированной среде, а не автоматически к host OS. +Например, `localhost` внутри Docker container указывает на container. Для доступа к host или соседнему service нужен +отдельный address или network name. + +Также возможна разница IPv4/IPv6: server, слушающий только `127.0.0.1`, не всегда примет соединение на `::1`. @@ -417,16 +675,26 @@ localhost - имя, которое указывает на текущий ком **Короткий ответ** -Сервером могут называть и физическую или виртуальную машину, и программу, и роль в сетевом взаимодействии. В базовом -смысле сервер - это сторона, которая принимает запросы и возвращает ответы или предоставляет ресурс. +Сервером называют роль, программу или машину, которая принимает запросы или соединения и предоставляет данные либо +другой ресурс. **Полный ответ** -Сервером могут называть и физическую или виртуальную машину, и программу, и роль в сетевом взаимодействии. В базовом -смысле сервер - это сторона, которая принимает запросы и возвращает ответы или предоставляет ресурс. +Термин server используется на нескольких уровнях: + +- **роль** в конкретном взаимодействии; +- **server process**, например Node.js HTTP server; +- **host или virtual machine**, на которой работают server processes; +- **hardware**, предназначенное для серверной нагрузки. + +Server обычно слушает endpoint, принимает запрос, выполняет обработку и возвращает response. Он может одновременно +обслуживать много clients через event loop, threads, processes или их комбинацию. -Ноутбук разработчика тоже может быть сервером, если на нем запущена программа, принимающая соединения. Например, -локальный dev server Angular, Vite или Astro обслуживает браузер во время разработки. +Ноутбук разработчика становится server для браузера, когда на нем работает Angular, Vite или Astro dev server. При этом +тот же dev server может выступать client, когда запрашивает данные у backend. + +Server не обязан быть удаленным и не обязан работать по HTTP. Файловый server, database, DNS server и WebSocket server +предоставляют разные protocols и ресурсы. @@ -438,16 +706,26 @@ localhost - имя, которое указывает на текущий ком **Короткий ответ** -Клиент - это сторона, которая инициирует запрос к серверу. Браузер, мобильное приложение, CLI-утилита или другой сервис -могут выступать клиентом. +Клиент - сторона, которая инициирует взаимодействие с server. Им может быть браузер, мобильное приложение, CLI или другой +service. **Полный ответ** -Клиент - это сторона, которая инициирует запрос к серверу. Браузер, мобильное приложение, CLI-утилита или другой сервис -могут выступать клиентом. +Client знает или находит endpoint server, устанавливает необходимое соединение и отправляет request либо команду. + +Для открытия HTTPS-страницы браузер упрощенно: + +1. разрешает domain через DNS; +2. устанавливает TCP или QUIC connection; +3. выполняет TLS handshake; +4. отправляет HTTP request; +5. получает response и обрабатывает данные. + +Роли относительны. Browser является client для web server. Backend может быть server для browser и одновременно client для +database, payment API или другого service. -Роли клиента и сервера зависят от конкретного взаимодействия. Одна и та же программа может быть сервером для одного -соединения и клиентом для другого. +Client также отвечает за собственные concerns: timeouts, retries, cancellation, validation response и обработку ошибок. +Безусловные retries опасны для non-idempotent операций, поэтому поведение зависит от protocol и semantics запроса. @@ -459,16 +737,31 @@ localhost - имя, которое указывает на текущий ком **Короткий ответ** -IP-адрес - это адрес устройства или сетевого интерфейса в IP-сети. Он нужен, чтобы пакеты могли быть доставлены от -одного узла к другому. +IP-адрес идентифицирует network interface в IP-сети и используется routing для доставки packets между узлами. **Полный ответ** -IP-адрес - это адрес устройства или сетевого интерфейса в IP-сети. Он нужен, чтобы пакеты могли быть доставлены от -одного узла к другому. +IP address является логическим адресом network layer. Router анализирует destination address и выбирает, куда переслать +packet дальше. + +Основные версии: + +- IPv4, например `192.168.1.10`; +- IPv6, например `2001:db8::10`. + +Адрес может быть public или private, static или dynamic. За NAT несколько устройств private network могут выходить во +внешнюю сеть через один public address. -IP-адрес отвечает на вопрос "к какой машине или сетевому интерфейсу обратиться", а порт отвечает на вопрос "к какой -программе внутри этой машины обратиться". +IP не всегда однозначно идентифицирует физическое устройство или пользователя: + +- у устройства несколько interfaces и addresses; +- address может измениться; +- proxy, VPN и NAT скрывают исходный address; +- один server address может вести к load balancer; +- domain может разрешаться в несколько addresses. + +IP отвечает за доставку между network endpoints, а transport port помогает выбрать приложение внутри host. Domain name +является удобным именем и разрешается в address через DNS. @@ -480,16 +773,30 @@ IP-адрес отвечает на вопрос "к какой машине и **Короткий ответ** -DNS - система, которая сопоставляет доменные имена с сетевыми адресами и другой служебной информацией. Благодаря DNS -пользователь вводит example.com, а не запоминает IP-адрес сервера. +DNS - распределенная система имен, которая сопоставляет domain names с IP-адресами и другими records. **Полный ответ** -DNS - система, которая сопоставляет доменные имена с сетевыми адресами и другой служебной информацией. Благодаря DNS -пользователь вводит `example.com`, а не запоминает IP-адрес сервера. +Когда приложению нужен `example.com`, resolver сначала проверяет доступные caches. Если ответа нет, запрос проходит через +recursive DNS resolver, который при необходимости обращается к hierarchy DNS servers. + +Частые record types: + +- `A` - IPv4 address; +- `AAAA` - IPv6 address; +- `CNAME` - alias; +- `MX` - mail servers; +- `TXT` - текстовые данные и verification; +- `NS` - authoritative name servers. -Перед сетевым соединением клиент обычно должен узнать, в какой IP-адрес разрешается доменное имя. Ответ может прийти из -локального кеша, от провайдера, корпоративного DNS или публичного DNS-сервера. +Ответ имеет TTL, поэтому изменения DNS распространяются не мгновенно: разные resolvers могут хранить старое значение до +истечения cache. + +DNS только помогает найти network data. Он не устанавливает HTTP connection и не выполняет redirect страницы. После +resolution client еще должен соединиться с нужным endpoint и выполнить protocol приложения. + +Для frontend-разработки DNS важен при настройке domains, CDN, certificates, CORS и диагностике ситуации, когда domain уже +изменен, но часть users видит старый address. @@ -501,16 +808,24 @@ DNS - система, которая сопоставляет доменные **Короткий ответ** -Socket - программная абстракция сетевого соединения или сетевого endpoint. Через socket программа отправляет и получает -данные по сети. +Socket - программная абстракция network endpoint. Через socket process отправляет и получает bytes или datagrams. **Полный ответ** -Socket - программная абстракция сетевого соединения или сетевого endpoint. Через socket программа отправляет и получает -данные по сети. +Socket является объектом ОС, через который приложение работает с network stack. Он связан с protocol, local endpoint и, +для установленного соединения, remote endpoint. + +Для TCP listening socket принимает новые connections. После `accept` ОС создает отдельный connected socket, через который +server обменивается ordered byte stream с конкретным client. + +UDP socket отправляет и принимает отдельные datagrams без обязательного постоянного connection. UDP не гарантирует +delivery, order или отсутствие duplicates на уровне protocol. -Для TCP-соединения socket связан с адресом, портом и состоянием соединения. Для UDP socket может отправлять и принимать -отдельные datagrams без постоянного соединения. +Socket API лежит ниже HTTP и WebSocket. **WebSocket** - application protocol с handshake и frames, который обычно работает +поверх TCP socket. Эти понятия нельзя использовать как синонимы. + +Приложение должно учитывать partial reads/writes, buffering, timeouts, closed connections и resource cleanup. То, что +`write` принял данные в local buffer, не всегда означает, что remote application уже их обработало. @@ -522,16 +837,35 @@ Socket - программная абстракция сетевого соеди **Короткий ответ** -npm run dev запускает script из package.json. Обычно этот script стартует dev server фреймворка или сборщика: Angular -CLI, Vite, Astro, Next.js и другие инструменты. +`npm run dev` выполняет script `dev` из `package.json`. Обычно этот script запускает dev server framework или build tool. **Полный ответ** -`npm run dev` запускает script из `package.json`. Обычно этот script стартует dev server фреймворка или сборщика: -Angular CLI, Vite, Astro, Next.js и другие инструменты. +`npm` не имеет встроенного универсального dev server. Он находит запись: + +```json +{ + "scripts": { + "dev": "vite" + } +} +``` + +и запускает указанную command в shell. На время script `npm` добавляет `node_modules/.bin` в `PATH`, поэтому локально +установленный `vite`, `astro` или другой binary доступен без полного пути. + +Dev server является обычным process. Обычно он: -Dev server - это обычный процесс на компьютере разработчика. Он слушает порт, отдает файлы браузеру, пересобирает проект -при изменениях и может поддерживать hot reload или HMR. +- bind-ится к port; +- отдает HTML, JavaScript и assets; +- наблюдает за files; +- запускает incremental rebuild; +- сообщает browser об изменениях через WebSocket; +- выполняет HMR или reload; +- иногда proxy requests к backend. + +Это development tooling, а не production server по умолчанию. Production flow обычно сначала создает optimized build, а +затем размещает static files на CDN/web server или запускает специально настроенный application server. @@ -543,16 +877,22 @@ Dev server - это обычный процесс на компьютере ра **Короткий ответ** -Браузер и dev server - разные процессы. Вкладка браузера является клиентом, а dev server продолжает работать в терминале -или IDE, пока его процесс не завершится. +Вкладка браузера и dev server - разные процессы. Вкладка является client, а server продолжает работать, пока не завершен +его process. **Полный ответ** -Браузер и dev server - разные процессы. Вкладка браузера является клиентом, а dev server продолжает работать в терминале -или IDE, пока его процесс не завершится. +Browser tab только подключается к dev server по HTTP и, возможно, WebSocket. При закрытии вкладки ее connections +закрываются, но server process остается запущенным в terminal, IDE, container или background session. + +Чтобы остановить foreground process в terminal, обычно отправляют `SIGINT` через `Ctrl+C`. IDE может иметь отдельную кнопку +stop. Если process был запущен через `&`, `nohup`, `tmux`, process manager или service manager, закрытие terminal также +может его не завершить. + +Это объясняет частую ошибку `EADDRINUSE`: старый server все еще слушает port, хотя browser window уже закрыто. -Поэтому закрытие вкладки обычно прекращает только клиентское соединение. Чтобы остановить сервер, нужно остановить -процесс dev server, например через `Ctrl+C` в терминале, кнопку stop в IDE или команду управления процессами. +Server может заметить disconnect конкретного client и освободить связанные ресурсы, но отсутствие clients не означает, +что process обязан завершиться. Он продолжает watch files и ждать новые connections. From c7d4197b90f4779204fbdc168bb2f96a56ed7de2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=98=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2?= Date: Tue, 28 Jul 2026 21:05:21 +0300 Subject: [PATCH 07/16] ci: generate formatting preview --- .github/workflows/format-preview.yml | 40 ++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/format-preview.yml diff --git a/.github/workflows/format-preview.yml b/.github/workflows/format-preview.yml new file mode 100644 index 0000000..945d287 --- /dev/null +++ b/.github/workflows/format-preview.yml @@ -0,0 +1,40 @@ +name: Format preview + +on: + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + format: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 24 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Format files + run: npm run format + + - name: Archive formatted files + uses: actions/upload-artifact@v4 + with: + name: formatted-files + path: | + CONTRIBUTING.md + package.json + scripts/audit-answer-depth.mjs + src/pages/computer-science/index.md + src/pages/operating-systems/index.md + if-no-files-found: error From 6085095d86f874781f893988261c3e140a661a2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=98=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2?= Date: Tue, 28 Jul 2026 21:07:14 +0300 Subject: [PATCH 08/16] ci: apply formatting preview --- .github/workflows/format-preview.yml | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/.github/workflows/format-preview.yml b/.github/workflows/format-preview.yml index 945d287..09dae70 100644 --- a/.github/workflows/format-preview.yml +++ b/.github/workflows/format-preview.yml @@ -5,15 +5,17 @@ on: workflow_dispatch: permissions: - contents: read + contents: write jobs: format: runs-on: ubuntu-latest steps: - - name: Checkout + - name: Checkout branch uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref || github.ref_name }} - name: Setup Node.js uses: actions/setup-node@v4 @@ -27,14 +29,16 @@ jobs: - name: Format files run: npm run format - - name: Archive formatted files - uses: actions/upload-artifact@v4 - with: - name: formatted-files - path: | - CONTRIBUTING.md - package.json - scripts/audit-answer-depth.mjs - src/pages/computer-science/index.md + - name: Commit formatting + run: | + if git diff --quiet; then + exit 0 + fi + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add CONTRIBUTING.md package.json scripts/audit-answer-depth.mjs \ + src/pages/computer-science/index.md \ src/pages/operating-systems/index.md - if-no-files-found: error + git commit -m "chore: format expanded answers" + git push origin HEAD:${{ github.head_ref || github.ref_name }} From 933bfc219f891f752d47f72176c9d9f4b475e146 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 28 Jul 2026 18:07:45 +0000 Subject: [PATCH 09/16] chore: format expanded answers --- CONTRIBUTING.md | 6 +- scripts/audit-answer-depth.mjs | 69 +++++++----------- src/pages/computer-science/index.md | 52 +++++++------- src/pages/operating-systems/index.md | 103 ++++++++++++++------------- 4 files changed, 108 insertions(+), 122 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 66c50be..acbec6c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,7 +38,8 @@ 4. ограничения, альтернативы или типичная ошибка; 5. практический вывод. -Не обязательно использовать все пять пунктов. Структура должна соответствовать вопросу, а не превращаться в формальный шаблон. +Не обязательно использовать все пять пунктов. Структура должна соответствовать вопросу, а не превращаться в формальный +шаблон. ## Пример @@ -59,7 +60,8 @@ Web Worker выполняет JavaScript в отдельном потоке. О ```md **Короткий ответ** -Web Worker выполняет JavaScript вне main thread и подходит для тяжелых вычислений, которые иначе блокировали бы интерфейс. +Web Worker выполняет JavaScript вне main thread и подходит для тяжелых вычислений, которые иначе блокировали бы +интерфейс. **Полный ответ** diff --git a/scripts/audit-answer-depth.mjs b/scripts/audit-answer-depth.mjs index 11d7518..33f25fb 100644 --- a/scripts/audit-answer-depth.mjs +++ b/scripts/audit-answer-depth.mjs @@ -35,41 +35,29 @@ function cleanAnswer(value) { } function extractQuestions(markdown) { - return [...markdown.matchAll(/
[\s\S]*?<\/details>/g)].flatMap( - ([block]) => { - const question = block - .match(/([\s\S]*?)<\/summary>/)?.[1] - ?.trim(); - const shortMarker = '**Короткий ответ**'; - const fullMarker = '**Полный ответ**'; - const shortStart = block.indexOf(shortMarker); - const fullStart = block.indexOf(fullMarker); - - if ( - !question || - shortStart === -1 || - fullStart === -1 || - fullStart <= shortStart - ) { - return []; - } + return [...markdown.matchAll(/
[\s\S]*?<\/details>/g)].flatMap(([block]) => { + const question = block.match(/([\s\S]*?)<\/summary>/)?.[1]?.trim(); + const shortMarker = '**Короткий ответ**'; + const fullMarker = '**Полный ответ**'; + const shortStart = block.indexOf(shortMarker); + const fullStart = block.indexOf(fullMarker); + + if (!question || shortStart === -1 || fullStart === -1 || fullStart <= shortStart) { + return []; + } - const shortAnswer = block.slice(shortStart + shortMarker.length, fullStart); - const fullEnd = block.indexOf('', fullStart); - const fullAnswer = block.slice( - fullStart + fullMarker.length, - fullEnd === -1 ? undefined : fullEnd, - ); - - return [ - { - question, - shortAnswer: cleanAnswer(shortAnswer), - fullAnswer: cleanAnswer(fullAnswer), - }, - ]; - }, - ); + const shortAnswer = block.slice(shortStart + shortMarker.length, fullStart); + const fullEnd = block.indexOf('', fullStart); + const fullAnswer = block.slice(fullStart + fullMarker.length, fullEnd === -1 ? undefined : fullEnd); + + return [ + { + question, + shortAnswer: cleanAnswer(shortAnswer), + fullAnswer: cleanAnswer(fullAnswer), + }, + ]; + }); } function diagnose({shortAnswer, fullAnswer}) { @@ -121,25 +109,18 @@ for (const file of files) { } if (findings.length === 0) { - console.log( - `Checked ${questionCount} questions in ${files.length} files: ` + - 'no shallow full answers found.', - ); + console.log(`Checked ${questionCount} questions in ${files.length} files: ` + 'no shallow full answers found.'); process.exit(0); } -console.log( - `Found ${findings.length} shallow full answers in ${questionCount} questions:\n`, -); +console.log(`Found ${findings.length} shallow full answers in ${questionCount} questions:\n`); for (const finding of findings) { console.log(`- ${finding.file}: ${finding.question}`); console.log(` ${finding.reason}`); } -console.log( - '\nA full answer should add reasoning, trade-offs, edge cases or a practical example.', -); +console.log('\nA full answer should add reasoning, trade-offs, edge cases or a practical example.'); if (STRICT) { process.exitCode = 1; diff --git a/src/pages/computer-science/index.md b/src/pages/computer-science/index.md index e7d8a39..b5ffc81 100644 --- a/src/pages/computer-science/index.md +++ b/src/pages/computer-science/index.md @@ -21,9 +21,9 @@ order: 10 **Полный ответ** -Архитектура фон Неймана описывает компьютер как систему, в которой **инструкции программы и обрабатываемые данные лежат в -одной адресуемой памяти**. Процессор получает из нее очередную инструкцию, декодирует ее, выполняет и переходит к следующей, -если сама инструкция не изменила поток управления. +Архитектура фон Неймана описывает компьютер как систему, в которой **инструкции программы и обрабатываемые данные лежат +в одной адресуемой памяти**. Процессор получает из нее очередную инструкцию, декодирует ее, выполняет и переходит к +следующей, если сама инструкция не изменила поток управления. Этот процесс обычно объясняют циклом **fetch — decode — execute**: @@ -44,8 +44,8 @@ order: 10 кеша, предварительную выборку и параллельное выполнение. Внутри процессора кеш инструкций и кеш данных могут быть разделены, но на уровне программной модели компьютер все равно часто остается фоннеймановским. -Например, при выполнении цикла над большим массивом производительность зависит не только от числа арифметических операций, -но и от того, насколько последовательно программа читает память и попадают ли данные в кеш. +Например, при выполнении цикла над большим массивом производительность зависит не только от числа арифметических +операций, но и от того, насколько последовательно программа читает память и попадают ли данные в кеш. ![img.png](assets/von-neuman-architecture.png) @@ -85,12 +85,12 @@ CPU объединяет управление и вычисления, RAM хр - **декодеры, планировщики и reorder buffer** — внутреннее управление выполнением инструкций; - **контроллеры памяти и внутренние шины** — обмен между ядрами, кешами, RAM и периферией. -Например, выражение `total += prices[i]` требует не только сложения в ALU: нужно вычислить адрес элемента, получить данные -из регистра, кеша или RAM, выполнить операцию и сохранить результат. Поэтому скорость программы определяется всей +Например, выражение `total += prices[i]` требует не только сложения в ALU: нужно вычислить адрес элемента, получить +данные из регистра, кеша или RAM, выполнить операцию и сохранить результат. Поэтому скорость программы определяется всей иерархией, а не только частотой ALU. -На интервью полезно сначала назвать учебную модель, а затем уточнить, что реальный CPU значительно сложнее. Это показывает, -что кандидат понимает границу между полезной абстракцией и физической реализацией. +На интервью полезно сначала назвать учебную модель, а затем уточнить, что реальный CPU значительно сложнее. Это +показывает, что кандидат понимает границу между полезной абстракцией и физической реализацией. @@ -103,13 +103,13 @@ CPU объединяет управление и вычисления, RAM хр **Короткий ответ** Frontend-разработчику эта модель помогает понимать CPU-bound задачи, main thread и стоимость доступа к памяти. -JavaScript-вычисления занимают CPU, объекты расходуют RAM, а сеть и диск работают существенно медленнее регистров и кешей. -Это объясняет long tasks, лаги интерфейса, memory leaks и пользу Web Workers. +JavaScript-вычисления занимают CPU, объекты расходуют RAM, а сеть и диск работают существенно медленнее регистров и +кешей. Это объясняет long tasks, лаги интерфейса, memory leaks и пользу Web Workers. **Полный ответ** -Браузер скрывает детали железа, но производительность интерфейса все равно ограничена процессором, памятью и вводом/выводом. -Базовая модель помогает связать симптомы в DevTools с реальными причинами. +Браузер скрывает детали железа, но производительность интерфейса все равно ограничена процессором, памятью и +вводом/выводом. Базовая модель помогает связать симптомы в DevTools с реальными причинами. **CPU-bound работа** занимает вычислительные ресурсы: @@ -173,8 +173,8 @@ CPU выполняет машинные инструкции и вычислен `регистры -> кеш CPU -> RAM -> SSD/HDD -> сеть` -Чем ближе уровень к CPU, тем обычно меньше объем и ниже задержка. Поэтому последовательный доступ к уже загруженным данным -обычно дешевле случайного чтения большого числа мелких файлов или сетевых ресурсов. +Чем ближе уровень к CPU, тем обычно меньше объем и ниже задержка. Поэтому последовательный доступ к уже загруженным +данным обычно дешевле случайного чтения большого числа мелких файлов или сетевых ресурсов. Frontend-пример: @@ -205,17 +205,17 @@ RAM быстрее и используется как рабочая памят RAM и диск различаются назначением, скоростью и временем жизни данных. -| Свойство | RAM | SSD/HDD | -| --- | --- | --- | -| Назначение | Активные данные процессов | Долговременные файлы | -| Сохранение без питания | Нет | Да | -| Задержка доступа | Ниже | Выше | -| Типичный объем | Меньше | Больше | -| Доступ CPU | Через память и кеши | После операции ввода/вывода | +| Свойство | RAM | SSD/HDD | +| ---------------------- | ------------------------- | --------------------------- | +| Назначение | Активные данные процессов | Долговременные файлы | +| Сохранение без питания | Нет | Да | +| Задержка доступа | Ниже | Выше | +| Типичный объем | Меньше | Больше | +| Доступ CPU | Через память и кеши | После операции ввода/вывода | Когда программа открывает файл, операционная система обычно читает нужные блоки с диска в RAM. После этого процессор -работает с данными в памяти. ОС также использует page cache, поэтому повторное чтение файла иногда обслуживается из RAM и -происходит быстрее. +работает с данными в памяти. ОС также использует page cache, поэтому повторное чтение файла иногда обслуживается из RAM +и происходит быстрее. Если физической RAM недостаточно, операционная система может выгружать неактивные страницы в swap или page file на диск. Это помогает продолжить работу, но из-за более высокой задержки storage система может начать заметно тормозить. Сам по @@ -223,8 +223,8 @@ RAM и диск различаются назначением, скорость а paging управляет операционная система. Эти механизмы связаны давлением на память, но не являются одним и тем же. Frontend-пример: загруженное изображение может занимать на диске 2 MB в сжатом JPEG, но после декодирования в RGBA-буфер -размером 4000 x 3000 пикселей потребовать около 48 MB RAM. Поэтому размер сетевого файла и фактическое потребление памяти -интерфейсом могут сильно различаться. +размером 4000 x 3000 пикселей потребовать около 48 MB RAM. Поэтому размер сетевого файла и фактическое потребление +памяти интерфейсом могут сильно различаться. ![img.png](assets/ram-vs-hard.png) diff --git a/src/pages/operating-systems/index.md b/src/pages/operating-systems/index.md index cc57f87..d6b2cb0 100644 --- a/src/pages/operating-systems/index.md +++ b/src/pages/operating-systems/index.md @@ -139,8 +139,8 @@ order: 11 утилиты. Например, Linux технически является ядром, а привычная Linux-система дополнена user-space инструментами и дистрибутивом. -Для frontend-разработчика ОС проявляется через файловые watchers, лимиты открытых файлов, процессы dev server, переменные -окружения, сертификаты, DNS, sockets и поведение браузера. +Для frontend-разработчика ОС проявляется через файловые watchers, лимиты открытых файлов, процессы dev server, +переменные окружения, сертификаты, DNS, sockets и поведение браузера. @@ -157,7 +157,8 @@ order: 11 **Полный ответ** -Kernel работает с повышенными привилегиями и контролирует ресурсы, которым нельзя безопасно доверить обычным приложениям. +Kernel работает с повышенными привилегиями и контролирует ресурсы, которым нельзя безопасно доверить обычным +приложениям. К основным задачам ядра относятся: @@ -171,8 +172,8 @@ Kernel работает с повышенными привилегиями и к - проверка прав доступа и системных ограничений. Пользовательская программа не читает диск и не управляет сетевой картой напрямую. Она вызывает API runtime или системной -библиотеки, а те переходят к system call, например `open`, `read`, `write`, `mmap` или `socket`. Kernel проверяет запрос, -выполняет привилегированную операцию и возвращает результат. +библиотеки, а те переходят к system call, например `open`, `read`, `write`, `mmap` или `socket`. Kernel проверяет +запрос, выполняет привилегированную операцию и возвращает результат. Ошибка в kernel или driver опаснее ошибки обычного приложения, потому что kernel имеет доступ ко всей системе. Поэтому минимизация кода с высокими привилегиями и четкая граница user/kernel space важны для безопасности и устойчивости. @@ -205,8 +206,8 @@ Kernel space и user space разделены уровнями привилег 3. Операция выполняется или отклоняется. 4. Управление возвращается в user space. -Это разделение ограничивает последствия ошибок. Если вкладка браузера падает, обычно завершается ее процесс, а не вся ОС. -Если же критическая ошибка происходит в kernel driver, возможен crash всей системы. +Это разделение ограничивает последствия ошибок. Если вкладка браузера падает, обычно завершается ее процесс, а не вся +ОС. Если же критическая ошибка происходит в kernel driver, возможен crash всей системы. Переход между режимами не бесплатен, но обычно проблема производительности не в одном system call, а в огромном числе мелких операций. Поэтому I/O часто буферизуют и объединяют в batches. @@ -236,8 +237,8 @@ Thread - минимальная единица выполнения, котор При этом потоки одного процесса обычно разделяют code, heap, открытые файлы и sockets. Благодаря общей памяти обмен данными дешевле, чем между процессами, но требуется синхронизация. -Например, browser process может использовать отдельные потоки для UI, I/O и служебной работы, а renderer process - -main thread и дополнительные worker threads. JavaScript страницы обычно выполняется на main thread, но Web Worker дает +Например, browser process может использовать отдельные потоки для UI, I/O и служебной работы, а renderer process - main +thread и дополнительные worker threads. JavaScript страницы обычно выполняется на main thread, но Web Worker дает отдельный execution context, который может работать параллельно на другом ядре. Если два потока изменяют общие данные без координации, появляется data race. Для защиты используют mutexes, atomics, @@ -264,19 +265,19 @@ message passing или проектируют систему так, чтобы Практические trade-offs: -| Свойство | Процессы | Потоки | -| --- | --- | --- | -| Изоляция | Выше | Ниже | -| Обмен данными | Через IPC | Через общую память | -| Стоимость создания и переключения | Обычно выше | Обычно ниже | -| Последствие crash | Чаще локализовано | Может повредить весь процесс | -| Риск data races | Ниже между процессами | Выше при shared state | +| Свойство | Процессы | Потоки | +| --------------------------------- | --------------------- | ---------------------------- | +| Изоляция | Выше | Ниже | +| Обмен данными | Через IPC | Через общую память | +| Стоимость создания и переключения | Обычно выше | Обычно ниже | +| Последствие crash | Чаще локализовано | Может повредить весь процесс | +| Риск data races | Ниже между процессами | Выше при shared state | Браузеры используют multi-process архитектуру, чтобы изолировать вкладки, сайты и привилегированные компоненты. Внутри каждого процесса при этом могут работать несколько потоков. -Выбор зависит не только от скорости. Для недоверенного кода или сильной изоляции процесс может быть правильнее, даже если -он тяжелее. Для тесно связанной вычислительной работы могут подойти threads или workers. +Выбор зависит не только от скорости. Для недоверенного кода или сильной изоляции процесс может быть правильнее, даже +если он тяжелее. Для тесно связанной вычислительной работы могут подойти threads или workers. @@ -294,8 +295,8 @@ message passing или проектируют систему так, чтобы **Полный ответ** -**Concurrency** описывает организацию нескольких незавершенных задач. Система переключается между ними или продолжает одну, -пока другая ждет I/O. +**Concurrency** описывает организацию нескольких незавершенных задач. Система переключается между ними или продолжает +одну, пока другая ждет I/O. **Parallelism** означает физическое одновременное выполнение как минимум двух операций, например на разных CPU cores. @@ -346,8 +347,8 @@ Frontend-примеры: тяжелая сортировка, декодиров Например, десять HTTP-запросов можно выполнять конкурентно, если backend допускает это. Но запуск десяти тяжелых вычислений на одном main thread не сделает их параллельными и может ухудшить responsiveness. -Сначала нужно измерить, где тратится время. Медленный пользовательский сценарий часто смешивает оба типа: сеть доставляет -большой JSON, после чего main thread долго его парсит и рендерит. +Сначала нужно измерить, где тратится время. Медленный пользовательский сценарий часто смешивает оба типа: сеть +доставляет большой JSON, после чего main thread долго его парсит и рендерит. @@ -430,8 +431,8 @@ terminal передает строку shell. Shell создает процес **Короткий ответ** -`stdin` - стандартный ввод процесса, `stdout` - обычный результат, `stderr` - ошибки и диагностика. Shell может соединять -и перенаправлять эти потоки. +`stdin` - стандартный ввод процесса, `stdout` - обычный результат, `stderr` - ошибки и диагностика. Shell может +соединять и перенаправлять эти потоки. **Полный ответ** @@ -451,8 +452,8 @@ node build.mjs > all.txt 2>&1 cat app.log | grep ERROR ``` -Разделение `stdout` и `stderr` важно для автоматизации. CLI может писать machine-readable результат в `stdout`, а progress -и диагностику - в `stderr`, не повреждая данные pipeline. +Разделение `stdout` и `stderr` важно для автоматизации. CLI может писать machine-readable результат в `stdout`, а +progress и диагностику - в `stderr`, не повреждая данные pipeline. Stream не обязательно является файлом на диске. Он может быть terminal, pipe, socket или специальное устройство. Запись может буферизоваться, поэтому момент вызова logging API и момент появления текста не всегда совпадают. @@ -492,8 +493,8 @@ API_URL=https://api.example.com NODE_ENV=production node server.mjs - переменные frontend build часто подставляются в bundle во время сборки и становятся видны пользователю; - runtime-конфигурация браузерного приложения требует отдельного механизма, например config endpoint или файла. -Хорошая практика - преобразовать сырые variables в типизированный config один раз при старте и завершить процесс с понятной -ошибкой, если обязательное значение отсутствует. +Хорошая практика - преобразовать сырые variables в типизированный config один раз при старте и завершить процесс с +понятной ошибкой, если обязательное значение отсутствует. @@ -510,8 +511,8 @@ Exit code - числовой код завершения процесса. Об **Полный ответ** -Когда процесс завершается, ОС сохраняет его status, который может прочитать родительский процесс или shell. По соглашению -`0` означает успешное выполнение, а ненулевые значения обозначают разные причины неуспеха. +Когда процесс завершается, ОС сохраняет его status, который может прочитать родительский процесс или shell. По +соглашению `0` означает успешное выполнение, а ненулевые значения обозначают разные причины неуспеха. Shell использует status для управления командами: @@ -545,8 +546,9 @@ CLI полезно выбирать стабильные codes и писать **Полный ответ** -TCP и UDP используют номера портов от `0` до `65535`, чтобы несколько сетевых программ могли работать на одном IP-адресе. -TCP port и UDP port являются разными пространствами: процесс может использовать один номер для TCP, а другой - для UDP. +TCP и UDP используют номера портов от `0` до `65535`, чтобы несколько сетевых программ могли работать на одном +IP-адресе. TCP port и UDP port являются разными пространствами: процесс может использовать один номер для TCP, а +другой - для UDP. Server обычно bind-ится к известному порту, например `443`. Client при исходящем соединении получает временный ephemeral port. TCP-соединение различается набором: @@ -558,8 +560,8 @@ port. TCP-соединение различается набором: В URL порт указывают после двоеточия: `http://localhost:3000`. Если порт не указан, схема задает default, например `80` для HTTP и `443` для HTTPS. -Порт не является физическим разьемом и не гарантирует конкретный protocol приложения. На `3000` может работать HTTP server, -но это определяется программой, а не самим числом. +Порт не является физическим разьемом и не гарантирует конкретный protocol приложения. На `3000` может работать HTTP +server, но это определяется программой, а не самим числом. @@ -706,8 +708,8 @@ Server не обязан быть удаленным и не обязан раб **Короткий ответ** -Клиент - сторона, которая инициирует взаимодействие с server. Им может быть браузер, мобильное приложение, CLI или другой -service. +Клиент - сторона, которая инициирует взаимодействие с server. Им может быть браузер, мобильное приложение, CLI или +другой service. **Полный ответ** @@ -721,8 +723,8 @@ Client знает или находит endpoint server, устанавлива 4. отправляет HTTP request; 5. получает response и обрабатывает данные. -Роли относительны. Browser является client для web server. Backend может быть server для browser и одновременно client для -database, payment API или другого service. +Роли относительны. Browser является client для web server. Backend может быть server для browser и одновременно client +для database, payment API или другого service. Client также отвечает за собственные concerns: timeouts, retries, cancellation, validation response и обработку ошибок. Безусловные retries опасны для non-idempotent операций, поэтому поведение зависит от protocol и semantics запроса. @@ -777,8 +779,8 @@ DNS - распределенная система имен, которая со **Полный ответ** -Когда приложению нужен `example.com`, resolver сначала проверяет доступные caches. Если ответа нет, запрос проходит через -recursive DNS resolver, который при необходимости обращается к hierarchy DNS servers. +Когда приложению нужен `example.com`, resolver сначала проверяет доступные caches. Если ответа нет, запрос проходит +через recursive DNS resolver, который при необходимости обращается к hierarchy DNS servers. Частые record types: @@ -795,8 +797,8 @@ recursive DNS resolver, который при необходимости обр DNS только помогает найти network data. Он не устанавливает HTTP connection и не выполняет redirect страницы. После resolution client еще должен соединиться с нужным endpoint и выполнить protocol приложения. -Для frontend-разработки DNS важен при настройке domains, CDN, certificates, CORS и диагностике ситуации, когда domain уже -изменен, но часть users видит старый address. +Для frontend-разработки DNS важен при настройке domains, CDN, certificates, CORS и диагностике ситуации, когда domain +уже изменен, но часть users видит старый address. @@ -815,14 +817,14 @@ Socket - программная абстракция network endpoint. Чере Socket является объектом ОС, через который приложение работает с network stack. Он связан с protocol, local endpoint и, для установленного соединения, remote endpoint. -Для TCP listening socket принимает новые connections. После `accept` ОС создает отдельный connected socket, через который -server обменивается ordered byte stream с конкретным client. +Для TCP listening socket принимает новые connections. После `accept` ОС создает отдельный connected socket, через +который server обменивается ordered byte stream с конкретным client. UDP socket отправляет и принимает отдельные datagrams без обязательного постоянного connection. UDP не гарантирует delivery, order или отсутствие duplicates на уровне protocol. -Socket API лежит ниже HTTP и WebSocket. **WebSocket** - application protocol с handshake и frames, который обычно работает -поверх TCP socket. Эти понятия нельзя использовать как синонимы. +Socket API лежит ниже HTTP и WebSocket. **WebSocket** - application protocol с handshake и frames, который обычно +работает поверх TCP socket. Эти понятия нельзя использовать как синонимы. Приложение должно учитывать partial reads/writes, buffering, timeouts, closed connections и resource cleanup. То, что `write` принял данные в local buffer, не всегда означает, что remote application уже их обработало. @@ -837,7 +839,8 @@ Socket API лежит ниже HTTP и WebSocket. **WebSocket** - application pr **Короткий ответ** -`npm run dev` выполняет script `dev` из `package.json`. Обычно этот script запускает dev server framework или build tool. +`npm run dev` выполняет script `dev` из `package.json`. Обычно этот script запускает dev server framework или build +tool. **Полный ответ** @@ -885,9 +888,9 @@ Dev server является обычным process. Обычно он: Browser tab только подключается к dev server по HTTP и, возможно, WebSocket. При закрытии вкладки ее connections закрываются, но server process остается запущенным в terminal, IDE, container или background session. -Чтобы остановить foreground process в terminal, обычно отправляют `SIGINT` через `Ctrl+C`. IDE может иметь отдельную кнопку -stop. Если process был запущен через `&`, `nohup`, `tmux`, process manager или service manager, закрытие terminal также -может его не завершить. +Чтобы остановить foreground process в terminal, обычно отправляют `SIGINT` через `Ctrl+C`. IDE может иметь отдельную +кнопку stop. Если process был запущен через `&`, `nohup`, `tmux`, process manager или service manager, закрытие terminal +также может его не завершить. Это объясняет частую ошибку `EADDRINUSE`: старый server все еще слушает port, хотя browser window уже закрыто. From c2f447ad5a2e675f59b8a96bf8c7dfc8ba7e4a95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=98=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2?= Date: Tue, 28 Jul 2026 21:08:17 +0300 Subject: [PATCH 10/16] ci: polish and format expanded answers --- .github/workflows/format-preview.yml | 32 ++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/.github/workflows/format-preview.yml b/.github/workflows/format-preview.yml index 09dae70..ad1740d 100644 --- a/.github/workflows/format-preview.yml +++ b/.github/workflows/format-preview.yml @@ -26,10 +26,38 @@ jobs: - name: Install dependencies run: npm ci + - name: Polish wording + run: | + python <<'PY' + from pathlib import Path + + path = Path('src/pages/operating-systems/index.md') + content = path.read_text() + replacements = { + 'JavaScript страницы обычно выполняется': 'JavaScript-код страницы обычно выполняется', + 'три стандартных stream': 'три стандартных потока', + 'чтобы runtime корректно завершил pending synchronous cleanup, вместо\nнемедленного': + 'чтобы event loop завершился естественно и buffered output успел записаться, вместо\nнемедленного', + 'физическим разьемом': 'физическим разъемом', + 'Options вроде': 'Опции вроде', + 'обращается к hierarchy DNS servers.': 'обращается к иерархии DNS-серверов.', + 'DNS только помогает найти network data.': + 'DNS только помогает найти сетевой адрес и другие records.', + 'хотя browser window уже закрыто.': 'хотя вкладка браузера уже закрыта.', + } + + for source, target in replacements.items(): + if source not in content: + raise RuntimeError(f'Missing text: {source}') + content = content.replace(source, target) + + path.write_text(content) + PY + - name: Format files run: npm run format - - name: Commit formatting + - name: Commit changes run: | if git diff --quiet; then exit 0 @@ -40,5 +68,5 @@ jobs: git add CONTRIBUTING.md package.json scripts/audit-answer-depth.mjs \ src/pages/computer-science/index.md \ src/pages/operating-systems/index.md - git commit -m "chore: format expanded answers" + git commit -m "docs: polish expanded answers" git push origin HEAD:${{ github.head_ref || github.ref_name }} From 5eccddb40307299261527de90caa5e5c70be7a6d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 28 Jul 2026 18:08:55 +0000 Subject: [PATCH 11/16] docs: polish expanded answers --- src/pages/operating-systems/index.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/pages/operating-systems/index.md b/src/pages/operating-systems/index.md index d6b2cb0..fa5ed2b 100644 --- a/src/pages/operating-systems/index.md +++ b/src/pages/operating-systems/index.md @@ -238,7 +238,7 @@ Thread - минимальная единица выполнения, котор данными дешевле, чем между процессами, но требуется синхронизация. Например, browser process может использовать отдельные потоки для UI, I/O и служебной работы, а renderer process - main -thread и дополнительные worker threads. JavaScript страницы обычно выполняется на main thread, но Web Worker дает +thread и дополнительные worker threads. JavaScript-код страницы обычно выполняется на main thread, но Web Worker дает отдельный execution context, который может работать параллельно на другом ядре. Если два потока изменяют общие данные без координации, появляется data race. Для защиты используют mutexes, atomics, @@ -436,7 +436,7 @@ terminal передает строку shell. Shell создает процес **Полный ответ** -При запуске консольный процесс обычно получает три стандартных stream: +При запуске консольный процесс обычно получает три стандартных потока: - `stdin` с file descriptor `0`; - `stdout` с file descriptor `1`; @@ -528,8 +528,8 @@ CLI полезно выбирать стабильные codes и писать может кодироваться особым образом и отличаться между shell и ОС, поэтому не стоит считать любое конкретное ненулевое число универсальным без документации инструмента. -В Node.js можно задать `process.exitCode`, чтобы runtime корректно завершил pending synchronous cleanup, вместо -немедленного `process.exit()`. +В Node.js можно задать `process.exitCode`, чтобы event loop завершился естественно и buffered output успел записаться, +вместо немедленного `process.exit()`. @@ -560,7 +560,7 @@ port. TCP-соединение различается набором: В URL порт указывают после двоеточия: `http://localhost:3000`. Если порт не указан, схема задает default, например `80` для HTTP и `443` для HTTPS. -Порт не является физическим разьемом и не гарантирует конкретный protocol приложения. На `3000` может работать HTTP +Порт не является физическим разъемом и не гарантирует конкретный protocol приложения. На `3000` может работать HTTP server, но это определяется программой, а не самим числом. @@ -632,7 +632,7 @@ lsof -i :3000 После определения процесса можно корректно остановить его или выбрать другой порт. Не следует без проверки завершать случайные системные процессы. -Options вроде `SO_REUSEADDR` влияют на повторное использование адреса, но не означают, что любые два servers безопасно +Опции вроде `SO_REUSEADDR` влияют на повторное использование адреса, но не означают, что любые два servers безопасно разделят один endpoint. Семантика зависит от ОС и socket options. @@ -780,7 +780,7 @@ DNS - распределенная система имен, которая со **Полный ответ** Когда приложению нужен `example.com`, resolver сначала проверяет доступные caches. Если ответа нет, запрос проходит -через recursive DNS resolver, который при необходимости обращается к hierarchy DNS servers. +через recursive DNS resolver, который при необходимости обращается к иерархии DNS-серверов. Частые record types: @@ -794,8 +794,8 @@ DNS - распределенная система имен, которая со Ответ имеет TTL, поэтому изменения DNS распространяются не мгновенно: разные resolvers могут хранить старое значение до истечения cache. -DNS только помогает найти network data. Он не устанавливает HTTP connection и не выполняет redirect страницы. После -resolution client еще должен соединиться с нужным endpoint и выполнить protocol приложения. +DNS только помогает найти сетевой адрес и другие records. Он не устанавливает HTTP connection и не выполняет redirect +страницы. После resolution client еще должен соединиться с нужным endpoint и выполнить protocol приложения. Для frontend-разработки DNS важен при настройке domains, CDN, certificates, CORS и диагностике ситуации, когда domain уже изменен, но часть users видит старый address. @@ -892,7 +892,7 @@ Browser tab только подключается к dev server по HTTP и, в кнопку stop. Если process был запущен через `&`, `nohup`, `tmux`, process manager или service manager, закрытие terminal также может его не завершить. -Это объясняет частую ошибку `EADDRINUSE`: старый server все еще слушает port, хотя browser window уже закрыто. +Это объясняет частую ошибку `EADDRINUSE`: старый server все еще слушает port, хотя вкладка браузера уже закрыта. Server может заметить disconnect конкретного client и освободить связанные ресурсы, но отсутствие clients не означает, что process обязан завершиться. Он продолжает watch files и ждать новые connections. From ec5e173709382d9ef983629776c588bd936d6398 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=98=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2?= Date: Tue, 28 Jul 2026 21:09:23 +0300 Subject: [PATCH 12/16] ci: capture answer depth audit --- .github/workflows/format-preview.yml | 58 +++++++--------------------- 1 file changed, 13 insertions(+), 45 deletions(-) diff --git a/.github/workflows/format-preview.yml b/.github/workflows/format-preview.yml index ad1740d..06cd615 100644 --- a/.github/workflows/format-preview.yml +++ b/.github/workflows/format-preview.yml @@ -1,14 +1,14 @@ -name: Format preview +name: Answer audit preview on: pull_request: workflow_dispatch: permissions: - contents: write + contents: read jobs: - format: + audit: runs-on: ubuntu-latest steps: @@ -26,47 +26,15 @@ jobs: - name: Install dependencies run: npm ci - - name: Polish wording - run: | - python <<'PY' - from pathlib import Path + - name: Check formatting + run: npm run format:check - path = Path('src/pages/operating-systems/index.md') - content = path.read_text() - replacements = { - 'JavaScript страницы обычно выполняется': 'JavaScript-код страницы обычно выполняется', - 'три стандартных stream': 'три стандартных потока', - 'чтобы runtime корректно завершил pending synchronous cleanup, вместо\nнемедленного': - 'чтобы event loop завершился естественно и buffered output успел записаться, вместо\nнемедленного', - 'физическим разьемом': 'физическим разъемом', - 'Options вроде': 'Опции вроде', - 'обращается к hierarchy DNS servers.': 'обращается к иерархии DNS-серверов.', - 'DNS только помогает найти network data.': - 'DNS только помогает найти сетевой адрес и другие records.', - 'хотя browser window уже закрыто.': 'хотя вкладка браузера уже закрыта.', - } + - name: Audit answer depth + run: npm run audit:answers | tee answer-audit.txt - for source, target in replacements.items(): - if source not in content: - raise RuntimeError(f'Missing text: {source}') - content = content.replace(source, target) - - path.write_text(content) - PY - - - name: Format files - run: npm run format - - - name: Commit changes - run: | - if git diff --quiet; then - exit 0 - fi - - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add CONTRIBUTING.md package.json scripts/audit-answer-depth.mjs \ - src/pages/computer-science/index.md \ - src/pages/operating-systems/index.md - git commit -m "docs: polish expanded answers" - git push origin HEAD:${{ github.head_ref || github.ref_name }} + - name: Archive audit + uses: actions/upload-artifact@v4 + with: + name: answer-audit + path: answer-audit.txt + if-no-files-found: error From 27db3a4eed648d1f579ae8494455c60fa1b4b440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=98=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2?= Date: Tue, 28 Jul 2026 21:11:24 +0300 Subject: [PATCH 13/16] ci: remove temporary audit workflow --- .github/workflows/format-preview.yml | 40 ---------------------------- 1 file changed, 40 deletions(-) delete mode 100644 .github/workflows/format-preview.yml diff --git a/.github/workflows/format-preview.yml b/.github/workflows/format-preview.yml deleted file mode 100644 index 06cd615..0000000 --- a/.github/workflows/format-preview.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Answer audit preview - -on: - pull_request: - workflow_dispatch: - -permissions: - contents: read - -jobs: - audit: - runs-on: ubuntu-latest - - steps: - - name: Checkout branch - uses: actions/checkout@v4 - with: - ref: ${{ github.head_ref || github.ref_name }} - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 24 - cache: npm - - - name: Install dependencies - run: npm ci - - - name: Check formatting - run: npm run format:check - - - name: Audit answer depth - run: npm run audit:answers | tee answer-audit.txt - - - name: Archive audit - uses: actions/upload-artifact@v4 - with: - name: answer-audit - path: answer-audit.txt - if-no-files-found: error From b601a1d693d2b8b6a44898f2e344ae0f95fa3bf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=98=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2?= Date: Tue, 28 Jul 2026 21:19:11 +0300 Subject: [PATCH 14/16] chore: keep answer changes limited to markdown --- package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/package.json b/package.json index 31308d2..f7b9c35 100644 --- a/package.json +++ b/package.json @@ -24,8 +24,6 @@ "preview": "astro preview", "debug": "npm run build && astro preview --host 127.0.0.1 --port 4321", "astro": "astro", - "audit:answers": "node scripts/audit-answer-depth.mjs", - "audit:answers:strict": "node scripts/audit-answer-depth.mjs --strict", "format": "prettier \"!package-lock.json\" . --ignore-path .gitignore --write", "format:check": "prettier \"!package-lock.json\" . --ignore-path .gitignore --check" }, From 214e63c5b11980490bc1f03c90253d025c48f9b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=98=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2?= Date: Tue, 28 Jul 2026 21:19:18 +0300 Subject: [PATCH 15/16] chore: remove unrelated contributor guide --- CONTRIBUTING.md | 100 ------------------------------------------------ 1 file changed, 100 deletions(-) delete mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index acbec6c..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,100 +0,0 @@ -# Как добавлять и редактировать вопросы - -## Короткий и полный ответы - -Каждый вопрос должен быть полезен в двух режимах: - -- **Короткий ответ** помогает быстро вспомнить основную мысль и ответить на первый вопрос интервьюера. -- **Полный ответ** помогает объяснить тему глубже, привести пример и продолжить разговор после уточняющих вопросов. - -Полный ответ не должен быть копией короткого ответа с одной дополнительной фразой. - -### Короткий ответ - -Обычно достаточно 1–3 предложений. Ответ должен: - -- сразу отвечать на вопрос; -- называть ключевые термины; -- не уходить в историю и второстепенные детали; -- быть понятным без чтения полного ответа. - -### Полный ответ - -Полный ответ должен раскрывать как минимум два дополнительных аспекта: - -- как механизм работает; -- зачем он нужен; -- практический пример; -- ограничения и trade-offs; -- распространенная ошибка или edge case; -- связь с frontend, backend, архитектурой или эксплуатацией; -- что важно сказать на интервью. - -Удобная структура: - -1. определение своими словами; -2. механизм или последовательность работы; -3. пример; -4. ограничения, альтернативы или типичная ошибка; -5. практический вывод. - -Не обязательно использовать все пять пунктов. Структура должна соответствовать вопросу, а не превращаться в формальный -шаблон. - -## Пример - -Плохо: - -```md -**Короткий ответ** - -Web Worker выполняет JavaScript в отдельном потоке. - -**Полный ответ** - -Web Worker выполняет JavaScript в отдельном потоке. Он помогает не блокировать main thread. -``` - -Лучше: - -```md -**Короткий ответ** - -Web Worker выполняет JavaScript вне main thread и подходит для тяжелых вычислений, которые иначе блокировали бы -интерфейс. - -**Полный ответ** - -Worker работает в отдельном execution context и обменивается данными с main thread через сообщения. У него нет прямого -доступа к DOM, поэтому UI обновляет основной поток. - -Например, в Worker можно вынести парсинг большого файла или расчет сложной агрегации. При этом нужно учитывать стоимость -создания Worker и сериализации данных. Для короткой операции перенос может оказаться дороже выполнения на main thread. -``` - -## Проверка глубины ответов - -Запустите аудит: - -```bash -npm run audit:answers -``` - -Команда находит ответы, которые полностью совпадают или слишком мало отличаются по объему. Это эвристика, а не замена -редакторской проверки: длинный ответ тоже может быть неточным или бесполезным. - -Для строгого режима с ненулевым exit code: - -```bash -npm run audit:answers:strict -``` - -## Checklist перед PR - -- Короткий ответ можно произнести примерно за 20–40 секунд. -- Полный ответ добавляет объяснение, а не только повторяет формулировку. -- Есть конкретный пример, когда он помогает понять тему. -- Указаны ограничения или trade-offs, если они существенны. -- Термины используются последовательно. -- Код и команды можно скопировать и выполнить в указанном контексте. -- Ответ не привязан к устаревшей версии технологии без явной пометки. From 139bfa2ecf1f60bf254533b149ebc85d6061ef60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=98=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2?= Date: Tue, 28 Jul 2026 21:19:24 +0300 Subject: [PATCH 16/16] chore: remove unrelated answer audit --- scripts/audit-answer-depth.mjs | 127 --------------------------------- 1 file changed, 127 deletions(-) delete mode 100644 scripts/audit-answer-depth.mjs diff --git a/scripts/audit-answer-depth.mjs b/scripts/audit-answer-depth.mjs deleted file mode 100644 index 33f25fb..0000000 --- a/scripts/audit-answer-depth.mjs +++ /dev/null @@ -1,127 +0,0 @@ -import {readdir, readFile} from 'node:fs/promises'; -import {relative, resolve} from 'node:path'; -import process from 'node:process'; - -const ROOT = resolve(process.cwd(), 'src/pages'); -const STRICT = process.argv.includes('--strict'); -const MIN_ADDED_CHARACTERS = 180; -const MIN_LENGTH_RATIO = 1.35; - -async function collectMarkdownFiles(directory) { - const entries = await readdir(directory, {withFileTypes: true}); - const nestedFiles = await Promise.all( - entries.map(async (entry) => { - const path = resolve(directory, entry.name); - - if (entry.isDirectory()) { - return collectMarkdownFiles(path); - } - - return entry.isFile() && entry.name.endsWith('.md') ? [path] : []; - }), - ); - - return nestedFiles.flat(); -} - -function cleanAnswer(value) { - return value - .replace(/!\[[^\]]*]\([^)]*\)/g, ' ') - .replace(/<[^>]+>/g, ' ') - .replace(/[`*_>#|\-]+/g, ' ') - .replace(/\s+/g, ' ') - .trim() - .toLowerCase(); -} - -function extractQuestions(markdown) { - return [...markdown.matchAll(/
[\s\S]*?<\/details>/g)].flatMap(([block]) => { - const question = block.match(/([\s\S]*?)<\/summary>/)?.[1]?.trim(); - const shortMarker = '**Короткий ответ**'; - const fullMarker = '**Полный ответ**'; - const shortStart = block.indexOf(shortMarker); - const fullStart = block.indexOf(fullMarker); - - if (!question || shortStart === -1 || fullStart === -1 || fullStart <= shortStart) { - return []; - } - - const shortAnswer = block.slice(shortStart + shortMarker.length, fullStart); - const fullEnd = block.indexOf('', fullStart); - const fullAnswer = block.slice(fullStart + fullMarker.length, fullEnd === -1 ? undefined : fullEnd); - - return [ - { - question, - shortAnswer: cleanAnswer(shortAnswer), - fullAnswer: cleanAnswer(fullAnswer), - }, - ]; - }); -} - -function diagnose({shortAnswer, fullAnswer}) { - if (!shortAnswer || !fullAnswer) { - return 'empty answer'; - } - - if (shortAnswer === fullAnswer) { - return 'full answer duplicates short answer'; - } - - const addedCharacters = fullAnswer.startsWith(shortAnswer) - ? fullAnswer.length - shortAnswer.length - : Number.POSITIVE_INFINITY; - const lengthRatio = fullAnswer.length / shortAnswer.length; - - if (addedCharacters < MIN_ADDED_CHARACTERS) { - return `full answer only adds ${addedCharacters} characters`; - } - - if (lengthRatio < MIN_LENGTH_RATIO) { - return `full answer is only ${lengthRatio.toFixed(2)}x longer`; - } - - return null; -} - -const files = await collectMarkdownFiles(ROOT); -const findings = []; -let questionCount = 0; - -for (const file of files) { - const markdown = await readFile(file, 'utf8'); - const questions = extractQuestions(markdown); - - questionCount += questions.length; - - for (const question of questions) { - const reason = diagnose(question); - - if (reason) { - findings.push({ - file: relative(process.cwd(), file), - question: question.question, - reason, - }); - } - } -} - -if (findings.length === 0) { - console.log(`Checked ${questionCount} questions in ${files.length} files: ` + 'no shallow full answers found.'); - process.exit(0); -} - -console.log(`Found ${findings.length} shallow full answers in ${questionCount} questions:\n`); - -for (const finding of findings) { - console.log(`- ${finding.file}: ${finding.question}`); - console.log(` ${finding.reason}`); -} - -console.log('\nA full answer should add reasoning, trade-offs, edge cases or a practical example.'); - -if (STRICT) { - process.exitCode = 1; -}