Как стать автором
Обновить
22
0
Игорь Чулинда @Igmat

Пользователь

Отправить сообщение

Я же сказал "не все авторы".
Было очень много дисскусий на эту тему и оба мнения были достаточно широко представлены. Но, в целом, победила позиция, что лучше не давать пользователю доступа к деталям имплементации вообще, чем потом ему говорить, что он сам дурак.


Кстати, приводился пример какого-то тестового фреймворка из Java, для которого существуют популярные плагины, которые используют приватное состояние через рефлексию, что в итоге существенно замедлило развитие самого фреймворка. (в деталях мог ошибиться, т.к. описывал по памяти, но суть думаю ясна)

Symbol.private предлагает решение и оно довольно простое.
У прокси действительно нет приватных полей оригинального объекта, но:


  1. Для каждой прокси всегда известен его таргет (и рантайм его знает)
  2. Доступ к полю используя что Symbol.private, что #priv отличается от доступа к публичному полю.

Это приводит к простому решению, если код пытается осуществить доступ к приватному полю на прокси то вместо обращения к прокси, он сразу обращаеться к таргету этого прокси.


Что-то вроде Reflect.GetPrivate(obj);

Это нарушает концепцию hard private, коммитет подобные escape hatch не будет даже рассматривать.

Мешает то, что прокси, которую вы взяли из примера в статье, была всего лишь демонстрацией проблемы. Ваш пример с реальной прозрачной прокси работать не будет, потому что:


  1. Доступ к __self тоже дернёт get trap, который обернёт результат, а по скольку ссылка на this уже имеет соответствующую ей прокси (в примере из статьи этого нет, но можно глянуть в моём комментарии здесь), то она и будет возвращена, а значит методы будут вызываться в контексте прокси и бренд-чек будет выкидывать ошибку.
  2. Допустим наша реализация не обарачивает __self проперти, тогда исключение не будет выброшенно, но методы/геттеры/сеттеры вызванные после __self будут вызваны в контексте __self, а нам нужно, что бы они вызывались в контексте прокси.
  3. const __self = Symbol("self"); не защищает от модификации — Object.getOwnPropertySymbols позволяет извлечь любой символ из объекта.

В кишки могут полезть не из-за того, что сделано плохо, а потому что разработчик плохо понимает ограничения библиотеки. Для примера:


class A {
    _x;
    get() { return this._x; }
    set(val) {
        const result = (Array.isArray(val))
            ? val
            : [val];
        result.forEach(doSomeSideEffect);

        return this._x = result;
    }

    method() {
        const transformedX = this._x.map(doImportantTransformation);
        doSomething(transformedX);
    }
}

doSomeSideEffect — делает что-то важное, но на первый взгляд незаметное (например проставляет каких-нибудь слушателей), и влияет на скорость выполнения.


Конечный пользователь решает проставить _x напрямую, не замечает что сайд-эффекта нет (в этот момент он ему не нужен), а скорость выполнения возрасла — "УРА, какой я молодец" думает он и переходит к другим задачам.


Автор библиотеки понимаеть, что он никогда не использует _x без применения к нему doImportantTransformation, а все другие методы (например, doSomething) расчитывают на вход получить уже трансформаированный массив, и решает сделать трансформацию только один раз, что ускорит работу его либы.


class A {
    _x;
    get() { return this._x; }
    set(val) {
        const result = (Array.isArray(val))
            ? val
            : [val];
        result.forEach(doSomeSideEffect);

        return this._x = result.map(doImportantTransformation);;
    }

    method() {
        doSomething(this._x);
    }
}

На первый взгляд автору кажется, что это даже не минорный апдейт либы, а просто патч. Но для пользователя, который использовал _x напрямую это breaking change, который ещё и не так просто найти т.к. ошибка возникает не в месте использования _x, а там где вызываеться method.


Не все авторы библиотек, готовы говорить своим пользователям, что раз вы юзали свойства с префиксом, то идите лесом.

Ответ довольно прост: [[Define]] семантика сделает декораторы для полей класса более предсказуемыми.

Собственно, была уже тьма дисскусий на тему hard private vs soft private.


Авторы популярных фреймворков/библиотек в большинстве своё выступают за hard private потому что это позволяет им четко отделить публичный контракт от деталей имплементации и потом спокойно проводить рефакторинги, делать новые фичи и, в целом, развивать свои продукты, не боясь сломать проекты, которые зависят от деталей имплементации, потому что таких просто не будет при хорошо инкапсулированом коде.

Вы, тут упускаете один момент. Да, для приложения, которое просто выкладывается в прод soft private из TS вполне достаточно, а вот для библиотеки — мало.


Hard private нужен как раз разработчикам библиотек — например Joyee, один из мейнтенеров node.js, приводил примеры, когда отсутствие нормальной инкапсуляции приводило к серьёзным проблемам.


Для авторов библиотек это большая проблема, когда консьюмеры начинают зависеть от деталей имплементации, а на данный момент это сплошь и рядом: _x, который является приватным только по соглашению, по факту начинают использовать в разнообразных даунстрим проектах, и хотя изначально это была просто деталь имплементации, теперь автор библиотеки не может её просто так убрать, не словам зависимые проекты.

Ответил здесь. Постарайтесь больше не промахиватся веткой;)

Ответ на комментарий пользователя Arlekcangp, который опять промахнулся веткой ¯\_(ツ)_/¯


Как, я уже говорил ранее, вот здесь я предоставил рабочий пример использования private синтаксиса, но и у него есть проблема — ключевое слово this получает ещё одно усложнение в понимании (а оно и так непростое) т.к. в зависимости от лексического контекста обладает разным смыслом, как вы и предлагаете.


Поэтому эта идея развилась в более непротиворечивый синтаксический сахар для всё того же Symbol.private стоящего в основе этой альтернативы.


В любом случае, скорее всего, ключевое слово private будет жить только в TypeScript, а в ES сможет появиться только вместе с аннотациями типов.


P.S.
Несмотря на то, что моей изначальной причиной погружения в эту тему был, как раз, неприятный синтаксис, сейчас я готов с ним даже смириться, лишь бы не было проблем с семантикой, которые я описал в статье.

Нет, ваш пример не получит доступа к приватному значению. Я, конечно, могу тут долго расписывать почему и давать ссылки на спеку и релевантные обсуждения, но надеюсь вы мне просто поверите. Да, аргумент плохой, но и вы ведь тоже можете прочитать спецификацию и полистать обсуждения на гитхабе (хотя задача это не тривиальная).


Единственный способ получить доступ к приватным полям — это программное получение доступа к сорсам (браузер и нода позволяют это нам), и такое их изменение, которое убирает прайват модификатор, после чего мы уже исполняем код, в котором приватных полей нет.


Это, правда, тоже можно решить, если вы контролируете окружение: тот же паттерн Мембрана и Realms пропозал для этого и придуманы.


P.S.
Function.protoype.toString — тоже можно обмануть заставив его возвращать строку [native code], но пусть это будет задачкой для любопытных;)

Почему не private там есть отдельное объяснение в FAQ.
А здесь я показал как можно таки использовать ключевое слово private, но у такого подхода есть определённые ограничения, самое главное из которых — это увеличение когнитивной нагрузки на и без того сложный this.

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

Говорили продвинутые люди и была повсеместно принята — это две абсолютно разные вещи.
И про Ройса я знаю ещё то, что его статья с, так называемой, критикой — это по сути первая формализация каскадной модели.
А вам стоит обратить внимание на то, что только с 4-го издания PMBoK (2008 год) в нём появляются элементы гибких методологий, хотя до этого там был ТОЛЬКО водопад. Я надеюсь вы помните о том, что этот гайд де-факто стандарт для управления проектами.

Вот ссылка на нужную часть статьи.

При этом в BaseT можно написать абсолютно любой тест. Вам ничто не мешает, сделает сложный assertion и экспортировать булево значение в baseline.

Я в самой статье писал, что TDD применим для проверки кода на корректность только в довольно ограниченном количестве случаев.
А фиксируют существующее поведение классический TDD не лучше, чем тот метод, что я предлагаю, если не хуже.


Если же мы хотим именно проверки на соответсивие требованиям, то лучше использовать BDD, и в таком случае BDD + BaseT выглядит ИМХО намного интереснее, чем BDD + TDD.

Тот разработчик, который поленится задуматся над предложенными результатами и просто их примет, в случае с классическим подходом просто не будет писать тест вообще или покроет только самые тривиальные случаи.


Я не говорю, что мой подход решает все проблемы. Но практика показывает что обычное юнит-тестирование слишком трудозатратно и это ведёт к тому, что его просто не делают.


Я не верю, что смогу переобучить и сподвигнуть большинство на правильное использование юнит-тестирования.
Я даю более простой инструмент и метод в надежде на то, что им будут охотнее пользоватся, ведь тесты как секс: "лучше плохой, чем никакого вовсе".


А при грамотном подходе к тестированию, ни в классическом подходе, ни в моём не будет серьёзных пробелов.

Спасибо, я очень рад, что вам понравилось :)
В BaseT полно планов на что-то подобное. Часть из которых перечислена здесь


  1. Со стороны IDE: есть идея сделать Workflow mode (очевидно, совместимый с любой IDE), который будет отслеживать изменения в сорсах и для начала скаффолдить тесты для экспортов, которые не протестированны. С изменением сигнатур, как в описаном сценарии, уже сложнее, но тоже можно что-то придумать.
  2. Со стороны эвристик: scaffold команда уже сейчас использует более сложные эвристики (правда работает только в тайпскрипте пока что) для генерации первоначальных тестов, но там ещё огромное пространство для улучшений.
  3. Со стороны тестовых данных: сбор статистики использования для тестовых данных — это интересная идея, но пока не вижу простых способов её реализации. С другой стороны TypeScript умеет находить использования того или иного класса/модуля/функции в вашем коде, и я собираюсь это заюзать для скаффолда тестов, используя реальные примеры применения модуля, что по идее должно существенно автоматизировать начальное создание тестов, ещё и с довольно неплохим качеством.
  4. Со стороны хайтека: к сожалению, не вижу как это можно было бы легко прикрутить к e2e тестированию, но машинным обучением точно можно было бы улучшить качество эвристик для скаффолда тестов. Но, с моей стороны, это для начала требует более качественной реализации scaffold комманды.

Собственно, всё реально, и над этим уже идёт работа — всё вышеперечисленное в том или ином виде описано в тасках #65 и #66. Так что следите за новостями, а лучше присоединяйтесь — я буду рад любому фидбеку и/или идее.

Просмотреть результаты, которые тебе предложила программа НАМНОГО проще, чем посчитать их самому и внести в тест.


Поэтому ИМХО вероятность того, что в бейзлайн проникнут некорректные значения не выше (а возможно и ниже), чем вероятность бага во вручную написанном тесте.


А что более важно, так это то, что упрощения процесса написания теста приведёт к тому, что будет покрыто больше кейсов т.к. необходимость писать инфраструктурный код больше не будет останавливать разработчика.


Более сложный процесс (ваш), не значит более надёжный. Мне кажется, что мой, более простой процесс, не менее надёжен. К тому же, он предлагает помощь при самом написании кода как я описывал здесь, что увеличивает количество проверяемых значений ещё на этапе разработки, а выкидывать проверочные кейсы, мне кажется, в голову никому не придёт.

  1. Спасибо за наводку на react-test-renderer, обязательно посмотрю внимательней и скорее всего заюзаю его вместо рендера в строку.
  2. snapshotSerializers — это совершенно не то же самое, что и резолвер.
    Начнём с того, что, во-первых, исходя из этой строки они просто сравнивают полученный вывод с снэпшотом сравнением строк. В baset от этого отказались (хотя фолл-бек к этому есть).
    Во-вторых, предназначение сериалайзера из Jest — это форматированние снэпшота, т.е. это та функция, которую делают baseliner модули у меня. Они правда и сериализуют, и десериализуют, что позволяет им дописывать дополнительный контент, который не приведёт к поломке теста при изменении несущественных элементов (например md-baseliner со временем научится дописывать текстовые доки к блокам кода, что сделает baseline ближе к докам).
    В-третьих, резолвер, именно резолвит, а не сериализует. То есть к нескольким экспортам из одного файла могут быть применены разные резолверы (например, для реакта и пикси) и записанны в один бейзлайн.
    В-четвёртых, экспорты проходяться рекурсивно, умеют находить циклические ссылки (и по особому их разруливают), резолвят промисы и делают ещё кучу всего.
    Как вывод, резолвер — это совершенно другая абстракция, для которой можно сейчас найти ещё миллион отличий (большая часть из которых, скорее всего, будет положительными)
  3. Я стараюсь делать всё, что бы получился "удобный для чтения вид", с тем же pixi, я рендерю картинку. Оутпуты могут быть разными для этого и придуманы резолверы, сайд-эффект коллекторы и бейзлайнеры, поэтому я думаю, что можно будет придумать самый удобный вид для отображения всего этого добра в итоговом бейзлане-доке.
1

Информация

В рейтинге
Не участвует
Откуда
Lublin, Польша
Дата рождения
Зарегистрирован
Активность