Google Apps Scriptでクラス構文を活用する【GAS】
V8 Runtime対応になったGoogle Apps Scriptですが、長年ES5での制限を受けてきた故に、ウェブ上でのコード紹介でも殆どが平打の関数を構築して呼び出すといった事による、原始的なコード記述も多く、またライブラリの場合ワンクッション入るせいか実行が遅いということで、コードの再利用性などが積極的に進んでいない現状があります。
元々大規模開発等ではGASは使わないという面もありますが、無理してクラスを使わなくても十分なアプリを構築可能であるため(小規模が多い為使うまでもない)、スルーされがちです。今回はV8から使えるようになったクラスについて、使い所や実際の現場で使うようなシーンを想定して掘ってみたいと思います。事前に以下のエントリーのClass構文も読んでおくと良いでしょう。
※自分自身は、GASについてはES2023までは対応してるのを確認しています。
目次
今回使用するサンプルファイル
Google Apps Scriptでクラス構文を使うためには、「V8ランタイム」はオンにしておく必要があります。プロジェクト設定を開き、「Chrome V8 ランタイムを有効にする」にチェックを入れましょう。以前はV8ランタイムでクラスを利用すると非常に遅いという事があったのですが、現在は解消されている模様です。
今回は固定資産の減価償却を計算するシートでclassを使ってみたいと思います。現時点ではES2019辺りまでの仕様は使えるみたいで、プライベートフィールドやプロパティなどのES2022からの仕様などはエラーとなり使えません。
※現在、ファイルの位置に関係なく定義したクラスは利用出来るので、クラスのファイルよりも下で呼び出さないとエラーになるといった現象はありません。
図:V8ランタイムで利用が可能になります。
クラスの使い所
関数のみでは駄目なのか?
単純なスクリプトであるならば、Classを使うメリットは殆どありませんが、ある程度の規模のプログラムになってくると、堪える。という話をよく聞きます。関数をいくつも作って、それを呼び出してプログラムを作るというスタイルでは何故堪えるのか?という視点で考えます。
今回のテーマのように固定資産の減価償却だと、個々の固定資産が持ってる情報はすべて必要でありながら、中身が異なっています。償却期間であったり、償却法、取得原価等。これらを回すにあたって、関数だけで行う場合、スプレッドシートとの読み書きが頻繁に発生する事となるため、大きなグローバル変数で配列を用意して、そこにこれらの設定値(プロパティ)を用意して、1個ずつ格納が必要になります。
さらに減価償却にまつわる様々な関数や処理、現在の設定値の取得等をするにあたっても、それらを考慮する必要があります。
この時、設定の塊を1つのテンプレートとして用意し、そこにまつわる専用の関数をメソッドチェーンで呼び出したりする為に使うのがクラスです。Google Apps ScriptだとGASで用意された各種クラスを皆さん普段から使っていたりします(JSのnew Date()もクラスと言えます)。
new Classでインスタンスというのを作るとこのテンプレートの塊が定義されて、個々に償却期間や償却法、原価取得がそれぞれ定義、それを処理する関数はclassを渡して上げればより簡単に処理が出来るわけです。
クラスの中身はどうなってるのか?
減価償却の場合、個々の固定資産の持つ情報や様々な計算方法などを一塊にしたものを用意するわけですが、情報は多岐に渡る上に償却法が異なる等、実際にこれをスプレッドシートを使って、年毎に資産毎に読み書きをしてると非常に煩雑になってしまいます。
クラスの基本的な構造は以下の要素で成り立っています
基本中の基本
クラスを作るとなった場合にまずは一番外側を作ります。そしてそれを、new classnameにて個々にインスタンスというものを作ってから変数に格納。その変数に対して、メソッドチェーンで関数を呼び出したり、命令を与えると計算結果や現在の値が返ってくるという仕組みになっています。
1 2 3 4 5 6 7 8 9 |
//クラスを定義 class assetman { } //クラスを元にインスタンスを作る var asset = new assetman() |
コンストラクタ
必ず定義する必要のあるのが、コンストラクタ。インスタンスを作成する時に引数で受け取って、個々の要素のプロパティというものを格納しておくものです。ここに、固定資産の各種値を入れておく事が可能です。個別の資産毎に作成します。
また、ただ引数で受け取って格納するだけでなく、計算をして初期値を格納する事も可能。以下の事例では償却補償額を引数では受けずに、取得価額 × 保証率で計算して、this.hosyouに格納しています。コンストラクタ内に定義した変数はパブリックになります。var _test = 0.5みたいな書き方でプライベート風に出来ますが、コンストラクタ内でだけしか使えないです。
※コンストラクタの外側にグローバルプロパティを作る事は出来ません
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
//クラスを定義 class assetman { //コンストラクタを定義 constructor(aname, s_kagaku, s_nen, s_type, s_per, k_per, h_per) { //プロパティをセットする this.aname = aname; //資産名 this.s_kagaku = s_kagaku; //取得価額 this.s_nen = s_nen; //償却年数(耐用年数) this.s_type = s_type; //償却方法 this.s_per = s_per; //償却率 this.k_per = k_per; //改定償却率 this.k_per = h_per; //保証率 //保証率を元に償却補償額を定義する(小数点以下四捨五入) this.hosyou = Math.round(s_kagaku * h_per); } } //クラスを元にインスタンスを作る var asset = new assetman("自動車","1000000",5,"定額法", 0.200, 0.250, 0.06552) |
この時、変数assetに格納された内容をconsole.logで出力すると以下のようになります。asset.s_nenとして出力すれば償却年数の値を取得する事が可能になります。逆に、asset.kagaku = 2000000として実行すれば、プロパティの値を変更する事が可能です。
1 2 3 4 5 6 7 8 |
{ aname: '1000000', s_nen: 5, s_type: '定額法', s_per: 0.2, k_per: 0.06552, hosyou: 65520 } |
関数を定義する
クラスで利用する関数を定義する事が可能です。クラス内に存在するthisの変数を利用し、引数を取って計算する仕組みを作ってみます。getAssetNameという関数を定義して、資産名を取り出して返すだけの関数です。
asset.getAssetName()にてこの関数が呼び出されて、プロパティの値を取得する事が可能です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
//クラスを定義 class assetman { constructor(aname, s_kagaku, s_nen, s_type, s_per, k_per, h_per) { //プロパティをセットする this.aname = aname; //資産名 this.s_kagaku = s_kagaku; //取得価額 this.s_nen = s_nen; //償却年数(耐用年数) this.s_type = s_type; //償却方法 this.s_per = s_per; //償却率 this.k_per = k_per; //改定償却率 this.h_per = h_per; //保証率 //保証率を元に償却補償額を定義する(小数点以下四捨五入) this.hosyou = Math.round(s_kagaku * h_per); } //資産名を返す関数 getAssetName() { return this.aname; } } //クラスを使ってみる function testasset(){ //クラスを元にインスタンスを作る var asset = new assetman("自動車","1000000",5,"定額法", 0.200, 0.250, 0.06552); console.log(asset.getAssetName()); } |
staticメソッド
変動することの無い予め格納しておいた値を返す為だけに利用するタイプで利用するもので、staticと付けて関数を定義するだけです。ただし、thisが使えない為、プロパティ等にはアクセスが出来ません。ちょっと変わった要素と言えます。
今回は3匹のモンスターを定義し、それぞれのインスタンスを生成。それらの持つ経験値の平均を取る為のstaticメソッドで計算して返すというものを作ってみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
//敵モンスター用クラス class monster { //コンストラクタを定義 constructor(prop) { //プロパティをセットする this.name = prop.name; this.hp = prop.hp; this.mp = prop.mp; this.exp = prop.exp; } //経験値の平均を出す static avgExp(monsters = []) { if (monsters.length === 0) { //対象が居ないので0を返す return 0; } else { //合計用の変数 let sumexp = 0.0; //各モンスターの経験値を合計に加算していく for (let mom of monsters) { sumexp += mom.exp; } //パーティモンスターの経験値の平均を出す return sumexp / monsters.length; } } } //各モンスターを定義する const monster1 = new monster({ name: 'スライム', hp: 8, mp: 0, exp: 4 }); const monster2 = new monster({ name: 'メタルスライム', hp: 4, mp: 999, exp: 1000 }); const monster3 = new monster({ name: 'はぐれメタル', hp: 6, mp: 999, exp: 10000 }); //各モンスターインスタンスの経験値の平均を出す function monexpAvg(){ // staticメソッドの呼び出し let avgexp = monster.avgExp([monster1, monster2, monster3]); console.log(avgexp); } |
このように、thisでコンストラクタ内のプロパティを利用して計算する為のものというよりも、クラス自身が持ってるユーティリティな機能として実装する場合に使うといった感じです。故にクラスですが通常のライブラリのメソッドのような動きをする、そのクラス特化の関数を格納しておくものとして定義すると良いでしょう。そのため、インスタンス化せず、いきなりmonster.avgExp()として利用できます。
計算結果としては、3668が返ってきます。
プライベートプロパティ風な手法
Google Apps Scriptの場合、現時点ではコンストラクタの外側に変数定義をしたり、クラス内部でだけ利用するプライベートプロパティを利用する事が出来ません。そのため、コンストラクタ内の変数には簡単にアクセス出来る反面、簡単に書き換えも出来てしまう為、安全面では難があります。
しかし、以下の即時関数 + Symbol + クラスを利用した場合には、外部からの値を格納後には簡単に書き換えも出来ないようにする事が可能です。次項のセッターを使ったりゲッターを使わないと取得できないように制限を課すことが可能なので、この手法はGASでクラスを使う場合には必須のテクニックかもしれません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
//Item情報クラスを作成する var Itemman = (function() { //Symbolで変数を定義 const name = Symbol('name'); const price = Symbol('price'); //クラスを定義 class Itemman { constructor(value,value2) { this[name] = value; this[price] = value2; } //格納されてる商品名を返すゲッター get ItemName() { return this[name]; } //販売価格を返す(15%上乗せ) getPrice(){ return this[price] * 1.15; } } return Itemman; })(); function itemimporter(){ //クラスからインスタンスを作成する var item = new Itemman("砂糖",5000); //情報を色々取得してみる console.log(item) console.log(item.getPrice()); console.log(item.ItemName); console.log(item.name); } |
- クラスを即時関数で囲っておく(即時関数の変数名とクラス名は一致させておく)
- 即時関数内ならば、グローバルで変数を定義出来る
- ただし直接変数名を定義して値を格納するのではなく、Symbolを使って固有のIDを生成しておく。
- クラス内のコンストラクタではthis[Symbolの変数]にて、プロパティを定義し、コンストラクタの引数がインスタンス生成時の引数になる
- ゲッターであるItemNameを使わないと直接格納された情報にアクセスは出来ません。セッターについても同じ。
- 変数を呼び出す時も、this[Symbolの変数]にて呼び出し、計算してreturnすれば計算結果として返すことは可能です。
- 上記に於いてconsole.logの出力結果は以下の通りになります。console.logで変数名だけだと何も出力されず、またitem.nameとしてもコンストラクタの変数に格納した値は取れません。
この仕組を使うと、外部から途中の変数を取得したりが不可能になるので、例えばOAuth2.0認証のAccess Tokenの取得であったり、その暗号化・復号化といった処理をクラスに作り込めば隠蔽できるので、安全性も高まります。
図:理想の形で値の取得を制御出来た
ES2022のクラスの新機能を調査
2024年6月、現在のGoogle Apps Scriptがどこまで最新のJavaScriptに対応してるのか?を調べてみたところ、2023まで追従してることが判明。ということは、ES2022で追加されたプライベートプロパティにも対応してるのでは?ES2022のClass FieldsやPrivate Propertyなどのクラスの機能が使えるようになってるのでは?ということで、追加調査してみました。
- Static変数については未対応でした。
- Class Fieldsについても未対応でした。
- Private Propertyについても未対応でした。
ES2023まで対応してるといっても、Class構文についてはES2023対応ではないようです。
ゲッターとセッター
通常、コンストラクタに定義したプロパティの内容は、例えばitem.nameといったものや、item.getName()といった形で簡単に取得する事が可能です。逆を行えば値をセットすることも可能。なので、敢えてゲッターやセッターという仕組みを使用せずとも、目的の処理は実現できますが、前述のように入力制限を課してるクラスの場合は直接値を取得できません。
そこで getやsetという文字を付けた関数を用意する事で、それぞれ値の取得、値の書き換えを実現するようにコードを組み、ゲッターとセッターからしかアクセスさせないようにすれば、より安全なクラスを作る事が可能です。ただし、コンストラクタ内のプロパティは読み書きが出来てしまうので、一度格納したら変更できないようにする場合は、上記のコードで言えばセッターで参照させなければOKです。
※アクセッサプロパティと総称されることもあります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
//Item情報クラスを作成する var Itemman = (function() { //Symbolで変数を定義 const name = Symbol('name'); const price = Symbol('price'); //クラスを定義 class Itemman { constructor(value,value2) { this[name] = value; this[price] = value2; } //格納されてる商品名を返す(ゲッター) get ItemName() { return this[name]; } //格納されてる金額を変更する(セッター) set ItemNameChange(value){ this[name] = value; } } return Itemman; })(); function itemimporter(){ //クラスからインスタンスを作成する var item = new Itemman("砂糖",5000); //商品名の取得 console.log(item.ItemName); //商品名の変更を試みる item.ItemNameChange = "オリゴ糖" console.log(item.ItemName) } |
ゲッターにて、item.nameとしても前述の手法の場合、プロパティの値は取得できません。必ずゲッターを経由してitem.ItemNameとして参照しないと取得が出来ない。同様にセッターを経由しないと、item.name = "お塩"としても、値は書き換わりません。
クラスの継承
クラスは1度定義したら基本的にはその構造を変更する事が出来ません。しかし、例えば固定資産の減価償却のクラスを定義した後に、それらと共通するような内容+αの「社用車クラス」というものを定義したくなったら、別個に作らなければならないとなると、もったいない(社用車だって減価償却する)。
ということで下敷きになる「減価償却クラス」をベースに、社用車クラスを作って機能を追加してみたいと思います。ただし、継承⇒継承と継承しすぎてわけが分からなくなるような運用は辞めましょう。
extendsで継承
extendsは、親となるクラスに対して、関数などの機能を追加する場合に利用します。そのため、新規にコンストラクタを定義したりは出来ません。新たに継承で定義したクラスでは、親のクラスを利用しつつ、追加した関数で演算させるといった事が可能になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
//クラスを定義 class assetman2 { //コンストラクタを定義 constructor(aname, s_kagaku, s_nen, s_type, s_per, k_per, h_per) { //プロパティをセットする this.aname = aname; //資産名 this.s_kagaku = s_kagaku; //取得価額 this.s_nen = s_nen; //償却年数(耐用年数) this.s_type = s_type; //償却方法 this.s_per = s_per; //償却率 this.k_per = k_per; //改定償却率 this.h_per = h_per; //保証率 //保証率を元に償却補償額を定義する(小数点以下四捨五入) this.hosyou = Math.round(s_kagaku * this.h_per); } //資産名を返す関数 getAssetName() { return this.aname; } } //assetman2をextendsで継承して関数を追加する class syayoucar extends assetman2 { //リセール価格を計算する resale() { return this.s_kagaku * 1.2; } } //継承クラスを使ってリセール価格を計算する function resalecalc(){ //クラスを元にインスタンスを作る let asset = new syayoucar("自動車","1000000",5,"定額法", 0.200, 0.250, 0.06552); //リセール価格を出す let resaleprice = asset.resale(); console.log(resaleprice); } |
上記のコードでは、assetman2という親となるクラスを定義しつつ、継承するsyayoucarをextends assetman2で継承しています。このクラスからインスタンスを生成し、asset.resale()を実行すると、きちんと取得価額×1.2の金額が計算されて返ってきます。
親のクラスでインスタンスを生成していない点とコンストラクタが無い点がポイント。親のクラスでインスタンスを生成しても、resale関数は当然実行されません。新たにコンストラクタを定義してしまうと、親のコンストラクタが消えてしまいます。
superでオーバーライド
前述のextendsの場合、新たにコンストラクタを追加出来ないので、関数等の機能の追加だけしか出来ません。親のコンストラクタに新規に追加しつつといった場合には、オーバーライドと呼ばれる継承を利用します(親のクラスを上書きするクラス)。今回のケースだとこちらのほうが目的に合っています。
親に加えて、走行距離であったり修理費用などといったプロパティを加えてみたいと思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
//クラスを定義 class assetman3 { //コンストラクタを定義 constructor(aname, s_kagaku, s_nen, s_type, s_per, k_per, h_per) { //プロパティをセットする this.aname = aname; //資産名 this.s_kagaku = s_kagaku; //取得価額 this.s_nen = s_nen; //償却年数(耐用年数) this.s_type = s_type; //償却方法 this.s_per = s_per; //償却率 this.k_per = k_per; //改定償却率 this.h_per = h_per; //保証率 //保証率を元に償却補償額を定義する(小数点以下四捨五入) this.hosyou = Math.round(s_kagaku * this.h_per); } //資産名を返す関数 getAssetName() { console.log(this.aname); return this.aname; } } //assetman2をextendsで継承して関数を追加する class syayoucar2 extends assetman3 { constructor(aname, s_kagaku, s_nen, s_type, s_per, k_per, h_per,runtotal,repairecost) { //このクラスの引数 super(aname, s_kagaku, s_nen, s_type, s_per, k_per, h_per); //継承する親のコンストラクタ this.runtotal = runtotal; this.repairecost = repairecost; } //親の関数を呼びつつresale価格を表示する resale(){ //資産名を呼ぶ関数を実行する super.getAssetName(); //走行距離に応じて減価する(1kmあたり10円) let runcost = this.runtotal * 10; //取得価額の1.2倍から減価した金額を計算 let totalcost = (this.s_kagaku * 1.2) - runcost + this.repairecost; //結果を返す return totalcost; } } //リセールバリューを計算する function testman2(){ var asset = new syayoucar2("自動車","1000000",5,"定額法", 0.200, 0.250, 0.06552,4500,69000); console.log(asset.resale()) } |
- syayoucar2クラスで、assetman3を継承しつつ、super()にて親のコンストラクタも継承する
- 必ず先にsuper()で継承してから、追加のコンストラクタを定義しないと、エラーになる
- syayoucar2のコンストラクタの引数は親の分+独自の分を定義しておく(runtotalとrepairecostを追加してる)
- 親の関数を呼ぶ場合は、super.関数名()にて呼び出す事が出来る
- 上記の関数だとasset.resaleにて1,224,000が答えとして返ってきます。
旧式のクラスの定義
V8ランタイムではない以前のGoogle Apps Scriptで動作出来るクラスのようなものとして使われていたコードは以下の通り(ES5準拠)。Class構文は使えない為、即時関数とPrototypeを利用して同じような仕組みを実現していました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
//ES5までの書き方 var enemy = (function() { //定数の定義 var area = 'field'; //コンストラクタの定義 var enemy = function(name, hp, mp, exp) { this.name = name; this.hp = hp; this.mp = mp; this.exp = exp; } //メソッドを定義 var pEnemy = enemy.prototype; // プロトタイプ内でメソッドを定義 //hpを減らす pEnemy.setDamage = function(damage) { this.hp = this.hp - damage; } //HPの残りを取得する pEnemy.getHitpoint = function() { return this.hp; } return enemy; })(); //モンスターを作る function makeEmemy(){ //インスタンス化 var mon1 = new enemy('スライムベホマズン', 280, 999, 450); //ダメージを与える mon1.setDamage(35); //ダメージ後のHPを表示 console.log(mon1.getHitpoint()); } |
どうしても、V8ランタイムでコードを記述できないようなケースでは上記のようなスタイルでクラスを実現するようにしなければなりません。上記のコードでmon1.getHitpointで返ってくる値は、245が返ってきます。
減価償却クラスのコードと解説
ソースコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
//固定資産の減価償却をして、償却額トータルを計算する function deprecalc() { //固定資産・償却額シートを取得する let sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("固定資産"); let exp = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("償却額"); //固定資産シートのデータを取得する let ss = sheet.getRange("A2:F").getValues(); //書き込み用の配列を用意 let array = []; //プロンプトを表示して年数を指定する let ui = SpreadsheetApp.getUi(); let title = '減価償却の実行'; let prompt = '償却年数を指定してください' let prop = ui.prompt(title, prompt, ui.ButtonSet.OK_CANCEL); let res = prop.getResponseText(); //固定資産データを回す for(let i = 0;i<ss.length;i++){ //レコードを1個取り出す let rec = ss[i]; //インスタンスを生成 let asset = new depreman(rec[1],rec[2],rec[3],rec[4],rec[5]) //指定年数で償却額を演算する let totalman = asset.calcret(res); //一時配列を作成 let temparr = [rec[0],rec[1],totalman]; //書き込み用の配列に追加する array.push(temparr); } //データを償却額シートに書き出す let lastColumn = array[0].length; //カラムの数を取得する let lastRow = array.length; //行の数を取得する exp.getRange(2,1,lastRow,lastColumn).setValues(array); //完了メッセージ ui.alert("償却額の計算が完了しました。") } //減価償却クラス class depreman { constructor(aname, s_kagaku, s_nen, s_type, s_per) { //プロパティをセットする this.aname = aname; //資産名 this.s_kagaku = s_kagaku; //取得価額 this.s_nen = s_nen; //償却年数(耐用年数) this.s_type = s_type; //償却方法 this.s_per = s_per; //償却率 } //指定年数で償却額を計算する calcret(calcnum){ let result = 0 if(this.s_type == "定額法"){ result = this.teigaku(calcnum); }else{ result = this.teiritsu(calcnum); } //値を返す return result; } //定額法の処理 teigaku(calcnum){ //償却費トータル用変数 let total = 0; //単年度償却額を計算 let deprice = Math.round(this.s_kagaku * this.s_per) //償却額を計算 for(let i = 0;i<calcnum;i++){ //残額がdepriceを下回ってるかどうかをチェック let zangaku = this.s_kagaku - total; if(zangaku < deprice){ //残額が1円ならば何もしない if(zangaku == 1){ continue; }else{ //残り1円まで償却する let lastman = zangaku - 1; total = total + lastman; } }else{ total = total + deprice; } } return total; } //定率法の処理 teiritsu(calcnum){ //償却費トータル用変数 let total = 0; //償却額を計算 for(let i = 0;i<calcnum;i++){ //残額がdepriceを下回ってるかどうかをチェック let zangaku = this.s_kagaku - total; //償却額を計算する let deprice = Math.round(zangaku * this.s_per) if(zangaku < deprice){ //残額が1円ならば何もしない if(zangaku == 1){ continue; }else{ //残り1円まで償却する let lastman = zangaku - 1; total = total + lastman; } }else{ total = total + deprice; } } return total; } } |
コードの解説
- 固定資産シートの資産一覧のデータを読み取って、指定の年数分の償却額合計を計算し、償却額シートにまとめて結果を出すクラスです
- プロンプトにて償却年数を取得して計算しています。
- s_typeの値を元に定額法と定率法で呼び出す関数を分岐しています。
- asset.calcretにて計算をさせて、償却額トータルを返してもらっています。
- データは一括で二次元配列にて書き出しを行っています。
- calcretからは償却方法に応じて同じクラス内の別の関数をthis.関数名(引数)にて渡しています。
実際に構築してみた結果ですが、これを単純に関数でやってしまうと、非常に冗長なものになってしまったり、後でメンテナンスをする際に関数の手直しが非常に面倒になる点です。
呼び出し元のコードは非常にシンプルになり、呼び出し先のクラスは部品単位で細かく管理が出来る。これがクラスの利点になります。今回のような大きな機能になりそうなものは、いきなりコードで書き始めるというよりは、クラスの設計をして後から機能の追加をしていくスタイルで作成すれば、機能追加も容易になると思います。
ゲームのように大量に同じパターンを生成して、それらを配列に格納し、1ターン毎に各インスタンスにランダムなパラメータを渡すなんて芸当は、クラスが無いととっちらかって大変なことになります。
図:減価償却計算がサクっと出来ました。
メソッドチェーンを作成する
応用編として、今回のような自作のクラスでメソッドチェーンを作る手法があります。GASでもよく見かけるSpreadsheetApp.getActiveSpreadsheet().getSheetByName("test")みたいな形で命令を数珠つなぎにして送り込むものです。クラスを用意できれば、これを構築する事が可能です。
以下のコードは課税額15%を上乗せし、利益を20%乗せて、最後に答えを取得するという一連のクラスを用意してメソッドチェーンとする手法です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
//メソッドチェーン用 var builder = function() { //新しいインスタンスを生成する return new mchain(); }; //クラスを用意 var mchain = (function() { //クラスを定義 class mchain { constructor() { this.price = 0; this.zei = 0; this.per = 0; this.salesprice = 0; } //原価に課税分を乗せる(15%上乗せ) getPrice(price){ this.price = price this.zei = price * 0.15; return this; } //課税分に利益を乗せる getPlus(per){ this.per = per; this.salesprice = (this.price + this.zei) * this.per; return this; } //最終販売価格を取得する getSalesPrice(){ return this.salesprice; } } return mchain; })(); //メソッドチェーンを実行する function chainmethod(){ let tomato = builder().getPrice(3000).getPlus(1.2).getSalesPrice(); console.log(tomato) } |
- builder変数ではクラスを初期化するコードだけを用意しておきます。
- クラス内では、取り敢えずコンストラクタの値は0で初期化して用意しておき、つなげる関数からの引数で入力する
- ポイントは各関数は最後値を返すのではなく、thisだけを返すようにします。
- 最終的な値を取得するgetSalesPriceのみ、答えをreturnで返すようにしてあります。
- chainmethodではbuilderをスタートとして数珠つなぎに2つの関数を呼び出し、最後に答えをもらっています。tomatoには上記のコードならば、4140という答えが返ってきます。
- それぞれの段階の値を保持しておけるのがクラスの良い点です。
関連リンク
- 【JavaScript入門】class構文の使い方・書き方が分かるようになる方法!
- 【JavaScript】 JSにおけるクラスとは?正体についても調べてみた
- 【全てのJSのオブジェクト指向嫌いマンに告ぐ】ES6のclass構文が素敵すぎて鼻水が止まらない
- 【初心者向け】Google Apps Scriptでクラスを理解するためのオブジェクトの基礎知識
- ES2021/ES2022を知ろう
- JavaScriptのクラスフィールドの話
- 【JavaScript入門】プロトタイプ(prototype)の使い方と継承まとめ!
- 日本語とオブジェクト指向
- JavaScript初心者にclassを伝える
- VBA クラスモジュールの使いどころ ~ Excelの一覧表とクラスモジュールは相性がいい
- 【GAS GoogleAppsScript | 基礎コード】class(クラス)について
- Google流JavaScriptにおけるクラス定義の実現方法(ES6以前)
- No.5410 減価償却資産の償却限度額の計算方法(平成19年4月1日以後取得分)
- javascript メソッドチェーンを作成する
- JavaScriptでメソッドチェーンを作る
- 【JavaScript】 JSにおけるカプセル化手法
- JavaScript (ES5) でクラスを実現するための基本