静的型付け言語と動的型付け言語。どっちがおすすめ?メリットとデメリット、おすすめ言語

プログラミングの基礎知識

動的型付け言語の方が楽なのになぜ静的型付け言語?

プログラミング言語には型をちゃんと宣言したり型変換を明示的に書く必要がある「静的型付け言語」とインタプリタが自動で変数の型や関数の戻り値を解釈して冗長な処理を書かなくて済む「動的型付け言語が」あります。

PythonやPHP,Rubyなどの動的型付け言語でコーディングしていると静的型付け言語は冗長で効率が悪い気がします。しかし大きなプロジェクトではJavaなどの静的型付け言語が採用されることが多いです。また意外と静的型付け言語を好む人が多かったりします。私の周りの話になってしまいますがフロントエンドも動的型付け言語のJavaScriptよりも静的型付け言語のTypeScriptを採用するケースがほとんどです。

私がまだエンジニアになる前にプログラミングのインターンをしていたときは動的型付け言語のほうがコーディングが楽にもかかわらず大規模な開発では静的型付け言語を採用するケースが多いことに疑問に思ってました。

プログラミングを学びたてのころだと動的型付け言語のほうがサクサクとコードがかけたり型変換などの面倒な処理を書く必要がないので楽しいと思います。けれど静的型付け言語にも魅力がありその魅力に早く気づければ新しい言語を学ぶきっかけになります。静的型付け言語生産性が悪いといった誤解がありますがその誤解を解ければと思います。

静的型付け言語とは?

静的型付け言語はコンパイラがソースコードを機械語に翻訳するときに値やオブジェクトの型をチェックする言語です。変数の代入、関数の適用、型の変換に関する記述がソースコードにあるときに型が正しいかどうかの確認を行っています。

ソースコード上の違いは変数名を宣言するときに型を明示的に表したり関数の戻り値の型を宣言したり型変換を明示的に表しています。

代表的な静的型付け言語はC言語、C++、Java、C#、TypeScript、Rust、Goなどが挙げられます。

動的型付け言語とは?

動的型付け言語はインタプリタがソースコードを実行するときに型を推定して値やオブジェクトの型を解釈します。型の検査は実行時のプロセス上で行われます。

ソースコード上の違いは変数を宣言するときに型を書いたりする必要が基本的にはありません。型の変換も暗黙的に変換可能な場合は自動で変換するようになっています。

代表的な動的型付け言語はLISP,Python,Ruby,JavaScript,PHPなどが挙げられます。

静的型付け言語と動的型付け言語の違い

静的型付け言語はコードをコンパイラに読ませて中間生成物に変換してビルドして実行可能な形式になり実行します。

動的型付け言語は一般的にはソースコードをインタプリタに読ませて処理が実行されます。 ここが静的型付け言語と動的型付け言語の仕組みという観点で見たときの違いです。

ここで中間生成物とは何かと疑問がわきますよね。 中間生成物はプログラムによってことなるので本記事ではこのように表現してます。

例えばC言語のソースコードをコンパイルすると機械語に変換されます。 機械語とはCPUで予め定義された操作をバイナリコードに割り当てた命令の一覧のことです。 機械語に変換された命令をリンクすることでソースコードに書かれたプログラムが実行可能な状態になります。ちなみにリンクとは断片的にコンパイルされた操作をつなげて一連の処理の流れにするようなイメージです。

Javaのコンパイラの場合はアセンブリではなくJavaバイナリコードに変換されます。 これはJVMというJAVAのプログラムを実行するソフトウェアが読み取れる形式のバイトコードです。 なぜ直接機械語に変換しないのかというのかというとプログラムの移植性を高めるためです。 機械語の場合はコンパイラが意図した環境によって実行できる環境が変わってしまいます。 JAVAの場合はJVMが入っているコンピュータ上ではJAVAのバイナリコードが実行できるので環境ごとにコンパイルする手間が省けます。 JVMはコードを読み込んでそれを実行するという意味ではインタプリタに似ています。

一方インタプリタはソースコードを直接解釈して適切な実行を行います。 そのためインタプリタはソースコードから型はどうあるべきかを解釈して操作を切り分けることができます。 なので動的型付け言語はソースコードで型を指定せずともプログラムを実行することができます。

こう考えるとコンパイラが型を解釈して上手くバイナリコードに変換することはできるのではないかと疑問に思うかもしれません。 少し定義とは矛盾するようですが実は静的型付け言語でも型を推論する機能を持つ言語がいくつか存在します。 実はメジャーな静的型付け言語(C,C++,C#,Javaなど)は途中から動的型付け言語の良いところを吸収して型推論の機能を一部持ってます。 そのため現代において静的型付け言語と動的型付け言語の違いはかなり曖昧になってきています。

更にややこしいことをいうと動的型付け言語でもコンパイルする機能があったりします。Pythonはコードをコンパイルしてから実行してたりします。PyPyなどのPythonをネイティブコードに変換する仕組みもあったりします。

なので本記事では静的型付け言語をソースコードに型を書くのが一般的な言語のこと 動的型付け言語をソースコードに型を書ないのが一般的な言語のことという解釈で解説させていただきます。

動的型付け言語を採用することのメリット

まずはじめに理解しやすい動的型付け言語のメリットについて確認します。

コードのビルドが不要なので実行するまでに時間がかからない

動的型付け言語はソースコードを読んでプログラムを実行してくれるのでデバッグするときにビルドするストレスがありません。頻繁にコードを修正しながらコーディングする必要があるケースではビルドがボトルネックになるので動的型付け言語のほうが効率的にデバッグできます。

必要なコードの量が減る

動的型付け言語では型を宣言したり型を変換する処理を書かなくて済むのでコードが簡潔になります。またテストコードなどあるクラスにオブジェクトを代わりに挿入するとき動的型付け言語の場合、型の扱いが曖昧であるがゆえモックを作るのが簡単だったりします。静的型付け言語だと型を厳密に扱う必要があるため抽象度の高いクラスを定義してそれらを継承するように実装する必要がありコード量が増えてしまうことがあります。

インタラクティブシェルが使える

動的型付け言語ではインタラクティブシェルでコードを実行して結果を確認しながらコーディングすることができます。そのためコードを修正し実行しその結果をみたりメモリに変数が残るのでその変数に対してなにか操作を行うようなど実行結果に対してコードを追記していくような開発が可能となります。

静的型付け言語ではコードをビルドしないと実行できないのが一般的です。そのため結果を実行してその結果をみてコードを追記して実行結果を見ていく操作をするためには何度もビルドし直して実行し直す必要があります。

データ分析などの分野では結果を可視化しつつ人が次の処理を考えてコードを変えることでデータを分析することが重要なので動的型付けのほうが使い勝手が良いです。またデータをロードしてそれを保持しながら色々と操作したい場合などは静的型付け言語だとプロセスを実行し直してかなり効率が悪化してしまいます。なのでPythonがデータ分析分野ではメジャーな言語として定着しています。

ただし静的型付け言語でもインタプリタを作ればインタラクティブなシェルを作ることは実現可能です。TypeScriptだとts-nodeというツールを作るとインタラクティブにコードを実行しながらコードを書くことができます。TypeScirptはJavaScriptにコンパイルされるものなので元々実装しやすいから作られているかもしれません。C言語とかはあまり聞いたことはなくJavaも現在開発が続いているものを知らないです。

動的型付け言語を採用することのデメリット

動的型付け言語だとコードの可読性が低くなるから

変数名だけだとどんな変数を用意すれば良いかわからないことがあります。

たとえば次のような消費税を計算して表示するプログラムがあったします。

from tools import func1, func2
def calc_tax(price):
	price2 = func1(price)
	return func2(price2)// calc_priceがどんな型を返しているかロジックを追う必要がある

このcalc_taxを使って標準入力から値を受け取って消費税の結果を表示するプログラムを書くことになったとします。このとき次の2つの問題が発生します。

  1. 引数のpriceにどのような型のpriceを渡せば良いか一見わからない
  2. 戻り値の方によって表示が変わってしまう。
price_input = input()
price = int(price_input)# intで大丈夫?それともfloat?decimal?
tax = calc_tax(price)
print(f"消費税は{tax}円です")# もし戻り値がfloatだったら表示が変になりそう

正しい結果を得ようとするとcalc_priceの中身を理解する必要がありその中でつかわれているfunc1,func2まで見に行かないといけないためコードを追うことになります。仮にコードを追えたとしてもロジックが複雑な場合は読み間違いが発生する可能性もあります。なので複雑なロジックの場合はデバッガでcalc_priceを実行してpriceがどんな型でどんな型かを確認する手間が増えます。このように動的型付け言語のほうが返って工数が増えてしまうことがあります。

他にも型の記載がない状況で引数がプロジェクトで独自に定義されている型だったりすると更に理解が難しくなります。例えば次のようにある商品の価格を計算するような関数があったとします。

from tools import func3, func4
def calc_price(product):
	price2 = func3(product)
	return price2

productが商品ということはわかりますがどのような型の変数を渡せば良いかわからず混乱してしまいます。productがもし独自に定義されたProductクラスのようなもの場合プロジェクトに参加したばかりの人には型がどのような構造になっているかを理解するのに苦労することがあります。変数名がクラス名と対応してなければ他にcalc_priceが使わている箇所を追ってどのように変数が作られているかまでみる必要があり大変です。

これは慣れてないライブラリを使う際にも発生することがあると思います。どんな型の変数を渡すのかを覚えてないとドキュメントをいちいち見に行く必要があって結構面倒くさいですよね。静的型付け言語だと型がテキストエディタ使うとすぐ見れるようになっているので思い出しやすいです。

これらの可読性が低くなる問題はちゃんとコメントを書くというルールを徹底することでも解消されます。しかしそうなってくると静的型付け言語とコードの冗長性はほとんど変わらなくなってしまいます。ルールを周知するぐらいだったら最初から静的型付け言語を採用したほうが楽です。

可読性が下がる問題はプロジェクトが大きくなるにつれて問題が大きくなります。人数が増えることによって自分以外が書いたコードを理解する必要が増えるからです。するとこのようなコードの曖昧性が増すことで効率性が著しく下がっていまします。

また長期に保守され続けるプロジェクトになればなるほどコードを書くよりもコードを読んでいる時間が多くなる傾向にあります。例えば一通り新規機能追加が完了して保守されるフェーズになると既存の関数を使いまわしたり不具合の修正をするようなタスクが増えてきます。

すると新規にコードを書くよりも既存のコードを正しく理解するためにコードを読む時間のほうが増えます。効率的にコードを書くよりも効率的にコードを読むことに重きが置かれてくきます。するとトータルで見た開発コストは動的型付け言語よりも静的型付け言語で書くほうが結果として少なくなることが多いです。

コードが実行されるまで型の安全性が保証されない

動的型付け言語の場合、実行されるまで型の誤りに気付かず不具合をリリースしてしまうリスクがたかまります。priceを1.1倍するcalc_tax2を次のように定義します。

from decimal import Decimal
def calc_tax2(price):
	return price * Decimal("1.1")

Decimalは小数を表す型の一つでfloatよりも正確に十進数の数値を正確に扱うことができます。同じ小数を表す型にも関わらずfloatとdecimalで四則演算を行うとエラーが発生します。

>>> from decimal import Decimal
>>> 100.1 * Decimal("1.1")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for *: 'float' and 'decimal.Decimal'

一方でint型とdecimal型の四則演算は正しく行われます。

>>> 100 * Decimal("1.1")
Decimal('110.0')

なのでcalc_tax2の引数にfloat型のpriceを渡すとエラーが発生しますがint型では正しく動作します。仮にint型のみでしかテストされていない場合はバグに気付かずリリースしたときに問題が発生することがあります。またfloat型でも直感的には正しい引数を渡しているように見えるのでバグに気付きにくいです。

このように型の縛りがない状況だと考慮しなければならないケースが発生するので慎重にコーディングする必要がありストレスを感じることがあります。

もし静的型付け言語の場合は最悪ビルドするときに気付くことができます。またテキストエディタやIDEの機能をつかうことでビルドしてなくてもコーディングしている最中に間違いを指摘してくれるので安心して快適にプログラムを書くことができます。

パフォーマンスが静的型付け言語に比べて劣る

動的型付け言語はインタプリタで実行時に型の判定を行うため静的型付け言語では不要な処理が発生するためパフォーマンスが悪化する傾向があります。

静的型付け言語を採用することのメリット

静的型付け言語のメリットは動的型付け言語のデメリットと裏返しになっています。

  • 動的型付け言語に比べてコードの可読性が高くなる。
  • コードが実行されるまえにビルドの段階で型の安全性がチェックされる
  • パフォーマンス面が優れている

使い分けはどうするべきか?

ここで静的型付け言語と動的型付け言語の使い分けについてまとめると次の通りです。

静的型付け言語が使われることが多いケース

  • 大規模になることが予想されるプロジェクト
  • 長く保守されることが予想されるプロジェクト
  • 正しく動くことに重きが置かれているプロジェクト
  • パフォーマンス面での要件が厳しいプロジェクト

動的型付け言語が使われることが多いケース

  • 個人でサービスを開発するとき
  • ちょっとした面倒な手作業を自動化したいときのスクリプト
  • コードを捨てる前提でのプロトタイピング
  • 静的型付け言語になれた人材が確保できない状況

ただし動的型付けか静的型付けかという基準以外にも様々な要因があるのであくまで傾向があるという感じです。

知っておくべき静的型付け言語

さてここまで静的型付け言語についての魅力を伝えきれたと思います。もし静的型付け言語を学びたいと思った人のためにメジャーな静的型付け言語を紹介します。

Java

歴史のある言語でWEB、デスクトップアプリケーション、アプリ開発、データ分析やデータ基盤の構築などありとあらゆる分野で使われている言語です。開発者も古くからあるため大規模なプロジェクトでは採用されるケースが多いです。多く人がアサインする大規模なプロジェクトで採用されているイメージがあります。すこしお固い職場で利用されるケースが多い印象です。

C#

C#はマイクロソフトが開発した言語でWindowsのデスクトップアプリを作りやすいです。さらに最近ではWindowsに限らずアプリ開発の分野でも人気があります。Xamarinを使うことで一つのソースコードからiOSとAndroidアプリのネイティブアプリを作成することが可能です。さらにUnityで採用されている言語なのでスマートフォンゲームやARやVRのアプリケーションなどが作りやすかったりと幅広い用途で使えます。

TypeScript

フロントエンドの静的型付け言語としてはもっともメジャーな言語です。コンパイラはTypeScriptのソースコードをJavaScriptのコードに変換する仕組みでフロントエンドでも使うことができます。私の主観にはなってしまいますがこれから新規に立ち上がるプロジェクトではJavaScriptよりもTypeScriptが多いように感じます。フロントエンドを書く機会があれば一度は触っておきたい言語です。

Dart

DartもGoogleが開発して言語です。Dartの魅力はiOSとアンドロイドのネイティブアプリを開発できるところです。基本的にはFlutterというフレームワークでアプリ開発が行われています。C#よりもモダンな構文なので人気がある印象です。

Rust

C言語に匹敵するパフォーマンスを持ちながらCやC++ほど書きにきにくさを感じることが少ない言語です。従来だとパフォーマンスが求められる場合はCかC++でしたが最近だとRustが採用されるケースが増えている印象で注目の言語です。

Go

GoはGoogleが開発した言語で静的型付け言語の中ではモダンな構文で言語仕様も少ないため比較的入門しやすい言語に思えます。並列処理が速度面は早い部類に入ります。並列処理が書きやすいという特徴があります。

動的型付け言語はだめなのか?

ここまで静的型付け言語の魅力を伝えるために動的型付け言語が悪者みたいになってしまいました。しかし最近の動的型付け言語は静的型付け言語の良い部分を動的型付け言語の機能として上手く取り入れられるケースがあるのでそれについて解説します。

静型付け言語のメリットを取りれているPythonの機能

静的型付け言語の良いところを上手く取り入れている代表的なケースがPythonです。

Pythonのタイプヒント

Pythonでも型を書くことができます。

from decimal import Decimal
from typing import Union
def calc_tax3(price: Union[int, Decimal]) -> Decimal:
    return price * Decimal("1.1")

これによってPythonでも型をコード上では明示することができるので可読性の問題は解決されます。一方で引数の型が間違っていてもスルーされるためあくまでコメントのような扱いにとどまっております。これは動的型付け言語はコンパイルするという過程がないこととインタプリタで型チェックまでしてしまうとパフォーマンスに影響がでるのが理由だと思われます。

>>> calc_tax3(1.1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in calc_tax3
TypeError: unsupported operand type(s) for *: 'float' and 'decimal.Decimal'

ただし型を明示することでテキストエディタが正しい型が渡るようにチェックできるようになるためテキストエディタの機能を上手く使うことで静的型付け言語とほぼ同じような開発体験を実現することができます。Visual Studioにプラグインを入れてPythonを型をつけて開発しているとオブジェクトのメンバ変数を推論してくれたりと実は型を付けて開発するほうがストレスなく開発できています。

ちなみにタイプヒントに関してはPythonに限らずPHP、Rubyでも新しいバージョンだと型が書けるようになっています。メジャーな動的型付け言語であれば最近は型も書けるようになっていると思われます。

PythonのJITで速度面を改善

PythonではNumbaというパッケージを使うことでPythonで書かれたコードの一部をコンパイルすることで静的型付け言語なみのパフォーマンスを実現する手法があります。これを使うとPythonでコーディングしてボトルネックになっているところにNumbaを適用することでパフォーマンスの問題を解決することができます。

Numba: A High Performance Python Compiler

動的型付け言語でも単体テスト信頼性を担保できる。

動的型付け言語でも単体テストを丁寧に書くことで実行される前に間違った型の値が代入されるこおとがないかを確認することができます。これで動的型付け言語でもコードの信頼性を担保することができるという考え方もあります。

静的型付け言語を学ぶべきか?

もし今まで動的型付け言語しか書いたことがなければ一度静的型付け言語を学んでみることをおすすめします。静的型付け言語を学ぶメリットとしては次の3つです。

  • 型に対する理解が深まるから
  • キャリアの選択肢が広がるから
  • 必要になったときに静的型付け言語の習得が容易になるから

静的型付け言語を学ぶと型に対する理解が深まります。動的型付け言語を書いているとなんとなく数値と文字列とオブジェクトぐらいの違いしか見えづらいですが静的型付け言語だと数値1つとってもint,short,long,float,doubleと多くの型があります。それらの違いを意識することで仕組みがわかったりと理解が深まります。

当然のことですが多くのプログラミング言語を知っているだけで応募できる仕事が増えます。たしかに業務経験があるかないかは採用においては重要な基準にはなりますが業務経験がなくても興味のある求人であれば応募してみて面接でアピールすることで評価してもらえることがあります。なので静的型付け言語を食わず嫌いせずに学べるように学んでおくことをおすすめします。

テクノロジーは速い速度で変化しているので何が流行るかはわからないです。将来的には静的型付け言語が流行ることも十分考えられます。実際に自分の周りでは3年前まではフロントエンドを書くならJavaScriptが一般的でしたが最近はTypeScriptしか見る機会がないです。必要になったときに静的型付け言語を学ぼうとすると慣れずに習得に時間がかかると思います。なのでそういった変化に備えてもし静的型付け言語を学んだことがなければ学んでおいて損はないと思います。

ちなみに初めて学ぶ言語という観点だと最初は少しでも学習コストが少ない動的型付け言語をマスターするのがおすすめです。なのでもし動的型付け言語を勉強している人はそのまま言語をマスターしてから静的型付け言語を2つ目の言語として学ぶのがおすすめです。もしJavaScriptを習得していたらTypeScriptは型がつくだけなので習得が容易です。

まとめ

まとめると型を明示化することは多人数がかかわったりプロジェクトが大規模になってくるとメリットが大きくなります。また動的型付け言語でも人気のある言語の場合、静的型付け言語の良い部分を取り入れることで進化しています。逆に静的型付け言語も動的型付け言語の良い点を吸収して進化しています。そのため動的型付け言語と静的型付け言語はどちらが良いかは一概には言えないです。システムに求められる要件や集められる人材、使える計算資源など様々な要因の組み合わせによって最適なプログラミング言語が決まります。

けれど動的型付け言語で型を書くかどうかみたいなチーム内で議論が起きたときは基本的に型を書くようにしたほうが良いでしょう。最近だとVisual Studio Codeなどの高性能なテキストエディタが無料で使えるので型を書く手間がそこまでかからない上にオブジェクトの場合、型を書いているとメソッドの候補が表示されたり型の間違いを指摘してくれたりと生産性が上がる機能を利用できます。ソースコードから曖昧さが排除されるので関数の属人化が防げて再利用しやすくなることも大きなメリットです。

コメント

タイトルとURLをコピーしました