Posts for: #Refactoring

2023年最初の記事は事例紹介

0時に寝て7時に起きて8時前までだらだらしていた。昨日は開発してないけれど、なんか疲れて起き上がれない。

先週末に恒大集団が米国で連邦破産法適用を申請というニュースが出ていたので香港市場でひと波乱あるのでは?という憶測も出ていたけど、とくに変わりなく1日が終わった。別の不動産大手もデフォルトの危機らしくて、その2回目の期限が1ヶ月ほどあるようなのでもう1ヶ月してから波乱があるのかもしれない。

最後のリファクタリング課題

このマイルストーンで開発を終えて次マイルストーンからテストへ移行する。機能拡張はほとんど終わっていて、作り直した方がよいリファクタリングのチケットをいくつかやっている。そのうちの1つに azure 連携するときに rest api を直接使っていて sdk を使っていない。microsoft 社も go の sdk を提供している。将来的には型によるバリデーションの仕組みを導入したいと考えている。あらかじめ sdk を使ったコードに置き換えておきたい。sdk が使いやすい api になっていれば、すぐに進捗するところが、これがなかなか、この sdk の api 設計もコードもあまりきれいなものではない。一方で大半のコードはスキーマからコードを自動生成しているようもみえるのでモジュールの構造を理解してしまえば、api の設計も類推は効くようになると思う。

ユーザー/グループの取得周り、ユーザー作成の web api 呼び出しを置き換えて疲れて晩ご飯を食べに帰ってしまった。家に帰ったらもうダレてしまってそのままだらだら休んでいた。残りの開発を明日に先送りにする。疲労もあるのか、モチベーションコントロールがうまくいかなくて、昔だったらあと2-3時間作業して帰るところのひと踏ん張りがきかなくなりつつある。気を引き締めないとあまりよくない。

事例紹介

昨日更新したたたき台 を最終版にするつもりが、午前中、先方に送る前に読み直したら細かいところに気付いてちょっとだけ推敲した。午前中にレビュー依頼を出して、夕方には返信がきてそのまま OK が出て、事例紹介を公開した。会社のサイトをすっかり触らなくなってしまっていて、8月になって2023年初めての更新になる。何度も書いているけど、この事例紹介は私の自己満足で、これを書くと世の中の役に立っている気がして嬉しくなる。さらに今回はプロジェクトマネージャーとして実績を出すことができたので次のキャリアへの大きな一歩となる。今後も課題管理の探求をがんばっていく。

設計の見直しとリファクタリング

2時ぐらいに寝て起きたかどうかあまり覚えていないが7時に起きた。あまり寝た気がしない。

エージェントアプリケーション開発

昨日の続き 。一晩寝かした甲斐があったのかどうか、意識的にはわからないが、少なくともつまづくことなく1つずつ設計の見直しや処理をパラメーター化していって一通りプロトコルの差異以外のところは整理できた。今日は自分のコードを書く余裕はなかったけれど、1日かけて既存の設計の見直しを終えられたことをうまくいったと自分で肯定しておこうと思う (他人からみたらそうじゃない可能性はある) 。

関数は1つの機能のみを実装して、入力しか依存関係をもたないようにシンプルにすべきという基本概念がある。その基本概念をちゃんと理解できていないと、設計のアンチパターンの1つとして、渡すパラメーターがどんどん増えていってしまうことに気付いた。まずは既存の処理やフローを変えずに設計を見直したのでそうなってしまっている。また後で時間があったら、インターフェースを明確にして根本的な設計を見直してもよいかもしれない。

go の channel 制御の学び

0時に寝て2時に起きて5時に起きて6時に起きた。久しぶりに夢をみたような気がする。

go の channel とクローズ制御

rabbitmq/amqp091-go を使った pubsub の consumer の実装で次のような for ループでメッセージを取得していた。この場合 deliveries の channel が閉じられると内側の for ループが終了して、外側の for ループの接続処理へ遷移する。

for {
    // コネクションの接続処理

    for d := range deliveries {
        // メッセージ処理
    }
}

この処理を context を使ってキャンセルできるよう、次に書き換えた。channel の Receive operator のドキュメントによると、channel は2値を返すことができて、戻り値の2番目の ok の値を調べることでその channel がクローズされているかどうかを判定できる。この仕組みを知っていれば select で ok を調べてエラー処理を実装できる。そうしないと deliveries の channel が閉じられれたときに select では常にゼロ値のメッセージが返るようになって意図したメッセージ処理とならない。

for {
    // コネクションの接続処理

    for {
        select {
        case <-ctx.Done():
            // context がキャンセルされたら終了処理
            if err := s.ch.Cancel(cid, false); err != nil {
                return fmt.Errorf("failed to cancel channel: %w", err)
            }
            return nil
        case d, ok := <-deliveries:
            if ok {
                // メッセージ処理
            } else {
                // エラー処理
                break
            }
        }

        // コネクションが閉じていればループを抜けて再接続
        if s.conn.IsClosed() || s.ch.IsClosed() {
            log.Info("the session was closed, try to reconnect", nil)
            break
        }
    }
}

同じ channel からメッセージを取り出すループ処理でも用途によって使い分けがあるなぁと今更ながら学んだ。というか、自分でバグを埋め込んで自分で直した (´・ω・`)

マージまで約1ヶ月

先日送った amqp091-go の pr が最終的にはほぼそのままマージされた。約1ヶ月ぐらいの議論やレビューがあって取り込まれた。新しいリリースバージョン (次は 1.9.0?) が出たらうちのアプリケーションでもこの新しいメソッドを使うようにして実績を積ませる。この pr は「あったら便利」程度の機能なのでそれほど重要ではない。

この成功体験をもって次は本命の go-ldap の pr のマージに向けて勢いをつける。こっちの pr は業務に直結しているので本気でやり取りしている。

コードを書いていると後から改善点に気づく

1時に寝て7時に起きた。起きてからドラクエタクトをだらだらやってて10時前に起き上がってそれからオフィスへ行って活動してた。

リファクタリングのリファクタリング

昨日書いたマージリクエストの変更点についてドキュメントの更新だけしようと作業していたら、その後、一緒に修正した方がよい追加の機能拡張に気付いて3時間ほど追加の実装をしていた。その後、お昼ご飯を食べているときに午前中に書いた実装を改善した方がよいところに気付いて、さらに追加で1時間ほどリファクタリングしていた。コードを書いていて、一旦はできたつもりになってその時点ではよさそうに思うのだけど、時間が経ってから考え直すと考慮漏れやもっとよい実装のアイディアを思い付いたりする。私の頭が悪いだけかもしれないが、最初からよい実装や設計を行うことは多くの人にとって難しいことだと思いたい。何度も考えて作り直したり改善したりしているうちにもっとよい方法に気付く。

多くの開発者はもっとよいアイディアを思い付いたとしても実際にリファクタリングをしようとしない。動いているコードを修正して壊れるリスクや他に優先度の高い作業があるとか、いろいろやらない理由を言う人たちもいる。私はそのやらない理由を議論している合間にリファクタリングしてしまう。というのは、誇張した比喩で実際にはもっと時間がかかることもあるけど、設計も見直す数千行レベルの変更を気軽に行う。もしそれで壊れたらどうすると聞かれてもシンプルに謝るだけ。謝ってから直す、テストを書く、再発防止のための仕組みを考える。やることはそれだけ。問題のあるコードを見て見ぬ振りをする開発者の方が普通という感覚がある。10年以上開発をやってきて思うこととして、そういう価値観を上位の開発者が壊していって模範を示すことがよい開発文化への第一歩となる。しんどいことに対して口だけでは人は動かない。個々の開発者が問題だと思ったらどんどん書き直していく、リファクタリングしていくという文化は一朝一夕ではできない。そういう開発文化を醸成していくと大きな技術的負債が溜まるといったことはなくなるのではないかと考えている。

リファクタリング一段落

1時に寝て7時に起きた。前日も21時頃までオフィスにいて、今日も午後からコードを書いていて22時ぐらいまでやってた。コードを書いていると時間がどんどんなくなる。本当は三宮.dev の勉強会があったんだけど、このリファクタリングは週末にやってしまわないとやばいという野生の勘でキャンセルした。

ストレッチ

今日の開脚幅は開始前156cmで、ストレッチ後160cmだった。だいたいいつも通り。先週から右すねの外側の筋に張りがある。先週よりちょっとよくなった気もするけれど、まだ張りが継続している。あと今週は右腰に張りがあった。今週はリファクタリングしていて座ってきる時間がいつもより長かったためだろうと推測する。どんなに忙しくてもストレッチだけは休まないようにしている。ストレッチに通い始めて2年以上経つが身体的に体調が悪いということは記憶にほぼない。日記にはその週のどこそこに張りがあるとか調子が悪いといったことを書いたりしているが、それは日常生活を送る上で支障が出るようなレベルではない。そうならないように予防している。健康を維持する上でストレッチは大きな影響を与えているため、中長期の展望から忙しくても継続するようにしている。

機能拡張とリファクタリング

今日は休日出勤して go のコードを書いていた。ある機能を作るときに内部的には汎用の api にしてしまって他のコレクションのデータ型でも再利用できるようにしたい。先週ずっと go の generics を使って mongodb 周りのコレクションとそのクライアントのリファクタリングをしていた。go の generics の理解も進んで crud なインターフェースを generics でどのように定義して実装すればいいかわかってきた。その過程で web api のアプリケーション層と mongodb のインフラ層 (データ層) の役割分担も明確になりつつある。どちらも generics を駆使して型チェックされた上でソースコードを共通化し、汎用 api としてリファクタリングしながら設計している。その集大成としてアプリケーション側で汎用的な機能を追加するときに、理想的には1つのコードを追加・変更すれば、別のデータ型でもすべて同じように動くといった機能として実装した。4つのコレクションのデータ型で同じ振る舞い (機能) を共通化する。その実装も丸1日やれば完了できるぐらいに設計の効率化ができてきた。

go はオブジェクト指向言語ではないので generics を駆使しても、java でいうところの抽象既定クラスを用いたテンプレートパターンの実装ができない。それぞれの構造体で基本的にはコピペとなる構造体のメソッドを定義しないといけない。もちろん別のヘルパーに移譲するといったことはできるけど、状態をもっていないコードの再利用はたしかに安全ではあるけれど、状態を参照できないからそのために値をコピーするといった整合性の懸念やボイラープレート的なコードを書く必要がある。これは設計におけるトレードオフになるので go の処理系の設計に不満があるわけではない。但し、generics でできることにはまだ機能不足がある。とくに直和型の扱い。できないのかな?とググると proposal の issue がみつかるので今後の機能拡張に期待したい。

正直マネージャーが開発に工数使っていて何やってるんだとみられているかもしれない。1-2週間集中してリファクタリングしてみて、いまのアプリケーションの設計の勘所が以前よりも理解が進んだ。コードレビューだけではわからないフィードバックがある。結合テストもいくつか追加したので今後の開発で役に立つはず。これで私の (リファクタリング) 開発は終了しようと思う。

今日作ったマージリクエストの diff が次になる。

51 files +1314 -648

diff の行数だけ数えると今週だけで1万行近くは変更したと思う。

コンテキストによるキャンセル処理

1時に寝て7時に起きた。昨日がっつりコードを書いていたせいか、起きてから今日は休みたいと思いながらだらだらしてた。休めないのだけど。貧乏暇なし。

echo のリクエストコンテキスト

昨日の続きで mongodb のリファクタリングをしている。mongodb はほぼすべての処理で コンテキスト を受け取れるように設計されている。メンバーが go の context の扱いを理解していないのでコンテキストを渡すようなインターフェースになっていない。私もこれまでのコードレビューでキャンセル処理は些事なのでそれよりも機能開発を優先して後回しにしていた。それで、いま機能が一通り開発完了したのでインターフェースにコンテキストを受け取るようにリファクタリングした。デフォルトのコンテキストとして echo のリクエストコンテキストを渡す。Canceling request #1815 によると、echo のリクエストコンテキストは次の状況のときにキャンセルしてくれる。

  • クライアントのコネクションがクローズされたとき
  • http2 でリクエストがキャンセルされたとき
  • ServeHTTP() が返るとき
    • サーバーがシャットダウンするタイミング?

使い方はこんなイメージ。

ctx := c.Request().Context()
store := c.Get(myStore).(Store)
store.Get(ctx, request.From, request.To)

mongodb の view 調査

4時半に寝て11時に起きた。午後から go のコードをリファクタリングしてた。平日はメンバーの issue を監視して困ってたらコメントしたり、マージリクエストをレビューしたり、管理画面の振る舞いを検証したりと、コードを書いていても途中で作業がちょくちょく中断する。休日にコードを書くとその中断がない分、いつもよりかなり捗った。リファクタリングに思いの外、時間を取られている気がしたのはそのせいもあるか。

mongodb の view 作成

実はこれまで mongodb を扱ったことがなくて今回初めて触っている。とくに難しくもないのだけど、ドキュメントを探してやりたいことを調べたり、デバッグや開発のためのツールとしてどういうものがあるかといった知見がない。ないものは仕方ないので1からドキュメントを読みながら開発というか、リファクタリングをしている。mongodb そのものの知見はなくても、様々なデータベースを操作する開発をしてきたのでやりたいことに対して実装方法はいくつか検討がつくし、その実装を支援するための機能もあるはずだと予測がつくので探すのも早い。

ある collection から複数のデータ構造のレスポンスを返すような処理がある。こういったものは view を使うとうまく整理できると知っているので調べると mongodb view が提供されていることがわかる。3.4 から追加されたらしい。いまクエリの中で aggregation pipeline を書いている処理のいくつかは、あらかじめ view を定義してクエリすることでインフラ層を堅牢にした上で実装もシンプルにできる。さらに 4.2 から on-demand materialized view が追加されていて、標準の view と比較して aggregation pipeline の計算結果をディスクに保持するのでパフォーマンス上のメリットがある。元データの更新が頻繁でなければ on-demand を使った方がよいのだろうと推測する。

またこれまで mongodb の管理画面に mongo-express を使っている。view の振る舞いを確認しようとしたところ、どうも view には対応していないようにみえる。web ベースの管理画面を他にも探してみたが、どうも他に適当なものがない。mongodb が公式に compass というデスクトップアプリケーションの gui クライアントを提供している。macos なら brew からインストールできた。

> brew install mongodb-compass

このツールは collection も view も両方扱えるし、クエリやパイプラインも実行できて機能も充実している。web ベースじゃないとインフラとして共有はできないところだけが残念なところ。それでも開発する上ではとても強力なツールにみえる。適当にデータをインポートしたり、テストで aggregation pipeline を作成してみて、それをエクスポートして view を生成するときのスキーマ定義も作ることができた。ui も洗練していて、こんな優れたデスクトップアプリケーションは久しぶりにみたと思うぐらい感心した。

mongodb を触ってみる

0時に寝て7時半に起きた。今日は1日中リファクタリングしてコードを書いていた。

リファクタリング

mongodb 周りのインフラ層のリファクタリングしている。過去に mongodb を触ったことがなかったので mongodb そのものの振る舞いや仕様なども理解しながらリファクタリングしている。その第一弾として差分が1500行以上のマージリクエストを作った。私の中ではまだ 1/3 ぐらいの進捗。動くレベルになったのでコードレビューを通じてメンバーと設計の考え方を共有していく。差分は多いけど、重要なところは一部だけ。やり過ぎだなと思えるのは、設計を見直すとテストコードを書き換える必要があって、その書き換えをやっていると時間が削られる。設計の部分だけ変更して、その後の作業をメンバーに引き継いでもらうといったやり方ならもっと早くできるかもしれないけど、テストコードを書き換える過程で私もユーザーの立場になって設計の良し悪しを検証するきっかけになるのでこの作業は自分でやることにも価値があると考えている。

設計がよくないところをどうやってメンバーに指導していくかはなかなか難しい。良し悪しは複数の設計を比較することで初めて気づくことも多い。私はその引き出しが多いので比較対象がたくさんあるだけでメンバーはその引き出しが少ないから悪い設計と認識できないでいる。その比較対象を私が提示してメンバーが考える機会にしてあげたい。理屈上はこれだけなんだけど、現実のコードと納期とのバランスを取るのが難しい。

いい加減マネージャーがリファクタリングに工数を使い過ぎだろうと私自身でも思うようになってきたので週末に残りのコードを書いて区切りのよいところでひと段落つけようと思う。

個人の人生と会社の経営

1時に寝て6時半に起きた。久しぶりに起きずに長時間眠れた気がする。1-2ヶ月に1回あるかどうかぐらいの感覚。

リファクタリング

ここ最近、私がバックエンドのリファクタリングを集中的にやっている。私からみたらバックエンドの設計にはいくつか改善の余地がある。動いているという視点では及第点ではあるし、メンバーは限られた時間の中で十分にうまく開発していると考えている。それでも、遊撃として余裕があるときにリファクタリングをしている。これから運用レベルの QA テストに入っていく。その前にがーっとリファクタリングしておくとテストで振る舞いを検証できるし、テストで別の不具合をみつけたときも保守コストが下がるように設計を洗練させておくことには一考の価値がある。実際にコードを書き始めると時間をどんどん取られて、マネジメントの業務から離れていってしまう。マネージャーがコードに集中し過ぎるのも問題だというのも理解できるようになってきた。本来はあまり深入りしない程度にメンバーに要点を伝えて改善したもらうのが正しいとは思う。いろいろプロジェクトの都合があるので必ずしも思い描いたようにはいかない。バランスをとってうまくやりたい。

余暇と経営と個人

1月以降は実家の法事のために私の余暇の半分程度の時間を取られている。先週末に49日を終えたので次の法事は初盆まで余裕がある。とはいえ、弁護士さんと実家と相続のやり取りがあってまだ何割か時間を取られている。この余暇の時間に、これまでは自社の業務や事務手続きをしている。その余暇が少なくなると、最終的には他のところにも皺寄せがいく。余暇に余裕がないと自分の会社を経営するのはなかなかしんどいところもあるかもしれない。

来季は実家の一部を自社のオフィスにしようと考えている。よくよく考えれば、神戸のオフィスでフルリモートワークをするのも、淡路島のオフィスでフルリモートワークをするのも、お客さんからみたら全く違いがない。いままでそうしてこなかったのは、神戸のオフィスでは業務に集中できるよう、私にとって最適な環境を構築し続けているからと言える。最も大きな違いは椅子だと思う。よい椅子でデスクワークしていると、あまり粗末な椅子でデスクワークしたくないというネガティブなモチベーションになってしまう。

メインオフィスと同じとはいかなくても準ずる程度のオフィス環境を実家に構築できれば実家に帰って1-2週間程度のフルリモートワークをしてもよいと考えている。その延長上で実家との行き来を出張扱いにして経費を使えるようにし、経営的にはそうすることで節税にもつながる。私個人の視点からも、親の介護という、私の人生設計上避けられない問題に対する解決策となる。個人の人生の問題と会社の経営を両立させることは、自分の会社であれば、十分に両立できると自信をもって言える。この話しも田舎から上京してその後のキャリアを考える上でのモデルケースの1つとして、いつかブログの記事として書きたい。

意図がわかる設計とリファクタリング

1時に寝て7時に起きた。久しぶりに HELLSING をみてた。アレクサンド・アンデルセンの狂信者ノリが好き。

煩雑な保守

昨日から着手した s3 とやり取りするアプリケーションの保守をしている。一通り機能は実装できたが、このアプリケーションの保守を今後どうやっていけばいいのかが私からはみえない。要件が変わる度に継ぎ接ぎで拡張してきて、意図をもった設計があるわけではないようにみえる。このまま保守することはできるかもしれないが、このロジックの説明もテストも検証もすべてが難しい。私がみても難しいのだから、経験の浅い開発者がみるともっと難しいのではないかと思う。

これを直すにはまず単体テストを直さないといけない。単体テストの大半がモックベースなので実際の振る舞いと異なる可能性がある。とくに s3 とやり取りするところの検証ができない。testcontainers の localstack があるので単体テストはモックからこのモジュールを使うように代替できそう。まずはそこからやるべきだが、2-3日はかかると見込まれるのでチームで承認を得られるかどうか、ちょっと聞いてみてから考える。

Job Summary を使ってみた

ちょっと前に github actions のワークフローの実行画面にサマリを出力できるようになったという記事をみた。

自動でよさげなサマリを出力してくれるわけではなく、自分でサマリを作らないといけないので面倒だなと思ってそのまま放置していた。先週末に モジュール別のビルド・デプロイのワークフロー改善 を行った。ふとワークフローの実行結果をみていて、選択したモジュール名が表示されているとわかりやすくていいなと思えた。それを出力する手段としてサマリがちょうどいいやということに気付いた。inputs などで動的に変更するパラメーターをワークフローの実行画面で確認できるといちいちログ確認する手間が省けてよいという場面が他の用途でもある気がしてきた。もっと積極的にサマリを使っていこうと思えた瞬間だった。

リファクタリングとオブジェクト指向プログラミング

0時に寝て8時に起きた。先週末の疲れが溜まってバテバテ。直近の1週間で睡眠時間が短いときは3-4時間しか寝てないのでなんか朝起きれなくて調子悪いなと思ったら単純に睡眠不足よね。なんか安心した。

既存ロジックのリファクタリング

ちょっと前にタイムラインで「値オブジェクト」の定義の話題で盛り上がっていた。私は詳しくないのでどちらが正しいのかの白黒を付けられないが、値オブジェクトがどういうものかの定義を知らなくても、オブジェクト指向プログラミングとしてデータ + メソッドでカプセル化していくモジュールの概念からすると普通のことであって、それの特殊ケースに名前が付いている・付いていないという議論だったのではないかと思う。

今日たまたま既存のある処理に機能を拡張するため、既存のコードを読んでいて、ロジックを扱うコンポーネントが肥大化していて、ここに手を入れていくとさらにコードの見通しが悪くなるように思えた。そのロジックコンポーネントの半分は出力用の DTO を生成する処理が占めていて、DTO を生成する処理をオブジェクトとして切り出すことでロジックコンポーネントを半分に分割できることに気付いた。パラメーターとして7つの値をそのオブジェクトの入力として、そのオブジェクトの内部であれこれやって、最終的に必要な DTO を出力できればよい。1つのコンポーネントを2つのコンポーネントに分割したことで、それぞれのコンポーネントのスコープにおけるメソッドが半分になったというだけの話し。一般的なカプセル化の話し。難しい話しをしなくても設計やオブジェクト指向プログラミングの基本的な考え方などをメンバーに教えていければいいんだけど、私はあくまで協力会社のお手伝いなので、教える業務は担っていない。次の契約更新のときにインフラやプログラミングを経験の浅い人たちに教えましょうか?みたいな話しもしたいと考えている。