#gcp Datastoreを使っているプロジェクトでCloud Firestoreを使うには

Firebaseのはなし。

で、ドキュメントを見たり上記ブログを見るとこういった状況になったGCPプロジェクトでは詰みのように見えるが、結論からいうとあとからなんとかなる

GCPのコンソールからDatastoreのダッシュボードへ移動するとFirestoreへ「アップグレードしますか?」というボタンがあるのでこれを押せば移行できるのでプロジェクトを作り直さなくとも良い。
ただし、データが空の時に限るようなので、既にデータがあったらやはり作り直さないといけないと思う。

dockerの.envファイルむずかしい

docker-compose/dockerで使える.envファイルを、docker-compose/docker以外からも使えるようにしたい。 ちょっとしたスクリプトの実行時に source .env すれば環境変数が設定されるような体験がほしい。

が、実際には一工夫いる。

dockerの .env ファイルは K=V という形式を厳密に守らないといけない。 ので export EK=EV みたいに書くとinvalidとみなされてdocker-compose/docker実行時に正しく設定されない。

一方、.envファイルとしてvalidなフォーマットだと export がないので source しても実行時のプロセスで環境変数は設定されない。

ので、けっきょくこうした:

|sh| eval "$(cat .env | ruby -anlpe '$ = %|export | + $')" ||<

@aereal/go-dsn: TypeScriptでgo-sql-driverのDSNを組み立てるNPMパッケージを作った

go-sql-driverのDSN (Data Source Name) をオブジェクトから生成するライブラリを書きました。

yarn add @aereal/go-dsn

github.com

使い方をsynopsisから引用します:

import { formatDSN } from "@aereal/go-dsn"

formatDSN({
  dbName: "test-db",
  passwd: "mypasswd",
  user: "root",
})
// => "root:mypasswd@/test-db"

便利。

AWS RDSへ接続するようなGoで書いたアプリをAWS CDKでECSにデプロイする際に使うと便利です。

import { Ec2TaskDefinition } from "@aws-cdk/aws-ecs";
import { DatabaseCluster } from "@aws-cdk/aws-rds";

const taskDef = new Ec2TaskDefinition(this, "TaskDefinition", {});

const dbCluster = DatabaseCluster.import(
  this,
  "DatabaseCluster",
  databaseClusterProps
);

const dsn = formatDSN({
  addr: dbCluster.clusterEndpoint.socketAddress,
  charset: "utf8mb4",
  collation: "utf8mb4_bin",
  dbName: "app",
  user: "root",
});

const appContainer = taskDef.addContainer("app", {
  environment: {
    DSN: dsn,
  },
  // ...
);

たいへん便利! どうぞご利用ください。

cdk-mackerel-container-agent: ECSのServiceにmackerel-container-agentを5行で追加

www.npmjs.com

GitHubリポジトリはこちら: GitHub - aereal/cdk-mackerel-container-agent: experimental: AWS-CDK library for mackerel-container-agent

mackerel-container-agentを5行くらいで追加

先日、mackerel-container-agentがベータリリースされましたね。めでたい。

mackerel.io

早速AWS ECSで使ってみたのですが、やや設定が煩雑な印象もあります (ベータなのでフィードバックしたら改善・検討してもらえるかもしれない)。 参考: コンテナを監視する - Mackerel ヘルプ

特に MACKEREL_CONTAINER_PLATFORM は使う側からすると自明な選択なので自動化したい! 構成管理の中にロジックを含められて、かつパッケージとして再配布できるとなるとAWS CDKだよね、ということで作った次第です。 使い方はsynopsisに書いた通りで:

import { addMackerelContainerAgent } from "@aereal/cdk-mackerel-container-agent"
import { Ec2TaskDefinition } from "@aws-cdk/aws-ecs"
import { Stack } from "@aws-cdk/cdk"

const stack = new Stack()
const taskDefinition = new Ec2TaskDefinition(stack, "TaskDefinition", {})

addMackerelContainerAgent({
  apiKey: 'keep-my-secret',
  taskDefinition,
})

……と、こういうかんじです。 TaskDefinitioncompatibilitynetworkMode を見てよしなに MACKEREL_CONTAINER_PLATFORM も設定します。便利。

注意点としてmackerel-container-agentはベータ版、AWS CDKはdeveloper previewということでこのライブラリもいきなり破壊的変更が入る可能性があります。 現時点でmackerel-container-agentやAWS CDKを利用している方はリスクを承知の上でのことと思いますが、念のため。

ちなみにAWS CDKとは The AWS Cloud Development Kit (AWS CDK) is an open-source software development framework to define cloud infrastructure in code and provision it through AWS CloudFormation. というもので、CloudFormationの高水準かつプログラマブルなインターフェースを提供するライブラリといった趣です。 JavaやTypeScriptで提供されているためIDEの恩恵を受けやすいことだけではなく、IAMやSecurity Groupなどセキュリティに関係する変更が含まれる場合は別途diffを出力し、ユーザが明示的に同意する入力をしないとデプロイしないといった、CloudFormationにはまだない改善点も含んだ便利なツールキットでもあります。

現在developer previewですが、GAに向けて開発が続いているので広く利用できる日も近いのではないでしょうか。

どうぞご利用ください!

どうぞご利用ください!

GraphQL: gqlgenで指定されたFieldに応じてresolverの実装を変える

GraphQLのいいところとして、クライアントがリクエストごとに欲しいフィールドを明示するので、サーバ側はリクエスト毎にレスポンス返却を最適化しやすい、という点が挙げられると思います。

gqlgenというGoのGraphQLライブラリがあり、これを使って実際に要求されるFieldに応じて、レスポンスの作成 (gqlgenではresolverという) を最適化するにはどうしたらいいか?
結論から言うと、 `graphql.ResolverContext` にクライアントが求めるFieldが入っているので、それを見たらよいです。

gqlgenでの実装例ですが、だいたいのライブラリはこういうかんじだろうと思います。ScalaのSangriaが似たAPIを供えていることは確認しています。


たとえばデータストアにはMarkdownはてな記法で書かれた本分を保存しておき、APIの応答としてはそれを展開したHTML断片を返却したい、というケース。
Markdownはてな記法の展開はそこそこコストがかかりますし、そもそも本分データは他のカラムに比べてデータ量が多く、クエリ数が増えるとDBとアプリケーションの間の転送量も馬鹿にならないので節約したいです。

gqlgenでは `graphql.ResolverContext.Field.Selections` にクライアントが求めるFieldが渡されるので、これの名前 (`Name`) の一致を見て分岐させることができます

schema.graphql:

type Query {
  article(slug: String!): Article
}

type Article {
  title: String!
  slug: String!
  formattedBody: String!
}

resolver.go:

import (
	"context"

	"github.com/99designs/gqlgen/graphql"
	"github.com/vektah/gqlparser/ast"
)

func (r *queryResolver) Article(ctx context.Context, slug string) (*Article, error) {
	var (
		article *Article
		err error
	)
	if shouldLoadBody(graphql.GetResolverContext(ctx)) {
		article, err = r.resolveArticleWithBody(ctx, slug)
		if err != nil {
			return nil, err
		}
	} else {
		article, err = r.resolveArticle(ctx, slug)
		if err != nil {
			return nil, err
		}
	}

	return article, nil
}

func shouldLoadBody(resCtx *graphql.ResolverContext) bool {
	for _, sel := range resCtx.Field.Selections {
		switch sel := sel.(type) {
		case *ast.Field:
			if sel.Name == "formattedBody" {
				return true
			}
		}
	}
	return false
}

`shouldLoadBody` が肝です。 `resolveArticleWithBody` / `resolveArticle` は省略しています。

`Selections` は `Selection` というインターフェースのスライスになっているので、構造体へキャストして `Name` を取り出します。
この例だと、 `formattedBody` が計算を要するFieldなので、これが含まれている場合のみbodyを引き、かつ展開する処理をする `resolveArticleWithBody` を呼び、そうでなければ展開を省略した `resolveArticle` を呼びます。

resolverのメソッドの返り値は構造体である必要があるので `Article` は必要とされるFieldすべての和である必要があります。
そのため `resolveArticle` は常に空の `formattedBody` を返すことになり、ちょっとドキドキしますが、そこはGraphQLのレイヤで参照されないことが担保されます。


その気になればDBから引くカラムをSelectionsに限定することもできそうですが、少なくともGoのORMの表現力だとコストばかりかかって大変そうなので、パフォーマンスに著しい影響がありそうなところだけピンポイントで使うのがよさそうです。

2018年

もう7年目。主に仕事について。
大きな仕事と新しい仕事に取り組んだ一年だったと思う。

this.aereal.org

今年前半はHTTPS化という大きい仕事を担当した。やりがいはあったけれど正直に言えば重荷が降りたという気持ちが大きい。

言葉を選ばずに言えば、2018年にこれをやるというのは問題の難しさを踏まえても遅かったなと思う。
ので、これからは自分のソフトウェアエンジニアリング力を大局的な意思決定へフィードバックできるように振る舞っていきたい。

geek-out.jp

まあいろいろ思うところはありつつ、自分が関わった仕事をふんだんに知ってもらう機会に恵まれ、インタビューなどしていただいたりもした。

this.aereal.org

その他、新しい仕事としてチームマネジメントに力を入れはじめた。

具体的に変わったことというと、中長期的な計画について考える時間が増えた。
数年後どんなチームにしたいか考えて、そのために半年〜1年後の人員計画を考えたり。
数年後はてなブログというサービスはどう成長するだろうからこういうアーキテクチャになっていくだろう、それに備えてこう改修させたい、などなど。

やるべきこと・やりたいことがとにかく多すぎて自分だけでどうにかしていくのもままならないので、自分がどうなりたいか・どうしたいかを踏まえて手段を考えた結果、自然にここに辿り着いた。

今でもマネジメントを自分がやるなんて大変なことだと思っているけれど、チームメンバーを通した先にあるはてなブログというサービスを良くしていきたいというゴールを見据えると自然と動けている。

逆にこういうかんじだから、マネジメントそのものを目的に働いていくというのはまだ想像がつかない。

新しい仕事なので慣れない点も多く、心身が付いてこないこともあり、同僚との関わり方を見直したりもした。

this.aereal.org

とにかく関わっているはてなブログを良くすることを第一に考えて動いたら自然と成果と道筋が見えてきているので、いい環境に身を置けているなと思う。
もちろん順風満帆ではないけれど。

とはいえサービスを成長させていくという仕事は外から見えてわかるレベルに達するのは難しいし、ソフトウェアエンジニアリングに限界を見てマネジメントに逃げるような姿勢をとるのはどちらの分野にも失礼だから、どっちも全力でやる。
特に来年はサービス開発という文脈に頼りすぎないソフトウェアエンジニアリングの面でちゃんと成果を出していきたい。戦略的にOSS開発をやっていきたい。

go-http-replay: VCRみたいに実際のHTTPレスポンスを保存してテストで再利用できるライブラリ

を作りました。

github.com

外部HTTPリクエストをスタブするTest::WWW::StubというPerlのモジュールや、スタブするレスポンスをあらかじめ記録しておいた実際のレスポンスから再現するVCRというRubyのライブラリなどにインスパイアされました。

使い方はこういうかんじ:

import (
    "net/http"
    "testing"

    httpreplay "github.com/aereal/go-http-replay"
)

func Test_http_lib(t *testing.T) {
    httpClient := &http.Client{
        Transport: httpreplay.NewReplayOrFetchTransport("./testdata", http.DefaultClient),
    }
    // httpClient will behave like the client that created from NewReplayTransport but DO actual request if local cache is missing.
}

ローカルにキャッシュが無い初回は実際にリクエストしそれを保存する、次回以降は保存されたレスポンスを再現するので実際にリクエストしない、といった振る舞いをします。

net/httpのAPIしか使っていないdependencies freeな実装なところがアピールしたいポイントです。

どうぞご利用ください!!!

Ruby: 非ASCII文字列がパーセントエンコードされていないかもしれないURLもがんばってパースする

normalized_url =
  begin
    URI.parse(url)
  rescue URI::InvalidURIError
    URI.parse(url.gsub(/\p{^ASCII}/) {|s| URI.encode_www_form_component(s) })
  end

URL中の非ASCII文字列をパーセントエンコードしてない (例: http://example.com/?q=姉) と `URI::InvalidURIError` が投げられるので、それを補足しUnicodeプロパティの否定を使ってエンコードしなおして再度 `URI.parse` するというもの。

雑なスクリプト中で使う用途なのでかなり雑だけど、これでだいたいうまくいったので満足。

TheSchwartzの失敗したjobとかerrorがいつどのように消えていくのか

この記事ははてなエンジニアAdvent Calendar 2018の16日目の記事です。
昨日はid:akiymによるOpenSSLはどこにいるでした。

地味にOpenSSL系のモジュールはリポジトリのセットアップ時にハマりがちで、かつたまにバージョンアップでビルド引数の渡し方が変わったりする曲者です。が、Crypt::OpenSSL::Guessはナイスモジュールですね。
あとbut sometimes consistency is not a bad thing eitherという下の句(?)があるのは知りませんでした。id:akiymはキャリア的には一応後輩(?)ですがPerlについては学ぶことばかりです。


16日目はid:aerealがお届けします。
普段はアプリケーションエンジニアですが、最近は社内カメラマンを拝命する機会をいただくことがあり、エンジニアインターン2018麻婆豆腐なしにどっしり構えられるエンジニア生きることとコードを書くことが同じエンジニアのインタビューの撮影に携わりました。
特にインタビュー二作は既にご一読いただけたでしょうか? 手前味噌ですがありのまま度の高い率直な内容で楽しんでいただけるかと思いますので、もしまだご覧いただけてない方がいらっしゃればぜひ。

さて、はてなブログをはじめとするいくつかのはてなのサービスではジョブキューにTheSchwartzというPerlMySQLを使ったミドルウェアを利用しています。

TheSchwartzの失敗したjobやerrorがいつ消えるのか、そのうち消えるといった認識だったけど、どれくらい経つと消えるのか、どういうタイミングなのか、詳しく知らないなーと思い調べました。

exitstatus

消しているところ: https://metacpan.org/source/JFEARN/TheSchwartz-1.12/lib/TheSchwartz/Job.pm#L162

  • set_exit_statusが呼ばれたとき:
    • ランダムに
    • exitstatus.delete_after < 現在時刻のレコードが
    • ……deleteされる
  • set_exit_statusが呼ばれるのは?
    • TheSchwartz::Jobを継承した子クラスで keep_exit_status_for を再実装してtruthyな値を返して、かつ completed/_failedメソッドが呼ばれたとき
    • = ジョブの実行が終了したとき

error

消しているところ: https://metacpan.org/source/JFEARN/TheSchwartz-1.12/lib/TheSchwartz/Job.pm#L126

  • add_failureが呼ばれたとき:
    • 発生時刻 (error_time) が一定時間より古いとき
      • 一定時間: 現在時刻 - maxage
      • maxageのデフォルトは7時間7日
    • ……deleteされる
  • add_failureが呼ばれるのは?
    • _failedが呼ばれたとき
    • = ジョブが失敗したとき

おわり

アドベントカレンダーは毎日1つ穴を開けるとお菓子が出てきたりするもので、そこから転じたブログ記事のアドベントカレンダーはもともとささやかなtipsを連続で紹介するといった風情の取り組みだったといいます。
本日の記事はアドベントカレンダーの起源に迫るような小粒でもぴりりと辛い、そんな記事を目指しましたがいかがでしたか?

明日はid:Windymeltです。

Dockerの--link/Docker Composeのlinksはdeprecatedだった、気になる脱出先は? 年収は?

知らなかった……。

Warning: >The --link flag is a legacy feature of Docker. It may eventually be removed. Unless you absolutely need to continue using it, we recommend that you use user-defined networks to facilitate communication between two containers instead of using --link. One feature that user-defined networks do not support that you can do with --link is sharing environmental variables between containers. However, you can use other mechanisms such as volumes to share environment variables between containers in a more controlled way.

https://docs.docker.com/compose/compose-file/#links

どうすればいいかというと↑に書いてあるようにカスタムネットワークを追加して、それに参照しあいたいコンテナをそれに参加させたらよい。

before

---

version: '3'
services:
  api:
    build: .
    ports:
      - '8000'
  front:
    build: .
    ports:
      - '3000:3000'
    links:
      - api

after

---
version: '3'
services:
  api:
    build: .
    expose:
      - '8000'
    networks:
      - local_dev
  front:
    build: .
    ports:
      - '3000:3000'
    depends_on:
      - api
    environment:
      GRAPHQL_ENDPOINT: 'http://api:8000/api/graphql'
    networks:
      - local_dev
networks:
  local_dev:

おわり

意外とさっくり移行できて助かった。

「EC2 Container Service (ECS) のBlue/Greenデプロイメント」のコードを読む

EC2 Container ServiceのBlue/Greenデプロイメント | Amazon Web Services ブログ
GitHub - aws-samples/ecs-blue-green-deployment: Reference architecture for doing blue green deployments on ECS.

これら眺めてみる。
方針としてはLBのtarget groupをつけかえるというもの。

deployment-pipeline.yamlがデプロイCIを構築するCFnテンプレート。

CodePipelineで各ステップを実行している。

やっていることはCFnで新しいtarget groupを作り、ボタンで承認されたあとで切り替えるLambdaを実行する。

新しいserviceを作るCFnテンプレートはservice.yamlにあり、ecs-clusterを作るテンプレートでパラメータを変えて2度実行してliveとbetaが作られる

切り替えるLambdaはPythonで書かれたblue_green_flip.pyを実行する。
swaptargetgroupがメインのロジック。

SNSで同僚をフォローするのをやめた

SNSというかTwitterで同僚をフォローするのをやめて精神衛生が良くなった。

一緒に仕事をするうえでパーソナリティも知りたいなと思って基本的にフォローしていたのだけれど、最近それによって楽しいとか便利と思うことより、きついと感じる機会のほうが多くなったのでやめた。

『エンジニアのためのマネジメントキャリアパス』を読んだ そもそものきっかけは、チームでテックリードというエンジニアのリーダー的な役割を拝命したからだった。 チームを良くするためにという思いもあって、チームのエンジニアのそれも職能のうちの一部に限定して基礎的なマネジメントに手をつけはじめた。

会社にはシニアエンジニアによるメンタリング制度がありそちらはチーム のエンジニアがメンターを務めることになっている。その制度とは別にテックリードとしてチームメンバーのキャリアプランなどを鑑みて、チームと個人がお互いにハッピーになるにはどうしたらいいか、ということを定期的に話している。

そうすると必然とメンバーの個人的な動向を気にするようになる。有り体に言うと最近仕事うまくいってそうだな・調子悪そうだな、とか、そういうこと。

調子が良い時は良いけれど、悪い時はだいたい何かあまり楽しくないできごとによって調子が上がっていないことが多い。プライベートで何かあったとか、季節の変わり目でフィジカルがついてこないとか、上司含む他のメンバーと折り合いが付かない、とか。

そういう動向を察知したら解決に向けて手伝えることはないかと声をかけたりもする。幸い今まで自分の手に負えないような巨大なできごとはなかったので、それなりの負荷でそれなりの点数で解決できたと思っている。

しかし、そう、少なからず自分にも負荷がかかることも事実なのだった。

少なくとも今のところの自分は、相手の感じているストレスを自分なりに受け止めて、まず相手のストレスを柔らげて緊張をほぐす、というやりかたをとる。

個人的な体験だけれども、エンジニアという課題を解決するということを生業にしていると、自分が感じているストレスを課題に変換できないことは、自分の能力の不足ではないかとか、そもそも課題ではないのだから周りを変えるかたちでの解決ではなく、自分が適応すべきなのではないか、とかそういうことを考え込んでしまう。

もっと言うと自分の感じているモヤモヤを課題というレベルまで昇華できなければ他人と共有できないとさえ思い込んでいる節がある。

こうして冷静な時に書き出してみれば「そんなことないでしょ」と思えるのだけれど、真剣に悩んでいる時あるいは弱っている時は、そういう考えに至ることができない。

こういう体験は学生時代にはあまりしなかったから職業病みたいなものだろうと捉え、そうすると個人的な特質というより普遍的である可能性が高いだろうから、特に同じエンジニアに対してはまずストレスの置かれている状況に共感を示して「モヤモヤしている状態を伝えてもいい・伝わる可能性がある」という安心をしてほしいな、という思いがある。

こういった振る舞いは自分の強みでもあると思っている。ストレングスファインダーを受けたら5つの特質の中に共感性 (sympathy) が挙げられていた。

だからこそ、こういったメンタリングが自然にできているのだと思う。

とはいえかなり消耗するので、得意というのも違うかなと思っている。燃費が悪い。

だから適度に休息が欲しいけれど、そこでSNSでの繋がりがネックになる。

自分の仕事はTwitterを見たりツイートすることではないので、仕事をするというコンテキストから (一時的に) 離れている時だけTwitterを見るが、そこで同僚をフォローしているとそのツイートに一喜一憂してしまう自分がいる。

わかりやすく「仕事がうまくいかない!」とか言っている場合もそうだし、こっちが滅入っている時は「疲れた」という発言にさえ自分の瑕疵を疑いぎくりとする。

ほとんどノイローゼみたいな時は、自分がファシリテータを務めたMTGを終えた10分後くらいに「は〜〜」みたいな溜息めいたツイートを見た時は、責められているのかと思い落ち込んだりもした。けれど普通に考えて無関係な確率のほうが高いと思う。

で、いろいろ考えた結果、同僚をフォローするのをやめた。少なくともチームメンバーはフォローしないことにした。 自分の負荷を減らすためでもあるし、またよりよいマネジメントを目指してのことでもある。

つまり一緒に仕事をするメンバーのことは、仕事というコンテキストから見える姿のみを見て理解しよう、ということ。

たとえば、大袈裟にいえばSNSとかで仕事とは関係のない思想信条を発露していたとして、それが自分の主義と反するものだった場合、いくら仕事と関係のない思想信条であったとしてどこかしらにひっかかりを覚えずにはいられないと思う。

また、実際にあったSNSでの発言に被害妄想めいた不安を抱いたとして、けっきょくそれを確かめるにはプライベートとかまで踏み込む必要があるが、それは泥沼だ。自分がケアしたいのは仕事仲間としての同僚であって、同僚という人間そのものではない。

というようなことを考えてフォローするのをやめた。そもそもけっこう前から同僚はフォローせずに非公開リストに入れて見ていたのだけれども、それも消した。

まあ、これは近年のTwitterがリスト機能をぞんざいにしているので、そもそも機能が使えなくなるのでは、といった不安もきっかけだった。

ちなみに結果からすると自分は同僚のSNS上の発言や振る舞いを見て落ち込んだりもしたけれど、あくまで自分が一方的な解釈をしただけのことなので、自分含めた人々は好きにSNSをやるのがいいと思う。

『みんなではじめるデザイン批評』から学んだコードレビューのやりかた

同僚が社内グループに『みんなではじめるデザイン批評』を読んだ感想を書いていて、それが良かったのとそういえばこんなことあったなと思い出したので読んだ感想がてら書きます。

みんなではじめるデザイン批評―目的達成のためのコラボレーション&コミュニケーション改善ガイド

みんなではじめるデザイン批評―目的達成のためのコラボレーション&コミュニケーション改善ガイド

命ずるな、尋ねよ

『みんなではじめるデザイン批評』を読んでからデザイナーとの会話だけではなくエンジニア同士でコードレビューをする時にも「まず、意図を尋ねる」ことに気をつけるようになった。

「自分だったらこうするのにな (だから、こうすべき)」と思ってもそのまま脳直で出すのではなく、なにか事情があるのかもしれないと思い「こうなっているのはなぜですか?」と聞く。 そうすると「時間がないけど急いでいるので、今は妥協したい」「イケていると思っていないけれど他に思いつかなかった」などの返事がもらえるので「時間がないのは仕方がないので落ち着いて考えなおすというissueを入れましょう」とか「こういうやり方だとしっくり来ませんか?」という返事へ進める。

この質問形式のコードレビューなら、チームに入り立てだったり経験の浅いエンジニア (以下、一般的な語法に則り「ジュニアエンジニア」とする) でも真似しやすいのではないか、と思っている。

コードレビューは構造上どうしてもレビュアーに対して権威付けが起きやすいが、質問形式でレビュアーが常に下手に出るよう振る舞う儀式的な関係を強いるとバランスが是正されるのではないかと思う。 加えて、この「レビュアーは常にまず自分の未知を想像して尋ねる」という姿勢をいつでも誰にでも強いて、レビュアーとレビュイーというロールで振る舞う時には対等であると意識付けることが肝。

コードレビューのやり方として「ここはこうだからこうすべきです」という言い方にしましょうというルールだと、そこまで断定できるドメイン知識や経験のない人はすぐに実践できず、かくあるべきレビュアーとしての振る舞いと現在の自分のあいだに断絶が生まれてしまう。 コードレビューでレビュアーを務めることはドメイン知識やエンジニア一般としての経験を積むチャンスなのに、レビュアーを務めるにはドメイン知識や経験が必要、というデッドロックが起きてしまって、これはまずい。

模倣

しかし「尋ねるレビュー」は誰でも真似できるのが一番の利点だと思う。そのシステムに詳しいエンジニアも、そうでない人も、変更に対して「どういった理由があってこういった変更になったのですか?」と尋ねる。 レビュイーはその疑問に明快に答える。言い淀むところがあればそれはより良くするチャンスかもしれない。それはエキスパートであってもそうでなくとも、そうする。

世の中いろいろな仕事があるがコードレビューは誰でもできたほうが良いし、そうできる可能性の高い仕事のひとつだと思う *1。 それは対象のソフトウェアの知識を効率良く培う方法であるからでもあるし、チームによるソフトウェア開発を効率的に進めるためだからでもある。

こういった仕事は担当できる人を効率的に増やすために模倣しやすく設計するのが大切だと常々感じる。 また、とても専門性の高い仕事もある一方、大抵の仕事はまず模倣するところから始まるはずで、裏を返せばそっくり真似のできないやり方はもっと改善の余地が潜んでいそう、とも言えそう。


また、普段のレビューをこういう形式にしておくと、絶対にやってはいけないこと (例: XSSCSRFなど脆弱性を作り込む) に対するフィードバックとして「〜してはいけないです」という表現が活きてくる。つまり何らかの指標に基づいて明らかにやってはいけないと誰でも断定できるときは断定してよい、という「例外」を設けられる。

さらに考えを深めると、普段コードレビューで指摘することの大半は、その是非がほとんど決定的じゃないということも考えられる。 つまりN+1クエリはバッドパターンだけれど、いついかなる時も致命的かというとそうではない、とか。アクセス数がほとんどないとか、Nが高々10くらいなら、大したことにはならない、みたいなそういう話。

もちろん今まで書いたことだけやっていれば万事良しかといえばそうではなくて、細かな言い回しに気を付けるといったことは依然として必要だろうと思う。 今回はコードレビューにおけるプロセスのうちいくつかを形式的にしようという話でしかないから、そういったウェットな話題は残っている。が今回は言及しなかった。

タイトルに反して「制作物に対しより効果的でヘルシーなフィードバックをしたい」と思う人にとって『みんなではじめるデザイン批評』は示唆と発見に富んでいることと思います。おすすめです。

みんなではじめるデザイン批評―目的達成のためのコラボレーション&コミュニケーション改善ガイド

みんなではじめるデザイン批評―目的達成のためのコラボレーション&コミュニケーション改善ガイド

*1: もちろん卓越したコードレビューのスキルとそれを持つ人がいることも知っている

『エンジニアのためのマネジメントキャリアパス』はなりたてのテックリードが読んで安心できる良い本

エンジニアのためのマネジメントキャリアパス ―テックリードからCTOまでマネジメントスキル向上ガイド

エンジニアのためのマネジメントキャリアパス ―テックリードからCTOまでマネジメントスキル向上ガイド

各所で良いという評判を聞いており、ちょうど先々週くらいに京都に台風が来るので自宅に引っ込み続けられるように急いで買った。Amazonでは売り切れていたのでリアル書店で買った。

読もうと思ったきっかけ

テックリードというロールを拝命して1年と数ヶ月が経つ。なんとなく今までの仕事と違うので知識の地図を広げたほうがいい気がするな、と思っていた。
現在所属する組織であるはてなとしてはテックリードはマネージャーではなくアプリケーションエンジニアの専門性を高めていく方向性の先にあるロールとされているけれど、マネジメント、とりわけプロジェクトマネジメントについては必要とされるなという感触をおぼえた。

見積もりとか計画作りとか、そういう分野は別途学んではいるが、しっくりこないというか要素技術だけ学んでおり有機的に繋がらず身に付いたかどうか怪しいので、シナプスを繋げたいなーとかねがね思っていた。
ソフトウェア技術では、最高のソフトウェアを作るためには、たとえばいつでも素早くリリースできる技術 (Continuous Delivery) があることで平均障害回復時間の改善に繋がったりリリースフローが滅びるリスクを下げられるとか、たとえば関心を分離する方法論 (layered architecture, clean architecture, etc.) があるとか、そういう咀嚼ができているからこそ「こういった分野の学習は、こういう時に役立つ (ので今やったほうがいい)」とか知っているけれど、マネジメントに関してはそうではない。
一言でいうと「テックリードはなぜプロジェクトマネジメントをやったほうがいいのか」「どれくらいできるといいのか」みたいなのが知りたい、というかんじかも。

そこでこの本が登場し、目次を読むとよさそうだった。で、実際に読んだところ良かった。

学び

最近テックリードないしマネジメントを学びはじめたけれど何をしたらいいかわからないや、という人はこれを読むととっかかりが見つかりそう。少なくとも自分は見つけられた。

まずこの本は「エンジニア (技術職) のために」というコンセプトなので、マネジメントについて本格的に書いてあるというより、エンジニアの視点・価値観に近いところからアプローチしてあるのがよい。
最初はマネジメントされる側の視点から始まって、メンター、テックリード、チームマネージャー……という風にマネジメントする対象がより大きくなっていくという構成が丁寧。

抽象的な精神論ばかりではなく、たとえば「提案を断る時にはいきなり『いいえ』『できません』と言うのではなく『なるほど、そのためにはこういうものが必要ですね』といった風に実現のために必要な対価を求める」といった具体的なテクニックが書いてあって、ほどよいバランス感だと思う。
実際に使える・使うかよりも、こういう今の自分でも実践できそうな情報が盛り込まれているかどうかで未知の分野について書かれた記述を信用していいか・地に足が付いているかを判断できるのが良い。

さらに自分が所属している組織にはチーフエンジニアやシニアエンジニアといった職位があるが、そういった同僚を他者評価する時にも「期待」が設定しやすくていいと思う。
つまり、まだ自分が経験していないポジションに求められるスキルや役割への理解が進みやすいということ。各章の冒頭に筆者が書いたとても丁寧なジョブディスクリプションがあり、通り一辺倒じゃない様々な働きについて述べられている。
(もちろんこれはあくまで一例であって、自分たちの組織において求めることを自分たちの言葉で書くのが良いし、そうすべきだと思う)

ジョブディスクリプションを書くのはこれからテックリードないしそういったポジションを目指すエンジニアにとっても意義がありそうなので、早速やろうという

後半のCTOや経営メンバーの章はだいぶ高度で「へー」というかんじでわりと流した。こういうことを考えているのだな、という知識がついたのはよい。

一方、マネジメントの各技術については別途専門の本を読むのがよさそうに思う。

シグマのSAマウント終了告知が丁寧かつ真摯で素晴らしかった

SAマウントカメラ・レンズをご愛用の皆さまへ | レンズ | SIGMA|株式会社シグマ

レンズ交換式カメラを開発・製造しているシグマがカメラシステムを終了させる告知を出して、告知をすること判断自体とその告知内容がとても丁寧で心証が良かったです。

前提

カメラシステムはカメラボディ自体と交換レンズおよびフラッシュなどのアクセサリからなります。
カメラシステムの導入にはカメラボディ + レンズで最低数万、熱心なユーザーは数十万・数百万という単位でお金をつぎ込みます。

カメラシステムは複数社が展開していますが、基本的に互換はないので熱心なカメラユーザーはこれから数年・十数年に渡って数十万〜数百万のお金を使う先を吟味して選び、決定しています。

またシグマという会社は撮像センサー (フィルムカメラでいうフィルム、カメラの根幹をなすパーツ) にFOVEONという他社とは一線を画す革新的なセンサーを採用しており、そのセンサーから得られるイメージはとても熱心なファンをひきつけています。
詳しくはメーカーのテクノロジー説明を読んでもらいたいのですが、一言で表すならば技術的な独創性ゆえに「XXXと比べて画質が良い・悪い」といった他のメーカーと同じ次元には立たずに、根本的に出てくるイメージそのものが違う、といったレベルの違いがあります。

終了告知自体の潔さ

カメラシステムに限った話ではないですが、莫大な資産を投じてきた既存ユーザーを刺激しないよう明示的に開発終了はアナウンスせずに、単に既存製品をディスコン (discontinued; 生産終了) させてフェイドアウトさせるに任せる、といった方針をとる会社が多いです。

実際、ユーザーは製品のリリースサイクルなどをもとに早いうちから「緩やかな死」を感じとって他システムへ移行しますし、すべてのユーザーに納得してもらえるようなアナウンスは難しい (なにせ大金を注ぎ込んできたものがほぼ泡になる) ですから、下手に行動を打つよりはひっそりと、というのは良し悪しはともかく合理的であるといえそうです。

そういった慣行・背景がありながらもきっぱりと終了をアナウンスする姿勢からは、システムそれ自体よりもシステムを作ってきたシグマという会社を信頼してくれているという自信が見えますし、ひいてはカメラシステムという息の長いプロダクトに対するビジョンの強さが伺え、ポジティブな印象です。

しかし単にアナウンスがあったからだけではなく、その内容が実に丁寧で真摯だったからだと思います。

告知の真摯さ

SAマウントカメラ・レンズをご愛用の皆さまへ | レンズ | SIGMA|株式会社シグマ

要旨としては:

  • SAマウントは終了します
  • Lマウントという新しいシステムを開発します
    • Lマウントはこれまで要望を受けてきた製品 (フルサイズセンサー搭載のカメラ) を実現するために最適な仕様です
    • 他社とのアライアンスを通じて最高のシステムにします
  • SAマウントのレンズ生産は続けます
    • Lマウントシステムのリリースまでに空白期間は設けません
    • 既存のSAマウントをLマウントで使えるよう移行手段を用意します

……といった内容です。

この告知は、事実を端的に説明し表現を濁した点が無く、ユーザーが最も知りたい点、すなわち現行システムの将来 (いつまでに、どうなる)、新システムへの移行がどうなるか、に絞って書かれています。

簡潔でわかりやすいだけではなく、実はこの告知には「申し訳ありません」などの謝罪表現が一切使われていません。
これから作る新しいものは絶対に良い自信があるし、それに向けた滑らかな移行手段も用意するから安心して、という強いビジョンが伺えますし、それを根拠付けるシグマからのアクションが提示されているからこそ説得力のあるものになっているのでしょう。

また、新マウントへ変更してより素晴しいシステムを作っていく必要があるというシグマ側の事情も匂わせつつも言い訳がましく見えないのは、けっきょくこの判断は最高のカメラシステムをユーザーに提供したいからです、という視点に立っているからという点も見逃せないです。

感想

これだけ丁寧にアナウンスしても少なからず反発はあるでしょうし、かなりハイカロリーな決断だと思いますが、シグマのプロダクトに対するビジョンの強さを感じさせ、シグマ製品を愛用している自分のシグマに対する信頼感は増しました。

アナウンスからはユーザーに対するケアだけでなく、これから作ろうとするシステムに対する強い自信を感じられます。
ユーザーに寄り添う姿勢も、新しいものに対する自信も、どちらか一方だけでは片手落ちで、両方を兼ね備えているからこそ納得感のあるアナウンスになったのでしょう。

カメラ業界的には決して大きなパイではないシグマですが、確かな地位を築いており今回のアナウンスはさらにそれを磐石にするものと思います。

いち開発者としても感じるところがたくさんあり、業界は違えど、見習いたいところばかりです。