本文はMediumにあります。
Goの勉強がてらにPythonで書いたCLIをGoで書き直した話
こんにちは。社会人2ヶ月が経過したたんごです。 社内研修も終わり部署配属されてはや2週間が過ぎました。本当に早いw
さてさて、部署配属されてからほとんどRubyばっかり書いていてPython触っていないんですが最近RustとGoに興味が出てきまして、せっかくなので少し前に書いたCLIをGolangで書き直してGoを学ぼうという魂胆です。
- 前提条件
- 文法を学ぶ
- Range
- CLI の設計
- JSONの読み込みと解析
- APIに実際に接続
- CLIの完成
- CircleCI 2.0を使ってテストする
- テストカバレッジの計測
- Windowsで動作を保証するためにAppveyorを使う
- ここまでで役に立った資料、手法など
- 疑問
- まとめ
前提条件
こう書くだけだと2つしかやること無いし楽じゃんと思いましたがそううまくは行きませんでした。
文法を学ぶ
まず、Goのデータ構造と文法を知らないのでそこを学ぶ必要がありました。 とりあえず公式にあるA Tour of Goをそのまま読み進めていきました。Pythonで書いていたときと所々異なる点があったのでそれだけメモしておきます。(とはいえ公式を読めばわかるんだけどね)
基本的には A Tour of Go をそのまま読み進めていくものです。ただ個人的に気になったことやメモを残しておこうと思い記事にしました。
配列
Pythonだと何も考えずに [ ]
に値を放り込むだけですが、Golangでは配列の長さを1度決めたら変えることはできないし、型も決まっています。
定義の基本
var <変数名> [長さ]<型>
また、2通りあります。1つは var
で型を確定させて定義するもの。また、2番めのように初期値をそのまま決め打ちで初期化することもできます。
var a [2]string a[0] = "Hello," a[1] = "world" primes := [6]int{2, 3, 5, 7, 11, 13}
スライス
配列は固定長のサイズであるのに対し、スライスは可変長サイズです。容量が足りなくなると自動的に領域を広げてくれます。
生成にはビルトイン関数の make
を使います。
a := make([]int, 5)
また、スライスに値を追加したい場合は append
という関数を用います。append
関数は第1引数が値を追加したいスライスを入れ、残りの引数は追加したい値を入れます。
package main import "fmt" func main() { var s []int s = append(s, 0) printSlice(s) s = append(s, 1, 2, 3, 4, 5) printSlice(s) } func printSlice(s []int) { fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s) }
実行結果は以下のようになる。
len=1 cap=1 [0] len=6 cap=6 [0 1 2 3 4 5]
Pythonでは、[1, 2].append(10)
のようにしますがGoはオブジェクト指向言語ではなく命令形言語なのでこうなります。
Range
RangeははPythonで言うところの enumerate と同じ挙動をしてくれます。雰囲気的にはスライスにアクセスする際にイテレータ操作ができるすぐれものです。
Pythonでこのような処理は
pow = [1, 2, 4, 8, 16, 32, 64, 128] for i, v in enumerate(pow): print("2**{i}={v}".format(i=i, v=v))
Golangではこのように書けます。
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128} for i, v := range pow { fmt.Printf("2**%d=%d\n", i, v)
辞書
Pythonではdict型をGolangではMapといいます。用語の違いなので覚えるしかなさそうです。
スライス同様で make
関数でも定義できます。
Goではmapはミュータブルなものであるので、最初にmapを定義してしまえば値を挿入したり、キーによる参照や削除が可能になっています。
package main import "fmt" func main() { m := make(map[string]int) // stringがキー。値はint m["answer"] = 42 fmt.Println("The value: ", m["answer"]) // 取得 m["answer"] = 28 fmt.Println("The value: ", m["answer"]) // 上書き delete(m, "answer") fmt.Println("The value: ", m["answer"]) // 削除 v, ok := m["answer"] fmt.Println("The value: ", v, "Present?", ok) // 存在するか }
削除した後に参照すると例外等は発生せず、初期値が変えるようになっています。また第2引数で存在可否が判定できます。
Exercise: Maps
A Tour of Goの練習問題として文字列s
に含まれる単語を数え上げるプログラムを書きました。
package main import ( "strings" "golang.org/x/tour/wc" ) func WordCount(s string) map[string]int { m := make(map[string]int) for _, key := range strings.Fields(s) { _, exist := m[key] if exist { m[key] += 1 } else { m[key] = 1 } } return m } func main() { wc.Test(WordCount) }
ちなみに実行結果はこんな感じになります。
$ go run aaa.go PASS f("I am learning Go!") = map[string]int{"I":1, "am":1, "learning":1, "Go!":1} PASS f("The quick brown fox jumped over the lazy dog.") = map[string]int{"jumped":1, "over":1, "lazy":1, "dog.":1, "The":1, "quick":1, "brown":1, "fox":1, "the":1} PASS f("I ate a donut. Then I ate another donut.") = map[string]int{"I":2, "ate":2, "a":1, "donut.":2, "Then":1, "another":1} PASS f("A man a plan a canal panama.") = map[string]int{"man":1, "a":2, "plan":1, "canal":1, "panama.":1, "A":1}
関数
Golangの関数には引数に関数を与えることができるよという話でした。またGoの関数はクロージャらしいです。
例題としてフィボナッチ数列をクロージャを使って書いてみました。
package main import "fmt" func fibonnacci() func() int { p, n := 0, 1 return func() int { p, n = n, p+n return n } } func main() { f := fibonnacci() for i := 0; i < 10; i++ { fmt.Println(f()) } }
こんな感じ。
メソッド
Goにはクラスがないため、メソッドは存在しないはずです。けれども型にメソッドを定義できるという話みたいです。このときはfunc
キーワードとメソッド名の間に引数リストを書きます。これらをポインターレシーバーといい、ポインターレシーバーがないと、自分の型に定義したメソッドで値を書き換えることができません。
例えばA Tour of Goのコードを拝借して自分なりの言葉でまとめます。
package main import "fmt" type Vertex struct { X, Y float64 } func (v *Vertex) Scale(f float64) { v.X = v.X * f v.Y = v.Y * f } func main() { v := Vertex{3, 4} v.Scale(10) fmt.Println(v) }
Scale
は型 Vertex
のメソッドで、引数に10を与えると結果は次のようになります。
{30 40}
これに対し、もし変数レシーバーとした場合、つまり
- func (v *Vertex) Scale(f float64) { + func (v Vertex) Scale(f float64) {
のように書くと次のような結果が得られます。
{3 4}
つまり、変数レシーバーの場合は元のVertex変数のコピーを操作しているため、呼び出し元の値は変更されません。
interface
最初公式ドキュメントを読んだときは全く意味がわからなかったのですが、簡単に言うと自分で好きな型を決めることができるみたい。
後々JSONをマッピングする際にこれを使いまくります。多分ですが、The empty interfaceから先は読んでおいたほうがいいと思います。
その他
今回は使いませんでしたが、Go Routine等もきちんと勉強しておくと並行処理をする際に役立つかもしれません。
さて、Go言語の大体の学習にPythonとの比較を踏まえてやっていたため、だいたい2時間程度を要しました。ここからは実際にCLI形式のプロジェクトをPythonからGoに書き直していきます。
CLI の設計
移植前のPythonのコードはこちらになります。
挙動としてはREADMEに書いてあるとおりですが、factordb という数字を投げつけると素因数分解の結果を返してくれるサイトがあります。(ちなみに毎度素因数分解を実行しているのではなく、DBというようにデータベースから既知の値を返してくれるだけです) 例えば、16なら 2 ^ 4 のように返してくれます。CLIでは次のように使います。
$ factordb 16 2 2 2 2
また、オプションに --json
をつけるとAPIからの返り値を使いやすいJSONの形式に変形して出力してくれ、かつもう少し詳細を出してくれます。
$ factordb --json 16 {"id": "https://factordb.com/?id=2", "status": "FF", "factors": [2, 2, 2, 2]}
のコードを参考に書きました。
最初に↑を参考にオプションを引き取って適当にCLIっぽい挙動をするコードを書きました。
package main import ( "fmt" "os" "github.com/codegangsta/cli" ) func main() { app := cli.NewApp() app.Name = "factordb" app.Usage = "The CLI for factordb.com" app.Version = "0.0.1" // Global option app.Flags = []cli.Flag{ // --json cli.BoolFlag{ Name: "json", Usage: "Return response formated JSON", }, } app.Action = callAction app.Run(os.Args) } func callAction(c *cli.Context) error { var isJson = c.GlobalBool("json") if isJson { fmt.Println("Response json type") } var paramFirst = "" if len(c.Args()) > 0 { paramFirst = c.Args().First() } fmt.Printf("Hi, I am receiving the number %s\n", paramFirst) return nil }
https://github.com/ryosan-470/factordb-go/blob/acd2fbb6c5e9bf7f84e7c2bd7e90001c3337b012/cli.go
↑のコードはもし、--json
がついていると標準出力に Response json type
と、あとは引数をHi, I am receiving the number 16
みたいな形で出力してくれるプログラムです。このままどの環境でも動かせると思います。
JSONの読み込みと解析
次に、JSONを読み込んでGolangの構造体に置き換えます。Golangでは
などにあるように、json.Unmarshal
などで解析して構造体に置き換えることでコード内でよしなに扱うことができるみたいです。しかしFactorDBのAPIにアクセスしてみればわかりますが返ってくるJSONはお世辞にもキレイなものとはいえません。
$ http http://factordb.com/api\?query\=10 HTTP/1.1 200 OK Connection: Keep-Alive Content-Encoding: gzip Content-Length: 67 Content-Location: api.php Content-Type: application/json Date: Wed, 07 Jun 2017 09:23:37 GMT Keep-Alive: timeout=5, max=100 Server: Apache TCN: choice Vary: negotiate,Accept-Encoding { "factors": [ [ "2", 1 ], [ "5", 1 ] ], "id": "10", "status": "FF" }
この factors
をどう表せばよいのかわからずつまりました。そこで色々調べてみると次のようなブログに当たりました。
書いてある通りで申し訳ないのですが、何も考えずに interface
に投げ込めばいいみたいです。あとは前学んだように自分でよしなに型を変えていきます。今回は2つの構造体を用意しました。
type FactorDB struct { Status string Id string Factors []Factor } type Factor struct { Number int Power int }
FactorDB
構造体は返ってくるレスポンスを表しています。Status
や Id
は string
で問題ないでしょう。Factors
だけはArrayで返ってくるので自分でさらに中身を定義し直します。
公式APIドキュメントなど存在しないので自分で勝手に解釈していますが、factors
は文字列の方を残りの数でべき乗するというような形になっていると推定しましたw なのでこのような構造体で扱えると思います。
最終的にJSON文字列を引数にとり、FactorDB
構造体を返すGoのプログラムは次のようになりました。
func ConvertToFactorDB(b []byte) FactorDB { var base interface{} err := json.Unmarshal(b, &base) if err != nil { log.Fatal("Cannot parse your input") } s := base.(map[string]interface{}) var factor FactorDB factor.Status = s["status"].(string) factor.Id = s["id"].(string) factors := s["factors"].([]interface{}) for _, f := range factors { tmp := f.([]interface{}) number, _ := strconv.Atoi(tmp[0].(string)) power := int(tmp[1].(float64)) factor.Factors = append(factor.Factors, Factor{number, power}) } return factor }
(こんな感じで簡単に書いてありますがだいたいここまでに3時間を要しましたw)
また、さりげなくですが、packageを分けました。まだGoのパッケージの切り方があまり良くわかっていませんが
How to Write Go Code - The Go Programming Languageの Your first library という部分を参考に切り分けました。
書いてある通りに $GOPATH/src/github/ryosan-470/factordb-go
のような場所にファイルを置き、go build
で何も文句が言われなければとりあえず OK というスタンスで行きますw
テストコードを書く
最近、コードを書くときにテストを追加していくというのが私の中で流行っているので、テストも書きましょう。
同様にHow to Write Go Code - Testingの章を見て、書きました。
Goには assert
のような関数はなく、比較演算子 ==
も基本型以外では動作しないようでしたので、自分で比較するような関数を別途テストに組み入れます。
- https://github.com/ryosan-470/factordb-go/blob/af37df2ea6527c3e02aae1140dfaeee69844a28e/handler/response_handler_test.go
- https://github.com/ryosan-470/factordb-go/commit/1fb069da61dba247e61add1673f6d2cbeea481d7
APIに実際に接続
次に実際にAPIに接続し、リクエストを送って、先程↑で作った構造体に直してやるプログラムを書きます。
なお、先程の FactorDB
構造体は汎用的すぎたため FactorDbResponse
という構造体になっています。
https://github.com/ryosan-470/factordb-go/commit/c928408522e91d8dc22888c4f9a5bd82181f37b9
さて、まず構造体の定義をしました。
type FactorDB struct { Number int Result handler.FactorDbResponse }
こうすることでmain関数内で先に var factordb FactorDB
として初期化し、CLIで第1引数として数字を受け取ったあとに factordb.Number = 16
みたいな形で、値を保持できます。
Pythonで言えばこんな感じで使いたいという気持ちだけでも伝わってほしいですw
class FactorDB(): def __init__(self, n): self.n = n self.result = None
あとはGoから実際に接続して値を取得しましょう。
func (f *FactorDB) Connect() error { values := url.Values{} values.Add("query", fmt.Sprintf("%d", f.Number)) resp, err := http.Get(fmt.Sprintf("%s?%s", ENDPOINT, values.Encode())) if err != nil { return errors.New("cannot connect" + ENDPOINT) } defer resp.Body.Close() b, err := ioutil.ReadAll(resp.Body) if err != nil { return errors.New("empty body") } response := handler.ConvertToFactorDB(b) f.Result = response return nil }
出ました。これが ポインターレシーバーですねw こうするオブジェクト指向プログラミングで言うところのメソッドのように扱えます! あとはmain 関数等で以下のような感じで呼び出せば OK です。
if err := factordb.Connect(); err != nil { fmt.Println(err) }
また、Pythonのメソッドでいうところの
- ID を取得する:
get_id
- Status :
get_status
get_factor_list
に相当するポインターレシーバー関数を定義します。ちなみに最後の get_factor_list
はAPIから返ってくるふざけた形式をいい感じに返してくれるメソッドで以下のようなコードで書かれています。
def get_factor_list(self): """ get_factors: [['2', 3], ['3', 2]] Returns: [2, 2, 2, 3, 3] """ factors = self.get_factor_from_api() if not factors: return [] ml = [[int(x)] * y for x, y in factors] return [y for x in ml for y in x]
リスト内包表記をバリバリ使っているので読みづらいですが動きとしては書いてあるとおりです。
Goでは次のように書きました。
func (f *FactorDB) GetFactorList() ([]int, error) { if f.Empty() { return []int{}, errors.New("Empty Result") } var ret []int for _, f := range f.Result.Factors { for i := 0; i < f.Power; i++ { ret = append(ret, f.Number) } } return ret, nil }
うーんめっちゃ愚直w
テストコードとこれらのメソッドのDiff差分は以下です。 https://github.com/ryosan-470/factordb-go/commit/1657c0397571762061f1c7112c512bd11498c99d
CLIの完成
さて、これら3つを組み合わせてCLIを作成します。動作としては単純で第1引数の値を受取り、FactorDB
構造体の Number
に代入し、FactorDB.Connect
を呼び、あとはよしなに各関数で値を整形して標準出力に表示するというものです。
強いて言えば JSON 形式で返すときに物凄く無理やりArrayからJSON文字列を書き出しているところが少し気持ち悪いです。
func callAction(c *cli.Context) error { var number = "" if len(c.Args()) > 0 { number = c.Args().First() } n, err := strconv.Atoi(number) if err != nil { log.Fatal("Your input is not number") } f := factordb.FactorDB{Number: n} if err := f.Connect(); err != nil { log.Fatal("Connection Error") } factors, _ := f.GetFactorList() var output string var isJson = c.GlobalBool("json") if isJson { id, _ := f.GetId() status, _ := f.GetStatus() var fs []string for _, f := range factors { fs = append(fs, fmt.Sprintf("%d", f)) } facs := fmt.Sprintf("[%s]", strings.Join(fs, ", ")) output = fmt.Sprintf("{\"id\": \"https://factordb.com/?id=%s\", \"status\": \"%s\", \"factors\": %v}", id, status, facs) } else { output = strings.Trim(fmt.Sprintf("%v", factors), "[]") } fmt.Printf("%s\n", output) return nil }
とりあえず、動くっぽいコードは以下のコミットハッシュでできました。
https://github.com/ryosan-470/factordb-go/commit/796c3a6828209561d17bda8813721355b74dfc5e
次に Ruby で言うところの Gemfile 的なもので外部パッケージのバージョンを管理します。Goではglideというものを使うとできるそうです。
https://github.com/ryosan-470/factordb-go/commit/ffeb93d6f289e3a35082c04aaaf574f762a0c1c6
CircleCI 2.0を使ってテストする
さて、ここまで来たら今度はCIを回しましょう。有名なCIサービスにはTravisやCircleCIがありますが最近はCircleCI 2.0の高速さに驚かされているため、CircleCIを採用しました。CircleCI 2.0とはなんぞやという人は私が過去に書いた記事を読んでみてください。 ビルドにVMを利用せずDockerコンテナがそのまま扱えるため、ビルド開始も高速に行われ非常に便利です!
config.yml は以下のとおりです。
version: 2 jobs: build: docker: - image: circleci/golang:1.8 # CircleCI Go images available at: https://hub.docker.com/r/circleci/golang/ working_directory: /go/src/github.com/ryosan-470/factordb-go steps: - checkout - run: curl https://glide.sh/get | sh - run: make deps - run: make - run: name: Running test code command: make test
さりげなくビルドはすべて Makefile
に書いてありますが make test
の挙動としては
go test -v $(go list /... | grep -v /vendor/)
となっています。これにより、Glideで入ってくる外部ライブラリをテストせず自分のコードのみをテストすることが可能になっています。
テストカバレッジの計測
今回はテストカバレッジの計測とビジュアル化のために、Codecov を使いました。
Goでcoverageを測定するには難癖があるみたいでいろいろ調べてでてきたものを使用しています。
https://github.com/ryosan-470/factordb-go/commit/25e3dc1277678e883560191b7944285eab58da67
記事執筆の段階でカバレッジ率が59%でGo lintなどの測定もはいっていないのでそれらも入れていきたいなと思っているところです。
Windowsで動作を保証するためにAppveyorを使う
ここまで来たからにはWindowsでビルドし動くことも確かめたいなと思いました。WindowsでCIを行えるサービスのうち有名なものにAppveyorがあります。はじめて使ってみましたがそこまで難しくなかったのでその情報も共有しておきます。
特にいじらなければ、Windows Server 2012上でテストが行われるようです。
appveyor.yml
version: 1.0.{build} platform: x64 clone_folder: c:\gopath\src\github.com\ryosan-470\factordb-go environment: GOPATH: c:\gopath install: - set PATH=C:\msys64\mingw64\bin;%PATH% - echo %PATH% - echo %GOPATH% - go version - go env - go get -v -t -d ./... build_script: - go build -o factordb.exe - go test -v -race ./... artifacts: - path: factordb.exe name: binary
そんなに難しいことはしていないですね。ただ、Makefileが使えないのでその部分だけ手書きしたという感じでしょうか。一応テストも行っているのでもしWindowsではビルドできないみたいな状況が起きてもテストで落ちてくれると思います。
WindowsのCIの結果:
Python版のCLIでもWindowsで動くことには動きますが、Windowsには元々Pythonはいっていませんし、文字コードの問題で動かなかったりといろいろあるのでバイナリが各環境で吐けるGoはいいですね!
ここまでで役に立った資料、手法など
基本的に1次情報はGoの公式ドキュメントで、あそこを読めば大体のことはわかります。ただ、如何せん初心者なので意味を汲み取れないなどのときに適当に検索していると大抵同じようなものにぶつかった人がいるのでそこから情報を得ました。
fmt モジュールが便利
fmtモジュールでは、値をただ出力するだけでなくその値の型や値の中身を表示することができます。
fmt - The Go Programming Language
一番上に書いてあるとおり、
%v
: デフォルトフォーマットで値を表示する。構造体を表示するときは%+v
を使う%#v
: 値のGo言語構文表現%T
: 値の型のGo言語構文表現
これらを使っていい感じにデバッグしていました。型エラーが大抵多い気がするのでこれでprintfデバッグして確認しましょう。
gore を使うと REPL が動かせる
毎度毎度ちょっとしたコマンドの確認にコンパイルして実行は面倒くさいのでREPLがあるとはかどります。
疑問
- Packagingの切り方がよくわからない
- Go routineを使う場所がなかったのでその辺を勉強したい
まとめ
GoはPythonから移行してもそこまで難しくなく簡単に書くことができました。また、Windows用のバイナリをはけるなどLinuxやMacで日々開発している人にとって他のディストリビューション用にも開発できるのは非常にありがたいです。
もし、興味を持ってくれたら、このリポジトリを Star してね♡
今度はSlackのBOTをGoで書き直してみようと思っています。
お一人様マストドンを運営してみて2週間が過ぎ去った
こんにちは。約2週間前のマストドンブームに乗っかりAWSに建ててから大体2週間が経過しました。
上の記事は帰りの電車でツイッターを見ていたらマストドンが流行っているらしいということを知ったのでそのまま夕飯を食べつつ1時間程度で建てたものを記事にしたものですが、2週間経って実際おひとりさまマストドンってどうなのよというあなたの質問に答えるよwというようなものです。
現状
instances.mastodon.xyz のリストによると現状ではこんな感じです。
Uptimeは99 % 超えなのでダウンタイムは総計で140分程度です。(結構多いなw)
弊ブログの前の記事からお試し登録をしてくださる方が幾人かいたので登録者数は15人になっていますが実質ローカルタイムラインは自分のトォートのみが占めています。
また、弊サーバーの https://mastodon.jtwp470.net/about/more のページをいい感じに書き直してみました。
書いてある通りですがサーバースペックは以下のようになります。
- AWS EC2 t2.micro @ 東京リージョン
- vcpu: 1
- mem: 1 GB
- ELB: 30 GB
Datadogによる監視
AWSインスタンスを建ててからすぐにログ取りをするためにDatadogをインストールしました。なんでDatadogを使っているかというと個人の好みなので、日本人ならはてなが提供しているmackerelもおすすめです。
CPU使用率
みてわかるように極めて平和的な使用量です。アクセス数が少ないのでこんな感じかなと思っています。
たまにCPU使用率が伸びている場所がありますが大抵その部分はマスタドンの新しいバージョンアップによる際のインスタンス内での docker-compose build
等でCPUを食っている部分です。
メモリ使用率
メモリ使用率に関しては4/17の午後2時ごろから一度大きく下がっている部分があります。
もともと初めてインスタンスを建ててマストドンを入れておくとだいたいメモリ使用率が80 % から 90 % 程度になっており、この状態で新しいリリースのための docker-compose build
などをするとメモリが爆発して死んでしまう現象が結構起きました。そこでこの時間からスワップを作成して適用してみたため、スワップ適用後はかなり快適なメモリ使用量になりました。
スワップ
スワップの作成は次のコマンドでできます。
sudo fallocate -l 4G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
これでディスクを4GB分犠牲にしてスワップメモリが生成されます。
スワップは大体400 - 500 MB分程度利用されているみたいです。
ディスク使用量
ディスク使用量に関しては基本的に右肩上がりで伸びていますが最近は止まってきました。 はじめのでかいディスク使用率の上昇に関しては何度も述べているとおり上でスワップを作ったためです。 向上的に上昇しているのは自分が画像をアップロードしているというよりもpawooのインスタンスのアカウントをフォローしていると連合タイムラインで流れるようになりますが流れてきた画像すべてを保存するようになっているらしく右肩上がりで上がっていく状況になっているようです。しかし最近は私のTLで流れなくなってきたためかディスク使用量も圧迫しないようになりました。
またディスクが足りなくなってきたら docker-compose build
でビルドした古いイメージを削除してしまえば少しではありますがディスク使用量が空いてくれます。
その他補足情報
ツイッターでのツイートをマストドンに流す
まだマストドンよりもツイッターのほうが常駐している確率が高いのでツイートしたら自分のマストドンにも流すということをしています。そのためツイートがほとんどそのままマストドンに流れています。
やり方は Qiitaの記事 Twitterの投稿をIFTTTでMastodonへ転送する をみてやってください。ただ手法としてはBashコマンドを打てないとちょっとつらいところがあるのでもしかすると以下のようなサービスを使ったほうが簡単かもしれません。
とりあえず、ツイッターのほうがまだ活発だよ〜という人はこういう感じで流すとみた感じマストドンにもいるみたいな感じになってよいかなぁと思います。
Android, iPhone 用クライアントアプリ
使ってみてのおすすめですがクライアントアプリでは先日pixivがリリースしたpawooです。 別にpawoo.net にアカウントを持っている必要はなく自前インスタンスにもログインできて使えるので重宝しています。ありがとうpixiv!
pixivさんに感謝の気持ちを込めて僕がインターンに行ったときの過去記事を上げておくので学生さんぜひインターンに行ってあげてください。(誰目線だよw)
これからやってみたいこと
AWSの無料枠があるうちにRedisとDBをそれぞれDockerコンテナから剥がしてしまい、それぞれAWS RDSとElasticCacheに載せ替えてみたいなぁと思っています。
まとめ
お一人様マストドンなる状況に陥る時代になるとはびっくりだなぁしていますが、お一人様だとpawooやmstdn.jp のような高速TLをお目にかかることもなく極めて平和に進んでいきます。時たまpawooなどにログインしてTLを眺めると早すぎて目が回ってしまいそうです。 上に記した通り案外お一人様ならあまりメモリやCPUを必要とせずに頑張れます。みなさまもこれを機におひとりさまマストドンいかがですか。
TypeScriptとWebpackとReactを使って簡単なTODOリストを作ってみた
最近フロントエンドの勉強をはじめました。フロントエンド界隈(JavaScript界隈)は変化が激しいようで今最新だーと思っていた技術も数ヶ月後には古いみたいなのがざらにあるようですが、最近私が興味を持っているTypeScriptとFacebookが作っているReact.jsをもとにWebpackを組み合わせて使ってみたというものです。
環境構築
nodeやnpmは既に入っているという前提にしておく。
$ npm init
で出てくる質問に片っ端から答える。yesをするとpackage.jsonが生み出される。
次にTypeScriptをインストールし、トランスコンパイル設定ファイルを生成する。
$ npm install typescript $ (npm bin)/tsc --init
すると配下にtsconfig.jsonが生成されているはず。 また、tsconfig.jsonはとりあえず以下のように書き直しておく。
{ "compilerOptions": { "module": "commonjs", "target": "es5", "jsx": "react", "noImplicitAny": false, "sourceMap": false } }
次にWebpackをインストールする。WebpackはJavaScriptやCSSなどのモジュール依存関係を解決しながら変換してくれるものらしい。
$ npm install webpack ts-loader -D
次にWebpackの設定をJavaScriptでかく。webpack.config.js
という名前で保存しておく。
const path = require('path'); module.exports = { entry: { app: "./index.ts", }, output: { path: path.resolve(__dirname, "./dist"), filename: "[name].bundle.js", }, resolve: { extensions: ["*", ".ts", ".tsx", ".js", ".jsx"], }, module: { rules: [ { test: /.tsx?$/, use: [{ loader: "ts-loader" }], }, ], }, };
次に, Reactをインストールする。
$ npm install react react-dom @types/react @types/react-dom -S
後ろの @types
から始まる部分のものは、すべてTypeScriptを使う際に必要でこれらは型定義となっている。
Hello, World
ここまで来たら、Hello Worldと出力させる部分だけをやってみる。
まず、index.htmlを以下のように作成する。
<!DOCTYPE html> <html> <head> <title>Tiny TODO app</title> </head> <body> <div id="app"></div> <script src="./dist/app.bundle.js"></script> </body> </html>
次に、index.tsを作成する。
import * as React from "react"; import * as ReactDom from "react-dom"; import TodoApp from "./todoapp"; ReactDom.render( React.createElement(TodoApp), document.getElementById("app") );
最後にTODOリストを定義するファイルとして、todoapp.tsxを作成する。
import * as React from "react"; export default class TodoApp extends React.Component<any, any> { render() { return ( <div>Hello, World</div> ); } }
これで、webpack
を使ってコンパイルしてみる。
$ $(npm bin)/webpack -p
こうしてから普通にindex.htmlへブラウザからアクセスしてみる。
こんな感じ。
TODOリストを作成してみる
まず入力フォームを作っておく。入力フォームは以下のような感じ。
import * as React from "react"; export default class TodoApp extends React.Component<any, any> { render() { return ( <div> <h3>TODO</h3> <TodoList items={this.state.items} /> <form onSubmit={this.handleSubmit}> <input onChange={this.handleChange} value={this.state.text} /> <button>{'Add #' + (this.state.items.length + 1)}</button> </form> </div> ); } }
一応できたけど、またstateの値やhandleSubmit, handleChangeと言った部分が実装されていない。
まず、コンストラクタを実装する。
constructor(props: any) { super(props); this.handleChange = this.handleChange.bind(this); this.handleSubmit = this.handleSubmit.bind(this); this.state = {items: [], text: ''}; }
こんな感じ。次にhandleSubmitとhandleChangeをそれぞれ書く。
handleChange(e: any) { this.setState({text: e.target.value}); } handleSubmit(e: any) { e.preventDefault(); let newItem = { text: this.state.text, id: Date.now() }; this.setState((prevState) => ({ items: prevState.items.concat(newItem), text: '' })); }
最後に今まで入力した値をきちんと出力するためのクラスとして TodoList
を定義する。
class TodoList extends React.Component<any, any> { render() { return ( <ul> {this.props.items.map(item => ( <li key={item.id}>{item.text}</li> ))} </ul> ); } }
これでおしまい。実際に実行してみる。Webpackでコンパイルした後、ブラウザで普通にindex.htmlを開いてみるとこんな感じになっているはず。
ちなみに書いたコードとかはこれ。
まとめ
環境構築あたりがものすごくしんどいという印象。フロントエンドが正しく動いているというようなテストを書くにはどうするのかまだよくわからないので調べていきたいと思っております。はい。
まだTypeScript自体も写経と言うような感じで使いこなせているわけではないので精進していきたいところです。
GitHubのプロジェクトをCircleCI 2.0でテストできるように移行してみる
前回書いたマストドンの記事が地味にバズっていてびっくりしているたんごです。
今日はマストドンとは全く関係ないですが、自分のGitHubのリポジトリでCircleCIでテストをしているプロジェクトをCircleCI 2.0でテストするようにしたので、簡単にそのメモ書きを残しておきます。
CircleCI 2.0ってなんぞ
を読め、という気持ちなんですが簡単に言うと以下のような違いがあります。
- 基本的にDockerコンテナがそのままCircleCIで動かせるよ。
- ローカルでビルドできるようになるよ。
- キャッシュがめっちゃ効かせられるよ
はい。意味がわかりませんね。一番でかいのはやっとNativeのDockerをサポートしたということではないでしょうか。今までだと、例えば docker -v
でマウントしたり、docker exec
でコンテナ内のコマンドが実行できなかった気がしますが、2.0を使えばそれも簡単にできます。
どうやって始めるの
CircleCI 2.0はまだベータ版なので使いたい人は自分で使いたいよ〜という申請をする必要があります。先程のリンクの下の方にあるAccess CircleCI 2.0’s Beta Nowという部分に行って入力すると、数分後か数日後にメールが届き使えるようになります。
とはいっても、すぐに自分のリポジトリがすべて2.0でビルドされるわけではありません。
まず、今までだとプロジェクトルートに circle.yml
というファイルを作っていました。2.0を適用させるには、.circleci/config.yml
というディレクトリに設定を書く必要があります。
今回私が移行したプロジェクトはこちらです。
基本的にはPythonのみのコードで構成されておりテストも簡単にユニットテストを動かすだけでした。
machine: python: version: 3.6.0 dependencies: cache_directories: - "~/.cache/pip" test: override: - python setup.py test
さてこれを2.0用のYAMLファイルに書き直していきます。 ドキュメントとしては以下の2つを読めば事足ります。
はじめにDockerのイメージを指定します。今回は公式イメージから最新のイメージを利用することにしました。
ここまでの設定を書くと次のようになります。
jobs: build: docker: - image: python:3
次にテストの前の依存関係のインストールやPythonなら requrements.txt
の読み込み、RubyならGemfileからのインストールなどをすると思いますが、そのステップも各必要があるみたいです。
version: 2 jobs: build: docker: - image: python:3 steps: - checkout - run: apt-get update && apt-get install -y libgmp3-dev - run: name: Install dependencies command: pip install -r requirements.txt
stepsのはじめの行である checkout
によりGitHubからコードを取得してくることができます。
また、runはnameとcommandというキーを持つことができ、これらはWeb UI上で次のように表示されるようになります。
お好みで書けばよいでしょう。
最後にテストの部分を追加します。
version: 2 jobs: build: docker: - image: python:3 steps: - checkout - run: apt-get update && apt-get install -y libgmp3-dev - run: name: Install dependencies command: pip install -r requirements.txt - run: name: Run tests command: python setup.py test
ローカルでCircleCIのビルドをテストしてみる
さて、今までのCircleCIであればこのあと実際にこれが正しく動くかどうかに関してはコミットしプッシュしてみないとわからないという状況でした。なので初歩的にはYAMLの構文が間違っていたとか、設定に誤りがあった場合その修正コミットを作成してプッシュするという屈辱的な状況に追い込まれていました。
そこでCircleCIコマンドの出番です。
インストールは簡単で1行で済みます。
curl -o /usr/local/bin/circleci https://circle-downloads.s3.amazonaws.com/releases/build_agent_wrapper/circleci && chmod +x /usr/local/bin/circleci
ちなみにDockerが必要なのでよしなにインストールしておいてください。
あとはおもむろにプロジェクトルートで
circleci config validate config file is valid
でコンフィグの検証も行え、 circleci build
で一貫したテストを行うことができます。
circleci build ====>> Spin up Environment Build-agent version 0.0.3052-5be8345 (2017-04-15T11:24:20+0000) Starting container python:3 using image python@sha256:0a979dbff79f466aad8d92a89980ebe19cbd481b135d8cc9c0d843d8cf802ef9 Using build environment variables: CI=true CIRCLECI=true CIRCLE_BRANCH=master CIRCLE_BUILD_NUM= CIRCLE_BUILD_TOKEN=**REDACTED** CIRCLE_JOB=build CIRCLE_NODE_INDEX=0 CIRCLE_NODE_TOTAL=1 CIRCLE_REPOSITORY_URL=git@github.com:ryosan-470/cocoa.git CIRCLE_SHA1=f8cdb852ca92b53ae61768852ad23a9871e3ea8b ====>> Checkout code Warning: skipping this step: running locally ====>> apt-get update && apt-get install -y libgmp3-dev #!/bin/bash -eo pipefail apt-get update && apt-get install -y libgmp3-dev Get:1 http://security.debian.org jessie/updates InRelease [63.1 kB] ... ... writing manifest file 'cocoa.egg-info/SOURCES.txt' running build_ext ============================= test session starts ============================== platform linux -- Python 3.6.1, pytest-3.0.6, py-1.4.32, pluggy-0.4.0 -- /usr/local/bin/python cachedir: .cache rootdir: /home/ubuntu/cocoa, inifile: setup.cfg collected 5 items tests/test_factorize.py::TestFactorizeTestCase::test_fermat_success PASSED tests/test_factorize.py::TestFactorizeTestCase::test_fermat_timeout PASSED tests/test_factorize.py::TestFactorizeTestCase::test_pollard_rho PASSED tests/test_factorize.py::TestFactorizeTestCase::test_small_prime PASSED tests/test_factorize.py::TestFactorizeTestCase::test_trial_division PASSED =========================== 5 passed in 3.73 seconds =========================== Success!
こんな感じです。あとはコミットしてプッシュすればおしまいです。
あとはWeb UIにアクセスしてみると表示が2.0 になっています。