오픈 소스 모험: 에피소드 76: Ameba linter for Crystal

13289 단어 crystal
Ameba은 Crystal의 린터입니다.

최근에 약간의 Crystal 코드를 작성했으므로 어떻게 진행되는지 살펴보겠습니다.

기본 설정과 --all 모두에서 실행하겠습니다. 기본 설정으로 잘못된 긍정이 거의 없기를 바랍니다.

Shebang 문제


ameba#!/usr/bin/env crystal로 시작하는 Crystal 스크립트를 무시했습니다. 확장자가 .cr인 파일만 확인했습니다. 이것은 Crystal의 주요 사용 사례는 아니지만 유효한 사용 사례입니다.

Crystal의 VSCode 확장에는 동일한 문제가 있습니다.

이 문제는 .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


그것은 확실히 진짜입니다.

--all 및 Lint/ComparisonToBoolean이 있는 crystal-z3



이로 인해 엄청난 수의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-z3Z3::BoolExpr#==를 재정의하므로 의미상 이 보푸라기 규칙은 누락되었지만 이러한 비정상적인 코드를 지원하지 않는 것에 대해 ameba를 비난할 수는 없습니다.

이것이 일반적으로 좋은 규칙인지 잘 모르겠습니다. x 가 부울 이외의 것이 될 수 있는 한 x == truex 와 완전히 동일하지 않습니다.

Thue Interptetter 및 성능/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 here).

let {line} = lines.shift()


해시/명명된 튜플/객체를 분해하기 위해 이러한 추가 항목을 포함할 이유가 없습니다.

인터프리터와 --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


그것은 개선되지 않을 것입니다. 메서드 또는 루프 본문의 시작 부분에 있는 가드 절은 일반적인 패턴이지만 본문 뒤에 배치하는 것은 이상합니다.

오픈 소스 모험과 스타일/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}
                      ^------------------^


이것은 Ruby에서 직접 수행할 수 없는 Crystal 코드입니다. 합리적인 제안입니다.

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 와 관련된 새로운 유형의 제안이 없었습니다.

당신은 아메바를 사용해야합니까?



전반적으로 괜찮은 linter처럼 보이며 기본값은 너무 많은 오 탐지를 생성하지 않습니다.

어떤 종류의 심층 분석도 수행하지 않습니다. 예를 들어 if 유형 문제로 인해 항상 true인 일부 LBool 수표가 있다는 것을 알고 있습니다. 순전히 구문 검사에는 표시되지 않기 때문입니다.

다음에 온다



좋아요, 지금은 Crystal로 충분합니다. 다음 에피소드에서는 약속대로 다른 기술을 시도하겠습니다.

좋은 웹페이지 즐겨찾기