Nao000のぶろぐ

蝶を追っている少年になりたい

【Elixir】DialyzerでTypespecの型指定を試したら思ったよりすごかった

目次

  1. バージョン情報
  2. dialyzerとは
  3. typespecsとは
  4. @specを試す
  5. すごかった部分
  6. 参考サイト

バージョン情報

Elixir: 1.8.1

Phoenix: 1.4.2

dialyzerとは

Dialyzerとは静的コード解析ツールです。

Typespecsによる型指定の他、GUIでステップ実行、ブレークポイントの作成を可能にするらしい。自分はdocker上で作業しているのでGUI未使用です。

typespecsとは

引数の型や返り値の型指定を明示します。

Elixirは動的型付け言語なので実際に型を指定している訳ではありません。なのでコンパイル自体は型に矛盾があってもエラーにはなりません。

ExDoc によるドキュメント生成にも利用できるらしいです。そのうち試したいと思います。

@specを試す

@spec 以外にも属性はあるようです。他の属性もそのうち試したいと思います。

@spec で型指定

sample関数はコードの通り、引数、返り値がinteger型の関数です。

文字列を型指定したい場合は charlist(), nonempty_charlist(), binary(), String.t() のどれかを指定するそうです。https://hexdocs.pm/elixir/typespecs.html#the-string-type

      @spec sample(integer) :: integer
      def sample(integer) do
        10
      end

mix dialyzer で静的コード解析実行

mix dialyzer で解析実行します。コード上に矛盾がなければ done (passed successfully) と表示されます。

    /home/nao000dotcom # mix dialyzer
    Compiling 1 file (.ex)

    ~省略~

    done in 0m5.64s
    done (passed successfully)

次に先程のsample関数の返り値を [10] としてlist型を返すようにしました。この状態でDialyzerを実行すると以下のようにコード上の矛盾箇所が示されます。

    /home/nao000dotcom # mix dialyzer
    Compiling 1 file (.ex)

    ~省略~

    done in 0m5.87s
    lib/nao000dotcom_web/database/blogs.ex:42: Invalid type specification for function 'Elixir.Nao000dotcomWeb.Database.Blogs':sample/1. The success typing is (_) -> [10,...]
    done (warnings were emitted)

すごかった部分

@specを指定した関数の結果を利用した別関数も解析してくれていることに感動しました。うまく表現でないのでコードで示します。

以下の関数はブログ記事をDBから取得します。引数を文字列、返り値を構造体 %Nao000dotcom.Schema.Blog{} に指定しました。

Nao000dotcomWeb.Database.Blogs モジュールに定義してあります。

    @spec fetchBlogDetail(String.t()) :: %Nao000dotcom.Schema.Blog{}
    def fetchBlogDetail(url_title) do
      Nao000dotcom.Schema.Blog |> Nao000dotcom.Repo.get_by(url_title: url_title)
    end

想定では検査が通る予定でしたが、以下のように矛盾が発見されました。「Controllerでなぜ矛盾が...?」となりました。

    done in 0m5.61s
    lib/nao000dotcom_web/controllers/user/blog_controller.ex:10: The pattern <_conn@1, 'nil', _> can never match the type <_,#{'__meta__':=_, '__struct__':='Elixir.Nao000dotcom.Schema.Blog', 'category_id':=_, 'created_at':=_, 'detail':=_, 'id':=_, 'keywords':=_, 'pv':=_, 'summary':=_, 'title':=_, 'updated_at':=_, 'url_title':=_},'nil' | [{atom(),_}] | map()>
    done (warnings were emitted)

おとなしく該当部分のコードを見る。

      defp renderDetailOrNone(conn, nil, _) do

上記の関数はブログ記事を取得できない場合の関数です。

第2引数に fetchBlogDetail の結果が入りますが、 @spec でnil指定していませんでした。そのためDialyzerが矛盾を発見してくれました。@spec を指定した部分だけ解析してくれるものだと思っていました。実際にはその先まで解析してくれており感動しました。

検査を通過するには返り値の型に nil を含める必要があります。以下のように指定します。

    @spec fetchBlogDetail(String.t()) :: %Nao000dotcom.Schema.Blog{} | nil

再び Dialyzer を実行すると無事に検査を通過します。

    done in 0m5.76s
    done (passed successfully)

個人開発でご覧の通りなので、チーム開発では十分に品質を保ってくれるツールだと思います。

参考サイト