CodePlex に置いてあった .NET Framework 3.0 時代に書かれたアプリケーションを、GitHub に移行しつつ .NET 5 で動くように 2 週間ぐらい頑張った話を書きます。正直なところ 12 年前に書かれたコードを何とかするのはめっちゃ大変でした。
今回コードの改善を頑張ったので色々な実験場としても使えるようにしています。特に GitHub 周りは新しい機能を使ってみるようにしています。
.NET Framework 3.0 時代に書かれたコードを何とかするのが本当に大変だった(まだ何とか出来てない https://t.co/u5SrISQRCL
— Tatsuro Shibamura (@shibayan) 2021年5月9日
実際には .NET 5 で動くようにはなっていますが、中身は古臭い実装がたくさん残っているので、ツイートの通り全然何とかなっていない状況ですが、4 週間スプリントで改善していく予定です。
さて、今回 .NET 5 と GitHub への移行に当たっては大体以下のような作業を行いました。
特に GitHub での公開のために行った作業は、これまで自分が特に意識せずに行っていた部分なので、今後迷った時のために何をやったかを書きだした感じです。
ぶっちゃけ当たり前のことしかやっていないのですが、作業メモということで。
既存コードの最新化
WPF (.NET 5) と C# 9.0 へ移行
元のコードを見ると WPF は使われていましたが、.NET Framework 3.0 時代で LINQ やラムダ式もまともに使われていない悲惨な状況だったので、その辺りを何とかする必要がありました。
とりあえず最初に .NET Framework 3.0 から .NET 5 へ移行する必要があります。今回は先に csproj
を .NET 5 向けにして、コンパイルエラーを潰していく方法で対応しました。Migration 用ツールもありますが、凝った作りではないので使いませんでした。
ターゲットを最新にすると C# 自体も最新のものが使えるようになるので、Visual Studio と ReSharper が大量にメッセージを出力するようになります。元がラムダ式ではなく匿名デリゲートが使われている状態だったので、R# を使って一括で整形しなおしました。
基準となるコードフォーマットを指定するために .editorconfig
を用意しておきます。昔は ReSharper の設定ファイルを Repo において共有していましたが、最近は .NET Runtime の .editorconfig
を使うようにしています。
C# コーディングスタイルは dotnet/runtime にある .editorconfig が事実上標準と思っているので、これをコピペして使うのが楽だと思う https://t.co/u3uz2W0fn1
— Tatsuro Shibamura (@shibayan) 2021年5月14日
一部 .editorconfig
上で suggestion になっている部分などは若干弄っています。 Visual Studio や R# の .editorconfig
生成機能はまだまだという感じでした。
.NET 5 と互換性のない API を使っていた部分を修正
.NET 5 は .NET Framework と高い互換性を持ちますが、一部のプラットフォームに依存する API は動作しなくなっています。以下のドキュメントにある通り、呼び出すと常に例外が投げられる API が存在しています。
コンパイル時に警告として出力されるのでわかりやすいですが、呼び出してしまうと常に例外が投げられるので当然ながら正しく動きません。無視せずに急いで対応します。
今回は Thread#Abort
が引っ掛かりました。そもそもスレッドを直接使うなという話ではあるのですが、流石に Task
ベースに置き換える時間が無かったので呼び出しを削除して対応しました。
基本的にはスレッドを直接使う部分を全て Task
を使って再実装する方向でいます。
.NET Remoting を使っていた部分を gRPC で再実装
一番の問題点は .NET Remoting を使っていた部分をどのように再実装するかでした。.NET 5 では .NET Remoting が危険な機能として扱われていて、実装されていないので別の手段で実現する必要がありました。
.NET Remoting は単なる RPC なので今回は gRPC を使うことにしました。正直なところあまり使う機会が無かったので、ちゃんと使ってみるかという意図もありました。
.NET 5 で gRPC を使うためのドキュメントはかなり揃っているので難しいことは特にありませんでしたが、ビルド時のコード生成系は IntelliSense の動作が不安定なのであまり好きではないです。
今回はクラスライブラリに proto ファイルを置いて、サーバーとクライアントで共有させるようにしています。特別なことは行っていませんが、バージョニングなどは検討する必要がありそうです。
動作しない実装、過剰な機能を削除
歴史的経緯からほぼデッドコードとなっていた部分は早々に削除しました。
ただし GitHub 上にはコミットログとして残しておきたいので、リポジトリへ最初に全て追加した後に削除する PR を作る形で対応しました。
GitHub 上でリポジトリを構築
Milestone で Sprint 毎のタスクを管理
今回のプロジェクトでは 4 週間のスプリントで計画したので、期間ごとに Milestone を作成してタスクを管理するようにしています。Milestone は期間が入力できるので、Project より適していると考えました。
タスクの着手時に Milestone に追加して、進捗の管理も同時に行います。
本来であれば Project で Auto Kanban などを使いたかったのですが、現状の GitHub Project は使い勝手が悪く感じたので Milestone と Label を使った運用にしています。
この辺りは有名どころのリポジトリを調べてみたのですが、使い方がバラバラだったので自分の中で今回のプロジェクト規模でしっくり来た方法を選びました。
GitHub Actions で build と lint を自動化
GitHub Actions を使って Pull Request が作成されたタイミングで各種チェックを行うのは当たり前かもしれませんが、設定をちゃんと行っているので書いておきます。
C# のコードフォーマットのチェックは dotnet-format
を使って行っています。
タグを作成したタイミングでリリース用のアプリケーションビルドを行うようにしていますが、まだ GitHub Release へのアップロード周りは自動化出来ていないので今後の課題となります。
Branch protection rule を設定
これもまた GitHub を使っている人には当然すぎる話題ではあると思いますが、Branch protection rule を設定してデフォルトブランチの保護を行っています。
Reviewer 周りのチェックは入れていませんが、ステータスチェックと管理者も対象になるように設定しています。管理者を対象にしないとデフォルトブランチへの直接 git push
という事故が起こります。
自分は新しいリポジトリの場合は設定をちょいちょい忘れてしまいがちです。
Pull Request の auto-merge を有効化
Azure DevOps には Set auto-complete という名前で同じ機能が昔からありましたが、GitHub でも最近は使うようにしています。
PR を作成後に auto-merge を設定すれば、Branch protection rule のパス後に自動でマージしてくれます。
今回のプロジェクトでは Windows 上で GitHub Actions の各種 Workflow を動かす必要があり、結構ステータスチェックに時間がかかってしまうことが多いので使っています。
Dependabot で依存関係の更新を自動化
C# プロジェクトは一般的には Node.js のプロジェクトに比べると依存関係が少ないので、アップデートは手動でも対応できることが多いですが、このプロジェクトでは Dependabot を有効化しています。
# To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "nuget" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "daily"
基本は package-ecosystem
に nuget
を指定するだけの簡単設定です。GitHub の Dependency graph ページから簡単に作成できるので、サクッと試してみるのは良いと思います。
Release 作成時に Discussion を同時に作成
少し前から Release を作成する時に、同時にその Release に対応した Discussion を作れるようになっているので、実際にプレビュー版のリリース時に試しています。
Discussion が作成された Release には Reaction と Discussion へのリンクが追加されています。
実際に作成された Discussion が使われるかどうかは別の話ではありますが、特定の Release で問題が発生した場合などに Issue や Discussion に散らばってしまうのを防げる可能性もあるのかなと思っています。
Discussion 機能自体は Issue が Q&A で埋まるのを避けることが出来るのでかなりおすすめです。最近は Issue で Q&A が作成されると即座に Discussion に変更するようになりました。
行わなかったこと
Issue / PR Template などを作っていませんが、最初から用意すると破綻しそうなので止めておきました。時が来たら改めて用意するつもりです。
Code of conduct や Contributing などは GitHub から簡単に作れるようになっているのですが、Private の時には Community profile 自体が表示されなかったので後回しにしました。