どうも。つじけ(tsujikenzo)です。このシリーズでは、2021年9月から始まりました「ノンプロ研GAS中級講座6期」について、全7回でお届けします。最終回の7回目を、5回に渡ってお送りしています。それって、全12回じゃないのでしょうか。。。
前回のおさらい
前回は、「外部設計」をお届けしました。
今回は、3回目で「内部設計・コーディング・テスト」です。
「テスト」という工程は、ノンプログラマーにはあまり馴染みがありません。
今日は、「テスト駆動開発(っぽいもの)」という手法を取り入れながら、プログラミングしていきます。
今日のアジェンダ
- プロジェクトを準備する
- ファイル内構成
- テスト駆動開発
- コンストラクタとメソッドの実装
プロジェクトを準備する
まず、プロジェクトを用意します。実際に使用するスプレッドシートの、コンテナバインドスクリプトでいいでしょう。
クラスごとのスクリプトファイルを用意する
クラス図を見ながら、1クラスにつき1スクリプトファイルを作成します。
スクリプトファイル名は「class_○○」で統一します。
onOpen()には、01_のような番号を振ると分かりやすいでしょう。
さっそく、スクリプトファイルの中身を書いていきます。
順番はどれからでもかまいません。チームで開発するばあいは、クラスの担当を分けて、GitHubなどにPUSHしていくのもいいでしょう。
ファイル内構成
スクリプトファイル内は、このような構成になります。
クラス系ファイル
クラス系ファイルは、かならず上にクラスを書き、下にクラスをテストする関数を書きます。
//class クラス{ }
//function クラスのテストをする関数(){ }
onOpenなどの実行用関数系
同様に、onOpenなどの実行用関数系ファイルも、下にfunctionをテストする関数を書きます。
//グローバル領域
//function onOpen(){ }
//function onOpenで呼ばれる関数1(){ }
//function onOpenで呼ばれる関数2(){ }
//function スクリプトファイル内のテストをする関数(){ }
まずは、これらのコメントを、スクリプトファイルにコピペしておいてもいいでしょう。
テスト駆動開発
クラスや、実行用関数を書くときは、まず、呼び出し側のテスト関数から書き始めます。
テスト関数の名前は、かならず「testクラス名」で統一します。
たとえば、DataSheetクラスのテストをする関数なら、「testDataSheet」という具合です。
まだ、中身はなにもありませんが、このような手順で書いてみましょう。
/** Dataシートクラス */
class DataSheet {
}
/** TEST関数 */
function testDataSheet() {
}
日本語でテスト関数を書く
テスト関数は、DataSheetクラスがちゃんと動くかどうか、テストをするための関数です。
なので、DataSheetクラスがもつ、状態や振舞いを、1つ1つ確認します。
たとえば、DataSheetクラスは、なにかしらのプロパティをもつはずですので、インスタンスが生成されたら、インスタンスとその中身を確認するテスト関数を 「日本語で」 書きます。
/** Dataシートクラス */
class DataSheet {
}
/** TEST関数 */
function testDataSheet() {
//インスタンスの確認
//シートがつかめているか確認
//シート名の確認
}
メソッドも同様に、DataSheetクラスの振舞いを確認するものなので、1つ1つ確認します。
テスト関数のコツは、「メソッドでやることは1つ」という原則を守ることです。
メソッドはとても小さな処理になります。それでかまいません。
クラスがやる処理をすべて書き起こして、日本語でテスト関数を書きます。
/** Dataシートクラス */
class DataSheet {
}
/** TEST関数 */
function testDataSheet() {
//インスタンスの確認
//シートがつかめているか確認
//シート名の確認
//全てのRecordsをobjArrayで取得するメソッド
//Recordsのlengthを確認するメソッド
//Dataシートの最終行を返すメソッド
//Dataシートの最終列を返すメソッド
//受け取ったobjArrayを貼り付けるメソッド
}
ある程度、テスト関数を日本語で書き終えたら、テスト関数のコードを書きます。
テスト関数では、ログ出力をもってコードが動いている、という確認をします。
なので、すべてのテスト関数に console.log() を書きます。
マルチカーソル機能(Alt + クリック) を使って、コーディングを効率化していきましょう。コピペより早いです。
console.log()の中に、確認するプロパティやメソッドを書いていきます。
チームで開発するさいは、テスト関数を書いて、次の人にパスしてあげると、次の人も作業がしやすいですね。
ということで、すべてのクラス(onOpenなども含む)の、テスト関数を日本語で書くことからはじめましょう。
/** Dataシートクラス */
class DataSheet {
}
/** TEST関数 */
function testDataSheet() {
//インスタンスの確認
const d = new DataSheet();
console.log(d);
//シートがつかめているか確認
console.log(d.sheet);
//シート名の確認
console.log(d.sheet.getName());
//全てのRecordsをobjArrayで取得するメソッド
console.log(d.getDataSheetValues());
//Recordsのlengthを確認するメソッド
console.log(d.dataSheetRecordsLength());
//Dataシートの最終行を返すメソッド
console.log(d.getCustomLastRow());
//Dataシートの最終列を返すメソッド
console.log(d.getCustomLastColumn());
//受け取ったobjArrayを貼り付けるメソッド
console.log(d.setCustomValues());
}
コンストラクタとメソッドの実装
テスト関数ができたら、クラスの中身を書いていきます。
コンストラクタになにをもたせて、インスタンスを生成するかは、非常にむずかしいポイントです。
絶対これが正しい、という正解はありませんで、まずは、自分のイメージと合うコンストラクタを書いていいと思います。
「この方が便利!かっこいい!」と思うものがあれば、修正しながら、でいいと思います。
/** Dataシートクラス */
class DataSheet {
/**
* @constructor
*/
constructor() {
this.id = SHEET_ID; //onOpen.gsのグローバル領域に定義しています。
this.sheetName = `Data`;
this.sheet = SpreadsheetApp.openById(this.id).getSheetByName(this.sheetName);
}
}
メソッドは、短く、純粋関数(戻り値を返すだけの関数)で書きます。
set系のメソッドには、テキストメッセージを持たせ、メソッドはかならず戻り値を持つようにします。
手順としては、まず、テスト関数をそのままクラス内に貼り付けます。
/** Dataシートクラス */
class DataSheet {
/**
* @constructor
*/
constructor() {
this.id = SHEET_ID; //onOpen.gsのグローバル領域に定義しています。
this.sheetName = `Data`;
this.sheet = SpreadsheetApp.openById(this.id).getSheetByName(this.sheetName);
}
//シート名の確認
console.log(d.sheet.getName());
//全てのRecordsをobjArrayで取得するメソッド
console.log(d.getDataSheetValues());
//Recordsのlengthを確認するメソッド
console.log(d.dataSheetRecordsLength());
//Dataシートの最終行を返すメソッド
console.log(d.getCustomLastRow());
//Dataシートの最終列を返すメソッド
console.log(d.getCustomLastColumn());
//受け取ったobjArrayを貼り付けるメソッド
console.log(d.setCustomValues());
}
マルチカーソル機能(とくにCtrl+矢印キーが大活躍します)を使って、メソッドに仕上げていきます。
メソッドは、かならず戻り値を持たせますので、すべてのメソッドにreturnを持たせます。
return文には、変数を持たせるように統一します。
returnで返す変数、ということは、変数宣言するはずですので、変数宣言も用意しておきます。
変数名はとりあえずvalueでいいでしょう。
これだけ準備しておくだけでも、コーディングがものすごく早くなります。(コーディング時に悩みが少なくなります)
/** Dataシートクラス */
class DataSheet {
/**
* @constructor
*/
constructor() {
this.id = SHEET_ID; //onOpen.gsのグローバル領域に定義しています。
this.sheetName = `Data`;
this.sheet = SpreadsheetApp.openById(this.id).getSheetByName(this.sheetName);
}
/** 全てのRecordsをobjArrayで取得するメソッド
* @return {Object} value
*/
getDataSheetValues() {
const value = '';
return value;
}
/** Recordsのlengthを確認するメソッド
* @return {Object} value
*/
dataSheetRecordsLength() {
const value = '';
return value;
}
/** Dataシートの最終行を返すメソッド
* @return {Object} value
*/
getCustomLastRow() {
const value = '';
return value;
}
/** Dataシートの最終列を返すメソッド
* @return {Object} value
*/
getCustomLastColumn() {
const value = '';
return value;
}
/** 受け取ったobjArrayを貼り付けるメソッド
* @return {Object} value
*/
setCustomValues() {
const value = '';
return value;
}
}
そして、すべてコーディングが終わったら、テスト関数 testDataSheet() を実行します。
ここで発生するエラー(タイプミスや浮動小数点など)のことを、バグと呼びます。
テスト関数を実行しながら、バグを1つずつつぶしていきます。
「バグに対処するために、もうすこし早めに、コードが動くかどうかテストしたい」という声もあると思います。
しかし安心してください。バグの必要な修正箇所は、比較的見つかりやすいです。
それは、メソッドが小さいからです。メソッドとメソッドは複雑に絡み合っていません。
このような書き方を「疎結合(そけつごう)」と言います。
メソッドを書くときは、疎結合を意識していきましょう。
まとめ
以上で、「内部設計・コーディング・テスト」をお送りしました。
チームで開発をするときなど、他人のコードが、コーディングガイドラインに違反した書き方だと、ついつい口を出してしまうものです。
しかし、まずはテスト関数が動いているなら中身(書き方)は問わない、という安心感を提供することで、チームの士気を高めます。
リファクタリングもしかりで、まずはコードが動くことを確認しながらコーディングするテクニックを、取り入れてみましょう。
テスト駆動開発、おススメです。
次回は、 「クラス設計のコツ」 をお届けします。
このシリーズの目次
[GAS]オブジェクト指向によるGAS開発のススメ