「配列のすべての要素が条件を満たすならtrueを返す」関数をあなたが作成するとして、配列が空配列[]
だった場合、結果はtrueにすべきでしょうか、falseにすべきでしょうか。
答えはtrueです。
「一つも要素がないならば、一つも条件を満たせないのだから、falseにすべきでは?」と考える人もいるかもしれませんが、「すべての要素が条件を満たす」という要件に「最低でも一つは条件を満たす」は含まれていません。
コンピュータサイエンスを学んだ人ならば「ド・モルガンの法則」を知っていると思います。この法則は非常に便利で、これを知っているプログラマーは、直感的でない論理式をわかりやすく置き換えるのに活用しているでしょう。
この法則は、以下の2つが可換であることを示しています。
(A and B and C and D and ...)
not (not A or not B or not C or not D or ...)
後者に置き換えて考えてみると、空の配列の中には条件を満たさないものは含まれていません。空だから当然です。そう考えると、配列が空だった場合にfalseを返してはおかしいのです。
ですから、正解は「trueを返す」となります。
プログラミング言語の論理式(if文の()の中に書かれるような式のことです)は、ド・モルガンの法則に従います。
論理値(true or false)を返す関数は、ド・モルガンの法則に従わないと、プログラミング言語上で扱いにくい、違和感のある、ミスを招きやすい、バグを生み出しやすいものになってしまいます。
実際、多くのプログラミング言語において、「配列のすべての要素が条件を満たすかどうか」をチェックする関数は空配列を渡された時trueを返します。(ChatGPT-4にお手伝い頂きました)
array = []
all_positive = array.all? { |n| n > 0 } # true
const array = [];
const allPositive = array.every(n => n > 0); // true
var array = new int[0];
bool allPositive = array.All(n => n > 0); // true
int[] array = new int[0];
boolean allPositive = Arrays.stream(array).allMatch(n -> n > 0); // true
array = []
all_positive = all(n > 0 for n in array) # true
要件を確認するということについて
この関数の作成を他人から依頼された場合には、ひょっとすると依頼者の意図は「空配列の場合にはfalseを返して欲しい」だったり「例外をスローして欲しい」だったりするかもしれません。しかし、その場合には、要件自体が「配列のすべての要素が条件を満たすならtrueを返す」だけではなかった、と考えなくてはなりません。
- 「配列のすべての要素が条件を満たすならtrueを返す」
- 「空配列の場合にはfalse(又は例外)を返す」
この2つは異なる要件であり、分離して捉えた方が良いプログラムが出来上がります。このような機能を混在させて実装すると、再利用性の低い、勘違いを生みやすいソースコードになってしまうでしょう。
ですから、あなたが作る「配列のすべての要素が条件を満たすならtrueを返す」関数は、空配列の時にtrueを返す方が良いコードとなるでしょう。
余談
この話を書くにあたって、「全ての要素が条件を満たすならtrue、但し、空配列の場合にはfalseを返して欲しい」という要件には、具体的にどのようなものあるか考えてみました。
しかし、うまい例が一つも思いつきませんでした。
例えば「工場の製造データに例外値を発見したらアラートを出す」プログラムがあったとします。
その場合のロジックは
「配列の全てのデータが条件を満たす」
になるはずです。上記の条件がfalseの時、アラートを出せばよいでしょう。
そして、製造データが一つもない時にfalseが返ってきてはダメでしょう。なぜなら、例外値は一つもないからです。
つまり、ド・モルガンの法則に従うことが、正しい実装となります。
もしこの時、「製造データが1つもないのはそれはそれでアラートが必要」となったとします。
しかしこれは、先ほどのチェックとはまた別のチェックであり、別のチェック機能として分離して実装した方が良いでしょう。
恐らく、製造データが1つもないことに対してアラートを出すというのは、「例外値を発見する」とは別の要件です。別の要件ですから、チェックするタイミングやチェックを実行する条件自体も別になってくる可能性が高いでしょうし、出力するアラートも別のものになりそうです。
上記をごちゃまぜにして単一のチェック処理としてしまうと、チェック目的が曖昧になり、今後問題となるかもしれません。
そう考えると、プログラミングとは無関係に、要件定義レベルでもこの考え方は正しいと言える気がします。
そもそも要件定義では、お客様の要件を論理的に整理する必要があるのですから、お客様の言う「全ての要素が条件を満たすならtrue」がどういう意味か確認するのは大事だとしても、あなた自身が「全ての要素が条件を満たすならtrue」の意味の中に「空配列の場合にfalseを返す」とか「例外を返す」という意味を含むこともある、というような捉え方をしてしまっては、最終的に定義された要件が論理的に破綻してしまう可能性があるかもしれません。