#DevOpsDaysTokyo で「サービス運用をまんなかにおいた開発」って発表をしましたー

楽しかった!

f:id:bufferings:20190410102555p:plain

DevOpsDaysTokyo

組織文化、Agile開発、自動化、ツール、とか、幅広くて、色んな話が聞こえてくるので面白いなぁ。

www.devopsdaystokyo.org

発表資料

はこちら。なんとなく英語で作ったけど、発表は日本語でやりました。

開発するときは、サービスを育てるってことを考えながら作ることが大切だと思う。ってお話でした。何人かに良かったよーって言ってもらえたので喜んだ。

togetter.com

今日は

スポンサーブースにいたりするので遊びに来てねー。

f:id:bufferings:20190410103621j:plain

エンジニアが何か問題にぶつかったときにあるといい力を5個

最近ちょこちょこ相談されることがあって、直接のスキルではないけど、こういうのもスキルだよなぁって思ったので、思いついた順に書いてみる。5個になった。

## 1. 問題を切り分ける力

「これがなぜか動かない」って相談されたときって、いくつかの要素が絡んでることが多い。

なので「ここは明らかに問題ないでしょう」という一番土台のところからチェックを始める。そうすると「え?そこは問題ないと思いますよ?」って言われるので「うん、それを『問題ないと思う』じゃなくて『問題ない』って断言できるようにしようと思って」みたいな会話をよくする。

可能性をひとつずつつぶしていくと「ここだなぁ」って場所が見つかって、そしたら、もうあとはそんなに難しくない。ひとつずつ確認していくのって遠回りに見えるけど、結局その方が確実ではやいと思う。

## 2. 想像と事実を切り分ける力

↑と絡んで、想像や思い込みなのに、「ここは問題ありません」って断言をする人もいる。ので「それは何かを確認してそう断言してるの?それとも想像?」って聞くと「( ゚д゚)ハッ!・・・確認はしてません」ってなる。「じゃ、そこ確認してみよう」。

自分の中の想像と事実を切り分ける力もそうだけど、誰かの中の想像と事実を切り分ける力も大切だね。

## 3. 探す力

問題にぶつかって、調べてみるかーってなるとして、ネットを探し回るのもスキルがいるよなぁって思う。だいたい英語のほうが情報が多い。

エラーメッセージの中からキーワードを選んだり。期間を指定したり、サイトを指定したり。Stack Overflowを見に行ったり。GithubのIssueを探してみたり。あと、うちの部署は全員Safaribooksを読めるので、そこで探したりもするなぁ。

こういうサイトにはたぶん載ってないとか。この翻訳は原文をあたったほうが良さそうとか。この人の記事なら信頼できるとか。もある。

それと、社内のドキュメントを探す力も重要。「ここにあったよ」って伝えると「え?そんなの知らなかった!」って言われることが多いんだけど「僕も知らなかったけど、探したら見つかったよ。ちょっとコツがいるよね」って感じ。このスキルは外では身につかないのが難しいところ。

## 4. 公式ドキュメントを読む力

公式ドキュメントに書いてある場合も多い。例えばSpringBootの設定を外部からどう与えるかとか。なので目を通しておいて、必要なときに「たしかあそこに書いてたなー」ってチェックできるようにしておく。読んでなくて想像で動かしてる人も多そう。

## 5. ソースコードを読む力

目の前にソースコードがあるのに「このスクリプトが動かないんですよね・・・」って言われるときも多い。「ソースコード読んでみたんだけど、こうやれば良さそうよ?」みたいな。「じゃあ、この場合はどんな風に動くんですか?」って聞かれて(ほほー。自分で読むってのは想像もしないのかー)って思いつつ「そうね。一緒にこのソースコード読もうか」ってなる。

Githubソースコードが公開されてるプロダクトなら見に行ったらいい。Vagrantの動きが気になったときとか、k8sのReadiness Probeが気になったときに、見に行ってほほーってなったりしてた。RubyとかGoとか分からなくても雰囲気で読む力がついてきたのかな。ちょっと前のバージョンを使ってる場合とかだと、ブランチの中を探してみたりもする。

コンテナの中身がどうなってるか分からない!って言う人もいるけど「それ、GithubにDockerfileあるからそれ読んだらだいたい分かりますよー」って話もしたりする。

## そんな感じ

エラーメッセージを読む力、はもういいよね。

とはいえ、書きながら自分自身も、Terraformの公式ドキュメントに目を通してないなぁとか思った。もっと頑張ろっと。

コードの読みやすさ

処理が一行ずつ書いてあるより、意味のある塊に閉じ込めてくれてると、読みやすいなぁって思う。僕は、たぶん、こんな順番で書いてる。

## 1. ゴールまで行く

処理を一行ずつ書いていって、さくっと動くことを確認する。

  • ちなみに、このときの僕→(おれすごい。とぎすまされてる!)
  • そして、このままにしてた場合の1週間後の僕→(なんもわからん。先週のおれ、叩く)

なので、ここで終わりにはしない。

## 2. グルーピングする

その中から意味のある塊を見つけて、グルーピングしてみる。

その意味のある塊に名前をつける。

あーでもないこーでもないって場所を動かしてみて、しっくりくるところを探す。

## 3. コードを眺める

さっきまでは書く頭だったけど、今度は頭を切り替えて、読む頭でコードを眺める。

ぱっと見て意味の分かりやすいコードになっているかを考える。

一晩寝かせたあとだとより良い。

そのファイルはそれで終わり。自分の参照用としては使うけど、実際のコードとしては使わない。

## 4. 実装する

最初に、そのグルーピングした意味の塊に対するテストを書く。

それから、実装を書く。のを繰り返して仕上げていく。

それをやってる中で気づいたり、思いついたりすることも結構あるかなぁ。

## 5. コードを眺める

最後にもういちど、読む頭で眺める

既存のコードとかアーキテクチャーにもよるけど、だいたいそんな感じ。

同じものを見てもらおう。弱さも見せよう。

とあるチームのリーダー的な人たちから「ちょっと相談したいことがあるんですけどいいですか?」って言われて「はいー。暇ですよ」って話を聞いた。

## 相談

「実は、進捗が遅れているので、どうしたらいいかと思いまして」

うん。

リカバリーするために、こういう風に動いてもらえないか?とメンバーに提案しようかなと思うんですけど、本当にこれでいいのか迷ってて・・・どう思いますか?」

## うーん

まず、分かってると思うけど、頑張りによるリカバリーは、ないよね。だって、今まででもチームは手を抜かずに全力で一番良いと思う方法でやってるもんね。

だから、リカバリーとして考えられるのは2つ:

  • スコープを削る
  • もうちょっと良いやり方がないか実験してみる

今提案しようとしてるのは、後者ってことよね。それは悪くないと思うし、メンバーも従ってくれるとは思うよ。でも

## それよりも

そもそも「メンバーが自分たちと同じ思いを持ってくれないと思ってること」が問題なんじゃないかな?

あなたたちは「遅れているのを何とかしたい」と思っているのに、それを「メンバーは思ってくれない」と思ってるから、自分たちが決めて伝えないといけないと思ってるように見える。

## 同じものを見てもらおう

「遅れているのを何とかしたいと思ってる」ってみんなに伝えてないなら伝えよう?そして、その理由も伝えよう?

## 弱さも見せよう

それから「何とかしたいと思って、こういう案を考えたんだけど、迷ってる」って素直に相談して一緒に考えよう?きっとそのチームなら大丈夫だよ。

## ちょっと離れたところから

見てたら、チームはそのリーダーたちの気持ちを理解して、どうするのが良いか試行錯誤しだしたみたい。もうちょっと揉めたりしたら面白かったのに、良いチームでちょっとつまらんw

そのふりかえりの改善策って実現可能なのかな?

大変だったプロジェクトの反省会みたいな振り返りとかで、うまくいかなかったことだけを並べて「反省しています!次からはそうならないように、これこれといった対応をしていきたいと思います。」みたいなのをたまに見る。

そういうときに感じるのは「良かったところを知りたいなー」ってのと「そもそもその改善策って、実現可能なのかな?」ってこと。

## 信頼していること

そもそも僕は、全員が全力でプロジェクトを成功させようとしていたこと、良いものを作ろうとしていたことを信頼している。誰も手を抜いていたわけじゃない。

だから、たくさんの良かったことをまず知りたい。この判断は良かったよね。とか、ここは大変だったけどなんとかなったね。とか。

## 課題

そのうえで、思った通りに進まなかったということなので、課題を出していく。例えば、Bが思っていた以上に難しかった。とか。

## それって改善になる?

さて。その課題に対する改善案として「Bが思っていたより難しかったから、Bを先にやっていれば良かった」と出てきたとして、気になるのは「じゃあどうしてそのときはBじゃなくてAを先にやることにしたの?チームとしてそれがベストだと判断したんだよねぇ」ということ。

Aの方が複雑だったから?ふーん。じゃあ、Bを先にやってたら、Aが問題になったんじゃないかな?Aを先にやったのはベストな選択だったんじゃないの?ということは、それは「Aを先にやってリスクを減らすことができた」という良いところだよね。すごいね。

てことは、今回、何かできたことはあったのかな?チームとしてはベストを尽くせたんじゃないかな?

## そのうえで

じゃあ、次にチームがこのプロジェクトをやるとして、そのベストを伸ばすにはどういうことをしていれば良いかな?

「AやBについての知識を普段から学んでおく・・・こと?」

あぁ、それは良さそうだね。毎朝、少し勉強の時間をとって、AとBのことをもっとよく知っておくと良いね。

プロジェクトが始まる前に終わらせておくこと

特に、何か自分たちの未だ知らない新しいことに挑戦する必要があるプロジェクトでは、プロジェクトが始まってからその新しいことを調査し始めると苦労する。

例えば、新しいフレームワークに載せ替えるときに、プロジェクトが始まってから新しいフレームワークの調査を始めたのでは時間がかかりすぎるし、コンテナ化をするというプロジェクトが始まってから、Dockerやk8sを調べ始めたのでは負債の多いシステムになってしまう。

こういった課題に対しては、普段からプロジェクトとは別で学習の時間をとっておくと良いと思ってる。

例えば、現在のプロジェクトでは不要だけど今後必要になりそうなフレームワークに触れておいたり、コンテナを使ってそのメリットやデメリットを実感しておいたりする時間をとっておく。

すると、プロジェクトの開始時点で既に、ある程度の知識があるので、それを実践レベルに持っていく部分に集中できる。

コンテナ化プロジェクトが始まるときには、既にコンテナ化しやすいアーキテクチャーに変更済みだとやりやすいし、欲を言えば既にコンテナがデプロイされていてあとは外部に公開するだけになっていると、良いんだよなぁと思う。

今日は花粉がつらいなー。

MicronautのBean定義のところらへん

今日は娘達とマリオをしたりしながらスキマ時間で、ぼーっと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

## 通常のDI

こういう感じで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

## Qualifying By Name

Bean定義が複数ある場合は、インジェクトするBeanを名前で指定できる。Engineを実装したV6EngineV8Engineがこんな風に定義されている場合:

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

この辺で「名前のルール」が気になって昼間にメモを残しておいた:

bufferings.hatenablog.com

## Qualifying By Annotation

@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

まぁ、複数の実装が共通のプレフィックスを持ってる場合とかに便利なのかな?バージョン番号とかでV1V2の2つの実装が沢山ある場合に、このアノテーションを作っておけば便利そうではある。あー、でも、Microserviceって文脈なら、もうそれは別のサービスに分けてルーティングをバージョン分けした方が良さそうか。ま、こういう機能があるんだなってくらいでいっか。

## Primaryアノテーション

複数の実装がある場合に、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を使いたいときとかに便利なのか。ふむふむ。

## Container Type

複数の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

## BeanContext

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して使う、ってぐらいだと思ってる。しばらく時間が取れなさそうだから、また気が向いたときに続き読もうと思う。