こんにちはsuzukenです。VOYAGE GROUPでは学生向けエンジニアインターンシップTreasureを毎年開催しています。今年の内容について主に講義の面から振り返ってみたいと思います。
https://voyagegroup.com/internship/treasure/
Treasureは私が入社するもっと前から開催されていて、かれこれ10年弱続いているそうです。去年までは @brtriver がメインの講師を担当していました。今年は私がメイン担当ということで、言語をPHPからGoに変えてみたり、新たな講義を追加してみたりしました。
今年のカリキュラムは http://techlog.voyagegroup.com/entry/treasure2016info にも少し書きました。最終的に今年の講義スケジュールは以下のようになりました。
- 1日目: アイデアソン by みっきー
- 2,3日目: Go入門 by @suzu_v
- 4日目: HTTP API設計 by @brtriver
- 5,6日目: 中間課題
- 6日目: データモデリング by @hironomiu
- 7日目: インフラ・CI by @_nishigori, @s_tajima_tech
- 8日目: チーム開発入門 by @katzchang
そしてあとはチームでのアプリケーション開発に取り組んでもらう、という日程です。全体で3週間あるなかで前半が講義という構成になっています。
事前課題
Treasureでは毎年事前課題を出しています。ここで参加者がどれくらい実装できるかというのをみておき、授業資料を調整しています。
Goの事前課題だと以下のものを出しました。
- その1 https://gist.github.com/suzuken/a8ec8fc95cd0c0d18d8d5584fdd4f3ab
- その2 https://gist.github.com/suzuken/b456e0f4679f86da572839d6d86f159e
Pull Requestで事前課題リポジトリに解答を提出してもらいました。ちなみに以下のコメントをよくつけました。
- gofmt おねがいします
- main 以外で os.Exit や panic するのは大抵好ましくないです
- error はよっぽど自明じゃないかぎり無視しないように
- 変数や手続き名は snake_case ではなくて camelCase で書きましょう
- 変数名は分かる範囲なら短くしていいです
- 余分なelseを書かないこと。正常なフローのインデントを最小にしましょう。
ほとんど https://golang.org/s/style にあることが多かったです。課題自体はそんなに難しい内容ではないのでさくさく解けている様子でしたが、Goのコードのスタイルについてはやはりレビューでのフィードバックがあったほうがよいと考えています。なので事前課題ではコードのスタイルについても丁寧にコメントをつけてフィードバックしていました。提出は一回して終わりではなく、コメントでやりとりしながら書き換えてもらい、最終的にはマージをする、という進め方をしました。
ちなみに事前課題リポジトリにはwerckerでの go build
と go test
を走らせていました。なので動作しないPull Requestは提出時点でわかるようになっています。
最初は https://tour.golang.org を丁寧にやっていれば事前課題は必要ないだろうと考えていたのですが、コードのスタイル周りはやはり自分でコードを書いてもらわないと身につかないのでやってもらってよかったです。
Go講義の設計
Treasureは例年Webアプリケーション開発のインターンとなっています。今回のGoの講義をする上で、何が最低限Webアプリケーション開発のためのベースの知識として必要だろうかと考えました。全体のスケジュールの兼ね合いでGoの講義には時間を使えても2日間だろうということで最終的には以下の内容に絞りました。
- なぜGoなのか?
testing
net/http
encoding/json
- テンプレート
- Goとデータベース
スライドは http://go-talks.appspot.com/github.com/voyagegroup/talks/2016/treasure-go/intro.slide です。
ちなみに原案ではもっと言語の基礎の話をする予定でした。しかしそれをやっていてはもう1日ほしくなる・・ということで結果的には「Tour of Goをしっかりやってきてね」と伝えて、言語の基礎部分については個別に補足していくということにしました。Goの言語仕様は小さいので課題をやっているうちになれるだろうと考えました。
講義は基本的に演習を中心に組み立てています。人間は30分説明を聞いていると眠くなるいきものなのかもしれません・・。ということで講義資料にも一定のペースで演習がはいっています。講義中の演習では以下のものを実装してもらいました。
- スタック(これはペアプログラミングで)
- 並行に動作するスクレイパ
- サンプルアプリケーションにコメント欄を追加しメモリに保存する
- MySQLのCRUDをする小さなコマンドラインアプリケーションをつくる
- サンプルアプリケーションにコメント欄を追加しDBに保存する
- サンプルアプリケーションに機能を追加する
また講義のためにサンプルのアプリケーションを2つ用意しました。両方共 https://github.com/gin-gonic/gin をつかっています。
- https://github.com/suzuken/wiki
html/template
をつかったフォームによる操作をメインとしたもの
- https://github.com/voyagegroup/gin-boilerplate
- JSONを返すAPIサーバとして書いたもの。クライアントはReact。
net/http
をそのままつかって書こうとしていたのですが、結局ginを使いました。理由はHTMLテンプレートに値を埋め込むのとJSONを返すのでインタフェースが統一されているため、もともと html/template
をつかっている実装をしていてもJSONを返すAPIに移行しやすいからです。
最初はgin-boilerplateだけしか用意していませんでした。事前課題の提出状況なりコメントをみていてあまりWebアプリケーション慣れしていなさそうな参加者も多かったので、ベーシックなHTMLを書くアプリケーションのサンプルがあったほうがいいなと考え、 suzuken/wiki の実装も追加しました。
サーバサイド実装はMVCっぽくなっていますが、単純にpackageを分割する以外のことはしていません。gin自体はルーター拡張 + 便利メソッド集なのであまり凝らずにかかれています。去年のPHPでの実装例では Pimple を利用したDIをいれていたりもしたのですが、今年はGoなので単純に struct
と package
をつかうのみにしました。アプリケーションの大きさによってはべたっと1ファイルに実装してもいいのですが、テスト容易性やチーム開発時の利便性を考えるとある程度package分割をしていったほうがやりやすいという話をしました。このあたりは一人での開発に慣れている参加者からは幾分面倒に感じられたようです。
より細かい内容や演習の中身についてはスライドをご覧いただければ幸いです。
中間課題とレビュー
中間課題をTreasureでは例年やっています。一通り小さなWebアプリケーションを自分でつくる、という課題です。課題は以下のとおり2コースあります。
- TODOアプリをつくる
- なんでもOK
約半分の参加者はTODOアプリですが、わりと好きなものをみんな作っていました。SlackのbotもあればECサイトっぽいものがあったり、Trello的なUIを実装してきたりと様々でした。中間課題の前にアイデアソン、Goの講義、HTTPの講義を終えているので、一通りのWebアプリケーションは作れるという設計です。とはいえこのときのアウトプットには講師一同驚くことになります。
中間課題はデモをしてもらいます。例年ではそれで終わりなのですが、今年はせっかく全力で書いてもらったコードなので個別にコードのレビューもして返すことにしました。とはいえ20人超のWebアプリケーションのコードをレビューするのはなかなか大変ではあったのですが・・個別のフィードバックほどクオリティを上げる方法もないかなと思いやっています。だいたい以下のようなフィードバックをしました。
- DBの正規化をうまくつかおう
- HTTP APIの名前付けをちゃんと考えよう
- 使ってないコードは消しましょう
- package分割をうまくつかってみましょう
- 変数のスコープはなるべく小さくしましょう
- structをうまくつかおう
- エラーハンドリングは適当なヘルパやハンドラを書くといい
- HTTPハンドラの中でカジュアルにpanicしないこと
特に変数のスコープについては半分以上の人にコメントしたような気がします。コードを読んでいる人は、変数が有効なスコープを意識して読みます。そうすると変数のスコープが広いと「あれ、今この変数はどういう状態なんだろう?」というのがわかりづらくなります。
特に対面フィードバックの際に「なぜこう書いたの?」と聞くと「動くから」という返答が多かったのも印象的でした。他の人にとって読みやすいコードを書きましょうという話をしたものの、やはりこれは複数人でコードを書き、コードをある程度の期間メンテナンスするということをしないとなかなか実感が得られないのかもしれないなと思うのでした。
またstructをうまくつかおうというのは関数の引数が長くなってしまっているケースが多い場合に話しました。こういうケースは引数のパラメータ化をしたいパターンとstructに状態を持たせたほうがよいパターンがあります。今回は後者のようにしたほうがよいケースがいくつかありました。なので関数ではなくメソッドをつかうことで問題をシンプルに解決できることもあるという話をしました。特に関数の名前がどうしても説明すると長くなってしまうようなものの場合、うまくstructを使えていないケースというのが多かったように思います。
エラーハンドリングについては特に他の言語に慣れている人にとっては面倒に感じられているケースが多いようにみえました。単純に以下のような簡単なヘルパを書くと楽になる例もあります。
func iferr(err error)
ひとによっては「 var ErrInvalid = errors.New()
のような定義が増えて辛い」という話もありました。こういう場合はstructに対して Error() string
を実装すると ハンドリングしやすくなる場合があります。パッケージレベルでのエラーを書く場合には var
でグローバルに宣言してもよいですが、コンテキストによってエラーの内容を変えたいような場合には error
がインタフェースであることをうまく利用するとハンドリングしやすくなります。以下のブログも参考にするといいでしょう。
Error handling and Go - The Go Blog
グレースフルにエラーをハンドリングしたい場合には、https://github.com/pkg/errors をつかうのもよいでしょう。
HTTPハンドラの中でカジュアルにpanicさせないというのも上のエラーハンドリングに繋がる話です。HTTPハンドラの中でpanicしてもginだとrecoverされてしまいますが、だからといって積極的にpanicさせるのは好ましくありません。panicは本当に例外的な状況でのみ使われるべきです。なので、ログ出力とエラーレスポンスを返すことを正しくやりましょうという話をしました。今回は認証周りだけmiddlewareを使う例を示したのですが、このあたりは net/http
での実装を説明したほうがわかりやすかったかもしれません。上のブログでも紹介されていますが、以下のようなエラーハンドリング用のラッパーをハンドラとしてつかえるようにしておくことで、単純にerrorを返せばエラーレスポンスが返るようにしておくこともできます。
// https://blog.golang.org/error-handling-and-go より func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if err := fn(w, r); err != nil { http.Error(w, err.Error(), 500) } }
こうすると例えばすべて500で返すのではなく、エラーの型に応じてエラーレスポンスを変えることもやりやすくなります。
講義全体の設計
今年は例年いれていないインフラとチーム開発の講義を追加しました。これは私達が普段働いている時もエンジニアとして知ってもらいたいと思っていることを感じてもらう何らかのきっかけになってもらえたら、と思い今年からとりいれることにしました。Treasureの後半戦は数人でチームを組んでアプリケーション開発をしてもらっています。例年「何をつくるか」で議論になってなかなか決まらないということがおきます。各チームの方針が決まるまで、なかなか進みだすことができません。それによって実装することがどんどん遅れていく、という問題がよく起きていました。
チーム開発というものを講義に入れようというのは今年の全体設計をする上で最初から考えていました。とはいえどういう講義にすべきかをなかなかイメージできませんでした。そこで @katzchang にお願いしてみようと考えました。インターンでのチーム開発と僕らが実際に仕事でやっているチーム開発というのは似ていて非なるものでもあります。とはいえ、そこから何かしらのエッセンスを伝えられたらよいだろうと考えました。この講義はある意味Treasureのエッセンスがつまっているとも言えるものでした。
インフラ及びCIの講義についても今年からはじめてのものです。これは @_nishigori と @s_tajima_tech 後半のチーム開発でのサーバ環境を用意してもらうというのとセットで講義をお願いしました。この講義の狙いは以下のとおりです。
- 私達が普段仕事でつかっているようなCIの環境をつかってもらうこと
- 各チームごとに独立したdisposableな環境を用意し、自分たちでインフラ構成をいじってもらうこと
- そしてインフラやCIもプログラマが問題解決に関われる領域であるということを知ってもらうこと
この講義は、私達が仕事で扱っているようなデプロイの整備された環境を提供して便利さを感じてもらうともに、インフラやCIといった部分にもソフトウェアエンジニアリングは活かすことができるというのを学生に疑似体験してもらう目的もありました。TreasureはWebアプリケーション開発のインターンであるというイメージが強いですから、参加者も「プログラムを書くことはWebアプリケーションをつくることである」と暗黙的に思っている人が多かったりします。ソフトウェアというのはいたるところにあって、実装するアプリケーションの選択肢もたくさんあります。Webアプリケーションを普段書いている人以外でも、プログラマの活動できる領域はもっとあるのだよ、ということを感じてもらえたらと思い、この講義をいれてもらいました。インフラ・CIの講義では実際にCIのスクリプトを自分で書いてもらう、ということもやってもらいました。
このあたりは参加者にあとから聞いてみると9割以上が難しいと感じていたので、継続してやってみると面白いかもと思っています。
まとめ
前半講義及び私の担当したGoの講義にしぼってTreasure2016のあとがき的なものを書いてみました。私自身、ソフトウェアエンジニアリングとの関わり方も毎年変わっていっています。事業環境もそうですし、よりよくアプリケーションを開発する方法というのも毎年多くのエンジニアが考え、実践し、改善されていきます。そのような背景を踏まえ、Treasure自体も毎年少しずつ形や内容を変えながら取り組んでいます。参加者のみなさんが少しでも多く現場のエンジニアから学び取り、今後のエンジニアリングに活かせてもらえたなら嬉しく思います。