ファイルアップロードのインターフェース

今日も昼間は普通にお仕事で開発して、お仕事を終えて晩ごはんを買い出しへ行ってきて、オフィスで晩ごはんを食べてから夜のコーディングに戻るといった一日になった。昼間は会議や問い合わせ対応、他のメンバーの進捗をチェックしたりなど、ちょくちょく割り込みが入る。夜はそれらがないことがわかっているので3-4時間集中してコーディングできる。

echo のファイルアップロードのサンプル

メンバーがファイルを受け渡しする web api のインターフェースがよくわからないということで私がサンプルを作ってみることにした。echo には File Upload のサンプルも紹介されているからすぐにできる。歴史的にブラウザからのファイルアップロードを前提にすると、ファイルを扱うときは次の Content-Type を使う。

Content-Type: multipart/form-data

これを echo のコードで書くと次のようなインターフェースとなる。

type MyRequest struct {
	Type      string    `form:"type" validate:"required"`
	Operation string    `form:"operation" validate:"required"`
}

func handle(c echo.Context) error {
	req := MyRequest{}
	if err := c.Bind(&req); err != nil {
		return err
	}
	if err := c.Validate(&req); err != nil {
		return err
	}
	file, err := c.FormFile("file")
	if err != nil {
		if errors.Is(err, http.ErrMissingFile) {
			return echo.NewHTTPError(http.StatusBadRequest, "file is required")
		}
		return err
	}
    ...

ファイル情報を MyRequest で管理できるとリクエストパラメーターを一元管理できて望ましいが、次の issue によると現時点ではそれはできないらしい。

curl コマンドでリクエストするには次のような cli になる。

$ curl -i -X POST -F type=misc -F operation=add -F file="@myfile.data" ...

寝ている間に再設計

昨日のもやもやは寝ている間に整理され、今日は標準的な開発者としての過ごし方になった感じがする。

マネージャーとの 1on1

これまでは私がマネージャーとして 1on1 をしてきたが、今後は交代したメンバーがマネージャーを務める。この開発フェーズでは私は開発者 (メンバー) として 1on1 をしてもらうといった感じかな。自分が開発者側だと思うと、いま私が取り組んでいることの話しをしやすかったりした。立場によって 1on1 のやり方は変わることを実感した。マネージャーはメンバーの話しを聞く方に注力したり、プロジェクト全体の話しをしがちになる。開発者として臨むと、自分の抱えている issue に集中して話せるといった違いがあった。

CPAP の中断

夜に睡眠外来へ行ってきた。以前に CPAP 装置 を借りて3ヶ月ぐらいやっていたものの、ここ2ヶ月ほどやらなくなってしまっていた。もともとは体脂肪コントロールの疲労回復のために始めたものだった。体脂肪コントロールがうまくいって目標達成したことで睡眠改善を行う目的もなくなってしまって関心を失ってしまった。借りているだけで毎月5千円ほどかかるので中断して機器を返却することにした。旅行のときなど、一時的に借りられないかを聞いてみたら、それは不可とのこと。また再開したくなったらいつでも言えばよいとのこと。

ツールの再設計

昨日の続き 。睡眠外来から帰ってきて、晩ごはん食べて、軽く飲みながら24時過ぎまでコードを書いていた。今日もまる一日ツールの再設計をしていた。昨日のノーアイディアな状況から少しずつ好転していって、ある処理で歯車が噛み合ってからはいつも通り着実に進捗していく様がみられた。意図的にやっていることではあるけど、課題について考えながら一晩寝ると、寝ている間に無意識に刷り込まれて脳が考えてくれる。それで徐々によい設計が現れてくるようになって明確な実装に落ち着いたのではないかと推測する。今日はよい感じに集中できた。

うまく進捗した翌日に詰まる

目次

昨日は開発が進捗したからお仕事も早めに終えたし、気分もよかったものの、今日から新たな issue でテストデータの生成ツールを作り直そうと再設計していた。過去にある程度の機能を作り込んだ知見があるから、すぐできるだろうという見通しで着手したものの、あーでもない、こーでもないと試行錯誤していて、うまくいかない。まる一日調査もしながら考えていたのに、これといった手応えはなくて、久しぶりにこのツールをどう作ってよいかノーアイディアのような雰囲気になってしまった。サーバーサイドやインフラのツールをいろいろ作ってきた過去の経験や蓄積があるから、あまりこういう状況になることはない。なまじ1度作った機能を改良するため、うまくいかないところや難しい課題もある程度わかっていて、それらの改善案が出てこないといったこともあったのかもしれない。プログラミングって、行き詰まったときの孤独感や焦燥感との戦いもあるなと開発の勘どころのようなものを思い出してきた。興に乗らなくて夜のコーディングは行わずに普通にお仕事を終えた。

小雨が降る中のパンク修理

目次

先週末に pr を送っていたから午前中にそのマージ作業をやって、他にもいくつか開発が順調に進捗したにもあって早めにお仕事を終えて自転車のパンク修理へ出掛けた。夕方から雨が降ったりやんだりで微妙な天候だったものの、大きく雨には降られなかったのでそれはそれでラッキーだった。

一番近い自転車屋さんへパンク修理にもっていくと、パッチ対応はしていなくてチューブ交換になってしまう。たまにしかないことではあるけど、チューブ交換で5,500円もかかる。ちょっと高い。他を探す時間もないから仕方ない。自転車を預けて1時間ほど待ち時間があり、その間にスーパーへ行って買いものして家に戻り、すぐにまた自転車屋さんに受け取りに行くといった感じで2時間ほどかかった。

軽く雨降りの中、傘をさして行ったり来たりしたのもあって疲れて夜のコーディングは行わずに早くに休んだ。

空き時間の草刈り

目次

田んぼの状態がよければトラクターで耕したり畝づくりをしたりするところを、それはまた雨が降った後に持ち越すことにした。また近いうちに実家へ帰る。せっかく帰ってきた機会でもあるから別の田んぼの草刈りをすることにした。どうやら親は土日月とパートに出掛けているらしく、今日もいないので代わりにやっておく。7時半ぐらいから草刈り機の刃を古いものから新しいものへ交換した。だいたい実家へ帰ると7時ぐらいから田んぼ仕事になる。家にある草刈り機の燃料 (混合ガソリン) がなくなるまでと親と話していたものの、実際に作業してみたら8時半にはなくなってしまい、それから神戸へ戻ってもよかったが、せっかく帰ってきたのにあまりにも作業しないと罪悪感を感じてしまった。

近所の 農業屋 へ行って混合ガソリンを購入してきてバテるまでやろうと燃料を入れ替えながら草刈りを何往復かした。暑くなって体力的にバテたらやめようと思っていたものの、休憩しながら、結局は12時前までやっていた。暑さにも慣れたのかな?たぶん体脂肪コントロールのための運動で体力がついたせいか、これまでよりも草刈りを長く続けられるようになっている気がする。それでもすべて刈り込むことはできなかったが、8割ぐらいの草を刈ったので次回また帰ったときに容易に終わらせられるだろう。普通の草刈り機は若干シャフトが短くて私にとっては腰をかがめないといけないのがしんどい。調べてみたら ロングシャフトの草刈り機 もあるらしい。記事を読んでいて草刈り機は2サイクルエンジンだとばかり思っていたら4サイクルもあって、エンジン部が大きい分の重量のデメリットに対して、燃費やトルクのメリットもあるらしい。うちには2サイクルエンジンの草刈り機があるから4サイクルも試してみたくなった。

お昼から戻ってきて14時過ぎには神戸へ着いた。それからオフィスへ行ってもよかったが、家でくつろいでいるうちに疲れもあって気付いたら夕方まで寝ていたのでそのまま休んだ。

1週間遅れの実家帰り

昨日も遅めの時間までコードを書いたら帰ってバテてしまい、朝から実家へ帰るつもりがお昼からになってしまった。本当は先週、帰るつもりだったのが1週間遅れになってしまった。それでも帰れてよかった。こうやっておかんが連絡してこなかったら、実家や田んぼのことを放置してしまうのではないかとふと思った。11時から軽くオフィスで作業して13時から三宮を出発して14時過ぎには実家へ着いた。1ヶ月ぶりぐらいに車を運転するのもあって気分転換にもなった。それからトラクターで田んぼを耕すことにした。晴れた日がずっと続いていると、地面がよく乾いてしまい、掘り返すと砂煙だらけになっていた。また地面も硬くてトラクターにも負荷がかかっているように思えた。一度耕した後も砂地になってしまって繰り返し耕せているかもわからなくなっていた。本当は一度耕した後に水はけがよいように畝を作るところを今回はその作業はやめた。また近いうちに雨が降った後に帰ってトラクターで畝作りをする。

ある会社の課題管理の考察

社内 slack で知人がその会社の、あるチームのメンバーについてコメントしていた。いま5年目の開発者で、課題管理的なことがうまくできなくて、これまでも何度も同じことを注意しているのに改善がみられないという。いくつか、私も自身の経験でコメントしてみていたが、どうやらアドバイスできるような状況ではないらしく、お手上げのような雰囲気だった。チームの他のメンバーはうまく課題管理できているし、毎日朝会もしているのに注意した内容に関する報告や相談をしてくれないという悩みもあるそうだ。もし業務委託社員なら解約しているとまで言うのだから問題社員なのだろう。そういった社員向けにアドバイスだったり、改善のための施策だったり、みえる化の指標を提供していくというのがうちの会社のビジネスになる。それでも出来ない人はどうするか?ということを知人とやり取りしながら考えるきっかけになった。多様性の文脈で向いていない業務はやめて、他の (相対的に向いている) 業務を担当すればよいのだろうけど、現実の会社ではそんな都合よく配置転換はできないかもしれない。

go の iterator 実装へのリファクタリング

mongo-go-driver を使った go の iterator 実装

先日 go 1.23 がリリースされた。このバージョンの目玉機能の1つに range-over function iterators がある。簡潔に言えば、ユーザー定義のイテレーターを言語機能の仕組みを使って簡単に実装できる。ある制約を満たして関数を実装すれば、for 文の range 構文を使ってイテレーターとして扱える。ユーザー定義のイテレーターを実装するのが、ほとんど関数を実装するだけで出来てしまうので実装の難易度が減ったと言える。どんなコードかをみるには次のチュートリアルがわかりやすいと思う。

昨日で私が抱えていたプロジェクトのボトルネックを解消できた。ここからはボーナスステージで相対的に自由に開発できる。手始めにプロダクトの依存バージョンを go 1.23 へアップグレードして mongodb からの fetch 処理をイテレーターに置き換えるリファクタリングをした。アプリケーションからみたら mongodb は Store という generic なインターフェースを定義していて、そこに Iter() メソッドを追加した。mongodb のコレクションは必ず Iter() メソッドを実装しなければいけないという制約を課す。

type Store[T any] interface {
    ...
    Iter(ctx context.Context, query query.Query, opt *sort.Option) iter.Seq2[*T, error]
}

mongo-go-driver はもともと cursor でイテレーター機能を提供しているため、これを range-over function iterators を使ってアプリケーションから使えるように間をつなげてあげればよい。具体的には generics を使って次のような汎用の iter() メソッドを mongodb client に実装する。

func (c *client[R, E]) iter(
	ctx context.Context, name string, filters queryFilters, sortOpt *sort.Option,
) iter.Seq2[*E, error] {
	if c.toEntity == nil {
		panic(fmt.Errorf("require toEntity() function"))
	}
	return func(yield func(*E, error) bool) {
		collection := c.raw.Database(c.db).Collection(name)
		opts := []*options.FindOptions{}
		if sortOpt != nil {
			opt := options.Find().SetSort(makeSortSet(*sortOpt))
			opts = append(opts, opt)
		}
		cursor, err := collection.Find(ctx, filters, opts...)
		if err != nil {
			if !yield(nil, err) {
				return
			}
		}
		defer cursor.Close(ctx)
		for cursor.Next(ctx) {
			var result R
			if err := cursor.Decode(&result); err != nil {
				if !yield(nil, err) {
					return
				}
			}
			if !yield(c.toEntity(&result), nil) {
				return
			}
		}
		if err := cursor.Err(); err != nil {
			if !yield(nil, err) {
				return
			}
		}
	}
}

Store インターフェースを満たすコレクションの実装は次のように型チェックのためのメソッド実装をもてばよい。

func (c *MyCollection) Iter(
	ctx context.Context, query query.Query, opts *sort.Option,
) iter.Seq2[*entry.MyEntity, error] {
	return c.iter(ctx, c.name, makeFilters(query), opts)
}

朝から iterator 実装の試行錯誤や使い方の勘どころを確かめながら、これも1日でほとんどリファクタリングして移行できた。python のジェネレーターもそうだけど、関数をイテレーターにできると直感的にわかりやすく簡潔に実装できる。すごくよい機能だと思う。1.23 以降の go のコードはイテレーターを使うよう、大きく変わっていくと思われる。

プロジェクトのボトルネック解消

目次

今日は開発の設計に関する打ち合わせが2つ入っていて、どちらも私はメインではないものの、メンバーが要件ヒアリングしやすいよう、ハドルで話しを聞きながらチャットにコメントしたり質問したりしてサポートしていた。その合間に ldap サーバーのグループメンバー管理のための api を実装していた。いま ldap client はライブラリとして作っている。docker-openldap というコンテナを使って dockertest で単体テストを実装している。openldap サーバーに対する ldap プロトコルの操作を容易にテストできるので開発がやりやすい。新規にグループメンバーを操作するための api をライブラリとしての ldap client に実装し、その後に web api としての機能を提供した。だいたい1日で完了した。この2週間ほど取り組んでいた、開発フェーズにおける私のボトルネックをこれで解消できた。ここからは私がやりたい開発ができるボーナスステージへと移行していく。これで少しストレスやプレッシャーが減っていくはず。

バドミントンという健康的な趣味

昨日、大きな仕様変更を入れた内容の、API ドキュメントを書き直していた。大きな変更をしたのでドキュメントの修正量も大きくて半日以上かかった。こういった効率化しにくい、地道にコツコツやらないといけない作業が忙しいときに降りかかるのがつらい。誰でもそうだとは思うけど。元来、私は積み重ねをしていく地道な作業は得意な方だと考えている。しかし、いまはリーダーとして他のメンバーを引っ張っていく立場にあるため、時間のかかる作業をしていると足踏みしているような罪悪感を感じてしまう。一方でそういった実務をちゃんとやることをメンバーにみせることも大事かなと思って、面倒な作業を自分でやるときもある。ケースバイケース。

体育館でバドミントン

前回の所感 。19時からバドミントンへ行ってきた。19時の時点では3人しかいなかったものの、後半にながいさんも来られて、最終的な参加者数は4人だった。ここ1-2週間は椅子にすわってデスクワークばかりやっていて、ほとんど運動できていないのでカラダを動かすのにちょうどよい機会になった。今回も line のオープンチャット 経由で加古川から初参加で来てくれた方がいた。三宮は職場の通勤途中になるらしく、それもあって立ち寄ってくれたようだ。その方も私と同じ世代で初心者からバドミントンを始めて1年ほどの経験らしい。毎週、地元の体育館で練習していると話されていた。うちらに比べればずっと上手だった。うちらはみんな初心者だから適当にやってきたのだけど、(うちらと比べて) 熟練者が来てくれたのでバドミントンの練習方法もいろいろ教えてもらった。その方は次の動画をみて勉強していると話されていた。私も後でみてみようと思う。

バドミントン経験1年の方の振る舞いをみていて、バドミントンは趣味でずっと続けていくのによいスポーツだなと思えた。運動にもなるし、カラダを痛めるといった健康を害することもなさそうだし、お金もかからないし、老若男女、初心者誰でもできるコミュニティ向けのスポーツでもある。教える方のスキルが高いほど、初心者の練習相手として、初めてイベントに来られた方がバドミントンに関心をもつ手助けもできそうに思えた。いまのお仕事の開発が一区切りつけば、有料のバドミントン教室へ通ってみて基礎を教えてもらいに行こうかなと思い始めた。

プロジェクトのボトルネック解消への壁

目次

週末は休んでいたので今日は調子よかった。週末にある程度やっておこうと思っていた実装作業が手つかずだったものの、午前中の定例会議でメンバーに設計内容の最終確認をした。それから集中してコードを書いていたら日付が変わるぐらいの時間まではやっていたけれど、大きな仕様変更を完了した。

連休の週末に時間があったにも関わらず、なにもやらなかったのはこの仕様変更のための作業に私自身が本当の意味で関心をもっていないのではないかという仮説をたてた。仕事でやらなければいけない、面倒でやや規模の大きな作業を、個人の時間で取り組むほどのモチベーションをもっていない。そして無意識的に脳が業務時間だけでできると判断してしまっている。実際にそれで完了することも多い。開発の前倒しはできないが。

夏休み3

目次

午後からオフィスへは行ったけれど、昨日に引き続き、しんどくてほぼお休みしていた。