Pull to refresh

Solidity: комментарии

Reading time 8 min
Views 2.6K
Original author: Jean Cvllr

Комментарии используются для того, чтобы объяснить что делает код. Роберт С. Мартин ("дядюшка" Боб) утверждает, что «правильное использование комментариев должно компенсировать нашу неспособность выразить что-то в коде». Поэтому комментарии делают исходный код более понятным для человека.


Почему комментарии так важны, когда дело доходит до Solidity? Потому что они помогают разобраться в работе вашего смарт контракта не только разработчикам, но и конечным пользователям.

Как писать комментарии в Solidity?

Для того, чтобы написать однострочный комментарий, можно использовать //

// This is a single line comment

Для многострочных используется /* в комбинации с */

/* 
This is a
multi-line comment
*/

Документация Solidity гласит:

Однострочный комментарий завершается любым знаком конца строки Unicode (LF, VF, FF, CR, NEL, LS или PS) в кодировке utf-8. Знак конца строки по-прежнему является частью кода, который идет после комментария. Поэтому, если он не является символом ascii (т.е. NEL, LS и PS), это приведет к ошибке парсера.

Разные типы комментариев в Solidity

В Solidity есть два основных типа комментариев: обычные и комментарии NatSpec. Так, например, в Remix IDE их можно узнать по разным цветам (зеленый используется для обычных комментариев, а синий для комментариев NatSpec).

Что такое комментарии NatSpec?

Контракты Solidity имеют специальную форму комментариев, которые составляют основу формата спецификации естественного языка Ethereum(Ethereum Natural Language Specification Format), также известного как NatSpec. NatSpec разрабатывается и распространяется командой Ethereum.

Формат комментариев NatSpec соответствует стилю нотации Doxygen:

  • Для однострочных комментариев используется ///

  • Для многострочных комментариев используется /** в сочетании с */

  • Комментарии используется перед объявлением function, contract, library, interface, function и constructor

/// This is a Natspec single line comment
/**
This is a 
Natspec multi-line
comment block
*/

Важно! Комментарии NatSpec неприменимы для переменных. Даже, если они объявлены публично и, следовательно, влияют на ABI. Подробности

Использование тегов Doxygen вместе с NatSpec

Для максимальной пользы, в работе с NatSpec стоит использовать теги Doxygen.

Doxygenгенератор документации программного обеспечения. Документация создается с помощью анализа комментариев в коде.

Фрагмент кода ниже (из документации Solidity) показывает, как используются эти теги. Теги Doxygen начинаются с @ и используются для того, чтобы, например, объяснить:

  1. Заголовок контракта(в более понятном для человека виде)

  2. Что делает функция rectangle

  3. За что отвечают параметры w и h(ширина и высота прямоугольника)

  4. Что вернет функция: s(площадь) и p(периметр)

pragma solidity >=0.4.0 <0.7.0;

/** @title Shape calculator. */
contract ShapeCalculator {
    /** @dev Calculates a rectangle's surface and perimeter.
      * @param w Width of the rectangle.
      * @param h Height of the rectangle.
      * @return s The calculated surface.
      * @return p The calculated perimeter.
      */
    function rectangle(uint w, uint h) public pure returns (uint s, uint p) {
        s = w * h;
        p = 2 * (w + h);
    }
}

Вы можете использовать Doxygen с комментариями NatSpec в качестве инструмента для создания документации. Однако имейте в виду, что в Solidity доступны только 6 тегов Doxygen:

Все эти теги опциональны, но их использование имеет следующие преимущества:

  • Позволяет документировать функции, конструкторы, контракты, библиотеки и интерфейсы

  • Предоставляют данные для пользовательского интерфейса (отображают текст подтверждения, который показывается пользователям, когда они пытаются вызвать функцию).

Примечание: В настоящее время NatSpec интерпретирует теги только в том случае, если они применены к external или public значениям. Вы по-прежнему можете использовать их для своих internal и private функций, но они не будут анализироваться.

Важно помнить про два основных тега:

  • @notice: информация о том, что делает функция, которая показывается пользователю во время исполнения

  • @dev: документация для разработчиков

Если не используются никакие теги(например, используется только ///), то все сообщение применяется с тегом @notice

Тег @notice, пожалуй, является самым главным тегом в NatSpec. Он предназначен для тех, кто никогда не видел исходный код. Поэтому следует избегать информации о деталях реализации кода.

Вот еще один пример использования тегов Doxygen в смарт-контракте SimpleStorage из документации Solidity:

pragma solidity >=0.4.0 <0.7.0;

/// @author The Solidity Team
/// @title A simple storage example
contract SimpleStorage {
    uint storedData;

    /// Store `x`.
    /// @param x the new value to store
    /// @dev stores the number in the state variable `storedData`
    function set(uint x) public {
        storedData = x;
    }

    /// Return the stored value.
    /// @dev retrieves the value of the state variable `storedData`
    /// @return the stored value
    function get() public view returns (uint) {
        return storedData;
    }
}

Теги @param и @return имеют следующую спецификацию:

  • После @param должно идти имя переменной, которая использовалась в функции

  • После @return должно идти имя или тип переменной, которую возвращает функция

Если ваша функция возвращает несколько значений, то следует использовать @return для каждого значения отдельно(аналогично с @param).

Хорошим примером контракта Solidity, который элегантно использует комментарии NatSpec с Doxygen, является библиотека Buffer от Oraclize (созданная и используемая ими для своего API). Вы можете увидеть, насколько хорошо задокументированы функции append и appendInt с помощью тега @dev, который точно объясняет, что именно делает каждая функция. Наконец, теги @param и @return помогают понять, как ведет себя функция.

Создание документации с NatSpec: введение

Компилятор Solidity автоматически создает файл JSON(метаданные контракта), который содержит информацию о скомпилированном контракте. Вы можете использовать этот файл для запроса версии компилятора, используемых источников, документации ABI и NatSpec для более безопасного взаимодействия с контрактом и проверки его исходного кода. Подробнее

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

{
  version: "1",
  language: "Solidity",.
  compiler: {
    ...
  },
  sources:
  {
    ...
  },
  settings:
  {
    ...
  },
  output:
  {
    abi: [ ... ],
    userdoc: [ ... ],
    devdoc: [ ... ],
  }
}

Компилятор Solidity solc может создать документацию двух типов (в формате JSON) после компиляции вашего смарт-контракта:

  1. Документация для разработчиков:  solc --devdoc my_contract.sol

  2. Пользовательская документация: solc --userdoc my_contract.sol

Создание документации с NatSpec: подготовка

Теперь, давайте посмотрим как компилятор Solidity создает документацию нашего смарт-контракта. Для этого вам нужен компилятор solc (инструкция по установке). После установки, откройте свой терминал и следуйте приведенным ниже инструкциям:

  1. Создайте новую папку(например, NatSpec) и пройдите в нее: $ mkdir Natspec && cd Natspec

  2. Создайте новый Solidity файл: $ nano natspec.sol

  3. Вставьте код ниже:

pragma solidity ^0.5.0;
/// @title A Geometry triangle simulator
/// @author Jean Cavallera
/// @notice Use this contract for only the most basic simulation
/// @dev Contract under development to enable floating point
contract Geometry {
    
    struct Triangle {
        uint side_a;
        uint side_b;
        uint hypothenuse;
    }
    
    /// @notice Math function to calculate square root
    /// @dev Not working with decimal numbers
    /// @param x The number to calculate the square root of
    /// @return y The square root of x
    function sqrt(uint x) internal pure returns (uint y) {
            uint z = (x + 1) / 2;
            y = x;
            while (z < y) {
                y = z;
                z = (x / z + z) / 2;
            }
        }
    
    /// @notice Calculate the hypothenuse length based on x and y
    /// @dev Not working as it returns integers and not float
    /// @param _a Side 1
    /// @param _b Side 2
    /// @return uint the hypothenuse length
    function calculateHypothenuse(uint _a, uint _b) public pure returns (uint) {
        return sqrt((_a * _a) + (_b * _b));
    }
    
    Triangle public my_triangle;
    
    /// @author Jean Cavallera
    /// @notice Enter the two legs of your right angle triangle
    /// @dev This function modifies the state of the variable `my_triangle` and use `calculateHypothenuse()` function
    /// @param _a Side 1
    /// @param _b Side 2
    /// @return string return to user a custom success message
    function createTriangle(uint _a, uint _b) public returns (string memory) {
        my_triangle = Triangle ({
            side_a: _a,
            side_b: _b,
            hypothenuse: calculateHypothenuse(_a, _b)
        });
        return "new triangle created";
    }
    
    
}

Создание документации с NatSpec: документация разработчика

Введите следующую команду:

solc — devdoc natspec.sol

У вас должно получиться что-то такое:

{
  "author" : "Jean Cavallera",
  "details" : "All function calls are currently implemented without side effects",
  "methods" :
  {
    "calculateHypothenuse(uint256,uint256)" :
    {
      "details" : "Not working as it returns integers and not float",
      "params" :
      {
        "_a" : "Side 1",
        "_b" : "Side 2"
      },
      "return" : "uint the hypothenuse length"
    },
    "createTriangle(uint256,uint256)" :
    {
      "author" : "Jean Cavallera",
      "details" : "This function modifies the state of the variable `my_triangle` and use `calculateHypothenuse()` function",
      "params" :
      {
        "_a" : "Side 1",
        "_b" : "Side 2"
      },
      "return" : "string return to user a custom success message"
    }
  },
  "title" : "A Geometry triangle simulator"
}

Давайте более подробно посмотрим на этот файл:

  • ключи представляют собой Doxygen теги, которые мы использовали до этого: @title, @autor, @param и @return . Единственная разница заключается в значении ключа details, которые берется из тега @dev.

  • значения ключей соответствуют описанию тегов из NetSpec комментариев.

Есть еще две важные вещи, которые касаются раздела devdoc в ABI:

  • функция включает типы передаваемых аргументов(пример: "createTriangle(uint256,uint256)")

  • показываются только public функции. Так, например, private функция sqrt(uint x) отсутствует в итоговом файле.

NatSpec формирует документацию только для public и external функций

Мы упоминали об этом ранее. Вот что указано в документации Solidity:

(документация Solidity): NatSpec в настоящее время интерпретирует теги только в том случае, если они применяются к external или public функциям. Вы по-прежнему можете использовать аналогичные комментарии для своих internal и private функций, но они не будут анализироваться.

Создание документации с NatSpec: пользовательская документация

Выполните следующую команду:

solc --userdoc natspec.sol

Вы должны получить что-то такое:

{
    "methods" : 
  	{
      "calculateHypothenuse(uint256,uint256)" :
      {
        "notice" : "Calculate the hypothenuse length based on x and y"
      },
      "createTriangle(uint256,uint256)" :
      {
        "notice" : "Create a new right angle triangle using two right angle sides and calculate the hypothenuse dynamically"
      }
    },
    "notice" : "Use this contract for only the most basic simulation"
  }

Как мы видим, у нас все еще есть имя функции с ее параметрами, а также тег @notice как для контрактов, так и для функций.

Пример: документация разработчика в Remix

Использовать Doxygen теги особенно удобно, если вы работаете с Remix.


Новая версия Remix содержит плагины, расширяющие его основную функциональность. Очень полезной функцией является "Documentation File Generator in Markdown". Она создает генерацию контракта в формате Markdown на основе NetSpec комментариев.

  1. Откройте Remix. Кликните по кнопке "Plugin Manager".

  2. Найдите "Solidity Documentation Generator" и активируйте плагин.

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

Плагин сгенерирует документацию в формате Markdown. Это выглядит примерно так:

Пример: пользовательская документация для отображения сообщения в UI

Кошелек может использовать пользовательскую документацию NatSpec для отображения подтверждающего сообщения пользователю всякий раз, когда он взаимодействует с контрактом, вместе с запросом авторизации для подписи транзакции.

Пример: динамические сообщения

/// @notice Send `(valueInmGAV / 1000).fixed(0,3)` GAV from the account of 
/// `message.caller.address()` to an account accessible only by `to.address()`
function send(address to, uint256 valueInmGAV) {
...
}

Если пользователь (например, с адрессом 0x2334) попытается вызвать эту функцию с to = 0x0 и valueInmGAV=4.135, то он увидит:

Send 4.135 GAV from the account of 0.2334 to an account accessible only by 0x0

Код, заключенный в обратные кавычки, будет выполняться в среде EVM Javascript, которая имеет доступ к message и всем параметрам функциии. Поэтому вы можете использовать любое выражение Javascript/Paperscript для создания кастомных сообщений.

Atom плагин для авто-генерации NatSpec комментариев

NodeFactory.io разработали Atom плагин — solidity-comments. Он содержит базовые функции для создания всей шаблонной документации для вашего смарт-контракта Solidity. Его можно использовать с помощью нажатия следующей комбинации клавиш: Ctrl + Alt + G.

Например, допустим у вас есть такой код:

pragma solidity 0.4.24;

contract SomeContract {

    function test(uint _param1, uint _param2) external {
        require(_param1 == 1 && _param2 == 2);
    }
}

Нажмите Ctrl + Alt + G и вы получите:

pragma solidity 0.4.24;

/// @title SomeContract
/// @notice
/// @dev
contract SomeContract {

    /// @notice
    /// @dev
    /// @param _param1
    /// @param _param2
    /// @return
    function test(uint _param1, uint _param2) external {
        require(_param1 == 1 && _param2 == 2);
    }
}

Осталось только заполнить пустые теги.

Ссылки

  1. Разница между /* и /**

  2. Ethereum Wiki

Tags:
Hubs:
+3
Comments 2
Comments Comments 2

Articles