コンテナの運用ツールを作る

1時に寝て明け方に起きて7時に起きた。あまり眠れてない雰囲気がある。

docker/compose の運用

いま作っているアプリケーションは docker compose で構成している。マージ単位で gitlab ci/cd から docker image をビルドしていて、テスト環境のデプロイスクリプトで最新の docker image を取得してコンテナを再作成するようにしている。デプロイスクリプトは docker cli と docker compose cli の2つを組み合わせてシェルスクリプトで実装しているが、複数のアプリケーションやミドルウェアがあるのでそれらを統合的に扱うことはできないし、さまざまな状況を想定して動くようにもなっていない。がんばれば doccker/compose cli と jq とシェルスクリプトで細かい要件を実装することもできるけど、それをお客さんの本番環境においても使うには一定の cli 操作に慣れが必要な上、docker/compose の知識やスキルも要求してしまう。少なくとも頻繁にある運用作業として docker image の更新やコンテナの再作成が想定される。ローリングアップデートまでは実装しないけど、アプリケーションの要件にあわせた docker image の更新とコンテナの再作成 (アプリケーションの再起動) ぐらいはまとめてやってしまってよいと思う。

github.com/docker/docker は github.com/moby/moby にリダイレクトされる。docker は開発ツール、moby はインフラやライブラリという住み分けでそれぞれに関心のあることに注力するようにモジュール構成を分離している。それが2019年に行われていまもおそらくまだ途上だと思う。あと docker のモジュール群は go modules に対応していない。大きなプロジェクトが移行するのが大変なのは理解できるけれど、依存解決のような複雑なところを放置するのはまったく賛成できない。そこが不健全だと依存ライブラリの整理やモジュール分割がうまく進まない気がする。docker の client は https://github.com/moby/moby/tree/master/client に定義されていて、readme のサンプルコードにあるようにすぐに使えるようになっている。一方で compose の spec は github.com/compose-spec/compose-go で定義されていて、github.com/docker/compose はまだライブラリとして使いやすいようにはなっていない。仕様と実装が混在していて動いている状態。そういう issue もあげられている。

testcontainers-go というアプリケーションが compose をライブラリとして使う実装をしている。このコードをみれば compose をどう使えばよいのかはわかる。

自分で compose を実装してもよいけれど、compose の service を扱うための project やオプションの設定が煩雑なことも伺える。仕様と実装が混在しているというのはそこら変の整理ができていないようにみえるからだ。testcontainers-go も自前の client を用意して使いやすいようにしているのでそれを再利用した方が運用ツールを作るのは簡単になる。testcontainers-go の compose client 経由で compose の up/down を制御する。その他のコンテナの操作は docker client を直接使って実装する。この組み合わせで自分たちのアプリケーション向けの運用ツールを作ろうと思う。

デスクトップマシンの買い替え

1時に寝て5時に起きて7時に起きた。昨日も吐き気がしてうまく眠れなかった。

dell マシンの買い替え

1月頃からデスクトップマシンの調子が悪かった 。ちょうど3年経ったところでそろそろ故障してもおかしくないと言える。ちょうど年度末なので dell の半期に一度の大感謝祭を待って購入することにした。これまでは vostro を使っていた。そろそろメモリが16GiBだと厳しくなってきたところ。メモリが32GiBついているデスクトップマシンは限られてくる。 その制約もあって xps マシンを購入をすることにした。

昨日のお昼過ぎに注文したのに翌日の午前中にマシンが届いた。あまりの早さに驚いた。dell の配送のワークフローの凄さを実感した。デスクトップマシンを注文して1週間ぐらいで来たらいいと普通の感覚で思っていたら翌日なんやもんな。24時間も経ってなかったしな。

グラフィックボードからモニタに接続しようとしたら4つのインターフェースのうち3つが displayport で1つが hdmi になっていた。いまどきは displayport が主流なんやね。そんなことすらも知らなくて変換アダプタ買わないととか思っていたら、モニターも displayport をもっていて、モニターの箱にも displayport ケーブルが同梱されていて、そのまま接続できることに気付いた。3年ぐらいでマシンを買い替えないとハードウェアのデバイスの変更にもついていけないのだなということも実感した。

私はもう世の中についていけてなくて、たまに普段やらないことをやるとそこで起きることに驚いて右往左往している。windows を起動して dell update から bios やファームウェアを最新のもの更新したりしていた。windows 上からダウンロードして設定して、再起動時に自動的に bios の更新がかかるような振る舞いをする。uefi のなせる技なんだろうけど、こういうところにも驚いて感心したりする。

ubuntu 22.04.02 が 4.8GiB あって dvd-r にぎりぎりおさまらない。もう dvd-r でもインストールメディアを作ることができなくなったんやなと気付く。22.04.01 は 3.6 GiB なので1つ前のバージョンをインストールしてアップグレードすればいいかとやってみたらブート時に次のようなエラーになった。

Verification failed:(0x1A) Security Violation

bios に secure boot 機能なるものがあって 22.04.01 ではインストールできないように署名チェックが行われるらしい。古いディストリビューションをインストールするには secure boot を一時的に無効にするしかなさそう。

プライベートリポジトリの go アプリケーションの依存解決

0時に寝て6時半に起きた。面倒なお仕事を朝から集中して片づけた。

go.mod のプライベートリポジトリの依存解決

非公開のプライベートリポジトリで開発している go アプリケーションを他のリポジトリから依存ライブラリとして使う方法を調べた。go modules は基本的に公開されたパブリックなリポジトリを前提としている。go.mod のワークフローで他の依存ライブラリと同様にバージョン管理ができるようにするには、プライベートリポジトリであることを go.mod に認識させ、トークンなどを使って認証する必要がある。

go 1.13 から GOPROXYGOPRIVATE という環境変数が追加された。デフォルトでは GOPROXY は https://proxy.golang.org に設定されており、このプロキシサーバー経由で依存ライブラリを取得する。これは公開リポジトリを、ある日、作者が急に削除したり非公開にしたときにビルドできないといった問題を防ぐために依存ライブラリのリポジトリをキャッシュしてくれる役割を担っている。

一方でインターネット上のプロキシサーバーからはプライベートリポジトリへアクセスできないので GOPRIVATE を設定してアクセスできないサイトを go.mod へ教えてあげる必要がある。

$ go env -w GOPRIVATE="private.repo.jp"
$ go env | grep GOPRIVATE
GOPRIVATE="private.repo.jp"

通常 go.mod は https で依存ライブラリをリポジトリから取得 (クローン) しようとする。このときに git の設定を変更することで特定のサイトへのアクセスを ssh 経由に変更できる。次の例では https://private.repo.jp へのアクセスをすべて ssh://git@private.repo.jp でクローンできる。

$ git config --global url."ssh://git@private.repo.jp".insteadOf "https://private.repo.jp"`
$ tail -n 2 ~/.gitconfig
[url "ssh://git@gitlab.osstech.co.jp"]
        insteadOf = https://gitlab.osstech.co.jp

これで git リポジトリのクローンは ssh で行われるようになるが、go.mod のワークフローでは https でもアクセスする処理があるようにみえる。https でもアクセスできるように PAT などのトークンを取得して次のように ~/.netrc に https でプライベートリポジトリへアクセスするための認証設定を追加する。

machine private.repo.jp
login t2y 
password ${PERSONAL_ACCESS_TOKEN}

コワーキングのオンラインイベント

月例のカフーツさんのオンラインイベントに参加した。先月の所感はここ 。いとうさんの近況を把握していないが、どうやらバリ島 (インドネシア) のコワーキングスペースをいくつか訪問してきたらしい。その旅程を写真をみながらふりかえるようなイベントだった。バリ島の自然や建物の雰囲気が伺えてとてもおもしろかった。

いとうさんからバリ島はデジタルノマドの先進的な取り組みをしているように聞いていた。例えば バリ島でリモートも可能!?インドネシアのノマドビザは最長5年を検討中 にあるように最長5年のビザを用意しているという話しが日本で盛り上がっているが、現地ではまだ正式にそのようなビザがあるようには存在が確認されていないらしい。それに近い長期のビザを取得するには、一定の金融資産があることを証明しないといけないらしく、日本円だと数千万円程度ないとビザを取得できないのではないか?といった話題もあったと思う。基本的にインドネシア政府は海外から金持ちを呼び込みたいらしく、金持ち向けに待遇のよいビザを整備するのではないかとのこと。ビザの話しはともかく、ここ2-3年でバリ島のコワーキングスペースもいくつか廃業しているらしい。それは利用者の大半が外国人であったため、コロナ禍により、インドネシア政府からの要請もあって外国人に帰国を促したという。外国人利用者の半分ぐらいが自分たちの国に帰ってしまい、施設の運用コストを維持できなくなった。バリ島は今後の成長が見込まれていることから数年前から外資が入ってきて土地や家賃が急騰しているために利用者が激減すると運用コストを維持できなくなったとのこと。

肝心のバリ島のコワーキングスペースの雰囲気を聞いていると、利用者は外国人が大半で、感覚的にはヨーロッパから来ている人が多そうだといとうさんが話された。バリ島のコワーキングスペースでいとうさんが見学していたときには、現地の人たちと外国人のコミュニティが活発に活動しているようにはあまりみえなかったらしい。利用者もそう多くはなかったし、その人たちも静かにただ作業しているだけのように映ったという。いとうさんはコワーキングスペースとはコミュニティやコラボレーションが重要という話しをよくしているけれど、バリ島のコワーキングスペースに関しては裕福な外国のデジタルノマドを呼び込むのに成功しただけのような、もしかしたらコロナ禍でそのコミュニティが破壊されてしまったのかもしれないが、普段このイベントで話しているような高い期待値に応えられるような状況ではないようにみえたという。

とはいえ、日本の田舎よりは遥かにデジタルノマドが集まる場所として世界的に認知されているところなので写真をみながら私もいつか行ってみたいと思えた。

出張所オフィスの準備

1時に寝て7時に起きて朝だらだらして9時過ぎからオフィスへ行って軽くお仕事をしていた。

駐車場の契約書作成

午前中に駐車場の契約書を作成して送付した。管理会社さんとやり取りしたときに 保管場所使用承諾証明書 を即日作ってくれてスムーズに納車できた。その駐車場の契約書をいま手続きしている。便宜を図ってもらって感謝。週末から車を動かした方がよいなと思いつつ、雨降りでタイミングが悪く出掛けるのに億劫になっている。

ツールの開発とリファクタリング

いつも通り3時間ほどお仕事していた。時間はたった3時間だけど、割り込みが入らないことがわかっているので開発に集中できて生産性は高い。issue を3つほど fix して区切りがよかったので夕方には家に帰ってお昼ご飯を作ってのんびりしていた。

実家の出張所オフィスの準備

親とやり取りしていて、出張所のオフィスとするための部屋にエアコンとインターネットの回線を繋ごうといった話しをしていた。エアコンは14畳のやや大きいものを購入しないといけない。工事に来てもらわないといけないので近所の量販店で信頼できるメーカーの製品を買った方がよいだろうと話していて、いま実家で取り付けしているエアコンのメーカーを聞いたらダイキンだという。日本の会社だったらまぁいっかと思ってダイキンでいいんちゃうと返しておいた。インターネット回線もスマートフォンのキャリアとセットにした方が割引で安くなるからショップ行って聞いてみたら教えてくれるだろうと伝えておいた。これを伝えてから実際に購入するまで何ヶ月かかるのかはわからないが。

リファクタリングにはまった

2時に寝て変な夢をみて何度か起きて7時に起きた。いつも通りな感じ。

go のシリアライズ/デシリアライズとポインタ

ldap の distinguished name (以下dn) をパース するときはエスケープを扱う必要があるのでわりとややこしいのでライブラリを使った方がよいことを前回学んだ。

複数の web api で dn を扱っていると、それぞれで dn をパースして正規化した形で扱わないと大文字小文字の表記揺れなどに対応できなくて困るということに気付いた。いや、前回もうっすら気付いていたのだけど、既存の api はすべて対応しているからいいかなと楽観的に考えていた。すると、たまたま新規に dn を扱う web api を作ったときに正規化を忘れていることに気付いた。今後の保守や拡張を考慮すると、dn を string 型として扱うのは潜在的に正規化漏れの懸念があることからよくないと理解できた。そのため、既存のリクエストで受け取る dn を特別な型として必ず正規化して扱えるようにリファクタリングすることにした。あちこち直す必要はあったが、幸いにも単体テストも結合テストもそこそこあるのでバグっていればテストが落ちることで不具合には気付けるようになっていた。

encoding/json に Marshaler/Unmarshaler のインターフェースが定義されているのでそれぞれのメソッドを実装する必要がある。DNParameter の値を json にシリアライズするときは値レシーバで MarshalJSON メソッドを実装し、デシリアライズするときはポインタレシーバで UnmarshalJSON メソッドを実装しないと json ライブラリで意図した振る舞いにならないようにみえる。ここで UnmarshalJSON するときに byte 列から一旦 json の文字列に変換 (引用符を外す) してから ldap.ParseDN() しないといけない処理を直接 string 型に変換する誤ったコードを書いてしまって、この誤りに気付くのに1-2時間はまってしまった。

  • 誤ったコード
func (p *DNParameter) UnmarshalJSON(data []byte) error {
	dn, err := ldap.ParseDN(string(data))
	if err == nil {
		(*p).Value = dn
	}
	return err
  • 正しいコード
func (p *DNParameter) UnmarshalJSON(data []byte) error {
	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return fmt.Errorf("dn should be a string")
	}
	dn, err := ldap.ParseDN(s)
	if err == nil {
		(*p).Value = dn
	}
	return err

このときに引用符を ldap のパーサーがエスケープした形で扱えてしまい、テストは失敗するけれど、見た目がほとんど同じ文字列で動いてしまうのにはまった。引用符がエスケープされてテストが落ちることには気付いたものの、どこの処理が問題なのかが分からなくてはまっていた。おそらくスクラッチからこの仕様でコードを書いていたらすぐに気付いたと思えるが、リファクタリングであちこち書き換えていたからどこの処理が誤っているのかの切り分けに時間がかかった。

予算作り

1時に寝て夜中に気分が悪くて何度も起きて吐き気と戦いながら7時に起きた。なぜか体調がよくなかった。

来季の予算策定

いつもは3月の上旬には出来ていて、外部の有識者にレビューしてもらって雑談したり意見をもらったりしているところが、今期は納期に追われていてまだ何もできていない。ひとまず来季の予算策定から着手した。ざっくり算出すると来季は数百万の赤字になる。数字をみるのが嫌になる。半年ほどで受託開発を辞めてプロダクト開発へ移行する年にしていこうという展望はある。とはいえ、お客さんがいることなので私がいなくなっても開発が問題ない状況に調整してからの話しにはなる。なるべくそれを半年以内に完了させて自社のプロダクト開発へ移行していければよいと考えている。が、そんなうまく予定が進むかどうかはわからない。事業の移行が遅れて受託開発期間が伸びればその分の赤字は減るので会社の財務的にはその方がよいという見方もできる。

本当は来期から役員報酬を半減させて社会保険料も下げて3年間ほどプロダクト開発に投資しようと考えていた。会社も個人も3年ぐらい遊んでいても支障のない程度の蓄えができた。おそらく私の人生において挑戦するのはこれが最後の機会になるだろう。この挑戦が終わったら私の人生は十分かなと思う。父の介護を完了 して、いずれ母の介護が必要な時期も来るだろう。来季は実家に出張所のオフィスも作る予定。社用車 を購入して神戸と淡路島の行き来もしやすくなった。淡路島で業務できるようになれば実家で田んぼしながら開発に注力する時期なんかもあるかもしれない。

gpt-4 に課金した

1時に寝て8時に起きた。昨日は久しぶりに飲みに行ってきてよい気分で寝た。

ストレッチ

今日の開脚幅は開始前154cmで、ストレッチ後158cmだった。すねの外側の筋は大分よくなって通常の状態に近くなってきたと思う。今日の張りが強かったところは右股関節の内側と右腰になる。これまでもずっとよくない部分ではあるが、いつも体調に戻ってきたという見方もできる。あと右のお尻の後ろの筋もやや張りが強い気がした。2月中旬から納期に間に合わせるためにいくつかズルをしてきたところの埋め合わせというか、体調管理をこれから少しずつやっていく。結果的にその方が日々の活動のパフォーマンスが高くなる気がする。

gpt-4 談義

タイムラインをみていると GPT-4 がすごいという投稿が散見されるので課金して使ってみることにした。$20/月なので十分に安いと思う。会社のお金を自由にできると、技術調査や業務に関することの決済を即決できる。これは経験を積んでお金をかけるところとそうではないところに一家言もつようになってくるとそのストレスの度合いも違ってくる。まだ使い始めたばかりなので技術調査をするときにググる代わりに gpt-4 に尋ねて回答を確認するといった使い方しかしていない。具体的に言うとどう使ったらいいのかわからないので誰でもやる使い方から慣れていって、そのうちにもっと幅広い応用に気付くようになったらいい。

タイムラインをみてもどう使ったらいいかをあーだこーだと議論しているようにみえる。次の資料をみていて、対話形式というのは人間の質問や誤りの指摘をアノテーションとして gpt-4 はより正しい言葉選びをしているとある。ドメイン知識がある人が品質の高いアウトプットを得られるというのは、アノテーションを正しく gpt-4 に与えてあげないといけないという理屈で理解できた。

アノテーションの与え方が従来の検索のように lang=go のように構造化されたパラメーター指定ではなく、自然言語のまま非構造化データで与えられるというのが、従来の検索よりも進化したとも受け取れる。一方で gpt-4 に質問して文章を生成するのに1-2分かかるのでその待ち時間は検索よりも体験が悪い。私が大学のときはインターネット検索というのが普及し始めた時期だったから「どうやってその情報をみつけたの?」「検索するときにコツがあるんですよ」みたいな会話がよく聞かれた。いま gpt-4 を使っていて、その時代と同じような既視感を覚える。

syncrepl ことはじめ

1時に寝て5時に起きて7時に起きた。

openldap の syncrepl

openldap サーバー同士のレプリケーションの仕組みとして syncrepl (LDAP Sync Replication) と呼ばれる仕組みがある。experimental ではあるものの、仕様は rfc で提案されていてオープンになっている。実際には openldap サーバー (slapd) で実装されている参照実装が仕様みたいなものかもしれない。

既存の java のアプリケーションで syncrepl の機能を使うツールがあって、ソースコードを読むとかなりレガシーで、もっと言うと設計はひどく、コードも推定バグのような実装をみかけるのであまり使いたくない。go-ldap で syncrepl できれば作り直してしまえばよいのではないかと考えている。ドキュメントには provider (master) と consumer (slave) という用語で説明されているので pubsub に近いような仕組みで実装されているのではないかと推測する。slapd は provider にも consumer にもなる可能性があるのでどちらの実装ももっている。私がやりたいことは更新エントリを取得したいだけなので go-ldap で consumer として振る舞うモジュールのみを作ればよい。

基本的には openldap サーバーに接続して bind して、syncrepl に準拠したやり取りをすればよいのでそんな難しそうにはみえない。いくつかの定型的な通信の実装をすれば consumer ができるのではないかと思う。go-ldap のソースを grep してみる分には特別な機能はなさそうにみえる。ないならないで私が作ってもいいし、このライブラリでやらない方がよい背景があるのであれば、それに従う。ひとまず背景がわかっていないので issue を立てて聞いてみた。

ポインタの学び直し、参照とは違う

2時に寝て7時に起きた。深夜に葬送のフリーレン10巻を読んでからなんとなく眠れなくて夜更かししてた。木曜日は会議がなくて自分のために時間を使える。機能開発に集中して実装していた。

go の学び直し

Gopher塾 #4 - 私も解説できるポインタ - DAY1 に参加した。

今日のテーマはポインタ。tenntenn さんが話すのだから深い内部実装の話しなどもあるのかな?と期待していたけれど、これは基本的な go のポインタの扱いを学ぶ講義だった。私にとっては9割は知っていることだった。それでも1割は知らないことがあったので参加して勉強にはなった。この歳になると本やイベントから1-2割学べたら十分だと思う。go は内部的にすべて値渡しで何でもコピーするするといった振る舞いをする。ポインタを渡すと、ポインタの値であるアドレスをコピーすることでプログラムが動く。コピーなので大きな構造体をそのまま渡すとその分のメモリのオーバーヘッドがあって遅くなったりする。ポインタならアドレスだけのコピーで済む。

go には参照の概念はないという説明があって、ポインタと参照は別の概念なんだと今更ながらに気付いた。gpt-4 にポインタと参照の違いを尋ねたりしていた。参照は初期化後に変更できなかったり、null を参照できなかったり、ポインタ演算のようなことができなかったりすることで安全にプログラミングするための言語機能と言える。一般的には参照はポインタを使って実装される。ポインタの方が低レイヤで制約が少ないと言える。参照はポインタの一部の機能を安全にプログラマーに提供していると言える。

次に go のポインタの特徴をまとめる。

  • 型付け
    • ポインタは型付けされていて、特定の型の変数のアドレスだけがその型のポインタに割り当てられる
  • アドレス演算子とデリファレンス演算子
    • これらを単項演算子と呼ぶ
    • アドレス演算子 (&) で変数のアドレスを取得してポインタを作成する
    • デリファレンス演算子 (*) でポインタが指すアドレスに格納されている値にアクセスする
  • ポインタ演算制限
  • ポインタ演算は許可されていない、メモリアクセスの誤りやセキュリティ上の問題が軽減される
  • new と make 関数
    • new 関数を使うと指定された型の新しい変数を作成してそのアドレスを返す
    • make 関数は、スライス、マップ、チャネルなどの複合データ構造を作成および初期化して、それらへのポインタを返す
  • ポインタの nil 値
    • ポインタは、無効なアドレスを表す特別な値である nil をもてる
    • nil ポインタにアクセスしようとすると、実行時に panic が発生する
  • メソッドレシーバとしてのポインタ
    • メソッドレシーバとしてポインタを使うとメソッド内でレシーバオブジェクトの変更ができる
      • 値レシーバは意図せぬ不具合を招く可能性があるから基本的にはポインタレシーバを使う方がよいのではないかといった話しもあった

キャッシュ機構の導入

1時に寝て7時に起きた。リリース延期を決めたので3月末へのストレスやプレッシャーからは解放されていつもよりよく眠れた気がした。あくまで気がしただけ。

キャッシュライブラリの選定

ある機能を作るための準備としてキャッシュ機構を作ることにした。avelino/awesome-go を眺めて適当なキャッシュライブラリを選定する。シンプルな用途向け、オンメモリで goroutine-safe で保守されていて実績があるものでのバランスを取るときむらさんのキャッシュライブラリになった。

ちょうどいまのお仕事を始める前に スカウトをもらった会社 のテックリードがきむらさんだったので、いまはその会社にいるんだと思い出したのが半年前。きむらさんとはリアルで何年も会っていないし、仲がよいわけでもないけれど、なぜか facebook でも繋がっているので存在を忘れるということもない。

閑話休題。ある特定の mongodb コレクションのデータをキャッシュしたい。

キャッシュの生成。

cache := gcache.New(128).Build()

特定の用途で一部の処理だけ使いたいのでこんな感じでキャッシュの処理を実装した。

value, err := cache.Get(id)
if err == nil {
    return value.(*myData), nil
} else if err != gcache.KeyNotFoundError {
    log.Error("failed to get value from cache", map[string]any{
        "key": id,
        "err": err,
    })
}

// 通常処理

cache.Set(id, setting)

念のため、キャッシュをすべてクリアするときは次を呼ぶ。これをキャッシュ制御 api から呼び出せるようにしておく。何らかの理由やバグなどでキャッシュをクリアする必要もあるだろう。

cache.Purge()

データを削除したときは id 指定でキャッシュを削除する。

cache.Remove(id)

キャッシュが使われているかどうかは内部的に統計を取っているのでそれをキャッシュ制御 api から取得することでわかる。 キャッシュが使われていれば HitCount が増え、mongodb にアクセスしていれば MissCount が増える。単体レベルでプログラムのデバッグにはなる。

return CacheStat{
    HitCount:    cache.HitCount(),
    MissCount:   cache.MissCount(),
    LookupCount: cache.LookupCount(),
    HitRate:     cache.HitRate(),
}

リリース延期

3時に寝て7時に起きた。昨日は遅くまで確定申告の準備をしてたから寝坊した。その分8時半に申告会場に立ち寄ってからオフィスに出社したので普段よりも朝はゆっくりできた。

windows インストーラー開発

windows のインストーラーを作る方法は2-3種類ほどあるそうだけど、もっとも標準的な visual studio に付属しているツールでインストーラーを作っている。特性の違う2つのアプリケーションを1つのインストーラーで扱っていると、アップデートやアンインストールでいろいろ不都合が生じるように思えたのでインストーラーを2つに分割したらよいのではないか?と先週末に助言をしていた。すると、1つのアプリケーションを2つのインストーラーを使ってセットアップするようなものを作ってしまって、複雑さはなにも解消していなくて、全然意図したものではないと訂正して作り直してもらうことになった。

私の指示が2つに分割すればよいという曖昧な助言だったのもよくないけれど、1つのアプリケーションを2つのインストーラーで操作するという発想が私の中になくて勘違いする余地があったんやと失敗体験となった。

一方でメンバーが「最初からそう言ってくれればよかったのに、、、」とか言い始めて、この姿勢はよくないなとも感じた。この問題の責任は指示が曖昧だった私にあるのは間違いないが、1つのアプリケーションを2つのインストーラーでインストールするという考え方に疑問を抱かないのは怠慢だし、実際にそのやり方で問題は解決していなかった。なんのために設計するかを当事者意識をもって開発していたら、言ってくれなかったから分からなかったという考え方にはならない。勘違いしていたとか、気付きが足りなかったとか、そういう言葉がほしかった。

定例会議で、意図しているのは、特性の違う2つのアプリケーションをそれぞれ独立させて制御すればシンプルになるよと説明して、もう一度作り直してもらって、結果として意図した通りシンプルにインストールを扱えるようになった。

リリース延期

昨日書いた通り、基準となる issue をすべて fix できないことが確認できたのでそれを説明して4月末まで延期とした。これにより、遅れていた機能開発には十分な期間を取れるし、QAテストのための準備にも余裕をもてる。インフラやドキュメントを整備する時間もあるだろう。

今月の私の稼働時間は (契約は160時間だけど) おそらく200時間を余裕で越えるだろうし、記帳していない稼働時間も含めれば230時間ほどはいくのではないかと思う。私の中では納期に間に合わせるためにがんばる意思はあったんだけど、クリティカルパスはすべて私が担当するといった調整をできなかったので仕方ないなといった落とし所で来月への延期を正当化した。