0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LibreOfficeでVBA開発して分かった環境差異と解決方法

0
Posted at

Class Moduleが使えない

Excel VBAではClass Moduleを利用できますが、LibreOffice BasicにはClass Moduleがありません。

困ったのは「クラスが書けないこと」そのものではなく、関連するデータを一つのオブジェクトとして管理できなくなることでした。

例えば、座標や種類など複数の値をひとまとまりのデータとして扱う場合、これらを個別の変数や配列で管理すると、関連性が分かりにくくなり、保守もしづらくなります。

そこで、内部実装としてDictionaryを採用しました。Dictionaryはキーごとに異なる型の値を保持できるため、オブジェクトのフィールドを表現する用途に適していました。

ただし、Dictionaryのキーを直接文字列で指定すると、

obj("name")
obj("type")
obj("value")

のようなコードが至る所に現れます。キー名を文字列で直接指定すると、タイプミスがあっても構文上は問題ないため、実際にそのコードが実行されるまで気付きません。そのため、保守性が低下します。

そのため、呼び出し側からはDictionaryを直接操作せず、アクセサ関数を経由する設計にしました。

ObjName(obj)
ObjType(obj)
ObjValue(obj)

これにより、フィールド名を一箇所に集約でき、タイプミスによるバグを防げます。また、呼び出し側は内部実装を意識せずに済みます。

アクセサ関数を使用

結果として、Excelへ移植した際もアクセサ関数はそのまま利用でき、環境依存部分を局所化できました。

Dictionaryへオブジェクトを格納できない

今回の開発で最も苦労したのが、この問題でした。

Excel VBAではDictionaryへオブジェクトを格納できますが、LibreOfficeでは同じコードが期待どおりに動作しませんでした。

実行時エラーが発生したり、格納したはずのオブジェクトがEmptyとして取得されたりすることもあり、原因の特定にはかなり苦労しました。
最終的にはLibreOfficeの互換実装による制約と判断し、設計を変更することにしました。

当初は、Dictionaryがオブジェクトを直接保持する構成でした。

Dictionaryがオブジェクトを管理

しかし、この構成はLibreOfficeでは利用できませんでした。

そこで、オブジェクトをDictionaryへ直接格納することをやめ、Dictionaryにはオブジェクトを識別するIDだけを保持し、オブジェクト自体はCollectionで管理する構成へ変更しました。

Collectionがオブジェクトを管理

オブジェクトが必要になったタイミングで、IDを利用してCollectionから取得します。

Set obj = GetObjectById(id)

オブジェクトを取得する処理が一段増えたため、当初想定していた設計から変更する必要がありました。
あわせて、オブジェクトを直接扱う前提で実装していた一部のコードも修正しました。

一方で、オブジェクトの保存先をCollectionへ一本化したことで、オブジェクトを一箇所で管理できるようになりました。

この構成はExcelへ移植した後もそのまま利用しています。結果として、LibreOffice固有の制約を吸収しながら、他のコードへの影響を最小限に抑えることができました。

ExcelとLibreOfficeでセル配列の構造が異なる

ExcelとLibreOfficeでは、セル範囲を配列として取得した際の構造が異なります。

Excelでは二次元配列として取得できますが、LibreOfficeではジャグ配列(配列の配列)として取得されます。

そのため、同じコードでセルを参照すると期待した動作にならず、実行時に問題が発生しました。

そこで、読み込んだ直後に共通の形式へ正規化するようにしました。

配列構造の共通化

Excel・LibreOfficeのどちらも、読み込み直後に共通の二次元配列へ変換しています。実装では、0始まりの二次元配列へ正規化しました。

こうすることで、以降の処理では「ExcelかLibreOfficeか」を意識する必要がなくなりました。

環境差異を後続の処理まで持ち込むと、分岐が各所に増え、保守が難しくなります。

今回の開発では、入出力を担当するエントリポイントで環境差異を吸収したことで、それ以降のロジックは共通コードのまま実装できました。

NothingNullの扱いが異なる

オブジェクトや配列を返す関数を実装していると、戻り値が存在しない場合の扱いで悩みました。

同じようにNothingを返すコードを書いても、ExcelとLibreOfficeでは判定結果が異なり、期待した条件分岐にならないことがありました。

当初はNothingNullを判定して対応しようとしましたが、環境ごとの差異を意識したコードが増えてしまいます。

そこで、判定方法を増やすのではなく、戻り値の契約を統一する方針に変更しました。

具体的には、戻り値が存在しない状態をNothingNullで表現するのではなく、型ごとに既定値を返すようにしました。

既定値
配列 正常系では現れない番兵値を持つ配列
Dictionary 要素数0のDictionary

これにより、呼び出し側は環境ごとのNothingNullの違いを意識する必要がなくなり、既定値だけを判定すればよくなりました。

今回のように複数の実行環境へ対応する場合は、判定を複雑にするよりも、戻り値の契約を統一する方が保守しやすいと感じました。

Errオブジェクトが使えない

Excel VBAでは、Errオブジェクトを利用してエラー番号やエラーメッセージを取得できます。

ユニットテストでは、不正な入力に対して例外を発生させ、エラーメッセージまで含めて検証していました。

しかし、LibreOffice BasicではExcel VBAと同じようにErrオブジェクトを利用できませんでした。
そのため、Excelと同じ例外検証をそのまま共通化することはできませんでした。

今回は、この差異を無理に吸収しようとはせず、テスト方針を変更しました。

LibreOfficeでは、エラーが発生した時点で異常系が正しく動作したと判断し、エラーメッセージの内容までは検証しませんでした。

一方、ExcelではErrオブジェクトを利用し、エラーメッセージまで含めたテストを実施しています。

つまり、

  • LibreOffice:例外が発生することを確認
  • Excel:例外の内容まで確認

というテスト方針にしました。

環境差異を完全になくそうとすると、かえって設計やテストが複雑になることがあります。

今回は、共通化できない部分は無理に合わせるのではなく、それぞれの環境で実現できる範囲をテストする方針を採用しました。

すべてを共通化することだけが良い設計ではなく、要件に応じて割り切ることも重要だと感じています。

デバッガが弱い

Excel VBAでは、デバッガのウォッチ式を利用して関数の戻り値や式の評価結果を確認しながらデバッグできます。

しかし、LibreOfficeのデバッガではこの機能が十分ではありませんでした。

例えば、関数の戻り値をその場で確認したい場面があります。

GetValue(key)

Excelであればウォッチ式で簡単に確認できますが、LibreOfficeでは同じような調査が難しく、期待した値になっているかをその場で確認できませんでした。

そのため、一時的にMsgBoxを追加して値を表示し、確認しながら開発を進めました。

MsgBox GetValue(key)

開発効率は多少落ちるものの、必要な箇所だけ一時的に表示を追加することで十分対応できました。

私は普段VSCodeのデバッガを利用しているため、関数の戻り値をその場で確認できないことが特に不便でした。しかし、MsgBoxを使った確認に切り替えることで、開発を進めることはできました。

AIを利用して分かったこと

今回の開発では、Gemini、ChatGPT、Codex、GitHub Copilotを利用しました。

AIごとに得意・不得意はあると思いますが、今回のようなLibreOffice Basicを利用した開発では、大きな使用感の差は感じませんでした。

1. Utility関数の作成

最も役立ったのは、Utility関数の作成です。

定型的な処理であれば、短時間で十分実用的なコードを作成してくれました。内容の確認や計算量の検証は必要でしたが、開発効率は大きく向上しました。

2. 変数名の提案

次に役立ったのは、変数名の提案です。

コードの意図を読み取り、適切な名前を付ける能力は高く、自分では思いつかないような分かりやすい名前を提案してくれることもありました。

3. ドキュメント作成

ドキュメント作成も非常に優秀でした。

多少の修正は必要でしたが、仕様書やコメントの整理を短時間で行えるため、自分で一から作成するよりも効率的でした。

4. テストコード

テストコードも短時間で生成できる点は便利でした。

一方で、仕様の抜け漏れがあることもあり、そのまま利用するのではなく、レビューを前提として活用していました。

今回の開発を通して感じたのは、AIは「人間の代わり」ではなく、「ペアプログラマ」として利用すると非常に優秀だということです。

最終的な設計判断やコードレビューは人間が担う必要がありますが、それぞれの得意分野を活かすことで、開発効率を大きく向上させられました。

AIにLibreOfficeのテストを実行させる準備

今回の開発では、AIにテストコードの作成だけでなく、実際のテスト実行も任せていました。

その際、次の3点を事前に設定しておくことで、スムーズに開発を進められました。

sofficeにPATHを通す

AIはコマンドラインからLibreOfficeを起動してテストを実行します。

そのため、sofficeコマンドを実行できるよう、あらかじめPATHを設定しておく必要がありました。

LibreOfficeを終了しておく

LibreOfficeが起動したままだと、AIによるテスト実行が正常に行えないことがありました。

原因までは調査していませんが、事前にLibreOfficeを終了しておくことで、安定してテストを実行できました。

日本語を含むパスを避ける

LibreOfficeでは、日本語を含むパスで正常に動作しない場面がありました。

そのため、プロジェクトは英数字のみのパスへ配置することをおすすめします。

これらを最初に設定しておけば、その後はAIによるテスト実行をスムーズに行えるようになります。

AIを活用してLibreOffice Basicを開発する場合は、最初に環境を整えておくことをおすすめします。

まとめ

今回紹介した内容は、どれもLibreOfficeとExcel VBAの環境差異によって発生した問題です。

Class Moduleが使えないことや、配列構造・型・例外処理の違いなど、いくつかハマりどころはありました。しかし、それぞれ設計や実装を工夫することで対応でき、最終的には約3000行のコードを、約130行の修正だけでExcel VBAへ移植できました。

重要だったのは、環境差異を後続のロジックへ持ち込まず、できるだけ早い段階で吸収する設計にしたことです。

早期に環境依存を吸収することで共通ロジックを使用できる

また、AIはUtility関数の作成やドキュメント整理などで開発効率を大きく向上させました。一方で、最終的な設計判断や環境依存の問題については、人間による検証が欠かせませんでした。

LibreOfficeはExcel VBAと完全に同じ環境ではありません。しかし、環境差異を設計で吸収することを意識すれば、十分に実用的な開発環境になります。

この記事が、自宅にExcelがない方や、LibreOfficeを代替環境として検討している方の参考になれば幸いです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?