Posts for: #2023/03

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

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時間ほどはいくのではないかと思う。私の中では納期に間に合わせるためにがんばる意思はあったんだけど、クリティカルパスはすべて私が担当するといった調整をできなかったので仕方ないなといった落とし所で来月への延期を正当化した。

期日前の敗北

0時に寝て6時半に起きた。日曜日に1ヶ月振りのお休みをとったので体力はぐっと回復した。

リリース判定前

明日をリリース延期を判断する期日としている。明日でリリースまでに対応しないといけない enhance ラベルが付いたタスクを fix できなかったら延期を決定すると先週の定例で基準を決めた。私がやろうと思っていたタスクは今日すべて fix したけれど、全体として fix できそうにないタスクが2つ残っている。さらに今日、検証していてリリースまでにやった方がよい enhance を2つみつけたので1ヶ月延期した上で、この2つも追加でやってしまおうかと考えている。事実上、今日の進捗をみた時点で明日の定例会議では延期の決断をする。がんばったけど、ダメなもんはダメなので潔く敗北を認めて残っている開発課題を今月末までに fix するようにスケジュールを調整していく。その分 QA テストのための工数を多く取れるのでテストツールを作ったり、結合テストを追加したりなど、余裕があれば品質を上げるためにできることも増える。1ヶ月前倒しにしていたから元の計画に戻るだけだし、私が管理対象にしていないモジュール群の開発遅れ (開発は不要と当初言われてた) なのでそもそも計画になかった。計画になかったとはいえ、その対応への気付きが遅れて、担当変更が遅れて、最終的にリリースが遅れたことはマネージャーとしての私の責任なのでシンプルに悔しい。もっとうまいやり方はあったはず。

2022年度の個人の確定申告

昨年の確定申告はこちら

一旦20時頃に家に帰って晩ご飯を食べてから21時にオフィスに戻ってきて確定申告の作業を始めた。15日(水)までなのでわりとぎりぎり。本当は日曜日にやろうと思っていたのにお休みしてしまったのでこんな時間から始めることになった。確定申告も例年と同じになっていて、且つ、いまは副業を受けていないので特別な売上や経費の登録をすることもない。取引一覧でやることは次の2つ。

  • 源泉徴収済みの印税収入を、売上と源泉徴収税の2つの明細として登録し直す
  • 寄付金の明細登録

これは15分ほどで完了してそれから確定申告書を作る。freee 確定申告 を5年以上使っている。その後の手続きも決まっていてワークフローに従ってこれらの入力を行う。

  • 源泉徴収票からの転機
  • 印税売上を源泉徴収済み雑収入として登録
  • ふるさと納税と npo 向けの寄付金の登録
  • 株式の取引報告書の登録 (損益通算)
  • 小規模企業共済の所得控除の登録

印税売上は源泉徴収済みなので無視してもよいが、寄付金や共済の所得控除があるので毎年行うことで節税になる。書類はすべて揃っていたので証明書を確認しながらワークフローを進めて3時間もあれば確定申告書を完成した。プリンタで紙に印刷して2箇所にマイナンバーを記載して、台紙に寄付金と共済の証明書を添付する。この証明書の添付をどうやって電子化して申告したらよいかわからないので毎年紙で提出している。

春休み

朝起きたんだけど、なんかしんどくてそのまま寝てだらだらしてた。今日は久しぶりにお休みしてた。

夕方からお休み

2時半に寝て8時に起きた。お昼から少し作業して夕方にお蕎麦食べに出掛けてそれから家に戻って休んでたらそのまま寝てた。

ストレッチ

今日の開脚幅は開始前157cmで、ストレッチ後159cmだった。ここ1ヶ月ほど患っていたすねの外側の筋の張りはまだ残っているものの先週より少しよくなった。復調の兆しがみえてきてよかった。出張戻りはいつも体調が悪めなものの、悪いだろうと予期していたほどは悪くなく、それまでの大量がよくなかった分だけ復調しているようにも感じ取れた。いまの状態は複雑。いつも担当してもらっているトレーナーさんが会社の技能試験のようなイベントの全国大会に出るという。全国で予選を勝ち抜いた人だけが参加できるそうで、200店舗以上ある中での予選を勝ち抜いた数人 (十数人?) が選抜されて全国大会に出れるという。それなりの倍率の予選を勝ち抜いたということなので改めてこのトレーナーさんはすごかったんやと見直した。全国大会もがんばってほしい。

昔の上長の背中を追う

2時に寝て7時半に起きた。昨日もWBCをみてから晩ご飯を食べて軽く作業してそのまま寝た。

伴奏

ここ2-3日若いメンバーの開発をサポートしている。

「◯◯ができません」

私が5分ほどでググってできそうなドキュメントや so をみつけてリンクを貼る。

「できました。」

みたいなやり取りを何度かした。大して難しくない処理を実装できないのは公式ドキュメントをちゃんと読んでいないのと、インターネットの検索方法を習得していないように私からはみえる。一度、定例会議のときに google の言語設定を英語にした方がよい。日本のキュレーションサイトの記事の品質は低いからと伝えたが、まだそれを実践しているようにはみえない。いまや英語は deepl で翻訳すれば大半を斜め読みできる。私も deepl で読んでいると教えたりしているのだけど、日本語の記事しか検索していないから未知のことをできないとなってしまう。

チャットで困っていることや問題点を整理したり、どういう視点で調べていくかをやり取りしながら本人が理解して作業できるようにサポートしている。私がやれば10分で終わるようなことを2時間ぐらいかけてチャットしている。それで昼間は自分のお仕事をやらずチャットの話し相手を務めている。誰もが最初は初心者なのでそういう時期はある。以前は質問すらできていなかったところを、あれができないとか、これがわからないとか質問できるようになったというのは成長したと受け取れる。わからないことを説明してもらうことで、私も相手のことを理解できて適切な指示や指導ができる。その過程でプログラミングの理解度も測れるので issue をアサインするときの参考にもなる。

曖昧なことをチャットで聞くのは効率が悪いから、口頭であれこれ質問してくれるようになるのがこの次のステップかな。以前と比べて質問してくれるようになったので信頼関係は少しずつ構築できてきつつあるのかもしれない。

昔の自分といまの自分

既存のある java のコードを読んでいて、私の中ではワーストから数えた方が早いほどのひどいコードをみている。java の言語仕様もプログラミングもどちらもよくわかっていない人がキュレーションサイトにあるような記事を読んで動くように作ったようなツールにみえる。アリエルを辞めてからいくつかの会社で働いてきて java のコードも読み書きしてきた。これまでの経験からその職場での java のコードは品質が高かったし、私もその影響で java の設計やアーキテクチャにも関心をもつようになった。私は未熟なので人並み程度のプログラミングしかできないが、そのスキルを底上げしてもらったのはその職場での3年間の java 開発といえる。私にとっての普通が当時の開発体験やチームの同僚になったことでそれ以降に出会った開発者の大半はスキルが低いようにみえてしまう。そして、その後に私がどんなプロダクトを開発しても決して当時の先輩方に敵うことはないと慢心することもない。だからプロダクトの開発を終えて、組織の方向性にあわなければすぐに辞めることもできた。

いま私がメンバーに教えていることも、メンバーからみたら少し厳しくみえるかもしれない。私にとって先人のような偉大な開発者に自分もなれるんやろか?とか思いながらマネジメントをしていたりする。今週はとくに18時にホテル戻って2-3時間寝て22時頃から2-3時間コードを書いたりしていた。当時の上長もよくそうやって開発していた。当時の私はよく働いたが、そんな私からみてもその上長もよく働いていた。そして上長の生産性は私よりも数倍高かった。お互いに課題管理システム上にいることはわかっていたし、夜中の1時頃にチケット上でやり取りすることもあった。私もいま当時の上長と同じようなことをやっているなと感慨に浸りながら夜中にコードを書いていた。うちのチームのメンバーは誰も夜中に開発していないことだけが当時とは違うことにも気付いた。