月: 2023年11月

squash and mergeしか使ってないけど全く困ってない

こんな記事を見かけた。

こういうことはレポジトリ構成・ワークフローと密接に紐づいているので、そういう前提を抜きにはどれがいいとかはいうことはできない。が、自分はいわゆるsquash and mergeのみの環境しかほとんど経験がないし、それで困ったことが一度もない、という話をしておきたいので書いておきたい、ので書いておく。

squash and mergeのメリットは書いてある通りで、基本的にPR内の細かい修正というのはゴミみたいなコミットが多く、メッセージも雑なことが多いので、それをコミットログに残しておくのは嫌だということがある。それよりは意味のある単位のコミットを残しておきたいし、それの単位はPRで行うのが良い、ということだ。

“Google-style” workflow

デメリットの方は、いわゆるfeature branchというワークフローで顕在化する問題であると思う。で解決策はあり、それはワークフローを変えてfeature branchは作らない、ということになる。全ての作業は主にmain branchに対して行う。もちろんPRのためにはブランチを作るが、それは個々のPR単位で作り非常に短命になる。その昔、Googleが「全てmain branchで作業している」開発スタイルだという話を公表して当時は結構話題になったので私は”Google-style”のワークフローと呼んでるけど、ぶっちゃけ今となっては常識化してきたと思う。もっと一般的な名前があるのかどうかはよくわからない。(追記:trunk-based developmentという名前がちゃんとあったというのを指摘された。専用のドメインまであるのか。thanks for the info!)

このワークフロースタイルでは、機能を開発するのにブランチを作ることはしない。個々の開発者は個別にPRごとのブランチを作る(topic branchということが多いと思う)。このブランチでそれなりに意味のある変更が積み上がったら、それをPRする。PRでコードレビューされ、レビュー内容に応じてちょこちょこ変更したりするが、ともあれそれが終わってPRがマージされたらおしまい。そのブランチも消す。ブランチは短命で、平均寿命は1日以下とかみたいになる。そういうふうに短いサイクルでブランチを作っては消していく。なのでリンク先のような問題は起こりえない。

もちろん開発する機能というのは大規模なものもあり、そういうものは一つのPRで終わることもないし時間もかかる。でも全てのコミットはmainに積み上げていく。完成していない機能はフラグなどを使って実際には使われないようにしたりする。この方が全員が全てのことを把握できるし、意図せず他の機能のものを壊すこともないのでいいのだ、というのがjustificationであると思う。

cascading PRs

一つの作業をしていく中で、その作業がちょっと規模が大きくなってきたのでPRを分けた方がいいな、ということはある。そういう場合にはPR1のブランチがあって、それに依存したPR2のブランチがあり、さらにPR3のブランチもあって……みたいな構造をとる事もある。そうしてPR1がマージされた後でPR2/PR3はどうすればいいのか?という話はあると思う。

ただこれはfeature branchとは本質的に違う点があり、PR2/PR3というのは基本的には自分一人しか触らない。feature branchとして開発している場合にはチームみんながそれをいじっているわけなので、どうやってマージするか、整合性を取るかというのは大きな問題になるけど、自分の手元のローカルブランチなので好きなようにいじってしまえばいい。具体的には、普通PR1をマージしたら、PR1ブランチを削除し、mainをgit pullした後で、PR2はgit rebase mainすればそれで済む。PR1に由来するコンフリクトは自分にとっては自明だし、ほとんどの場合にはgit rebase –skipするだけだ。もしくはgit rebase -iして自分でPR1由来のコミットを消してもいいだろう。

release branches

場合によってはリリースごとにブランチを作るということはある(昨今のモダンなウェブ開発ではあんまりないかもしれないけど)。例えばChromeブラウザでは新しいバージョンごとにブランチが作られている。このブランチはリリーススケジュールにもよるがそこそこ長命であることが多い。Chromeは4週間ごとに新しいブランチが作られるが、stableチャンネルの他にbeta、devというチャンネルがあるため、8週間くらい生き延びる。

ただ、基本線は同じと思う。

まず、リリースブランチはリリースごとに新しいものを作る。例えばChromeの場合、一つの「stable channel用のブランチ」があるわけではない。Chromeは今バージョン119でbetaが120だが(あってるかな)、この場合には「バージョン120のブランチ」と「バージョン119のブランチ」が作られている。stableが120に移るというのは、stableは「バージョン120のブランチ」を使うように変更することを意味する。その時betaは新しく作られた「バージョン121のブランチ」を使うようにスイッチしている。「バージョン119のブランチ」はそのまま消されることはないが放置され、忘れ去られる。

そしてリリースブランチでは開発をしない。メインの開発とは別にセキュリティの修正など重要なパッチをリリースに含めたいことはとてもよくある。だがその時もリリースブランチに直接変更を加えることはしない。あくまでも修正はmainのブランチに対して作り、そちらにマージする。マージした後でその修正をリリースブランチにcherrypickする。

もちろんワークフローとか開発スタイルというのは大きく優劣があるようなものではない。例えばこういう開発スタイルは大規模分散的なオープンソースソフトウェア開発(例えばLinuxカーネルとか)には向いてないだろうと思う。文化的な面もある。

ただ、この「squash and merge」というのは基本的にはここで私のいう「Google-style workflow」と相性がよく、というかこのワークフローでないなら難しい面もあるということなのだと思う。どのマージスタイルを選ぶのかというのはちょっとした選択のようでいて、ワークフローも含んだ選択でもあるのだろう。

Android-style workflow

ところで、この記事の元ネタのような呟きを bluesky に書いていたところ「Androidは……」というツッコミというかなんというかをいただいた(笑)。AndroidはGoogleの一部でありながらこういう「Google-style」ワークフローでは全くなく、完全なるカオス。自分はちょっとだけ体験したけどマジでやばい。どうしてこうなってしまったのか、こうなる前にもっと別なところを改善すべきだったのでは、と個人的にいつも思っている。

あんまり詳しくは公表されていないのではっきり書かない方がいい気もするけど、例えばAndroidは社内にmainブランチ的なものがある一方でAOSP (Android OpenSource Project)として公開されているブランチもあり、どちらも活発に開発されている(新しいコミットがどんどんマージされている)。しかもwearとかはまた別の自前ブランチを持っていたりする。そういう中で、誰かの入れたコミットがそれぞれのブランチに適切にマージされていくような仕組みがある。だが(上記だけでも結構複雑そうに思えるが)実際にはさらに数段複雑な構造になっているため、「適切なマージ」の仕組みは複雑怪奇な仕組みになっている。

こういうことはやめましょうね、という反面教師としてしか存在価値はないような構成・ワークフローになってしまっていると思う。もっとシンプルでstraightforwardなもので充分です。AndroidやLinuxカーネルを開発するんじゃないんだからね、と、私としては思う次第。

Marvel’s Spider-Man 2

PS5のSpider-Man 2をクリアした。かなり面白かった。今回も良作。ただ総合的な満足感は前作の方が上という気がした。

PS4にあったゲームの続編で、ニューヨークを舞台にしたオープンワールドゲーム。今回の主なヴィランはKraven the hunterとヴェノム。Kraven the hunterっていうヴィラン初めて見たんだけど、ゲーム内でのデザインはよくできててかっこ良くはあるものの、「君ちょっと世界観違くない?」という気がする見た目だったのがすごく気になった。オープニングシーンとかちょっと笑っちゃった。

ゲームとしてはそんなに難しすぎないしやっていて楽しい。スパイダーマン的な動きが色々できたりアクションも派手。ゲームメカニズムとしては基本的には前作そのままでやることもそのままだが、色々追加要素(糸を渡したりとか)もあり、そこそこ複雑化していて楽しくはなっている。空飛ぶアクションは苦手だったのでそこはちょっとイマイチだったが。

ストーリーも複雑化していたが、でもきちんと繋がったストーリーになっていて感心した。特にLiのストーリーラインがなかなか良かった。ただ、ピーターとマイルズのダブル主人公制はそんなにすごく機能しているという感じではなかったかなと思う。あとMJ強すぎでは。というかMJの持ってる武器が強い。スパイダーマンたちもそれ装備しなよ!と少し思った。

総合的な満足度という点では、サブクエストがかなり少なかったように思う。前作ではスパイダーマンのマスクを被った猫を探すみたいな些細なサブクエストがたくさんあって楽しかったように記憶しているのだが(スパイダーキャットは本作でもちらっと再登場していて良かったけど)、そういうサブクエストが多くなかったかなと思った。序盤のサブクエストの一つはかなり良いものだったが、ああいうのがもう少し色々あると楽しかったかなぁ。一方で収集要素は増えていたけど、そっちはそこまで面白さを感じられず……。

サブクエストというかサブストーリーでいうとThe Flameのストーリーライン、あれで終了なの? 最後の一つをやり逃したのかと思ってしまった。続編を作る気満々だから、続編へのヒキも考えての展開だったのかな。

続編といえばエンディングからは続編作る気がビンビン伝わってきたが、アメコミ知識がなさすぎて最後に名前だけ出てくるキャラクターに「えっ誰?」となった。速攻で検索して把握しました。

というわけで、色々言いたいことはあったが総合的にはすごく楽しかった。続編も楽しみです。

github copilot

数週間前くらいに会社で導入してもらって、github copilotを使ってみている。

今のところ、すごくいいっていう感じではない。copilotの動作、おおむね

  • 関数などの名前だけ書いて空白にしておくと、関数全体の中身を勝手に埋めてくれる
  • 細かい修正をしているときなどの補完入力的な挙動
  • コメントなどの入力支援

と言ったところであるように思う。が、

  • 最初のパターンはあんまり使ってない。出てきたものも微妙なものが多い気がする
  • 細かい修正は便利なことは多い。特にGo言語はエラー処理などでboilerplateが多いので、そういうところでやってくれるのは本当に助かる
  • ただ挙動として、プログラミング言語の意味的な情報(型とか)を利用しているわけではなく、周囲のコンテキストなどをプロンプトとして生成しているだけなので、結果として「雰囲気で補完している」ようなことがままある。ないフィールドを補完してしまったりとか。
  • コメントの入力支援はなかなか便利。特に英語が母語ではない身としては割と助かる。ただそういうことではない、みたいなコメントも多い気がする。

というわけで、便利は便利なんだがなけりゃないで構わないな……という気持ちもある。個人的には、コメントの文章とかのこなれてない表現をいい感じに編集してくれたりしてくれた方が助かるのだが、copilotはそういう機能ではないなあ、という話ではある。

ただ、もっと最初のパターンを使いこなすようになってくるともっと生産性が上がるのかもしれない。まだ「上手い使い方」がわかっていない感じがある。関数の名前に加えてコメントを適切に書いたりと言った、一種のプロンプトエンジニアリングでもっといい感じにできるのかも。その辺を追求するべきなのかどうかは悩みどころ。


さて、そんなことを思っていたらcopilot workspaceの話題を見かけた。

すごい。すごいけど、このcopilotの現状からしてどこまでできるようになるものなんだろうか。

これまでの自分の体験としては、確かにすごいけどそこまでのことができるようには思えないようにも思える。ただ、それなりにきちんとした実装方針を提示すると実際のコードや修正をそれなりの形に持っていける、というのは全く不可能ではないような気はする。この実装方針のブレークダウンが肝で、そこをうまく作るためにはそれなりにプロンプトエンジニアリングが必要な気がするし、現在の実装に基づいて具体的にissueを設定しないといけないんじゃないか、という気がする。まあただの想像ですけど。

ただ、そのようにしてなされた実装はどちらかというと設計が汚くなったり重複したりとなってしまいがちなんじゃないか、という気もする。同じようなコードを何度も書くことはAIにとってはそれほど苦ではないような気がするので。そしてこれを継続していくとコードは複雑化し、読めなくなっていき、メンテナンスができなくなっていったりするんじゃないだろうか。そこで適切なリファクタリングを導入する必要がある。もちろんそれにもcopilotを使って……となっていったりするのかな。

ところで、複雑なソフトウェアにおいては、問題をどのレイヤでどのように解決するかが自明でないことがよくある。copilot workspaceはそういう問題を解決するものではないだろう、と思う。例えば、ChromeOSのウィンドウマネージャを作っていたとき、「なぜかこのタイミングでフォーカスがあるはずなのにない」「フォーカスがないはずなのにある」みたいなことがある。その問題を解決するのに一番単純な方法は、問題が報告された場所にif文を一個足すことだったりする。しかしそうやっていくと管理するべき状態はどんどん複雑化し、また別な予測不能な問題を引き起こすこともある。でもそこでイベントの流れをよく追ってみると、正しい修正は別なコードの分岐のところに適切な処理を入れることだった、みたいなこともあったりする(そしてそこまでわかってみると修正は非常に単純であり、1行で済んでしまったりする)。

ソフトウェア開発ではそういうことがよくあるわけだけど、この種はAIは(まだ?)そこまではやってくれるようにはならないだろう。issueでもdiscussionの流れを見て……とか言ってたし、そういう分析や発見をissueにどんどん足していってもらうのが前提で、そういう全てを見て実装方針を決定したりするのだろうが、そのためには人間がそういうことをコメントに足していく必要がある。これまあ、悲観的に言えば「AIがソフトウェアを書くようになり、残された人間の仕事はデバッグだけ」ってことな気もするが、別な言い方をすると人間の仕事っていうのは「何が正しい動作なのかを考えて決める」「問題に対してどのような修正を行うべきなのかを決める」といったところとも言えるかもしれない。

子が3歳になった

子が3歳になった。誕生日会をやったり、プレゼントをあげたり、まあまあいろんなイベントをこなした。

それと別に保育園でもお祝いをしてもらったようで、アプリ経由で写真が送られてきた。Happy Birthdayと書かれた壁紙の前に撮って記念写真を撮ってもらったもののようだ。Vサインのような手を作っているが、よくみると手の指は3本立てている。

それで思い出したが数ヶ月前のこと。どこかに買い物をしに行った時だったかと思うが、適当に子供の写真を撮ったりしていると、子もポーズを取って撮影されてくれたりする。その中でVサインをしていることがあった。そして、自分はtwo歳なのでこうしているが(子は日本語と英語が混ぜこぜになっている)three歳になったらこうするのだ、といった趣旨のことを述べて指を3本立てて親に見せたのだった。

そのことを覚えていて初志貫徹して、3歳になったから指を3本だろと思ってそうしたのだろうか。それともそれと無関係に保育園の先生から何か言われたので3本指にしたのだろうか。

正直なところその辺のことはよくわからないし、そもそも「2歳だから」と言う理由でVサインをするという発想自体も、保育園の先生か同級生から仕入れた発想だったのか、自分で考えたものだったか、その辺も不明ではある。

それはさておき、こういうことは書いておくことで覚えておきたいなと思っていたので書いておくことにする。


ものすごくどうでもいい余談であるが、この文章を書くにあたって一応Vサインの情報をwikipediaで読んでいたところ、日本における事例が妙に詳しく書かれていて興味深かった。そんなわざわざ特記するほどのことなのかはよくわからないけど。

VS codeを起動しているとファンが回るようになったので

なんか最近になって、VS codeを起動しているとファンが回るようになってしまった。topで調べると関連するプロセスがCPUを100%利用しているので、これが原因だろうとは思う。ただプロセス名が全部codeなので、どんな処理をしている奴がどんな理由でCPUを使っているのか、いまいちよくわからない。仕方ないのでVS codeを再起動するとまあ直ったりするのでそういうふうに使っていた。

今日もそういうことがあったが……いい加減うんざりしたので、調べてみることにした。

VS codeにはprocess explorerが同梱するようになっていたようだ。Ctrl-Shift-Pでコマンドパレットを起動して process explorer を調べて起動すると別ウィンドウが開いて教えてくれる。この機能は正直なところ完成度があまりにも低く、例えばCPUやメモリ使用量でソートしたりできないし、私の使っているLinux版ではCPU利用率が全く取れていないようで全部0%になっていたりするが、必要最低限の機能はある。自分の場合、そこまでプロセス数が多くないのでひとまずこれで乗り切れた。

さて、VS codeのウィンドウからはCPU利用率がわからないのでtopで問題のプロセスのPIDを特定し、それとVS codeのウィンドウにあるPIDの表示を突き合わせたところ、どうもgoplsを起動・管理するユーティリティプロセスが暴走していたということがわかった(ところでChromeでは、メインのブラウザプロセスやblinkを動かすレンダラプロセスの他に、外部コマンドやプラグインを別プロセスで安全に動作させるユーティリティプロセスというものもある。こういうjargonがelectron経由でこういうところにも見られるんだなーとちょっとした感慨がある)。しかも管理対象のはずのgoplsはdefunctしている。何らかの理由でgoplsがクラッシュするか動作を終了したけど、それを管理するユーティリティプロセスの側がその状態をうまく扱えておらずおかしくなっているという問題のようだ。なんかビジーループしているんじゃないだろうか。他のgoplsプロセスは普通に動いているのでVS code全体としての動作には問題が起きていない。要するにCPUの無駄遣いであった。

VS codeのprocess explorerから右クリックで個別プロセスをkillできるので、問題のあるユーティリティプロセスをkillして問題解決。問題の把握方法と単純な解決策を理解できたのは良かった。

まあしかし今後また起きると面倒なので、goplsの問題というべきかVS code側の問題かわからないけど、直ってくれるといいな。あとタスクマネージャ、流石にCPU利用率ぐらい取れるようにしてください……。