Приключения с открытым исходным кодом: Эпизод 76: Ameba — линтер для Crystal

Ameba — это линтер для Crystal.

Недавно я написал немного кода для Crystal, так что давайте посмотрим, что из этого получится.

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

Проблема шебанга

ameba игнорировала скрипты Crystal, начинающиеся с #!/usr/bin/env crystal — она просматривала только файлы с расширением .cr. Это не основной вариант использования Crystal, но вполне допустимый.

Расширение VSCode в Crystal имеет ту же проблему.

Проблема может быть решена с помощью конфигурационного файла .ameba.yml.

crystal-z3 и Lint/UselessAssign.

В настройках по умолчанию он находит одну проблему:

$ ameba
Inspecting 25 files

......................F..

src/z3/api.cr:89:7
[W] Lint/UselessAssign: Useless assignment to variable `var`
> var = LibZ3.mk_const(Context, name_sym, sort)
  ^-^

Finished in 223.71 milliseconds
25 inspected, 1 failure
Вход в полноэкранный режим Выход из полноэкранного режима

Он не показывает никакого контекста, но это из этого метода:

def mk_const(name, sort)
  name_sym = LibZ3.mk_string_symbol(Context, name)
  var = LibZ3.mk_const(Context, name_sym, sort)
end
Войти в полноэкранный режим Выйти из полноэкранного режима

Это определенно реально.

crystal-z3 с --all и Lint/ComparisonToBoolean

Это порождает огромное количество проблем Lint/ComparisonToBoolean, некоторые примеры здесь:

spec/bool_spec.cr:10:6 [Correctable]
[W] Lint/ComparisonToBoolean: Comparison to a boolean is pointless
> [a ==  true, b ==  true, c == (a & b)].should have_solution({c =>  true})
   ^--------^
spec/model_spec.cr:15:19 [Correctable]
[W] Lint/ComparisonToBoolean: Comparison to a boolean is pointless
> solver.assert a == true
                ^-------^
[W] Lint/ComparisonToBoolean: Comparison to a boolean is pointless
> raise Z3::Exception.new("Cannot evaluate") unless result == true
                                                    ^------------^
Вход в полноэкранный режим Выход из полноэкранного режима

Ну, crystal-z3 переопределяет Z3::BoolExpr#==, так что семантически это правило не работает, но я не могу винить ameba за то, что она не поддерживает такой необычный код.

Я не уверен, что это хорошее правило в целом. Пока x может быть чем угодно, кроме булевой, x == true — это совершенно не то же самое, что x.

Thue Interptetter и Performance/CompactAfterMap.

Есть две проблемы, обнаруженные с интерпретатором Thue из этой серии:

./episode-65-crystal-thue-randomized-finite-automaton/thue_rfa.cr:162:27
[C] Performance/CompactAfterMap: Use `compact_map {...}` instead of `map {...}.compact`
> next_tries = active.map{|t| t[char]}.compact
                      ^-----------------------^
Вход в полноэкранный режим Выход из полноэкранного режима

compact_map — достойное предложение, так как эти объединенные общие операции обычно более производительны, но подобные методы продолжают добавляться в каждую минорную версию каждого языка, трудно запомнить, в каком языке какие комбинации есть.

./episode-65-crystal-thue-randomized-finite-automaton/thue_rfa.cr:191:13
[W] Lint/UselessAssign: Useless assignment to variable `line_no`
> line, line_no = lines.shift
        ^-----^

Вход в полноэкранный режим Выйти из полноэкранного режима

Мне это не нравится. Бесполезное присвоение одной переменной может быть удалено, так что это имеет смысл, но при деструктуризации массива альтернативой является уродливый код, как в одном из примеров:

line, _ = lines.shift
line, _line_no = lines.shift
line = lines.shift[0]
Войти в полноэкранный режим Выйти из полноэкранного режима

Я бы предпочел придерживаться оригинала.

Но это не специфично для ameba, в большинстве линтеров проверка «Бесполезное присваивание переменной» не имеет исключения для множественного присваивания.

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

let {line} = lines.shift()
Войти в полноэкранный режим Выйти из полноэкранного режима

Для деструктуризации хэша / именованного кортежа / объекта нет причин включать эти дополнения.

Thue Interpretter и --all

Поскольку мы не используем --all, ложные срабатывания вполне ожидаемы. Вот один пример, таких случаев больше:

./episode-65-crystal-thue-randomized-finite-automaton/thue_rfa.cr:221:5 [Correctable]
[C] Style/GuardClause: Use a guard clause (`return unless debug`) instead of wrapping the code inside a conditional expression.
> if debug
  ^^
Вход в полноэкранный режим Выход из полноэкранного режима

Таким образом, linter хочет, чтобы мы заменили это:

  def run(debug)
    @state = @initial
    if debug
      @rules.each do |rule|
        STDERR.puts rule
      end
    end

    while match = @rfa.not_nil!.random_match(@state)
      rule = match[:rule]
      idx = match[:idx]
      if debug
        STDERR.puts "Applying rule #{rule} at #{idx} to #{@state.inspect}"
      end
      @state = rule.apply(@state, idx)
    end

    if debug
      STDERR.puts "No more matches. Final state: #{@state.inspect}"
    end
  end
Войти в полноэкранный режим Выход из полноэкранного режима

этим:

  def run(debug)
    @state = @initial
    if debug
      @rules.each do |rule|
        STDERR.puts rule
      end
    end

    while match = @rfa.not_nil!.random_match(@state)
      rule = match[:rule]
      idx = match[:idx]
      if debug
        STDERR.puts "Applying rule #{rule} at #{idx} to #{@state.inspect}"
      end
      @state = rule.apply(@state, idx)
    end

    return unless debug
    STDERR.puts "No more matches. Final state: #{@state.inspect}"
  end
Войти в полноэкранный режим Выйти из полноэкранного режима

Это не будет улучшением. Охранные клаузулы в начале метода или цикла — это обычная схема, но ставить их после основной части — это странно.

Приключения с открытым исходным кодом и Style/VerboseBlock

Чтобы заставить ameba увидеть все файлы, мне нужно запустить его со странным интерфейсом:

$ ameba `git grep -l '#!/usr/bin/env crystal'` `git ls "*.cr"`
Войти в полноэкранный режим Выйти из полноэкранного режима

Вот некоторые предложения:

episode-65/minesweeper.cr:40:25 [Correctable]
[C] Style/VerboseBlock: Use short block notation instead: `map(&.ite(1, 0))`
> neighbourhood(x, y).map{|v| v.ite(1, 0)}.reduce{|a,b| a+b}
                      ^------------------^
Войти в полноэкранный режим Выйти из полноэкранного режима

Это код Crystal, который нельзя напрямую реализовать в Ruby. Это разумное предложение.

episode-70/aquarium:17:25 [Correctable]
[C] Style/VerboseBlock: Use short block notation instead: `map(&.[2..].chars)`
> @board = lines[2..].map{|l| l[2..].chars}
                      ^-------------------^
Войти в полноэкранный режим Выйти из полноэкранного режима

Кажется, что это слишком, но, возможно, я смогу к этому привыкнуть.

episode-72/dominosa:65:23
[W] Lint/UnusedArgument: Unused argument `type`. If it's necessary, use `_` as an argument name to indicate that it won't be used.
> @dominos.each do |type, vars|
                    ^
Войти в полноэкранный режим Выйти из полноэкранного режима

Здесь можно использовать .each_value, но, как и в случае с множественным присваиванием, мне не нравится предложение @dominos.each do |_type, vars| или @dominos.each do |_, vars|.

episode-68/switches:50:67 [Correctable]
[C] Performance/ChainedCallWithNoBang: Use bang method variant `sort!` after chained `select` call
> puts @nodes.select{|n| model[@switches[n]].to_s == "true" }.sort.join(" ")
                                                              ^--^
Вход в полноэкранный режим Выход из полноэкранного режима

Я понимаю, почему это имеет смысл для производительности, но .sort! в середине цепочки — это так странно, что я бы предпочел этого не делать.

С --all не было никаких новых типов предложений.

Стоит ли использовать ameba?

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

Он не делает никакого глубокого анализа. Например, я знаю, что у меня были некоторые проверки if, которые всегда true из-за проблемы с типом (Z3 выдает LBool enum, а не Bool, поэтому результат всегда true), но ameba никогда не обнаруживал этого, так как это не видно при чисто синтаксической проверке.

Далее

Ладно, на сегодня Кристалла действительно достаточно. В следующем эпизоде мы попробуем другую технологию, как и обещали.

Оцените статью
devanswers.ru
Добавить комментарий