はじめに
旅行メディアシステム部の二郎と申します。
私は静的型付け言語であるC#での開発経験が長いのですが、
現在は動的型付け言語であるRubyやPHPでの開発に携わっています。
反論やお怒りをすぐにいただけそうなテーマではあるのですが、
せっかく両タイプの言語を経験しているので整理してみたいという気持ちが出発点です。
あくまで現時点での個人的な見解を整理する、という内容です。
(個人的な見解なので、距離を置いて冷めた目で見ていただけますと幸いですmm)
静的型付け言語と動的型付け言語の整理とは書いていますが、実際にはC#とRuby/PHPを比較して整理しています。
そのために考慮が不十分な点もあるかと思いますので、その点もご了承ください。
動的型付け言語(Ruby/PHP)のメリット
テスト用にインターフェースを書かなくてよい
C#で開発をしていると、テストを書くためにインターフェースを定義しなければいけないことがあります。
というか、真面目にテストを書くとするとインターフェースを作成することは避けられないです。
これ、私はとても面倒くさいと思ってしまうんです……。
業務仕様のためのインターフェースを定義するのは全然よいのですが、
テストのためだけに書くのは面倒だなと思ってしまいます。
次のプログラムはC#でテストを書くためにインターフェースを定義している例です。
プログラム仕様
- RamenJiroFileクラスがテキストファイル(RamenJiroFavoriteStore.txt)から好きな店舗を取得する
- RamenJiroServiceクラスはRamenJiroFileクラスを保持し、取得店舗を利用したメッセージを出力する
using System; internal class Startup { public static void Main() { var service = new RamenJiroService(); // 好きなラーメン二郎の店舗は、XXXXXです。 Console.WriteLine(service.GetFavoriteStoreMessage()); } }
internal class RamenJiroService { private readonly IRamenJiroFile _file; public RamenJiroService() { _file = new RamenJiroFile(); } public RamenJiroService(IRamenJiroFile file) { _file = file; } public string GetFavoriteStoreMessage() { return $"好きなラーメン二郎の店舗は、{_file.GetFavoriteStore()}です。"; } }
using System.IO; internal class RamenJiroFile : IRamenJiroFile { public string GetFavoriteStore() { using var reader = new StreamReader("RamenJiroFavoriteStore.txt"); var favoriteStore = reader.ReadToEnd(); return favoriteStore; } }
// テストのためのインターフェース(面倒くさい) internal interface IRamenJiroFile { public string GetFavoriteStore(); }
RamenJiroFileクラスはIRamenJiroFileインターフェースを実装していますが、プログラム本体のためには不要なものです。
IRamenJiroFileインターフェースはRamenJiroFileクラスの代わりのテスト用モックを作成するために定義しています。
以下はRamenJiroServiceクラスのユニットテストプログラムになります。 RamenJiroServiceクラスにテスト用モックであるRamenJiroFileMoqクラスを依存性注入(DI)しています。
using Microsoft.VisualStudio.TestTools.UnitTesting; internal class RamenJiroFileMoq : IRamenJiroFile { public string GetFavoriteStore() { return "神田神保町店"; } } [TestClass] public class RamenJiroServiceTest { [TestMethod] public void TestGetFavoriteStoreMessage() { var fileMoq = new RamenJiroFileMoq(); var service = new RamenJiroService(fileMoq); var message = service.GetFavoriteStoreMessage(); Assert.AreEqual("好きなラーメン二郎の店舗は、神田神保町店です。", message); } }
どうでしょうか?IRamenJiroFileインターフェースを定義するのが面倒だなと感じる方もいらっしゃるのではないでしょうか?
逆に整理されていて好きだという方ももちろんいると思います。
次に同じ内容をRubyで書いてみます(ファイル名はクラス名から推測できると思いますので省略します)。
require_relative './ramen_jiro_service' service = RamenJiroService.new # 好きなラーメン二郎の店舗は、XXXXXです。 puts service.get_favorite_store_message
require_relative './ramen_jiro_file' class RamenJiroService def initialize(ramen_jiro_file = nil) @file = ramen_jiro_file || RamenJiroFile.new end def get_favorite_store_message "好きなラーメン二郎の店舗は、#{@file.get_favorite_store}です。" end end
class RamenJiroFile def get_favorite_store file = File.open("RamenJiroFavoriteStore.txt", "r") favorite_store = file.read file.close favorite_store end end
require 'minitest/autorun' require_relative '../app/ramen_jiro_service' class RamenJiroFileMoq def get_favorite_store '神田神保町店' end end class RamenJiroServiceTest < Minitest::Test def test_get_favorite_store_message file_moq = RamenJiroFileMoq.new service = RamenJiroService.new(file_moq) message = service.get_favorite_store_message assert_equal '好きなラーメン二郎の店舗は、神田神保町店です。', message end end
動的型付け言語であるRubyはダックタイピングができるので、インターフェースの定義なしで同じことができます。 C#で開発をするとテストを書くためのインターフェースを数多く作成する必要があることを考えると、 個人的には非常に大きなメリットと考えています。
ちなみに、Rubyだとインターフェースというもの自体がないですが、PHPにはインターフェースがあります。 PHPは堅いソースコードも書くことができるようになっているということでしょうか(理解が浅いです)。 PHPですと上記の例のどちらのパターンでも書けそうですが、私はダックタイピングで書きたいです。
なお、上記サンプルはC#11(テストはMSTest)、Ruby3.2.2(テストはMiniTest)で書いています。 モック作成のライブラリを利用するとそれぞれの言語での書き方も変わると思いますが、C#でインターフェースを定義することは変わらないかなと思います。
コンパイルせずにすぐに実行できる
動的型付け言語≒インタープリタ言語と乱暴に整理させていただくのですが、
インタープリタ言語であるRubyやPHPはすぐにプログラムの実行ができてとても快適です。
コンパイル時間がないというのは大きなメリットだと思います。
静的型付け言語≒コンパイル言語であるC#だとプログラム実行前にコンパイルする時間があるため、
マシン性能によりますが、どうしても数秒程度の待ち時間が発生してしまいます。
静的型付け言語(C#)のメリット
クラス定義やメソッド定義へのジャンプが確実にできる
IDEやVSCodeなどの高機能なエディターを利用すると、プログラム中のクラスやメソッドの定義へジャンプできます。 以下に先ほどの例で挙げたC#のRamenJiroServiceクラスを再掲します。
internal class RamenJiroService { private readonly IRamenJiroFile _file; public RamenJiroService() { _file = new RamenJiroFile(); } public RamenJiroService(IRamenJiroFile file) { _file = file; } public string GetFavoriteStoreMessage() { return $"好きなラーメン二郎の店舗は、{_file.GetFavoriteStore()}です。"; } }
7行目の「new RamenJiroFile()」の部分からRamenJiroFileクラスの定義へジャンプしたり、
17行目の「_file.GetFavoriteStore()」の部分からGetFavoriteStoreメソッドの定義へジャンプしたりできます(インターフェース定義へのジャンプもできます)。
また、RamenJiroServiceの利用箇所の一覧を表示して利用箇所へジャンプすることもできます。
私はプログラムを読むときにIDEやエディターのこのような機能を利用することが多いので、 静的型付け言語であるC#だと、この動きが100%保証されるので非常に有難いです。
RubyやPHPだと、ある程度はジャンプできるのですが、できない場合があります
(私は現在VSCodeを利用しているので、よい拡張機能を選定できていないだけかもしれません)。
その場合はファイル名でジャンプしたり、メソッド名などをGREP検索して探すことになるのですが、
できればプログラムの内容からクラス定義やメソッド定義へジャンプしたいというのが私の趣味嗜好です。
型情報がプログラムを読むときに役立つ
私がRubyやPHPでの開発経験が短いからというのが大きな理由だとは思うのですが、
型情報の記述がないので、この変数はどういう型なんだろうと困ることがあります。
あるメソッドを読みたいだけなのに、メソッドの引数の型がわからずに呼出し元のプログラムを確認したり、
あるメソッドの戻り値の型がわかれば読み進められそうなのに、わからないのでメソッドの内容を確認したりします。
ですので、個人的には型情報があるC#は読みやすいというメリットがあります。 書き方が無茶苦茶なプログラムでも型情報があるのでなんとか読める、みたいなこともあります。
C#には冗長性を排除するための型推論という機能がありますが、型推論を利用しても型は事前に決定されています。
以下のプログラムだとramen変数に何が入っているかさっぱりわかりませんが、
C#のIDEであるVisual Studioでramen変数にカーソルを当てるとramen変数の型情報を教えてくれます。
var ramen = GetRamen();
PHPですとメソッドに型を指定することもできるので、個人的にはメソッドに型を指定したくなります。
コンパイルエラーで間違いに気づく
これは静的型付け言語のメリットとしてよく言われているイメージです。
変数名のタイポであったり、メソッドを削除したときに参照が残っているのを気づいたりできます。
コンパイルエラーで間違いに気づくことは私もメリットと思います。
ただし、動的型付け言語を利用していても、IDEや高機能なエディターによって、類似の検出ができる場合も多いと考えています。
まとめ
最後までお付き合いいただき、ありがとうございました。
静的型付け言語と動的型付け言語って結局どこが違うんだろうという漠然とした疑問があったのですが、
私としてのメリットとデメリットを考えることで個人的に整理できました。
- テスト用にインターフェースを書かなくてよい
- コンパイルせずにすぐに実行できる
静的型付け言語(C#)のメリット
- クラス定義やメソッド定義へのジャンプが確実にできる
- 型情報がプログラムを読むときに役立つ
- コンパイルエラーで間違いに気づく
あくまで現時点での整理なので、半年後に考えが変わっているかもしれません!
カカクコムでは、ともにサービスをつくる仲間を募集しています!
カカクコムのエンジニアリングにご興味のある方は、ぜひこちらをご覧ください!