出直し!! ヘルプ

連載中

連載 終了

2024/05/30

クラス化に挑戦: #8)Google マップ - Place API の結果をクラス化 ③

Google マップ の Place API の検索結果をクラス化してみる企画の 3 回目です。前回作成したクラスを実際に組み込んでテストします。


検索用関数の準備

現在のライブラリの状態は下図の通りです。一番近い場所を検索する xGetNearestPlaceName 関数は、エージェントから移行したままの状態です。これをクラスに対応し、利用できるようにします。

修正の方針は、

  1. 外部からアクセスできるように Public 化する
  2. 戻り値を名前(文字列)ではなく、Place のインスタンスに変更

とします。

修正後の関数は次の通りです。赤字が修正箇所となっています。

Public Function GetNearestPlace(ByVal vdLat As Double, vdLng As Double, _
                                                                                  ByVal vsKeyword As String) As Place
   Dim sURL As String
   Dim sPram As String
   Dim http As NotesHTTPRequest
   Dim jnav As NotesJSONNavigator
   Dim jeResults As NotesJSONElement
   Dim jePlace As NotesJSONElement

   'URL の準備
   sURL = "https://maps.googleapis.com/maps/api/place/nearbysearch/json"
   sPram = xGetPram(vdLat, vdLng, vsKeyword)
   sURL = sURL & "?" & sPram

   'API に接続しレスポンスを JSON で取得
   Set http = xns.CreateHttpRequest()
   http.PreferJSONNavigator = True
   Set jnav = http.Get(sURL)

   '一番近隣の地点情報を取得
   Set jeResults = jnav.GetElementByName("results")
   Set jePlace = xGetPlace_Nth(jeResults, 1)

   '地点情報からPlaceインスタンスを取得して返す
   Set GetNearestPlace = New Place(jePlace)
End Function

関数の戻り値の型が作成したクラス Place となっています。Plase のインスタンス、ようはオブジェクトを返すので戻り値のセットには Set が必要となります。


テストエージェントの作成

ライブラリの準備が整いましたので、テストエージェントを作成します。

Option Declare
Use "lsGoogleMAP"

Sub Initialize
   Dim dLat As Double '緯度
   Dim dLng As Double '経度
   Dim oPlace As Place

   dLat = 34.683742526906634
   dLng = 135.49698067096077

   Set oPlace = GetNearestPlace(dLat, dLng, "鳥貴族")
   MsgBox oPlace.PlaceName, 64
End Sub

もととなったエージェントの Initialize とほぼ同等です。ポイントとなるのは赤字の部分となります。

検索結果が Place クラスのインスタンスとなりますので、それを受けるためのオブジェクト変数 oPlace を利用しています。また、メッセージボックスでは、PlaceName プロパティを取得して表示しています。

座標にアクセスしたい場合は、次のように記述すれば OK です。

MsgBox oPlace.Location.Latitude, 64


前回 クラス化に挑戦 次回


2024/05/29

クラス化に挑戦: #7)Google マップ - Place API の結果をクラス化 ②

Google マップ の Place API の検索結果をクラス化してみる企画の 2 回目です。いよいよ具体的なコーディングに入ります。


位置クラス

Google マップ の 検索結果や GPS など地図上の位置情報を使用するには緯度と経度を使用します。まずはこれを管理する位置クラス Location を作成します。クラス初期化時に緯度と経度を与え、それをメンバ変数で保持し、プロパティから参照できるとても単純なクラスです。

サンプルコードは次の通りです。

Public Class Location
   Private zdLatitude As Double '緯度
   Private zdLongitude As Double '経度

   Public Sub New(ByVal vdLatitude As Double, ByVal vdLongitude As Double)
      zdLatitude = vdLatitude
      zdLongitude = vdLongitude
   End Sub

   Public Property Get Latitude As Double
      Latitude = zdLatitude
   End Property

   Public Property Get Longitude As Double
      Longitude = zdLongitude
   End Property
End Class

この程度であれば、クラス化する必要性はないかも知れません。サンプルの提示という側面もありますが、緯度・経度の入力チェックなど今後の拡張を考えて、クラスを採用しています。


場所クラス

続いてが本題である検索結果の 1 件分を表すクラスです。検索結果の JSON より場所の情報を取得してプロパティとして参照できる単純な仕様とします。

作成するプロパティは、とりあえず 3 つです。

PlaceID Google Place データベースで一意に表す ID
PlaceName 場所の名称
Location 場所の座標

まずは、クラスの大枠を作成します。クラスを定義し、メンバ変数とプロパティを作成します。

Public Class Place
   Private zsPlaceID As String
   Private zsName As String
   Private zoLocation As Location

   Public Property Get PlaceID As String
      PlaceID = zsPlaceID
   End Property

   Public Property Get PlaceName As String
      PlaceName = zsName
   End Property

   Public Property Get Location As Location
      Set Location = zoLocation
   End Property
End Class


場所クラスの初期化

続いてコンストラクタを作成します。

Place API の検索結果 result は配列となっていました。その 1 要素分を表す JSON が New メソッドの引数となる前提で作成します。JSON から取得した値はそれぞれのメンバ変数に格納しています。

   Sub New(vjePlace As NotesJSONElement)
      Dim jobjPlace As NotesJSONObject
      Dim jobj As NotesJSONObject
      Dim je1 As NotesJSONElement
      Dim je2 As NotesJSONElement

      Set jobjPlace = vjePlace.Value

      'プレースID
      Set je1 = xGetElementByName_Obj(jobjPlace, "place_id")
      If Not(je1 Is Nothing) Then
         zsPlaceID = je1.Value
      Else
         zsPlaceID = ""
      End If

      '場所の名称
      Set je1 = xGetElementByName_Obj(jobjPlace, "name")
      If Not(je1 Is Nothing) Then
         zsName = Trim(je1.Value)
      Else
         zsName = ""
      End If

      '場所の座標
      Set je1 = xGetElementByName_Obj(jobjPlace, "geometry")
      If Not(je1 Is Nothing) Then
         Set jobj = je1.Value
         Set je2 = xGetElementByName_Obj(jobj, "location")
         Set zoLocation = xGetLocation(je2)
      End If
   End Sub


この処理では 2 つのサブ関数を利用しています。

1 つ目が xGetElementByName_Obj 関数です。JSON 内のエレメントを名称で検索機能なのですが、NotesJSONObject の GetElementByName メソッドは検索にヒットしない場合エラーを返します。メインルーチンでエラー処理を書くと煩雑になるで関数化しています。エラーの場合は Nothing を返す仕様ということですね。

Function xGetElementByName_Obj(vjobj As NotesJSONObject, _
                                                       ByVal vsName As String) As NotesJSONElement
   Dim je As NotesJSONElement

   On Error GoTo ErrProc
   Set je = vjobj.GetElementByName(vsName)

ExitProc:
   Set xGetElementByName_Obj = je
   Exit Function

ErrProc:
   Resume ExitProc
End Function


2 つ目のサブ関数は今回作成した Location のインスタンスを返す関数です。

JSON を確認すると座標を表す項目がいくつもあります。今回は location で指定されている座標を使用していますが、汎用的に使用できるよう関数化しています。

関数は次の通りです。

Private Function xGetLocation(vjeLocation As NotesJSONElement) As Location
   Dim jobj As NotesJSONObject
   Dim jeLat As NotesJSONElement
   Dim jeLng As NotesJSONElement
   Dim dLat As Double
   Dim dLng As Double

   If Not(vjeLocation Is Nothing) Then
      Set jobj = vjeLocation.Value
      Set jeLat = xGetElementByName_Obj(jobj, "lat")
      dLat = CDbl(jeLat.Value)
      Set jeLng = xGetElementByName_Obj(jobj, "lng")
      dLng = CDbl(jeLng.Value)

      Set xGetLocation = New Location(dLat, dLng)
   End If
End Function

※ 2024.7.24 バグ修正

上記プログラムにバグがありました(赤字)。上記プログラムは修正済み。 
 修正箇所は下記の通り。

誤) 

      dLng = CDbl(jeLat.Value)

 正)

      dLng = CDbl(jeLng.Value) 

 

ちなみに、この関数を利用している New メソッドでは以下のようになっていました。

location のエレメントを取得してこの関数に渡しているので、location の位置情報を保持させているということですね。

   Sub New(vjePlace As NotesJSONElement)
         ・・・
      '場所の座標
      Set je1 = xGetElementByName_Obj(jobjPlace, "geometry")
      If Not(je1 Is Nothing) Then
         Set jobj = je1.Value
         Set je2 = xGetElementByName_Obj(jobj, "location")
         Set zoLocation = xGetLocation(je2)

      End If
   End Sub


ライブラリの状態

ここまでの作業が完了するとライブラリの状態は以下のようになっています。今回作成した部分は赤枠の部分となります。

次回はこのクラスを実際に使用してみましょう。

前回 クラス化に挑戦 次回


2024/05/28

クラス化に挑戦: #6)Google マップ - Place API の結果をクラス化 ①

これまで 5 回にわたって LotusScript でクラスを作る方法についてまとめました。説明ばかりでは面白くありませんので、そろそろ実践をしてみましょう。

何をネタにしようかと思案したのですが、抽象的であったり使えないものでは面白くありません。そこで日々の開発でクラス化して便利だと感じている DXL や JSON をネタにしようと思います。DXL も JSON も構造化されたテキストデータです。階層が深くなることもあり、プログラム長くなったり、メンテナンスが低くなりがちです。この課題をクラス化で解決してみようということです。


今回のテーマ

ということで、今回ネタにするのは、別の連載『つないでみよう』で紹介した Google マップ の Place API で近隣を検索した結果をクラス化してみます。

関連する連載記事は次の通りです。必要の応じてご確認ください。

10 Google マップ - Place API で一番近隣の場所を取得
9 Google マップ - Place API の仕様
8 Google マップ - 連携準備

動かしながら作ってみたいという方はこの記事を参考に Google の API の利用登録を行ってください。無料枠の設定がありますので、検証用途程度であれば費用が掛かることはまずありません。


この記事で Google マップの検索結果の JSON は下図のようなイメージでした。results オブジェクト内に配列で列挙されていて、その要素1つが1件の場所を表していました。これをクラスで実現してみましょう。


クラス化の方針 と ライブラリの準備

クラスは汎用的に使用する前提としますので、独立したスクリプトライブラリ lsGoogleMAP に作成するものとします。

とりあえず、ライブラリには #10 で作成したプログラムをすべてコピペします。ただ、Initialize のコードはライブラリとしては不適切ですので削除します(以下の画像ではコメントアウト)。


この状態をクラス化のスタと地点とします。次回から順次クラス化作業を進めます。


前回 クラス化に挑戦 次回


2024/05/21

クラス化に挑戦: #5)クラスの初期化

クラスを利用する際は New を使用してインスタンスを作成します。

   Dim oEmp As New Employee

このタイミングで実行される特殊なメソッドがあります。オブジェクト指向の世界では『コンストラクタ』と呼ばれるのですが、簡単に言うとクラスの初期化処理を記述するメソッドです。


LotusScript とクラスの初期化

LotusScript では、”New” という固定された名称のメソッドがコンストラクタとして使用されます。どのユーザ定義クラスであっても New メソッドとなるのでわかりやすいですね。

New はメソッドですから通常のメソッドと同様にクラス内に記述します。

Public Class Employee
   Private zvBD As Variant

   Sub New()
      zvBD = DateNumber(2000, 1, 1)
   End Sub

         ・・・
End Class

このサンプルではメンバ変数 zvBD に 2000/1/1 という日付値をセットしています。


New メソッドと引数

NotesDatabase クラスを New する際にはサーバ名とファイル名を引数にセットします。

   Dim ndb As New NotesDatabase("Server1/Domino", "Sample.nsf")

これと同様のことがユーザ定義クラスでも可能です。実現方法は New メソッドに引数を指定するだけです。

例えばすでに従業員情報を管理するノーツデータベースがあったとします。前回までサンプルにしていた Employee クラスがこの DB から情報を取得する場合、次のようなイメージになります。

Public Class Employee
   Private zsEmpNo As String    '社員番号
   Private zvBD As Variant

   Sub New(ByVal vsEmpNo As String)
      Dim ndEmp As NotesDocument

      Set ndEmp = xGetEmpDoc(vsEmpNo)
      If Not(ndEmp Is Nothing) Then
         '従業員の情報をメンバ変数にセット
         zvBD = ndEmp.Birthday(0)
         zsEmpNo = vsEmpNo
      End If
   End Sub

   Private Function xGetEmpDoc(ByVal vsEmpNo As String) As NotesDocument
      '社員番号から従業員の文書を取得
         ・・・
   End Function
         ・・・
End Class

まず、インスタンスを初期化する際に社員番号を引数で与えます。社員番号から従業員データベースを検索し文書を取得します。その文書内の誕生日をメンバ変数 zvBD に保存しています。

クラスを利用する側では、次のように宣言することになります。

   Dim oEmp1 As New Employee("00123")


ディストラクタ

コンストラクタと対になる機能としてインスタンスが消滅する際に実行される処理があり、ディストラクタと呼ばれます。

LotusScript では、Delete という名称のメソッドとなります。

Public Class Employee
         ・・・
   Sub Delete()
      'ディストラクタをここに記述
         ・・・
   End Sub

         ・・・
End Class

私はあまり使用経験がないのですが、後片付けが必要なクラスで利用すると、もれなく処理を行えるので便利です。


まとめ

今回はクラスを初期化する機能を中心に、コンストラクタとディストラクタについて紹介しました。LotusScript の文法では、それぞれ New と Delete と名称が固定されたメソッドで記述します。

どちらも省略可能で、メソッド自体を定義しないと省略したことになります。


前回 クラス化に挑戦 次回


2024/05/19

クラス化に挑戦: #4)Property の役割

今回は Property ステートメントを使用したプロパティの作成について補足します。


プロパティとメンバ変数

前回作成したクラスの Birthday というプロパティに関連する部分だけを抽出すると以下の通りとなります。

Public Class Employee
   Private zvBD As Variant

   Public Property Get Birthday As Variant
      Birthday = zvBD
   End Property
   Public Property Set Birthday As Variant
      zvBD = Birthday
   End Property
   ・・・
End Class

この事例のようにメンバ変数の値をプロパティとして受け渡しするだけでいいのであれば、別の記述方法があります。メンバ変数 Birthday を Public で宣言するだけです。

Public Class Employee
   Public Birthday As Variant
   ・・・
End Class

Public なメンバ変数はクラス外からアクセスできます。コーディングの際はタイプアヘッドで表示されますので、プロパティと同等に利用できます。違いはアイコンがプロパティを表す ◆ ではないことぐらいですね。


Property ステートメントの役割

では、Property ステートメントは無用かというと、そんなことはありません。主に2つの役割があると考えます。

1つ目がアクセスの制限です。Property Get でインスタンスからの値の取得、Property Set でインスタンスに値を設定できます。両方を記述すると入出力双方向のアクセスが可能となり、片方だけ記述すれば一方通行のプロパティが作成できます。

2つ目は入力チェックです。

例えば今回の Birthday プロパティがとりうる値が過去の日付値であることが条件だったとします。この判定を実現したサンプルコードは次の通りです。

   Public Property Set Birthday As Variant
      On Error GoTo Proc_Err

      '入力チェック
      If IsDate(CStr(Birthday)) = False Then Exit Property
      If (Birthday < Today) = False Then Exit Property

      'メンバ変数にセット
      zvBD = Birthday

Proc_Exit:
      Exit Property

Proc_Err:
      'エラー発生時はメンバ変数を操作せずに終了
      Resume Proc_Exit
   End Property

このプロパティは日付値であることから Variant 型となっています。Variant は日付値に限らず数値や文字列などさまざまな値、場合によってはオブジェクトまでセットできてしまいます。まずは、これら異常値を排除する必要があります。

そこで、入力チェックの最初の処理として、入力値を文字列に変換して、それを日付型に戻せるか確認しています。

      If IsDate(CStr(Birthday)) = False Then Exit Property

文字列など日付型でない値では、この If 文で Exit されます。また、値がオブジェクトの場合は文字列に変換しようとした時点でエラーとなるので、エラー処理にジャンプした後 Exit します。

この判定を抜けた場合は日付値となります。安心して日付のチェック行い、プロパティとして受け付けてよい値か判定します。すべて問題なければ、メンバ変数に代入して値を保持させています。


まとめ

メンバ変数を Public 化するのとは違い、Property ステートメントを使用すると ”処理” を記述できます。

Set では、入力値チェックをはじめ、小文字 ⇔ 大文字や全角 ⇔ 半角変換を行い、値を整える処理を入れることができます。フォームのフィールドの『入力の確認』や『入力の変換』のような処理が記述できるということですね。

Get の場合は、メンバ変数を加工して出力したい場合に活用できます。


前回 クラス化に挑戦 次回


2024/05/17

クラス化に挑戦: #3)クラスとスコープ

前回はクラスの定義方法をまとめました。ただ、構造の解説を主題にしたので、割愛した部分があります。それが今回のテーマのスコープです。

スコープに関しては別の記事『変数や関数の宣言とスコープ』にまとめました。この記事では変数や関数に関して記載しましたが、ユーザ定義クラスも関係します。どのような動作になるのか順に確認しましょう。


ライブラリの準備

今回はよりスコープを意識できるよう、クラスはスクリプトライブラリに作成し、エージェントで利用する前提とします。

まずは、前回作成したクラスをスクリプトライブラリ lsClsSample に移動します。

続いてエージェントでこのライブラリを読み込み、前回と同様にクラスを利用してみます。すると、オブジェクト変数の宣言でエラーが発生し、クラスが利用できません。

このエラーの原因がスコープです。

ライブラリの最初に Option Declare を宣言していました。これが原因で Employee クラスは Private 扱いとなり、ライブラリ外では使用できなくなっていました。クラスを Public 宣言するとこのエラーは回避できます。

Public Class Employee

この対応で前回と同等の状態に戻りました。


プロパティやメソッドとスコープ

ライブラリに戻りプロパティやメソッドを確認すると各エントリに黄色い ■ アイコンが表示されています。

これは Private を表すアイコンのはずなのですが、エージェントから参照でき、利用もできます。これが、仕様なのかバグなのかはわからないのですが、不明瞭なので省略しない方がよさそうです。

プロパティやメソッド、メンバ変数でスコープをきちっと宣言すると次のようなプログラムになります。

Option Declare

Public Class Employee
   Private zvBD As Variant

   Public Property Get Birthday As Variant
      Birthday = zvBD
   End Property
   Public Property Set Birthday As Variant
      zvBD = Birthday
   End Property

   Public Function GetAge() As Integer
      Dim iAge As Integer

      ' 現在日付と誕生日(zvBD)から年齢を計算
      iAge = xGetAge(Today)

      GetAge = iAge '戻り値をセット
   End Function

   Private Function xGetAge(vvBase As Variant) As Integer
      Dim iAge As Integer
      Dim v As Variant

      ' 誕生日(zvBD)から基準日時点の年齢を計算
      iAge = Year(vvBase) - Year(zvBD)
      v = DateNumber(Year(zvBD), Month(vvBase), Day(vvBase))
      If zvBD > v Then
         iAge = iAge - 1
      End If

      xGetAge = iAge '戻り値をセット
   End Function
End Class

また、上記コードでは GetAge メソッドが動作するようにしています。

引数で指定した日付から年齢を計算する関数 xGetAge を追加しました。この関数はクラス内だけで使用するので Private 宣言をしています。このように明示的に宣言することでクラスのインターフェースとしたいプロパティやメソッドを明確にできます。

この対応で、デザイナーの表示も正しくなり視覚的にもインターフェースが明確になりますね。

ライブラリを利用するエージェントからは Private な関数にはアクセスできません。インターフェースを明確にすることで、安全で安心なクラスが構築できますね。


前回 クラス化に挑戦 次回


2024/05/14

クラス化に挑戦: #2)クラス定義のステートメント

クラス定義に必要なステートメントの基本的な使い方とユーザ定義クラスの基本的な構造についてまとめます。


使用するステートメント

クラス定義に使用するステートメントは主に次の通りです。

ステートメント 役割
Class クラスをユーザ定義します。
Property Get/Set クラスにプロパティを定義します。
Function
Sub
クラスにメソッドを定義します。

Function と Sub については、クラス定義以外でも使用するので、なじみがあると思います。


クラスの定義

まずはクラスの定義方法です。

Class ステートメントで記述を開始し、クラス名を指定します。以下の例では Employee がクラス名となります。そして、Class ~ End Class の間に作成するクラスの中身を記述します。

Class Employee
   (クラスの定義を記述)
End Class

ドミノデザイナーでは、クラスを作成すると左側のオブジェクトのリストに追加されます。


プロパティとメンバ変数

プロパティはインスタンスに対して値の取得と設定ができます。Property の後ろに Get をつけるとプロパティから値を取得でき、Set でプロパティに値を設定できるようになります。また、As 以降でプロパティの型を定義します。下記の例では、Birthday というプロパティは Variant 型で設定と取得ができる定義となっています。

Class Employee
   zvBD As Variant

   Property Get Birthday As Variant
       Birthday = zvBD 
   End Property
   Property Set Birthday As Variant
       zvBD = Birthday 
   End Property
End Class

当たり前ですが、Get / Set とも同じ型になります。なお、Property は Get だけを記述すると取得のみ、Set だけだと設定のみのプロパティとなります。


Class ステートメント直下には変数を宣言できます。この変数はインスタンス内の値を保持するエリアとして使用されます。

Property Get / Set ステートメントではプロパティ名が値のインターフェースとなります。例えば Set の場合は、Birthday にコール元の値が入ってきます。メンバ変数 zvBD をセットすることでインスタンス内に保持させています。


メソッド

値を返すメソッドの作成には Function、戻り値が不要の場合は Sub ステートメントを使用します。Class ステートメント内に記述すること以外は、通常の関数作成とほぼ同様です。以下の例では使用していませんが、引数を定義することもできます。

違いがあるとすれば、メンバ変数にアクセスできることが挙げられます。

Class Employee
      ・・・
   Function GetAge() As Integer
      Dim iAge As Integer

      ' 現在日付と誕生日(zvBD)から年齢を計算
      iAge = ・・・   

      GetAge = iAge   '戻り値をセット
   End Function
End Class


デザイナーとユーザ定義クラス

ここまで作成するとデザイナーでは次のように表示されます。

作成したプロパティやメソッド、メンバ変数はクラス配下のエントリとしてリストされます。リストはソートされており、クリックするとその部分のソースコードが表示できるので、効率的に開発できますね。


クラスの利用

ユーザ定義型クラスは、以下のように変数宣言の New でインスタンスが作成できます。

   Dim oEmp As New Employee

そして、定義済みのクラスと同様に、"."(ピリオド)を入力することにより、プロパティやメソッドにアクセスできます。


前回 クラス化に挑戦 次回


2024/05/12

クラス化に挑戦: #1)クラスって何?

LotusScript でのノーツアプリケーション開発では、NotesSession クラスなど Notes Object Class と呼ばれる定義済みのクラスを使用して開発します。ですので、日ごろから必ずと言っていいほどクラスを利用していることになります。

このシリーズでは、定義済みのクラスではなく、開発者自身が定義する方法をまとめます。クラスには、開発効率を高める効果がありますので、習得しておくと おトク だと思います。

なお、この記事は私自身の経験をもとに記載しています。真似事から始めて、少しずつ経験を積んできた結果をまとめたものになります。オブジェクト指向を本気で勉強し、正確な知識に裏打ちされた記事ではないので、誤解や偏りがあるかもしれません。あらかじめご了承ください。


クラスとは?

プログラミングの世界で ”オブジェクト指向” と呼ばれ、モノ(=オブジェクト)に着目した考え方があります。このオブジェクトの設計書(定義)をクラスといいます。プログラム言語的に表現すると、クラスは属性(プロパティ)と操作(メソッド)を持つデータ型といえます。

例えば、社員情報を管理するクラスを考えます。

社員情報としては、社員番号 / 名前 / 誕生日 などが考えますが、これら項目がプロパティとなります。そして『年齢を取得する』などの何らかの処理を行って結果を返すような操作がメソッドとなります。


インスタンス

クラス定義をもとに作成した実態をインスタンスといいます。クラスは設計書(定義)でしたが、インスタンスは実態ですのでデータを保持しています。

プログラム上では、インスタンスはオブジェクト変数に格納されます。例えば、先の ”社員情報” クラスで 2 名分のインスタンスを作成すると次のようになります。

◇ 山田 太郎 のインスタンス


◇ 佐藤 花子 のインスタンス


なお、インスタンスは ”オブジェクト” と呼ばれることが多いです。どちらも ”実態” で同じ意味となります。


LotusScript とクラス

LotusScript では、クラスを定義するステートメント Class が用意されています。開発者自身が自由にクラスを定義し、プログラム内で利用できます。

このステートメントを使用してクラスを定義する方法については次回にまとめます。


クラス化に挑戦 次回


2024/05/08

コメントの有効活用

前回の記事 でプログラム内のコメントについて、後任開発者のためにも手を抜かずしっかり記述することが重要だとまとめました。とはいっても、記述するにはそれなりの時間がかかります。せっかく書いたコメントですから有効に活用したいですよね。


ホップアップヘルプ

コメントを有効活用する上で効果的なのが、関数ヘッダコメントです。

8.x 以降のドミノデザイナーでは、関数やメソッドのカーソルをあてるとその関数の定義がポップアップヘルプとして表示されます。

この機能は、開発者自身が作成した関数でも有効となるのですが、ここに関数ヘッダコメントを表示することができます。


関数ヘッダコメントの書き方

別の記事 『Notes - Excel 連携:#25)罫線の設定 ①』で作成した RGB という関数を例に記載します(関数の中身は今回の記事とは何ら関係ありません)。

コメントを記載する前の状態は次の通りです。

Public Function RGB(_
              ByVal vbyR As Byte, ByVal vbyG As Byte, ByVal vbyB As Byte) As Long
    RGB = vbyR + CLng(vbyG) * 256 + CLng(vbyB) * 256 ^ 2
End Function

この関数を使用したエージェントでは、以下のようにポップアップヘルプが表示されます。


スクリプトライブラリを開き、この関数にヘッダコメントを記載します。

%REM
RGB メソッド ( lsXls ライブラリ )
---------------------------------------------------
R, G, B の各要素から Excel で使用する色番号を返します。
 
◆ 引数
vbyR  Byte 赤
vbyG  Byte 緑
vbyB  Byte 青
  
◆ 戻り値
Long
%END REM

Public Function RGB(_
              ByVal vbyR As Byte, ByVal vbyG As Byte, ByVal vbyB As Byte) As Long
    RGB = vbyR + CLng(vbyG) * 256 + CLng(vbyB) * 256 ^ 2
End Function

このように記述すると、この関数のポップアップヘルプに関数ヘッダコメントが表示されます。


変数コメントの場合

変数コメントは、定義の右側にコメントを記述することとしていました。ただ、このコメントはポップアップヘルプに表示されません(事例は変数ではなく定数)。

ポップアップヘルプの機能は、定義直上のコメントしか表示しない仕様のようで、以下のように記述すると表示されました。


ドミノデザイナーでは、右クリックメニューから [宣言を開く] を選択すると、関数や変数などの定義を参照でき、この機能を使えばコメントも確認できます。ただ、元の位置に戻る機能がないので、コメント参照のためだけに画面を切り替えるのは、作業効率が悪くなります。

関数内の変数であれば、宣言と変数の利用は近接するので参照も容易ですし、元の位置に戻るのも簡単です。このような側面から、コメントは宣言の右側とし、プログラムの行数を減らし、関数の一覧性を優先しました。蛇足ですが、関数内の変数のコメントを頻繁に参照する必要がある関数は仕様が複雑すぎるのかもしれません。独立性の観点から改善を検討すべきかもしれませんね。

先の事例のように定数宣言や Private 変数の場合や Public 変数(独立性の観点から積極的にお勧めはしませんが)では、ポップアップヘルプの活用は有効な記述方法だと思います。


まとめ

今回は、記述したコメントの有効活用を紹介しました。この方法を使えば、開発中の作業効率が上がるだけでなく、コメントの不備にも気づくことができます。コメントを記述する動機付けにもなりますので、コメントが更新されていないという状況は減るかと思います。

私の開発チームでは、スクリプトライブラリ開発において Public な関数や定数には今回のようにコメント記述するルールにしています。

2024/05/05

コメントの書き方

ほかの開発者が作成したプログラムがわかりにくいと感じませんか?

その理由は、仕様やインターフェースが不明瞭であったり、プログラムの構造が自分自身の思考とは違っていたり、コーディングの癖が生理的に合わなかったりさまざまです。

ただ、このような状況であっても、コード内のコメントが適切に記述されていれば状況は改善されています。そこで、今回はコード内のコメントの書き方について整理したいと思います。


コメントの種類

プログラム内のコメントは次の 4 つに大別できます。

変数コメント 変数の用途を記述
ブロックコメント ひとまとまりの機能や複雑な部分の説明を記述
関数ヘッダコメント 関数のインターフェースや機能を記述
ライブラリヘッダコメント スクリプトライブラリの機能や使い方を記述


変数コメント

変数コメントは、変数宣言でその変数の用途を記述するもので、変数宣言の右側に続けて記述すると場所を取らずわかりやすくなります。

まれにすべての変数に対してコメントされているプログラムを見ることがありますが、必要最小限にしておかないとかえって見づらくなります。

ループ変数など汎用的な変数、NotesSession や NotesUIWorkSpace など中身が固定的なものは不要です。

例えば、ステータスの変数では、とりうる値やその値の意味、単語を省略した変数名では誤解を招かないよう注記するなど、重要な変数だけにとどめましょう。


ブロックコメント

プログラム内に記述するコメントです。

ビューから次の文書を取得する GetNextDocument メソッドに対して『次の文書を取得』などとコメントしているプログラムを見ることがあります。このようなコードを見ればすぐわかるコメントは不要です。

ブロックコメントは、一連の機能や複雑な部分に対して説明するコメントすると有用です。例えば、あるループ処理があったとして、ループ内の機能概要やループの継続条件/終了条件を記述すると効果的です。


関数ヘッダコメント

関数の先頭に記述するコメントで、機能や使い方を記述します。例えば次のような項目です。

  • 関数の機能
  • 引数の型と説明
  • 戻り値の型と説明
  • 外部参照(この関数を利用するために必要な設計要素)
  • 使い方

これらが記述されていると、安心して関数を利用できます。すべての関数について記述することが望ましいですが、少なくともパブリックな関数には必ず記述しましょう。


ライブラリヘッダコメント

スクリプトライブラリの機能や使い方を記述するコメントで、スクリプトライブラリの先頭である Options セクションの先頭に記述します。

記述する内容は次のような項目です。

  • ライブラリ名称
  • 最終更新者/更新日
  • 作成者/作成日
  • バージョン
  • 外部参照(このライブラリを利用するために必要な設計要素)
  • 使い方
  • 更新履歴


不要なコメントの削除

プログラムの修正履歴や修正前のコードをコメントして一時保管することがよくあります。修正を繰り返したプログラムだとコメントだらけになり、現在のコードのためのコメントがどれか判別できなくなります。

不要となったコードの残骸、修正前のコードのコメント、必要以上の更新履歴は削除するようにしましょう。


まとめ

プログラム内に適切にコメントがあると、プログラムの理解が早くなります。

後任の開発者が最初によりどころとするのはコメントです。プログラムの動作に関係なくても、ビジネス上は重要な役割を持つといってもいいでしょう。手を抜くことなく、しっかり記述しましょう。また、修正時にはコメントもメンテナンスするとよいかと思います。


2024/05/02

関数の引数と値渡し

チーム開発』のラベルを付けた前回の記事『関数のインターフェースと独立性』では、関数作成において、引数と戻り値以外のインターフェースをなくせば、独立性が高くなり、後任者に伝わりやすいプログラムになることを紹介しました。

今回は、この引数にもう少し掘り下げたいと思います。


引数のタイプ

VBA の言語仕様として、関数の引数には、値渡しと参照渡しの 2 つのタイプがあります。


◇ 値渡し

値渡しの特徴と使い方は次の通りです。

  • 引数の値はコピーされ関数に渡される
  • 関数内で値を変更しても呼び出し元の値は変更されない
  • 値渡しは、引数の定義で ByVal を指定すること

図式化すると次のようになります。

まず、外側のプログラムで、関数 Sample を呼び出します。引数 a には "Lotus" と入っていて、関数内の変数 b で値を受け取ります。関数 Sample 内で、b の値を "Script" に変化させていますが、関数を抜けた後の変数 a の値に変化はありません。


◇ 参照渡し

参照渡しには次のような特徴があります。

  • 値を保持しているメモリ領域のアドレスを関数に渡す
  • 同じメモリ領域を共有するので、関数内で値を更新すると呼び出し元の値も変更される

先の事例を参照渡しに変更した場合を同じく図式化すると次の通りとなります。

参照渡しの場合は、関数内で値を変更すると呼び出し元の変数 a の値が更新されます。

値渡しの場合、引数の宣言で ByVal と記載しました。LotusScript では、参照渡しを指定するキーワードがありません。ByVal をつけないと参照渡しとなります。


引数の使い方

引数のタイプを指定するには条件があります。

値渡しが設定できるのは、数値や文字列などの単一のデータを表す変数(スカラー型)となります。引数の宣言で ByVal と記述すると値渡し、省略すると参照渡しとなります。

配列や Variant、オブジェクト変数は値渡しは指定できません。必ず参照渡しとなります。


引数のタイプと関数の独立性

引数と戻り値以外のインターフェースを持たない関数を前提に考えます。

引数が値渡しの場合、関数内でどのような処理が行われたとしても、呼び出し元に影響を与えるのは戻り値だけとなります。逆に引数が参照渡しの場合、関数内の処理によっては値が書き換わる可能性があるということです。

独立性という側面で考えた場合、値渡しでよい引数は ByVal と記述することで独立性を高められるということになります。


チーム開発と引数

以前、紹介した『命名規則の例(LotusScript)』で変数の命名規則について記述しました。変数名の先頭がスコープやコーディング上の用途を指定していました。この ”用途” にあたる部分が今回の引数のタイプに該当します(命名規則をまとめたページはこちら)。

下図の通り紹介したのですが、赤枠の部分が引数に関する部分です。

例えば、次のように記述します。

   Sub Sample( ByVal vsName As String )

   Sub Sample( rsName As String )

これで変数名を見るだけで、値渡しか参照渡しかの判定が可能となります。


同様の命名規則は、スカラー変数以外にも適用しています。実際には、値渡しは設定できないのですが、変数名で次のように使い分けています。

vndSample 関数の外側から与えられる文書
関数内で文書を変更しない
rndSample  関数内で文書をセットし呼び出し元へ返す変数

vndSample では、文書は変更しないとなっていますが、別の文書に差し替えない限り、フィールドのセットなどの更新までは禁止していません(利便性優先)。


まとめ

今回は関数の引数にフォーカスして、引数の値渡しと参照渡しについて紹介しました。

かなり細かな話になりましたが、こういった機能があることを理解し、活用することで、より見やすい、理解しやすいプログラムを作成できるようになります。参考になれば幸いです。