2020-12-18

Steep by Steep

 この記事は、ケーシーエスキャロット Advent Calendar 2020 17 日目の記事です。(ちょっと過ぎちゃったけど)

Ruby 3の静的解析機能の1つ。steep を使ってみようと思います。

 (styleが消えてコードが見にくい…)

リポジトリは、こちら

タイトルの Steep by Steep は、中学の頃流行った New Kids On The Block の "Step By Step" にかけてみました。

 


インストール

Rubyは、3.0.0-preview2を使っています。

steepのinstallをします。gem でinstallします。



$ gem install steep
Successfully installed steep-0.38.0
Parsing documentation for steep-0.38.0
Installing ri documentation for steep-0.38.0
Done installing documentation for steep after 2 seconds
1 gem installed
サンプルで使うコードです。 色々な型を試してみたかったので、設計面は無視したコードになっています。

lib/user.rb


class User                                                                                                                                                                             
  def initialize(no, name)
    @no = no
    @name = name
    @scores = Array.new
  end
  attr_reader :no, :name, :scores

  def add(score)
    @scores << score
  end
end

lib/analyzer.rb


require 'csv'                                                                                                                                                                          
  
class Analyzer
  @@score = Struct.new("Score", :subject, :score)

  def self.load(path)
    users = Array.new
    CSV.foreach(path, headers: true) do |row|
      users << parse_row(row)
    end

    users
  end

  def self.parse_row(row)
    u = User.new(row["No"], row["Name"])

    row.headers[2..-1].each do |subject|
      u.add @@score.new(subject.downcase, row[subject].to_i)
    end

    u
  end
end

bin/main.rb


LOAD_PATH << File.join(File.dirname(File.expand_path(__FILE__)), "../lib")                                                                                                            
  
require 'user'
require 'analyzer'

# 使用するデータ
data = <<-EOS
No,Name,English,History,Science
1,Akina,85,88,87
2,Bob,78,95,85
3,Candy,83,80,92
4,David,85,83,88
5,Emily,78,93,85
6,Fumiya,80,82,89
7,George,92,88,79 
EOS

それでは、実際に使ってみます



$ steep init
と入力すると、Steepfile が作成されるので、ここに定義をしていきます。

Steepfile


target :app do
  signature "sig"

  check "bin"
  check "lib"
end
これだけ定義して、まず実行してみます。

$ bundle exec steep check --log-level=fatal
bin/main.rb:20:2: NoMethodError: type=singleton(::File), method=open (File.open(fpath, "w") { |fd| fd.write data })
lib/user.rb:7:2: NoMethodError: type=self, method=attr_reader (attr_reader :no, :name, :scores)
エラーが出ました。
main.rbはおいておいて、Userクラスで、attr_reader に定義をしていないことを指摘しているようなので、定義を追加してみます。
定義ファイルは、sigフォルダの中に `.rbs`ファイルで書くとあるので、 sig/sample.rbs を作成して以下のように定義してみました。

class User                                                                                                                                                     
  @no: Integer
  @name: String
  @scores: Array[Score]
end
もう1度実行してみると、

sig/user.rbs:4:17...4:22        UnknownTypeNameError: name=::Score
エラーが変わりました。
ScoreというTypeがわからないというエラーが出ました。 

これは、Analyzerクラスの中でStructで定義しているからかな… ひとまず、先に進めるために `untyped` として、もう1回実行してみると、次のエラーに変わりました。

lib/user.rb:2:2: MethodArityMismatch: method=initialize (def initialize(no, name))
initializeの定義をしていないので、次はメソッドの定義をしていきます。


こんな感じかな(とりあえずuntyped)

class User
  @no: Integer
  @name: String
  @scores: Array[untyped]
  def initialize: (no: untyped, name: untyped) -> untyped                                                           
  def add: (score: untyped) -> untyped
end
実行してみると、、、

lib/user.rb:2:2: MethodArityMismatch: method=initialize (def initialize(no, name))
lib/user.rb:9:2: MethodArityMismatch: method=add (def add(score))
エラーになってしまいました… 


Analyerクラスが定義されていないことによる影響かな…と頭をよぎったので、Analayzerクラスも定義してみます。

class User
  @no: Integer
  @name: String
  @scores: Array[untyped]
  def initialize: (no: untyped, name: untyped) -> untyped
  def add: (score: untyped) -> untyped
end

class Analyzer
  def self.load: (path: untyped) -> Array[User]
  def self.parse_row: (row: untyped) -> User
end
実行してみると、、、

$ bundle exec steep check --log-level=fatal
bin/main.rb:20:2: NoMethodError: type=singleton(::File), method=open (File.open(fpath, "w") {|fd| fd.write data })
bin/main.rb:22:24: ArgumentTypeMismatch: receiver=singleton(::Analyzer), expected={ :path => untyped }, actual=::String (fpath)
lib/analyzer.rb:6:2: MethodArityMismatch: method=(self) (def self.load(path))
lib/analyzer.rb:15:2: MethodArityMismatch: method=(self) (def self.parse_row(row))
lib/analyzer.rb:16:8: IncompatibleArguments: receiver=singleton(::User), method_type=(name: untyped, no: untyped) -> ::User (User.new(row["No"], row["Name"]))
lib/user.rb:2:2: MethodArityMismatch: method=initialize (def initialize(no, name))
lib/user.rb:9:2: MethodArityMismatch: method=add (def add(score))
エラー増えた… 


リポジトリ内の sig/project.rbi も確認してみたのですが、定義の仕方がおかしいわけではなさそうです… 

もう少し中身を確認した方がよさそうなので、日を改めて取り組んでいきたいと思います。

0 件のコメント:

コメントを投稿