生きているのならシェルスクリプトにだってなってみせる、そうPerlならね

シェルスクリプトを書くのをやめる - blog.8-p.info

これを見て:

Shell - run shell commands transparently within perl - metacpan.org

use Shell qw(cat ps cp);
$passwd = cat('</etc/passwd');
@pslines = ps('-ww'),
cp("/etc/passwd", "/tmp/passwd");

Synopsisより。

もうちょっとそれっぽく書くとこう:

cp '/etc/passwd', '/tmp/passwd';

仕組み

Shell.pm - metacpan.org

コメントを除いて150行に満たない小さいコードです。答えを言うとメタプログラミングで実現されています。

オリジナルはLarry WallによってPerlの強力さをデモンストレートするために書かれたそうです。興味深いですね。

use

Perlを知らない人向けに解説すると、Perluse Shell qw(cp mv);BEGIN { require Shell; Shell->import('cp', 'mv'); } のsyntax sugarです。 qw(cp mv) はリストリテラルの一種で ('cp', 'mv') と等価です。

requireはパス上からShellというパッケージを探して読み込む処理で、それを行った後にimportというサブルーチンの呼び出しを行います。

呼び出されるShellパッケージでこのimportサブルーチン *1 を定義しておくと、読み込まれた際に任意の処理を実行できます。

シンボルのインポート・エクスポートとシンボルテーブル

ところでこういうPerlのコードを見たことがあるかもしれません:

use Carp qw(croak);

croak('oops');

Carpというモジュールをuseするとcroakというサブルーチンが使えるようになりました。不思議ですね。

これは専用の言語機能があるわけではなくて上述のimportで何かをしています。

Perlにはシンボルテーブルというものがあり、他の言語でいうスコープとか環境 (environment) と呼ばれるものとだいたい一緒です。

変数やサブルーチンはこのシンボルテーブルに属しており、パッケージごとにシンボルテーブルが別れています。言い換えるとシンボルテーブルの境界をなすのがパッケージです。

Perlのおもしろいところはこのシンボルテーブルをランタイムにわりと自由に触れるということです。触れるというのは読み出すだけではなく、書き換えることもできます。

MyPackage::my_symbol など パッケージ名::シンボル名 という風にアクセスできます。

Shellのコードでいうと *{"${callpack}::$sym"} = \&{"Shell::$sym"}; がシンボルテーブルを操作するコードになります。

$callpack は呼び出し元のパッケージ、つまり use を書いたパッケージにあたるので、呼び出し元のシンボルテーブルに対して $sym の値を \&{"Shell::$sym"} に書き換えています。

$symimport の引数リストの要素なので、 'cp''mv' になります。つまり Shell::cp などのサブルーチンを指すのですが、これらの定義はどこにあるんでしょうか。

AUTOLOAD: Perl版method_missing

答えはAUTOLOADという関数です。これはRubyでいうmethod_missingで、AUTOLOADが定義されたパッケージに存在しないシンボルを参照しようとした時に呼ばれます。

ShellのAUTOLOADは呼び出そうとしたシンボル名を取り出し、 _make_cmd に渡しています。この _make_cmd がコマンドを実行するサブルーチンを作るので、それをシンボルテーブルに書き加えることで任意のコマンドを実行できるサブルーチンが無から湧き上がったようにみえるのでした。

補足

シンボルテーブル操作とか、スコープによるガードレールを破壊するような危険な行為じゃん! と思ったあなた。そうですね。

Perlはpragmaという仕組みで、一部の言語機能の使用を制限したり警告することができます。strict refs でこのシンボルテーブル操作を制限できます。

use strict; とだけ書くとこのstrict refsも有効になります。

ちなみにこのpragmaのon/offはレキシカルに行えるので、これらをどうしても使いたい時には no strict 'refs'; と書くと一時的に無効にできたりします。

Perl後方互換性を保つだけではなく書き手をある程度信頼する作りであるとも感じます。 これをもって大規模チームでの開発に向かないと評価する向きは理解できますが、2021年だしこういう「大いなる力には責任が伴う」を地で行くようなファンキーな言語がひとつくらいあっても良いのではないでしょうか。

おわり

scrapbox.io

私はちょっとした処理もGoで書くようになりました。

シェルスクリプトの一番の問題点はテスタビリティに欠ける言語設計だと思っているので、GoじゃなくてPerlでもRubyでもなんでも良いがテスタビリティが担保される言語ならなんでも良いと思います。

テストのないプロダクションコードはおぞましいと言う人が多くコモンセンスと言って差し支えないと思いますが、それが開発用の便利スクリプトとかになると途端に緩むのはどうしてなのでしょうか。 認証情報などが環境変数に露出していて、間違えると本番データを消してしまったり数百万の請求がやってくるかもしれない環境で、テストもない・dry-run機構が壊れているかもしれないスクリプトを実行することが恐しくないのでしょうか。

私は他人の書いた古びたシェルスクリプトほど怖いものはないです。

以上です。

Rubyのshellについて (2021-09-17追記)

miyagawaさんから情報提供いただきました。

Ruby演算子オーバーロードができるのでよりそれっぽい見た目になって最高ですね。

*1:関数のこと

prpl: AWS SSMパラメータストアの値を環境変数に設定するツールを作った

github.com

作った。

prpl = parameters pull toolです。

使い方

go run github.com/aereal/prpl/cmd/prpl -path /app/staging env

こういう風に使う。 -path はパラメータストアのパラメータパス。このパス以下のパラメータをすべて取得し、環境変数として設定、コマンドを実行する。 SecretStringもdecryptされて設定される。

injecuetと組み合わせるために作った。もちろんこれに限らずSSMパラメータストアと連携させてコマンドを実行するのにも使える。

this.aereal.org

ちなみに横着してバイナリの配布はしていない。自分の用途では go run で問題ないので。気が向いたらバイナリ配布します。

環境変数命名規則

READMEにも書いてあるけど、 -path に指定した文字列を取り除いた残りを使う。英数字以外はすべてアンダースコアにして大文字になる。

なぜこうしているかというと、 /app/staging/creds/id/app/production/creds/id のようにパスの先頭に環境などを含めているケースを想定しているため。 環境変数を参照するアプリは環境を意識せずに CREDS_ID と参照し、prplを実行する際に -path を切り替えて実行することを想定している。

ssmwrapとの違い

非常によく似たツールにssmwrapがあって、というかもともとssmwrapを使おうとしていたけれどバグがあり修正PRを送ったものの応答がなかったのと、上記のようなもっと合理的な命名規則を採用したく、では別のものを作ろうと思い立った。

ssmwrapと違いオプションは -debug-path だけで、コマンドを実行する機能しかない。 正直、ssmwrapのオプションは -paths とか -names とか -prefix とか -env とかかなりややこしくて所望する結果を得るのにどういうオプションを渡したら良いのかかなりとっつきにくかったので、しょっちゅうオプションを変えるわけではないとはいえ、UNIX的世界観のツールらしくもっとsimpleかつeasyにしようという思いもあった。

また、ファイルへの書き出し機能は、おそらくECSやDockerのenvironmentFileのために追加されたのだろうけれど、これって env(1) で良いよなという思いもあり削りたかった。

いかがでしたか?

最近ちまちましたツール作りが捗っていて楽しい。どうぞご利用ください。

ISUCON 11予選に参加して敗退した

id:karupaneruraid:Sixeight とチームを組んでISUCON 11予選に参加した。
再試験スコアは25746点、ベストスコアは記録をちゃんと残せてなかったけど3万ちょっとくらい。

去年の思い出:
ISUCON 10の予選に参加しました - Sexually Knowing

使ったリポジトリ:
GitHub - aereal/isucon11-qualifier: 一発あてるぞ


id:karupaneruraさんの: ISUCON11予選に参加しました - 時計を壊せ

id:Sixeightさんの: ISUCON 11でISUCONに初出場して予選敗退した - ちなみに

参加するまで

仕事でバタバタしているうちにチーム編成がおぼつかず応募が締め切られたので、今年はお流れかなーと思っていたら既に応募していたid:karupaneruraメン募していたので声をかけ組んだという流れ。
そこにid:Sixeightも合流してチームにゃんこ選抜になった。

縁がなければ参加していなかっただろうから感謝です。

前日まで

過去問を解く練習を2回、過去問の講評を一緒に眺める回を1回やった。

AWSアカウントの準備とかはid:karupaneruraがサクサク進めてくれて助かる、ありがとうございます。

ISUCON 10の予選問題を解いたりして、まあまあ調子よくスコアを出せたりしたものの自分とid:karupaneruraは参加していたので当然といえば当然でもある。

また、集まってやる練習日以外にもScrapboxプロジェクトを作ってそこにカンペを溜めていくなどした。これはISUCON 10の時にもやっていて、いわゆるWiki的な使い方ができて本当に助かっています、ありがとうScrapbox.

当日

やったことは二人が既に書いてあるとおり。

自分は、conditionの挿入をバックグラウンドのGoroutineにやるように書き換えたり、trendのクエリ改善をしたり。

trendのクエリ改善は、order byの条件をミスって出してはfail続きでスコアを伸ばしきることができず、前述のスコアでフィニッシュ。

感想と反省

writeとreadにそれぞれボトルネックが設定され、かつ相互に関係しあうので思考停止キャッシュやKVS退避では解決しない解きごたえのある内容でとても楽しかった。

それだけに競技中に自分が納得のいくパフォーマンスを発揮できなかったことは悔しいし、はっきりと実力の不足を感じた。

競技後に自分が最後まで担当していたN+1クエリを解決させて、それがブロックしていた他の小さい変更を入れたら予選通過ボーダーくらいにまで上がったのでなおさら悔しい。

それだけではなく、その他のコード修正でもチームメイトにGoのコードレビューをしてもらった時に知らないことが多かったし「window関数を使ったらいけそう」って言われても書けなかった。

自惚れるほど自信があったわけではないけれど実力は足りていないことは改めて実感したし、この調子だとまた来年のISUCON (あれば) の1〜2ヶ月前から準備してもぜんぜん足りなさそうなので、今からしこしこ準備することにした。
とりあえず講評で出てきた知らない新機能や使用の自信のない機能とかは一通り調べていくことにした (MySQLのlateral句とかnginxのtry_filesやinternal redirectとか)。

ISUCONという競技自体は楽しめたけれども、これまでよりずっと悔しいし反省点の多い回だったので次回は絶対に本戦に出場するぞ。

injecuet: CUEに環境変数を注入する便利CLIツールを書いた

CUEとは

CUEJSONYAMLのスーパーセットのような構文を持ちながら、データ・スキーマ検証などが行える言語のこと。 KubernetesYAML生成にも使われているそう (そのシーンで使ったことはない)。

軸となるコンセプトはTypes are valuesと表される。言い換えると型と値はを成す。 束は半順序集合で、CUEでは型のとりうる範囲を表現している。値は要素がただひとつそれ自身の束とされる。ちなみにCUEにおいて値を得ることを解決 (resolve) と呼んでいる。

ここらへんの話はすべてThe Logic of CUE | CUEからの引用・言い換えなので詳しくはそちらをあたるのがよい。

The Logic of CUEにある下記の図がわかりやすい。

{x, y}
{x}
{y}
{}

もうちょっと実用上の特徴について触れると、CUEにおいて制約も型表現のひとつとなる。たとえば >0 は「ゼロより大きい数値」という型を表す。 さらにこれら制約 (= 型) は合成可能で、 >0 & <10 だと「0より大きく10未満の数値」になる。

そして値は「ちょうどその値だけを表す型」とみなされる。言い換えると要素数が1の集合が値となる。

injecuetを作るきっかけ

ecspressoで使うサービス定義・タスク定義中にIAMロールのARNなどをtfstateプラグインで埋めていたが、使い勝手が悪く・拡張性にも乏しいので別の方法でサービス定義やタスク定義を生成したいと考えた。

というのもtfstateプラグインはTerraformが出力するtfstateの情報すべてにアクセスできてしまい、Terraformモジュールのカプセル化を破壊するおそれがあることがひとつ。 Terraformのリファクタリングをしている中でモジュールへ分割したところtfstate内の識別子が変わったことによりecspressoからの参照が壊れたことが直接のきっかけだった。

加えてコレクションの扱いが弱いことも挙げられる。 ecspressoは内部でtext/templateを使用している。range 構文が使えるが、validなJSONで配列を出力しようとすると strings.Join を使うか「ループの最後だったらカンマを出力しない」という記述が必要になる。 text/template内で strings.Join のような関数を使うにせよ、ループの最後かを検出するにせよ、テンプレート処理する時にカスタム関数の定義を渡す必要がある。

そしてそのカスタム関数の定義はecspressoの実行時に渡せるようにはなっていない。 tfstateプラグインはあくまでecspresso内に閉じた機構により動的に有効になるだけであって、外部から機能を追加できるような純然たるプラグイン機構は持っていない。

ecspressoにそのような機能を追加することも考えられたがいささかオーバーキルだし、JSONを良いかんじに出力するという点に関しては別のツールに任せるのが良いように思えた。

そこで先に紹介したCUEを利用することを思い付いた。とはいえCUE自体では実用上足りない機能があった。injecuetはその欠けていた機能を足すことを目指している。

injecuetの使い方

github.com

さてinjecuetについて。injecuetはCUE文書を受け取り、 @injectenv という属性がついたフィールドに対応する環境変数の値を注入した新しいCUE文書を出力する。

文章だけだとわかりづらいのでsynopsisを引用する。

cat src.cue
# name: string @injectvar(USER_NAME)

env USER_NAME=aereal injecuet ./src.cue
# name: "aereal" @injectvar(USER_NAME)

より実践的な使い方はこう:

cat complex.cue
# import "strconv"
# 
# #varAge: string @injectvar(AGE)
# age: strconv.Atoi(#varAge)

env AGE=17 injecuet -output ./out.gen.cue ./src.cue

cat out.gen.cue
# #varAge: "123"
# age:     123

cue export ./src.cue ./out.gen.cue
# {
#     "age": 123
# }

cue exportJSONYAMLを出力できるコマンド。引数にCUE文書を渡すことができ、複数渡すこともできる。 複数渡すとすべての制約を満たすよう合成される。

cue export は合成されたCUE文書が解決されている必要がある。言い換えるとすべて値になっていなければいけない。 age: >=0 のような型は要素数が2以上の集合なので値ではない。これが age: 17 だと値なのでOK.

injecuetの実践的な使い方

タスク定義中の taskRoleArn を外部から渡すことを考えてみる。 refs. Task definition parameters - Amazon Elastic Container Service

まずCUEによる記述:

family: "my-app"
taskRoleArn: string

これをexportしてみる:

[2021-08-17 22:31:31] ✘╹◡╹✘ < cue export testdata/task-definition.cue
taskRoleArn: incomplete value string

想定通り怒られる。

injecuetを使って環境変数を注入し、タスク定義のJSONを出力したい。 まず属性を付与する。

family: "my-app"
taskRoleArn: string @injectenv(ECS_TASK_ROLE_ARN)

injecuet実行時に環境変数として渡す:

[2021-08-17 22:37:14] ✘╹◡╹✘ < ECS_TASK_ROLE_ARN='aws:arn:iam:123456789012:role/task-role' injecuet ./task-definition.cue > resolved.task-definition.cue
[2021-08-17 22:37:26] ✘╹◡╹✘ < cat resolved.task-definition.cue
{
        family:      "my-app"
        taskRoleArn: "aws:arn:iam:123456789012:role/task-role" @injectenv(ECS_TASK_ROLE_ARN)
}

期待通り taskRoleArn に値が注入されている。

最後にexportする。

[2021-08-17 22:38:24] ✘╹◡╹✘ < cue export ./resolved.task-definition.cue | jq .
{
  "family": "my-app",
  "taskRoleArn": "aws:arn:iam:123456789012:role/task-role"
}

完成。ちゃんとvalidなJSONが出力されている。

正規表現を使う

さらにCUEのTypes are valuesという特性について思い出してほしい。以下の例を見るとCUEの強力さが伺えると思う。

[2021-08-17 22:41:14] ✘╹◡╹✘ < cat task-definition.cue
family: "my-app"
taskRoleArn: =~"^aws:arn:iam:.+" @injectenv(ECS_TASK_ROLE_ARN)
// ↑stringという定義から変わっていることに注目
[2021-08-17 22:41:56] ✘╹◡╹✘ < ECS_TASK_ROLE_ARN='aws:arn:ecs:...' \  # 間違ってIAMロールではない別のサービスのリソースのARNを渡してしまった
  injecuet ./task-definition.cue > resolved.task-definition.cue
[2021-08-17 22:42:03] ✘╹◡╹✘ < cat resolved.task-definition.cue
{
        family:      "my-app"
        taskRoleArn: =~"^aws:arn:iam:.+" @injectenv(ECS_TASK_ROLE_ARN)
        // ↑環境変数が展開されていないことに注目
}
[2021-08-17 22:45:32] ✘╹◡╹✘ < cue export ./resolved.task-definition.cue # 当然exportは失敗する
taskRoleArn: incomplete value =~"^aws:arn:iam:.+":
    ./resolved.task-definition.cue:3:15

CUEは正規表現を型に指定できる=~"^aws:arn:iam:.+" は「^aws:arn:iam: で始まり任意の文字が1つ以上続く文字列」という型を表す。

aws:arn:ecs:... という文字列はマッチしないので解決されず、JSONへのexportもできない。 環境変数の注入はありふれたアイデアだが、JSONは数値や配列など豊かな型を持っており、文字列として表現される環境変数からそれらへの変換は自明ではない。 制約を表現できるならそれに越したことはない。

式を書いて文字列を他の型へ変換する

さらにCUEは簡単な式を書くことができる。

import "strconv"

_varPort: =~"^[0-9]+$" @injectenv(PORT)
port: strconv.Atoi(_varPort)

これをexportしてみる:

[2021-08-17 23:24:54] ✘╹◡╹✘ < PORT=8080 injecuet ./port.cue > resolved.port.cue
[2021-08-17 23:25:04] ✘╹◡╹✘ < cue export ./resolved.port.cue
{
    "port": 8080
}

PORTに制約を満たさない値を渡した場合:

[2021-08-17 23:25:41] ✘╹◡╹✘ < PORT=abc injecuet ./port.cue > invalid.port.cue
[2021-08-17 23:25:48] ✘╹◡╹✘ < cat invalid.port.cue
import "strconv"

_varPort: =~"^[0-9]+$" @injectenv(PORT)
port:     strconv.Atoi(_varPort)
[2021-08-17 23:25:50] ✘╹◡╹✘ < cue export ./invalid.port.cue
error in call to strconv.Atoi: non-concrete value string:
    ./invalid.port.cue:4:11
    ./invalid.port.cue:3:11

便利。pkg · pkg.go.devに標準で利用できるパッケージがあるので、たとえば strings.Split を使ってカンマ区切りの文字列をリストに展開できる。

ちなみに _ 始まりのフィールドはHidden fields を呼ばれ、export時には公開されない。 なのでこういう風に、環境変数を束縛するが最終的な出力に含めたくない一時変数として利用することができる。

おわりに

シンプルながらCUE自体の強力さもあって複雑なJSONの構築に役立てる便利なツールができた。

どうぞご利用ください。

自動車教習所の学科教習のスケジュールを立てるのに便利なSPAを作った

教習スケジュール

ちょっと前から普通自動車免許を取ろうと思い立ち教習所に通いはじめた

技能教習はWeb上から予約できて、基本的に順番にやっていくだけなので難しいことはない。

問題は学科教習で、月ごとにスケジュールが配布されるのだけれどもExcelで作った表をPDFにエクスポートしたもので、とてもじゃないが機械的に扱うのに向いていない。 一応、PDFをパースしてテキストを取り出したらなんとかならないかなと思ったけれど、開講されていない時限に空白が入る関係上、取り出したテキストデータからセルの位置を同定できないことがわかったので諦めた。 テキストデータ以外の、たとえば位置情報とか (とれるのか?) を頑張って計算したらなんとかなりそうな気もするけれど大変そうだし、そもそもやりたいことは取り出した後にあるので手で入力することにした。

さてデータは頑張って手でインポートするとしてスケジュールを立てる際、どんなことができてほしいか。

  • 常勤の仕事がある関係上、できるだけ半休で済む単位で履修したい
    • 未履修の科目が集中しているからといって全休を取って履修するようなスケジュールは避けたい
    • そもそも1日中ずっと開講されているわけではなくて、たとえば午前と午後の間に2時間以上の間隔が空く場合は持て余してしまう
  • 同じ科目が別の日・別の時限に開講されているが、受講は1回だけで良いので「履修済みか」「別の日に履修予定があるか」を俯瞰したい
  • 全26時限のうち、未履修の科目・履修予定を立てた科目・履修済みの科目を一覧したい

……あたりができてほしい。

というわけで作ったのが冒頭の教習スケジュールアプリ。

f:id:aereal:20210804102907p:plain

見た目はこんなかんじ。

f:id:aereal:20210804103530p:plain

こんな風に、同じ科目の予定が別の日に入っていたらそれとわかるようになっている。加えてここからチェックボックスを操作するとこの日に予定を変えることもできる。

特徴は:

  • 科目ごとに色分け
  • 「未履修」「履修予定が入っている」「履修済み」が一目でわかるようアイコンと科目の明度を分ける
  • 同じ科目が別日に予定が立っている場合、予定を入れ替えられる
    • 例: に科目12が入っている状態で、のスケジュールを開いてこちらに変更できる
  • 前日以前の背景をグレーにし、これから予定が入れられる範囲を視覚化
  • 履修状況ページで科目ごとに「未履修」「履修予定がある」「履修済み」を可視化

色分けについて、科目が多い関係上、どうしても色相が近く特に色覚異常のある方にとってかなり厳しい見た目になってしまったし、なんなら色覚について (おそらく) 正常である自分でもたまたま近い色相の科目が並ぶと混乱することがあるのであまり良い筋ではないかも、とも思う。 しかし色分けしなかったら数字だけで判別しなければいけないので、これはこれで厳しいからやむをえない。

また、予定が入っていたらそれとわかる。上記スクリーンショットだとたとえば8/6の5〜7時限に予定を入れたので、午前の1〜2時限を取ろうとすると全休しないといけないし、そもそも3〜4時限ぶん空いてしまうな、とかがわかる。

賢いテクノロジーでスケジュールを自動で立てようかと思ったけど、仕事の都合とか自分の体調その他の都合で急遽別の日にしたいこともあるので、自分でスケジュールを立てること前提で使いやすいものにした。

技術的な詳細は:

……というかんじ。さっと作ってさっと動かしたかったのでいつものグッズにした。

おかげさまで無事にあと3時限履修したら卒業テストが受けられる状態になったので、あとは技能教習をがんばります。サンキューです。

OpenAPI定義に沿ってバリデーションをしてくれるGoのライブラリを書いた

GitHub - aereal/go-openapi3-validation-middleware: net/http middleware to validate HTTP requests/responses against OpenAPI 3 schema using kin-openapi.

kin-openapiというOpenAPI 3定義を読んでリクエスト・レスポンスのバリデーションをしてくれるGoのライブラリがあるんだけど微妙に使い勝手が悪い。 素朴に使おうとするとHTTPハンドラ内でバリデーションに関するコードを書く必要があって関心を分離させるという目的を果たすにはちょっと弱いし、得られたエラーを一貫して取り扱うにはエラーをHTTPレスポンスに加工して返すところまで一気通貫で取り扱いたい。

Goでnet/httpを使ってHTTPサーバを書く時は、RackやPlackのように、ミドルウェアあるいはサンク (thunk) を組み合わせてリクエスト/レスポンスの参照や加工を行えるので、この仕組みに乗りたい。

というわけでREADMEのsynopsisから引用:

import (
    "net/http"

    "github.com/aereal/go-openapi3-validation-middleware"
    "github.com/getkin/kin-openapi/routers"
)

func main() {
    var router routers.Router // must be built with certain way
    mw := openapi3middleware.WithValidation(openapi3middleware.MiddlewareOptions{Router: router})
    http.Handle("/", mw(http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) {
        // this handler is called if validation succeeds
    })))
}

見てわかるように、http.Handlerを扱えるライブラリならnet/http以外でも使える。 実際に自分はhttptreemuxで使った。

WithValidation はリクエストとレスポンスどちらも検証する。 用途に合わせてリクエストのみを検証する WithRequestValidation, レスポンスのみを検証する WithResponseValidation というエントリポイントをそれぞれ用意してある。

  • 自動テストやQAが十分であればレスポンスの検証は本番環境では不要という考え方もありうること
  • 実装の都合上、書き出されたレスポンスボディをすべてメモリ上に確保するためGoのio.Writerインターフェースの良さをスポイルしてしまっていること

……といった理由からレスポンスは検証せずリクエストのみ検証するというオプションを用意した。 ちなみに WithValidationWithRequestValidationWithResponseValidation を合成しただけ。ちょっとおしゃれで好き。

実装してのおもしろポイントといえばhttp.ResponseWriterを独自実装したところとか。

go-openapi3-validation-middleware/response_writer.go at main · aereal/go-openapi3-validation-middleware · GitHub

http.ResponseWriterはインターフェースなのでnet/httpがデフォルトで持っている実装以外を使うこともできる。

このライブラリではレスポンスボディとステータスコードを保持するための実装を書いた。 レスポンスを検証する際の最終的なレスポンスは、内側のHTTPハンドラが書き出すかもしれないし、このライブラリがエラー報告を書き出すかもしれない。 なのでbufferingResponseWriterのWrite()とWriteHeader()の書き込みインターフェースはそれらを呼び出した時点では引数を保持するだけにしてある。

レスポンスを書き換えるパターンのミドルウェアは初めて書いたので手札が増えてよかった。

as a builder

ソフトウェアエンジニアリングやると究極的には一行もコードを書かなければバグが混入することもないしなっていう気持ちになることがある。

それはテコを効かせるエンジニアリングの考え方として理に適っているので納得している。

早すぎた最適化とかは「やりすぎ」自体が問題なのではなく最適化という枝狩りの山はよく外れるからエビデンスを集めてからやれということだと思うし、最初から将来に渡ったユースケースもカバーしてめちゃくちゃ速いものを作れるならそれに越したことはないはず。

一方でソフトウェアコンストラクションという手段にこだわりたい自分もいて、この時折顔を見せる自分はなんだろう、とずっとモヤモヤしていた。

ふとこれはビルダーとしての自分だなと気がついた。作ることが手段であり目的でもある存在としてビルダーというラベルはしっくりくる。

常に便利なソフトウェアを作ってものごとを良くしていくというやりかたにこだわろうとする自分をビルダーという振る舞いにあてはめて認めてあげたい。

言葉を尽くす

大抵の誉め言葉を嬉しいとは感じない。どれもこれも社交辞令に聞こえる。なぜ社交辞令に聞こえるかといえば、着眼点がずれていたり、解像度が低かったりするから。

これも本当にズレている・見えていないだけのこともあれば、当人の認識は的を外していないけれどもそれが言語化する際にスキルが伴わず伝わらないだけということもあろう。

 

ポジティブ・ネガティブ問わずフィードバックする際に限らず、たとえばタスクひとつ振る際にも背景や要件だけではなく「なぜあなたを指名したか」「これを通してあなたが何を得ると期待しているか」といったことを過不足なく伝えることはこだわり徹底したいことのひとつ。

別に指名の理由が大仰なものと限らず「単に暇そうだったから」ということもあるだろうし、それはそれで良いと思う。その場合は正直にそう伝える *1。でも「他に頼れる人がいないのでお願いしたいと思っている」とそう伝えることはできよう。

 

大人数の群として大きな仕事をやっていこうという中でどうしたって個人の自尊心は後回しにされがちなのは否めない。

もちろんそれは当人の内的なものであって外から与えたり充たすような類のものではない。

ただ、不足や欠落があって少しずつ瑕がつき、いずれ大きなものとなるというのはよくあること。

何より自分にとっては言葉を尽くして伝えようとしてくれる行為そのものに好感を抱くし、群を動かし大きなことを成そうとする能力があると信頼に足る。

 

1on1を、会議よりも有意義な対話にするためには、もっと自然体で臨む必要があります。メンバーの言葉を聴き、感じた印象はストレートに伝えます。とりわけ、お互いの感情を織り交ぜながら対話するほうが伝わりやすくなります。

リーダーとメンバーがお互いの感情を伝え合い、感情面での共感や反発があって「ガチ対話」が生まれやすくなります。テクニックを身に付けていくことはもちろん重要ですが、テクニックを活かす土台作りとして、感情を共有しあうことを意識しています。

「ガチ対話」でエンジニアチームのエンゲージメントを高める1on1の工夫 - ZOZO Technologies TECH BLOG

最近インターネットで見かけた似た考えについて書かれている記事。

 

自分にとってはグレード昇格時の id:motemen さんからのコメント・フィードバックがとても嬉しくて印象に残っているので、目指すべき姿のひとつとなっている。

*1:さすがに「暇そうだったから」とそのままは言わないけれど

入社以来未だオフィスに踏み入れていない自分の転職とオンボーディングについて

前回までのあらすじ

転職した: 新しい職場で半年経った

面接を受ける1年前 (2019年初頭) にカジュアル面談に行った時にオフィスを訪れて以来、面接から入社後に至るまで自分は一度も現職のオフィスに踏み入れていない。
転職から入社

転職活動

新しい職場を決めるにあたって重視した点は金沢からの勤務実現に前向きか同僚となるメンバーが刺激的かどうかの2つ。

これは以下に引くようにこれまでに言及してきた:

数年前に観光で訪れた金沢を歩いてから一目惚れしてしまい、自分がここで生活する想像をするうちに単なる夢想から具体的に実現することを考えはじめた。
これを書いている時点で、株式会社はてなの事業拠点は東京と京都のみであり、在宅勤務は育児や介護、その他会社が認めるに足る理由があるケースのみ認められている。
平時は週数日程度スポットでの在宅勤務はマネージャーと合意した上では認められている。またコロナ禍においては在宅勤務推奨となっている。ただし、継続的にフルリモート勤務というのは上記の通り認められる事例は限定的になっている。
このまま在職しつづけても実現が難しそうと判断したのが、転職を決意した最大の動機といえる。

株式会社はてなを退職

転職先を決めるにあたって一緒に働くメンバーから刺激をもらえそうかを一番重視した。
これは抜きん出たタレントを持っているとか輝かしい経歴を持っているかとかではなく:

  • 知的好奇心を持っているか
  • それがよく表出しているか (= アウトプットされているか)

……ということを重視している。

新しい職場で半年経った

転職ドラフト

上述の条件の他、漠然とWeb系で、くらいしか思い付かなかったのでとりあえず世にどんな企業があるのか知ろうと思い転職ドラフトに登録した。
当初は自分にスカウトが来るとしてどれくらいの条件なのか知りたい、くらいの気持ちだったけれど。

転職ドラフトに登録していくつかスカウトをもらってわかったことがいくつかある。

まず、提示される条件は自分の市場価値がどうとかより企業が儲かるかどうかが支配的ということ。
具体的には、スカウト文言の熱量の高さと年俸の高さに相関はあまりなかった (熱烈だけど低いというケースはままあった) し、額面は高いけれど明らかにこちらのレジュメ読んでいないなってわかるケースもあった。
考えてみれば当然ではある。
「リモートワーク希望、で少なくとも首都圏では働きたくないです」ってレジュメに書いてあるのに「当社の(都内の)オフィスにはこんなに福利厚生があって〜」みたいなことをスカウト文言に書いていた企業は申し訳ないけど笑ってしまった。

金沢から勤務したいっていう条件が折り合い付かなかったのでお断りさせていただいたけれども、PLAIDからのスカウトはこちらのレジュメに書いてあることをしっかり読んでもらえたことがわかって嬉しかったし好印象だった。(レジュメに載せた過去の発表内容や就労条件についてちゃんと言及いただいていた)
今回はマッチしなかったけれども記憶には残ったので、こういう細かい取り組みが強みになるんだろうなーと感じる。

企業探し

そんなわけで転職ドラフトに参加してみて、漠然と首都圏や近畿で働いても良いなら便利な仕組みではありそうだが自分の条件ではファジーすぎるので不適と判断して地道に自分で探すことにした。

まずremote-in-japanを眺めてみることにした:
GitHub - remote-jp/remote-in-japan: Tech companies in Japan that hire remote workers

よくまとまっていて便利。転職活動をはじめた2020年前半はCOVID-19の影響下にあり半ば緊急避難的にリモートワークをやっている会社も多かったけれども、やはり以前から運用している組織のほうがいろいろと不都合はなかろうと参考にした。

が、結論からいうとここからはマッチしなかった。事業会社かどうか・メンバーの魅力 (そもそも誰が所属しているかよくわからないとかあった) などを考えるとリモートワーク可以外の点で決め手に欠けた。

面接

けっきょく知人の所属している・していた数社にカジュアル面談を設定していただき、さらにそこから合いそうだと感じたいくつかの面接を受け、最終的に現職の内定をいただき受諾となった。
面接はすべてzoomなどを利用した。

入社後

全社的に半強制的なリモートワークにあった (ある) ので、オフィス勤務時のように黙って座っているだけで新顔だなって覚えてもらう機会はなく、能動的に行動しないとかなり仕事しづらいだろうと考えていたのでとにかく存在感を出して認知してもらうことを考えた。

特に自分は基盤バックエンドというサーバサイドのお困りごと全般をなんとかするというプロダクト横断のチームに配属されたので、考えなしでいると薄く広くという関係構築になることが予想されたのでかなり気にかけた。

Slackで存在感を出す

リモートワーク環境においてはチャットで見かけるかどうかが良くも悪くも存在感に繋がると前職から感じていたので、まずSlackでの認知を高めることにした。

xxx-zassouのようなカテゴリごとの雑談チャンネルがいくつかあるので、とりあえずそこで絵文字のreactionをつけるくらいから始める。
reactionをつけること自体が認知に繋がるとは思えないけれど、とりあえず自分がそのチャンネルの雰囲気に馴染むことを目的として絵文字をつけていった。

慣れてきたら発言したり。分報チャンネルで仕事するのはやめよう高校出身なので、用意されていた *1 分報チャンネルのtopicは「社内Twitterです」と書いて未だに仕事の話はほとんど書いていない。archiveしても良い気はしているけれど、思いがけないコミュニケーションが生じていてこれはこれで惜しいのでそのままにしている。

しかし振り返ってみても「あとはがんばる」以上の取り組みをしていないな。

顔出し

zoomやGoogle MeetなどでのWeb会議ではカメラの映像を映すようにしている。
少しでも情報量が増えたほうが相手に認知してもらうフックは増えるので出したらいいかと思ってそうしている。

実際「そのフリースHaglofsですか?」とかフックが生まれたりしているのでわりと良い取り組みなのだと思っている。

まあこれは自分が必要性を感じているから苦に感じないのであって、結果が同じでも会社に「出しましょう」って言われたらエッとなりそうだなとは思う。
ので、こういう良い変化はありましたよとは書くけれど無理に勧めることはしない。

文化のすりあわせ

よく中途で入った人は「新しい風をもたらすことを期待しています」と言われるし、実際言われたけれど、しかし新しい風がなにもかも良いかというとそうではない。
なにより「前職ではこうやったで」おじさんは、たとえ言っていることが正論であっても量が過剰だったり、現職へのリスペクトがないと鬱陶しがられるだろう。

もたらす情報:受け取る情報 = 3:2くらいの割合を心がけた。根拠は特にない。求められていることに違いはないだろうから、
あと単にまだ知らなくて知っておいたほうが良いこととの比ってこんなものかな? という印象。

いかがでしたか?

開発者ブログの再興とか調整事も必要なプロジェクトを提案して軌道に乗せられていたりと、本来のパフォーマンスを発揮するには3ヶ月くらいはかかるという定説から外れてわりと良い仕事ができているのでうまいことやれていると評価して良いでしょう。

もちろん現職が提供してくれたオンボーディング施策によるところも多分にあるだろうから、ここに書いたことが結果のすべてだとは思わないけれども。

初めての転職がフルリモート下というわりとハードな状況のわりにはよくやっていると思うので自信にはなった。

*1:事前に作られていた

DDD is overratedについて

DDD is Overrated | Stefan Tilkovについて。

DDD is overratedの自分なりの受け取り方

  • DDDだけが設計手法や考えではないんやで
  • 必要なら(DDDにおけるaggregateやvalue objectのような)概念に対する名前を自分たちで付けてもええんやで

自分の考え

筆者が言わんとする「盲目的にDDDを受け入れたり、その他の手法などの探索を諦めるような思考停止はいかがなものか」ということは理解はできる一方で、生半可な労力でもってこうした一般的な設計手法の(再)発明を試みるのは概ね良い結果をもたらすとは言えないのでやめたほうが良いと思っている。

DDDなりPoEAAで紹介されているパターンなりは、数多くのプロジェクトを調査して頭の良い人が心血を注いで一般化したもので、完璧でないにせよ少なくとも見てきた事例の多さという点は評価できそう。

一方、事業(サービス)を持ってその開発をしている組織のデベロッパーが立ち会うプロジェクトの数は知れている。 それはたとえばLINEやYahooのような規模であっても一般化を踏まえるとサンプル数や偏りの是正は不足していると考える。

そも事業ドメインの知識モデリングだけでもかなりタフな仕事なので、ましてやその一般化をしようとするのはコスパが悪い。

餅は餅屋ではないけれど、こういった開発手法や設計・分析については借用に留め、発明を試みないほうが総合的な生産性は高く保てる。これらは最終的に良いソフトウェアを作るためにあるので。

もちろん選ぶのがDDDでなくとも良いし、きちんと批判的に見て合いそうか評価することは大事だけれども、ある程度は型にはまる・型通りにやるということも大事だと考える。

新しい職場で半年経った

from: 株式会社はてな 退職エントリ
to: 株式会社Classi 社の開発者ブログに書いたエントリ

決め手

転職先を決めるにあたって一緒に働くメンバーから刺激をもらえそうかを一番重視した。
これは抜きん出たタレントを持っているとか輝かしい経歴を持っているかとかではなく:

  • 知的好奇心を持っているか
  • それがよく表出しているか (= アウトプットされているか)

……ということを重視している。

転職ドラフトに参加したり、いろいろな企業を見る中でbuildersconなどのカンファレンスで顔を合わせて話すことがあったid:nkgt_chkonkさんやid:lacolacoさんが所属していることを思い出した。

所属がはっきりしているメンバーで事前に認知していたこの2人がいたので、先に述べたような資質を引き出す働きかけは頑張れそうかなと思えたのが決め手になった。

いろいろ考えたけれど初めての転職だし、うまくいかなかったとしてもこの2人と働いた経験が短くとも得られたら元は取れるかなと思えた、という防衛的な見方もある。

やっていること・やりたいこと

基盤バックエンドチームというサーバサイドのソフトウェア技術的な困り事全般をなんとかするチームにいる。
……はずなのだけれど、もはや「サーバサイド」「ソフトウェア技術的」という但し書きすら怪しくなるくらい様々なことに手が出せているのでざっくりソルジャー上がりのなんでも屋って思っている。

この基盤チームやプロダクトチームなどの現在の組織に関する情報の一端はセキュリティインシデントと大規模障害を経てClassiは開発組織をどう変化させたのか - Classi開発者ブログに書いてもらえた。

チーム・機能横断の基盤システムの設計と開発

dron: クラウドネイティブなcron代替の紹介 - Classi開発者ブログ

(自分にとっての) 本業というかんじ。得意かつ求められているかつ好きなこと。

今のところうまくいっているけれどドメイン知識がまだまだ足りていないなーと感じている。ので後述するようにプロダクトチームに出向しはじめた。

開発者ブログの編集長業

決め手でも述べたように好奇心とその表出が強い人間が好きで、そういう人間が集まっていると自然とブログを書いたりといった活動が生まれると信じている。
Classiは2020年4月に高負荷インシデントが起きたりなどの事情があってアウトプットがすっかり止まってしまっており寂しかった。

一方で社内のesaには日々のサービス開発・運用で発見したことが想像以上に活発に投稿されていて、アウトプット自体はあったのは嬉しい誤算だった。
単に対外的に出していないだけでアウトプットに対する感度は決して低くなかった。

とはいってもこのままだと進展しないのも確かなので「復興させたいです」と手をあげていろいろやらせてもらった。

  • 編集部の立ち上げ
  • 公開スケジュールの段取り
  • ブロガーのスカウト

……など。

編集部は自分とVPoEでミニマルに立ち上げて今は3人体制。まず開発者ブログをどう位置付けるのかということを表明し、興味を持っている人やステークホルダーに判断しやすい環境を整えた。
向こう1〜2年のあいだは執筆者が不特定多数の社外の読者に自分たちの取り組みを過不足なく理解してもらうだけのアウトプットをするスキルを養うことを目的としその場として開発者ブログを整えるということにした。
採用や広報のツールとして活用するという視点は、少なくとも現時点では時期尚早かつ高度すぎるのでうまくいかないだろうと思ったのでスコープから外すようステートメントに盛り込んだ。なのでPVやSNSでの反響は主要なKPIとして見ないことにしている。

プロダクトチームに出向しての草の根活動

テックリードの相談相手になったり外様として「この会議の位置付けってどういうかんじでしたっけ」みたいなことを言ったり改善の意見を出したり、もちろん一緒にちょっとしたタスクをやったりといった本当に様々なことをやっている。

これは自分がプロダクト領域の知識を付けるという意味もあるし、いろんな環境・状況での開発経験がちょっとある人間としてチームに新しい視点をもたらすという意味もある。

11月くらいから始まって評価は上々っぽいけれど塩梅がむずかしいな〜と日々悩んでいる。自分がしゃしゃり出て改善できたとしても、きっとそれはチームの糧にならないだろうし、そもそもチームの状況・事情を咀嚼しなければ「正しいだけの正論」になるだろうし。
かといってチームに悪い意味で馴染みすぎたら役割を果たせないし、というかんじでとても難しい!
が、少なくとも自分はプロダクトの知識や事情のキャッチアップが進んでいるので成果は大きいと感じる。

自分の働きが効果を発揮できているかは、おそらく自分が出向をやめたあとでしか評価できないので今はあれこれ模索中。

事前の印象と変わらないところ・異なったところ

相対的に見て前職と比べて所属を明らかにしてオープンな活動をしている人は少なかったので印象らしい印象はそんなに強いものはなかったけれど、面接などを通して文化的背景がさほど遠くないと感じ、それは今も変わらない。なんというか落ち着きがあると思う。

印象的なのは社内外で開発組織のマネージャーやリーダー経験のある人が多いので、自分のようにマネジメント・メンタリングが得意じゃない人間でもそういう仕事をやっていてささいなアドバイスをもらえて孤独感を感じにくいし、リーダー・マネージャーポジション以外にそういう背景・キャリアの人がいるのは層が厚くて良いところ。

ギャップを感じているというほどではないけれど、プロダクトチーム制への変化はけっこうドラスティックで無理が生じているところも見え隠れしている。
まあこういうのはビッグイベントの当事者でなかった自分のようにMPを消耗させた経験のない人間がサポートしていくのが良いんだろうなと思うので、しばらくそこらへんを頑張りたい。

Google Formでお手軽に自分のヘルスチェックをしてみる

気分によってパフォーマンスが左右されるな〜と数年前くらいから自覚するようになり、自分の生産性とどういう相関があるのか・ないのかを知りたいと考えた。

生産性は普段の仕事のアウトプットで評価すれば良いだろうから、あとは気分を記録すれば良いだろう、ということで掲題の通りGoogle Formで手軽に始めていたことを思い出したので書いておく。

収集

f:id:aereal:20210106173225p:plain

Google Formで記録

こういう素朴なGoogle Formを作った。

  • 😀 良い
  • 😎 最高
  • 😑 感情なし
  • 😱 大変だった
  • 😇 大いなる動きの前では何もできないちっぽけな私

みたいなかんじ。

f:id:aereal:20210106173138p:plain

Slackで毎日リマインド

これをSlackで毎日リマインドする。数年前に素朴に作ったけれど、今だとSlack AppにしたらSlack上で完結して良いかもしれない。

収集と可視化

Google Spreadsheetsに送られるのでいろいろグラフを出してみた。

まず曜日によって傾向が変わるのかを出してみる。

f:id:aereal:20180404094950p:plain

曜日ごとのバブルチャート

縦軸が曜日で、1が日曜日、7が土曜日。

どうやら自分は尻上がりタイプらしいということがわかった。

 

またこの画像を保存した当時は水曜日と金曜日に厳しい気分になることが多いようだった。

確か厳しい定例ミーティングがあったのだったような。

f:id:aereal:20180404094955p:plain

期間での感情の割合

 期間全体だとこういうかんじ。こうしてみるとわりとまんべんなく現れており、感情の浮き沈みがはっきりしているといえそう。

 

カレンダーの予定と組み合わせての評価とかやったらおもしろそうだと今思った。

データの集計方法はあまりに素朴すぎるので改善の余地はある気がする。

2020の振り返り

去年

作ったもの

仕事

はてな

退職

株式会社はてなを退職 - Sexually Knowing

8月を最終出社日として退職した。来年から金沢に引っ越す。

退職する時に同僚からフィードバックをもらう - Sexually Knowing

今でもたまにもらったフィードバックを読み返すことがあるのでやってよかった。

新メンバーのメンター

4月に新しく入ったメンバーのメンターをやった。

メンター自体は何度も経験があるけれど、その時に辞意は固めていたこともあり自分の考えとか振る舞いとかを伝えるっていうことを意識してみようと思い、いろいろメンタリングのやりかたを変えてみた。

かなりまめに様子を見たり、正解のないものごとについて「自分はこういう風に考えている」ということを伝えることが多かった。 これまで価値観のインストールはメンタリングの範疇を越えていると考えて意図的にやってこなかったけれど、新メンバーが新卒ということもあり価値基準が確固たるものになっていない内から「あとはあなたの考えに基づいて行動してね」では突き放しすぎかなとも思いやってみることにした。

もちろん染まりきっていない新卒だからこそ、自分の一挙一動に染まりやすいので果たしていかがなものかという思いはあったけれども。

その他

AWS API Gateway HTTP APIを使って認証エンドポイントを生やしたりといった仕事をしていた。

チームメイトをつかまえてLambdaとかの知識を伝授したり、単に作る以上のことはできたと思う。

現職

AWSでちょっとしたシステムを作ったり、他のチームに出向いてDX改善おじさん業などをしている。

来年

最高のアーキテクトになる - Sexually Knowing

これかなあ。転職したので、新しい職場でちゃんと実績と信頼を勝ち取っていきたい。

ポジションを作る動きが1年後にできていたら良いかなあ。

あとは新天地で楽しく生活したい。

たいへんな仕事は仲間を募ると良い

重い仕事・比較的やりたくない仕事・とっかかりが見つからない仕事など、アサインされたけれどどうにもやる気が出ない仕事に出くわすことはしょっちゅうあると思う。

そういう時におすすめしたいのが「誰か一緒にやりませんか」とペアプロ・ペア作業相手を募集すること。

実際に一緒に作業することになれば:

  • 相手の作業時間を確保するので否応なしに向き合うことになる
  • 視野が広がる
    • 単純に他人の目が増える
    • またプレッシャーが減ることで自身の視野も良くなりうる
  • 作業効率が上がる
    • 単純作業がたくさんある場合なんかにはとても助かる

また、チームの状況がそれを許さない場合でも:

  • なんか大変そうだなというアラートが届く
    • だいたいアラートを上げるのが苦手な人は顔色を変えないことが多いので、周りからフォローしにくい
  • アラートをあげる心理的障壁が低い
    • 「つらいのでやりたくないです」と言える人はなかなかいないと思うし、言ったとして何かを失うリスクも高い
    • 「つらいので誰かと一緒にやりたいです」だとやりかたを変える提案なので言う側も受け取る側もそれなりにポジティブに捉えられる

というような話をチームメイトにした。

この考えは自分が独自に辿り着いたわけではなくて id:hitode909 さんが言っていたので影響された。 一緒に仕事していた時にペアプロ・ペア作業の効能について話した時だったと思う。

ブログに書いていなかったかな〜とあれこれ調べたけれど見つからないので書きました。 かわりに見つけたお気に入りツイートを置いておきます (そのエピソードの登場人物が「hakobeさん」だったはずなのでそれで検索した):

Twitterでつぶやいていたらhitodeさんが発掘してくれました:

blog.sushi.money

こちらも併せてご覧ください。また引き続きリリカルなのは大学のエピソードもお楽しみください。