オブジェクト指向の目的
オブジェクト指向ってよくわかんない。
継承、派生、カプセル化、クラス、インスタンス、色々技術があるけど結局何が目的なのかわかんない。。オブジェクト指向っぽいプログラムはクラスが分かれててあちこちに飛ぶし、場合によってはプロパティの宣言だけしかしてないクラスなんてあって、何のためなのか理解不能。
そんなことを思っている人向けの記事です。
自分が昔働いていた会社は金融系のシステム会社でした。90年代のスパゲッティコードををメンテするような仕事で、システムによってはドキュメントが電子化されていない、なんていうところもあった。転職して新興のベンチャー企業に入った。そこでオブジェクト指向について一から教えてくれた先輩には感謝しても感謝しきれないくらいだ。
そこで、自分なりにオブジェクト指向について整理するためにまとめてみる。
すごいざっくばらんに書いているので、色々抜け落ちていますが、とりあえず。
参考書籍等は以下の通り。他にもいくつか見てみたけど以下の3つが一番良かった。
- 動画:Microsoft Virtual Academy の Testing with Visual Studio
- 本:リーダブルコード
- 本:チーム開発の教科書
オブジェクト指向って結局何が目的なのよ
まず、僕なりの理解を述べると、オブジェクト指向の究極的な目標はRapid ReleaseとDevOpsだ。オブジェクト指向で開発すると、以下のメリットがある。
1.素早く開発・リリースできる
2.開発と運用がスムーズに連携できる
どういうことか?
そこに行くには、まずオブジェクト指向で作られたプログラムの特徴を理解する必要がある。
僕の理解では、オブジェクト指向で作られたプログラムの特徴は以下の2点だ。
適切な範囲でクラス分けされていること
クラス分けをするということは、関係あるプロパティ、メソッド類が一つのクラスにまとまっている状態を指す。
オブジェクト指向の基本は以下の2つのステップだ。
- オブジェクトのプロパティに値を詰める
- そのオブジェクトに対して何かを実行する(.Runする)
例えば、生徒クラスがあれば、そこに[名前]と[年齢]のプロパティがあって、そこに[授業を受ける]、というメソッドがあったりする。クラス分けされていないコードはMain()に全てをダーッと書くようなコードになる。(これを僕は勘違いしていたのだけれど、Main()の中のコードの複雑な部分をMain()の外にメソッド分けしたって同じクラス内であれば実質同じことだ。)
テストが自動化されていること
修正が入っても、ボタン一つでテストを自動で行ってくれる。そしてそのためには、コードの中で適切にクラス分けがされている必要がある。どういう意味かというと、Main()に全てのコードを書いているコードはテストが自動化できないという致命的な欠点がある。Rapid Releaseにはテストの自動化が欠かせない。(ちなみに、テストの自動化がされていないコードをレガシーコードというらしい)。
オブジェクト指向で作られたプログラムとそうでないのを比較してみる
ここに本当に簡単なものだけど、電卓プログラムを作ってみた。(色々考慮が足りてない点があるけど、そこは目をつぶってください。。。)
オブジェクト指向サンプル
・四則演算のみできる
・ゼロ除算、もしくは不正な値が入力された場合エラーメッセージ、エラーラベルを表示する
ObjectOrientedとNonObjectOrientedの二つのプロジェクト、ObjectOrientedの方には自動化された単体テストプロジェクトが付属している。WindowsFormsプロジェクトなので、Main()ではなく、Button_ClickイベントがMain()に相当する。ここで気を付けてほしい点は、Main()に全てを書き連ねてしまったNonObjectOrientedでは単体テストが自動化できない、という点だ。ObjectOrientedの場合、Main()でやっていることは単純だ。
オブジェクト指向で作った場合
Modelに画面に入力された情報を詰めて、Validationして、問題がない場合は計算結果を表示している。実際の処理は隠ぺいされている。
ObjectOrientedのButtonClickイベント Public Class ObjectOrientedForm Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click '画面に入力された情報をModelオブジェクトに詰める Dim model = Me.GetCurrentModel() 'Modelオブジェクトに格納された情報に対してバリデーションチェックを行う Dim result As ValidationResult = model.ValidateModel() 'バリデーションチェックの結果でエラーラベル、メッセージを表示する ErrorLabel.Visible = result.HasErrorRequiredInputData If result.IsRequredFieldError Then MsgBox("数字を入力してください") Exit Sub ElseIf result.IsDivisionByZero Then MsgBox("0で除算はできません") Exit Sub End If '計算結果を表示する Me.ResultTextbox.Text = model.Calculate End Sub
自動化された単体テストのコード Imports System.Text Imports Microsoft.VisualStudio.TestTools.UnitTesting Imports ObjectOriented <TestClass()> Public Class UnitTest1 <TestMethod()> Public Sub Devide左辺が10で右辺が5の場合結果は2になる() 'Arrange Dim model As New Model model.Num1 = 10 model.Num2 = 5 model.Divide = True Dim expected As Double = 2 Dim actual As Double = model.Calculate 'Assert Assert.AreEqual(expected, actual) End Sub
非オブジェクト指向で作った場合
Public Class NonObjectOrientedForm Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click Dim num1 = 0 Dim num2 = 0 Dim result = 0 If Double.TryParse(Number1Textbox.Text, num1) = False And Double.TryParse(Number2Textbox.Text, num2) = False Then ErrorLabel.Visible = True MsgBox("数字を入力してください") Exit Sub End If Dim radiobutton = (From f In Me.Controls Where TypeOf f Is RadioButton Where f.checked = True).FirstOrDefault Select Case radiobutton.Name Case "PlusRadiobutton" result = num1 + num2 Case "MinusRadiobutton" result = num1 - num2 Case "MultiplicationRadiobutton" result = num1 * num2 Case "DivisionRadiobutton" If num2 = 0 Then ErrorLabel.Visible = True MsgBox("0で除算はできません") Exit Sub End If result = num1 / num2 Case Else result = 0 End Select ErrorLabel.Visible = False ResultTextbox.Text = result End Sub Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load Me.PlusRadiobutton.Checked = True End Sub End Class
ビジネスロジックとメインロジックを分離する意味
テストプロジェクトを見てもらうとわかると思うけど、実際にテストしているのはmodel.Calculate()とmodel.ValidateModel()の部分だけだ。言ってみれば、実際のビジネスロジックの部分。イベントの中では処理を呼び出すだけにとどめて、ビジネスロジックは分離されている。ビジネスロジックの結果、どのように分岐し処理する、という部分はテストの対象範囲外となっている。
もちろん完全にやらないわけではなく、実際にはメインロジックの部分は手動テストや結合テストで実施することになる。ただ、考えてみれば、ビジネスロジックで変更・修正が入ることはままあっても、メインのロジックで修正が入ることはまれだと思う。自動化された単体テストはミリ秒レベル、手動テストは数分、場合によっては数時間から数日かかる。だから、変更頻度の高いビジネスロジック部分は自動化された単体テスト、変更があまり入らないメインロジック部分は手動テスト、という風にテストを分担している。
そして、ここで重要なのは、modelをクラス分けして、[画面に入力された情報]、[それらの情報に対するチェックメソッド]という風にひとまとまりにして外だししていることで、初めて自動化された単体テストの対象にできるという点だ。インスタンス化できない部品は単体テストを自動化できない。(Formクラスをインスタンス化して、Main()メソッドをテストするのは難しすぎる。)わざわざステップ数を増やしたりしてまでクラス分けしているのは、畢竟自動化された単体テストのため、と言ってもよいと思う。