Blog

リストをn個ずつのリストに分割する

[a, b, c, d, e, f, g, h]というリストを3個ずつに分割して、最後の要素は3個以下の[a, b, c], [d, e, f], [g, h]というリストを得たいとする。そのときC#でどう書くかという質問の答えとして人気があるのが、以下のLINQである。

stackoverflowでは二つの質問(Split a List into smaller lists of N size [duplicate]Split List into Sublists with LINQ)の合計で900以上のup voteを得ている。

しかし、この方法は性能の点では最悪である。まずリストの要素数だけIndexとValueを持つオブジェクトを生成している。オブジェクトの生成はメモリと速度両面で、とてもコストの高い操作である。それと比べると小さなコストではあるが、要素の数だけ除算と比較を行うのも性能上よくない。

速度にこだわるのならそもそもLINQを使わなければいいのだが、もしLINQで簡潔に書きたいのなら、以下の解はどうだろうか。

以下はBenchmarkDotNetで取った、1000要素のintの配列を3個ずつに分割するベンチマークの結果である。最初に挙げたものがSplitter1、これがSplitter2である。Meanが平均値で、それ以外は測定誤差を表している。Meanを見るとSplitter2が3.5倍以上速いのがわかる。

BenchmarkDotNet=v0.13.1, OS=Windows 10.0.19044.1766 (21H2)
AMD Ryzen 7 3800X, 1 CPU, 16 logical and 8 physical cores
.NET SDK=6.0.106
  [Host]  : .NET 6.0.6 (6.0.622.26707), X64 RyuJIT
  LongRun : .NET 6.0.6 (6.0.622.26707), X64 RyuJIT

Job=LongRun  IterationCount=100  LaunchCount=3  
WarmupCount=15  
MethodMeanErrorStdDev
Splitter189.94 μs0.351 μs1.779 μs
Splitter224.04 μs0.100 μs0.517 μs

ただし、.NET 6にはChunkという標準のメソッドが追加されているので、.NET 6以降が対象ならChunkを使うべきだ。Chunkを加えて上と同じベンチマークを実行すると結果はこうなる。Splitter2と比較しても4,000倍以上速い。

MethodMeanErrorStdDev
Splitter188,650.102 ns245.2557 ns1,251.8625 ns
Splitter223,481.503 ns117.8934 ns600.6975 ns
Chunk5.609 ns0.0198 ns0.0984 ns

dotnet formatコマンドをStyleCop.Analyzersと組み合わせて使う

この記事では、.editorconfigを使用したdotnet formatコマンドの使用方法とその限界について説明し、StyleCop.Analyzersでそれを解決する方法を紹介する。また、ソリューション内のすべてのプロジェクトにStyleCopルールセットを適用する方法についても言及する。

"dotnet formatコマンドをStyleCop.Analyzersと組み合わせて使う" の続きを読む

よいコミットの作り方

この記事では、gitに代表されるバージョン管理システムにおける、よいコミットの作り方について以下の項目を説明する。

  • コミットを作成する目的
  • 目的別のよいコミットの作り方
  • よいコミットメッセージの書き方
  • 「git コミットメッセージ」で検索すればすぐわかること

コミットを作成する目的

ソフトウェア開発では、時間とともに成果物(文書やソースコードなど)に変更が加えられていく。gitを含むバージョン管理システムはその変更を管理するものであり、その時間の流れのある一瞬を切り取る機能を提供する。それがコミットである。

特定の瞬間を切り取る目的には以下の三つがある

一つ目は、ソフトウェアが正常に動作する瞬間を切り取るためである。そこからの変更でバグが生じたら、直前のコミットに戻れば正常な状態を回復できる。正常なはずのコミットにバグがあったら、さらにその前のコミットに戻ればよい。そこに同じバグがなければ、そのバグはそのコミット間に入ったと特定できる。

二つ目は、その一瞬にメッセージを付けて記録するためである。コミット間で生じた変更に、それを理解するためのメッセージを付けて記録する。自分の作業を振り返るときにだけでなく、ほかのメンバーに自分の仕事を理解してもらうときでも、そうしたコミットは役に立つ。

三つめは、チームのメンバーと変更を共有する瞬間を決めるためである。バージョン管理システムにおいて、他のメンバーとの変更の共有にはコミットが必要である。gitではさらに共有リポジトリにpushする必要があるが、どうあれ、他のメンバーと変更を共有するにはコミットが必要だ。

よいコミットとは

そうしたコミットの目的を考えると、よいコミットの基準、つまり変更され続ける成果物をどのタイミングで切り取るのがよいかがわかりやすくなる。

正常な状態を切り取るためのコミット

正常な状態を保存してバグの追跡を容易にするコミットは、当然正常に動作すると見なせるタイミングで行う。バグを追跡するためにさかのぼるときのことを考えると、コミットには複数のバグが入らないよう配慮するべきだ。

コミットを作るタイミングは、前のコミットから複数バグが入る可能性の低い小さな変更を加えた後の、次に正常に動作するときがいいだろう。この観点では、バグが入る余地のまったくないコミットは、さかのぼる手間を増やすだけの無駄なコミットとなる。これから述べる目的に該当しないなら本当に不要なので、むやみにコミットを増やすべきではない。

ある時点にメッセージを付けるためのコミット

ある一瞬にメッセージを付けるためのコミットは、当然そのメッセージと関係する変更だけが含まれる状態がそのタイミングである。そしてそのメッセージは、自分にとっても他人にとってもわかりやすくなければいけない。

どの瞬間にどのようなメッセージを付けるのがよいのかは判断がわかれるところであり、明確に基準を示すことは難しい。

少なくともメッセージは一言ですむようにするべきだ。「AとB」のようになるなら、それは二つのコミットに分けるべきである。また、メッセージを具体的にしてコミットに含まれる変更が小さくなるようにするべきだ。

あいまいなメッセージによってあらわされた、変更が数千行にも及ぶようなコミットは誰の役にも立たない。ただし変更が数千行に及んだとしても、「メソッド名AをBにする」といった具体的なメッセージが付くのなら、そのコミットは有用である。

ほかのメンバーと変更を共有するためのコミット

ほかのメンバーに自分の変更を伝えるために行うコミットは、チームの開発プロセスが求めているタイミングに行う。たとえば、GitHub issue等の課題管理システムを用いているなら、自分に割り当てられた課題を解決したらコミットを作成して課題と紐づける。

この目的のコミットでは、ある時点にメッセージを付けるコミットと同様に、具体的なメッセージを付けて変更を小さくする必要がある。人に自分のコミットを見てもらうのだから、自分でも見る気がしないようなコミットを作ってはいけない。もちろん、他にチームのルールがあれば、それに従わなければいけない。

よいコミットメッセージとは

コミットが一瞬一瞬を切り取る物だとしたら、そこに付与されるメッセージは、前回と今回の間に生じた変更を説明するものか、切り取るきっかけとなった出来事を説明するものか、その両方になるはずだ。それをコミットメッセージの最初の一行で伝わるように書く必要がある。

gitをはじめ多くのバージョン管理システムは、もっとも簡潔なコミットログの形式ではメッセージの最初の一行しか表示しない。その形式が最も多くのコミットを俯瞰できるので、その状態で各コミットが何のためのものか理解できるようにしたほうがよい。

gitでは最初の一行に50字以内の概要を書くことになっているが、その一行だけで上記の内容が伝わるようにする。50字より長くなってもかまわないが、人間には一目で読み取れる情報量に限界があるので長すぎてはいけない。

チームでバージョン管理システムを用いているとき、他のメンバーの目に触れる自分のコミットメッセージは、他のメンバーへのメッセージであり、チーム間のコミュニケーションの重要な構成要素である。コミットログはチーム内コミュニケーションのための掲示板であり、そこに書かれる情報は簡にして要を得たものでなければならない。

具体的にどう書くのがよいかは、開発しているプロダクトやチームの構成によって異なるが、前回と今回の間の変更を説明するコミットメッセージでは、「何を」「どうする」コミットなのかを書くのがよいだろう。「どうする」は簡単な動詞(日本語なら動作性名詞)でよい。その候補は以下の通りだ。

  • 追加 (Add)
  • 実装 (Implement)
  • 作成 (Create)
  • 修正 (Fix)
  • 更新 (Update)
  • 変更 (Change)
  • 改善 (Improve)
  • 削除 (Remove/Delete)
  • などなど

コミットメッセージを何語で書くかについては、メンバーが全員日本語話者なら日本語でよい。英語の得意でない日本人が50字以内で短く書いたメッセージは、英語話者から見ると多くの場合意味不明である。それなら日本語で正しくメッセージを残したほうがよい。

メッセージの例としては、「署名アルゴリズムのバグを修正」「ユーザ一覧表示速度の改善」「企業テーブルの変更を反映する更新」「ConnectionCraetorをConnectionBuilderに変更」といった感じである。

多くのプロジェクトで行っているように、対応するissueを識別するためのタグや、サブシステムやモジュールを識別するためのタグを先頭に付けると、短い文字数で変更の範囲を絞り込むことができる。

その必要があり、かつ短く書けるのなら「何のために」を付けるのもよいだろう。長くなるようなら、詳細は二行目以降(gitなら空行を挟んだ三行目以降)にゆずればよい。課題管理システムに紐づくコミットでは、対応する課題のコメント欄に詳細をゆずってもいいだろう。

まとめ

コミットとは何なのかからはじめて、どうすればよいコミットを作れるのかを述べてみた。この記事は、今参加しているチームのメンバーに、コミットの粒度とメッセージの品質を改善してもらうために書いた。記事内には「こうすべき」と書いてはあるが、「私がそうしてほしい」と思っているに過ぎない部分が多々あるので、その点は容赦してほしい。

システムトレイアプリケーションをWPFとMVVMで実装する

この記事では、WPFとMVVMとによるシステムトレイアプリケーションの一実装について解説する。ソースコード全体はGitHubリポジトリにある。

この実装は二つの特徴がある。まず、よく使われているWPF NotifyIcon使っていない。ライセンスのCPOLが、どのOSSライセンスとも互換性がないからだ。また、MVVMパターンを採用していてコードビハインドがない。

"システムトレイアプリケーションをWPFとMVVMで実装する" の続きを読む

UnityアプリケーションをNamedPipe経由で制御する

以下のようなWindowsアプリケーションから、UnityアプリケーションをNamedPipeを介して制御する方法を紹介する。なおソースコード全体はGitHubのリポジトリに置いてある。

"UnityアプリケーションをNamedPipe経由で制御する" の続きを読む

C#でロックフリーなメモリープールを自作する

基本的にシングルスレッドでしか使わないライブラリを、速度を落とさずにスレッドセーフにするために、ロックフリーなメモリープールを自作した話を紹介する。この記事はC# その2 Advent Calendar 2020の18日目の記事である。

"C#でロックフリーなメモリープールを自作する" の続きを読む

[Unity] Physics.OverlapCapsuleの使い方

2021-09-06更新: 対象のGameObjectのrotationと、CapsuleColliderのdirectionを考慮するように大きく修正した。

Capsule and Cube Collider

上記のようにCapsulColliderと重なった2つのColliderを取得するには、Physics.OverlapCapsuleを使用する。このメソッドは、カプセルの位置と大きさを受け取り、そのカプセルと重なっているすべてのColliderを返す。

"[Unity] Physics.OverlapCapsuleの使い方" の続きを読む

厳格なJSONパーザーの作り方

僕の作ったJSONパーザーのDynaJsonはとても厳格にできている。RFC 8259に準拠しているものはすべて受理するし、そうでないものは二つの例外を除きすべて受理しない。

受理する例外は、ケツカンマと02-02のような0で始まる数字である。前者を受理するのは実用性のためであり、後者を受理するのはDynamicJsonとの互換性のためである。

JSONの文法はとても単純だが、不用意にパーザーを実装するとRFCに準拠したJSONを受理しなかったり、非準拠のものをエラーにできなかったりする。パーザーを実装する際の地雷ポイントはParsing JSON is a Minefieldによくまとまっている。

"厳格なJSONパーザーの作り方" の続きを読む