[GAS]スプレッドシートのシートから取るところをルール化したらよくなった


# はじめに

GASの大部分の処理はスプレッドシート関連、すなわち

- スプレッドシートにある以下のような情報を取得する
  - 従業員情報
  - グーグルフォームの情報
  - 売上情報
- スプレッドシートにある情報を編集する
  - 従業員情報を修正したり
  - 従業員情報にグーグルフォームの情報を追加したり

だと思います。スプレッドシートをデータベースとして使うイメージですね。

高頻度で触る処理になるので、以下のようなルールを設けてみたらうまくいきました。

1. シートの情報は連想配列でまとめる
2. シートの情報をとるときは基本共通処理を使う
3. シートの情報をとったときはクラスに格納する

です。

# ルール1 シートの情報は連想配列でまとめる

```javascript:sheet.gs
const SHEET = {
  member : {
    name : '従業員情報',
    row : {
      // 実際にデータが何行目からあるかを定義しています。共通処理の中でここの情報を取得しています。
      data : 2,
    },
    column : {
      // 何列目になんの情報があるかを定義してます。後ほどクラスに設定するときに使います。
      id : 1,
      name : 2,
      status : 3,
    },
  },
  form : {
    name : 'フォーム',
    row : {
      data : 2,
    },
    column : {
      time : 1,
      id : 2,
      text : 3,
    },
  },
};

```
# ルール2 シートの情報をとるときは基本共通処理を使う

```javascript:sheet.gs

// シートからの情報を取得する処理(見出しを省く)
function getSheetData(sheetConfig, spreadSheetId){
  let data = getSheetDataFull(sheetConfig, spreadSheetId);
  [...Array(sheetConfig.row.data - 1)].forEach(_ => data.shift());
  return data;
}

// シートからの情報を取得する処理(見出しを省かず、シートの情報全体をとる)
function getSheetDataFull(sheetConfig, spreadSheetId){
  const ss = (spreadSheetId !== undefined)
    ? SpreadsheetApp.openById(spreadSheetId)
    : SpreadsheetApp.getActive();

  const sheet = ss.getSheetByName(sheetConfig.name);
  return ss.getSheetByName(sheetConfig.name).getDataRange().getValues();
}

// 1つのセルに対して情報を設定
function setText(sheetConfig, row, column, text){
  setList(sheetConfig, row, column, [[text]]);
}

// 複数のセルに対して情報を設定
function setList(sheetConfig, row, column, list){
  if(!list.length) return;
  const sheet = SpreadsheetApp.getActive().getSheetByName(sheetConfig.name);
  sheet.getRange(row, column, list.length, list[0].length).setValues(list);
}

// シート全体を更新
function refreshSheet(sheetName, list, startColumn, startRow){

  if(!list[0].length) return;
  const sheet = SpreadsheetApp.getActive().getSheetByName(sheetName);

  startRow = startRow ? startRow : 2;
  startColumn = startColumn ? startColumn : 1;
  sheet.getRange(startRow, startColumn, sheet.getLastRow(), list[0].length).clear();
  sheet.getRange(startRow, startColumn, list.length, list[0].length).setValues(list);
}

// シートに情報を追加(見出しの下の行から足す)
function addSheet(sheetConfig, column, list){
  if(!list.length) return;
  const sheet = SpreadsheetApp.getActive().getSheetByName(sheetConfig.name);
  sheet.insertRows(sheetConfig.row.data, list.length);
  sheet.getRange(sheetConfig.row.data, column, list.length, list[0].length).setValues(list);
}

// シートに情報を追加(シートの最後の行から足す)
function addSheetLastRow(sheetConfig, list, column){
  if(!list.length) return;
  const sheet = SpreadsheetApp.getActive().getSheetByName(sheetConfig.name);

  sheet.getRange(
    sheet.getLastRow() + 1,
    column !== undefined ? column : 1,
    list.length,
    list[0].length
  ).setValues(list);
}
```

# ルール3 シートの情報をとったときはクラスに格納する

## 取得するところ

```javascript:sheet.gs
function getMemberList(){
  return getSheetData(SHEET.member).map(row => new Member(row));
}

```

## クラスを定義しているところ

```javascript:class/Member.gs
class Member{
  constructor(row){
    this.id = row[SHEET.member.column.id - 1];
    this.name = row[SHEET.member.column.name - 1];
    this.status = row[SHEET.member.column.status - 1];
  }

  isValid(){
    return this.id !== ''
      && this.name !== ''
      && this.status !== '';
  }

  getHonorificName(){
    return `${this.name} 様`;
  }
}

```

# 効果
上のようなルールを設けることで

- 列が変わったときにも対応が容易になった
- PGやコードレビューがしやすくなった

などの恩恵がありました。
フレームワーク化もしてみたいなぁと思いつつ一旦こんな運用をしています。