Классическая фраза из фильмов звучит примерно так: «Я люблю запах рефакторинга по утрам», верно? Вот как я это помню.

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

Ниже представлена ​​(очень) очищенная версия исходного кода.

pragma solidity ^0.4.22;
contract Voting {
  address[] public candidates;
  mapping(address => uint) public votesReceived;
  function Voting(address[] candidateNames) public { //330717 gas w/ 5 candidates
    candidates = candidateNames;
  }
  function voteForCandidate(address candidate) public { //21404 gas
    require(isValidCandidate(candidate));
    votesReceived[candidate] += 1;
  }
  function totalVotesFor(address candidate) view public returns (uint) {
    require(isValidCandidate(candidate));
    return votesReceived[candidate];
  }
  function isValidCandidate(address candidate) view public returns (bool) {
    for(uint i = 0; i < candidates.length; i++) {
      if (candidates[i] == candidate) {
        return true;
      }
    }
    return false;
  }
}

Первое, что привлекло мое внимание, - это использование устаревшего синтаксиса для функции конструктора. В Solidity функция-конструктор контракта обычно определялась с тем же именем, что и сам контракт. То есть, если контракт назывался Голосование, то функция с именем Голосование будет восприниматься как его конструктор, специальная функция, которая запускается только один раз, во время развертывания, но функция с именем голосование (маленькие заглавные буквы) будет нормальной, общедоступной функцией. Эта синтаксическая конструкция была источником ряда дорогостоящих ошибок, когда развернутые контракты оставались неинициализированными, а чувствительные административные функции оставались открытыми для любого DevOps199Теперь мы используем функцию с именем Конструктор, его сложнее не заметить.

Затем были структуры данных. Хранение массива с адресами всех допустимых кандидатов в цепочке может быть дорогостоящим. Я имею в виду, что идеальная стоимость - это функция полезности наличия данных, доступных контракту для манипулирования, поэтому нам нужно посмотреть, что с ними делает контракт, прежде чем выносить суждение. Здесь он использовался для очистки ввода с использованием функции «isValidCandidate» и ключевого слова «require». Ой, это определенно дорого.

Написание смарт-контрактов ближе к разработке для встраиваемых устройств, чем к веб-разработке, потому что каждое вычисление рассчитывается и оценивается соответствующим образом с помощью механизма «газа». Это делает циклы и чрезмерную запись и чтение из состояния нецелесообразным, потому что даже вызов функции «просмотра» только для чтения будет стоить определенного количества эфира, если будет вызван из другого смарт-контракта, что хорошо, потому что в этом случае плохой разработчик будет стоит дороже, чем хороший :-)

Зная, что Solidity предоставляет нам бесплатные функции получения для типов данных, определенных как «public», функции «totalVotesFor» и «isValidCandidate» дублируют уже доступные функции, просто вызывая « кандидаты [_candidateAddr] .votes »и« кандидаты [_candidateAddr] .isValid »соответственно, поэтому я решил их удалить.

pragma solidity ^0.4.25;
contract RefactoredVoting {
    struct CandidateStruct {
        bool isValid;
        uint64 votes;
    }
    mapping(address => CandidateStruct) public candidates;
    constructor(address[] _candidates) public { //205108 gas w/ 5 candidates
        for (uint i = 0; i < _candidates.length; i++) {
            candidates[_candidates[i]].isValid = true;
        }
    }
    function voteForCandidate(address _candidate) public { //5910 gas
        candidates[_candidate].votes += 1;
    }
}

Проведение простого теста с 5 адресами кандидатов показало хороший прогресс с точки зрения затрат на газ. Развертывание теперь стоит ~ 205 тыс. Газа, по сравнению с ~ 330 тыс., А для функции "voteForCandidate" требуется всего ~ 6 тыс. Газа, по сравнению с ~ 21 тыс. В исходном коде.

Можно уменьшить потребление газа при развертывании, сохранив голоса как uint256 (в Ethereum используются 32-байтовые слова), но функция голосования увеличивает до ~ 20 тыс. Газа, и, честно говоря, я не думаю, что кому-то когда-либо понадобится больше, чем 9223372036854775807 голосов. :-)

Получайте лучшие предложения по программному обеспечению прямо в свой почтовый ящик