前日16日は清流Elixirのオーガナイザー @takamizawa46 の「Enum.reduceのデバッグにEnum.scanが便利」でした!
今日は私がだいたい毎週木曜に開催しているオカザキリンビーム1でElixirを学び感じた「プログラミングにおける線形性」(のようなもの)を言語化してみたいと思います。(オカザキリンビームについては本記事の最後にちょろっと宣伝します
それでは本題に入ります。
「単機能に作る」って言うけどなんで?
本を読んだり、記事を読んだりしてると「単機能に作る」ということが書かれていますが、「単機能に作る」は手段であって、目的ではありません。なんかそうすると良さげだけど実際のところなんででしょうか?
私の理解では、
作ろうとしているものを「足し引き」できるようにする
ためです。
例えばEnum
これは話し尽くされていますが、
1..10
|> Enum.map(&(&1 * 2))
# |> Enum.filter(&(rem(&1, 4) == 0)) # コメントアウトを解除すれば機能を足せる
|> Enum.reverse() # コメントアウトをすれば機能を引ける
enumerableな値に対して、Enumモジュールが提供する関数はその操作を「足し引き」することができます。
enumerableな値を操作するオレオレ関数を作る場合、
oreore_fun =
fn enumerable ->
enumerable
|> Enum.filter(&(rem(&1, 4) == 0)) # 単機能な関数を
|> Enum.reverse() # 「足し」合わせて別の機能を作る、機能を削りたいときは「引く」
end
1..10
|> Enum.map(&(&1 * 2))
|> oreore_fun.()
のように自身(信)の関数の機能を「足し引き」することができます。
その「足し引き」を可能とするのに必要な性質が単機能であることなのかなと思っています。
shellでは単機能なコマンドを組み合わせることで以下のようなことができます。(普通はワンライナーかも?
#!/bin/sh
# TCPリッスンソケット -> 一行目削除 -> ポート抽出 -> ソート -> ユニーク
ss -ltn \
| sed -e '1d' \
| awk '{printf $4"\n"}' \
| sed -e 's/\:\:\://' \
| sed -e 's/\*\://' \
| sort \
| uniq
PhoenixのContextモジュールでも
def get_user!(id)
Repo.get!(User, id)
end
def get_user_with_address!(id) do
get_user!(id)
|> Repo.preload(:address) # addressをロードする機能を「足す」
end
既存のget_user!/1
をいじらず(単機能を維持し)、機能を足した別の関数を作成します。
(既存のテストを修正する必要もありません。
機能を削りたいときは、作成した関数を削除してやればよいですね。
aliasでも
defmodule OssuOreModule do
alias Accounts.Users.{User, Guest}
# 上と下、「足し引き」に向いている書き方はどっちか?
alias Accounts.Users.User
alias Accounts.Users.Guest
...
end
Elixirを初めて少しするとaliasの書き方に「上」があることを知り「なるほど!」ということで使い始めましたが、途中でこれはいまいちと気づきました。
理由は、Guestを使用しなくなり削除する際のコミットが扱いにくくなるからです。
- alias Accounts.Users.{User, Guest}
+ alias Accounts.Users.User
alias Accounts.Users.User
- alias Accounts.Users.Guest
どちらも使用しなくなったGuestを外すという一つのことを達成しようとしていますが、どちらが見やすく分かりやすいでしょうか?
このことから、私は一つのパラグラフ(今回の場合は一行)で表現したいことが、多少冗長であっても独立していることが大事だと考えるようになりました。
(なので、一度限りの実行して捨て置くワンライナーでないshellのスクリプトなら、先に書いたようにしています。
gitのブランチやコミットでも
ある機能を作ろうと切ったブランチで作ったコミットが
- リバートできる粒度になっているか(引けるか)
という観点で見直すとどうでしょうか?
また、そのブランチで作った機能がマージされる時にsquashマージされたら、そのコミットはリバートできるでしょうか?
「引く」ことができると、「あれ、仕様間違ってたわ」というときに気軽に引いてなかったことにできます。
DB設計でも
私はDB設計は学習中の身ですが、
- 外部キー制約を使わない
- 交差テーブルを使う
これらもテーブル間の依存関係をなくし、「足し引き」しやすくするためだと思っています。
まとめ
プログラミングにおける線形性(?)のようなものは伝わりましたでしょうか?
スパゲッティコードとはよく言ったもので、絡まっていると「足し引き」できないですよね。
プログラミングに関わらず作っているものが「足し引き」できるようになっているか考えてみると、いろいろと見えてくるものがあるかもしれません。
以下のツイートを頑張って言語化してみた記事でした(伝われ〜)
最後にちょろっとOkazaKirin.beamの宣伝
「OkazaKirin.beam」は私が主催している
- だいたいElixirについて勉強する
- だいたい毎週木曜日に開催する
リモートもくもく会です。
(「だいたい」というのは、Elixir以外を勉強していることも有りますし、忙しかったり忘れたりでやらない週もあるからです)
2020/01/08に初めたので、はじめてから丸二年が経ちました!
本会はリモートもくもく会で、私が木曜日に開催宣言をし、都合が合う人がslackで開始時間("だいたい"19:00)になると個々が今日取り組みたいことをつぶやいたりつぶやかなかったりし、もくもくと学習をしています。なので、毎週木曜に誰にも宣言せずに家で一人で勉強していることと何が違うのか?と問われると答えに窮するものがあります。
ただ、参加しているメンバーがどんなものを読み学んでいるのかを知れるのはちょっと楽しいし、リモートなんだけれど同じ時間を共有し学んでいる感じは図書館でもくもくしているような雰囲気もあるようなないようなで私は好きです。
興味がある人はオープンなので遊びに来てください。図書館という感じです笑
また、偶然にも本日17日は「EDI#10:15箇所のElixirコミュニティ大集合で忘年会+開催50回突破4コミュニティお祝い」がありますので、ご興味お時間有りましたら気軽にご参加ください🎉
明日は @Yoosuke の「LiveViewを使って簡単にステートフルなタイピングゲームアプリを作ろう! 後編」です。お楽しみに!
-
サッポロビームをリスペクトし、それをもじって岡崎とキリンビールまぜてつくった説明不要な、、名前です! ↩