ダック・タイピングがダメな理由

2019年6月13日

本投稿はスクリプト言語とは何か~これだけは押さえておきたいの続きなので、先にそちらを読んでいただきたい。

ダックタイピングとは何か?

Wikipediaにダック・タイピングという説明があるのだが、正直なところ、これを書いた方は何もわかっていないと断じてよい。

まず、「これによりポリモーフィズム(多態性)を実現することができる。」としているが、ダック・タイピングと多態性は全く何の関係も無い。明らかな間違いなので注意されたい。

さらに、ここでは専門用語を駆使したやたらに難しい説明になっているのだが、ほぼ中身などは無いので無視してよい。

※ただし、「Javaは言語構文レベルで動的型付けをサポートしないが、リフレクションを用いることで、ダック・タイピング相当を実現できる」としているのは正しい。

※また、検索してみると、他のページで「Javaのinterfaceを使ったダック・タイピング(のようなもの)」を紹介しているページがあるが、これは全くの間違いなので注意されたい。このようなものを読むとかえって混乱してしまう。

ともあれ、ダック・タイピングとは、単に「何の事前打ち合わせ・準備も無く、その場でオブジェクトの特定のメソッドを呼び出す」というだけの話だ。

このWikpediaページには、こんなRubyの例がある。

 def test(foo)
   puts foo.sound
 end

 class Duck
   def sound
     'quack'
   end
 end

 class Cat
   def sound
     'myaa'
   end
 end

 test(Duck.new)
 test(Cat.new)

ここでのポイントとしては、

  • DuckとCatという二つのクラスは相互に何の関係も無く、同じ名称のメソッドsoundを持っている。
  • testメソッドに与えられる引数fooには、それがどんなクラスのオブジェクトなのかという事前知識が一切無い。

この状態で、二つのオブジェクトをtestというメソッドの引数にしてsoundを呼び出すと、それぞれのメソッドが呼び出されるという。

これはスクリプト言語のみをやっている方であれば、いわば当たり前のことなので何の不思議も無いところだろう。

しかしこれには、便利である反面大きなデメリットがあるのだ。

ダック・タイピングの便利さと危険性

ダック・タイピングは便利である。便利であるからゆえ非常な危険性を伴う。

便利さというメリットを得て、デメリットが生じないものは存在しない。メリットとデメリットは表裏一体なのである。しつこいようだが、これはスクリプト言語とは何か~これだけは押さえておきたいで書いたことと同じだ。

  • メリットだけでデメリットが生じないものは存在しない
  • 仮にメリットだけであるならば、世の中すべてそれになっているはずである

ダック・タイピングの便利さとは、単純に「その名前のメソッドを持っているなら、それを呼び出せる」という点だ。このメソッドを呼び出す側にとっては、事前に何の知識も不要である。先の例のように、単純に渡されたオブジェクトがsoundというメソッドを持っているなら、それが呼び出せる。便利ではないか。

しかし、その危険性は大きい。もし先のtestメソッドの引数として、soundメソッドを持たないオブジェクトを指定したらどうなるのだろうか?当然エラーになり、そのままでは処理がストップしてしまうだろう。

もしtestのようなメソッドが多数あり、プログラマも多数であり、これが巨大なプロジェクトだとしたらどうなるだろうか?

間違いが大量発生するだろう。そして、その間違いは、たまたまsoundメソッドを持たないオブジェクトが渡されたその時点まで発覚しない。

そうでないことを保証するために、スクリプト言語ではより一層ユニットテストが必要になってくる。

ダック・タイピングとはこのようなもの

ダック・タイピングでは、どんなオブジェクトでもtestメソッドに入れることができてしまう。本来はsoundというメソッドを保持するという「資格」をもったオブジェクトが必要であるのに、とりあえずtestメソッドのドアを通れるようにしてしまう。

そして、部屋の中でsoundという質問を受けた時に、初めてその資格など無いことが発覚する。個人的には、これがまっとうな考え方とはとても思えない。

再度だが、大きなプロジェクト、大人数での開発では、プログラムの動きによってどんなオブジェクトがtestメソッドに放り込まれるかはわからない。誤りが生ずる可能性が大きくなり、それは実行時にのみ発覚するのである。

ダック・タイピングを健全に行う方法、その1

では、ダック・タイピングを健全に行う方法を考えてみよう。もちろん、この方策をとったとしても「ダック・タイピングはダメ」なのだが、これを考えてみれば、いかにダメであるかが理解されよう。

つまり、testのようなメソッドには詳細にコメントをつけるということである。

 # fooはsoundという引数無しのメソッドを持ち、呼び出し結果は標準出力への一行出力であること
 def test(foo)
   puts foo.sound
 end

あるいは、もっと複雑なメソッドであれば、こんなところかもしれない。

# aオブジェクトは引数として名前文字列、住所文字列をとるsetPersonというメソッドを持ち、
# さらには、getAgeという引数無しのメソッドを持ち、整数での年齢を返す。
# bオブジェクトは、仕事を表すが、引数無しのgetTermで日数を表す整数を返し、
# 引数無しのgetSalaryは報酬金額整数を返す。
def sample(a, b)
 ....
end

おおよそスクリプト言語というのは、引数として渡される仮引数の型の指定方法が無いため(例外もある)、引数として実際に渡されるものが何なのかわからない。

そのため、上述のように実引数として渡されるオブジェクトがいかなるものであるかは、上述のように逐一コメントで表さなければならないはずである。

これが健全なスクリプト言語の記述方法というものであろう。これがJavaのような強い型付けの言語とは決定的に異なる点である。

スクリプト言語では、失われてしまったもの、プログラマの頭の中に移動してしまったものをコードで表現することはできず、コメントで表現する他はない

ダック・タイピングを健全に行う方法、その2

ダック・タイピングを健全に行うために重要なもう一つとしては、対象とするメソッドが存在しなかった場合の方策を常に考えておくということだ。

強い型付けの言語では、少なくとも「メソッドが存在しなかった」という状況はあり得ない。これらのケースは静的に排除済だからだ。

もちろん、そのような言語でも実行時エラーは発生するが、ダック・タイピングでは実行時エラーの可能性は格段に高まる。ダック・タイピングを使うと共に、そのような事態にいかに備えるかが示されていなければならない。

巷に見られる多くの解説では、この点を省略してしまっているのだ。メリットだけを強調する広告のようなものとみなして差し支えない。

ダック・タイピングが有用なケース

とは言ってもダック・タイピングが有用なケースも当然ある。それは、

  • プログラマの数が一人でコードの規模がせいぜい1万行以下

というケースだ。この場合には、一人のプログラマの頭にすべてが入っており、引数を間違えることも少ないだろう。こうしたときには、いちいちオブジェクトの型を記述したりする必要が無くて便利だということだ。

そもそも、スクリプト言語はそのような利用形態で使われてきたものなのである。大規模な開発には全く向かない。

大きな社会では規律が必要になる

人間社会ではどこもそうなのだが、大きくなるほど規律は厳しいものになる。

家族や二三人の友人の間であれば、「なぁなぁ」の取り決めでうまく行くだろう(そうでないことも多々あるが)、しかし、これが町レベル国レベルになった場合はどうだろうか?当然規律が必要になってくる。

ダック・タイピングとは、こういった規律の無い状態にたとえられる。というよりも、これはそもそもスクリプト言語がこういった1人2人といった環境で使われてきた歴史的事情に由来している。

少人数の世界ではうまく行っていたものだが、大人数の社会では、その「なぁなぁ」ぶりが問題になってくるのは明らかだろう。