Posts for: #Go

経済的且つ健康

22時頃から寝てしまって24時に起きて2時間ほどネットみたりしてまた寝て4時に起きて7時に起きた。

健康によさそうな紅生姜

昨日の続き 。業務スーパーの紅生姜は 1kg 338円、賞味期限も9ヶ月、とお得ではあるものの、中国産且つ合成保存料/着色料を使っているところに懸念がある。日常的に食べるものならもう少し健康に注意を払いたい。amazon で購入できる商品で 坂田信夫商店 という生姜専門店をみつけた。「合成着色料・保存料 不使用」なのがよい。この紅生姜も色はついているものの、野菜色素で色付けしているらしい。この商品は 1kg 1,800円、賞味期限は製造日から4ヶ月になる。健康に気を遣うと割高になるが、普段食べるものはそれで構わない。amazon の定期便で食べる量にあわせて送ってもらうのがよさそう。

お昼ご飯の行方

週末にスーパーのポイントカードの振る舞いをチェックするためにいつもより食材をたくさん買い込んでしまった。冷蔵庫が膨らんでいるので土日の両日とも自炊して、今日のランチも外食ではなく、お昼に帰って自炊した。オフィスと家の距離が徒歩3分なのですぐ帰れる。家で自炊してお昼ご飯を作るメリット・デメリットを考えてみる。

メリット

  • 外食するよりも自炊した方が安い
    • 物価が上がっているので外食すると1,000円前後になる
    • 自炊なら高めに見積もっても500円もあれば済む
  • 野菜を多く採れる
    • 基本的に外食のランチは野菜が足りない
    • 野菜は値段が高いのでランチやお弁当ではしばしば省略される

デメリット

  • 自炊するのが面倒くさい
  • 家だと仕事の合間の気分転換にならない?

基本的に私が労力さえ払えば、経済的にも健康的にもメリットしかない。自炊すればするほどスーパーのポイントも多く貯まる。毎日じゃなくても、たまに多めに買い込んでお昼も自炊してみようと思う。

go の set 型の導入予測

コードレビューをしていて、ある処理の実装を集合演算でやった方がよいとアドバイスした。go の型システムや標準ライブラリには set 型に相当するものがない。しかし、map を使って簡単に実装できるので実際の開発で問題になることはほとんどない。いまは generics と comparable があるので、汎用の set 型を map で実装することもできるはず、と思って調べたら次の記事をみつけた。

私のイメージとも合致して、こういったものをユーザー定義型で実装すればよいと reviewee とやり取りしていた。generics で汎用の set 型を実装できるなら、当然、それを標準ライブラリに導入しようという話しも出ているはずで軽く調べてみた。次の issue がその議論らしい。

で、私は issue を読んでいないのだけど、どうやらユーザー定義の iterator 機能に依存しているらしい。従って iterator 機能が導入された後に set 型が導入されるといった流れになるだろう。

iterator 機能は早ければ次の 1.22 ともみられているが、まだマイルストーンがついていないので開発状況によっては見送られるかもしれない。これから1-2年後あたりには go 本体が提供する set 型が支えるようになっているのではないかと推測する。

rbac なロールでの動的な権限チェック

23時に寝て何度か起きて7時に起きた。夜に作業するつもりが、メイドインアビス を見ながら寝落ちした。

ldap で認証したユーザーの権限管理

rbac なロール管理 の仕組みを api サーバーに実装した。初期実装したときは静的にリソース (url) に対して acl から権限が許可されているかどうかのみをチェックするだけでよかった。ldap で認証したユーザーは自分の情報は更新できるが、他人の情報は更新できないといったリクエスト単位に権限が許可されているかどうかをミドルウェアで判別したい。いわば、リクエスト時に動的にリソースの情報をチェックしないといけない。

いくつか試行錯誤して、あーでもないこーどもないとやった結果、次のように落ち着いた。

  • リソースは動的な権限チェックのために AuthorizeFunc を取得するための AuthorizeKey をもつ
  • AuthorizeFunc は Context から取得する
  • AuthorizeFunc が true を返す場合は権限を付与する
func (r *Role) CanAccess(ctx context.Context, resourceName, target string) bool {
	for _, ac := range r.ACL {
		if ac.Resource.Match(resourceName) {
			for _, perm := range ac.Permissions {
				if perm.IsGranted(target) {
					return true
				}
			}
			if authorize, ok := ctx.Value(ac.Resource.Key).(AuthorizeFunc); ok {
				if authorize(ctx, resourceName, target) {
					return true
				}
			}
		}
	}
	return false
}

ロールに渡す Context にはあらかじめ、AuthorizeKey と AuthorizeFunc を登録しておく。

ctx = context.WithValue(ctx, role.LDAPResource, role.IsLDAPResourceOwner)

リソースを生成するときに AuthorizeKey をセットしておく。

ldapUserResource = rbac.NewResource(
	"ldap-user-specific-operation",
	"(/api/ldap/entry|/api/ldap/entry/.*)",
	LDAPResource,
)

ちょっと設定は煩雑になるが、ひとまずこれで要件を満たせた。

論理の通じない人たち

23時に寝て何度か起きて8時に起きた。ホテルの部屋が暗いと朝になった気がしなくて2度寝したら寝坊した。

組織の対応と sns の議論

先日の sns 騒ぎ の続き。公式からの声明も出たので軽くまとめておく。

簡潔な文章に事実の記述、責任の所在、関係者への配慮が含まれていて十分な内容にみえる。法律なども関係するため、弁護士チェックが必要なことを考慮すると、こんな短期間で組織の見解を出せたことは運営側の体制を鑑みることができる。それが適正かどうかは人によって判断は異なるかもしれないが、私はコミュニティ運営というボランティア主体の組織であれば十分なものだと思えた。その後のネット上の議論も、ちゃんと追えてはいないが、様々な見解で議論は進んでいるようにみえる。

今回みていて感じたことの1つに、コミュニケーションが成り立たない人が世の中にはたくさんいるということ。議論の前提や論理の出発点が異なる人たちは、一定の論理を含む全体や大局を理解できず、細部や詳細のところだけを拠り所に自身の論理を組み立てる。意見の差異があることはなんら問題はないが、論理が通じないのは議論の余地すらないようにみえた。そういう人たちを会話するときは前提条件を同じにしたり、思想の背景を共有したり、もっと時間をかけて丁寧にすり合わせていく作業が必要になる。そして sns のような、流れが速い不特定多数の議論はそういった丁寧な作業にまったく向いていない。だから sns で議論することは時間の無駄である。

コネクションを共有しないプール

go の非同期処理であまり使われることはないが、semaphore が準公式ライブラリとして提供されている。私はセマフォを気に入っていてたまに使う。

ldap プロトコルではコネクションの確立とログインに相当する bind の操作が分かれている。コネクションを確立したまま、ログアウトに相当する処理ができればプールを設けることでコネクションの再利用ができる。

しかし、このドキュメントの説明によると、unbind という操作は用意されているものの、ログアウトに相当する機能ではなく、クローズする前に通知するといった用途だと書いてある。unbind のリクエストをした後にはクローズするしかないといったものになる。それを踏まえて、プールはセマフォで同時接続数のみを制御するのでよいのではないかと思う。そんなワーカープールを実装してみた。

type ClientPool struct {
	config *config.LDAP
	sem    *semaphore.Weighted
}

func (p *ClientPool) Get(
	ctx context.Context,
) (*LDAPClient, error) {
	if !p.sem.TryAcquire(1) {
		return nil, fmt.Errorf("failed to acquire, wait and get later")
	}
	client := NewLDAPClient(p.config)
	if err := client.Connect(ctx); err != nil {
		p.sem.Release(1)
		return nil, fmt.Errorf("failed to connect: %w", err)
	}
	return client, nil
}

func (p *ClientPool) GetAuthenticated(
	ctx context.Context,
) (*LDAPClient, error) {
	client, err := p.Get(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to get: %w", err)
	}
	dn := p.config.BindDN
	passwd := p.config.BindPasswd.String()
	if err := client.Bind(ctx, dn, passwd); err != nil {
		p.sem.Release(1)
		return nil, fmt.Errorf("failed to bind: %w", err)
	}
	return client, nil
}

func (p *ClientPool) Close(client *LDAPClient) error {
	err := client.Close()
	p.sem.Release(1)
	return err
}

func NewClientPool(cfg *config.LDAP) *ClientPool {
	return &ClientPool{
		config: cfg,
		sem:    semaphore.NewWeighted(cfg.ClientPoolSize),
	}
}

アイマスク効果

1ヶ月と2週間ぶりの出張。

0時に寝て2時半に起きた。なにも準備していないのが悪いけれど、出張前夜は寝坊したらどうしようというのが気になってあまり眠れない。起きてから出張の準備をして5時過ぎに出た。電車に乗るまで少し時間があったので朝からなか卯で まぐろのたたき丼 を初めて食べた。値段を考えると親子丼を食べた方がお得なように思えた。出張の日は夕方眠くて集中できないことが多い。新幹線の中で眠れるよう、ダイソーでアイマスクを買ってみた。その成果もあって、わりとよく眠れた。アイマスクの効果は確かにあったと思うが、なぜか寝違えて首を痛めた。新幹線でよく眠れたように考えていたが、15-16時は眠気に襲われた。やはり3-5時ぐらいから起きているとそのぐらいの時間に眠くなってしまうんやろか?

マイルストーン定例

いつもの定例会議をやって、開発状況の共有や設計レビューなどを行う。私は権限管理の仕組み作りをちょっとずつ作っているのであまり全体を見渡せていないが、メンバーが主体となって段取りを進めてくれた。あと新規メンバーが先週から開発に参加する予定が、別プロジェクトの段取りがうまく行っていないようでまた仕切り直しになった。もう2週間ほど様子をみるという。3回目のスケジュール調整になる。こういう五月雨式に直前に遅れていくパターンを私は信用しない。スケジュールを自分で制御できる状態にない、または制御できたとしてもやる気がないようにみえる。いずれにしても、私が制御できないプロジェクトの影響で、うちのプロジェクトの成果物に影響が出ないようにしたい。機能開発を3ヶ月と見積もっているうちの、1ヶ月がすでに過ぎた。もう新規メンバーの受け入れは諦めて、既存メンバーだけで成果物ができるような体制へ移行することに決めた。幸いにも既存メンバーは十分にタスクをこなせるようになってきているし、私も10月後半ぐらいからバーンアウトを消化して開発の集中力も戻ってきた。11月にちょっと本気出してサーバーサイドの開発をしてもよいかもしれない。

go のオンライン勉強会

Go 1.21 リリースパーティ & GopherCon 2023 報告会 をオンラインで参加した。ホテルについて少し寝てからアラームで起きて視聴してた。始まる前に30分ほど仮眠しても、移動日はしんどくて、みながら半分ぐらいは寝てた。最初と後半2つぐらいのセッションしか覚えていない。メルカリの社員さんたちは GopherCon 2023 に参加して、その内容などを紹介していた。もう私は海外カンファレンスへわざわざ行こうというモチベーションはない。たぶんもう行くことはないのだろうけど、そういった場に行って学んでいる開発者にはもう追いつけないのだろうなとも思えた。

権限管理ができるようになった

1時に寝て何度か起きて7時半に起きた。なんかしんどい夢をみたが、もう覚えていない。

rbac なミドルウェアの実装

昨日の続き 。rbac (role-based access control) なライブラリが一通り実装できたのでそれを使って echo のミドルウェア を実装した。やりたいことは次のようなこと。たったこれだけだが、これを実装するために、ここ1週間ほど、あれやこれやの実装をしてきた。ようやくそれが動くようになったというところ。私にとってはミドルウェアの仕組みは過去に何度も実装してきたものだが、頭に描いたイメージのまま、実装できたのがよかったと思う。

func rbacWithConfig(cfg rbacConfig) echo.MiddlewareFunc {
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			if cfg.Skipper(c) {
				return next(c)
			}

			req := c.Request()
			ctx := req.Context()
			sessionStore := c.Get(keySession).(session.Store)
			userName := c.Get("username").(string)
			s, err := session.Get(ctx, sessionStore, userName)
			if err != nil {
				return fmt.Errorf("failed to get session: %w", err)
			}
			if !s.Role.CanAccess(ctx, req.URL.Path, req.Method) {
				return echo.ErrForbidden
			}

			return next(c)
		}
	}
}

node.js のアップグレード

ちょうど管理画面も少し触ろうと思って環境を構築し始めた。たまたま node.js のバージョンをみると、ちょうど10月18日で 18 (LTS) の active support 期間が終了していた。security support は2025年4月まであるのでそんな急がなくてもよいが、それに気付いたついでなので 20 (LTS) にアップグレードすることにした。幸い、大半のライブラリは 20 (LTS) でもそのまま動いた。しかし、依存ライブラリのアップデートをしていて、一部 conflict してうまく動かないライブラリもあった。細かい原因調査をしていないが、フロントエンドの方がこまめにバージョンを上げていかないと何が原因でアプリケーションが正常に動かなくなるのか、分からなくなる気がする。

非日常を提供するという価値

1時半に寝て3時に起きてもう1回起きて6時半に起きた。

interface はデシリアライズできない

昨日の続き 。rbac なライブラリを使ってアプリケーションを実装していく。ログイン時にユーザーにロールを割り当ててセッションにロールを保持するのが都合よさそうに思えた。ロールの実装で一部の型は interface にして後から拡張できるような設計にしていた。例えば encoding/json ライブラリだと、Marshaler/Unmarshaler の interface を満たすことで任意の json のシリアライズ/デシリアライズをフックできる。調べたり、実際に動かしていていて気付いたのだけど、interface の場合はシリアライズは任意にできるけど、デシリアライズはできない。当たり前と言えば当たり前だが、interface を満たす複数の型がある中で json ライブラリがどの型でデシリアライズしていいか判別できないからだ。当初の interface を用いた設計が誤りだったことに気付いて、一部の型を汎用の構造体で設計し直すようなことをしていた。

またデシリアライズするときに一部の値を初期化したいといった要件がある。例えば mutex を初期化したい。このときに処理の内部で派生型を宣言して、それにキャストした上でデシリアライズの処理を実行した上で差分の処理を実装するというテクニックを学んだ。スコープが限定されて、コードがシンプルになって保守性も高い、久しぶりに頭のよいスマートなコードをみた。

func (r *Role) UnmarshalJSON(b []byte) error {
	type Alias Role
	if err := json.Unmarshal(b, (*Alias)(r)); err != nil {
		return err
	}
	r.mu = &sync.Mutex{}
	return nil
}

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

月例のカフーツさんのオンラインイベントに参加した。前回の所感はここ 。今日は参加者が2人だけだった。コワーキングスペースを運営するコワーキングスペースマネージャーの連携を強化することで、コワーキングスペースの付加価値が上がったりしないか?といった内容を話したりしていた。コワーキングスペースマネージャーは、普通はお仕事で自分のコワーキングスペースにいないといけないから、なかなか他所のコワーキングスペースへ訪問すること自体が難しい。コワーキングスペース同士の連携により、お仕事でコワーキングスペースマネージャーが自分ところのコワーキングスペースの利用者を連れて、他所のコワーキングスペースへ訪問して、そこでイベントをしたりすればいいんじゃないかという案が出た。

いとうさんがよく コワーキングツアー と称して、全国各地のコワーキングスペースへ訪問して、そこでイベント開催をしたり、その地域の取り組みなどを紹介したりしている。それと全く同じことを、コワーキングスペースの利用者に対してもその付加価値というのはあるかもしれないと私もよいアイディアだと思った。例えば、大阪のコワーキングスペースの利用者を広島へ連れていって、そこでイベントやって交流する。その逆も然り。通常のコワーキングスペースの利用者は自ら広島のコワーキングスペースへ行ってコラボレーションを行ったりしない。いや、いとうさんみたいに自らやる人もいるんだけど、そんな人は対象の利用者ではない。自分からは行かないが、誘われたら行ってもいいかなと考える人 (私もそんな1人だ) を移動させることで、新しい価値やアイディアが生まれるかもしれないと思える。私もいまはフルタイムのお仕事があるから自由に移動はできないが、いずれ会社の投資期間に入って、自分でスケジュールを決められる状況になれば、コワーキングツアーにも出掛けてみようと思う。

あと勉強会やイベント以外でコワーキングスペースで出来ることはないか?という話題でも盛り上がった。私は主催者の準備が大変だと出来ないから、主催者のコストが低いものという視点から考えて猫コワーキングがいいんじゃないかと提案してみた。ある週だけコワーキングスペースに猫が10匹ぐらいいますといった取り組み。課題は猫をどこから連れてくるかだけだが、そういう機会があれば確かに私も行ってみたい。そのアイディアの発散で非日常の体験ができるような取り組みがよいんじゃないかとまとめられていた。

  • 猫コワーキング (猫がたくさんいる)
  • 深夜コワーキング (深夜に開いている)

深夜コワーキングスペースのモデルとなる 弐拾dB さんというコワーキングスペースが広島の尾道にあるらしい。23-翌5時という営業時間だという。いとうさんが絶賛していたのでおもしろいオーナーが運営されているのだと思う。そのオーナーが執筆したエッセイの 頁をめくる音で息をする を購入してみた。

初めて rbac なライブラリを実装した

22時頃から休んでいて寝たり起きたりで7時に起きた。起きたらネットの記事とかだらだら読んでた。

rbac なライブラリの実装

昨日から認可のための仕組みを調査している。私の中ではもっとも一般的な rbac (role-based access control) でまずは作ってみようと思う。次の2つのライブラリの利用を検討したが、自分たちのやりたいことにあわない気がして今回は見送ることにした。

一通り、ライブラリとして使えるように参照実装した。これから実際のアプリケーション要件にあわせてミドルウェアとして rbac な認可処理を作っていく。

変わりゆく世界秩序

サンフランシスコが陥った負の“スパイラル” の記事にあるような、米国で950ドル以下の窃盗は軽犯罪とするという法律の変更によって、万引きを逮捕しなくなってモラル崩壊が起きて、小売店の商品を普通に盗むという事件が多発しているらしい。fin-py でおがわさんとそんな話しをしていたら次の動画を教えてもらった。私は歴史が好きなので、こういった「歴史は繰り返す」といったものはだいたいみてしまう。厳密な裏付けはわからないが、盛者必衰という言葉もあるように、どんな国でも栄枯盛衰のサイクルはあるだろうというのは大局の視点として同意できる。過去の歴史と国の栄枯盛衰をいくつかの指標とお金の視点から調査したものでおもしろかった。

日本は80年サイクルで戦争の周期がくるといった説もあるが、この動画でもサイクルの切り替わりのタイミングで平和的にしろ暴力的にしろ、かならず戦争は起きると説明している。もうすでに戦争は始まっている感もあるが、戦争は避けようがないという点も同意するところだ。本も読んでみようと思う。

echo のミドルウェア関数のスタイル

1時に寝て何度か起きて6時半に起きた。起きてから軽く部屋の掃除をした。

echo のミドルウェア開発

go の api サーバーの開発に echo という定番のフレームワークを使っている。少し前にメンバーに認証の処理をミドルウェアとして実装してもらった。いま認可の仕組みもミドルウェアで実装しようと、いくつかソースコードを読んでいて、echo のフレームワークが提供しているミドルウェアの関数名や config には共通点があることに気付いた。echo middleware によると、20個ぐらいのミドルウェアが提供されている。例えば、適当にそのうちの3つほどを取り出すが XxxWithConfig という命名規則で config を受けとって echo.MiddlewareFunc を返すというインターフェースになっている。

func HTTPSRedirectWithConfig(config RedirectConfig) echo.MiddlewareFunc
func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc
func BodyDumpWithConfig(config BodyDumpConfig) echo.MiddlewareFunc

また config の中身をみていると、ミドルウェアの処理に必要な関数を渡すような設計になっている。複数のミドルウェアにとって共通なのは、ミドルウェアの処理を迂回する条件を実装するため middleware.Skipper という型が次のように型で定義されている。

e.Use(middleware.BasicAuthWithConfig(middleware.BasicAuthConfig{
	Skipper: func (c echo.Context) bool {
		// Skipper defines a function to skip middleware.
    },
	Validator: func(string, string, echo.Context) (bool, error) {
		// Validator is a function to validate BasicAuth credentials.
		// Required.
    },
	Realm: "Restricted",
}))

典型的なミドルウェアは次のように実装する。最初に Skipper を呼び出して処理の有無を確認する。

return func(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		if config.Skipper(c) {
			return next(c)
		}

        // TODO: ミドルウェア本体の処理

		return next(c)
    }
}

認証のミドルウェアを実装してもらったときに、私がここまでみていなかったなということに気付いて、既存のミドルウェアを echo のそれと同じスタイルにあわせるようにリファクタリングした。自分でソースコードを読んでいるとコードレビューで気付かなかったことに気付くことが多い。自分がコードを書いているときと、コードレビューをしているときでなにかしら視点が違う。

メールの結合テストのツール

0時に寝て4時と5時に起きて6時に起きた。最近は4時から1時間ごとに起きたりする。メールの送信処理のリファクタリングのコードレビューが長く続いていてなかなかややこしい。

mailhog の web api を使って検索する

メール送信の結合テストに mailhog というツールを使っている。smtp サーバーとしてメールを受け付けて web ui でそのメールの内容を確認できる機能をもっている。さらに web api で任意のメール取り出すこともできる。パスワードリセットの一時トークンをメールで送信するため、結合テストで一連のパスワードリセットを検証するにはメールから一時トークンを取り出さないといけない。そこで mailhog の出番だ。

検索 api を使うと任意のメールをフィルターできる。例えば、送り先のメールアドレスで検索するときは次のようにリクエストする。

$ curl -s "http://localhost:8025/api/v2/search?kind=to&query=myuser1@example.com" | jq .

うちらのやり方はメールのメッセージ ID に意図した uuid をセットしている。メールのメッセージから指定した uuid を含んでいるかを検索することで任意のメールをフィルターできる。

$ curl -s "http://localhost:8025/api/v2/search?kind=containing&query=28c9391f-25a5-4a8f-9035-7b8d5ac2d0f4" | jq .

それを結合テストのテストコードから呼び出すようにユーティリティを作ると次のようなコードになる。

import (
	"github.com/mailhog/data"
)

type mmSearchResult struct {
	Total int            `json:"total"`
	Count int            `json:"count"`
	Start int            `json:"start"`
	Items []data.Message `json:"items"`
}

func searchMail(
	host string, kind string, query string,
) (*mmSearchResult, error) {
	q := url.Values{}
	q.Set("kind", kind)
	q.Set("query", query)
	u := &url.URL{
		Scheme:   "http",
		Host:     host,
		Path:     "/api/v2/search",
		RawQuery: q.Encode(),
	}
	url := u.String()
	slog.Debug("mailhog search endpoint", "url", url)

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, fmt.Errorf("http create new request error. err: %s", err)
	}
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("http request error. err: %s", err)
	}
	if res.StatusCode >= 400 {
		return nil, fmt.Errorf("returned response code is %d", res.StatusCode)
	}
	var r mmSearchResult
	if err := convertBody(res, &r); err != nil {
		return nil, fmt.Errorf("failed to convert: %s", err)
	}
	return &r, nil
}

歯医者

17時から歯科検診へ行ってきた。歯科検診をしてくれた今回のスタッフはおそらく初めてだったと思うが、手際がよくていつもよりもストレスが少なかった気がした。うちのチームのメンバーは朝方なので8時過ぎにはだいたいみんな始業し始める。なので、朝よりも夜にいない方がメンバーにとってよいだろうと考えて、朝一 (9時) に歯医者へ行くよりも最終 (17時) に行くようにしている。

pycamp ふりかえり

先週末の Python Boot Camp のふりかえり。ここまでがスタッフのお仕事なので一般的な kpt でイベントのふりかえりをした。仕事じゃないし、大きなトラブルもなかったし、参加者の評判 (アンケートの内容) もよかったので概ねよい内容だったと思う。テキストの改善点は直接 issue に追加してほしいと言われたので報告した。是非も含めてスタッフに判断してほしいので私が直すつもりはない。

標準ライブラリに XOAUTH2 の実装がない

0時に寝て3時に起きて5時ぐらいまでネットで遊んでて6時半に起きた。昨日の夜に洗濯しようと思って忘れていたので朝から洗濯した。

隔週の雑談

顧問のはらさんと隔週の打ち合わせ。今日の議題はこれら。

  • 税理士さんとの打ち合わせのふりかえり
  • 昔お手伝いした会社の開発体制の話
  • 新しいチーム勉強会 の導入

3人の税理士さんと打ち合わせしてみて最終的に顧問契約をお願いする方を決めた。話してみてやり取りした雰囲気だと、その税理士さんもスキルやこちらの要件対応については全く問題なさそうに思えた。あとは報酬とうちの会社の規模などを考慮して選択した。

昔お手伝いした会社で2年経ってちょっと相談にのってほしいという打ち合わせをした。私がいた2年前と開発体制はまったく変わってなくて、未だにテックリードがほぼ1人で開発している状況らしい。私が辞めてから以降も何人かは開発者が入っては辞めを繰り返しているのだと推測する。私も2度とその開発者と一緒に働きたくないと思うぐらいには信頼してなくて、開発者が引く手あまたな世の中の状況において、人間として信頼されないリーダーって致命的なんだなということを改めて実感した。おそらくテックリードを追放しない限り、あの開発体制 (と言ってもほぼ独り開発) は何も変わらないのだろうと思う。

oauth 2.0 で認証して google の smtp サーバーを使う

昨日の続き

リフレッシュトークンを使って取得したアクセストークンで smtp の AUTH コマンドで XOAUTH2 で認証すればよい。仕様は次のドキュメントに書いてある。

なぜか go の標準ライブラリの net/smtp には Plain と CRAM-MD5 の2つしか実装されていない。AUTH コマンドの実装は smtp.Auth インターフェースで定義されている。

type Auth interface {
	Start(server *ServerInfo) (proto string, toServer []byte, err error)
	Next(fromServer []byte, more bool) (toServer []byte, err error)
}

正常系の雑な実装だとこんな感じ。

type oauth2 struct {
	user        string
	tokenType   string
	accessToken string
}

func (o *oauth2) Start(server *smtp.ServerInfo) (string, []byte, error) {
	if !server.TLS {
		return "", nil, fmt.Errorf("need tls")
	}
	resp := []byte("user=" + o.user "\001auth=" + o.tokenType  + " " + o.accessToken + "\001\001")
	return "XOAUTH2", resp, nil
}

func (o *oauth2) Next(fromServer []byte, more bool) ([]byte, error) {
	if more {
		return nil, errors.New("unexpected server challenge")
	}
	return nil, nil
}

ググるとサンプルコードを実装している人たちがちらほらいるので、そのうち標準ライブラリに誰か実装してくれると思う。

go 本体に pr を送るチャンスでもあるけど、Contribution Guide を少し眺めて大変そうと思って、いまそこまでのモチベーションないなって感じ。

税理士さんの選定

1時に寝て3時頃に吐き気で起きて1時間ぐらい苦しんでた。久しぶりにやばかった。その後なんとか寝て7時半に起きた。

税理士さんとの打ち合わせ1

うちの会社のイベントとして毎年ワーケーション (開発合宿) をやろうかと考えている。コワーキングやコミュニティの延長上でワーケーションを行うわけだが、いくらかうちの会社の持ち出しで費用負担してよいと考えている。しかし、そのときの支出はどういった建付けで経費として扱えるのかどうか、私は税理士ではないのでよくわかっていない。そういったことを相談するために税理士さんに顧問になってもらおうと考えている。うちから税理士さんへの要件としては freee のデータを正として扱ってくれればそれでいいかな。

また2021年度は赤字決算になったので2022年度に 法人税の欠損金の繰り戻し還付 を行った。このお金を2022年度に計上していないため、その分の金額が資産のマイナスとして2023年度の決算に残ってしまっている。法人税の支払いは正しいのだが、還付金を2023年度に雑収入として登録するか、2022年度に遡って未計上の金額を登録するかのどちらかで訂正しないといけない。過去の法人決算の訂正自体も行う仕組みはあるので手続きするだけだが、それも手間暇がかかるのでついでに税理士さんにやってもらうと考えている。

会計システムに freee を使っているので freee さんの税理士紹介サービスを使って選定する。3事務所ピックアップしてくれたので順番に打ち合わせしていく。今日はその最初の税理事務所の方と打ち合わせした。話した感覚でうちの会社の考え方や規模にあった税理士さんだったのでこの方でいいんじゃないかとも思っているけれど、せっかく他の事務所もピックアップしてくれたので他の税理士さんの話も来週また聞いてみる。

コードレビュー

まる一日コードレビューをしていた。私もマージリクエストを投げていてレビューしてもらいつつ、メンバーのコードレビューも順番にやっていった。その中で smtp の仕様を把握しておく必要があっていくつかシニアエンジニアの方からもアドバイスをもらって、私もそうだったんだと勉強していた。メールヘッダーのエンコーディング、昔は覚えていたけど、ずっとメールを送るコードを書いてなかったので私も忘れてしまっていた。こういうことをさらっと指摘できるのがシニアエンジニアのすごいところ。

go だと標準ライブラリに mime パッケージがある。mime パッケージを使って件名を utf-8 でエンコーディングされた文字列で指定すると次のようになる。q エンコーディングと b エンコーディングの2種類がある。b エンコーディングの方がデータ量が減ってよさそう。

fmt.Println("Subject: " + mime.QEncoding.Encode("utf-8", "テスト"))
fmt.Println("Subject: " + mime.BEncoding.Encode("utf-8", "テスト"))
Subject: =?utf-8?q?=E3=83=86=E3=82=B9=E3=83=88?=
Subject: =?UTF-8?b?44OG44K544OI?=