Перейти к содержанию

Видимость функций Solidity и вспомогательные операции

Функции контракта могут служить внешним интерфейсом или внутренними строительными блоками. Если перепутать эти роли, реестр получит неожиданный путь изменения. Рассмотрим external, public, internal и private через публикацию курса 17 и небольшой внутренний помощник.

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

Внешний договор реестра

publish и deactivate являются внешними операциями. Их сигнатуры входят в интерфейс, с которым работает сайт. Мы выбираем external, поскольку эти методы предназначены для внешнего вызова. Это не означает разрешения любому адресу успешно менять состояние: модификатор отдельно проверяет отправителя.

Getter публичного поля также доступен снаружи. Читатель может узнать издателя и текущий выпуск, не имея права их изменять. Поэтому слово public нельзя переводить как «любой пользователь редактирует». Оно описывает видимость и, для полей состояния, предусмотренный getter.

address public immutable publisher;
mapping(uint256 => Course) public courses;

Эти объявления не создают сетевой setter. Публикация по-прежнему проходит через метод с собственными требованиями. На сайте можно читать fields напрямую через сгенерированные функции, но запись никогда не появляется только из-за существования похожего элемента формы.

Вынесем общую операцию

Представим внутренний помощник, который делает запись после проверок:

function _storeRelease(uint256 courseId, bytes32 contentHash)
    internal
{
    courses[courseId] = Course(contentHash, true);
    emit Published(courseId, contentHash);
}

Это явный вариант организации базового контракта. Внешняя publish сначала применяет onlyPublisher и проверяет ненулевые параметры, затем вызывает _storeRelease. Помощник не создаёт новый публичный маршрут и выполняется в контексте вызывающей внутренней функции.

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

Для маленького реестра вынесение не обязательно. Иногда полный короткий publish читается лучше. Разделять код стоит, когда помощник имеет ясное назначение и действительно улучшает композицию, а не ради превращения каждой строки в отдельную функцию.

Public и внешний вызов самого себя

public-функцию можно использовать как внешний интерфейс и вызывать внутренним способом по предусмотренным правилам языка. external имеет другую форму такого обращения. Попытка заменить внутреннее выполнение на this.publish(...) создаёт внешний вызов к собственному контракту.

У этого вызова меняется граница сообщения: внутри вызванного метода непосредственным отправителем становится адрес самого контракта. Если publisher — P, собственный адрес C не пройдёт нашу обычную проверку. Это не ошибка адреса пользователя; это следствие выбранного маршрута.

Внутренний помощник: контекст отправителя сохраняется
this.publish(...): новый внешний вызов, sender = адрес реестра

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

Private не скрывает публичное состояние

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

Также private-помощник не виден производному контракту так же, как internal. Это важно при расширении: требование повторно использовать логику должно быть согласовано с видимостью. Если автор копирует внутренний механизм вместо продуманной композиции, две копии могут начать расходиться по проверкам.

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

Карта путей изменения

Составим небольшую таблицу не названий, а назначений:

Объект Доступность Требование
publish Внешняя Издатель и допустимый вход
deactivate Внешняя Издатель и активная запись
courses getter Внешняя Только получение данных
_storeRelease Внутренняя в варианте рефакторинга Предусловия выполнены вызывающим кодом

Теперь можно проверить мысленный случай: автор случайно объявил _storeRelease как public. Если он не добавил самостоятельные проверки, любой участник получает маршрут записи, обходящий publish. Ошибка связана с новой доступностью метода, а не с недостатком криптографии. Поэтому изменение видимости относится к безопасности интерфейса.

В результате у нас есть различие между «можно вызвать» и «разрешено изменить». Первое задают видимость и ABI, второе — условия роли и состояния. Следующий урок оформит собственные ошибки, которые помогают клиенту понять, какой слой отказал. Видимость Solidity содержит полный набор правил.

Интерфейс после рефакторинга

При рефакторинге полезно различать изменение внутренней организации и внешнего договора. Если короткое присваивание вынесено в internal-помощник, опубликованные сигнатуры могут сохраниться. Если helper стал public, ABI получил новый маршрут. Даже без изменения прежних строк это уже другое приложение для внешнего участника.

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

Сравните также private и internal при наследовании. Более широкий внутренний доступ позволяет производному коду использовать функцию, но создаёт новые потенциальные вызовы. Нужно документировать предпосылки помощника: например, допустимый вход уже проверен. Тогда автор расширения понимает, что ему нельзя просто передать произвольные значения.

В этом курсе мы не обещаем поддержку любого наследника CourseCatalog. База остаётся небольшой самостоятельной моделью. Отдельный viewer использует интерфейс чтения и не получает права записи. Такое разделение позволяет развивать пример без неопределённой иерархии полномочий.

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

Оглавление курса. Листинги, расчёты и наблюдения здесь разобраны как ожидаемое поведение; компиляция, учебные программы и проверки при написании не запускались.

Оглавление серии