フェイルセーフの観点から顧客信頼性を確保しよう

[mixiグループアドベントカレンダー](https://qiita.com/advent-calendar/2017/mixi) 17日目です。

前日の @nukosuke さんに引き続き、XFLAGスタジオCREチームから私 
 @manji602 がアドベントカレンダーをお送りします！

今日は、私がサポート業務における顧客信頼性のために開発したプロダクトについてご紹介します。

## プロダクト導入前に起きていた問題

今回ご紹介するプロダクトを導入する前には、サポート業務において度々誤案内が起きており、高い水準での顧客信頼性を維持するのが難しい状況にありました。

その誤案内を大別すると2種類の要因がありました。利用していたシステム起因と私たちの業態起因のものです。

### ZendeskのUI起因の誤案内

XFLAGスタジオでは、多くのプロダクトのサポート業務に[Zendesk](https://www.zendesk.co.jp/) と呼ばれるCMSを利用しています。

Zendeskでは、受信したお問い合わせメール1件に対して「チケット」という単位でやりとりを管理しており、その投稿フォームは下記の画像のようになっています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/36593/992bae7c-b5a6-1648-5100-06119783e8fc.png)

上部にある「パブリック返信」というタブがユーザーさんへの返信で、「社内メモ」というタブが対応するスタッフ間で情報を共有するためのメモ書きのようなものです。

この「パブリック返信」と「社内メモ」は投稿フォームの背景色が異なるだけで、同じ操作手順を経て投稿が完了するようになっています。「パブリック返信」で投稿時に注意を喚起するようなダイアログも出ません。

それ故に、「社内メモ」のつもりで記入した内容を、誤って「パブリック返信」でユーザーさんに送信してしまったという事故が度々発生していました。

もしユーザーさんが普段目にすることのない社内向けツールのURLであったり、未公開の情報を誤って送信してしまったら…そういった事故は容易にTwitter等を通じて拡散する時代になっているので、プロダクトはおろか、会社自体の信頼性を失ってしまうことになりかねません。

### 業態による誤案内

私たちは、複数のリージョンに渡って様々なプロダクトを提供しており、それらのサポート業務の実対応についてはいくつかのパートナー企業に委託しています。各パートナー企業はサポート業務を行うオフィス(拠点)を複数抱えていて、各拠点毎に複数のプロダクトを横断的に担当していただいているという現状があります。

それ故に、リージョンやプロダクトについて取り違えた内容でユーザーさんに案内してしまう事故も度々発生していました。例えば、下記のような例です。

**リージョンの取り違え**

例: ファイトリーグの繁体中国語のユーザーさんに対して、簡体中国語の文言で案内してしまう

**プロダクトの取り違え**

例: モンストのユーザーさんに対して、ゲーム内通貨の表現をファイトリーグで利用されている「ファイトマネー」という文言で案内してしまう（正しくは「オーブ」）

## ユーザースクリプトの導入で誤案内を防止

うっかりミスを防止するためには人間が気をつけることも重要ですが、それだけではカバーしきれないケースが多いです。「人間は必ずミスをするものだ」という視点から、システム的な検知を行える機構を用意しておくことが肝要と言えます。

そこで対策として、「パブリック返信投稿時に投稿内容をチェックし、指定したキーワードが含まれる」場合にはアラートを表示するユーザースクリプトを実装し、サポート業務に携わっているスタッフに導入してもらうことにしました。

この機能は、Gmailで「添付」というキーワードが含まれているのに、添付ファイルがないメールを送ろうとした場合にアラートが表示される機構からインスパイアされて開発したものです。

実装としてはシンプルで、

* (1) AWS S3からNGワードのリストを定義したJSONファイルを取得
* (2) 送信時に押下されるボタンのクリックイベントをトリガーにして、投稿フォームの入力内容をチェック
* (3) NGワードに定義のある単語が含まれていたらアラートを表示

![image.png](https://qiita-image-store.s3.amazonaws.com/0/36593/1586c4aa-bca3-b315-ea07-fb7e1901e188.png)


というフローでチェックするようになっています。

NGワードのリストは、実装当初はユーザースクリプトにべた書きで定義していましたが、定義の改修依頼に応じる度に全ての対応スタッフに再インストールをお願いする状況が続きました。4度目の改修依頼の際にリファクタリングを行い、NGワードのリストだけS3に外出しすることでメンテナンスコストを下げています。

### JSONファイルの設定内容とNGワードの検知方法

**JSONファイルの設定内容**

擬似コードを置いておきます。

```json
{
    "hosts" : [
        "aaa.zendesk.com",
        "bbb.zendesk.com",
        "ccc.zendesk.com"
    ],
    "targetWords" : {
        "common" : ["zendesk.com", "hogehoge", "hugahuga", ...],
        "aaa.zendesk.com": ["piyo", ...],
        "bbb.zendesk.com": ["moge", "aaaで使われているアイテム名", ...],
        "ccc.zendesk.com": ["poyo", "bbbで使われているアイテム名", ...]
    }
}
```

NGワードの定義リストとなるjsonは上記のように設定します。 `hosts` には対象となるZendeskアカウントを設定しておき、 `targetWords` には各アカウントごとのNGワードを設定しておきます。 `common` に指定した内容は全てのZendeskアカウントで有効になります。

**NGワードの検知方法**

こちらも擬似コードを置いておきます。

```javascript
var setValidatorOnSubmitButton = function(targetDOM) {
  var preventEvent = true;

  targetDOM.on("click", function(event) {
    var confirmText = createConfirmText();

    if (confirmText !== "" && preventEvent === true) {
      event.preventDefault();
      event.stopPropagation();

      if (!confirm(confirmText)) {
        return false;
      }

      preventEvent = false;
      $(this).trigger("click");
      preventEvent = true;
    }
  });
};

var createConfirmText = function() {
  var confirmText = "";

  var inputText = $("div.textarea").val();

  if (isTargetText(inputText)) {
    confirmText += "以下の文章はパブリック返信にふさわしくないキーワードが含まれているおそれがあります。\n\n" + inputText + "\n\n" + "本当に送信しますか？";
  }

  return confirmText;
};

var isTargetText = function(text) {
  var host = location.host;
  var commonTargetWords = config.targetWords.common;
  var particularTargetWords = config.targetWords[host];

  var allTargetWords = particularTargetWords === undefined ? commonTargetWords
                                                           : commonTargetWords.concat(particularTargetWords);

  var joinedTargetWords = "(" + allTargetWords.join("|") + ")";
  var targetRegExp = new RegExp(joinedTargetWords);

  return text.match(targetRegExp) ? true : false;
};
```

特筆すべき点はあまりないとは思いますが、

* `setValidatorOnSubmitButton` で送信ボタンのクリックイベントを追加
* `createConfirmText` でNGワードが含まれる場合は確認用のメッセージを作成
* `isTargetText` でNGワードが含まれているかを確認

というような実装になっています。

### 導入の効果と今後の展望

このユーザースクリプトを導入することで、NGワードに定義のある単語については前述の誤案内を防止することが出来ました。
現状、NGワードのリストに登録されている単語は、過去に起こった誤案内を元に人手で列挙したものになっており、十分にフェイルセーフとはいえない状況にあります。今後はNGワードの登録を機械的に追加できるような機構の作成にも取り組んでみたいです。

## おわりに

お問い合わせをしていただくユーザーさんは、程度の差はあるもののプロダクトへの愛着があるはずです。

何か不安や疑問に思った時、普段は利用しない導線から問い合わせフォームやメーラーを立ち上げ、今起きていること・どう解決してほしいかといった内容をサポート担当宛に送ることには労力を要します。

そうした労力を費やしてくださったユーザーさんへのサポート対応の品質が悪ければ、解決してくれるかもしれないという期待が裏切られ、プロダクトの大切なファンを失ってしまうかもしれません。

今回はお問い合わせ対応についての紹介となりましたが、今後もCREとして高い顧客信頼性を維持したサポート環境を提供できるよう、開発視点で出来ることに取り組んでいきたいです。

明日は @Yuki_Yamada さんがKotlin系で何か書いてくださります。

---

## おまけ

CREチームは全員で今年のmixiグループアドベントカレンダーを書きます！以下にリンクしていますので、よろしければ合わせてお読みください :relaxed: 

* [day2: CREチームを設立しました！ by @otoyo](https://career.xflag.com/report/engineer/cre/)
* [day9: アプリケーションサーバに引きこもるDBを自立させてDocker Composeで開発環境を作る by @upscent](https://qiita.com/upscent/items/8d9ebd1f4f4cc11b12a6)
* [day16: なれる！CRE by @nukosuke](https://qiita.com/nukosuke/items/ff087fc1fa033a00dbd9)
* [day21: MySQL+ActiveRecord環境で絵文字に対応したお話 by @njin](https://qiita.com/njin/items/49742748d20ea051b7cb)