Runner in the High

技術のことをかくこころみ

Maybeに代えてカスタム型を使う

 ElmのMaybeはデータの有無を型で表現できるゆえ非常に便利なものであるが、文脈が失われるため無闇に使い過ぎるとワケがわからなくなる。ケースによっては、カスタム型を使うことによって型でデータの有無を Maybe に代えて表現するほうがよりメンテナブルにある可能性がある。

前提

 ユーザーログインの機能を持つTODOアプリを開発している。

 上記の仕様から、おそらくモデルに currentUser: User みたいなフィールドと TODO を表す todos: List Todo があるだろうと言うことは容易に想像できる。 だが、実際には「ログインしていない」と「ログインしている」のふたつのコンテキストを表現しなければいけないため、両方とも Maybe になる。ログインしていなければ両方共 Nothing になる。

type alias Model =
    { currentUser: Maybe User
    , todos: Maybe List Todo
    }

 正直ここの Maybe は苦しい。なぜならこの Maybe は文脈を持っていないからだ。

 例えば、ログインに失敗して Nothing なのか、純粋にまだ TODO を一個も持っていないから Nothing なのかは、ここでは分からない。 ユーザーがログインに失敗したから Nothing なのか、まだログインしていないから Nothing なのかも、判断が難しい。場当たり的な処置として Result 型を使う手もある。だが、結局文脈をもたない Maybe はコードを書く側が頑張ってその文脈をコード・リーディングしながら紐解いていくことになる。これはまず大変だし、バグを生む可能性がある。

 ではログインにまつわる文脈を持たせましょう、ということで LogInStatus 型のデータを与えると以下のようになる。

type alias Model =
    { currentUser: Maybe User
    , todos: Maybe List Todo
    , logInStatus: LogInStatus
    }


type LogInStatus
    = NotLoggedIn | LoggingIn | LogInSucceeded | LogInFailed

 これでモデルにログイン文脈のデータが与えられたが、それでもまだ苦しい。

 それぞれの Maybe の結果は LogInStatus と独立しているため、開発者のうちひとりが LogInSucceeded の状態であるにも関わらず currentUser を Nothing のままにしているなどのコードを書いてしまう可能性がある。ログインが成功しているのにユーザーがいない、というのはまず仕様としてもおかしい。

カスタム型で仕様を表現する

 Elmは型付言語なので、仕様を表現するような型制約を設けるのがよい

 たとえば、以下のようにデータが前提としてログインの状態に紐づくようにすることによって Maybe を使わずに currentUsertodos の存在有無を表現できる

type alias UserData =
    { currentUser: User
    , todos: List Todo
    }

type Model
    = NotLoggedIn
    | LoggingIn
    | LogInSucceeded UserData
    | LogInFailed

 このコードであれば、まずログインが成功していない限りユーザーも、TODOも存在していないということが型レベルで表現されるため、もとのコードにあったような Maybe の結果とログイン状態が食い違ってしまうようなコードを書いてしまうのを防ぐことができる。

基礎からわかる Elm

基礎からわかる Elm

  • 作者:鳥居 陽介
  • 出版社/メーカー: シーアンドアール研究所
  • 発売日: 2019/02/27
  • メディア: 単行本(ソフトカバー)

2018年で最も早くTypeScript+Reactのアプリを作る方法

結論から言うとこれです。

$ npx create-react-app myapp --typescript

create-react-app で --typescript オプションが公式にサポートされたということで、例えば以下の僕が昔 Qiita で書いた時に使った react-scripts-ts みたいなジェネレータ・スクリプトよりもこのオプションを使うほうがいいかと思います(絶対ダメ! というわけではないけど、公式のほうが安心感がある気がするので)

qiita.com

ちなみにこの --typescript オプション追加の PR は以下です

github.com

ちなみに、この PR で TypeScript のテンプレートが create-react-app の中にコミットされけど、これが react-scripts-ts のコピーなのかどうかは分からない。似てる気はするけど、コメントなどで言及もないしたぶん違うかな。

gpd-pocket-ubuntu-respinの更新を適用したらファンが止まらなくなった

本日久しぶりにGPD Pocketのコミュニティパッチを更新して適用したところ、まだ44℃だというのにCPUファンが思いっきり回転しはじめた

おそらくこれはファン周りのデーモンかなにかがうまく動いてないな...ということでおもむろにログを確認

 $ journalctl -u gpdfand.service 
-- Logs begin at 金 2018-08-24 17:54:08 JST, end at 金 2018-08-24 18:06:16 JST. --
 824 17:54:10 izumisy-gpd-pocket systemd[1]: Started GPD Fan Daemon.
 824 17:54:11 izumisy-gpd-pocket gpdfand[769]: Traceback (most recent call last):
 824 17:54:11 izumisy-gpd-pocket gpdfand[769]:   File "/usr/local/sbin/gpdfand", line 78, in <module>
 824 17:54:11 izumisy-gpd-pocket gpdfand[769]:     set_fans(1,0)
 824 17:54:11 izumisy-gpd-pocket gpdfand[769]:   File "/usr/local/sbin/gpdfand", line 46, in set_fans
 824 17:54:11 izumisy-gpd-pocket gpdfand[769]:     gpio.write(unicode(a))
 824 17:54:11 izumisy-gpd-pocket gpdfand[769]: IOError: [Errno 1] Operation not permitted
 824 17:54:11 izumisy-gpd-pocket systemd[1]: gpdfand.service: Main process exited, code=exited, status=1/FAILURE
 824 17:54:11 izumisy-gpd-pocket systemd[1]: gpdfand.service: Unit entered failed state.
 824 17:54:11 izumisy-gpd-pocket systemd[1]: gpdfand.service: Failed with result 'exit-code'.

変更を遡って見てみると、どうやら以下の変更でIOErrorが起きるようになったっぽい?

github.com

いったんコミットを 7813b8a まで戻して再び更新を適用

$ git checkout 7813b8a
$ ./update.sh

これでとりあえずはファンが静かになった

Golangではinterfaceはどのパッケージに属するのか

Golangを使い始めてinterfaceでDIPっぽいことをしようとするとたしかに湧きがちな疑問のひとつ。結論から言うと、interfaceはそれを使う側のパッケージに所属させるのがセオリーらしい。なるほど。

Go interfaces generally belong in the package that uses values of the interface type, not the package that implements those values.

CodeReviewComments · golang/go Wiki · GitHub

なぜか

せっかくなので理由を考えてみた。

以下はJSONXMLなど複数のフォーマットでログを出力できるロガー・アプリケーションのクラス関係図の例。 このアプリケーションの設計は、中核となるLoggerクラスと具体的なXMLJSONなどへのシリアライザ実装がそれぞれISerializerインターフェースという抽象に依存している典型的なDIP(依存性逆転)だ。LogParamsクラスは、ログとして出力するデータの内容を保持しているものとする。

f:id:IzumiSy:20180819114326p:plain

それぞれ、黒矢印は「利用」、白矢印は抽象に対する「実装」、点線の矢印は「依存」を表している。

まず、このアプリケーションにおいてserializerパッケージというのは「どのように出力するか?」のHowが所属するレイヤであり、一方でmainパッケージはアプリケーションのビジネスロジックとして「なにをするのか?」のWhatが所属するレイヤにあたる。

そう考えると、具体的な実装を持たないinterfaceであるISerializerが、「出力の方法」を責務として所属させるべきserializerパッケージにいるというのは違和感がある。なぜならinterfaceは単なる「使い方」を示すだけのものであって具体的な実装を持つものではないのに、Howが所属するパッケージにいるからだ。このような理由で、interfaceはそれを使う側に配置するのがよい、というプラクティスに合点がいく。

(ちなみにではあるが、そもそもこの関係図だとmainパッケージとserializerパッケージが双方向依存になっているため、Golangではコンパイルの時点でエラーになってしまう)

f:id:IzumiSy:20180819115021p:plain

ということでISerializermainパッケージに所属させる。

パッケージ内での依存関係はいくら循環していようと構わないので、これは問題なく動作する。また、ISerializerインターフェースが実装先のパッケージから切り離された。これで正しく「使う側」に所属させることができた。

DDD的な観点: 例えば今回のISerializerに当たる部分が、DDDでいうところのリポジトリであると考えてみる。そう考えると、リポジトリドメイン層のインターフェースに当たる部分なので、リポジトリの具体的な実装のパッケージにそれが所属するのは解せない、というのがしっくりくる。意図せずここで不思議なつながりが見えた気がする(?)

Clean Architecture 達人に学ぶソフトウェアの構造と設計

Clean Architecture 達人に学ぶソフトウェアの構造と設計

依存関係について再び考える

izumisy-tech.hatenablog.com

  • あとからこの過去記事を読み返して「ムム」と思うところがあったので改めて。

  • CategoryId ではなく Category を引数として渡すことでデータ構造が隠蔽されているという旨の説明をしているが、これは fetchArtclesByCategory を呼び出す側の責務が「Categoryの データとしてIDが取れる」という知識を持ってしまうのを防げるという意味で書いたのではないかと思う。

  • けれどもその実装だと、結局 fetchArticlesByCategory 関数側が、引数として受け取る Category からIDを取り出すという処理をせなばならず、ID呼び出しでないという設計指針が fetchArticlesByCategory に対して Category への依存を与えてしまっている。

  • この依存が別に構わないという可能性もあり、たとえば Categoryドメインモデルなのであれば、そのドメインモデルに対して単方向の依存を持つクリーンアーキテクチャ的観点でいうところの外部のレイヤとして fetchArticlesByCategory が存在していると考えることもできるので、さほど問題を感じなくなる。

  • ところで fetchArticlesByCategory 関数を呼び出すのは、アプリケーションの責務のうち誰になるのだろうか... またまたクリーンアーキテクチャを考えてしまうが、おそらくユースケース層ではないかと思う。

  • DDD的観点: CategoryId を渡す場合においての別の懸念点としては、もしも CategoryIdCategory という集約ルートにおける値オブジェクトだとしたら、それを CategoryId 単体で受け取ってしまえるインターフェースもった関数 fetchArticlesByCategoryId があることで、利用者によって不変条件を保たずにつくられたおかしな CategoryId が渡ってくる可能性があるのではないかということ。正直ここは実装の問題のような気もするけど...

Clean Architecture 達人に学ぶソフトウェアの構造と設計

Clean Architecture 達人に学ぶソフトウェアの構造と設計

クラウドバンクをやっている

OneTapBUYをやっていたときもこういう記事を書いていたので今回はクラウドバンクについて書く

izumisy-tech.hatenablog.com

つい最近クラウドバンクも始めた。いまのところ投資額はOneTapBUYと同じくらいだが、パフォーマンスで言うと年6〜7%といったところらしく、さほどリスキーではなさそうな利幅でよろしいと思う。こちらもどういう風にお金が増えるのか楽しみだ。

ちょうどこの記事を書いた2017年の10月から始めていた。結論からいうと、自分のクラウドバンクのパフォーマンスは年利で3.8%という感じになっている。

f:id:IzumiSy:20180805121827p:plain

今現在で80万程度つっこんでいるわけだが、それで増えたのが1.8万くらい。こう見るとOneTapBUYにもっとお金を突っ込んでいたほうが今頃には利益がもっと出ていたかもしれない。

2017年の10月の記事ではOneTapBUYでは12万で1万の利益がでている! みたいに書いたが、その翌月には5000近くまで下がってしまっていた。もちろん株なんてものはそうやって上がったり下がったりするものではあるが「こういうの、いつも気になってしまってソワソワしてしまうな〜」という感覚を持った。その当時はまだ学生で毎月定期的な収入があるわけでもなかったため、いわゆるドルコスト平均法のような積立戦略を実践するにも難しさがあり、短期的な売買に終始していたからという理由もある。

その点クラウドバンクはある意味ただの投資に近いので、自分の利益が上がったり下がったりするのを見る必要はないし、そもそも見えない。預けたものに毎月分配金がつくだけでシンプル。見方を変えれば、株と違って自分で取捨選択をしている感覚がないので、地味といえば地味ではある。とはいえ、社会人にもなるとそんなに毎日自分のポートフォリオの上がり下がりを見ている余裕はそんなにないので、どちらかといえば勝手に誰かがやっていてくれるほうがうれしいというものだ。そういう理由もあって2017年の11月にはOneTapBUYで投資していた分を5000円プラスで売却し、クラウドバンクに突っ込むことにしたのだ。

f:id:IzumiSy:20180805122942p:plain

ココ最近のペースでは毎月3000円くらいの分配金が入ってきている。毎月一回くらい飲み会にタダでいける程度ではあるが、それでもこうやってお金が増えていく様子をみるのはうれしいものがある。

JavaScriptにおける配列操作の計算量オーダー

日本語だとググっても出てこなかったのでまとめた

操作 計算量
添字アクセス O(1)
挿入(splice) O(n)
削除(splice) O(n)
削除(delete) O(1)
最後に追加 O(1)
先頭に追加 O(n)
スワップ O(1)

添字アクセスがO(1)だったりするのは、JavaScriptの配列は連結リストなどではなく単なるインデクスをキーで持つObjectだから。

ちなみにdeleteを使った削除というのはspliceと違って要素がundefinedと交換されるだけのものなので後続要素への走査が走らないためO(1)になる。

参考: https://stackoverflow.com/questions/11514308/big-o-of-javascript-arrays

結果整合性について

歴史

  • かつて、分散システムのデータ複製における唯一無二の理想は「更新されたデータは即座に反映される」というものだった。
  • 70年台の分散システム技術において試みられているものの多くは、いくら背後にたくさんのシステムが控えているとしても「使う人間からはひとつのシステムを使っている」ように見せることで、その観点から主に議論されていたものの多くはデータの一貫性(Consistency)をいかにして獲得するかという部分に主眼が置かれたものだった。
  • 90年台中頃からインターネット・システムの規模が大型化してくると、開発者の多くはデータの一貫性を犠牲にしてもスケーラビリティを担保することが重要なのではないかと考えはじめた。
  • AWSは世界規模で利用されるシステムを開発するにあたってあらゆる場所でレプリケーション技術を活用してきたが、その中で結果整合性モデルはレプリケーションにおいてデータの一貫性を担保するための技術として提供されてきた。
  • AmazonのCTOであるWerner Vogelsは「並行処理における書き込み・読み込みパフォーマンスの担保」と「データロックによるノードの可用性の低下の抑止」の2つの観点から、データの一貫性はスケーラブルな大規模システムにおいてはさほど重視される必要はないと考えている。

強整合性と結果整合性の違い

強整合性 結果整合性
データのロック あり なし
スケーラビリティ 低い 高い
一貫性 担保される すぐには担保されない
  • 強整合性 の場合、データの更新の際にデータベースをロックすることによってデータの一貫性(Consistency)を担保するが、ロックされる期間が長いほどその間のデータベース・アクセスがブロックされ、可用性(Availability)を犠牲にすることになる。
  • 結果整合性 はデータの更新でデータベースがロックされることはないため、可用性とスケーラビリティを維持することができる。その代わりノード間でのデータの一貫性はデータ複製にかかる時間に依存することになるため、必ずしも担保されない。

その他

結果整合性は必ずしも高度な分散システム固有の難解な技術ではなく、多くのモダンなRDBMSは同期・非同期レプリケーションのシステムが組み込まれている。同期的レプリケーションの場合にはレプリケーションの更新はトランザクションの一部として行われるが、非同期的レプリケーショントランザクションログの伝播の前にプライマリーでのデータ更新が失敗すると、ノード間で一貫性のないデータが生まれることになる。つまり非同期レプリケーションRDBMSにおける古典的な結果整合性のケースのひとつである。

参考

データ指向アプリケーションデザイン ―信頼性、拡張性、保守性の高い分散システム設計の原理

データ指向アプリケーションデザイン ―信頼性、拡張性、保守性の高い分散システム設計の原理