Custom Functions in Google Sheetsを訳してみた

カスタム関数を作っていていろいろとハマったので、その[説明文書(Custom Functions in Google Sheets)](https://developers.google.com/apps-script/guides/sheets/functions)を訳してみました。リンク先や訳文は、日本語環境に合わせて若干変更してある箇所があります。

# Google シートにおけるカスタム関数

Google シートでは [AVERAGE](https://support.google.com/drive/answer/3093615), [SUM](https://support.google.com/drive/answer/3093669), [VLOOKUP](https://support.google.com/drive/answer/3093318) といった、多くの[ビルトイン関数](https://support.google.com/drive/topic/1361471)を提供しています。これらの関数だけでは機能的に不足する場合、Google Apps Script を使ってカスタム関数を書くこともできます。具体例としては、[メートルをマイルに変換](https://developers.google.com/apps-script/quickstart/macros)したり、[インターネットから現時点のコンテンツを取って来る](https://developers.google.com/apps-script/guides/sheets/functions#optimization)といったことが挙げられます。Google シートの中で作成したカスタム関数は、ビルトイン関数と同じように利用できます。

## 開始方法

カスタム関数は標準の JavaScript を使って作成します。まだ JavaScript を使ったことがない場合、Codeacademy で[初心者向けの魅力的な学習コース](http://www.codecademy.com/tracks/javascript)をご提供しています。（このコースは Google が作成したものではなく、Google とは無関係ですのでご注意ください）。

まずサンプルとして、以下のようなシンプルなカスタム関数を考えます。これは DOUBLE という名前で、入力値を２倍にするものです。

```JavaScript
function DOUBLE(input) {
  return input * 2;
}
```

JavaScript の書き方がわからないんだけど学習しているヒマもないという向きには、[アドオンストアをチェック](https://developers.google.com/apps-script/guides/sheets/functions#getting_a_custom_function_from_the_add-on_store)して、自分が必要とするカスタム関数を誰かすでに作ってくれたりしていないかどうかを調べてみてください。

### カスタム関数を作成する

カスタム関数を書く手順は以下の通りです。

1. Google シートでスプレッドシートを[作成(create)](https://docs.google.com/spreadsheets/create)またはオープンします。
2. メニューから**[ツール]**＞**[スクリプトエディタ]**を選択します。ようこそ画面が表示された場合、左側の**[空白のプロジェクト]**をクリックして新しいプロジェクトを開始してください。
3. スクリプトエディタ内にあるコードをすべて削除します。その後、前述の DOUBLE 関数をコピーしてスクリプトエディタに貼り付けます。
4. メニューから**[ファイル]**＞**[保存]**を選びます。スクリプトプロジェクトに名前をつけて**[OK]**をクリックします。
5. これでカスタム関数を使えるようになりました。

### アドオンストアからカスタム関数を取得する

アドオンストアでは[Google シートのアドオン](https://support.google.com/drive/answer/3641454)としてカスタム関数がいくつか提供されています。以下の要領でこれらのアドオンを使ったり探したりできます。

1. Google シートでスプレッドシートを[作成(create)](https://docs.google.com/spreadsheets/create)またはオープンします。
2. メニューから**[アドオン]**＞**[アドオンを取得]**を選択します。
3. [シート用のアドオンストア](https://docs.google.com/spreadsheets/create?addon_store)が開きます。右上隅の検索ボックスをクリックし、"custom function" とタイプして Enter を押します。
4. 使ってみたいカスタム関数アドオンが見つかったら**[無料]**をクリックしてインストールします。
5. アドオンの認可を要求するダイアログボックスが表示される場合があります。その場合、注意書きをよく読んでから、（アカウントを選択して）**[許可]**をクリックします。これでアドオンがインストールされます。
6. これでアドオンがこのスプレッドシート内で有効になりました。別のスプレッドシート内でそのアドオンを使いたい場合、別のスプレッドシートを開いてメニューから**[アドオン]**＞**[アドオン名]**を選択します。

### カスタム関数を使う

いったんカスタム関数を書いたりアドオンストアからインストールした場合、以下の手順でビルトイン関数のように手軽に使えるようになります。

1. その関数を使いたい場所のセルをクリックします。
2. たとえば **=DOUBLE(A1)** のように、等号(=)に続けて関数名をタイプし、入力値を指定して **[Enter]** を押します。
3. そのセルが **Loading ...** という表示になり、しばらくして結果が返されます。 

## カスタム関数のガイドライン

自作のカスタム関数を書く前に、いくつか知っておいていただきたいガイドラインがあります。

### ネーミング

JavaScript 関数のネーミングに関して、標準の命名規則以外に、以下の点にも注意してください。

- カスタム関数の名前は、**SUM()** のような[ビルトイン関数名](https://support.google.com/docs/table/25273)と明確に区別できる必要があります。
- カスタム関数名はアンダースコア(_)で終わってはなりません。ちなみにこれは、Apps Script ではプライベート関数を意味します。
- カスタム関数名は **var myFuntion = new Function()** 形式ではなく、**function myFunction()** 形式で宣言されなければなりません。
- スプレッドシート関数の名前は伝統的に大文字を使いますが、カスタム関数では大文字小文字どちらを使っても構いません。

### 引数
ビルトイン関数と同様に、カスタム関数も入力値として引数を取ることができます。

- 引数として（**=DOUBLE(A1)**のように）単一のセルを指定した場合、引数はそのセルの値となります。
- 引数として（**=DOUBLE(A1:B10)**のように）セル範囲を指定した場合、引数はそれらのセルの値の２次元配列となります。例として以下のスクリーンショットを見てください。**=DOUBLE(A1:B2)** の引数は Apps Script によって**double ([[1,3],[2,4]])** と解釈されます。ちなみに前述の DOUBLE のサンプルコードは、入力として配列を受け付けるように書き換える必要がありますのでご注意ください。
![arguments-example.png](https://qiita-image-store.s3.amazonaws.com/0/47305/266561a4-4e85-3fd6-4d9b-e556995e00ae.png)

- カスタム関数の引数は[決定的](https://ja.wikipedia.org/wiki/%E6%B1%BA%E5%AE%9A%E7%9A%84%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0)でなければなりません。つまり、計算のたびに異なった値を返す **NOW()** や **RAND()** のようなビルトインのスプレッドシート関数は、カスタム関数の引数としては指定できないということです。もしカスタム関数がこのようなビルトイン関数に基づく揮発性の値を返そうとすると、**Loading ...** が永久に表示され続けることになります。

### 戻り値
カスタム関数は、以下のように表示可能な値を返さなければなりません。

- カスタム関数が単一の値を返す場合、その関数が呼ばれたセルにはその値が表示されます。
- カスタム関数が２次元配列の値を返す場合、そこに隣接したセルの値が連続して空である間、それらのセルに値が溢れて流れ込みます。溢れた値が既存のセルの中身を上書きすることになる場合、カスタム関数は代わりにエラーを投げます。具体例としては、カスタム関数の最適化の章をごらんください。
- 値を返さないカスタム関数は、セルに影響を与えることができません。つまり、カスタム関数が編集できるのは、その関数を呼出したセル、およびそれに隣接したセル（＋それに続くセル）のみであり、カスタム関数から任意のセルの編集をすることはできないということです。任意のセルの編集をしたい場合、[カスタムメニュー](https://developers.google.com/apps-script/guides/menus)を使ってカスタム関数を実行します。
- カスタム関数は 30 秒以内にリターンしなければなりません。もしこれを超えた場合、そのセルには **Internal error executing the custom function** が表示されます。

### データ型
Google シートは、さまざまなデータをそれらの性質に基づく[異なったフォーマット](https://support.google.com/docs/answer/56470)で格納します。これらの値がカスタム関数内で使われる場合、Apps Script はこれを [JavaScript における適切なデータ型](https://developer.mozilla.org/ja/docs/Web/JavaScript/Data_structures)として取り扱います。ここはもっとも混乱しやすい部分です。

- シートにおける時刻と日付は、Apps Script 内部では [Date](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date) オブジェクトになります。（レアケースではありますが）もしスプレッドシートとスクリプトで異なったタイムゾーンを使っている場合、カスタム関数内でこれを補正してやる必要があります。
- シート内で期間を表す値も **Date** オブジェクトになりますが、[これへの対応はかなり複雑になる](http://stackoverflow.com/questions/17715841/gas-how-to-read-the-correct-time-values-form-google-spreadsheet)可能性があります。
- シート内で百分率を表す値は、Apps Script では小数になります。たとえば **10%** の値を持つセルは、Apps Script では **0.1** になります。

### オートコンプリート
Google シートでは[ビルトイン関数](https://support.google.com/docs/answer/91932)と同様に、カスタム関数でもオートコンプリートをサポートしています。セルに関数名の一部をタイプすると、それにマッチするビルトイン関数とカスタム関数の一覧が表示されます。

以下の DOUBLE() の例のように、スクリプトに [JsDoc](http://usejsdoc.org/about-getting-started.html) の **＠customfunction** タグが含まれていれば、候補一覧の中にカスタム関数が表示されます。

```JavaScript
/**
 * 入力値に２をかける
 *
 * @param {number} input The value to multiply.
 * @return The input multiplied by 2.
 * @customfunction
 */
function DOUBLE(input) {
  return input * 2;
}
```

## より進んだ使い方

### Apps Script サービスを使う
カスタム関数から [Apps Script サービス](https://developers.google.com/apps-script/guides/services/)をコールして、より複雑なタスクを実行できます。たとえば、カスタム関数から [Language](https://developers.google.com/apps-script/reference/language/) サービスをコールして、英語の表現をスペイン語に翻訳できます。

Apps Script における他のほとんどのタイプとは異なり、カスタム関数は個人データへのアクセスの認可を要求することはありません。つまり、カスタム関数は個人データへのアクセス権を持たないサービスしか呼び出せないということです。特に以下のものが挙げられます。

| サポートされる<br>サービス | 備考 |
|:-----------------|:------------------------------------------------------------------------------|
| [Cache](https://developers.google.com/apps-script/reference/cache) | 動きますが、カスタム関数で特に有用ということはありません。    |
| [HTML](https://developers.google.com/apps-script/reference/html) | HTML を生成できますが、それを表示できません（よって、ほとんど意味はありません）。 |
| [JDBC](https://developers.google.com/apps-script/reference/jdbc) |               |
| [Language](https://developers.google.com/apps-script/reference/language) |               |
| [Lock](https://developers.google.com/apps-script/reference/lock)  | 動きますが、カスタム関数で特に有用ということはありません。 |
| [Maps](https://developers.google.com/apps-script/reference/maps)     | 距離を計算できますが、マップを表示することはできません。 |
| [Properties](https://developers.google.com/apps-script/reference/properties) |            |
| [Spreadsheet](https://developers.google.com/apps-script/reference/spreadsheet) | リードオンリーです（ほとんどの **get*()** を使えますが **set*()** は使えません）。<br>他のスプレッドシートを開けません（**SpreadsheetApp.openById()** や **SpreadsheetApp.openByUrl()**） |
| [URL Fetch](https://developers.google.com/apps-script/reference/url-fetch)    |            |
| [Utilities](https://developers.google.com/apps-script/reference/utilities)    |            |
| [XML](https://developers.google.com/apps-script/reference/xml-service)          |            |

あなたの関数が **You do not have permission to call X service.** というエラーメッセージを投げる場合、そのサービスがユーザーの認可を必要としているため、カスタム関数内では使えないことを表しています。

前述の一覧で提示されたもの以外のサービスを利用する場合、カスタム関数を記述する代わりに[カスタムメニュー](https://developers.google.com/apps-script/guides/menus)を使って Apps Script 関数を実行してください。メニューからトリガーされる関数は、必要であればユーザに対して認可を問合わせるため、すべての Apps Script のサービスを利用できます。

### 共有

カスタム関数は、それらが作成されたスプレッドシートに[関連付けられた形(bound)](https://developers.google.com/apps-script/guides/bound)で開始されます。つまり、あるスプレッドシートで書かれたカスタム関数は、以下のいずれかの方法を取らない限り、他のスプレッドシートでは使えないということです。

- **[ツール]**＞**[スクリプトエディタ]**をクリックしてスクリプトエディタを開き、元のスプレッドシートからスクリプトの本体をコピーして、別のスプレッドシートのスクリプトエディタに貼り付けます。
- カスタム関数を含むスプレッドシートで**[ファイル]**＞**[コピーを作成]**をクリックしてコピーを作成します。スプレッドシートがコピーされると、それに関連付けられたスクリプトもコピーされます。スプレッドシートにアクセス権のあるユーザーは誰でもスクリプトのコピーを作成できます。（参照アクセス権しか持たない共同作業者は、元のスプレッドシートでスクリプトエディタを開けません。しかし、いったんコピーを作成すれば、そのコピーの所有者になるのでスクリプトが見えるようになります。）
- Google シートの[アドオン](https://developers.google.com/apps-script/add-ons/)としてスクリプトを公開する。

> [★ コンテナと結合したスクリプトは、いずれもそのコンテナと同じアクセスリストを持ちます。](https://developers.google.com/apps-script/guides/bound#access_to_bound_scripts)つまり、そのスプレッドシートの編集権を持つ人なら誰でも、それに付随した Apps Script のコードを編集できるということです。

### 最適化
スプレッドシート内でカスタム関数が使われるたびに、Google シートは Apps Script サーバに対して毎回呼び出しを行います。もしあなたのスプレッドシートに数十の（または数百や数千の！）カスタム関数がある場合、処理がかなり遅くなる可能性があります。

このため、もしあなたが広範囲のデータに対して複数回のカスタム関数を使おうと思っているなら、入力として２次元配列を取り、元のセルとその隣接セルに対して二次元の値を返すような形にカスタム関数を修正することを検討してください。

たとえば、前述の DOUBLE() についても、以下の通り単一のセルでもセル範囲でも取ることができるように書き換えることができます。

```JavaScript
/**
 * 入力値に２をかける
 *
 * @param {number} input The value or range of cells to multiply.
 * @return The input multiplied by 2.
 * @customfunction
 */
function DOUBLE(input) {
  if (input.map) {            // 入力が配列かどうかを判定し、
    return input.map(DOUBLE); // 配列の場合は配列に対して再帰的に呼び出す。
  } else {
    return input * 2;
  }
}
```

この例では JavaScript の **Array** オブジェクトの [map](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/map) メソッドを使って、セルの２次元配列にあるそれぞれの値について再帰的に DOUBLE を呼び出すようなアプローチを取っています。この方法により、以下のスクリーンショットにあるように、DOUBLE を一回呼び出すだけで多数のセルの計算を一気に行なうことができます。（**map** の呼び出し以外にも、ネストされた if ステートメントについても同様のことが可能かもしれません）。

![custom-functions-example.png](https://qiita-image-store.s3.amazonaws.com/0/47305/7bbfedfd-15bc-fa12-854d-73a964c7986a.png)

以下の例も同様に、インターネットから現時点のコンテンツを効率的に取得して、二次元配列を使って１回の関数呼び出しだけで２カラムを持つ結果の一覧を表示しています。それぞれのセルが毎回関数呼び出しをするようになっている場合、Apps Script サーバは毎回 XML フィードをダウンロードしてパースしなければならなくなり、その動作はかなり長時間に及ぶことになるでしょう。

```JavaScript
/**
 * G Suite 開発者ブログの最初の投稿ページのタイトルと日付を表示する
 *
 * @return Two columns of data representing posts on the G Suite
 *     Developer blog.
 * @customfunction
 */
function getBlogPosts() {
  var array = [];
  var url = 'https://gsuite-developers.googleblog.com/atom.xml';
  var xml = UrlFetchApp.fetch(url).getContentText();
  var document = XmlService.parse(xml);
  var root = document.getRootElement();
  var atom = XmlService.getNamespace('http://www.w3.org/2005/Atom');
  var entries = document.getRootElement().getChildren('entry', atom);
  for (var i = 0; i < entries.length; i++) {
    var title = entries[i].getChild('title', atom).getText();
    var date = entries[i].getChild('published', atom).getValue();
    array.push([title, date]);
  }
  return array;
}
```

これらのテクニックは、１個のスプレッドシートを通して繰り返し使われるほとんどすべてのカスタム関数に対して適用可能ですが、その実装の詳細は、その関数の振る舞いにより異なります。

Except as otherwise noted, the content of this page is licensed under the [Creative Commons Attribution 3.0 License](http://creativecommons.org/licenses/by/3.0/), and code samples are licensed under the [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0). For details, see our [Site Policies](https://developers.google.com/terms/site-policies). Java is a registered trademark of Oracle and/or its affiliates.

最終更新日: 5月 18, 2017