プログラミング

フロントエンドはサーバサイドと同じ轍を踏むか

下記の記事で考えていた「移植性の高いWebアプリのアーキテクチャパターン」や「Fat Controllerをコードで防ぐ方法」は、サーバサイドを主に扱ってきた経験に基づくものだ。

ただ、あらためて考えてみれば、サーバサイドでHTMLを組み立てるアーキテクチャパターンそのものが、Webアプリの移植性を低めている元凶なのだろう。気を抜けばビューロジックがコントローラに漏れ出すし、多くの場合テンプレートエンジン依存のビューテンプレートファイルが大量に作られるし、いざ移植したくなったときには、相当な労力が必要になりがちだ。

となれば、サーバサイドをAPIに特化させるのが一案だろう。APIに特化させれば、少なくともサーバサイドにおいては、ビューテンプレートファイルを作らなくてよくなる。

なんだか、汚いものをフロントエンドに押し付けているようで気が引けなくもないが、ちょうどよいことに、昨今ではサーバサイドをAPIに特化させるのが当たり前になってきている。これは、ブラウザ・Android・iOSなど、さまざまなプラットフォームでアプリを提供するのに適しているからだ。

そうなると、今度はフロントエンド側の移植性が気になってくる。たとえば、Nuxt.jsからNext.jsに乗り換えたい、みたいなニーズがあるのかどうか、あるとしたら労力はどのくらいなのか、といったことだ。

そのあたりの感覚はフロントエンドの経験を積まなければわからない。サーバサイドが生んできたいわゆる技術的負債をフロントエンドも生んでいくことになるのか、もうすでに生みまくっているのか、はたまた、優秀なエンジニアたちが日々返済してくれているのか。ぼくが気にすることではないのかもしれないが、でも気になる。


少し探したら下記の記事が見つかった。「強く依存しているライブラリやツールを減らす」といった表現が出てくる。やはりサーバサイドと同じ轍を踏んでいるようだ。とはいえ、あきらめずに返済を進めているのがすばらしい。

テストコード追加でOSSに貢献、いい

Echo(GoのWebアプリケーションフレームワーク)に送ったテストコード追加のプルリクエストが、送ってから2時間半後にマージされた。貢献といえば大げさかもしれないが、もし自分がメンテナだったら、テストコード追加のプルリクストがいちばんうれしいはずだ。レビューに時間がかからないところが良い。今後も折を見てトライしたい。

ぼくがたまにOSSに貢献したくなるのは、あけすけにいえば、自分の承認欲求を満たしたいからだ。それが人として善なのかどうかはわからない。ただ、誰かに大きな迷惑をかけるわけではないし、メンテナはうれしいだろうし、ぼくも(一時的には)満足できるし、まったくの悪ではないと思いたい。

現在、Echoのカバレッジは85%だ。まずまずの網羅率とはいえ、Codecovで詳細を見ればわかるとおり、テストしやすそうな部分がけっこう残っている。Echoのメンテナはテストコードを書くのが(ぼくと同じく)おっくうなタイプなのかもしれない。

今さらながらGoのインターフェースが面白かった

ようやくGoを勉強し始めました。実装した関数のテストコードを書きながら、Goのインターフェースの面白さを実感しているところです。

たとえば、下記のWebsiteIsUp関数を実装したとします。引数で渡されたURLにGETでアクセスし、ステータスコードが200ならtrue、それ以外ならfalseを返す関数です。

func WebsiteIsUp(url string) (bool, error) {
	resp, err := (&http.Client{}).Get(url)
	if err != nil {
		return false, err
	}
	return (resp.StatusCode == http.StatusOK), nil
}

しかし、この関数はテストしづらいものです。テストする立場に立てば、テスト時には *http.Client のGetメソッドをモックしたくなります。モックしないと、テストの成功/失敗がアクセス先のURLに依存してしまうからです。

というわけで、モックを渡せるようにWebsiteIsUp関数に引数を追加しましょう。さて、どんな型の引数が適切でしょうか?

そこで使えるのがインターフェースです。具体的には、*http.Client のGetメソッドと同じシグネチャのGetメソッドを持つインターフェースです。

type HTTPGetter interface {
	Get(url string) (*http.Response, error)
}

このHTTPGetterインターフェースを定義すると、*http.Client もHTTPGetterインターフェースを持つことになります。面白いですね。この仕組みなら、他のパッケージの構造体がいくらでもモックできます。

さて、WebsiteIsUp関数に引数を追加してみましょう。

func WebsiteIsUp(url string, g HTTPGetter) (bool, error) {
	resp, err := g.Get(url)
	if err != nil {
		return false, err
	}
	return (resp.StatusCode == http.StatusOK), nil
}

これで、必要に応じてモックが渡せます。やった。

type notFoundGetter struct{}

func (g *notFoundGetter) Get(url string) (*http.Response, error) {
	return &http.Response{StatusCode: http.StatusNotFound}, nil
}

func main() {
    isUp, err := WebsiteIsUp("http://example.com/", &notFoundGetter{})
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(isUp)
}

ちなみに、モックしない版のコード例全体はこんな感じです。いやあ面白い。

package main

import (
    "fmt"
    "log"
    "net/http"
)

type HTTPGetter interface {
	Get(url string) (*http.Response, error)
}

func WebsiteIsUp(url string, g HTTPGetter) (bool, error) {
	resp, err := g.Get(url)
	if err != nil {
		return false, err
	}
	return (resp.StatusCode == http.StatusOK), nil
}

func main() {
    isUp, err := WebsiteIsUp("http://example.com/", &http.Client{})
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(isUp)
}
プロフィール
Web開発者。現在の関心事はシステム品質の改善(特に性能効率性と保守性)。JAPAN MENSA会員。
カテゴリ別アーカイブ
記事検索