10
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ElixirAdvent Calendar 2023

Day 18

【Elixir アンチパターン】 戻り値の型が変化する関数

Last updated at Posted at 2023-12-17

はじめに

Elixirのドキュメントにanti patternsがあり、結構いい資料だと思います。

その中の一つalternative-return-typesを読み解いてみます

戻り値の型が変化する関数

ああ~。いかにもダメそうな関数。
でも、例をみてみると、やってしまいそうなデザイン。

アンチパターンのコード例で使われてる、Integer.parse()の動作を復習しておきます。

iex(18)> Integer.parse("123")
{123, ""}
iex(19)> Integer.parse("123a")
{123, "a"}
iex(20)> Integer.parse("b123a") 
:error
iex(21)> Integer.parse("b123") 
:error
iex(22)> 

数字の後に文字がある場合、その文字も取得できるという関数です。

アンチパターンのコードは以下の通り。

defmodule AlternativeInteger do
  @spec parse(String.t(), keyword()) :: integer() | {integer(), String.t()} | :error
  def parse(string, options \\ []) when is_list(options) do
    if Keyword.get(options, :discard_rest, false) do
      Integer.parse(string)
    else
      case Integer.parse(string) do
        {int, _rest} -> int
        :error -> :error
      end
    end
  end
end

文字列をパースして値を取得する関数です
discard_restがtrueの場合は、整数値のみ。残りの文字列は返さない。
discard_restがfalseだったり指定されてない場合は、Integer.parse()の戻り値をそのまま返す。残りの文字も取得できる。

こんな関数作ってしまいそうです。

何が良くないのか?

まず、返り値の型が一定でないので、扱いずらいです。
この関数は次の二つの機能をもっています。

  • 文字列を整数に変換して、整数と、残りの文字を返す
  • 整数か、{整数,残りの文字}どちらにするか選択する

一つの関数に複数の機能を持たせている事も、関数を使う人に余計な知識を要求する事になります。

確か、達人プログラマーにflagの使用は良くないとかあったような気がします。
この観点からもアンチパターンですね。

どうするのが良いか

二つの関数に分けます。
パースして整数を得る
パースして、{整数,残りの文字}を得る
だったら、それぞれ一つの機能と言えます。

defmodule AlternativeInteger do
  @spec parse(String.t()) :: {integer(), String.t()} | :error
  def parse(string) do
    Integer.parse(string)
  end

  @spec parse_discard_rest(String.t()) :: integer() | :error
  def parse_discard_rest(string) do
    case Integer.parse(string) do
      {int, _rest} -> int
      :error -> :error
    end
  end
end

parse_discard_restは、Integer.parseとcase文の複数の処理をしているじゃないか!という突っ込みがあるかもしれませんが、ここで問題にしている複数の機能とは、そいう事ではありません。
一つの機能を実現するために複数の処理を組み合わせる事は、必要な事で問題ではありません。
parse_discard_restは文字列を与えると、parse_discard_restが決めた形式の値を返すという単機能の関数になっています。

別の解決方法

この例は、parseの内容が単純なので、二種類のparse関数を作るので十分だと思いますが、こんな関数を用意する方法もあると思いました。

  def discard_rest(parsed_value) do
    case parsed_value do
      {int, _rest} -> int
      :error -> :error
    end
  end
10
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?