友人が作成していた https://github.com/skirino/croma に、 Mix.env の値によってコンパイル時に def として解釈するか、 defp として解釈するマクロがあります。
その使われ方が面白かったのでメモ。
その箇所だけ切り出して、参照値を環境変数のMIX_ENVにしたものが以下の Functions モジュールです。それをimportする Defpt モジュール、利用する Alexa と Alexaを呼び出す Me 。この Alexa で定義している defpt が今回の対象です。
これを見ると、 Me の中の Alexa.hello が、MIX_ENV == “test” の時はアクセス可能だけれど、それ以外ではundefined function errorになることがわかります。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| defmodule Functions do | |
| # switch using `def` or `defp` in compile time | |
| defmacro defpt(func, body \\ nil) do | |
| case test_env? do | |
| true -> quote do: def(unquote(func), unquote(body)) | |
| false -> quote do: defp(unquote(func), unquote(body)) | |
| end | |
| end | |
| defp test_env? do | |
| case System.get_env "MIX_ENV" do | |
| "test" -> true | |
| _ -> false | |
| end | |
| end | |
| end | |
| defmodule Defpt do | |
| defmacro __using__(_) do | |
| quote do | |
| import Functions | |
| end | |
| end | |
| end | |
| defmodule Alexa do | |
| use Defpt | |
| # use defpt which defines as macro | |
| defpt hello do | |
| "hi" | |
| end | |
| end | |
| # Failed call Alexa.hello if MIX_ENV is not "test" | |
| # Anyone can't find Alexa.hello is used or unused by warning in compile time | |
| # or should cover coverage. | |
| # iex> Me.inu | |
| # ** (UndefinedFunctionError) undefined function Alexa.hello/0 | |
| # Alexa.hello() | |
| # neko.exs:37: Me.inu/0 | |
| defmodule Me do | |
| def inu do | |
| IO.puts Alexa.hello | |
| end | |
| end |
ここで発生する可能性のある不具合としては、意図せず外部から defpt にアクセスしようとしてしまう、ということがありそうです。例えば、coverageが高いけれどテストコード実行時は def として扱われるので正常に動作する。ただ、それ以外だと defp として扱われるのでエラーに成る。
Elixirはコンパイルされるので、コンパイル時にdefpへ外部モジュールからアクセスしようとしたらwarningが出ます。そこで気付くことができるし、warningが出たらコンパイル失敗にもすることができるので、検出は可能。見逃しなどしなければ。
そう考えると defpt による定義間違いによるエラーは防げそうですね。
じゃぁ、 defpt 自体の有用さは置いておいて、隠蔽しようとしている関数をテストコードでテストするのか、です。これはprivateなメソッドをテストするか、という時折見る議論と重なるところがあります。それは開発途上、開発完了などの段階によって変わると思います。が、私は隠蔽しようとしているところ(外部モジュールから入出力で見ることができないところ)は無理にハックしない側の人なのでそこまでは不要かな、と感じます。
一方で、細かくメソッドを分割してそれらの小さな部品をチェックしつつも、それらを統合した時はその小さすぎるメソッド群は隠蔽したいということもあると思っています。(そして、それは理解出る…私も不安なときよくやる) そういう不安に立ち向かうときは、こういうprivateな関数をチェックすることは有用かなと。ただ、その場合は関数単位での小さなチェック以上の必要最低限のテスト、という形とは異なってはきますね。