スクリプト言語とは何か~これだけは押さえておきたい

2019年6月8日

本稿は、本サイトの講座シリーズの一部である

スクリプト言語について少々検索してみたのだが、怪しげなサイトでデタラメな解説がされていることに驚いた。曰く、「専門知識いらず」だの「スクリプト言語とは習得するのを簡単にするために工夫されたプログラミング言語の総称のこと」だのと、全くのウソが堂々と記述されている。

–> このサイトは非常に評判が悪いようだ。中身が無いにも関わらず、検索で上位に上がってくるため「邪魔」という声が多数上がっている。

このサイトはプログラマ養成のための講座を提供するためのものであるようだが、こんなデタラメを平気で書いているようでは程度が知れるというものである。そこで、ここでは思いっきり初心者向けにスクリプト言語と他の言語の違いを解説してみる。

スクリプト言語とは

まず、スクリプト言語の種類やその用途については説明しない。それらは検索してみればいくらでもある。また、Wikipedia等にも解説らしきものが載っているが、それらとは全く異なる視点であることを最初に断っておく。

まず、私が思うところのスクリプト言語とは、プログラムを記述する際の面倒な部分を、できるだけ実行時に押し付けてプログラミングの手間を減らすということである。しかしこれは、「簡単に書ける」ことを意味はしない。

これを初心者向けに説明するのは非常に難しいのだが、例えていえば、オートマ車を考えてみよう。

オートマ車では、複雑なギアチェンジ操作をせずとも、ほぼD(ドライブ)に入れておけば、アクセルを踏むだけで低速から高速まで走行することができる。実際には、走行スピードによってギアチェンジが車の内部で行われているのだが、運転者はそれを意識する必要は無い。

これに対し、マニュアル車ではギアチェンジの際にはクラッチを切らないとエンストしてしまったりする。坂道発進は特に大変で、うかうかしていると後退してしまったりする。もっとも最近は、オートマしか運転したことが無い人がほとんどだろうから、この説明も理解してもらえるかどうかわからないのだが。。。

ともあれ、この場合、当然ながらオートマ車を選択することになるだろう、日常生活では。

しかし、レースに出る場合を考えてみる。この場合、その車の持つ性能を最大限に引き出したいのである。そういった場所でオートマ車が使われるかと言えば、答えは絶対的に否である。

オートマ車では、操作の簡単さと引き換えに、性能を最大限に引き出せないというデメリットが生じているのだ。スクリプト言語の考え方は、これに非常に近い。

「ある種の簡単さ」というメリットと引き換えに、大きなデメリットを生み出しているのがスクリプト言語である。

メリットとデメリットは表裏一体である

どんなものでも同じであるが、デメリットの無いメリットというものは存在しない。簡単さを採用して、それによるデメリットが発生しないものは無いのである。

しかし、これを推進する方としては、できるだけデメリットを過小評価してもらいたい力学が働いてしまう。そして、学習者がそのデメリットに気づくのは、相当な修練を積んだ後である。

スクリプト言語とそれ以外を同程度経験しなければ、気づくことは無い。

そもそも考えて見てほしい。仮にスクリプト言語のメリットがそれほど大きいのであれば、世の中すべてスクリプト言語になっているはずであるが、そうではない。事実として、例えばGoogle等ではスクリプト言語は、ほとんど使われていないか、あるいはそれを置き換えようとするプロジェクトが多数存在する。

  • 数年前の話なので現在もそうであるかはわからないが、Googleにはrubyのプロジェクトは存在しない。
  • JavaScriptを置き換えようとするプロジェクトは多数ある。ClosureやGWT、Dartなどである。GoogleのエンジニアはJavaScriptを使いたくないのだ。
  • これは想像であるが、おそらくはphpのプロジェクトなどというものも存在しないのではないか?

つまり、Googleにいる技術者のような「賢い」連中は、ほぼスクリプト言語など使ってはいないだろう、あってもほんの少しと想像できる。

理由を考えてみればわかる。彼らにはオートマ車は必要無いのであり、常にすべてをコントロールできるマニュアル車によって、最大限の効率を追求しているのだ。

本物のプログラマはスクリプト言語を使わない、のか?

では、こういった本物のプログラマがスクリプト言語を全く使わないのかと言えば、答えは否だろう。

これもオートマ車とマニュアル車の例えで説明してみよう。レースに出るには、F1カーのようなマニュアル車を使うかもしれないが、そういったF1ドライバーも街乗りではオートマ車を使うだろう。わざわざF1カーに乗ったりはしない。

つまりは適材適所ということである。

彼らはどのような場所でスクリプト言語を使うだろうか?そしてもともと、スクリプト言語はどのような目的のために生まれたのか?私の感じるところでは以下である。

  • 作り、使い、目的が果たされたら捨ててしまっても良いような、ちょっとしたプログラムであること。
  • 「プロジェクト」名さえも与えられず、長期間メンテされるようなものでもなく、大きくとも1万行以下のもの。
  • 実行スピードは全く要求されない。役割を果たしてくれればそれでいい。

もちろん、大きな異論があると思うが、しかしそもそものスクリプト言語の役割がこうであったことは否定できまい。今あるスクリプト言語も上述の考え方に引きずられた欠陥を引きずっているのだ。特に、「プロジェクト」としての大規模な開発では顕著になる。

スクリプト言語の欠陥とは具体的に何か?

これは何度でも強調しておくが、スクリプト言語はオートマ車のようなものである。ちょっとした街乗りには便利で価値があるが、F1レースで勝利するための大きなプロジェクトには、そもそも適してはいない。

それと同じで、複数の開発者、膨大な行数、度重なる仕様変更といった大規模なプロジェクトには全く適していない。そして、何らかの「製品」として世に出すプログラムであれば、たとえ一人で細々と開始しようが、成功したあかつきには、こうした大規模プロジェクトになる可能性が常にある。

であれば、世に出そうとするプログラムには、最初からスクリプト言語を使わないことだ。

さて、スクリプト言語は具体的に何が「悪い」のだろうか?

実行時負担を強いるため効率が悪い

オートマ車は便利さと引き換えに効率が悪くなっている。一口に言えば燃費が悪いのである。これと同じで、スクリプト言語は、その実行時に大きな負担を強いるため、効率が悪い。同じことをさせるにも、おそらくはコンパイラ言語の何十倍・何百倍もの計算量が必要になってくる。

実行時チェックによるため、あらかじめの間違いがわからない

しかし、前項は計算機のスピードが増大すれば済む話しである。より問題となるのは、「動かしてみないとプログラムが誤っているかどうかわからない部分が多すぎる」ということだ。

通常、プログラミング言語には、文法というものがあり、その構文に従って記述しなければ実行する以前にエラーとなる。これが非常に厳格な言語もあれば、緩やかな言語もあり、スクリプト言語はおおよそ後者である。

この種の言語では、実行してみないと、その構文上の間違いさえわからないのだ。

これも例え話をしてみよう。様々なメーラーや文書作成ソフトには、スペルチェッカというものがあるが、これは英単語のスペルミスを報告してくれるものである。相手にメールなり文書を送る以前に単語の間違いを指摘してくれる。

あまり適当な例では無いのだが、スクリプト言語にはスペルチェッカが無く、その他のコンパイル言語等にはあると考えてみて欲しい。

スクリプト言語では、こういった事前のチェック機能が無いため、相手(つまりコンピュータ)に届いてから初めてエラーが判明する確率が高い。

なぜスクリプト言語は実行時にあらゆることを押し付けているのか?

以上見たように、そもそものスクリプト言語の発想というのは、プログラム作成時に厳格なチェックを行わず、それらを実行時に行うことにしたものである。つまり、実行時に押し付ける、実行時まで遅らせるという仕組みである。

そもそも、なぜこのような発想が生まれたのか?

それは、ちょっとした、すぐに捨ててしまうようなプログラムに、わかりきったことを書きたくなかったからだ

例を見てみよう。スクリプト言語なら、「足す」をこんなふうに書くだろう。

def add(a, b) {
  a + b
}

しかし、Javaではこうは書けない。型を決めないといけないのだ。例えば以下のようになる。数字を「足す」であれば、

int add(int a, int b) {
  return a + b;
}

あるいは、文字列を「足す」であれば、

String add(String a, String b) {
  return a + b;
}

どちらが「簡単に書ける」かは明らかである。スクリプト言語に軍配があがる。

しかし、ここで良く考えて見てほしい。「足す」とは何を意味しているのだろう?「加算」だろうか、あるいは文字列の「連結」だろうか?

最初のスクリプト言語のプログラムを書いた人は、このプログラムを数字にしか使わないつもりだったのかもしれない。彼はそのことを念頭に置きつつ、この関数を利用しているのである。

しかし、他の人が見たらどうだろう?この関数が数字を対象にしているのか、文字列を対象にしているのかわからない。あるいは、他の複雑なデータ構造をここに入れたらどうなるのだろう?

同じ関数のJavaのバージョンは明確だ、第一のものは整数しか受け入れないのであり、第二のものは文字列しか受け入れない。

スクリプト言語は、「プログラム中の約束ごと」を人間側が覚えていることを前提としている

前項の簡単な例からわかることは、スクリプト言語では、そのプログラム記述時に「簡単に」書ける、つまりタイピングが少なくて済む代わりに、省略した情報を人間側が覚えておかねばならないということである。あるいは、コメント等で示しておかねばならない。

// これは整数どうしを加算するもの。他のデータに使ってはいけない
def add(a, b) {
  a + b
}

コンピュータ・プログラムというものは、どんな型のデータに対して作用するのか、少なくとも実行時には決定されなければならないのだ。しかし、これをプログラム記述時には省略してしまうのが、スクリプト言語の特徴の一つである。

プログラム記述ではこれを省略してしまったので、逆に「その関数がどんな型に使えるのか」は、その関数を利用する人間側が判断しなければならない。

これを見れば明らかだろう。スクリプト言語とは、そもそも、開発者一人がプログラム全体の構造を把握できるような範囲で利用されてきたものなのだ。Unix上のシェルスクリプトやPerlなどといったものが源流と思われるが、もともとそういう性質のものである。

開発者一人が自分で作った関数を、自分で作ったプログラムから呼び出すような場合に、大きく記述を省略することができて便利だというわけである。

大規模開発にスクリプト言語は適していない

実行時には絶対に必要となる情報をプログラム記述時には省略してしまう。スクリプト言語は、タイピングの手間を減らすために、このような方策を取っており、その反動としては、結局のところ実行時に必要になる情報は人間の頭の隅に置いておくということである。さらに、実行時にデータ型判定を実行環境にも負わせるということである。

コンピュータを扱う以上、データ型から逃れることはできない。スクリプト言語では、「見た目」ではデータ型から逃れられるように見えるのだが、その反動はいかにも大きい。

大規模開発にスクリプト言語が向いていない理由がわかるかと思う。もし、先の関数がJavaの場合のように、タイピングの手間を厭わずに、

int add(int a, int b) {
  return a + b;
}

と記述されていれば、この関数を他者が利用する場合でも、「整数にしか使えない」ということがわかるし、実行環境の方でも「+演算子が使える型かどうか」を判定する必要もなくなる。

もちろん、大人数でなく、一人での開発でも同じである。ある程度大きな量のプログラムをスクリプト言語で記述し、数年後にメンテすることになったらどうだろう?

仕様変更されるプログラムにスクリプト言語は適していない

以前に何かの本で読んだことがあるのだが、「プログラマが避けられないもの三つ」として以下が挙げられていた。

  • 税金
  • 仕様変更

もちろん、この場合、仕様変更されるか否かはプログラマが自分で決めたことでは無いという意味だろう。プログラムが何らかのビジネスに関わっていれば、仕様変更されるのは当然のことである。

しかし、スクリプト言語はこの仕様変更にとりわけ弱い。なぜか?仕様変更によって、既存のプログラム中の関数の引数が変わったり、データ構造が変わったりするからである。この結果、これまで動作していたスクリプト言語によるプログラムにエラーが頻発することになる。

コンパイル時にある程度の整合性をチェックしてくれるコンパイル言語と異なり、スクリプト言語では実行時にしかそれらをチェックしないからである。

仕様変更される大規模開発ではユニットテストが必須

このスクリプト言語の欠点(欠陥と言ってもよい)を補うため、仕様変更を前提とする大規模開発で必須となるのがユニットテストである。

これは、プログラムが正しく動作するかをチェックするプログラムということになる。

もちろん、コンパイル言語の場合でもユニットテストを書くことはあるが、スクリプト言語の場合にはより一層ユニットテストが必要になってくる。例えば、

// これは整数どうしを加算するもの。他のデータに使ってはいけない
def add(a, b) {
  a + b
}

という関数が、仕様変更のあいだに、なぜかしら文字列データに使われてしまっているかもしれない。そんなことがあれば、実行時にエラーになってしまうかもしれない。

そうで無いことを保証するためには、この関数を呼び出している箇所を調べ上げ、整数以外が与えられていないことを確認するテストプログラムを記述する必要がある。

とどのつまり、少ないタイピングで「簡単に」書けることを目指したスクリプト言語ではあるが、結局はその省力化のために失われたものを補完しなければならないのだ。

これなら始めから、いわゆるコンパイル言語を選んだ方がマシというものである。コンパイル言語であれば、こんなことについてユニットテストプログラムを記述する必要はない。

「コンパイルされてから実行される」のウソ、その他

最後に、「コンパイル言語はコンパイルされてから実行される」のウソについて書く。これ自体は間違ってはいないのだが、しかし実際の開発工程においては、スクリプト言語と同様に「書いたらただちに実行できる」のである。

なぜなら、例えばEclipseのような開発環境では、ソースプログラムを記述してセーブした途端にその部分が即座にコンパイルされてしまうからだ。だから、ほとんどいつでも実行可能である。ショートカットキーの「^S」でセーブした後は、即座に開発環境の実行ボタンを押すことができる。

また、こういった開発環境では、先に書いたような「スペルチェッカ」が常時目を光らせており、少しでも構文上問題のあるもの、関数呼び出し時に引数の型が違うといった問題があれば、ただちに知らせてくる。

例えば、以下のように、問題のある箇所をただちに知らせてくれる。

そして、開発環境がプログラムの構造を把握しているため、様々な「リファクタリング」を自動で行ってくれる機能もある。例えば、

  • 関数名を変更すると、同時にそれを呼び出しているすべての箇所の名称も変更してくれる。
  • 関数引数を追加したり削除したりすると、同時にその呼び出し側もすべて変更してくれる。

これらはごく一部であるが、「本来必要であるはずの情報」を省略してしまったスクリプト言語の構文では、このようなことはできない。

なぜなら、タイピングの手間を減らすために省略し、代わりに人間の頭の中に移動してしまった事柄を、コンピュータが指摘することはできないからである

この続編としてダック・タイピングがダメな理由があるので参照されたい。