滅多にコラム的な事は書かないけど、ちょっと今日は書いてみたい。オープンソースプログラマとしてソースコードのリリースをしていく上での判断の仕方について。

オープンソースソフトウェア(クローズドでももちろんそうでしょうが)を書いているとよくわかるのだが、コードというものは常に進化し続ける。関数の引数が変わったり、内部動作が微妙に変わったり。そう言った変更自体はバグの修正やそれまで想定していなかったユースケースをカバーするという命題においては常に推奨されるべき事ではある。そして、その進化し続けるコードに古いバージョンとの互換性を100%求めるのは現実的ではない。

ただしそれを踏まえた上で、我々オープンソースプログラマーは最大限自分がリリースしているソフトウェアに依存しているソフトウェアへの突然の変更を強いる事を避けなくてはいけない。これが私の主張。

例えばとあるライブラリがあったとして、そのライブラリに依存する他のソフトウェアが色々あったりするわけです。ライブラリAとソフトウェアXとでもしましょうか。XはAのfoo(int x, float y)という関数に依存してると仮定しましょう。これを、ライブラリAの作成者である自分がある日はたと「あ、これだと動かないケースがある!」と気づきます。今までの通常のケースであればそれで動いていたけれども、潜在的なバグがある。こういう場合直さないわけにはいかない。

具体的には元々
int foo( int x, float y )
だった関数のシグナチャを
int foo (int x, float y, double z)
にしたいと仮定します。

さて、ではデベロッパーである私はどうするべきか。ここは具体的にコード自体をどうこうする、というより前にまずポリシーとして「可能な限り現存の利用者のソフトウェアを壊さない」というスタンスこそが正しいと主張したい。

なぜこれを強く主張したいかというと、これがオープンソースで、誰がどこで使っているか分からないからだ。バグは存在するのかもしれない、でも関数のシグナチャのような物が変わってしまったら、それまで安定して運用できていた人達にはアップグレードする事イコール自分の今まで使っていたソフトウェアを書き換える、という事になってしまう。当然コストも発生する。

さらに問題なのは、この非互換APIがリリースされたバージョンに、他のバグフィックスが入っていた場合だ。ライブラリAを使っているソフトウェアXは、そのfoo() という関数は今まで問題なく使っていて、別に今回のフィックスでは特に恩恵を被れないのに、その他のバグフィックスが欲しい場合はどうするの?これではいくら「改良」とうたわれても、正直「余計なお世話」でしかない。しかもそれが断続的に行われるライブラリだったら使う気をなくす事請け合いだ。

だからこそ我々オープンソースに何かを提供している人達はコードの進化とリリースのタイミングや実際の変更の適用の仕方に一定のバランス感覚を持たなければならない。

前置きが長かった。じゃあ実際どうすればいいの?ということだが、そこはとにかく「ユーザーにチョイスを与える」が正しい。

例えば一番シンプルな方法では、普通に新しい関数を作る:
int foo_XXX (int x, float y, double z) /* 別にXXXはなんでもいい。"new"でもなんでも、分かりやすい名前にする */
そして、Changes(更新履歴を記したファイル)やドキュメントに、ちゃんと書いておく:
* バージョン x.xx.xxより、foo()に変わるfoo_XXX()を実装しました。 ◯◯◯という潜在的なバグを回避するにはfoo_XXX()を使用してください。 なお、foo()はバージョンy.yy.yyにはライブラリより削除されます
これだけでいい。それまでfoo()を使っていたライブラリは問題なくそのままアップグレードできる。もし新しい機能を使いたければ、ライブラリアのバージョンx.xx.xx以降に対応した新規バージョンのソフトウェアXをユーザーに特に混乱を与えずに後からリリースすることができる。

関数の名前が変わると面倒な事がある。じゃあその場合どうするか?これまた色々なやりかたがあるが、先ほど言ったチョイスさえあればいい。例えばconfigureやperl Makefile.PL時にユーザーにどっちを使いたいか聞く、という手もある。Cだと自分は手法に疎いのでちょっと具体的なベストプラクティスは思いつかないが、LL言語ならば、ビルド時に適正な方を選ぶようにする事や、もしくは暗黙のうちにコンパティビリティAPIを呼び出す事も簡単にできるはずだ。

色々と手法はあるわけだが、どの方法も駄目だったら、そこは素直に変える。でも変えるタイミング、そして下位互換のAPIを削除するタイミングは考慮を要する。Perl本体の場合のポリシーは特にメジャーバージョンじゃないと変更しちゃいけないとかそういうルールはない事が多いけど、ユーザーに対する影響が大きい場合は必ず年単位で先にアナウンスされてる。しかも具体的には

  1. MLでのディスカッション
  2. ML等でアナウンス
  3. コードに警告を発するコードを挿入
  4. バージョンアップの時に最終的に変更/削除
という手順を取ってる。まぁ言語とライブラリでは扱いは多少違って当然だとは思うが。

ともあれ、ユーザーの事を考えるとやはり警告を発するバージョン→最終的な変更が入ったバージョン、というステップを入れるのはほぼ確実に守るべきスタイルではないだろうか。

会社の都合で「アップグレードさせる」という手順を踏ませる決断をできる、そもそもオープンではないライブラリならギリギリわからないでもない。しかし「オープンソース」はただコードをオープンしているのではない。その「開発過程」をオープンにしているものだ。だからコードレポジトリが重要だったり、MLが重要だったりする。わざわざオープンソース開発をするのならいつのまにかAPIが変わるような事はしてはいけないと思うのです。