Elixirでテストファイルとの行き来を楽にする
はじめに
- 今回はtips的な記事です
背景
- Elixirを書いているときにテストファイルと実行ファイルの行き来に困っていました
- JavaとかKotlinを書いていたときはIntelliJ IDEAがいい感じにやってくれていた分、Elixirはエディタがなんでも良い分そこら辺を拡張で頑張る必要があるなと思い探していました
これと出会う
- 求めていた感じの拡張
- READMEを見ただけでこれだと思い早速使ってみます
使ってみる
- VSCodeのPluginからインストールする
- 検索窓から
alternate file
で検索すると引っかかるので、それをインストールすれば終了(必要に応じてVSCodeの再起動をしてください) - その後、プロジェクトのルートディレクトリに
.projections.json
を作成します(下記な感じで検索→作成とすれば雛形は作ってくれます)
-中身はこんな感じ
{ "apps/**/lib/**/*.ex": { "alternate": "apps/{dirname}/test/{dirname}/{basename}_test.exs" }, "apps/**/lib/*.ex": { "alternate": "apps/{dirname}/test/{basename}_test.exs" } }
- ここでアプリケーションコードとテストコードの対応性を書いておけば、拡張側で対応してると認識してくれるので、移動が可能になります。
- ここのディレクトリ構造は一定自由に設定できるのですが、一定ルールを決めておいた方が管理は楽になると思います。
(例えば、テストコード側は
/test
以下を全部フラットにしてるのに、アプリケーション側はlib
以下にディレクトリを切ると、ここのjsonで対応性を頑張らないといけなくなります。そんなことをするくらいならディレクトリ構造を見直してしまって、test
以下とlib
以下のディレクトリ構造は一致させるようにするのがいいのかなと思いました)
拡張でできること
- 以下READMEから抜粋ですが、非常にシンプルでファイルの切り替えと作成。それを画面分割して開くか開かないか。それだけです。
Alternate File - Switch to the alternate file (if found) in the current pane Alternate File in Split - Switch to the alternate file (if found) in a split pane Create Alternate File - Switch to the alternate file (if found) in the current pane. If not found, create the file. Create Alternate File in Split - Switch to the alternate file (if found) in a split pane. If not found, create the file.
- 上記のアクションは拡張がインストールされれば、下記の感じでサジェストされるようになります
- あとは、ショートカットの設定しておけば、シームレスに移動ができるようになります。
- 個人的には、Alternatte fileとCreate Alternate Fileを同じショートカットに設定しておくと、ファイルがあれば移動できるしなければ作成するというような挙動をしてくれるので非常におすすめです。
Elixirでいい感じに並行処理を行う
はじめに
- 今回は表題の通りです
- よくあるケースでAPIコールを並列で行いたい時に、Elixirだったらどうするのかというのを残しておきたいと思います
実践
- まず、公式を参照する
Task.async/ 1
という関数で非同期的に処理を呼び出せて、Task.await /2
という関数でその処理の終了を待つというもの- 至ってシンプルで、他の言語でもあるようなasync/awaitの組み合わせです
- ということで実際に動かしてみる
別関数を呼び出して、printするだけというもの。 Task.async()の引数はfunctionを求められるので、そこだけ要注意。 あとは、awaitしてあげれば良いだけという。
defmodule Sample do def hello(arg) do task = Task.async(fn -> print_str(arg) end) Task.await(task) end def print_str(str) do IO.puts(str) str end end
テストも特に特殊なことはしなくて良い。
defmodule SampleTest do use ExUnit.Case test "call hello" do assert Sample.hello("hoge") == "hoge" end end
- 実際に並列処理を行う場合、単一の呼び出しは考えられないので、複数の呼び出しをしてみる
defmodule Sample do def hello(arg) do task = Task.async(fn -> print_str(arg) end) task2 = Task.async(fn -> print_num(1) end) Task.await(task2) Task.await(task) end def print_str(str) do IO.puts(str) str end def print_num(num) do IO.puts(num) num end end
処理自体に意味はなく、複数の呼び出しをしてみた。 先述のテストを通すために、awaitをかける順番に意図を持たせてしまったが、基本的に呼び出しが増えてもやるべきことは一緒。
- エラーハンドリングを追加していく
- まずはテストケースから
2つの引数を渡して、数字は偶数、文字列は7文字より小さければ成功する処理を書く
defmodule SampleTest do use ExUnit.Case test "call hello success" do assert Sample.hello("hogeho", 2) == {:ok, "hoge", 2} end test "call hello fail of string length is longer than 6" do assert Sample.hello("hogehog", 2) == :error end test "call hello fail of number is not even" do assert Sample.hello("hoge", 1) == :error end end
defmodule Sample do def hello(arg1, arg2) do task = Task.async(fn -> print_str(arg1) end) task2 = Task.async(fn -> print_even(arg2) end) handle(Task.await(task), Task.await(task2)) end defp handle({:ok, result}, {:ok, result2}), do: {:ok, result, result2} defp handle(_, _), do: :error def print_str(str) do if(str |> String.length() < 7) do {:ok, str} else :error end end def print_even(num) do if num |> rem(2) == 0 do {:ok, num} else :error end end end
上記のような感じになる。 handlingする用に同一名で引数違いのfunctionを定義しておいて、そこでパターンマッチさせる。 そうすることで、割と楽に例外処理を行うことができる。
- では、3件以上の呼び出しをどうするか。 現状は2回の呼び出しを行うだけなのでTask.async()を呼び出すのも2回で済むが、これが比例して増えるのはアプリケーションコードとしてはあまりイケてないのでそこら辺をいい感じにしていく
defmodule SampleTest do use ExUnit.Case test "call hello success" do assert Sample.hello("hoge", 2) == {:ok, {"hoge", 2}} end test "call hello fail of str" do assert Sample.hello("hogehoge", 2) == :error end test "call hello fail of num" do assert Sample.hello("hoge", 1) == :error end end
そのために、まずはテストの修正。 正常系のレスポンスのタプルの形が扱いにくかったので修正をしておく。
defmodule Sample do def hello(arg1, arg2) do [fn -> print_str(arg1) end, fn -> print_even(arg2) end] |> Enum.map(&Task.async(&1)) |> Enum.reduce_while({:ok, {}}, fn task, {:ok, response} -> case Task.await(task) do {:ok, res} -> {:cont, {:ok, Tuple.append(response, res)}} :error -> {:halt, :error} end end) end def print_str(str) do if(str |> String.length() < 7) do {:ok, str} else :error end end def print_even(num) do if num |> rem(2) == 0 do {:ok, num} else :error end end end
そして、実装の修正。 呼び出しのところを、ListにいれてあとはMapで処理を進める。そして、reduceでくっつけてしまうというだけの処理。 思ったよりシンプルになった。
まとめ
- 今回は簡単に並列処理を段階的に書き換えて行って、少しは良い書き方ができたのではないかと思う。
- あと、Elixirのパターンマッチは非常に協力だなとますます感じた。
プロダクションコードででElixirを採用した話
はじめに
- 今回は表題の通りなんでElixirをプロダクションコードとして採用した話をします
- もともと、プロダクトチームではElixirの採用実績はあったが、BFFとかではなく定期実行などで叩かれるAPIなどに止まっていました
背景
- 弊社Uzabaseでは下記のようなプロダクトを作っていて、プロジェクトごとに技術選定をエンジニアが自由に行っています
- そんなこともあって採用実績のある言語はこんな感じになっているのですが、
Kotlin, Clojure, Dart, Rust, Elixir, Scala, Go, Java 8, TypeScript, JavaScript, Python
割と、BFFは動的型付け言語、APIは静的型付け言語みたいな感じで言語選定を行っていて、 Kotlin - PythonとかGo - Clojureみたいな組み合わせが多いです。
今回
BFFでElixir を採用した理由
- ここについては、BFFは動的型付け言語を選択するという考えのもと(BFFの責務的にBFFで静的型付け言語を採用するメリットがあまりない)3つの言語(Clojure, Python, Elixir)を候補にしました
- その中でElixirを採用した理由は、ClojureはBFFとして採用実績があり、チーム内にも書ける人間が多くあまりチャレンジ要素がなかった(技術選定の際にチャレンジ要素を求める文化が前提としてあります)
- PythonもBFFとして採用実績があり、書いたことがある人がいるということからElixirの採用がすんなり決まりました
- また、後付けの理由にはなりますが、並行処理や軽量なプロセスによる処理で負荷耐性が期待できるなど、今回僕たちのプロジェクトが作るケースにマッチするような言語特性を有していたので、そのような観点からElixirを採用しました
APIで採用した理由
- BFFでElixirを採用することは特に問題なかったのですが、一方でBFFのみでしか使わないことには別の問題が生じます
- それは、言語の特徴を生かした実装をする機会があまりないということです。
- 具体的には、Elixirではパターンマッチやパイプライン演算子など有名なものがいくつかありますが、BFFに限ると出番が少ないというのが実情です。
- というのも、BFFはデータを受け流すだけという責務に留まることが多く、パターンマッチも下記のようなエラーハンドリングをする程度で終わってしまいます(パイプライン演算子なんてほとんど出番がない)
def hogeGateway do case fugaDriver.get do {:ok, response} -> response :error -> :error end end
- また、データの複雑な処理は基本的にAPIで行うこと、BFFは実装が終わるとあまり手が入ることが少ないこと、などを考えるとAPIでもElixirを採用した方がElixirのことをもっと知ることができるのではないかという思いからElixirをAPIでも採用することになりました。
実装するまでにやったこと
- 基本的にElixir Schoolを1周して、あとはPhoenixでAPIを何本か実装してみたという感じです
- エディタはVS Codeを使ってみて、ElixirのPluginが結構用意されてたので実装に困ることはありませんでした
技術スタック
- Elixir周辺でしようしたものを書き残します
- Phoenix(ElixirのWebフレームワーク)。Elixirでフレームワークはこれ1強らしいのでこいつを採用しました。ただ、フロントエンドは別のマイクロサービスで実装したので、実際に必要だったのはcowboyだけでした
- mixプロジェクト(buildツール的なもの)これを使うことで複数の.ex ファイルを1つのディレクトリに束ねて、コンパイル・実行することが可能になる
- umbrellaプロジェクト(mixプロジェクトを束ねるパッケージマネージャーのようなもの)ディレクトリ構造を管理するのに非常に役立つ。依存関係を制限することが可能なので、依存性逆転などを制御することも可能になる
- mox(elixirのモックライブラリ)弊社では非常に厳密にTDDを行っているので、これを結構使いました
やってみた感想
- Elixirはとっつきやすい(私はJava, Kotlinなど静的型付け言語を書いてきた期間がエンジニア人生の大半を占めており、パラダイムの違う言語を書くのはほぼ初めてでしたが、かなりわくわくした。特にパターンマッチ)
- 学習コストはそこまで高くない。Clojureと比べたときに個人的にはElixirの方が書きやすいなという印象を持ちました
- Elixir Schoolが結構充実している
- 最初、docker imageを作ってデプロイするときにハマった(これは単純に公式ドキュメントをきちんと読んでなかっただけでした)
- 静的型づけ言語から来るとクラスが恋しくなる。これは、個人的に思ったのですが、DDDとかやるときにdefstructで独自のstructを作れるけど、structであってクラスではないなという当たり前の感想を持ちました。ドメインクラス擬にプロパティを追加した際に、テストコードが型が違うとJavaとかKotlinとかだと怒ってくれますが、Elixirはそこまで怒ってくれないので、動的型付け言語も一長一短だなという感想を持ちました。
- とはいえ、パターンマッチとかパイプライン演算子とかイケてるなという感じの実装もあるので多くあるので、決してネガティブな印象を持ったわけではないです
最後に
- 簡単ではありますが、僕たちのチームがプロダクションコードでElixirを採用した背景から感想までを書いてみました。
- 個人的に関数型言語の入りとしてはとても良い言語だなと思いましたし、書いてみたいと思ってる人とか新しい言語を身に付けたいと思っている人は書いてみる価値があるのではないかと思いました。
- 次回は、Kotlinとか他の言語と比較しようかなと思っています。
リーンソフトウェア開発を読んだのでまとめておく
はじめに
- 今回は表題の通りです
- すこし古い本ではありますが、下記の本を読んでのでさらっとまとめていきます
リーンの7大原則
リーンには7つの原則があります(以下列挙)
- ムダを排除する
- 学習効果を高める
- 決定をできるだけ遅らせる
- できるだけ早く提供する
- チームに権限を与える
- 統一性を作り込む
- 全体を見る
これらについて簡単に要約と関係性をまとめておく
ムダを排除する
- リーンではムダを最大の問題としている
- ムダというのは「すぐに必要になる機能以上のものを実装」したり、 「いろんなところに資料や情報が散らかってる状態」であったり、「作業をいろんな人に引継ぎまくる状態」など。
- 本書ではソフトウェア開発における7つのムダを掲げている(今の時代に完全に当てはまるのかはわからないが)
- 未完成の作業
- 余分なプロセス(ここでいうプロセスというのは承認プロセスみたいなもの)
- 余分な機能
- タスク切り替え(1人を複数のプロジェクトにアサインするなという話)
- 待ち
- 移動
- 欠陥 (欠陥のムダの量の算出については、欠陥の影響×気づかずに過ごした時間である)
- これらに関して、まずは認識することから始める必要がある
学習効果を高める
- ソフトウェアの開発での品質とは「認知統一性」と「コンセプト統一性」の両方に帰結する
- 前者は、UX、信頼性、経済性のバランスが保たれていること
- 後者は、システムのコンセプトが全て合わさっていて、スムーズな統一体として機能すること
- 上記のような品質を満たすためには、「可変性」を意識し、より正しいモノへと学習サイクルを回していく必要がある(顧客によって満足できる品質は異なり、それを認識し改善するためにフィードバックループが必要)
- そして、学習サイクルをただ回すだけではあまり意味がなく、できるだけ効果的に回し続ける必要がある。
- そのためには、「短い学習のサイクルを回し続ける」必要がある
決定をできるだけ遅らせる
- 遅い意思決定を許容する開発手法は、不確定要素の多い場合における開発では非常に有効的である
- 重要な意思決定を不確実性の高い状態で下そうとすると、推測ベースで議論が進み正しい意思決定ができなくなってしまう
- そこでオプションベースでの開発が生きてくる
- 金融や商品取引において使われる「オプション」という概念をソフトウェア開発でも取り入れることで、不確実性を解消するためにコストや時間を支払って、その引き換えにリスクを低減する
- そして、意思決定を遅らせることで利益を最大化にする
- 最終的には不確実な要素が、良い方向にいった場合のみのオプションを考えておけばよい
- すなわち、重要な意思決定を不確実性の高い状態で下し、いずれ変更を強いられるよりも、変更を許容するようなソフトウェアにしておいて、確度が高まった時点で意思決定をし必要なオプションを行使した方が、変更は少なくて済む(モジュール・インターフェース・抽象などがここでは生きてくる)
- 要するに、オプションを使うことで、推測ではなく事実に基づいた意思決定が可能になる
できるだけ早く提供する
- これは、「ムダを排除する」や「学習効果を高める」というプラクティスと関連が高い
- 今ユーザーが必要としている機能をできるだけ早く提供する
- そして、提供した機能に対してフィードバックサイクルを回し学習効果を高める
- それを実現するためには、「ムダの排除」が必要になる
- 例えば、「待ち行列理論」を少し意識してみるといい
- 具体的にはサイクルタイムの短縮化が有効
- 到着の標準化:大量の作業の到着を待つのではなく、小さく作業することで待ち行列を短くできる
- サービス時間の標準化:処理時間を平準化すること。小さな作業にしていくことで、くるいが少なくなる。
- あとは、ゆとりを持たせることも有効である
チームに権限を与える
- ソフトウェア開発の成功にはやる気のある訓練された人材が不可欠
- また、何か大きなことを成し遂げるためには、人々がグループになって結束し合う必要がある
- これらのためには、マネージャーによる計画的な戦略よりも、個々の従業員の自主性と創造性によって発展し続ける組織が必要(自己組織的なチームの必要性)
- 自己組織的なチームにするためには、魅力的でかつ達成可能な目的がある必要がある。
- そして、その目標達成のために、全勢力を傾けるのなら、目標達成に必要なリソースを入手する権限をチームが持つ必要がある
- 基本的には「内在するモチベーション」は「自己裁量」と「目的意識」によって喚起されるが、敵対的な環境では育まれない
- 所属意識
- 安全(心理的安全性)
- 能力(自分にはいい仕事ができる能力があると信じる必要がある)
- 進歩
統一性を作り込む
- 製品の統一性には外面的な統一性と内面的な統一性がある
- それを「認知統一性」と「コンセプト統一性」と呼び替えている
- 「認知統一性」はシステムに関する顧客に全てが影響する、すなわち「マインドシェア」とほとんど同義である
- 「コンセプト統一性」は「認知統一性」の必要条件である
- 「コンセプト統一性」はシステムの中心にあるコンセプトによって全ての機能スムーズにまとまっていること
- 「認知統一性」の実現のためには「モデル駆動設計」が非常に有効である
- ドキュメント引き継ぎの繰り返しをするのではなく、システムの統一性を判断できる人と直接連絡でき、お互いの共通言語でやりとりすることが統一性を作り込む最も有効な方法であるから
- 「コンセプト統一性」を作り込むためには、ソフトウェアアーキテクチャの理解が不可欠である
- そして、健全なアーキテクチャを維持するためには、定期的なリファクタリングが必要である
- 単純さ(単純かつ機能的な設計になっているか)
- 明確さ(プログラムに携わる全ての人がそのコードを理解できるか)
- 利用への適合(意図した目的を達成する設計になっているか)
- 繰り返しのないこと
- 余分な機能がないこと
全体をみる
- システム思考について
- 「システム」とは相互依存し相互作用する部品群がある目的を果たすために集まったもの
- 「システム思考」では組織をシステムとみなす。組織がどのように関連しあい、組織全体が時間と共にどのようにふるまうか分析をする
- 組織の行動をコンピュータシミュレーションにより分析することを「システムダイナミクス」という
- システム思考の基本パターンとして以下の3つがあげられている「成長の限界要因」「重荷の転嫁」「部分的最適化」
- 「成長の限界要因」とは、あるプロセスによって望む結果が得られたとしても、同時に成功を平均化するような二次的影響が発生し、徐々に成功を妨げるようになるというもの。大きな成功のためには同じプロセスを強いるのではなく、「成長の限界要因」を見つけ取り除く必要がある。
- 「重荷の転嫁」とは、根底にある問題が目に見える症状を生み出しているのだが、根底問題に立ち向かうのが困難な場合に発生しやすい。そもそも、人は問題の元凶よりも症状を解決する方向にあり、根底問題が悪化しているのに見過ごしてしまうことがある(-> 5 whys)
- 「部分最適化」とは、システムが複雑であればあるほど、システムを分割し部分的に管理したくなるもので、局所マネジメントにより生産性の局所的なものさしができことが多い。これらの局所的なマネジメントは全体としての生産性を低下させ、全体に悪影響を及ぼす。
- 通常、局所マネジメントによる全体に対する害は隠れて見えないので、迷信(原因と結果の間に実証がない繋がり)と習慣からくる局所最適的な計測から抜け出すことができない
- 局所最適化した部分的な計測ではなく、ビジネス全体として成果を最大化するに必要な「すべて」を測定する必要がある(「個人成果計測」ではなく「情報化計測」)
おわりに
入門 ~ iced coffee~
はじめに
- 今回は技術に全く関係ないですが、完全に優勝するアイスコーヒーのレシピを見つけたので、経緯から実践した結果、考察までをまとめて書き残しておく
経緯
- 今まではごく普通の水出しアイスコーヒーを飲んできた
- 作り方は一般的で、コーヒー豆と水の比率は
1:15
(コーヒー豆20gに対して300gの水を使用するということ) - 個人的には浅煎りのコーヒーが好きなため、水出しにしたときに未抽出になることを防ぐと言う観点から、300gのうち100gほどお湯を使って蒸らしをするようにはしていた
- 蒸らしの時間とか湯量はロースターごとに違いはあるが、水出しアイスコーヒーの割と王道的なレシピではあると思ってたし、味にも満足はしていた
- しかし、つい最近アイスコーヒーの衝撃的なレシピに出会い、私の中の水出しアイスコーヒー最強説が崩れて現在に至る
Tim Wendelboeとの出会い
- 先週末、コーヒーの抽出動画を見ていた際に、偶然Tim Wendelboeの動画に行き当たった
- 補足すると、外国の有名なロースターは大体、自分たちのサイトにLocationとかに加えてBrewing Guideを載せている
- お店によって載せているレシピの種類に違いはあるが、V60・カリタウェーブ・エアロプレス・フレンチプレスなどが載っていることが多い
- 日本のロースターと違うなと思うのは、日本は豆を購入するとレシピの書いた紙をくれたり・口頭で教えてくれることが多いのに対し、外国はオンラインで常に公開されている(ここに関する理由はちょっとわからないが、外国の方がコーヒー業界のオンライン整備が進んでいるからかもしれない。もしくは、日本は喫茶店文化からくるレシピに対する何かしらの秘伝書てきな要素を持っているのかもという想像をしている)
Who is Tim Wendelboe
- 少し脱線して、Timの説明をしておく
- 1979 年生まれ。ノルウェー人バリスタ。数々のノルウェー・バリスタ・チャ ンピオンシップに優勝。 2004 年、世界バリスタチャンピオンに輝き、2001 年、2002 年は銀賞を受賞。 2005 年には世界カップテイスターズチャンピオンシップ優勝。オスロで10店舗のコーヒーバーを持つ小 型チェーン店「ストックフレッス」でバリスタとして働き、2007年7月、オスロのグリーネルロッカ地区で、自身のエスプレッソ・バー、トレーニングセンター、そしてマイクロ・ロースター設立。現在はそこで最高品質のコーヒーの輸入や焙煎、販売を行っている。(引用元)
- Timはサードウェーブの草分け的存在で、海外ではもちろん日本のバリスタの中にも彼に影響を受けてる人が多い。
- サードウェーブの最先端はよくオーストラリア(特にメルボルン・シドニー)と北欧という風に言われるが、北欧が最先端と言われる所以には、彼がすでに15年以上前から世界の最先端に立ちコーヒー業界を引っ張ってきたというところが少なからず関係しているのかと思う
Tim Wendelboeのレシピ
- 上述の通り、世界中にTimのファンはいるといっても過言ではない
- そのため、彼は自身のレシピをYouTube上に公開している
- そして、その公開されているレシピの中からアイスコーヒーのレシピを発見し、実践してみた (動画)
- レシピ自体は非常に簡単で、30gの豆に対して500gのお湯、6gの砂糖を使用
- 基本的にはホットコーヒーを抽出する手順と同じ
- まず、60gのお湯を注いでステアし30秒蒸らす
- 30秒経過した後は200gまでお湯を注ぐ
- あとはドリッパーに対してお湯が多くなりすぎないように気をつけながら何投かに分けてお湯を注いでいく
- そして、落ち切ったタイミングで砂糖を入れてかき混ぜて、サーバーを氷水に入れて冷やす
実践してみた
- 上記のレシピ通りに入れてみた
- 抽出方法については、普段入れているレシピに似通っていたので、戸惑なく抽出ができたが、冷やす部分については、氷の溶けるスピードが想定よりも早く予想以上に消化してしまったので、保冷剤などを代用する方がいいかもしれない
- また、抽出したアイスコーヒーをそのまま冷蔵庫に入れることも考えたが、他の食材や冷蔵庫に入っている物への影響を考えると、最低限粗熱は取ってからの方がいいと考えられる
感想
- とても美味しかった(ボキャ貧ですみません)
- 今まで飲んでいたアイスコーヒーは何だったんだろうという感覚に陥った
- このレシピのコーヒーは室温くらいに馴染んだ時に一番ポテンシャルを発揮してきて、酸味とフローラルさが複雑に口の中で広がり、また微妙に加えた砂糖がその個性を一層引き立ててくれ、今まで味わったことのない一杯に仕上がった
- 水出しアイスコーヒーはどうしても、ホットコーヒーに比べると単調な味わいになりがちで、Tim曰くMalt Whiskeyみたいって言うのがわかる(それは完全に感想であって、それが好きな人はもちろんいると思う。そこは好みが別れるポイントではありそう)
- ただ、アフリカの代表格であるエチオピアやケニアといった華やかな酸味とかフローラルさ、それらが織りなすcomplexityを味わうのなら、Timのレシピの方がいいのかと思った
- 一方で、旧来の中南米産のコーヒー(例えば、ブラジル・グアテマラなどナッツやチョコレートを思わせるフレーバーをもつコーヒー)または発酵プロセスを経たコーヒー(ナチュラルプロセスのものやハニープロセスのものなど、ワイニー感のあるコーヒー)などは、今までの水出しコーヒーの方が美味しいのかもしれない
- 最後の仮説については検証しきれてないのだが、Tim自身も動画の中でアフリカ産のコーヒー(エチオピアとまで言及していた)を勧めていたので、アフリカ産の華やかな酸味を持つコーヒーを、アイスコーヒーで飲むときはこのレシピを勧めたい
アート・オブ・アジャイル・デベロップメントを読む
はじめに
- 今回は少し古い本ではありますが アート・オブ・アジャイル・デベロップメントを読んだ感想をさらっと書いておきます
そもそも
- なんで読んだのか?
- チームでXPを実践していて、個人的にインプットが欲しかった
- CTOがチームの新卒に勧めていた
- コロナウイルスの影響で、XPというかペアプロにやりにくさを感じていて、なんかしらの解決策を模索していた
感想
- インプットとしてかなり良い本だった。
- XPとかアジャイルについて何か学びたい人は必読。
- 以後、印象に残った箇所を列挙していく
アジャイルを導入すべきかどうかについて
- これは冒頭の部分において書かれているのだが、「アジャイル開発が が僕たちが成功するのにもっと役立つのか」ということに照らし合わせて考えて、もしそうなら導入すべきだし、そうじゃないなら導入すべきじゃないという話をしている。
- ここでいう「成功」というのは、納期通りに当初の仕様を満たしたソフトウェアを納品するということではなく、「組織的な成功」と「技術的な成功」と「個人的な成功」それぞれを満たすような状態のことを意味する。
- 「組織的な成功」はそのプロジェクトが投資に見合う成果を出し続けること、「技術的な成功」とは保守性の高いコードを書き続けていること、「個人的な成功」は個人がやりがいを感じながらやれていることである
XPはオフライン前提
- XPでは、「ペアプログラミング」であったり、「関係者が全員同じ場所にいる」ことであり、ベストプラクティスとされるものたちが、基本的にオフラインを前提とされている。
- そのため、当初私はリモートでXPをやるためにはどうしたらいいのだろうという解を求めて、この本を読み始めたが、それはXPのあるべき姿からは離れていくことなのかなというのを感じた。
XPのプラクティスは依存関係にある
- 「ペアプロ」と「TDD」であったり、「コードの共同所有」と「ペアプロ」であったり、「インクリメンタルな仕様」と「全員同席」であったり、「完全DONE」と「継続的インテグレーション」であったり、なにか1つのプラクティスを実践しようとすると、それを実践する目的が裏側にはあって、それを実現させるためには関係する他のプラクティスもあって、みたいな感じで、諸々が依存関係にあるということを感じた。
- 逆にいうと、何か1つだけ導入するとなると中途半端なことになるので、アジャイル開発の恩恵は受けにくく、成功から返って遠ざかってしまうと思った。
ペアプロとTDDの関係性
- ペアプロとTDDは非常に相性がいい。ペアで実装しているときに、どちらか一方がテストを書き、もう一方はそれを通すための実装を考えたり、その次のテストケースを考えたりと、アジャイルの「成功(特に技術的な成功)」を達成するのに役立つと思った。
- また、ペアプロをすることでコードの共同所有という状態になり、障害が発生したり、バグ修正が起こった場合などに特定の人のみが直すことが可能という状況を極力減らすことに繋がる。
インクリメンタルな仕様と全員同席
- アジャイル開発では、仕様や要件を最初から詰めきることはせず、インクリメンタルに必要になったフェーズでその要件を詰める。ここは、「組織的な成功」というところに関係する。
- ここは、ウォーターフォールの成功とアジャイルの成功が異なるため。
- なので、アジャイルでは都度必要な機能がでてくれば、その機能について話し合い、関係者で要件を決めるような流れになっているので、全員同席して、いつでもそのような変更に対応できるようにしておく必要がある。
完全DONEと継続的インテグレーション
- XPでは完全DONEになって初めてそのストーリが終了したとして、ベロシティに計上ができる。そのため、CI/CDであったり10分ビルドという環境が整っていて、いつでもリリースできるようにしておく必要がある。そうでないと、そのストーリはリリースのタイミングを待つことになり、完全DONEとして報告するタイミングがおくれてしまい、ベロシティとして計上するタイミングも遅れてしまう。
- また、ベロシティだけでなく、POへのデモなどにも影響がでてしまい、「君たちは何をやっていたんだ?」という印象を与えかねないのかなという印象を受けた。
見積もりの話
- 見積もりは1ストーリ1分以内
- 弊社のCTOによく言われてきたのですが、要するに1分以内に見積もれないようなものはその場にいる見積もりを出す人たち(主に開発者)で見積もりに必要な情報がそろってないということの証拠らしい。
- 5分経っても見積もれないようなら、ちゃんと時間をとって目線合わせ、すなわち見積もる上で何が足りないのかということを明確にした方が良いということ。
- このようなことを避けるためにも、普段から全員同席してコミュニケーションを丁寧にとっておく必要があるし、全員同席をしておけば、関係者がいないから確認できずに今は見積もれないというような状況には陥らずにすむということ
まとめ
- 簡単ではあるが、読んだ感想と印象に残ったことを自分なりにまとめてみた。
- 改めてインプットをしてみたことで、なんでこのプラクティスをやっているのだろうということを自分なりに考えを持つことができたし、人にもなんとなく説明できる気がする。
- まだこの本を読んだことがない人はぜひ読んでみて欲しい。
Elixir School 4日目
はじめに
- 今回は前回の応用編の続き
- 並行プログラミングについて学んでいく
並行性
actors
- Elixirでは
actor model
と呼ばれる並行プログラミングの手法が使用できる 参考 - このモデルでは
actor
たちは並行で動く独立したentities
である actor
は独立しているので、他のactor
に対して状態を共有しないので、競合状態になることはないactor
同士はお互いにメッセージと呼ばれるものを送ることでコミュニケーションしていて、一つのactor
はメッセージを順次処理していく- 並行性というのは、いくつかの
acotor
が並行に実行されることによって生じるものである
process
actor
はprocess
とも呼ばれる- 多くの言語では、プロセスは重いものである。一方で、Elixirではリソース消費と起動速度という観点からスレッドよりも軽量である
- そのため、Elixirでは幾千ものプロセスが走ることがある(Elixirではスレッドプールみたいなものを用意して、管理する必要はない。そこらへんは、Erlang VMが頑張ってくれる)
message passing
によってElixirの並行プログラミングは動く
Messages and Mailboxes
- Messagesは非同期である
- actorにMessageを投げると、actorのMailboxに置かれる
- actorの呼び出しはノンブロッキングに実行される
- MailboxはElixirにおいては
queue
である - actorが準備ができていれば、MailboxからMessageを引き出し、Messageに応答し他のMessageを送る。それを、Mailboxが空になるまでactorは一連の流れを繰り返す
spawn and spawn_link
- 新しいプロセスを作る最も簡単な方法は、匿名/名前付き関数を引数に取る
spawn
。 新しいプロセスが作られると、 プロセス識別子 別名PIDが返る。これはアプリケーション内部でプロセスを一意に識別するものである。
one_message = fn () -> receive do {:hello} -> IO.puts(“HI!”) end end actor = spawn(one_message)
- 上記の例だと、一度recieveの部分で期待してるメッセージがきた際に、actorのライフサイクル的には終了する
- なので、次は1度きりではなく、期待するメッセージが来てもなんども処理できるようなプログラムにする
defmodule HiThere do # recieve block def hello do receive do {:hello} ->IO.puts(“HI!") end # infinite loop hello end end
- いくつか走っているプロセスのうち1つが終了した場合(正常終了orクラッシュした場合を問わず)、他のプロセスに知らせてあげる必要がある
- その場合に、
spawn_link
を使う
defmodule Example do def explode, do: exit(:kaboom) def run do Process.flag(:trap_exit, true) spawn_link(Example, :explode, []) receive do {:EXIT, _from_pid, reason} -> IO.puts("Exit reason: #{reason}") end end end
spawn_link
が呼ばれると、リンクしたプロセスがお互いに通知を受け取るので、受け取った側のプロセスも死ぬ。- iexでも、
spawn_link
を呼び出すと、プロセスが両方死んだことがわかる
iex(11)> spawn(Example, :explode, []) #PID<0.147.0> iex(12)> spawn_link(Example, :explode, []) ** (EXIT from #PID<0.105.0>) shell process exited with reason: :kaboom Interactive Elixir (1.10.2) - press Ctrl+C to exit (type h() ENTER for help) iex(1)>
- リンクしたプロセスは殺さずに、
spawn_link
を呼び出したプロセスだけを殺すためには、Process.flag/2
を使用してあげる Process.flag/2
の第1引数には:trap_exit
をというフラグを渡してあげて、第2引数にはtrue/falseを渡してあげる(この場合はフラグを立ててあげる必要がある)
defmodule Example do def explode, do: exit(:kaboom) def run do Process.flag(:trap_exit, true) spawn_link(Example, :explode, []) receive do {:EXIT, _from_pid, reason} -> IO.puts("Exit reason: #{reason}") end end end
Task
- Taskは関数をバックグラウンドで実行し、後でその戻り値を受け取る
- アプリケーションの動作を妨げることなく、実行コストの高い演算を処理する時に特に役立てることができる
defmodule Example do def double(x) do :timer.sleep(2000) x * 2 end end iex> task = Task.async(Example, :double, [2000]) iex> Task.await(task)
- こっちの方は
async
とawait
を呼び出すという他の言語でも馴染みのある関数
genserver
- genserverはclient-serverという関係のサーバーを実装するためのビヘイビアモジュールある
- genserverはビヘイビアの一種なので、実装する上で呼び出す必要がある関数がいくつかある(同期・非同期で異なる)
defmodule SimpleQueue do use GenServer # クライアント側 def start_link(state \\ []) do GenServer.start_link(__MODULE__, state, name: __MODULE__) end # コールバック関数 def init(state), do: {:ok, state} end
- あとは、同期関数の
call
を呼び出すか非同期関数のcast
をよびだすかによって、コールバック関数の定義も異なる
def print(n) do GenServer.call(__MODULE__, {:print, n}) end def print_async(n) do GenServer.cast(__MODULE__, {:print, n}) end def handle_call({:print, n}, _from, state) do {:ok, fb, state} = sum(n, state) {:reply, fb, state} end def handle_cast({:print, n}, state) do {:ok, fb, state} = sum(n, state) IO.puts fb {:noreply, state} end def sum(n, state) do ... end
まとめ
- 今回はElixirの並行プログラミングの触りのところを理解した