動的型付け言語の方が楽なのになぜ静的型付け言語?
静的型付け言語は型を人が書くため冗長なプログラミング言語です。そこで型なんてコンパイラに推測させれば良いじゃんと思った開発者が動的型付け言語を作りました。
当初はコンピューターリソースの問題で静的型付け言語が主流でしたが、ムーアの法則のおかげで現代ではリソースの問題なんて気にしなくて良くなりました。
「動的型付け言語は型を書かなくて良いので開発が楽になる!静的型言語は過去の遺物と化した!」と思ったのも束の間。
現代のWEB開発現場ではフロントエンドはTypeScript、バックエンドは静的型付け言語のTypeScriptやGOだったり、Pythonでもタイプヒントなどで型を書くのが一般的です。フロントエンドをJavaScriptで開発しようと新規開発を始めたらチームからの反発を受けること間違いなし!
では、なぜ静的型付け言語が人気になるのでしょうか?
その答えとしては静的型付け言語の方が長い目で見たら効率的だからです。
というのも型の書いてないソースコードを読むのは苦痛です。型が書いてないと何を引数として渡せば良いのかわからず、場合によっては一々処理を追いながら開発することになります。
またビルド時に変な型を入れることがあっても動的型付け言語だと許容していしまいます。バグに気付かずサービスを公開してからバグに気付くことも動的型付け言語だと静的型付け言語に比べて多くなってしまいます。
これらの理由から静的型付け言語は業務ではよく使われるようになりました。それでは動的型付け言語はダメかと言われるとそうでもないです。そんな奥の深い静的型付け言語と動的型付け言語について本記事では解説します。
静的型付け言語とは?
静的型付け言語とは?
静的型付け言語はコンパイラがソースコードを機械語に翻訳するときに値やオブジェクトの型をチェックする言語です
変数の代入、関数の適用、型の変換を行うときに型が正しいかどうかの確認を行っています。
ソースコード上の違いは変数名を宣言するときに型を明示的に表したり関数の戻り値の型を宣言したり型変換を明示的に表しています。ソースコードを書いたときに型が静的に固定されるので静的型付け言語です。
代表的な静的型付け言語はC言語、C++、Java、C#、TypeScript、Rust、Goなどが挙げられます。
動的型付け言語とは?
動的型付け言語とは?
動的型付け言語はインタプリタがソースコードを実行するときに値やオブジェクトの型を推定して処理を実行する言語です。型のチェックは基本的には行われず、もし型の不一致があった場合は実行にエラーが発生します。
このように実行時に型の推定が行われるため、コードを記載するときに型を書く必要がありません。型の変換も暗黙的に変換可能な場合は自動で変換するようになっています。実行時に型が動的に決定されるから動的型付け言語です。
代表的な動的型付け言語はLISP,Python,Ruby,JavaScript,PHPなどが挙げられます。
静的型付け言語と動的型付け言語の違い
静的型付け言語はコードをコンパイラに読ませて中間生成物に変換してビルドして実行可能な形式になり実行します。
動的型付け言語は一般的にはソースコードをインタプリタに読ませて処理が実行されます。 ここが静的型付け言語と動的型付け言語の仕組みという観点で見たときの違いです。
静的型付け言語と動的型付け言語の定義が曖昧になってきている
ちなみに静的型付け言語でもコンパイラが型を解釈して上手くバイナリコードに変換すれば良いと疑問に思うかもしれません。 少し定義とは矛盾するようですが実は静的型付け言語でも型を推論する機能を持つ言語は存在します。
メジャーな静的型付け言語(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つの問題が発生します。
- 引数のpriceにどのような型のpriceを渡せば良いか一見わからない
- 戻り値の方によって表示が変わってしまう。
具体的にコードで説明すると次のようなことになります。
price_input = input()
price = int(price_input)# intで大丈夫?それともfloat?decimal?
tax = calc_tax(price)
print(f"消費税は{tax}円です")# もし戻り値がfloatだったら表示100.0円みたいになって不味いかも
2行目の入力だけどintに変換して大丈夫かな?
calc_taxの戻り値はなんだろう。これを表示するときにどう文字列に変換しようか?
正しい結果を得ようとすると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が使わている箇所を追ってどのように変数が作られているかまでみる必要があり大変です。
職場以外でもこういうことある!
これは慣れてないライブラリを使う際にも発生することがあると思います。どんな型の変数を渡すのかを覚えてないとドキュメントをいちいち見に行く必要があって結構面倒くさいですよね。静的型付け言語だと型がテキストエディタ使うとすぐ見れるようになっているので思い出しやすいです。
でもこれってコメント書いてないのが悪いんじゃないの?
これらの可読性が低くなる問題はちゃんとコメントを書くというルールを徹底することでも解消されます。ただし、コメントをわざわざ書くのであれば、静的型付け言語とコードの冗長性はほとんど変わらなくなってしまいます。動的型付け言語を使うとルールを周知する手間も増えるのでルールを徹底するぐらいだったら最初から静的型付け言語を採用したほうが楽です。
時間や人が多くなるに連れてこの問題はどんどん深刻になるので気をつけましょう。
可読性が下がる問題はプロジェクトが大きくなるにつれて問題が大きくなります。人数が増えることによって自分以外が書いたコードを理解する必要が増えるからです。するとこのようなコードの曖昧性が増すことで効率性が著しく下がっていまします。
また長期に保守され続けるプロジェクトになればなるほどコードを書くよりもコードを読んでいる時間が多くなる傾向にあります。例えば一通り新規機能追加が完了して保守されるフェーズになると既存の関数を使いまわしたり不具合の修正をするようなタスクが増えてきます。
すると新規にコードを書くよりも既存のコードを正しく理解するためにコードを読む時間のほうが増えます。効率的にコードを書くよりも効率的にコードを読むことに重きが置かれてくきます。するとトータルで見た開発コストは動的型付け言語よりも静的型付け言語で書くほうが結果として少なくなることが多いです。
黒魔術が多すぎて、もはや0から書き直したほうが早いのでは…
コードが実行されるまで型の安全性が保証されない
動的型付け言語の場合、実行されるまで型の誤りに気付かず不具合をリリースしてしまうリスクがたかまります。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を適用することでパフォーマンスの問題を解決することができます。
動的型付け言語でも単体テスト信頼性を担保できる。
動的型付け言語でも単体テストを丁寧に書くことで実行される前に間違った型の値が代入されるこおとがないかを確認することができます。これで動的型付け言語でもコードの信頼性を担保することができるという考え方もあります。
静的型付け言語を学ぶべきか?
もし今まで動的型付け言語しか書いたことがなければ一度静的型付け言語を学んでみることをおすすめします。静的型付け言語を学ぶメリットとしては次の3つです。
- 型に対する理解が深まるから
- キャリアの選択肢が広がるから
- 必要になったときに静的型付け言語の習得が容易になるから
静的型付け言語を学ぶと型に対する理解が深まります。動的型付け言語を書いているとなんとなく数値と文字列とオブジェクトぐらいの違いしか見えづらいですが静的型付け言語だと数値1つとってもint,short,long,float,doubleと多くの型があります。それらの違いを意識することで仕組みがわかったりと理解が深まります。
当然のことですが多くのプログラミング言語を知っているだけで応募できる仕事が増えます。たしかに業務経験があるかないかは採用においては重要な基準にはなりますが業務経験がなくても興味のある求人であれば応募してみて面接でアピールすることで評価してもらえることがあります。なので静的型付け言語を食わず嫌いせずに学べるように学んでおくことをおすすめします。
テクノロジーは速い速度で変化しているので何が流行るかはわからないです。将来的には静的型付け言語が流行ることも十分考えられます。実際に自分の周りでは3年前まではフロントエンドを書くならJavaScriptが一般的でしたが最近はTypeScriptしか見る機会がないです。必要になったときに静的型付け言語を学ぼうとすると慣れずに習得に時間がかかると思います。なのでそういった変化に備えてもし静的型付け言語を学んだことがなければ学んでおいて損はないと思います。
ちなみに初めて学ぶ言語という観点だと最初は少しでも学習コストが少ない動的型付け言語をマスターするのがおすすめです。なのでもし動的型付け言語を勉強している人はそのまま言語をマスターしてから静的型付け言語を2つ目の言語として学ぶのがおすすめです。もしJavaScriptを習得していたらTypeScriptは型がつくだけなので習得が容易です。
まとめ
まとめると型を明示化することは多人数がかかわったりプロジェクトが大規模になってくるとメリットが大きくなります。また動的型付け言語でも人気のある言語の場合、静的型付け言語の良い部分を取り入れることで進化しています。逆に静的型付け言語も動的型付け言語の良い点を吸収して進化しています。そのため動的型付け言語と静的型付け言語はどちらが良いかは一概には言えないです。システムに求められる要件や集められる人材、使える計算資源など様々な要因の組み合わせによって最適なプログラミング言語が決まります。
けれど動的型付け言語で型を書くかどうかみたいなチーム内で議論が起きたときは基本的に型を書くようにしたほうが良いでしょう。最近だとVisual Studio Codeなどの高性能なテキストエディタが無料で使えるので型を書く手間がそこまでかからない上にオブジェクトの場合、型を書いているとメソッドの候補が表示されたり型の間違いを指摘してくれたりと生産性が上がる機能を利用できます。ソースコードから曖昧さが排除されるので関数の属人化が防げて再利用しやすくなることも大きなメリットです。
補足
静的型付け言語でコンパイルされて生成されるもの
先程の説明で「コンパイルされたときの中間生成物とは具体的には何?」と疑問がわいた人のために簡単に説明します。 中間生成物はプログラムによってことなるので本記事ではこのように表現してます。
例えばC言語のソースコードをコンパイルすると機械語に変換されます。 機械語とはCPUで予め定義された操作をバイナリコードに割り当てた命令の一覧のことです。 機械語に変換された命令をリンクすることでソースコードに書かれたプログラムが実行可能な状態になります。ちなみにリンクとは断片的にコンパイルされた操作をつなげて一連の処理の流れにするようなイメージです。
Javaのコンパイラの場合はアセンブリではなくJavaバイナリコードに変換されます。 これはJVMというJAVAのプログラムを実行するソフトウェアが読み取れる形式のバイトコードです。 なぜ直接機械語に変換しないのかというのかというとプログラムの移植性を高めるためです。 機械語の場合はコンパイラが意図した環境によって実行できる環境が変わってしまいます。 JAVAの場合はJVMが入っているコンピュータ上ではJAVAのバイナリコードが実行できるので環境ごとにコンパイルする手間が省けます。 JVMはコードを読み込んでそれを実行するという意味ではインタプリタに似ています。
コメント