【質問術】コードをシンプルにして質問の回答を得やすくする。【 #ノンプロ研 GAS初級~中級向け】【リファクタリング】

2022-05-29

当サイトではアフィリエイト広告を利用して商品を紹介しています。

GAS初級や中級を卒業していざ実践!これでばりばり業務の効率化をするぞ~!

実務で使いたい、やりたいことがあって、インターネットをしらべると似たようなサンプルコードが!

あれ、でも実際にコピーすると動かない・・・。

ちょっと改造するとわからない・・・

という経験は誰しも有るのでは?

今回はそんなときどうすればいいのか?を解説していきます。

例えばこんなコード

(いい実例なんで借りさせてもらいます@a03)

やりたいことがあって、調べて出てきたサンプルコードを自分なりに書き換えて、途中のコードです。

途中までわかるけどあとちょっとがわからない!ぐぬぬ!というコードです。


オブジェクトと配列で訳がわからなくなってきたので相談させてください。 最終的にやりたいことは、Slackのチャンネル名一覧をスプレッドシートに書き出すことです。 現時点のコードをスニペットに示します。 APIを用いて、Jsonデータを取得し、それをオブジェクトに変換まではできた、と思います。 その後、オブジェクトから取り出す、配列にする、のあたりで混乱しています。 ちょっと休んだ方がいいのかもしれない、、、。

//インストーラブルトリガで定期実行とする
/**
 * xxxx
 * @param {xxxx} xxx - xxxx
 * @param {xxxx} xxx - xxxx
 */

function setSlackChannnelsForSheet() {
  // 現在日時を取得
  const currentDate = new Date();

  //書き込む前にスプレッドシートの情報をクリアする

  //書き込む情報を取得
  const channelsArray = objectToArray();

  // スプレッドシート取得
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('list');

  //スプレッドーシートにSlack Channel情報を書き込む 最終的には現在日時とか差分どうにかする
  sheet.appendRow(channelsArray);
}

// 参考
//【GAS】オブジェクトやJSONデータから配列を作成する(Object.keys,values,entries)
//https://moripro.net/gas-object-to-array/
function objectToArray() {
  const responseObject = getResponseAsObject();
  // console.log(responseObject);

  const channelsObject = responseObject.channels;
  // console.log(channelsObject);
  const channelsArray = Object.entries(channelsObject);
  console.log(channelsArray);
  return channelsArray;
}

//プロジェクトの設定>スクリプト プロパティ で設定しておく。PropertiesServiceのsetコード書いてもいいけど。
function getResponseAsObject() {
  const token = PropertiesService.getScriptProperties().getProperty('slackBotUserOAuthToken');
  const apiMethod = "conversations.list";
  const response = callWebApi(token, apiMethod);
  const responseObject = JSON.parse(response);
  console.log(responseObject);
  return responseObject;
}

//  参考
//【GAS】オブジェクトやJSONデータから配列を作成する(Object.keys,values,entries)
//  <https://moripro.net/gas-object-to-array/>
function callWebApi(token, apiMethod, payload) {
  const response = UrlFetchApp.fetch(
    `https://www.slack.com/api/${apiMethod}`,
    {
      method: "post",
      contentType: "application/json",
      headers: { "Authorization": `Bearer ${token}` },
      payload: payload,
    }
  );
  return response;
}

やりたいことを整理し直す。

一度、やりたいことを整理して、挑戦したことをふまえてどうすればできそうか?を書いていきます。

ここはPC上でもいいですが、複数のシステムやAPIを使う場合は紙とペンを使い図で書くのがおすすめです。

今回だったら・・・

やりたいこと:

Slackのチャンネル名一覧をスプレッドシートに書き出すこと

手順:

SlackのAPIでチャンネル一覧を取得する。

取得したオブジェクトを二次元配列に変換する

スプレッドシートを取得する

スプレッドシートに二次元配列を書き込む

サンプルコードそのままで動くことを確認する

まずはサンプルコードをそのままで動くことを確認しましょう。

特にAPIを扱う場合API側の仕様が変わっていてそのまま動かない可能性があります。

記事がどれくらいの時期に書かれたのかも確認しましょう。

古い記事の場合、もっと便利な書き方ができるようになっていたりAPIのエンドポイントが非推奨になっていたりすることがあります。

これらを確認しつつまずはそのままで動くかどうかを確認します。

今回は、部分部分としては、APIを取得するまではうまく行ってそれ以降が動かなくなっています。

関数名、メソッド名、クラス名、変数、定数を適切に設定し直す

手順を元に、サンプルコードからそのままだった名前を自分の環境に合わせて適切に設定します。

これは可読性がよくなり自分が理解しやすくなるのと、 他人から見て読みやすいコードにすることで質問への回答を上げるのに役立ちます.

ミノ駆動本では名前は名前設計とも呼ばれ抜粋して重要なポイントが簡単にまとめられています。

詳しくは本を読みましょう!

  • 可能な限り具体的で意味範囲が狭い特化した名前を選ぶ。
  • 存在ベースではなく、目的ベースで名前を考える
  • どんな関心事があるか分析する
  • 声に出して読んでみる
  • 違う名前に置き換えられないか検討する
  • 疎結合高凝集になっているか点検する

名前を変更するに当たり必要であれば、引数の追加や削除を行います。

例えば下記のコードを見てください。

function callWebApi(token, apiMethod, payload) {
  const response = UrlFetchApp.fetch(
    `https://www.slack.com/api/${apiMethod}`,
    {
      method: "post",
      contentType: "application/json",
      headers: { "Authorization": `Bearer ${token}` },
      payload: payload,
    }
  );
  return response;
}

callWebApi という関数名ですが、今回必要なアクセス先はAPIはSlackのAPIだけです。

tokenはPropertiesServiceに保存されています。

apiMethodは今回の例はconversations.listだけしか使いません。

tokenやresponseといった一見して何が入っているかわからない名前のものも

slackToken slackConversationslist と言ったようにわかりやすく書き換えます。

function getSlackConversationslist(payload) {
  const slackToken = PropertiesService.getScriptProperties().getProperty('slackBotUserOAuthToken');
  const slackConversationslist = UrlFetchApp.fetch(

    `https://www.slack.com/api/conversations.list`,
    {
      method: "post",
      contentType: "application/json",
      headers: { "Authorization": `Bearer ${slackToken}` },
      payload: payload,
    }
  );
  return slackConversationslist;
}

ドキュメンテーションコメントを書く

コメントアウトでコメントがかかれていますが、ドキュメンテーションコメントを書きましょう。

何が入力されて何が出力されているか?

ここを読めばぱっと理解できるようになります。

コメントアウトで補足していたものも整理しましょう。

/**
 *  SlackからConversationslistを取得する関数
 * @param {object} payload - 
 * @return {object} slackConversationslist - 
 */
function getSlackConversationslist(payload) {
  const slackToken = PropertiesService.getScriptProperties().getProperty('slackBotUserOAuthToken');
  const slackConversationslist = UrlFetchApp.fetch(

    `https://www.slack.com/api/conversations.list`,
    {
      method: "post",
      contentType: "application/json",
      headers: { "Authorization": `Bearer ${slackToken}` },
      payload: payload,
    }
  );
  return slackConversationslist;
}

クラス化する(関数を分割する)

扱うデータとロジックをひとまとまりにしてクラス化ができないか検討しましょう。

上手にクラス化をすることで、どこを触れば変更ができるのか?が明確になります。 デバッグする際もコード全体を読む必要がなくなり不具合が起きたそのクラスの中身、メソッドの中身だけを調査すれば良くなります。

わからないことを質問するにしても、そのメソッドで行われていることだけを質問すれば良いので、質問が単純で限定的なものになり回答を得やすくなります。

今回のコードだとslackに関するデータとロジック、スプレッドシートに関するデータとロジックはひとまとめにするときれいになりそうです。

class Slack {
  /**
   * @constructor
   */
  constructor() {
    this.slackToken = PropertiesService.getScriptProperties().getProperty('slackBotUserOAuthToken');
  }

  /**
   * @param {object} slackConversationslistObject - 
   * @return {array} channelsArray - 
   */
  objectToArray(slackConversationslistObject) {
    //省略
    const channelsArray = []
    return channelsArray
  }

  /**
   * SlackからConversationslistを取得する関数
   * @param {Object} payload - payload
   * @return {Object} slackConversationslist - slackConversationslist
   */
  getSlackConversationslist(payload) {
    const slackConversationslist = UrlFetchApp.fetch(

      `https://www.slack.com/api/conversations.list`,
      {
        method: "post",
        contentType: "application/json",
        headers: { "Authorization": `Bearer ${this.slackToken}` },
        payload: payload,
      }
    );
    const slackConversationslistObject = this.getResponseAsObject(slackConversationslist)
    return slackConversationslistObject;
  }

  /**
   * UrlFetchApp を利用して取得した値をオブジェクト化して返す関数
   * @param {string} response - UrlFetchAppで取得した値
   * @return {object} object - 変換したオブジェクト
   */
  getResponseAsObject(response) {
    const json = response.getContentText();
    const object = JSON.parse(json);
    return object;
  }

}
class Sheet{
  /**
   * @constructor
   * @param {string} sheetName - シート名
   */
  constructor(sheetName){
    this.ss = SpreadsheetApp.getActiveSpreadsheet();
    this.sheet = this.ss.getSheetByName(sheetName);
  }
  /**
   * @param{array} insertArray - 書き込む配列
   */
  appendRow(insertArray){
    //省略
  }
  /**
   * シートをクリアするメソッド
   */
  clear(){
    //省略
  }
}

細分化すると問題はシンプルになり、回答をもらいやすくなる。

ここまで整理すると

メインのコードは下記のようになります。

/**
 * 毎日Slackのチャンネル一覧をスプレッドシートに書き込む関数
 * 
 */
function setSlackChannnelsForSheet() {
  // 現在日時を取得
  const currentDate = new Date();
  
  const sheetName ='list'
  const sheet = new Sheet(sheetName)

  //書き込む前にスプレッドシートの情報をクリアする
  sheet.crear()

  //書き込む情報を取得
  const slack = new Slack()
  const payload = []
  const slackConversationslistObject = slack.getSlackConversationslist(payload)
  const channelsArray = slack.objectToArray(slackConversationslistObject)

  sheet.appendRow(channelsArray);
}

とてもシンプルになりました。

そしてわからないことは、SlcakクラスのobjectToArrayのメソッドの処理の方法だけです。

/**
   * 
   * @param {object} slackConversationslistObject - 
   * @return {array} channelsArray - 
   */
  objectToArray(slackConversationslistObject) {
    //省略
    const channelsArray = []
    return channelsArray
  }

ここの引数として渡すslackConversationslistObjectは メソッドgetSlackConversationslistから取得でき、

戻り値としてほしい値はSheetクラスのappendRowに渡す配列です。

質問として、具体的なオブジェクトを提示して、これを配列にするにはどうすればよいか?

という具体的な質問になり、提示する関数(メソッド)が短くなります。

SlackAPIから取得した下記のオブジェクトからチャンネルIDを取り出して、 配列に格納を行いたいです。どうすればよいでしょうか?

const slackConversationslistObject = [{
  //省略
}]

/**
   * 
   * @param {object} slackConversationslistObject - 
   * @return {array} channelsArray - 
   */
  objectToArray(slackConversationslistObject) {
    //省略
    const channelsArray = []
    return channelsArray
  }

元の質問だと、回答者はSlackのAPIを試せる環境を用意して、全体のコードをすべて読み解いてどこに問題が有るのか確認をして回答するという作業になります。

しかし、上記の質問であれば、環境の環境の構築も必要なく、検証をすることができます。

つまり回答者の手間が減るので、回答を得やすくなります。

またもしかしたらまとめる過程で、問題がシンプルに整理されるので、自己解決ができたり、一般化した問題になるので検索したら回答が見つかるかもしれません。

時間をあける

それでもできなさそうなら一旦時間を開けて考えましょう。

寝る。

シャワーを浴びる。

ご飯を食べる。

解決策がふっと思いつくこともあります。

待っている間に誰かが質問に回答してくれるかもしれません

時間を開けて考えることも大切です。

まとめ:問題を整理すれば怖くない。

プログラミングをしていて、わからないことがたくさんで、壁にぶつかることがあるかと思います。

しかし、地道に問題を整理してとをひもといていくと、個別個別の問題はシンプルなものです。

そのレベルまで問題を分割できれば怖くありません。

最初は分割するのも難しいかもしれませんが、徐々に慣れていきましょう。