楽しかった!
DevOpsDaysTokyo
組織文化、Agile開発、自動化、ツール、とか、幅広くて、色んな話が聞こえてくるので面白いなぁ。
発表資料
はこちら。なんとなく英語で作ったけど、発表は日本語でやりました。
開発するときは、サービスを育てるってことを考えながら作ることが大切だと思う。ってお話でした。何人かに良かったよーって言ってもらえたので喜んだ。
今日は
スポンサーブースにいたりするので遊びに来てねー。
楽しかった!
組織文化、Agile開発、自動化、ツール、とか、幅広くて、色んな話が聞こえてくるので面白いなぁ。
はこちら。なんとなく英語で作ったけど、発表は日本語でやりました。
開発するときは、サービスを育てるってことを考えながら作ることが大切だと思う。ってお話でした。何人かに良かったよーって言ってもらえたので喜んだ。
スポンサーブースにいたりするので遊びに来てねー。
最近ちょこちょこ相談されることがあって、直接のスキルではないけど、こういうのもスキルだよなぁって思ったので、思いついた順に書いてみる。5個になった。
「これがなぜか動かない」って相談されたときって、いくつかの要素が絡んでることが多い。
なので「ここは明らかに問題ないでしょう」という一番土台のところからチェックを始める。そうすると「え?そこは問題ないと思いますよ?」って言われるので「うん、それを『問題ないと思う』じゃなくて『問題ない』って断言できるようにしようと思って」みたいな会話をよくする。
可能性をひとつずつつぶしていくと「ここだなぁ」って場所が見つかって、そしたら、もうあとはそんなに難しくない。ひとつずつ確認していくのって遠回りに見えるけど、結局その方が確実ではやいと思う。
↑と絡んで、想像や思い込みなのに、「ここは問題ありません」って断言をする人もいる。ので「それは何かを確認してそう断言してるの?それとも想像?」って聞くと「( ゚д゚)ハッ!・・・確認はしてません」ってなる。「じゃ、そこ確認してみよう」。
自分の中の想像と事実を切り分ける力もそうだけど、誰かの中の想像と事実を切り分ける力も大切だね。
問題にぶつかって、調べてみるかーってなるとして、ネットを探し回るのもスキルがいるよなぁって思う。だいたい英語のほうが情報が多い。
エラーメッセージの中からキーワードを選んだり。期間を指定したり、サイトを指定したり。Stack Overflowを見に行ったり。GithubのIssueを探してみたり。あと、うちの部署は全員Safaribooksを読めるので、そこで探したりもするなぁ。
こういうサイトにはたぶん載ってないとか。この翻訳は原文をあたったほうが良さそうとか。この人の記事なら信頼できるとか。もある。
それと、社内のドキュメントを探す力も重要。「ここにあったよ」って伝えると「え?そんなの知らなかった!」って言われることが多いんだけど「僕も知らなかったけど、探したら見つかったよ。ちょっとコツがいるよね」って感じ。このスキルは外では身につかないのが難しいところ。
公式ドキュメントに書いてある場合も多い。例えばSpringBootの設定を外部からどう与えるかとか。なので目を通しておいて、必要なときに「たしかあそこに書いてたなー」ってチェックできるようにしておく。読んでなくて想像で動かしてる人も多そう。
目の前にソースコードがあるのに「このスクリプトが動かないんですよね・・・」って言われるときも多い。「ソースコード読んでみたんだけど、こうやれば良さそうよ?」みたいな。「じゃあ、この場合はどんな風に動くんですか?」って聞かれて(ほほー。自分で読むってのは想像もしないのかー)って思いつつ「そうね。一緒にこのソースコード読もうか」ってなる。
Githubでソースコードが公開されてるプロダクトなら見に行ったらいい。Vagrantの動きが気になったときとか、k8sのReadiness Probeが気になったときに、見に行ってほほーってなったりしてた。RubyとかGoとか分からなくても雰囲気で読む力がついてきたのかな。ちょっと前のバージョンを使ってる場合とかだと、ブランチの中を探してみたりもする。
コンテナの中身がどうなってるか分からない!って言う人もいるけど「それ、GithubにDockerfileあるからそれ読んだらだいたい分かりますよー」って話もしたりする。
エラーメッセージを読む力、はもういいよね。
とはいえ、書きながら自分自身も、Terraformの公式ドキュメントに目を通してないなぁとか思った。もっと頑張ろっと。
処理が一行ずつ書いてあるより、意味のある塊に閉じ込めてくれてると、読みやすいなぁって思う。僕は、たぶん、こんな順番で書いてる。
処理を一行ずつ書いていって、さくっと動くことを確認する。
なので、ここで終わりにはしない。
その中から意味のある塊を見つけて、グルーピングしてみる。
その意味のある塊に名前をつける。
あーでもないこーでもないって場所を動かしてみて、しっくりくるところを探す。
さっきまでは書く頭だったけど、今度は頭を切り替えて、読む頭でコードを眺める。
ぱっと見て意味の分かりやすいコードになっているかを考える。
一晩寝かせたあとだとより良い。
そのファイルはそれで終わり。自分の参照用としては使うけど、実際のコードとしては使わない。
最初に、そのグルーピングした意味の塊に対するテストを書く。
それから、実装を書く。のを繰り返して仕上げていく。
それをやってる中で気づいたり、思いついたりすることも結構あるかなぁ。
最後にもういちど、読む頭で眺める
既存のコードとかアーキテクチャーにもよるけど、だいたいそんな感じ。
とあるチームのリーダー的な人たちから「ちょっと相談したいことがあるんですけどいいですか?」って言われて「はいー。暇ですよ」って話を聞いた。
「実は、進捗が遅れているので、どうしたらいいかと思いまして」
うん。
「リカバリーするために、こういう風に動いてもらえないか?とメンバーに提案しようかなと思うんですけど、本当にこれでいいのか迷ってて・・・どう思いますか?」
まず、分かってると思うけど、頑張りによるリカバリーは、ないよね。だって、今まででもチームは手を抜かずに全力で一番良いと思う方法でやってるもんね。
だから、リカバリーとして考えられるのは2つ:
今提案しようとしてるのは、後者ってことよね。それは悪くないと思うし、メンバーも従ってくれるとは思うよ。でも
そもそも「メンバーが自分たちと同じ思いを持ってくれないと思ってること」が問題なんじゃないかな?
あなたたちは「遅れているのを何とかしたい」と思っているのに、それを「メンバーは思ってくれない」と思ってるから、自分たちが決めて伝えないといけないと思ってるように見える。
「遅れているのを何とかしたいと思ってる」ってみんなに伝えてないなら伝えよう?そして、その理由も伝えよう?
それから「何とかしたいと思って、こういう案を考えたんだけど、迷ってる」って素直に相談して一緒に考えよう?きっとそのチームなら大丈夫だよ。
見てたら、チームはそのリーダーたちの気持ちを理解して、どうするのが良いか試行錯誤しだしたみたい。もうちょっと揉めたりしたら面白かったのに、良いチームでちょっとつまらんw
大変だったプロジェクトの反省会みたいな振り返りとかで、うまくいかなかったことだけを並べて「反省しています!次からはそうならないように、これこれといった対応をしていきたいと思います。」みたいなのをたまに見る。
そういうときに感じるのは「良かったところを知りたいなー」ってのと「そもそもその改善策って、実現可能なのかな?」ってこと。
そもそも僕は、全員が全力でプロジェクトを成功させようとしていたこと、良いものを作ろうとしていたことを信頼している。誰も手を抜いていたわけじゃない。
だから、たくさんの良かったことをまず知りたい。この判断は良かったよね。とか、ここは大変だったけどなんとかなったね。とか。
そのうえで、思った通りに進まなかったということなので、課題を出していく。例えば、Bが思っていた以上に難しかった。とか。
さて。その課題に対する改善案として「Bが思っていたより難しかったから、Bを先にやっていれば良かった」と出てきたとして、気になるのは「じゃあどうしてそのときはBじゃなくてAを先にやることにしたの?チームとしてそれがベストだと判断したんだよねぇ」ということ。
Aの方が複雑だったから?ふーん。じゃあ、Bを先にやってたら、Aが問題になったんじゃないかな?Aを先にやったのはベストな選択だったんじゃないの?ということは、それは「Aを先にやってリスクを減らすことができた」という良いところだよね。すごいね。
てことは、今回、何かできたことはあったのかな?チームとしてはベストを尽くせたんじゃないかな?
じゃあ、次にチームがこのプロジェクトをやるとして、そのベストを伸ばすにはどういうことをしていれば良いかな?
「AやBについての知識を普段から学んでおく・・・こと?」
あぁ、それは良さそうだね。毎朝、少し勉強の時間をとって、AとBのことをもっとよく知っておくと良いね。
「プロジェクトが遅延しないようにするにはどうしたらいいか?」という問いに対して「プロジェクトが始まったときに既にリリースされてたら遅延しない」と割と真面目に回答したー。
— Mitsuyuki Shiiba (@bufferings) 2019年3月8日
特に、何か自分たちの未だ知らない新しいことに挑戦する必要があるプロジェクトでは、プロジェクトが始まってからその新しいことを調査し始めると苦労する。
例えば、新しいフレームワークに載せ替えるときに、プロジェクトが始まってから新しいフレームワークの調査を始めたのでは時間がかかりすぎるし、コンテナ化をするというプロジェクトが始まってから、Dockerやk8sを調べ始めたのでは負債の多いシステムになってしまう。
こういった課題に対しては、普段からプロジェクトとは別で学習の時間をとっておくと良いと思ってる。
例えば、現在のプロジェクトでは不要だけど今後必要になりそうなフレームワークに触れておいたり、コンテナを使ってそのメリットやデメリットを実感しておいたりする時間をとっておく。
すると、プロジェクトの開始時点で既に、ある程度の知識があるので、それを実践レベルに持っていく部分に集中できる。
コンテナ化プロジェクトが始まるときには、既にコンテナ化しやすいアーキテクチャーに変更済みだとやりやすいし、欲を言えば既にコンテナがデプロイされていてあとは外部に公開するだけになっていると、良いんだよなぁと思う。
今日は花粉がつらいなー。
今日は娘達とマリオをしたりしながらスキマ時間で、ぼーっとMicronautのドキュメントの3.1-3.5までを読んで手を動かしてみた。
https://docs.micronaut.io/latest/guide/index.html#ioc
ソースはここ。
https://github.com/bufferings/hello-mn/tree/20190301-ioc/src/main/java/hello/mn/ioc
Javaは11を使ってて、Micronautは1.0.4を使ってる。
❯ sdk current java Using java version 11.0.1-open ❯ sdk current micronaut Using micronaut version 1.0.4
こういう感じでBeanを定義しておいて:
public interface Snack { String name(); } @Singleton public class Chocolate implements Snack { @Override public String name() { return "Chocolate"; } }
こんな感じでDIして使うことができる。他のインジェクション方法も使えるみたいだけど、コンストラクターインジェクションが好きなので、それ以外は僕は使わないと思う:
@Controller("/hello/ioc") public class HelloIocController { private final Snack snack; public HelloIocController(Snack snack) { this.snack = snack; } @Get(produces = MediaType.TEXT_PLAIN) public String index() { return snack.name(); } }
実行するとこうなる:
❯ curl localhost:8080/hello/ioc
Chocolate
Bean定義が複数ある場合は、インジェクトするBeanを名前で指定できる。Engine
を実装したV6Engine
とV8Engine
がこんな風に定義されている場合:
public interface Engine { int getCylinders(); String start(); } @Singleton public class V6Engine implements Engine { int cylinders = 6; @Override public int getCylinders() { return cylinders; } @Override public String start() { return "Starting V6"; } } @Singleton public class V8Engine implements Engine { int cylinders = 8; @Override public int getCylinders() { return cylinders; } @Override public String start() { return "Starting V8"; } }
@Named
アノテーションを使って、こんな風にインジェクトできる:
@Controller("/hello/nq") public class HelloNamedQualifierController { private final Engine v6Engine; private final Engine v8Engine; public HelloNamedQualifierController( @Named("v6") Engine v6Engine, @Named("v8") Engine v8Engine) { this.v6Engine = v6Engine; this.v8Engine = v8Engine; } @Get(produces = MediaType.TEXT_PLAIN) public String index() { return v6Engine.start() + ", " + v8Engine.start(); } }
実行するとこうなる:
❯ curl localhost:8080/hello/nq
Starting V6, Starting V8
この辺で「名前のルール」が気になって昼間にメモを残しておいた:
@Named
使う代わりに、自分でアノテーション作ることもできるよ、って書いてあって試した。
@Qualifier @Retention(RUNTIME) public @interface V6 { } @Qualifier @Retention(RUNTIME) public @interface V8 { }
こんな感じで作ると、"V6"とか"V8"のBeanが取得できるみたい。試しにもう一個作ってみた。
@Qualifier @Retention(RUNTIME) public @interface V8Engine { }
"V8Engine"にしたけどこれも動いた。あんまり使うつもりがないから「アノテーション名で判別してるんだろうなー」って雰囲気だけ理解しておいた。こんな風に使える:
@Controller("/hello/aq") public class HelloAnnotationQualifierController { private final Engine v6Engine; private final Engine v8Engine; private final Engine v8Engine2; public HelloAnnotationQualifierController( @V6 Engine v6Engine, @V8 Engine v8Engine, @V8Engine Engine v8Engine2) { this.v6Engine = v6Engine; this.v8Engine = v8Engine; this.v8Engine2 = v8Engine2; } @Get(produces = MediaType.TEXT_PLAIN) public String index() { return v6Engine.start() + ", " + v8Engine.start() + ", " + v8Engine2.start(); } }
実行するとこうなる:
❯ curl localhost:8080/hello/aq
Starting V6, Starting V8, Starting V8
まぁ、複数の実装が共通のプレフィックスを持ってる場合とかに便利なのかな?バージョン番号とかでV1
とV2
の2つの実装が沢山ある場合に、このアノテーションを作っておけば便利そうではある。あー、でも、Microserviceって文脈なら、もうそれは別のサービスに分けてルーティングをバージョン分けした方が良さそうか。ま、こういう機能があるんだなってくらいでいっか。
複数の実装がある場合に、Primaryアノテーションを使うとそのBeanが優先される。逆の意味のSecondaryもあるみたい。
public interface ColorPicker { String color(); } @Primary @Singleton public class Green implements ColorPicker { @Override public String color() { return "green"; } } @Singleton public class Blue implements ColorPicker { @Override public String color() { return "blue"; } }
普通に使うと、PrimaryのGreenがDIされる。
@Controller("/hello/pt") public class HelloPrimaryTypeController { private final ColorPicker colorPicker; public HelloPrimaryTypeController(ColorPicker colorPicker) { this.colorPicker = colorPicker; } @Get(produces = MediaType.TEXT_PLAIN) public String index() { return colorPicker.color(); } }
実行するとこうなる:
❯ curl localhost:8080/hello/pt
green
んー。テストのときに上書きしたくてやるぐらい?しか用途を思いつかん。あー、でもあれか、Blueの方は名前を指定すれば利用できるから、通常はGreenを使うけどたまにBlueを使いたいときとかに便利なのか。ふむふむ。
複数のBeanが定義されているときに、Iterable
とかArray
とかが使えるみたい。あと、Optional
を使うとBeanがない場合にはemptyになるみたい。他にもいくつか対応してるみたい。まぁ、これもあんまり使わんと思うけど一応試しておいた。
@Controller("/hello/ct") public class HelloContainerTypeController { private final List<Engine> engines; private final List<ColorPicker> colorPickers; public HelloContainerTypeController( List<Engine> engines, List<ColorPicker> colorPickers) { this.engines = engines; this.colorPickers = colorPickers; } @Get(produces = MediaType.TEXT_PLAIN) public String index() { return engines.stream().map(Engine::start).collect(Collectors.joining(", ")) + ", " + colorPickers.stream().map(ColorPicker::color).collect(Collectors.joining(", ")); } }
実行するとこうなる:
❯ curl localhost:8080/hello/ct
Starting V8, Starting V6, blue, green
IoCのコンテナはBeanContext
なのかな。実際はそれを継承したApplicationContext
があって、それを実装したDefaultApplicationContext
が使われてる。
こんな風にコンストラクターでInjectしてみたら使えた。
@Controller("/hello/bc") public class HelloBeanContextController { private final BeanContext beanContext; private final ApplicationContext applicationContext; public HelloBeanContextController( BeanContext beanContext, ApplicationContext applicationContext) { this.beanContext = beanContext; this.applicationContext = applicationContext; } @Get(produces = MediaType.TEXT_PLAIN) public String index() { return beanContext.getBean(Engine.class, Qualifiers.byName("v6")).start() + ", " + beanContext.getBean(Engine.class, Qualifiers.byName("v8")).start() + ", " + beanContext.getBean(ColorPicker.class).color() + ", " + applicationContext.getBean(Engine.class, Qualifiers.byName("v6")).start() + ", " + applicationContext.getBean(Engine.class, Qualifiers.byName("v8")).start() + ", " + applicationContext.getBean(ColorPicker.class).color(); } }
実行するとこうなる:
❯ curl localhost:8080/hello/bc
Starting V6, Starting V8, green, Starting V6, Starting V8, green
どうなんかなー。使うなら、ApplicationContext
をDIしといたらいいんかな。
3.5までを読みながら触ってみたお話でした。
普段は特に複数Beanを定義することもないだろうから、シンプルにBeanを定義してそれをDIして使う、ってぐらいだと思ってる。しばらく時間が取れなさそうだから、また気が向いたときに続き読もうと思う。