エレガントに削る / エレガントに実装するということ
BtoBにしろCtoCにしろBtoCにしろ、プロダクトを開発するエンジニアに求められるエレガントさについてふと思うことがあったので書いておく。
言われた仕様や要件をそのまま作ってしまうエンジニアはどんなにコードを書く速度が早かったり難しい実装ができたり、 難しいアルゴリズムを考案することができたとしてもプロダクト開発には向かないと思う。 前提としてリスペクトがあることには留意しておくが、 単純に尖ったエンジニアリングがしたい / その能力が高いならばOSSへコミットしたり言語を作ったり、 研究をやっていたほうが人類を前に進められると思う (どのみち、それらの分野においても要求を「そのまま作る」ことしかできなければ役に立たないと思うが…)
エンジニアに求められるのはある種のエレガントさだ。 ここでのエレガントさは
- エレガントに要求を削れること=問題を適切にハンドルできること.
・必要なもの/解決すべき問題に対して適切に技術を用いることができること. - エレガントに実装できること.
・解決すべき問題を別の問題を誘発せずに解決することができること.
・負債を生まない.
・運用上の手間を増やさない.
・手作業.
・コミュニケーション
の2つに大別されると思う。
A.に関しては、下手に要求を削ってしまうと骨抜きになってしまうし、要求を課題解釈して問題に適切に対処できないと巨大な城を建築することになってしまう。どちらの場合も、リソースを使ってゴミを生生していることにほかならない。チームのスループットを確実に低下させる。
ゴミを作らないためにはAのエレガントさが必要だ。Aのエレガントさを高めるためには、技術的な勘所とビジネスドメイン特有のお作法(何をするとユーザに喜ばれるか)の両方を体得していなければいけない。技術的な勘所は削る判断をするために必要なコストパフォーマンスについての示唆をもたらしてくれる。ビジネスドメインの知見に関しては要求が本質的かどうかを見極めるのに使うことができる。
これらの感覚は意識して磨くことができると思う。磨くための方法論に関しては僕も整理がしきれていない(故に記載はしない)。
B.が何故必要かに関しては、大抵の場合は本番でフルスクラッチでコードを書くことなく、エレガントな実装ができなければリリースまでに必要な時間が膨らむし、変なコードを既存コードに混ぜ込んでしまうことで負債を生んだり、別の問題を生んだりしてしあうことがその理由と言える。
AとBどちらに関しても、ユーザ / チームとのコミュニケーションに長けていることは必要条件だと思う。
エンジニアはコミュ障でもいいみたいな意見もあると思うのですが、実際問題無理ゲーだと思う。
このエレガントはプランナーが別に居て企画を立てる場合でもそうでない場合も成り立つかなと。
Crystalの配列(0)
配列
[1,2,3] [1, "aaa", 'ks']
文字列配列はRubyと同じ表記が可能
%w(one two three)
シンボルの配列
%i(one two three)
配列ライクな型
https://crystal-lang.org/api/0.20.3/Array.html
You can use a special array literal syntax with other types too, as long as they define an argless .new method and a #<< method. Set is one such type:
set = Set(typeof(1, 2, 3)).new set << 1 set << 2
引数なしのnewと <<
が定義されていれば Array特有の文法を使うことができる。
Arrayはgenericな型、複数の型を含む配列の方はUNION型と呼ばれる
[1, "aaa", 'ks']
など
structural subtyping的になっているので、呼び出されるメソッドに応答するればその型の 条件を満たしているとみなされ、コンパイルが通る
=> 不用意に型制約をすると柔軟性が減る
crystalでyamlなど触ってみた
crystal-langが少し流行りだしそうなので触ってみた。
timeの扱い
t = "2011-10-11 11:00:00" t = Time.parse(t_str, "%F %T") puts t
yaml(読み込んだyamlの特定のキーに対応する部分を別の変数に代入したい RubyならばHashでよしなにできるのだけれど、crystal-langには空ハッシュがないので空のYAML::Anyを定義してあげる必要がある。
file_path = "hoge.yaml" y_data = YAML.parse_all(File.read(file_path)) y_data = banner_yaml[0] empty_yaml = YAML::Any.new("")
net/http触り直し(1)
GolangでWAFを使わなくても問題ない的な話が年末上がっていたのと(advent calendar界隈で)自分的にもそう思ったので RequestのBodyやらHeaderやらを適当にParseする方法を復習がてらメモ。
package main import ( "fmt" "log" "net/http" "github.com/davecgh/go-spew/spew" "github.com/julienschmidt/httprouter" ) func main() { router := httprouter.New() router.GET("/test", testFuncGET) router.POST("/test", testFuncPost) log.Fatal(http.ListenAndServe(":9999", router)) } func testFuncGET(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { h := r.Header spew.Dump(h) val := h.Get("Hoge") fmt.Println("=======") fmt.Println(val) fmt.Println("=======") q := r.URL.Query() spew.Dump(q) } func testFuncPost(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { r.ParseForm() f := r.Form spew.Dump(f) }
httprouter
くらいは使わないとさすがにアレなコードになる気はしている。
curl "http://localhost:9999/test" -H "Hoge:Fugaaa" -XPOST -d "a=1" -d "a=2" -d "a=3" -d "vals=[{'key':'value1'},{'key':'value2']"
(string) (len=1) "v": ([]string) (len=1 cap=1) { (string) (len=5) "1,2,3" } } (url.Values) (len=2) { (string) (len=4) "vals": ([]string) (len=1 cap=1) { (string) (len=34) "[{'key':'value1'},{'key':'value2']" }, (string) (len=1) "a": ([]string) (len=3 cap=4) { (string) (len=1) "1", (string) (len=1) "2", (string) (len=1) "3" } }
-d "a=1" -d "a=2" -d "a=3"
この送り方で配列になるのは意外だった。RFCにでもそう書いてあるのだろうか(調べるか)
Goでgitリポジトリを操作しようと軽くトライした話
はじめに
PerlやRubyで色々ツールを作っていた頃
Git::Repository - search.cpan.org
GitHub - schacon/ruby-git: Ruby/Git is a Ruby library that can be used to create, read and manipulate Git repositories by wrapping system calls to the git binary.
この辺りを使って、差分を見てよしなに本番作業の手順書を自動生成したりしたのを思い出して、今でも個人プロジェクトではRubyとかPerlで描いてしまうことが多いのですが、Goでそのあたりを書くためにはどうすればいいか試行錯誤した話を書きます。
使用できるライブラリ
go git などでぐぐると下記ライブラリがヒットします。サンプルコードを動かすなどしてとりあえず触ってみることに (私のローカルの環境はgo1.6.3かgo1.7を使っています)
GitHub - src-d/go-git: A low level and highly extensible git client library
- source{d}社が作っている
- ライブラリを用いてGitHubのリポジトリを解析しているとのこと
- [Topic Modeling of GitHub Repositories · source{d}](https://blog.sourced.tech/post/github_topic_modeling/
- 解析してトピックモデルLDAなどやっている
GitHub - libgit2/git2go: Git to Go. Like McDonald's but tastier.
src-d/go-gitを試す
READMEには
go get -u gopkg.in/src-d/go-git.v4/...
と v4を go get
して利用するように記載されているのですが、READMEに書かれたコードが動かず、出鼻をくじかれました。
% go run main.go (git)-[master] # gopkg.in/src-d/go-git.v4/plumbing/format/packfile ../../../../../../gopkg.in/src-d/go-git.v4/plumbing/format/packfile/scanner.go:158: undefined: io.SeekCurrent ../../../../../../gopkg.in/src-d/go-git.v4/plumbing/format/packfile/scanner.go:314: undefined: io.SeekCurrent
problems install v4 · Issue #107 · src-d/go-git · GitHub こちらに書かれている通り
Hahaha, yes, we only support 1.7, とのことです。goのversionを変えたら正常に動作しました。
go1.6でも、
go get -u gopkg.in/src-d/go-git.v3/...
v3を go get
してくれば無事に動く模様です。
(しかしインターフェースが若干異なるようです)
用例
用例をつらつら書こうと思ったのですが、下記公式の example
がよく整理されています。
go-git/examples at 78516127590ef4a8157f8850be007966b142c1ca · src-d/go-git · GitHub
git pull
でデプロイしている個人プロジェクトがあるので、それらのdeploy用の簡単なツールなど書いてみました。
(shellとかansibleで事足りるんじゃないかという話はあります)
複数の開発用サーバ(ホストに番号が振られている)などに一斉に変更を反映したい場合のツール作りなどに使えそうです (適当に全サーバにはいってgit pullなど)
libgit2/git2goを触る
lib2git自体が必要なので、Macで利用する場合はにhomebrewでdownloadします。
brew install lib2git
Github側にはドキュメントが少ないので godoc
をせっせと読みながら使う感じになります。
git - GoDoc
使い方としてはgit側の使いたい機能を godoc
と lib2git
のドキュメとから引いて対応させていく感じです。
st
package main import ( "fmt" "github.com/libgit2/git2go" ) func main() { repoPath := "path_to_git_repo" r, err := git.OpenRepository(repoPath) if err != nil { panic(r) } // conf := r.Config st, err := r.StatusFile(repoPath) fmt.Printf("%v", st) stlist, err := r.StatusList(&git.StatusOptions{ Flags: git.StatusOptIncludeUntracked, Show: git.StatusShowIndexAndWorkdir, }) if err != nil { panic(err) } fmt.Println("%v", stlist) }
まとめ
lib2gitに依存しないという意味では src-d/go-git
はそれなりに使いやすそうだという感想です。 lib2gitを利用する方がAPIは色々提供されているし(各種バインディングがすでにあることで枯れている)ため、痒いところに手が届かないということはなさそうに見えました。
【翻訳】【Golang】標準的なパッケージのレイアウト
Golangレベルアップしたいので色々な記事を翻訳して整理してみようと思った(会社でもそういう試みをやっているので真似っこです)
Standard Package Layout — Medium
vendoring、Generics。Goコミュニティではこれらは大きな問題とみなされてきた。そしてほとんど言及されることのないもう一つの問題がある。アプリケーションのパッケージのレイアウトだ。
私がこれまで開発してきたGoアプリケーションは、「自らのコードをどう構造化すればよいのか?」という問題に対してそれぞれ異なる答えを出してきた。すべてを1つのパッケージに押し込めたアプリケーションもあれば、型やモジュールごとにグループ化したアプリケーションもある。チーム全体で一貫した良い戦略がなければ、コードはアプリケーションの種々のパッケージにとっちらかってしまうだろう。われわれは、Goアプリケーションの設計のためにより良い基準を必要としている。
よく普及している欠点のあるアプローチ
Goのアプリケーションの構造化へのよく普及しているアプローチはそんなに多くないように見える。そして、それぞれ特有の欠陥がある。
アプローチ1: モノリシックなパッケージ
すべてのコードを1つのパッケージに投げ込むアプローチは実際問題、小さなアプリケーションに対してはうまくいく。アプリケーションの内部に限れば、何の依存性もないのだから、循環参照の可能性が全く取り除かれるのだ。
アプローチ2: Rails方式のレイアウト
もう一つのアプローチは機能的な型ごとにコードをグルーピングすることである。 例えば、すべてのハンドラを1つのパッケージに、すべてのコントローラを別のパッケージに、モデルをさらに別のパッケージにと言った具合だ。このアプローチは元Rails開発者(私自身を含む)が書くコードに多々見られる。
しかし、このアプローチには2つの問題がある。第一に、名前が嫌な感じだ。結局最後にはcontroller.UserControllerみたいな型名に落ち着くことになり、パッケージ名と型名が重複するのだ。 私は命名にはこだわる癖がある。コーディングに困った時、命名が一番よいドキュメントであると私は考えている。命名は品質の目安にすることができる。誰かがコードを読むときに、名前を最初に知ることができる。 しかし、より大きな問題は、循環参照である。機能ごとの異なる型がお互いに参照しあう必要があることがあるだろう。このレイアウトアプローチは、一方向の依存関係を持っている場合にだけうまく働くが、多くの場合は、アプリケーションはそう単純ではない。
アプローチ3: モジュールごとのグループ化
このアプローチはRails方式のレイアウトと機能ごとにまとめる代わりにモジュールごとにグルーピングすることを以外は似通っている。例えば、usersパッケージとaccountsパッケージを持つことになるだろう。
より良いアプローチ
私が自分のプロジェクトに用いているパッケージ戦略は4つの教義から成っている。
- ルートパッケージはドメインタイプのために存在する
- 依存関係毎にサブパッケージをグルーピングする
- 共有された「モック」サブパッケージを用いる
- メインパッケージは依存関係を結びつける
ルートパッケージはドメインタイプのために存在する
アプリケーションはどのようにしてデータと処理が相互作用するかを記述する論理的な工事の言語を持っている。その言語はドメインである。Eコマースのアプリケーションであれば、ドメインは顧客と口座と、クレジットカード、そして在庫管理を含む。もしFacebookならば、ドメインはユーザ、いいね、そして関係性である。ドメインは、背後にある技術要素に依存しない物事のことである。
私はドメインタイプをルートパッケージに配置する。このパッケージは、ユーザのデータを保持するUser構造体や、ユーザのデータをフェッチしたり保存したりするためのUserServiceインターフェイスなどの単純なデータ型のみで構成される。
下記のような感じである。
これにより、ルートパッケージは非常にシンプルになる。アクションを形成する型を含めても良いが、それはそれらの方が他のドメインタイプに依存している時のみ許可される。例えば、定期的にUserServiceを呼び出す方を持つこともあるかもしれない、しかしながら、その型は外部サービスを呼び出したり、でデータベースへ保存するべきではない。それらは実装の詳細である。 ルートパッケージはアプリケーションの他のどのパッケージにも依存するべきではない。
依存関係毎にサブパッケージをグルーピングする
ルートパッケージが外部への依存性を持つことを許可されないとしよう、そのとき、われわれはそれらの依存関係をサブパッケージに押しめなければならない。パッケージレイアウトに対するこのアプローチでは、サブパッケージはドメインと実装をつなぐアダプターとして存在する。
例えば、UserServiceはPostgreSQLをは背後に持つかもしれない。postgres.UserServiceの実装を提供するpostgresのサブパッケージをアプリケーションに導入することができる。
この方法でPostgreSQLの依存関係を分離することができ、テストをシンプルにし、将来的な別のデータベースへの移行の簡単な方法が提供される。BoltDBのようなほかのデータベースの実装をサポートすることを決める場合、プラガブルなアーキテクチャとして利用することもできる。 このアプローチはレイヤー化実装の方法を提供する。もしかすると、postgreSQLの前段にインメモリのLRUキャッシュを持ちたいかもしれない。PostgreSQL実装をラップすることのできるUserServiceを実装したUserCacheを追加することができる:
われわれはこのアプローチを標準ライブラリにも見ることができる。io.Readerはバイトを読み込むためのドメインタイプであり、その実装は依存関係毎にグルーピングされている。つまり、tar.Reader, gzip.Reader, multipart.Readerである。これらは同様にレイヤー化可能である。os.Fileはbufio.Reade にラップされ、bufio.Readerはgzip.Readerにラップされ、gzip.Readerはtar.Readerにラップされる。
依存関係の間の依存関係
依存関係は孤立して存在しているわけではない。ユーザデータをPostgreSQLに保存するかもしれないが、金銭にまつわるトランザクションのデータはStripeのようなサードパーティーに存在しているかもしれない。この場合、われわれはストライプの依存関係を論理的なドメインタイプを持ってラップし、それをTransactionServiceと呼ぼう。
type UserService struct { DB *sql.DB TransactionService myapp.TransactionService }
今、われわれの依存関係は共通のドメイン言語を通じてのみつながっている。
サードパーティに依存関係にだけ制限しないこと
奇妙に聞こえるかもしれないが、私は同じ方法論で私の標準ライブラリの依存関係を分離している。例えば、net/httpパッケージもただの1つの依存にすぎない。我々はhttpサブパッケージを我々のアプリケーションに含めることで、同様に依存関係を分離することができる。 ラップしている依存関係と同じ名前のパッケージを持つことは奇妙に見えるかもしれないが、意図的である。net/httpをアプリケーションの他の場所で使うことを許さないかぎり、パッケージ名の衝突は起こりえない。すべてのHTTPのコードをhttpパッケージへ分離することが要求されることが、名前を重複させることの恩恵である。
いま、http.HandlerはドメインとHTTPプロトコルのアダプターとして振る舞う。
共有された「モック」サブパッケージを用いる
われわれの依存関係は他の依存関係からドメインインターフェイスによって分離されているから、われわれは接続点を持っく実装を差し込むために使うことができる。 GoMockのようなモックをするためのライブラリはいくつか存在するが、私は個人的にはそれらを自分自身で書くことを好む。多くのモックのためのツールが過度に複雑であると感じている。 私が用いるモックはとてもシンプルである。例えば、UserServiceのモックは下記のような感じである。
このモックにより、引数をバリデーションする目的や、期待されたデータを返すため、機能不全を差し込むためにmyapp.UserServiceインターフェイスを使用しているあらゆる箇所に関数を差し込むことができる。 例えば、われわれは上で定めたhttp.Handlerをテストしたいとする。
メインパッケージは依存関係を結びつける
あちらこちらのパッケージのあらゆる依存関係を分離したので、どのようにしてすべてを一緒にするのか悩ましくなるでしょう。そしてそれは、mainパッケージの役割です。
メインパッケージのレイアウト
アプリケーションは複数のバイナリを生成することがありえるため、われわれはcmdパッケージのサブディレクトリとしてメインパッケージを配置するというGoの規約を用います。例えば、われわれのプロジェクトはmyappサーバのバイナリをもつが、それと同時にサーバをターミナルから制御するためmyappctlクライアントのバイナリを持つかもしれません。
myapp/ cmd/ myapp/ main.go myappctl/ main.go
コンパイル時に依存関係を注入する
「依存性の注入」という用語はいわれのない避難を受けている。 しかし、オブジェクトをビルドすることや依存関係それ自体を発見することを要求する代わりに、オブジェクトに依存関係を渡すことこそが、用語の真に意味するところです。 メインパッケージはどの依存関係をどのオブジェクトに注入するかを選択するものです。メインパッケージは部品を単純に組み合わせているから、とても小さく自明なコードになる傾向があります。
メインパッケージもまた1つのアダプタであることは需要です。メインパッケージはターミナルとドメインを接続します。
結論
アプリケーションの設計は難しい問題です。設計上決めなければいけないことは大量jに有り、ソリッドな原理がないことには問題はより悪い方向に行くでしょう。われわれはいくつかのGoアプリケーションの設計に対する現行のアプローチを見て、多くの欠陥を見てきました。
依存関係の観点で設計にアプローチすることでコードの構造化を論理的に考えることがよりシンプルで簡単になると考えています。まずはじめに、われわれはドメイン言語を設計します。それから、依存関係を分離し、その次にテストを分離するためにモックを導入しました。そして最後に、われわれはメインパッケージの中にすべてを結びつけるのです。
これらの原理原則を次にアプリケーションを設計するときに考慮してみてください。設計に関して議論や疑問がある場合は、Twitterの @benbjohnsonにコンタクトをとるか、Gopherのslackで私を見つけてみてください。