Суть Читай небольшие сниппеты — объект-счётчик, exports модуля, пробой через устаревшее состояние и протечку stack overflow — и выбирай, что abstraction реально открывает, прячет или не может спрятать.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на middle-высоте — в небе
◷ 14 min
Abstraction оценивают по коду, а не по определению. Читай каждый сниппет, реши, где кончается interface и начинается скрытая implementation, и замечай момент, когда граница держит — или ломается.
Цель
Потренируй чтение, которое работающий программист запускает постоянно: найди interface, от которого зависит вызывающий, найди implementation за ним и определи, держит ли сокрытие под кодом перед тобой.
Сниппет 1 — связка-счётчик
const counter = { count: 0, // data field — состояние increment() { // method field — операция this.count = this.count + 1; }, value(): number { // method field — ещё операция return this.count; },};counter.increment();counter.increment();let n = counter.value(); // n становится 2
Викторина
Completed
Какие строки — единственные, что называют поле данных, и что это говорит об interface связки?
Heads-up counter.value() вызывает метод — он никогда не пишет count. Метод читает count внутри через this; вызывающий видит только метод, а это и есть смысл связки.
Heads-up Объект про count, но трогают его через this только тела методов. Три строки вызывающего называют только методы — это разделение и даёт encapsulation.
Heads-up Тела методов называют его как this.count. Поля данных называемы внутри собственных методов объекта; encapsulation прячет именно называние их снаружи.
// другой модуль, импортирующий тот, что вышеimport { format } from "./text-format";format(" hi "); // окpad("x"); // ??? — pad никогда не экспортировали
Викторина
Completed
Импортирующий модуль вызывает pad('x') напрямую. Что происходит и каков public interface модуля?
Heads-up Нахождение в одном файле с export не экспортирует имя. Через границу проходят только имена с пометкой export; у pad её нет, поэтому импортирующий модуль его не видит.
Heads-up Вызов приватного хелпера внутри не экспортирует его. То, что format использует pad, — деталь implementation; pad остаётся приватным и недостижимым снаружи.
Heads-up Модуль грузится нормально; ошибка — только вызов pad('x'). Граница блокирует эту одну ссылку, а не ломает модуль, легально экспортировавший format.
Сниппет 3 — пробой
// module: banklet balance = 100; // приватно — без exportexport function deposit(n: number) { balance = balance + n; }export function getBalance() { return balance; }
// вызывающий — что он может и не можетimport { deposit, getBalance } from "./bank";deposit(50); // ок — через interfacebalance = -999; // ??? — мимо interface
Викторина
Completed
Вызывающий присваивает balance = -999, чтобы сделать баланс отрицательным в обход deposit. Почему дизайн модуля не даёт этому стать реальным риском?
Heads-up Вызывающий не может дотянуться до модульного balance: его не экспортировали. Присваивание трогает отдельное имя в вызывающем, а не приватное поле модуля, так что состояние модуля в безопасности.
Heads-up Экспорт deposit экспортирует функцию, а не переменную balance. Приватный balance остаётся приватным независимо от того, какие функции экспортированы рядом.
Heads-up Это ровно то, что граница предотвращает. Приватное имя нельзя достать или перезаписать снаружи; именно это принудительное сокрытие позволяет модулю гарантировать инвариант.
Сниппет 4 — протечка
function depth(n: number): number { return depth(n + 1); // не останавливается — нет базового случая}depth(0); // RangeError: Maximum call stack size exceeded
Викторина
Completed
Abstraction вызова функции обещает «вызови, она выполнится, вернёт». Этот код падает, называя стек вызовов. Как юнит это назвал и в чём урок?
Heads-up Abstraction в порядке — баг в отсутствии базового случая. Суть в том, что даже хорошая abstraction протекает нижним слоем (стеком), когда тот упирается в реальный предел, а не в том, что рекурсия запрещена.
Heads-up Синтаксис валиден; код работает, пока стек не переполнится. Ошибка — рантайм-предел скрытого слоя, проступивший наружу (определение протечки), а не проблема разбора.
Heads-up Больше памяти лишь отсрочит переполнение; неограниченная рекурсия исчерпает любой конечный стек. Никакой объём памяти не делает abstraction над реальной машиной непротекающей.
Итог
Каждый сниппет — одно и то же чтение на разном масштабе. В счётчике interface — методы, а count скрыт за this. В модуле через границу проходит только экспортированный format, а pad, trim и TABLE остаются приватными. В банке приватный balance нельзя достать снаружи, поэтому модуль может гарантировать инвариант — граница превращает надежду в правило. А в безудержной рекурсии конечный стек вызовов протекает сквозь abstraction вызова в момент, когда нижний слой упирается в предел. Найди interface, найди скрытую implementation и проверь, держит ли сокрытие.