isyumi_netブログ

isyumi_netがプログラミングのこととかを書くブログ

早期ReturnでIF文の組み合わせを見やすくしよう

IF文の中を整理する簡単な方法を紹介しよう。

こういう仕様を考えてほしい。

お客さんがある商品を購入可能か判定したい。購入可能の条件として

  • ログインしているか
  • 在庫があるか
  • 在庫がないなら再入荷可能か
  • お金は足りているか

があったとする。購入可能ならTrueを、購入不可能ならFalseとReasonを返してほしい。途中でReturnしないならこういうコードになる。

途中でReturnしないIF文

ネストが深く分岐のルートが追い辛いためリーダビリティが低い。さらに、こういうコードは変更に弱い。ある日急に「お金が足りなく、かつ在庫もなければどんなエラーが出るの? その場合お金が足りてないメッセージを優先して出してほしいんだけど」って言われたとして、対応可能だろうか。

こういうコードはとにかく順番にReturnしていこう。

どんどんReturnしていくIF文

見やすくなったであろう。このコードは読みやすいし処理が追いやすい。また仕様とコードの見た目の乖離が少ない。どんどんReturnしよう。

ちなみに、こういうIF文はガードと呼ばれる。ある条件を満たしているか確認してからやりたかった処理をするためのIF文のことだ。

RESTful APIのエンドポイントを綺麗に設計しよう

ブラウザやスマホアプリがサーバーとデータをやり取りする際にHTTPが使われることが多い。今回はそのURLの話だ。まずなんでこの話をするかを聞いてほしい。

 

あなたが参加しているシステムのサーバーとクライアントのやり取りは全部で何種類あるだろうか?

  • ユーザー登録
  • 投稿検索
  • 書き込み

等々。きっと数十種類以上はあると思う。HTTPを使う場合これら一つ一つの機能にURLを割り振らなければいけない。

ちなみにhttps://example.com/users?age=20 というURLの中でhttps://の部分をスキーマといいexample.comをホスト、/usersをパス、age=20をパラメータという。機能の割り振りに使えるのはパスの部分だ。何十個もある機能に統一感のある割り振りをしたい。そのために、どんな基準を使えばいいか。

メンタルモデルという言葉を知っているだろうか。物と物がどう作用するかについてのイメージだ。

例えば、車のウィンカーだ。ウィンカーは上に持ち上げれば左が点滅し、下に下げれば右が点滅する。あなたはどうやってこのルールを覚えているか。きっと「これからハンドルを回す向きに動かす」と覚えているはずだ。ではもし将来車が空を飛ぶようになり、上下にもウィンカーを出さなけれが行けなくなったとき、レバーをどっちに倒すべきかあなたは迷ったりしないだろう。これがメンタルモデルだ。本当はこれからハンドルをどちらに回すかとウィンカーの向きには何の関係もないのにそう考えるとわかりやすい。

プログラマーにとって良い実装をすることと同じくらい良いメンタルモデルを設計することは大事な仕事だ。URLを見れば即座にそのAPIがどんな機能を持っているのか想像がつき、将来こんな機能を増やしたいならどんなURLにするか判断に困らないようなメンタルモデルを設計するべきだ。

実践すべきことはこちらのQiitaに全部書いてある。細かいところで議論になっているが大筋この通りにすればいい。あとで読んでほしい。

 

qiita.com

では、どういうメンタルモデルでこれを考えればいいだろうか。これから作るシステムは1枚のJSONに全て管理されているというメンタルモデルで考えてほしい。そのJSONにはすべての情報が書き込まれている。またそのJSONを書き換えると世界はその通りに変化する。そこでクライアント側は望む機能を実行するにはサーバーに保存されているそのJSONの箇所を指定して読み込んだり書き込んだり削除したりすればいいとわかる。RESTful APIのパスはJSONの場所指定と一致する。

だからあなたの仕事はシステムの状態を全て表現したJSONのフォーマットを考えることだ。例えば掲示板ならこんなJSONになりそうだ。

{

  "users" : [

    {"userID": "111", "name" : "たろう"},

    {"userID": "222", "name" : "じろう"}

  ],

  "messages" : [

    {"userID" : "111", "message" : "こんにちは"},

    {"userID" : "222", "message" : "はじめまして"}

  ]

}

 

ということは投稿を全部取得するAPIは/messagesで、名前編集APIはPUT /users/rename ではなくPUT /users/111/name だとわかるだろう。

このように、システムの状態を1枚のJSONで考えれば、必然的にきれいな設計になる。

For文を縦に分割する

他人にこの話をすることはほとんどない。自転車置き場のそしりは免れ得ないとおもう。とはいえもしあなたがプログラミング初心者で、僕に何かアドバイスできることがあるならちょっと見ていってほしい。へぼプログラマーの僕があなたにあげられるものはこんなものしかない。この記事を上級者のところに持って言ったらきっとこういうだろう。「……まあ、やったほうがいいかやらないほうがいいかでいえば、51:49でやったほうがいいと思うよ……」と。

 

というわけで長いFor文を縦に分割する話をする。For文のネストが深くならないようにする話は後日する。

 

あなたがTwitterのようなものを作っているとしよう。タイムラインを取得するAPIだ。自分がフォローしているユーザーの投稿と、その人がRTした投稿を取得することにする。

 

一つのFor文で全部やる

このようなコードになった。

  • 一番外側のループでユーザーを一人ずつ見て行く
  • そのユーザーをフォローしているか判定
  • そのユーザーの投稿を抽出
  • そのユーザーがRTした投稿を抽出

例が簡単すぎたのであまりメリットが見えてこないかもしれない。スマソ。

 ここでFor文を縦に分割してみよう。処理が二つの山にまとまった。

 

二つのFor文に分ける

しかし、まだ問題がある。フォロワーかどうかを判定する式が重複している。そこでフォロワーだけを抽出した配列を作ってしまおう。

 

まずフォローしているユーザーを配列に抽出してから続きの作業をする

見やすくなったと思う。最後にここから山を一個ずつ別の関数に移していこう。

山ごとに小分けの関数にする

ちなみにFor文の中で三つの関数を呼び出してもいい。どっちでもいいが、前者のほうが要所要所で値が確定しているのでデバッガブルでいいのではと思う。あと、もっとFor文の中が複雑になってきた場合一つのForの中に複数の処理があるとcontinueやbreakに起因するバグの可能性が一個増える。強要はしません。

 

ここからさらに一歩。どうしたら最初からこういう綺麗なコードを書けるようになるか。まず、変数を用意したら定義していない関数を呼び出して値を代入する。するとIDEで警告が出る。多くのIDEには未定義関数の呼び出しから関数を生成するショートカットキーが用意されている。その後関数の中身を書いていく。

今回のコードであればこうだ。無意識に意味とまとまりとコードのまとまりが対応したコードになる。

 


IDEでコンパイルエラーを出しながら開発する

 

 

補足

多くの言語の配列には.filterや.mapのような便利な関数が用意されているので、それを使いこなせるようになるともっといいです。

 

純粋関数の割合を最大化しよう

不勉強な僕ですが、僕が知っているいくつかの小手先のテクニックをまとめておこうと思う。

誤解しないで欲しいが僕は他人にこの話をすることはほとんど無い。僕があなたの同僚になったとして、いちいち突っ込んでめんどくさい思いをさせることはない。

単にあなたの人生の一助になればという思いである。

 

というわけで第一弾は純粋関数の割合を最大化しよう、である。

純粋ではない関数には二種類ある

 

ひとつ目は関数の外側の状態に影響を受ける関数だ。

var value = 10;

int main(int mul) {

  return value * mul;

}

このmain関数は関数の外側にあるvalueという変数を読み取っている。この関数を何回も実行した場合、引数が毎回同じでもvalueの値が違えば違う結果が返る。

 

もうひとつは関数の外の変数を書き換える関数だ。 

var value = 10;

int main(int mul) {

  value = mul;

  return mul * 2; 

}

 

このmain関数は関数の外にあるvalue変数を書き換えている。

 

このように純粋ではない関数には2種類あることを見てきた。 

逆に純粋な関数とは引数が同じなら毎回同じ値を返すし、関数の外の世界に影響を及ぼさない関数だ。

ちなみに引数が同じだったら必ず同じ結果を返すことを「引数に対して決定的である」と言い回すらしい。

 

void main(int value , int mul) {

  return value * int;

}

 

こういう関数の利点として

  • 気軽に呼び出しやすい
  • テストしやすい

が挙げられる

 

気軽に呼び出しやすいとは、この関数を最初に意図した以外の目的に使う場合に影響を調査しなくていいということだ。

例えば、「画面に〇〇を計算した結果を表示してほしい」と頼まれたとしよう。

〇〇を計算する関数はすでに定義済だった。

もし、この関数が別の場所に定義された変数によって結果が変わったり、関数を実行するたびにDBをUpdateしたりするなら、そのまま使えない。

しかし、純粋関数であることが明白なら、気軽に呼び出して使うことができる。

つまり、純粋関数は便利だということだ。

 

ところが、純粋関数だけで何かの役に立つプログラムを書くことはできない。

例えば関数の外にある値として

  • DB
  • 現在時刻
  • 乱数

が挙げられる。

また、関数の外の値を書き換えることとして

  • HTTPレスポンス
  • DBに保存

などが挙げられる。

通常このような機能を一切持たないプログラムを書くことはない。

どうしてもひとつのプログラムには純粋関数とそうではない関数が交じり合うことになる。

 

そこで、純粋関数の割合を最大化することを目指すべきだ。

下のGistをご覧頂きたい。

 

純粋関数の割合を最大化

 

originalとgoodは結果的に同じことが起こるコードだ。

method1からmethod2を呼び出し、更にmethod3を呼び出している。間に挟まっているhoge関数は適当ななんか難しい処理だと思っていたきたい。

 

originalの方ではmethod3で現在時刻を取得し、DBへ値の書き込みをしている。

 

method3は純粋関数ではない。そして純粋ではない関数を呼んでいるmethod2とmethod1も自動で純粋関数でなくなる。これまでのところ純粋:非純粋の比率は0:3である。

 

しかし、下のgoodの方では、

  • 現在時刻の取得とDB書き込みを一番浅い場所に持ってきた
  • 深い場所にあるメソッドは自分で現在時刻を取得するのでなく引数で受け取っている
  • 深い場所にあるメソッドは自分でDBに保存するのでなく保存する値を戻り値で帰す

という書き換えを行った。結果、method2とmethod3は純粋関数にすることができた。

 

method1を見捨ててmethod2とmethod3を救うことができた。

純粋:非純粋の比率は2:1になり大逆転勝利を収めることができた。

 

 

結論

純粋関数の割合が最大化するように書き換えよう。

 

 注釈

用語の正確な定義について自身がなかった。

もともと僕は

関数の外部の変数を書き換えること:副作用

関数の外部の変数に依存すること:参照透過性がない

副作用がなく、かつ参照透過な関数:純粋関数

と思っていた。

しかし、Wikipediaのこの記述を見るとどうもそうじゃないらしい。

副作用 (プログラム) - Wikipedia

同じ条件なら必ず同じ結果を返す、かつ、外に影響を及ぼさないことを参照透過と呼ぶということかな。

参照透過な処理は副作用から独立しているというのは、「他の処理に副作用を及ぼさないし他の処理の副作用に影響を受けないよ」っていう意味か。

ちょっと良くわからなかった。

じゃあ他の処理に影響を与えるけど引数に対して決定的な処理はなんて呼ぶのだ。

逆に他の処理の影響を受けるけど他の処理に影響を与えない処理はなんて呼ぶのだ。

まさかりは怖いけど枕投げくらいならWanted。

 

はてなブログに移行することにした。

ブログを移行した。

今日まで、手作りブログエンジンをFirebase上においていた。Cloud FunctionsでReactをSSRしていた。しかし、Cloud Functionsがあまりにも遅い。そこで脱出することにした。

別の側面もある。手作りブログエンジンでブログを書くと、ブログのネタを思いつくペースの3倍でブログエンジンの機能追加案が出てくる。そのうち、「いつこの素晴らしいアイディアを発表するべきだろうか。CMSに〇〇機能を追加した後でなければいけない!」というアンビバレントな愛が芽生えだす。これは不健全だ。

どうせならSaaSに移行してしまえということでこっちに移ってきた。

古記事の移行はいつかやっておく。

はてな村の皆さん、どうぞよろしくお願いします。