isyumi_netブログ

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

人生の5箇年計画

やりたいことがいっぱいありすぎるので一覧を作る。

5箇年計画と書いたが、実際には何年かかるか全く見当がつかない。

 

  1. 数学検定準一級
  2. TOEIC800点
  3. レッドコーダーになる
  4. Webフロントエンドのベストプラクティスホスティングサービスを作る
  5. Dartのサーバーサイドフレームワークを作る
  6. ビジネスロジックからDBのクエリを自動生成するツールを作る
  7. 数学検定一級
  8. 応用情報技術者をとる
  9. データベーススペシャリストをとる
  10. ネットワークスペシャリストをとる
  11. セキュリティスペシャリストをとる
  12. LPIC Level3になる
  13. この辺で年収1000万になる
  14. CPUを作る
  15. OSを作る
  16. 言語を作る
  17. RDBの理論面を勉強する
  18. 型システムの理論面を勉強する
  19. 素晴らしい数式処理システムを作る
  20. 暗号論を勉強する
  21. 新しいWebの仕様を策定しバーナーズリーの後継者になる

30才までに終わるかな。

外部からSSH接続を受け付けるプロダクトのSSH設定

今、デベロッパー向けにこういうサービスあったらいいなーと思っているものがある。

その中でSSH接続を受け付けたいなと思ったから実現方法を考えた。

WordPressホスティングしてvimで記事を編集できるようなものをイメージして欲しい。

 

ルール

・1人1Docker コンテナ

・コンテナを実行するEC2インスタンスを運営側がいつでも変更できるようにする

・サーバーの状態の保持は別問題

・HTTPも別問題

・ユーザー名は全ユーザーで1意、ホストは共通

 

サインアップしたユーザーにDockerコンテナを用意し、そこにSSH接続を受け付けるようなものを作りたい。

 

せっかくなので、1Dockerに複数のユーザーを用意できるようにしたかった。それができると「管理者」「編集者」「記者」等のロールを作れる。しかし、難易度が上がりそうなので今回は諦め。

 

コンテナを実行するEC2インスタンスを運営側がいつでも変更できるようにしたい。

特定のホスト名、ポート番号で待ち受け続けないといけないと運用がきつくなる。

例えばもっと安いEC2インスタンスが発表されたらこっそりコンテナを移動したい。

また、使われてないコンテナを勝手に落としておき、次にアクセスが来たときに空いているインスタンスで立ち上げる仕組みも入れられるようにしたい。

そのため、生EC2アドレスをユーザーに渡すのではなく、SSHプロキシのアドレスを渡してそこからEC2にブリッジする。

 

インスタンスを立ち上げなおしたときにユーザーが変更したファイルの保持をどうするかは、ネットワーク設計とは別問題なので今日は気にしない。それはアプリケーション設計の話だ。

大方 /var/hoge/fuga 以外を変更しても保存は保障しませんよといっておけばいいだろう。

 

HTTPでも受け入れられるようにしたいが、そっちはそっちで話がでかすぎるので別で考える。

 

ユーザー名は全ユーザーで一意にする。

さっきも書いたが本当はホストごとに名前空間を区切って

- admin@user1.some-service.com

- author@user1.some-service.com

- editor@user1.some-service.com

- junior-staff@user1.some-service.com

という風にロールわけをできるようにしたい。

しかし、後述のライブラリがまだHostに対応していないので諦めた。

 

とりあえず

- user1@some-service.com

- user2@some-service.com

というユーザー名体系にする。

 

実現方法

これはまだ試していなくて、こうすれば大丈夫だろうと僕が今想像していることである。

 

用意するもの

・ELB

・セキュリティゾーン

・EC2を3台以上

 ・内2台はSSHプロキシ

 ・もう一台はDockerのせるインスタンス(アプリケーションインスタンスと呼ぶ)

  ユーザーが増えてきたらこのインスタンスを増やしていけば良い

・DockerImage

 

まず、ELBから2台のSSHプロキシサーバーに大してTCPロードバランスを設定

外向きのホスト名はssh.some-service.comで統一する。

 

SSHプロキシサーバー内で下記SSHプロキシツールを起動

https://github.com/tg123/sshpiper

 

アプリケーションインスタンスでDockerを起動

このとき、内向きのSSHポートは22とする。

外向きは可変にする。

docker -p 3333:22 等

 

これで準備は完了。

新しくユーザーがサインアップしたらどこかのアプリケーションインスタンスでDockerを起動する。

鍵ペアを生成し、Docker内に渡すと同時にSSHプロキシにそのインスタンスのホスト名、ポート名、鍵を設置。

ユーザーに秘密鍵を渡す。

 

これでできるんじゃないかな?

ビジネスロジックのコードからDBのクエリを自動生成することは可能か?

最近取り組もうと思っていること

ビジネスロジックのコードを解析することでこのようなことができるのではないか?

  • このコードを実行するために必要なDB上のデータの範囲を特定する
  • そこからDBへのクエリを自動生成できるのではないか

という疑問だ。

 

前提とするコード

ビジネスロジックの前段階で必要なデータを落としてくる

僕はビジネスロジック内でDBにアクセスするのではなく、その前にデータを落としてきておいて、そのデータを引数に取る事にしている。

 // こうではない

func getUserSalesSum(req Request, db DB) Response {
sales := db.QuerySalesByUser(req.UserID)
return sum(sales)
}
// こう
func onRequest(req Request, db DB) Response {
sales := db.QuerySalesByUser(req.UserID)
return getUserSalesSum(req, sales)
}
func getUserSalesSum(req Request, sales []Sale) Response {
return sum(sales)
}

 これはgetUserSalesSumがスローテストにならないようにするためだ。

また、DBへのクエリが正しく書けたかということとこのビジネスロジックが正しく書けたかということは本来別問題なのに、同時にテストしなければいけなくなる。だからビジネスロジックに入る前に必要なデータは全部落としておくべきだ。

 

ビジネスロジックでも同じフィルターを書く

前述のこのコードは悪いコードの例である。よく「バッドコードの例」として紹介される。

func getUserSalesSum(req Request, sales []Sale) Response {
return sum(sales)
}

暗黙の前提が存在することについてだ。

このコードでsalesという値はあるユーザの購買履歴だけを含む配列ということになっている。しかし、このコードにそのメッセージ性がない。このコードを読んだ人は普通「このsalesはすべてのsaleが入った配列だな」と感じると思う。

そこで、いくつかの手を打つ。例えば引数名や関数名を工夫することがある。また、Assertを書くというのもただしい選択の一つだろう。しかし、僕はビジネスロジックにもフィルターを書くのがいいと思う。

func getUserSalesSum(req Request, sales []Sale) Response {
sales = filterUserSales(sales, req.UserID)
return sum(sales)
}

 

こうしたほうがビジネスロジックの1行1行が仕様と1:1で対応するようになるのでわかりやすい。

また、前者はこの配列が特定の条件でフィルターされていることに依存していたコードだが後者は必要なデータが入っていればそれ以外のデータが入っていても正常に動くコードになる。後者のほうがキャッシュなどとの相性が良くなる。

またクエリを書くことを手抜く余地が出る。例えば国コード一覧というのがあったとする。それは高々200個くらいの値しかないはずだ。そうであれば、毎回DBに取りに行くより手元のインメモリに持って置きたいかもしれない。そういう時に特別なコードを書かなくてよくなる。

DB上のすべてのテーブルを一つのオブジェクトツリーにまとめる

このコードの問題点の一つとして使うデータの種類が増えれば引数が多くなってしまう点がある。ユーザー情報や購買情報や位置データやあれこれ……

そこで、DBのテーブルを全部一つのオブジェクトツリーに含めてしまうといいと思う。

type Table struct {
Users []User
Products []Product
Sales []Sale
}

func getUserSalesSum(req Request, table Table) Response {
sales := filterUserSales(table.Sales, req.UserID)
return sum(sales)
}

さて、「ビジネスロジックの前段階で必要なデータを落としてくる」でビジネスロジックが参照透過性を手に入れた。そして、「ビジネスロジックでも同じフィルターを書く」でテーブルが特定の条件に依存するコードから。さらに一つのオブジェクトツリーにまとめることによって、テーブル全体の関数に変わった。

HTTPリクエストハンドラとは

ここで一つの結論に到達した。

HTTPハンドラとは、HTTPリクエストとDB上のすべてのデータに対してHTTPレスポンスを返す純粋関数である。

POSTメソッドなどのこともあるので、HTTPレスポンスとDB書き込みコマンドを返す関数であるということにしておく。

自動生成

 

おさらいだが、このビジネスロジックは引数でDB上のすべてのデータを受け取った前提で書いてある。しかし、実際に必要なのはその一部分である。その部分は必ず入っていなければいけないが、それ以外の部分にデータが入っていても入っていなくても動作は変わらない。逆に混ざっているとまずいデータというのはありえない。

たとえば上記のこのコードの場合だ。

type Table struct {
Users []User
Products []Product
Sales []Sale
}

func getUserSalesSum(req Request, table Table) Response {
sales := filterUserSales(table.Sales, req.UserID)
return sum(sales)
}

func filterUser(sales []Sale, id interface{}) []Sale {
var results []Sale
for _, s := range sales {
if s.UserID == id {
results = append(results, s)
}
}
return results
}

このコードを見れば誰でもUsersとProductsにデータが入っていても入っていなくても同じ動きをすることはわかる。

また、Salesにreq.UserIDのすべてのSalesが入っていなければならないが、UserBさんやUserCさんのデータはあってもなくても同じということもわかる。

僕の問題意識は、「人間がこのコードを読めば必要なデータと不要なデータを判定できるならコンピューターに計算させることができないか?」である。

UsersとProductsが不要であるということはASTを見ていけば結構簡単にできそうだ。しかし、req.UserIDのSalesだけが必要だ、ということはどうすれば判定できるのだろうか。

今のところ、すべての「値」に対して「その値を作るのに必要な値」を挙げていき、それをツリー上にすればある程度可能かと思ったが自信がない。

 

誰か教えてくれ。

 

 

Dartのサーバーサイドフレームワークを作ろうと思う

サーバーサイドもDartで書いてきた。

ワンパターン化してきたのでフレームワーク化する。

とりあえず構想を発表する。

認証を楽にするため、HTTPサーバーはなくFirebaseサーバーとして振舞うものにする。

 

サポートする機能3つだ。

  1. Firebaseの型定義
  2. View
  3. コード生成支援

 

Firebaseの型定義

FIrebaseJSONでデータをやりとりするため、エンコーダとデコーダが必要になる。

Firebase特有の機能があるので一般的なDartJSONエンコーダーライブラリでは機能不足だ。サーバー側とクライアント側で微妙にユースケースが違うこともネックになる。

 

だから、型定義ファイルからサーバー・クライアントのコードを自動生成する。

定義ファイルの書式はDartにする。型定義ファイルのフォーマットとして、JSONは書きたくない。yamlも辛い。yamlは序盤は快調だが行が増えてくると辛くなる。

結局、型定義にどのフォーマットが適しているか。プログラム言語が一番いい。だからDartにした。これはそのDartを実行したりリフレクションで値を入れたりするものではない。DartASTをとって解釈してそれを元にコードを生成する。Dartの書式で型を定義すれば読みやすいし各自のエディタのシンタックスハイライトや補完がきく。

 

将来はマイグレーション機能もつけたいと思っている。

Firebaseを使っているとDBのようにAlter Tableが打てないとめんどくさくなる局面がある。

型定義の変更履歴から既存のデータベースを変更できるコマンドを作れるはずだ。

 

2.view

FirebaseDBViewの機能が欲しい。もしかしたらこれは本家が対応するかもしれない。して欲しい。

例えばユーザーがいろんなグループに参加できるグループチャットアプリを作っているとする。ユーザーごとに取得したい書き込みはバラバラである。これを頑張ってクエリで表現するよりも

 A チャットの書き込みを全部一箇所にまとめたデータ

 B 誰がどのグループに所属しているかまとめたデータ

 C ユーザーごとに所属するグループの書き込みをまとめたデータ

を別々に保持したい。

 

だから、AとBが変更された時にリアクティブにCを再作成したい。

どうするかというと、AとBの全データを引数にCの全データを作成する純粋関数を一個定義すると、A、Bが変更された時に毎回その関数が実行され、結果をFirebaseの所定の場所に書き込むものが欲しい。

 

いくつか最適化の方法を考えている。

DBにならってAとBをテーブル、Cをビューと呼ぶ。

第一に、テーブルの全データを毎回Firebaseから読みだすのは悪いので、テーブルのコピーをローカルファイルにおいておきたい。

第二に、作り直すたびにビューの全データをFirebaseに書き込むのは申し訳ないので、前回作成したビューをローカルに持っておき、差分をチェックしパッチを送る形にしたい。

第三に、どのテーブルが更新された時にどのビューを更新するのか、どのビューを更新するためにどのテーブルを読み出さないといけないのかを自動で判断する。ソースコードを解析することで可能だ。

第四に、実現方法がわからないので夢物語だが、ビューの1行がテーブルのどの行に依存しているかを判断できたら、毎回テーブルの全データを読み込まなくてよくなる。

 

コード生成支援

以前設計の逆流問題の話をした。

 

blog.isyumi.net

 

この最後の部分にinterfaceを自動生成していってくれればいいということを書いた。

それを含めたい。

 

設計の逆流問題とその解決策案

僕が設計の逆流問題と呼んでいる概念の話をしたい。

僕がコードを書いていて一番疲れることの一つだ。

一般に下レイヤーのコードが上レイヤーのコードに感知しないのがいい設計ということになっている。

例えば、WebAPIを開発しているとしよう。APIのハンドラ内でDBからデータを取得し、それを加工してレスポンスするとする。

Web APIを書くディレクトリの中にDBアクセスのコードを書くと汚くなるので、DBアクセスは別のディレクトリに書いてインポートすることにした。

こんな感じのコードになる。

 

/db/user.dart
User getUser(String userID) {
   ...
}   

 

/web_api/get_user.dart 

import ../db/users.dart as db;

HttpResponse getUser(HttpRequest httpRequest) {
return new HttpResponse()..body = db.getUser(httpRequest.param["user_id"]);
}

 このとき、/db/users.dartから書くか、/web_api/get_users.dartから書くか、それが問題だという話だ。

善悪の観点から言えばdbアクセサを書いてからWebAPIハンドラを書くのが正しい。

なぜなら依存関係を1方向にし、依存関係の後ろ側から書いていく方が自然だからだ。

しかし、ここで設計の逆流問題が起こる。

上レイヤーで何をするかわからないと下レイヤーがどんな機能を公開するか決められないのだ。

例えば/db/users.dartにはどんな処理が必要か?

  • UserIDでUserを一件取得する
  • 30才以上のユーザーを取得する
  • 商品1を購入したユーザーを取得する

などなど。

これは/web_api/**のすべてのコードがわからないとと決めようがない。

では/web_api/から書いていくべきか?

そうでもない。

上レイヤーから書いていくと終始コンパイルエラーがでて目障りだ。

しかも、コード補完やリファクタリングなどIDEの機能が使えなくなる。

 

ここで二つの方法が考えられる

一つは深さ優先探索だ。

WebAPIハンドラを書きながら必要に応じてDBアクセサを書いていく。

些細なことならこれが一番楽だ。

しかし、あっちこっちいろんなファイルを触ることになるので、僕のように脳内ワーキングメモリが乏しい人は「あれ?  今何してたんだっけ」状態になりやすいのが難点だ。

そうでなくとも、要領が悪い方法なのは確かだ。

 

もう一つ、事前リストアップ作戦がある。

先に脳内でWebAPIハンドラのコードを書き、必要な下層の機能をメモ帳に書いていく。

この方法はかなり時間短縮になる。特に大きい作業ではかなり効いてくる。

しかし、まず疲れる。

第二に不正確になりがちである。

第三に、そもそもプログラミング言語が一番得意そうな領域のことをわざわざメモ帳に自然言語で書くせいで消耗するのは筋が悪くないか?

 

結局どうしようもないというのが僕の結論だ。

 

そこで僕は考えた。要するにリストアップする専用の書式か何かがあればいいのではないか?

単にリストアップする書式よりも、下層が上層からどのように使われるかを擬似コードで書いていけばその使われ方を集約してくれるものがあればいい。

それなら、プログラム言語をそのまま使った方がいい。

 

そして至った暫定解がこれだ。

まず、WebAPIハンドラを先に書く。

バックグラウンドプロセスがそのファイルを監視し、そのコードのコンパイルが通るようにinterface文を自動生成していく。

例えば、WebAPIハンドラに

var user = db.users.getUser(httpRequest.useID)

というコードがあれば

abstract class DB {
SomeInterface users;
}
abstract class SomeInterface {
dynamic getUser(String arg1);
}

というinterfaceを自動で生成すればいい。

すると、まずコンパイルエラーが消える。もちろん、本当に間違った処理を書いていたらエラーを消して欲しいので、例えば「db.」で始まるステートメントとその子どもっぽいものだけ自動生成すればい。

さらに、一回使ったメンバーは次回からIDEが自動補完してくれる。リファクタリングIDEから直接実行可能だ。

上レイヤーを書き終わったら、interfaceを実装するように下層を書いていけばいい。

僕は今、具体的な実装方法やより詳細なかた情報をバックグラウンドプロセスに伝える手続きなどを色々考えてる。

モデリング関数

僕がモデリング関数と呼んでいる概念の話をしたい。

 

DBから落としてきたデータをもとにビジネスロジックを書くと、関数の引数が増えすぎてうざくなる経験はないだろうか。

例えばTwitterを作っているとして、AさんがBさんをフォローしているか判定する関数はこうだ。

(followsはFollowテーブルのレコードの配列だ)

if(isFollow(toUserID, fromUserID, follows)){
// 処理
}

投稿数が5以上のユーザーをフィルターするのはこうだ。

users = filterPostCount(users, posts, 5);

どうも汚い。

オブジェクト指向言語で書く以上こういう風に書きたい。


if(user.isFollow(toUserID)){
// 処理
}

users = users.filterPostCount(5);

これを実現するためにはそもそも生データ用のクラスとビジネスロジックで使う用のクラスを分けるといい。僕は前者をEntity、後者をModelと呼び、EntityをModelに変換する関数をmodeling関数と呼んでいる。

EntityはDBのスキーマと1:1で対応し、ORMがインスタンスを作ってくれるものだ。


// エンティティ
class UserEntity {
String userID , name;
}

class FollowEntity {
String to , from;
}

// モデル
class UserModel {
String userID , name;
List<FollowEntity> follows;

bool isFollowTo(String to) {
return follows.where((f ) => f.to == to).isNotEmpty;
}
}

// モデリング関数
List<UserModel> modeling(List<UserEntity> userEntity , List<FollowEntity> follows ){
// ここにいろんなフィルターとかマップとか
}

これで目的が達成できた。

欲を言えばEntityとModelを一つのオブジェクトツリーにぶら下げることで、全てのmodeling関数を共通化することができる。そうすれば、どんな時もとりあえずEntityを取得して一種類のmodeling関数にかければいいということになりわかりやすい。


class Entities {
List<UserEntity> users;
List<FollowEntity> follows;
// その他いろんなエンティティ
}

class Model {
List<UserModel> users;
// その他いろんなモデル
}

// モデリング関数
List<Model> modeling(Entities entities ){
// ここにいろんなフィルターとかマップとか
}

この方法のメリットは「そのオブジェクトに関することは、そいつに聞けばなんでもわかる」状態になりコードが綺麗になることだ。

デメリットは、そのビジネスロジックに必要のないところまでモデリングするためリソースが無駄になることだ。

注意点は、Modelに生やすメソッドはそのModelの状態を書き換えてはいけないということだ。単に聞かれたことに答えるだけの関数を書こう。

このような反論が想定される。クラス自体を分けずにEntityにメソッドを生やすだけではダメなのか。それをしようとするといつどのタイミングで関連するEntityをそのEntityに配ればいいのかわからないからダメだ。

今回はサーバーサイドを例に説明したが、本質的にデスクトップアプリでもWebアプリでも使えるテクニックだ。僕はReduxでmapStateToPropsとReducerの先頭でStateをModelに変換している。

考え方の背景を説明しよう。データを保持するということとそのデータをもとに何かを判断するということでは、求められるデータ構造が異なる。

前者は重複がなく、極力1操作で状態を更新でき、矛盾しづらいデータ構造だ。

後者はそのオブジェクトに関することはなんでも知っている方がよく、データの重複は問題ではない。Twitterのフォロワー・フォロイーの話なら、AさんオブジェクトがBさんをフォローしているという情報を持っておりBさんオブジェクトもAさんにフォローされているという情報を持っていてもかまわない。

f(保持用のデータ) = 判断用のデータ

の関係になっており、判断用のデータは毎回保持用のデータから作成されそして使い捨てになっているといい。

 

Active Recordとかの話は長くなりそうなので省略した

10年のプログラミングの変化

お断り:僕は数年前まで東海地方のクソ田舎に住んでたので「時系列がおかしい!」と思った方は、それは都会↔田舎のタイムラグです。東名高速道路オーバーヘッドです。死罪死罪。

Java

一番俺史が長いJavaについて。

まずJavaアップレットがなくなりました。ブラウザでJavaが動くって相当夢広がリングだったと思いますが、支持を得なかったようです。

その後ちょうどJavaFXが満を持して登場して「JavaGUIの覇権来るか!」って時にAjaxからのHTML5の潮流に飲み込まれてしまいました。

その代わりサーバーサイドのJavaは元気みたいですね。

あと、文字列処理系のミドルウェアJavaが強いみたいです。

それに、Javaが「速い」扱いされるようになったのは大きな変化じゃないでしょうか?

レンタルサーバー

昔はCGIを置くのは簡単でしたがプロセス常駐型のプログラムを置く場所を確保するのは大変でした。だからPHPPerlが使われてたんですね。きっと。今ではNode.jsやGoで書いた常駐プロセスを簡単に公開できるので便利ですね。

静的型付き言語に揺り戻し

業界的に「動的型付け言語はちょっと……」という空気が増した気がします。僕は昔のことはよく知らないので「人類はなぜ動的型付け言語なんかを普及させていたのだ」と思ってます。僕が就職する前にいい感じにして置いてくれた皆様ありがとうございます。

>人類が静的型チェックなしの動的スクリプト言語に費やしたこの20年くらいは、将来黒歴史として語り継がれるかもしれません。

六本木ではたらくソフトウェアエンジニアへのよくある質問とその答え (FAQ) (2015 - 2017) — hayato.io

ところでスクリプト言語(ざっくり言ってコンパイルしない言語)で静的型付きってDart以外に何があるんですかね。TypeScriptを直接実行するVMとか出てくるんですかね。

 状態を持たない

我らが伊藤直也さんのこのスピーチ、傑作だと思うんですよね。僕が言いたいことを全部言ってくれてます。

www.publickey1.jp

 この状態を持たないようにするという流れがこの10年の最大の進化だと思います。

 

感想

ちょうど本格的にプログラミングを始めだしたのが10年前くらいだな~。

思えば遠くへ来たものだ。

ホリエモンさんを見て「俺もプログラミングを勉強すれば億万長者になれる」と思ったのです。

想像していたよりはるかにプログラミングができるようになったのに、5,000兆円は手に入りませんでした。