user
Shun Namiki

Freelance full-stack Endigneer @ Shibuya, Japan

Published on Dec-3rd, 2018 ( 2 min read )

【Elixir】ifやcaseの中で代入・束縛を行うべきでない

退職エントリでは書いてないがElixirを1年くらい業務で使っていた。

そのときにPR指摘で「if/caseなどのブロック内で束縛をするでない!」と、かなりの回数を行ったし、自分も最初はよくやっていたしこともあるので、Elixir初心者あるあるだと思う誤りなので、これから学ぶ人は注意すると良いと思う。

バッドパターン:if/caseの中で束縛を行ってしまう

# バッドパターン
if is_nil(user) do
  user_money = get_money(user) # ①Bad
  user_gacha = get_gacha(user) # ②Bad
  user_tax = calc(user_money)  # ③Bad
else
  user_money = 0
  user_gacha = nil  
end

この書き方はバッドパターンだ。おそらくJavaScriptとかに慣れ親しんでいると自然にこういう書き方になってしまうのではないかな?と思う。

Elixirらしい書き方はこう書く

# グッドパターン(その1) ( 上のバッドパターンと同じ内容 )
{ user_money, user_gacha } =
  if is_nil(user) do
	{ get_money(user), get_gacha(user) } # ①②Good
  else
  	{ 0, nil }
  end

user_tax = calc(user_money) # ③Good
  • バッドパターンの①②として、if-elseの中で束縛を行ってしまっている。そうすると、else-endの中にも同様の束縛を行う行が必要になってしまう。①②のグッドパターンのように、if-endブロックの返り値として値を返そう。その際に使うのはアトムの形式で値を返す・受け取るのが通例だ。
  • バッドパターンの③として、user_taxについての処理を行っている場所が悪い。この書き方だと、elseブロック内では束縛しないがifブロック内でのみ束縛する変数が生まれてしまう。こうなると後続処理にて未定義変数へのアクセスが可能になる余地が大きく発生しバグの温床なので、if-else-endの外に出せるなら出すと良い。

さて、この間違いの表層的なところは「if / caseなどの中で束縛を行ってしまう」だろう。だが、そもそも、このような書き方になってしまわないように根本的に思想を変えるほうが良い。その考えとして、if-else-endブロックは1つの関数だとみなそう。実際にコードとして書く場合は下記の通りとなる。

# グッドパターン(その2) ( 上と同じ内容 )
{ user_money, user_gacha } = get_data(user) # ①②Good
user_tax = calc(user_money) # ③Good

...
def get_data(user) when is_nil(user), do: {0, nil}
def get_data(user) do: {get_money(user), get_gacha(user)}

これでget_dataという関数になりすごくスッキリとなった!!

ただし、この思想をベースにすべてのif / case などを関数化するのは「過剰な関数化」を行ってしまっている印象だ。この思想が絶対的な是なら最終的にif/ case は容認されない、ということになってしまう。あくまで、「グッドパターン(その2)」はコードリファクタリングされた最終的なゴールの一つとして、「グッドパターン(その1)」の状態に留めておくことも可能なのがElixirの良さだと思う。

つまり、

  • 「グッドパターン(その1)」のように、if-endブロックのままにするか?
  • 「グッドパターン(その2)」のように、すべて関数化するか?

の選択は

  • 処理を簡潔にしたいか?
  • 処理を部品化させたいか?
  • 処理に名称をつけたいか?(関数名で表現できる)

などのその時々の背景を元に書くと良いと思う。

結論

  • 「関数化しつくした最終形態をイメージできること」
  • 「その上で、あえて関数化しないという選択肢も持つこと」

の2点だと思う。ただ、最初の頃は「全部関数化するんじゃぁー」くらいのスタンスの方が関数型的案思考になるのでおすすめ。

おまけ:Elixirでifを使うべきか使わないべきか?

よくElixirを書いていると「if文はやめてcaseのみにすべき」や「すべて関数にすべき」的な発言を目にした。

「すべて関数にすべき」か?の答えはこの記事に書いてあるとおりでだが、「if文はやめてcaseのみにすべき」は自分は否定的だ。

ifの場合はtrue/falseの2択だが、caseの場合は複数選択肢の場合やガード節でなにかをしたい時にのみ書くべきだと思う。

case user.is_admin do
  true -> # 処理1
  _ -> # 処理2
end

# これだけの記述なら、ifで十分書けるしコードリーディング的にも、上よりも下の方が良い
if user.is_admin do
  # 処理1
else
  # 処理2
end

ifで書かれている場合はその時点で「選択肢は2つなんだな」ととなるが、caseが出てくると「どういうパターンがそもそもあるんだ?」と思考の選択余地が増えてしまい、コードリーディング的にあまりエンジニアに優しくないように思う。また、柔軟にcaseの値を取れる分、バグの温床になりがちなので、True/falseのみに出来るならifにすると良いと思う。

arrow_back

Previous

【Elixir】Enumを完全に理解する | 効率的なEnumの学習方法・暗黙のルール・意識すべきこと

Next

ReactでQiitaのアプリケーションを作った話
arrow_forward