株式会社クリアコード > ククログ

ククログ

タグ:

ibus-daemonの--mem-profileオプションは非推奨になりました

はじめに

IBusの1.5.19からは、ibus-daemon--mem-profileオプションは非推奨になりました。 今回はその経緯について紹介します。

修正の経緯

IBusはGLibを外部ライブラリーの一つとして使っています。 このGLibですが、2.46からメモリプロファイリングに関する機能が動作しなくなりました。*1

これまでibus-daemonでは--mem-profileというオプションを指定するとメモリプロファイリングの機能が有効になり、 SIGUSR2を送りつけることでメモリ使用量に関するレポートを標準エラーに出力することができるようになっていました。 この機能を実現するために、ibus-daemonではGLibが提供する以下のAPIを使っていました。

しかし、GLib 2.46以降ではこれらの機能は非推奨となり使えません。

とはいえ、GLib 2.46というのはやや古く、これより前のものを採用しているのはUbuntu 14.04(trusty)あたりのリリースバージョンです。*2 そういった古めの環境でわざわざ最新のIBusを採用するというのは考えにくいため、IBusではGLib 2.46以前をサポートしないようにしました。

GLib 2.46以降の環境でibus-daemon--mem-profileオプションを指定した場合には警告するようになっています。

まとめ

今回はibus-daemon--mem-profileオプションが非推奨になった経緯を紹介しました。

*1 GLibとGIOとでメモリー確保の仕組みに互換性がないため。

*2 Ubuntu 14.04ではGLib 2.40が採用されている。

2018-08-14

リーダブルなコードを目指して:コードへのコメント(4)

まだギリギリ3週間に1回のペースの須藤です。このシリーズのファンができました

リーダブルなコードを目指して:コードへのコメント(3)の続きです。前回はユーザーからの入力処理のところを読んでコメントしました。

リポジトリー: https://github.com/yu-chan/Mario

今回のコメントに関するやりとりをするissue: https://github.com/yu-chan/Mario/issues/4

フレームレート

今回はメインループ中のフレームレート関連の処理を見ていきましょう。

まずはメインループの中をおさらいします。

	while(!InputInterface::isOn(KEY_INPUT_Q)) { //Qを押したら終了
		if(ProcessMessage() != 0) {
			break;
		}
		InputInterface::updateKey();
		Framerate::instance()->update();
		ClearDrawScreen();

		//ゲーム開始
		Sequence::Parent::instance()->update();

		ScreenFlip();
		Framerate::instance()->wait();
	}

この中の以下の部分がフレームレート関連の処理のはずです。

		Framerate::instance()->update();
		Framerate::instance()->wait();

なぜかというとFramerateクラスに属しているからです。

Framerate.hは次のようになっています。

#ifndef INCLUDED_FRAMERATE_H
#define INCLUDED_FRAMERATE_H

class Framerate {
public:
	static void create();
	static void destroy();
	static Framerate* instance();

	void update();
	void wait();

	int cnt() const;

private:
	Framerate();
	~Framerate();
	static Framerate* mInstance;
	
	int mStartTime;
	int mCnt;
	float mFramerate;
};

#endif

以下の部分はシングルトンパターンを実現するためのコードなので今回は無視しましょう。

	static void create();
	static void destroy();
	static Framerate* instance();
	Framerate();
	~Framerate();
	static Framerate* mInstance;

ということで注目するのは以下の部分です。

class Framerate {
public:
	void update();
	void wait();

	int cnt() const;

private:
	int mStartTime;
	int mCnt;
	float mFramerate;
};

名前から想像するとそれぞれのメンバー関数が実現する機能は次の通りです。

  • update()
    • 環境に合わせたフレームレートを変える。
    • マシンが重かったらフレーム減らすとか。
  • wait()
    • 現在のフレームレートに合わせたフレーム数にするために速すぎたら少し休む。
  • cnt()
    • 単位時間内でいまのところ何フレーム表示したかを返す。

うーん、update()はメインループ中で毎回呼ばれているんですが、そこでフレームレートを毎回調整するとは思えないんですよねぇ。実装を見てみましょう。

//フレームを更新
void Framerate::update() {
	if(mCnt == 0) {
		mStartTime = GetNowCount();
	}
	if(mCnt == INTERVAL) {
		int t = GetNowCount();
		mFramerate = 1000.0f / ((t - mStartTime) / (float)INTERVAL);
		mCnt = 0;
		mStartTime = t;
	}
	mCnt++;
}

この中で初めて見るのはGetNowCount()INTERVALです。

GetNowCount()はDXライブラリが提供している「Windowsが起動してからの経過時間をミリ秒単位で返す」関数でした。

INTERVALCommon.hで次のように定義されていました。

//フレームレート
#define INTERVAL 60
#define FPS 60

これをふまえると次のことがわかります。

  • フレームレートの単位時間は「Framerate::update()の呼び出し回数がINTERVAL(60)回」
    • 1秒とかではない。
  • フレームレートの単位時間がくる度にmStartTimeの時刻をリセットしている。
  • Framerate::update()を呼び出す毎にmFramerateを更新している。
    • が、ここで計算しているフレームレートの単位時間がわからない。変な計算式な感じ。
    • mFramerateを使っているところがなさそうなので、ここのコードは今は必要なさそう。

mFramerateは必要なさそうなので消した方がいいでしょう。必要ないコードがあると、読むときに「どうしてここにこんなコードがあるんだ。。。」と考えないといけなくなり、理解を妨げてしまいます。コードをバージョン管理していれば消したコードを戻すことができるので、1人で開発しているときでもバージョン管理しましょう。バージョン管理していれば安心してリーダブルなコードにするための変更を重ねていけます。実は、バージョン管理したほうがいいというのはリーダブルコードの解説にも書いています。

実装を読んでみた結果「フレームレートは更新していない(mFramerateがいらなそう)だし、フレームレートの更新というかフレームを1回進めるのが目的っぽいのでupdate()じゃない名前がよさそう」という気持ちになりました。なんていう名前がいいのかなぁ。tick()とかかなぁ。フレームを1つ進めます、というイメージ。Node.JSにはprocess.nextTick()というのがあるし。

ということで、こんな感じにするのはどうだろう。

//フレームを進める
void Framerate::tick() {
	if((mCnt % INTERVAL) == 0) {
		mStartTime = GetNowCount();
		mCnt = 0;
	}
	mCnt++;
}

うーん、FramerateというかFrameの方がいいのかなぁ。

もやもやしたまま次に進みましょう。Framerate::update()です。

//フレームが早かったら、早いぶんだけ待つ
void Framerate::wait() {
	int t = GetNowCount() - mStartTime;
	int w = mCnt * 1000 / FPS - t;
	if(w > 0) {
		Sleep(w);
	}
}

こちらは名前から予想していた通りの実装です。ただ、FPSの使い方がもやっとします。update()では単位時間は秒ではなかったのにここでは単位時間は1秒(FPSはFrame Per Secondだから)になっています。INTERVALFPSを統合できないかしら。こんな感じ?

//フレームを進める
void Framerate::tick() {
	if((mCnt % FPS) == 0) {
		mStartTime = GetNowCount();
		mCnt = 0;
	} else {
		int t = GetNowCount() - mStartTime;
		int w = mCnt * 1000.0 / FPS - t;
		if(w > 0) {
			Sleep(w);
		}
	}
	mCnt++;
}

で、メインループでは最後にtick()を呼ぶだけ。これで動かないかしら。

	while(!InputInterface::isOn(KEY_INPUT_Q)) { //Qを押したら終了
		if(ProcessMessage() != 0) {
			break;
		}
		InputInterface::updateKey();
		ClearDrawScreen();

		//ゲーム開始
		Sequence::Parent::instance()->update();

		ScreenFlip();
		Framerate::instance()->tick();
	}

あと、変数名はこんな感じにしたいですね。

//フレームを進める
void Framerate::tick() {
	if((mIndex % FPS) == 0) {
		mStartTime = GetNowCount();
		mIndex = 0;
	} else {
		int expectedElapsedTime = (1000.0 / FPS) * mIndex;
		int elapsedTime = GetNowCount() - mStartTime;
		int restTime = expectedElapsedTime - elapsedTime;
		if(restTime > 0) {
			Sleep(restTime);
		}
	}
	mIndex++;
}

Framerate::cntは単にmCntを返しているだけでした。

int Framerate::cnt() const {
	return mCnt;
}

そうだろうなぁという実装です。が、これを使っているコードはなさそうなので消したほうがよさそうに思いました。

まとめ

リーダブルコードの解説を読んで「自分が書いたコードにコメントして欲しい」という連絡があったのでコメントしています。今回はメインループ内で使っているFramerateを読んでコメントしました。次回はメインループの違う処理を読んでいきます。

「リーダブルなコードはどんなコードか」を一緒に考えていきたい人はぜひ一緒にコメントして考えていきましょう。なお、コメントするときは「悪いところ探しではない」、「自分お考えを押し付けることは大事ではない」点に注意しましょう。詳細はリーダブルなコードを目指して:コードへのコメント(1)を参照してください。

2018-08-13

DebConf18でuscanの改善に関する発表をしました

はじめに

クリアコードの林です。

今回は、DebConf18で発表する機会があったのでその内容を紹介します。 今年のDebConf18は台湾の国立交通大学(National Chiao Tung University - NCTU)にて開催されました。*1

NCTU

桃園国際空港からはMRT*2 とHSR*3、台湾バス*4を乗り継いで行きました。

Taoyuan to Hsinchu

DebConf18の統計情報 *5によると、全体で309人が参加、そのうち日本からは27名が参加したようです。距離的にも近いということで参加者も多かったのではないでしょうか。

Rethinking of debian/watch rule

Xueshan Room X

発表資料はRabbit Slide Showにて公開しています。

Debianのパッケージでは、アップストリームの更新を検出するために uscan というプログラムが使われています。uscan の設定は debian/watch という設定ファイルに記述します。 debian/watch ファイルにはアップストリームのリリースファイルのアクセス先や、パッケージング内容に沿うようにファイル名を変換したりするなど、いくつかのルールを記述します。 このルールは正規表現を使って記述するのですが、場合によっては複雑なルールを記述しなければならないことがあります。

例えば、複雑なルールを記述しなければならない例の一つに、OSDN.netでリリースされているソフトウェアがあります。

version=4
opts="uversionmangle=s/-beta/~beta/;s/-rc/~rc/;s/-preview/~preview/, \
pagemangle=s%<osdn:file url="([^<]*)</osdn:file>%<a href="$1">$1</a>%g, \
downloadurlmangle=s%projects/sawarabi-fonts/downloads%frs/redir\.php?m=iij&f=sawarabi-fonts%g;s/xz\//xz/" \
https://osdn.net/projects/sawarabi-fonts/releases/rss \
https://osdn.net/projects/sawarabi-fonts/downloads/.*/sawarabi-mincho@ANY_VERSION@@ARCHIVE_EXT@/ debian uupdate

Debianパッケージ化をするときには、必須ではありませんが debian/watch ファイルも用意してあると、その後のメンテナンスが楽になるのでおすすめです。 ただし、Debian Policyでは Optional 扱いです。*6

発表時には、パッケージングをするときに複雑になりがちな設定を簡単にできないかという観点で、debian/watch の使われ方の傾向を調べつつ、フォーマットの改善案を提案してみました。

発表して終わりという性質のものではないので、引き続き、bugs.debian.orgにて議論していければいいなぁと思っています。

おまけ

8/2の夜は海鮮が美味しいとされるおすすめのところでのカンファレンスディナーでした。DebConfのスポンサーのおかげですね。

カンファレンスディナーのパネル

画像の説明 画像の説明 画像の説明

まとめ

今回は、DebConf18に参加して発表する機会があったので、その内容を紹介してみました。

映像記録ありのセッションに関しては、順次アーカイブからWebM形式の動画が参照できるようになっているようです。また、YoutubeのDebConf Videosチャンネルからも(過去のDebConfの映像を含め)閲覧することができるようになっています。 見逃したセッションがあればそちらを確認してみるのはいかがでしょうか。

*1 博愛キャンパスと光復キャンパスとがありますが会場であるNCTU MIRC(National Chiao Tung University Microelectronics and Information Research Center)は光復キャンパスにあります。

*2 桃園捷運(とうえんしょううん)。いわゆる地下鉄のこと。

*3 台湾高速鉄道。いわゆる新幹線のこと。

*4 前乗り、前降りというあまり日本人には馴染みのないスタイル。

*5 Debian SSOによるログインが必要です。

*6 https://www.debian.org/doc/debian-policy/ch-source.html#optional-upstream-source-location-debian-watch

2018-08-06

不具合をフィードバックする時に、不具合の様子を動画で説明する

一般的に、バグ報告は「再現手順」「期待される結果」「実際の結果」をセットで説明する事が望ましいと言われます。報告をする側にとっては自明の事でも、報告を受ける側にとってはそうではないという事は多々あり、そういった情報を過不足なく伝えるための指針と言えるでしょう。

しかしながら、特定のタイミングで操作した場合にのみ再現するというような不具合だと、言葉で説明しきるのは難しい場合があります。また、日本人が英語でフィードバックするというように自分の得意でない言語を使う場面では、説明自体がそもそも困難な場合もあります。そのような場合に便利なのが「スクリーンキャスト(screencast)」です。

スクリーンキャストとは、自分が操作しているPCの画面全体またはその一部の様子を動画にした物の事です。ゲームの実況配信などもスクリーンキャストの一種と言えます。今回、Firefoxにおいてドラッグ操作と同時にタブの復元が行われると、以後のドラッグ操作のイベントが発行されなくなる、という不具合の報告を行うにあたり、現象の再現のための操作のタイミングがシビアだったため、念のためスクリーンキャストの形式でも現象発生時の様子を報告することにしました。同様の形でフィードバックをしたい人は参考にしてみてください。

スクリーンキャストの録画方法

以下は、OS標準の機能や標準添付のアプリケーションなどによる手軽なスクリーンキャスト撮影の方法をWindows、macOS、Linuxの各プラットフォームごとに例示します。文字を入れるなどの高度な編集を行いたい場合は、動画編集ツールを別途組み合わせて使ったり、より高機能なスクリーンキャスト撮影用アプリを使ったりする事をおすすめします。

Windowsの場合

Windows 10 Creators Update(バージョン1703)以降では、「ゲームバー」という機能を標準状態で使えます。これはその名の通りゲームアプリのための機能で、前述の例のようなゲーム実況を行えるよう、アプリケーションのウィンドウ単位でのスクリーンキャスト機能を含んでいます。1つのウィンドウの中で完結する現象であれば、これを使って説明用のスクリーンキャストを作成できます。

スクリーンキャストを撮影したいウィンドウにフォーカスがある状態で、キーボードのWindowsキーを押しながら「G」キーを押すと、以下のような画面が表示されます。

ゲームバーが表示された様子

ゲームバー上部の「Allow gaming features」のチェックボックスにチェックを入れると一旦ゲームバーが消えますので、もう一度ゲームバーを開きなおします。すると、先ほどまでは使用できなかったゲームアプリ向け機能を使えるようになっています。左上の「Game capturing」という領域にあるボタンがスクリーンキャスト用の機能です。

有効化されたスクリーンキャスト機能

中央の「●」アイコンのボタンをクリックすると、即座に録画が始まります。録画中は画面の右上の方に以下のような小さなツールバーが表示され、「■」アイコンのボタンをクリックすればその時点で録画が終了します。

スクリーンキャスト録画中に表示されるツールバー

録画が終了すると、撮影された動画が「ビデオ」内の「キャプチャ」フォルダ(C:\Users\(ユーザー名)\Videos\Capturesの位置)に保存されます。

保存されたスクリーンキャスト

macOSの場合

macOSでは、標準状態で添付されているアプリの「QuickTime Player」にスクリーンキャスト撮影機能が含まれています。スクリーンキャストを撮影する準備が整ったら、QuickTime Playerを起動します。

アプリケーション一覧からQuickTime Playerを選択している様子

QuickTime Playerを起動したら、メニューの「ファイル」から「新規画面収録」を選択すればスクリーンキャスト録画用のウィンドウが開かれます。

メニューの「新規画面収録」を選択している様子 画面収録の操作用ウィンドウ

赤い「●」アイコンのボタンの横にある「⌄」マークをクリックすると、カーソルを含めるかどうかなどのオプションを設定できます。準備ができたら「●」アイコンのボタンをクリックします。すると、画面上のどの領域の様子を動画として録画するかを訪ねるメッセージが表示されます。

録画する領域を訪ねるメッセージ

デスクトップ全体を録画すると動画が大きくなりすぎますので、現象を説明するのに必要最小限の領域を設定するのがおすすめです。画面上をドラッグすると、矩形選択の要領で録画対象の領域を設定できます。

録画領域が設定された状態

録画領域が決まったら、「収録を開始」ボタンをクリックすればすぐに録画が始まります。録画中は画面上部のメニューバーに「■」アイコンのボタンが表示されており、これをクリックすると録画が終了します。

録画を終了するボタン

録画された動画のプレビューが表示されますので、後は任意のファイル名で保存すれば*1スクリーンキャストは完成です。

Linuxの場合

Linuxディストリビューションのデスクトップ環境は、標準で録画機能を備えている例は今のところ無いようですが、スクリーンキャスト録画用のアプリケーションは簡単に導入できます。ここではその例としてUbuntu 16.04LTSでKazamを使う例を紹介します。

sudo apt install kazamでパッケージをインストールして、端末上でkazamコマンドを実行するかメニューから「Kazam」を選択すると、Kazamが起動します。

Kazamが起動した様子

Kazamは画面全体の録画、アプリケーションのウィンドウ単位での録画、指定した領域の録画のそれぞれに対応しています。「Window」をクリックすると、録画対象にするウィンドウをクリックで選択する画面に切り替わります。「Area」をクリックすると、録画領域を矩形で選択する画面に切り替わります。矩形選択の場合は、範囲を選択した状態でEnterキーを押すと選択が確定されます。

録画領域を設定している様子

録画対象が決まったら、メインウィンドウの「Capture」ボタンをクリックすると録画が始まります*2。Kazamが起動している間はデスクトップ上部のバーにカメラのアイコンが表示され、これをクリックしてメニューから「Finish recording」を選択すると録画を終了できます。

録画を終了する様子

録画を終了すると、動画の取り扱いを選択するダイアログが表示されます。

録画した動画の取り扱いの選択

ここから動画編集アプリを起動して加工工程に移る事もできますが、録画した物を無加工でアップロードするだけであれば、「Save for later」を選択して動画ファイルを保存します。

スクリーンキャストの公開

FirefoxのBugzilla(bugzilla.mozilla.org)ではbugに任意のファイルを添付できますので、録画したスクリーンキャストをそのまま添付・アップロードする事も可能です。

メーリングリストへの投稿の場合のように、動画そのものを添付できない*3ケースでは、別途YouTubeのような動画共有サイトに投稿してそのURLを記載するという方法をとると良いでしょう。その場合、動画の共有範囲を「公開」とするか、またはURLを知っている人なら誰でも閲覧できるように設定しておく必要があります。

まとめ

以上、各プラットフォームでのスクリーンキャストの録画手順を簡単に紹介しました。

障害報告のフィードバックは分かりやすい言葉で書かれているに超した事はありませんが、「言葉で説明しなければならない物」ではありません。スクリーンキャストや、あるいはスクリーンショットのように、「言葉で説明するより見た方が早い」物については、むしろ積極的に画像や動画を活用して障害発生時の様子を伝えた方が良いでしょう。皆さんも是非、様々な手段を駆使してより分かりやすいフィードバックを行うように工夫してみてください。

*1 初期状態では「.mov」形式になります。

*2 初期状態では5秒間のカウントダウン後に録画が始まります。

*3 メールに動画を添付すると、そのファイルが全受信者に複製されて届き、各受信者の通信帯域やローカルディスクを圧迫する事になるため、メールへの大きなファイルの添付は避ける事が望ましいです。

2018-07-31

Go言語でつくるインタプリタ

まともにGoでプログラムを書いたことはない須藤です。

2018年6月にオライリー・ジャパンから「Go言語でつくるインタプリタ」というプログラミング言語のインタプリタをGo言語で実装するという内容の本が出版されました。

Go言語でつくるインタプリタ
Thorsten Ball/設樂 洋爾
オライリージャパン
¥ 3,672

内容

書名からはわかりにくいですが、「インタプリタを作りながらGo言語を学べる本」ではなくて「Go言語を使ってインタプリタの作り方を学ぶ本」です。

必要なGoの知識

Go言語自体の説明はとくにありません。なにかしらプログラミング言語を知っていたら見ればわかるよね、という感じです。結構前にA Tour of Goをやったことがあるくらいの私はなんとなくわかりました。ただ、プログラミング言語が初めてな人はわからないと思うので、「これからプログラミングをはじめよう!Goというのがよさそうだからこの本で勉強しよう!」という人には向きません。

私がわからなかったGoの書き方は以下の通りです。300ページ弱でこれだけだったのでGoを知らない他の人も大体わかると思います。

6ページ:

func New(input string) *Lexer {
    l := &Lexer{input: input}
    return l;
}

*Lexerとか&Lexer{...}ってなんだ!?(後で調べてGoにもポインターがあったことを知った。)

11ページ:

if tok, ok := keywords[ident]; ok {
    return tok;
}

ifの条件部に代入文(文でいいの?)と式を書けるってこと?(私はうまく構文解析できなかった。)

126ページ:

switch node := node.(type) {
// ...
}

node.(type)ってなんだ!?(後で調べてtype switchという書き方と知った。気持ちはわからなくもないけど同じ変数に代入しなくてもいいんじゃないかなぁ。)

137ページ:

leftVal := left.(*object.Integer).Value

キャストってこんな風に書くの!?(後で調べてtype assertionという書き方と知った。)

142ページ:

integer, ok := tt.expected.(int)

キャストできるかどうかってこんな風に書くの!?(後で調べてtype assertionの別の書き方だと知った。)

224ページ:

t.Fatalf("Eval didn't return Hash. got=%T (%+v)", evaluated, evaluated)

%T%+vってどういう表示方法の指定なんだ!?(後で調べて%Tは型名を表示する指定で%+vはフィールド名付きで構造体を表示する指定だと知った。)

実装するインタプリタの機能

この本では{...}でコードブロックを表現するプログラミング言語のインタプリタを実装します。このプログラミング言語には四則演算機能や(...)での評価順の変更機能、変数、関数、配列・ハッシュテーブル(コンテナー)など一通りの機能が揃っています。クロージャーも実装しています。また、付録ではマクロも実装します。

クロージャーやマクロをどうやれば実現できるかの雰囲気を知れるので、普段使っている機能はどうやって動いているかが気になる人には面白いでしょう。

字句解析から始まるので、プログラミング言語に限らずパーサーを実装する機会がある人には参考になると思います。たとえば、CSSのパーサーを実装する機会がある人とか。

雰囲気

文章はテンションが高いです。TDDの本みたい。

そうそう、テストを書きながら実装を進めていくのは他の本ではあまりやっていないスタイルだと思います。ただ、いろいろテスティングフレームワークを実装している私からすると野暮ったいテストだなぁという気持ちになります。Goのtestingパッケージはエラーメッセージのフォーマットを自分でやる方針なので私と好みが違うだけなんですが。。。

日本語はすごく読みやすいです。もともと日本語で書かれた文章みたいというほどではなく、翻訳された日本語という感じです。ただ、「ひとつの文がすごく長い」とか「日本語として意味がわからない」とか「そもそも原文ではなにを言いたかったんだ?」となることはありません。リーダブルコードの解説は翻訳された日本語風に書いたんですが、そんな感じです。ダジャレはありませんでした。(気づきませんでした。)

まとめ

プログラミング言語の処理系の実装の勉強をしたいとかGoの雰囲気を知りたいという人は楽しめると思います。私は手を動かさずに読んだだけなんですが、手を動かしながら読むともっと理解が進むと思います。(私は移動時間で本を読むことが多くて、パソコンの前にいるときに読むことはあまりないんです。。。)

2018-07-30

Windows Subsystem for LinuxでプレゼンツールのRabbitを動かす

皆さんはRabbitというプレゼンテーションツールをご存じでしょうか。2018年のRubyKaigiでのMatz氏によるKeynoteでも使われており、「名前は知らないが見た事はある」という方もいらっしゃるかもしれません。

RabbitはRubyで開発されており、Git等でバージョン管理しやすいMarkdown形式やRD形式などのプレーンテキスト形式でスライドを記述できたり、「スライドの進行割合」を表すウサギと「時間の経過度合い」を表すカメのアイコンでスライドの進行状況を把握できたりと、痒い所に手が届くツールです。

RabbitはWindowsでも動作するのですが、開発は主にLinux上で行われているため、Windows上では動作が不安定だったり表示が崩れたりと、期待通りの動作結果にならない場合があります。このような場面に出くわした場合、Windows上での動作を改善するためのフィードバックやパッチの作成に挑戦してみるいい機会なのですが、発表の日程が差し迫っているためそこに時間をかけられない、というような場合もあるでしょう。

一般的に、有志の個人開発者によって開発・メンテナンスされているフリーソフトウェアは、開発者自身が日常的に使っている環境に近い環境で最も安定して動作します。そういう意味では、仮想マシンを用意したり、使用していないPCを用意したりしてLinuxディストリビューションをインストールしたりという形をとるのが常道なのですが、この変形として、Windows 10であればWSL(Windows Subsystem for Linux)とWindows用のXサーバーを組み合わせるという方法が使えます。

以下、本記事での解説は、WSLのLinuxディストリビューションとしてUbuntuを使用している状況を前提とする事にします。

WSLでRabbitが動く理由

WSLは、Windowsのカーネルに対して、Linuxカーネルのように振る舞うための層を被せることで、UbuntuやFedoraなどのディストリビューションで提供されているビルド済みバイナリをそのまま動作させられるようにする技術です(動作イメージ、導入手順はまんがでわかるWSLなどをご覧ください)。

画面描画の基盤技術はWindowsとLinuxで顕著に異なる部分であるため、Windows向けにRabbitやその依存ライブラリ群を完全対応するのは非常に大変な作業です。しかし、WSLであれば基盤部分はLinux用の物がそのまま使われるため、Rabbitのように主にLinux上で開発されているGUIアプリは、「Windows用の移植版」を動かすよりも「WSL上でLinux用のオリジナル版」を動かした方が良い結果を得られる場合がままあります。

ただし、そのために1つだけ欠かせない物があります。それがWindows用のXサーバーです。

Windows Subsystem for Linuxで使えるXサーバー

X(X Window System)は、現在多くのLinuxディストリビューションで一般的に使われている、GUIを実現するための最も重要な基盤技術です。「物理的な画面をキャンバスとして管理し、各ウィンドウを描画する」「ユーザーのクリック操作やキー入力などの操作を一元的に受け付けて、情報を各アプリケーションに引き渡す」といった事を行う物なのですが、現在の所WSL上ではXサーバーは動作しません。何故かというと、WindowsはWindowsでXとは別の画面描画の仕組みを持っており、両方が同時に動作するとリソースの奪い合いになってしまう(マウスやキーの入力をWindowsが受け取ればXが受け取れなくなるし、Xがそれらを受け取ればWindowsが受け取れなくなる)からです。

そこで登場するのがWindows用のXサーバーです。これはWindowsアプリケーションの1つとして振る舞いつつ、一般的なXサーバーと同じ機能を提供する(アプリケーションがXに対して指示した位置にWindowsのウィンドウを開き、そのウィンドウに対して操作が行われた場合はXが操作を受け付けたものとしてアプリケーションに情報を引き渡す、という働きをする)物です。VcXsrv(※リンク先はダウンロードページ)はその代表的な例で、VcXsrvを起動しておき、WSL上のアプリケーションに対してVcXsrvをXサーバーとして使うよう指示する事により、WSL上のGUIアプリケーションがWindows上でも動作するようになります。

という事で、まずはリンク先のページのダウンロード用ボタンからVcXsvrのインストーラをダウンロードして、インストールしましょう。

VcXsrvを初めて起動すると、「Display settings」というタイトルのウィザードが表示されます。ここではとりあえず以下のように設定して下さい。

  1. 「Multiple Windows」を選んで、「次へ」を押す。
  2. 「Start no client」を選んで、「次へ」を押す。
  3. オプションは変えず、「次へ」を押す。
  4. 「完了」を押す。
  5. WindowsのファイアウォールがVcXsvrによるネットワーク接続に対して警告を出すので、接続を許可する。

初期設定を終えると、VcXsrvのアイコンがタスクトレイに表示されます。VcXsvrを使わないときは、このタスクトレイ上のアイコンから終了させておくと良いでしょう。

VcXsvrのアイコンがタスクトレイに表れている様子

Windows上のXサーバーを使うように、WSLの環境を設定する

Windows上でXサーバーが動作しているだけでは、WSL上のGUIアプリケーションは動作しません。WSL上のGUIアプリケーションに対して、Windows上のXサーバーを使うように指示する必要があります。これは以下のようにすれば実現できます。

  1. WSLのUbuntuが提供するBashを起動する。
  2. echo 'export DISPLAY=localhost:0,0' >> ~/.bashrc と実行する。
  3. exitでWSL上のBashを終了し、再度WSL上のBashを起動する。

この操作により、Bash起動時に読み込まれる初期化用の設定ファイルに、入出力用の画面としてWindows上のXサーバーが提供する仮想的な画面を使うように指示するための指定が追記され、それが反映された状態でBashが起動します。

Rabbitのインストール

Xサーバーの準備ができたら、いよいよRabbitのインストールです。Rabbitは、プラットフォームのパッケージ群と、RubyGemsのパッケージ群の2段階に分けてインストールする必要があります。

  1. Rabbitの実行に必要なパッケージ群をインストールする。 sudo apt install rubygems ruby-dev build-essential fonts-ipafont で一通りインストールされる。 (フォントはfonts-notoなど他の選択肢もありますが、行の高さの違いが原因で表示が崩れてしまう事があるので、安全のためにはfonts-ipafontをインストールする事をお勧めします。)
  2. Rabbitをインストールする。 sudo gem install rake rabbit で必要なGemパッケージ群が一通りインストールされる。
    • この時、Windowsのファイアウォールがgemコマンドによるネットワークへの接続に対して警告を出すので、明示的に許可を与える。

以上でRabbitのインストールは完了です。

スライドの作成と実行

Rabbit用のスライドは、前述したとおりRDやMarkdownなどの形式で作成できます。試しに、以下のような内容でファイルを作成し、Windowsのデスクトップ上にsample.mdのような名前で保存して下さい。文字エンコーディングや改行コードは自動判別されますが、安全のためにはLinux上で一般的な「UTF-8(BOM無し)、改行コードLF」を使用する事をお勧めします。

# Rabbitでプレゼン

subtitle
:   WSL上のRabbitで表示

author
:   自分の名前

institution
:   所属会社

allotted_time
:   45m

# 準備

 * Xサーバーのインストール
 * Rabbitのインストール

# おわり

ご静聴ありがとうございます

Markdownファイルができたら、ファイルを置いたディレクトリーに移動し、ファイル名を引数に指定してrabbitコマンドを実行しましょう。RabbitのウィンドウがWindows上で開かれ、スライドの1ページ目が表示されます。

$ cd /mnt/c/Users/(ログオン中のユーザーアカウント名)/Desktop
$ rabbit ./sample.md

WSL上のRabbitのウィンドウがWindowsのウィンドウとして表示されている様子

Rabbitのスライドは、Enterキーで次のページに進み、BackSpaceキーで前のページに戻ります。サンプルを見ると分かる通り、最大レベルの見出しがそのままスライドの各ページのタイトルになります。詳しくはMarkdown形式でのスライドの書き方を参照してください。

ウィンドウの最下部のウサギは現在スライドの何ページ目が表示されているかを示しています。allotted_timeで時間を指定してある場合(この例では「45分」という意味になります)、その時間に合わせてウィンドウの最下部をカメが進んでいくようになります。カメが先行していれば進行が遅れ気味、ウサギが先行していれば逆に走り気味という事になります。

Rabbitのスライド下部に表示されているウサギとカメ

まとめ

以上、Windows上でWSL経由でRabbitを動作させる手順をご紹介しました。

Rabbitのサイトでは、Rabbitで作成された様々なスライドの例が公開されています。どんなスライドを作れるのか、ぜひ参考にしてみて下さい。

タグ: Ruby
2018-07-27

サポートエンジニアNight vol.3

「サポートエンジニア」と聞いて、皆さんはどのような職務内容を想像するでしょうか。ついたてで仕切られたブースに大勢の人が並んで座っている? 開発経験の浅い、あるいは経験が無い人が、マニュアルに従って問い合わせを右から左にさばいている? 突っ込んだ事を質問されたら「仕様です」で煙に巻く? エンドユーザー向けの無料の電話サポートだとそのような形態のサポートもあるようですが、BtoB(法人向け)のテクニカルサポートは、それとはだいぶ状況が異なります。

去る2018年7月18日、そのようなサポート業務に関わる人達の情報交換を目的としたイベントであるサポートエンジニアNight vol.3が開催されました。発起人の方による背景説明では、BtoBのテクニカルサポートが技術的な問題解決力を要求される物であり、ビジネス上も非常に重要な役割となっている事が語られています。

当社は自社Webサービスは運営しておらず、またパッケージ製品の販売も行っていませんが、WebブラウザのFirefoxやE-mailクライアントのThunderbird、全文検索エンジンのGroonga、自社開発したmilter managerのほか、ご相談に応じて様々なフリーソフトウェアのテクニカルサポートを法人向けに提供しています。その過程で必要となる「調査の仕方」や「開発元へのフィードバックの仕方」などはフリーソフトウェア開発一般の知見と言う事ができ、ノウハウも広く知られていますが、それらをお客様にサービスとして提供しビジネスとして成立させる部分については、我流でどうにか回してきたというのが正直なところです。ノウハウとして確立しているとは言い難く、新たに採用したメンバーにサポート業務に関わってもらう際には特に、知見の伝達に苦労しています。この記事では、サポートエンジニアNight vol.3で発表されていた知見について、当社のサポートビジネスでの知見も交えつつまとめてみます。

(なお、会場入り時間の関係でTreasure Dataとはてなの事例については発表を聞けておりません。)

Sider, Inc.の事例

専任のサポート担当者をいつ置くべきかという問題

GitHub上でのプルリクエストに対して自動コードレビューを行うサービスであるSiderでは、元々専任のサポート担当者は置かれておらず、開発チームの人員がサポートを兼任するという体制だったそうです。その後、サポートにかかる負荷の増大を受けて、段階的にサポート体制を改善していったという事が述べられていました。

  • 専任のサポート担当者がいない時期には、「気付いた人が対応する」というモデルから始まった。
    • 問い合わせがあると、その情報が社内のSlackに流れてくるので、それに気がついた人が対応する。
    • このモデルだと、重い開発タスクが発生しているときにサポートに手が回らない事があった。
  • 週替わりで担当者をローテーションする方式(ラウンドロビン方式)にしてみた。
    • このモデルでも、担当者が不在の場合は結局元と同様に「誰か気付いた人が代わりに対応」する必要があった。
  • 専任のサポートエンジニアという役割を設ける事にした。
    • 重めの開発タスクは振られなくなった。
    • ドキュメントのメンテナンスは職責外だが(おそらくサポートの過程で発覚した不備は)対応している。
    • 開発タスクも、期限が切られていない(重くない)物については対応している。
    • サービスのバックエンドとして様々なOSSを使用しているため、サポートの中でそのOSS製品にある不具合に遭遇した場合には、アップストリームへのフィードバック(コントリビュート)も行う。
    • サービスの性質上、サポート業務上でも様々なコードに触れる必要がある。

具体的な数字として、直近7.5ヶ月での対応件数は291件だったそうです。1ヶ月の営業日を仮に20日と仮定すると、291÷(20×7.5)≒4.2で1営業日あたり4件強のサポートをこなしている計算になるでしょうか。テクニカルサポートでコンスタントに1日4件の問い合わせを1人でさばくというのは、当社の場合は相当忙しいという印象*1です。

サポートをやりやすくするための工夫

サポートを効率よくこなすための方法としては、以下のような事が語られていました。

  • ユーザー側から見えるステータスとは別に、内部的にはエラーのステータスがさらに細かく分かれており、それを見ればだいたいの見当がつくようになっている。
  • Inspectlet(行動トラッキングツール)を採用しており、UIに関する問い合わせにおいて、ユーザーが実際に行った操作がどのようなものであったかを調べられるようにしている。
  • 様々な手法を駆使し、なるべくユーザーの手を煩わせないで問題を解決できるようにしている。

エラー情報をリッチな物にしておく事は、ソフトウェア開発において一般的に大事な事です。というのも、最初に伝えられるエラーに十分な情報が出力されていれば、そこからの原因調査が楽になるからです。エラー情報が不十分だと、まず同じ現象を再現させる所から取りかからなくてはならず、再現できなけれは調査は大幅に困難なものとなります。当社のFirefox/Thunderbirdサポートにおいても、Firefox自体が持つエラーコンソールの内容や、より低レベルのログの収集を依頼する事が多々あります。

サポート品質をどう定めるべきかという問題

現状の問題点としては、以下のような事が述べられていました。

  • サポート品質の指標が定まっていないため、「なるべく早く返す」「なるべく詳しく説明する」といった事について、担当者個人の裁量任せとなってしまっている。
  • 現状ではまだ担当者が一人だけのため、不在時にどうすればよいかについては未解決のままになっている。
  • サービスは世界中で使われるため、問い合わせの10〜20%は海外から寄せられる物であるが、タイムゾーンが異なるため、問い合わせが日本での業務時間外にくる事がある。
    • そのような問い合わせに即時対応しようとすると、労働基準法に抵触する事になるため、即時対応ではなく翌日対応としている。
    • どうしてもという場合には、労働基準法が適用されない管理職の人に対応してもらうという方法がある。

当社のサポートサービスの場合、約款によって「サポートの回答期限はN営業日以内」「サポートの受付時間を何時から何時までと定め、その時間外に発生した問い合わせについては翌営業日を起算日とする」という事を明記しています。基本的には可能な限り早く対応していますが、問い合わせが同時多発的に発生した場合にはそのすべてに1日以内で対応しきれない場合もありますし、担当者が不在の場合もあります。そのため、「諸々の条件を考慮して確実に提供可能なサービスレベル」を先に見積もっておき、そのサービスレベルの範囲内での対応をお約束するという形で、あらかじめお客様に了解を頂いている*2という事になります。

株式会社レトリバの事例

音声認識のソフトウェアパッケージを提供しているレトリバでは、「研究部門」で生み出された技術を「製品開発部門」で製品に落とし込み、「営業技術部門」で販売やサポートを行っているそうです。このため、テクニカルサポートは営業技術部門の担当範囲となっていて、他の業務との兼務でサポートを行われているとの事でした。

サポートへの問い合わせが減るようにするという事

こちらの会社では、サポートにあたっては以下の事を大事にされているそうです。

  • お客様が困っている問題を迅速に解決する事
  • お客様が困っている課題を製品にフィードバックし改善して、お客様の課題を解決する事
  • お客様にスキルを伝達し、なるべくサポートに問い合わせなくても良いようにする事

一見してユニークなのは3番目の点です。近視眼的に考えれば、お客様からのお問い合わせを受けてさばくのがサポートの仕事である以上、問い合わせ件数を減らすのは自分で自分の仕事を減らして、自分の存在意義を減じる事に思えるかもしれません。ですがより大きな視点で考えると、問い合わせが減る事には以下のようなメリットがあります。

  • 簡単なトラブルはお客様側で自己解決してもらえるようになると、お客様のビジネスがより円滑に進むようになる。
  • 問い合わせが減るとサポート業務が減り、その分他の事に時間を割けるようになる。
  • 簡単で退屈な作業が減って、困難な少数の仕事にじっくり取り組めるようになると、純粋な技術者視点においてやりがいが増す。
  • 問い合わせに多く答える事が直接的な収益増につながらない契約形態なのであれば、問い合わせ対応に要する工数と収益には負の相関関係が成立する。端的に言うと、問い合わせが発生しない方が助かる。

パッケージの販売や製品の使用権のサブスクリプション契約を伴わない、純粋なサポート契約のみを行っている当社の場合でも、これらの点はメリットになっています。というのも、当社のサポート契約はインシデントサポート*3なので、上記の4番目の点と同じ事が言えるからです*4

ただ、どの程度まで「お客様を育てる」かは場合によりけりであるとのことでした。サポート提供側の狙い通りに自己解決できるようになってくれるお客様ばかりではなく、中には、これについて教えてくれるのであればあれもこれも教えてほしい、と本来のサポート範囲以上の事を求めてくるタイプのお客様もいるそうで、そのようなケースではある程度のところで対応を打ち切る必要もあるようです。当社のサポートにおいても、障害の発生原因を切り分けて対応責任の有無を明確にする事を心がけています。

サポート提供者もお客様も、どちらも「完璧」ではないという事を前提にした運用

また、それ以外のトピックとしては、過去のサポート対応事例はその都度チケットに記録を残しておき、同じお客様から同じ問い合わせがあった時に時間をかけずに回答したり、何月何日に回答済みと返したりできるようにしておく、という事も行っているとのお話がありました。お客様側も人間である以上、忘れるという事はありますし、また担当の方が変わった時に十分な情報が引き継がれていなかったり、複数の担当の方がいる場合に情報が随時共有されていなかったりという事は起こりえます。またサポート担当者が複数名いる場合には、サポート提供者の側でも情報を共有しておく必要があります。

当社でも、サポートのお問い合わせはすべてRedmineのチケットで管理しており、基本的な運用は以下のような形を取る事が多いです。

  • 新しい問い合わせが発生した場合、問い合わせ内容を説明文として、新しくチケットを作成する。
  • その問い合わせに対して回答を返し終えたら、回答文を注記として加える形で、ステータスを「解決」に設定する。
  • 回答に対し再度質問があった場合や、回答の中で「この点を調査した結果をお知らせください」と記載した部分について情報をご連絡いただいた場合、その問い合わせや情報を注記やチケットへの添付ファイルとした上で、ステータスを「フィードバック」に設定する。
  • ステータスが「解決」または「終了」でないチケットは、すべて「こちらがボールを持っており、こちらから連絡(回答)しなければならない」状態として取り扱う。

ただし、当社では、お客様からのお問い合わせのメールを元にRedmineに起票して、それ以後のやりとりをそのチケット上で管理するという場合と、お客様自身の手でRedmineに直接起票を頂く場合の両方をお客様ごとに切り替えています。メールベースでのサポートの場合、メールの本文がすべてRedmineのチケットの情報として記録される形になっており、過去に回答済みの情報はRedmineの検索機能を使って見つける事になります。

Herokuの事例

チケットベースでのサポート運用

Herokuでは、サブスクリプションモデルである事からユーザーの離脱率を下げる事が重要視されているようで、単なる「問い合わせ窓口」にとどまらず、サポートの質を高める事を強く意識しているという事が述べられていました。

  • サポートの受付はすべてチケットの起票を以て行う運用をとっている。
    • チケットが起票されると、サポートエンジニアが閲覧しているSlackに通知が流れるようになっている。
  • 第1担当者から第3担当者までの3人1ユニットを「その週のサポート担当ユニット」として、週替わりでローテーションしている。
    • その週の担当ユニットの中では原則として第1担当者が新規チケットを拾い上げるが、第1担当者が不在であれば第2担当者が、第2担当者も不在であれば第3担当者が拾い上げる。
    • サポートエンジニアは世界中の各地に在住しており、ユニット間・チーム間の連絡はインターネット越しに英語で行っている。
  • 日本語での問い合わせチケットは、日本語の問い合わせであることを示す専用の分類になっている。
    • 日本語で問い合わせがあった場合、それを英訳して、通常のサポートチケットとして起票し直す。
    • 回答が出たら、それを適宜日本語に翻訳し直して、元の問い合わせに対する回答とする。

当社のサポートでお客様に直接Redmineに起票を頂くケースは、お客様自身が開発者やサーバーの運用担当者である場合に取る事が多く、ユーザー層がITエンジニアであり要領が掴めている事が期待されるHerokuと事情が似通っていると言えそうです。

日本語の表現の問題

日本語でのサポートがシステム上は特別な扱いになっているというのは、英語アレルギーの人が多いという日本固有の事情でしょうか。問い合わせ内容を日本語から英語へ・回答を英語から日本語へ翻訳する際には、以下の点が難しいとの事を述べておられました。

  • 直訳すればよいという物ではなく、ケースバイケースで時には大幅な「意訳」も求められる。
    • 書かれていない事を補ったり、書いてある内容を言い換えたりしなくてはならない。
  • サポートの回答に特有の表現などがあるためか、ビジネス日本語・敬語の取り扱いに苦慮している。
    • 「これを読んでからメールを書けば失礼がない」というような参考書が求められている。

当社のサポートサービスは日本国内のお客様が対象なので、敬語については同様の悩みを持っています。当社では特にビジネスメール研修のような事は実施していないため、サポート経験が浅い担当者が書いた回答文は手直しが必要な場合もあり、ある程度慣れるまではベテランサポート担当者が回答をレビューするという体制をとっています。教科書のようなテキストとして使える参考書籍があれば、当社でも是非導入したいところです。

サービスへのフィードバック

問い合わせに対する回答以外での顧客満足度に寄与する取り組みについても紹介がありました。

  • サービスのステータス(負荷の状況、障害の発生状況や復旧状況)は積極的・自発的に公表するようにしている。
    • 障害が発生しない安定したサービス、であるかのように装おうとして障害の発生を隠そうとしても、ユーザーのリテラシーが高いためすぐにボロが出る。
    • それよりも、正確な情報を公開しておく事の方が信頼に繋がる。
  • 同じ問い合わせが何度も発生する状況は、サービスの異常や不備の兆候と考えて、サービス運用に適宜フィードバックする。
  • FAQ(よくある質問と回答)というレベルに至っていなくても、受けた問い合わせに基づく特定の事例でも、ナレッジとして文書にまとめて公開する。
    • ナレッジからサービス・製品にフィードバックする場合もある。

先のレトリバ社の事例でも同様の話があったと思われますが、同じ問い合わせが度々発生するというのは不調や不備の兆候と考えるのが妥当で、FAQを用意して終わりと片付けて良いものではありません。皆が躓く石があるのであれば、まずはそれを取り除くのがベストで、避け方をノウハウとして周知するのはあくまで次善の策です。サービス品質やユーザー体験の向上のための情報が集まる窓口として、サポートが大事な役割を担っているという事がよく分かる事例ですね。

当社の場合も、自社開発製品でないフリーソフトウェアのサポートにおいて未知の不具合に遭遇する事は度々あり、原則としてそれらはアップストリームにもフィードバックするよう努めています。また、Redmineにお客様自身で起票を頂くケースでは、ナレッジ蓄積・共有の場としてRedmineに付属のWikiをお客様自身がお使いになっている場合もあります。

かなり割り切った評価システム

サポートそのものの品質に関わる話として、得られたサポートに対する顧客からの評価・フィードバックという物があります。満足度を5段階評価で入力するなど、この種のフィードバックの収集方法には様々なやり方があり得ますが、詳細なアンケートをとろうとすればするほど顧客側の負担が増えてしまうため、どこまで質問するかというのは非常に難しい問題です。

Herokuでは(一旦10段階評価を導入したものの意味が無かったので改めた、というような経緯があったわけではなく、)当初から単純な「良かった or 悪かった」の2値入力にしているとの事でした。これについて、サポートエンジニアの方々は元気がない時に「良かった」評価を眺めて元気を出すというような「使い方」もされているそうです。製品開発での「リリース」のような分かりやすい成果が見えにくいサポート職において、従事する人自身のモチベーションを高める一つの方法として参考になる事例だと言えるでしょう。

密なコミュニケーションを心がける事

会社のメンバーが各国に離れていることから、エンジニア間でのコミュニケーションには、メール、Slack、Google Hangoutでのビデオチャットなど様々な方法を併用しているそうです。中でも、特に文字情報でのコミュニケーションにおいては、「思っていて表情には出ているが、文字には現れない」というような、その通信手段上では伝達されない情報からくる微妙な齟齬をなくすために、大げさなくらいに感情や状況を報告するという、いわゆるオーバーコミュニケーションを心がけているという事が語られていました。

それでも全く対面した事がないままでコミュニケーションを続けていると伝わらない部分が出てきてしまうそうで、年に何回かというペースで全社的に一カ所に実際に集まり、お互いに顔を見て対面しながら話す機会を設けて、感謝の言葉などのやりとりをしているそうです。

サポートにおける、スピードと品質のトレードオフについて

質疑応答の際には、Herokuの有償サポートの「1時間や30分といった時間の中での回答を保証」というプランについて、「回答の速度と正確性のどちらを優先しているのか?」という質問が寄せられていました。これについては明確に「正確性を優先している」とのことで、回答内容に確信を持てない時などは、時間がかかる旨を伝えた上できちんと調べてから回答をしているそうです。そのように時間がかかるとお客様からの評価が下がるのでは? と心配する所ですが、実際の所は、回答の質が高ければユーザーからの評価が悪くなるという事は無いという感触があるそうです。

当社の場合、調査に非常に時間がかかるようなお問い合わせを受けた時には、まず1時間から数時間という一定時間の範囲内で判明した事実を元に、「現時点ではここまでは分かっていますが、ここから先は未調査領域のため不明です。N時間の範囲内で継続調査を実施しますか?」とその都度判断を仰ぐという運用をとっています。これは、お客様によって、あるいは同じお客様でも状況によって、回答のスピードと正確性・詳細さのどちらを優先するかが変わるためです*5

Microsoftの事例

Microsoft社の事例紹介では、リクルーティング用の資料を引用しながら、キャリアとしてのサポートエンジニア*6という点に焦点を当てて話されていました。

Microsoft社内でのサポートエンジニアの位置付け

Microsoft社内では、サポートエンジニアが属するセクションは開発やセールスなどの他のセクションとは別の、独立性の高い組織として存在しているそうです。

  • 職務上、開発部門にコンタクトをとるための正式な権限や、ソースコードのリポジトリを閲覧するための権限などが与えられている。技術の最後の砦、ゴールキーパーと言えるような位置づけである。
  • ミッションを一言で言うと「Microsoftのファンを作る仕事」。
  • 国ごとのチームの結びつきが強い他部署に比べると、サポートエンジニアは「全世界のサポートエンジニアが属する1つの集団」という性質が強い。
    • サポートエンジニア全員が書き込めるOneNoteを使用して情報共有・集積を図っている。

Microsoftのサポートエンジニアが本当にこれらの権限を持っているという事は、過去の対応事例からも分かります。過去、当社のお客様がMicrosoftとサポート契約を結んでいたことから、Windows上でFirefoxを使用した際に発生する希な現象について調査を行った事がありましたが、Firefoxのソースコードを調査した結果を踏まえた当社の回答に対し、Microsoft側もソースコードレベルでの調査結果を出してきてくれて、責任範囲が明確になったという事が実際にありました。

国際的なコミュニケーションという点では、発表中では特にアジア圏の各国にまたがったコミュニケーションについて紹介されていました。アジア各国のスタッフは、英語ネイティブでない人ばかり(基本的に第二言語として英語を使っている人ばかり)なので、音声での会話よりも文字での読み書きの方が得意という人が多いそうです。そのため、メール、チャット等、連絡事項は基本的に文章にするという文化があるとの事でした。

社内でサポートエンジニアをどう評価するか

Herokuの事例でも触れられていましたが、Microsoftでもサポートエンジニアの評価の仕組みは独特な物になっているそうです。

  • 回答の数が多いからといって優秀と評価されるわけではない。
  • 車輪の再発名は評価されない。既にある物は、極力そのまま使う事が評価される。
  • 一人で抱え込むよりも、他の人に助けを求めている方が評価が高くなるようになっている。
    • 他者を巻き込んで、他者の成功に貢献する事が評価に繋がる。
    • 「誰々さんのおかげで回答できた」「誰々さんが書いてくれたドキュメントに助けられた」といったフィードバックを行う社内ツール*7があり、そのデータが評価の指標になる。

当社の場合、会社自体の人数規模が非常に小さい事もあって、厳密なデータに基づく評価制度という物はまだありません。しかし、ある程度以上の規模になり「お互いの存在を知らない」スタッフ同士という関係があり得る状態になってくると、公正な運用のためにも数値に基づく評価制度は必要になってくるでしょう。

サポートエンジニアのキャリアに求められる物、得られる物

以上のような職務内容から、Microsoftにおけるサポートエンジニアには以下の能力が求められ、また、職務を通じてこれらの能力が高まるという事が語られていました。

  • 技術力(調査力、問題解決力)
  • コミュニケーション力(交渉力、チームワーク力)
  • 英語力

今回発表を見る事ができたいくつかの企業においても、また当社においても、これらは同様の事が言えます。テクニカルサポートの業務で行う事は、既にあるプロダクトのバグ報告や質問に対応する際に行う調査・検証の作業と、かなりの部分が共通しています。一般的に言って、バグ修正の作業の大部分は調査が占めており、調査の正否が修正の正否を決めると言っても過言ではないでしょう。また、新機能の開発などにおいてエンドユーザーや顧客の要望を的確に吸い上げるためにはコミュニケーション力が求められますし、スケジュール調整や、その機能を次のリリースに含めるかどうかなど、意見や利害の調整・交渉が必要になる場面もあります。最新情報を調査するためには、翻訳を待っていては遅すぎるため英語で元資料を参照する事が欠かせませんし、トラブルの類似事例調査についても、日本語だけで調査するよりも英語で公表されている事例も調査範囲に含められた方が、より有用な情報に辿り着きやすいです。

こうして考えると、テクニカルサポートは開発に特化したスキルセットとはまた異なる、技術とコミュニケーションの総合力が求められる業務であると言えるのではないでしょうか。

まとめ

以上、サポートエンジニアNight vol.3にて聞く事ができた他社でのテクニカルサポートの事例を、当社で提供しているテクニカルサポートの運用事例を交えつつご紹介しました。

具体的なプロダクトやサービスという成果が目に見える形で世に発表される「開発」や、売り上げが数字として出る「営業」に比べると、サービスの安定した稼働を支える「運用」や、トラブル発生時の問い合わせに対応する「サポート」という分野は、成果が可視化されにくい性質があります。職務の性質上公に語る事ができない情報も多いので、どういう人がどういう働き方をしているのかという情報もあまり表に出てこず、また、メディアに大々的に取り上げられてモデルケースになるような人物もそうそういません。学生さんなどにとっては「イメージしにくい」という印象だけでもあればまだいい方で、そもそも存在すら意識されないという事になってしまうのも致し方ないでしょう*8

実際のサポートエンジニアは、業務を遂行する上でITエンジニアとしての総合力が求められる仕事と言えます。突き抜けた開発力には自信がないが、技術もコミュニケーションもそつなくこなせる、というジェネラリスト型の人は、自分の特性を活かせる職としてサポートエンジニアにも目を向けてみてはいかがでしょうか?

*1 当社では各メンバーがサポート専任ではなく開発案件等と兼任している事が多い事と、対象製品のソースコードを深く調査するようなケースでは集中力の消費が激しい事から、実際に1日で対応可能な上限はこのくらいになる場合が多いです。

*2 このような取り決めを一般的に「Service Level Agreement(SLA)」と言います。

*3 平たく言うと、回数券のような契約形態。問い合わせ1件につきいくらという都度支払い/従量制とは異なり、あらかじめ決まった回数/時間の問い合わせが可能な権利を販売する、という形をとります。

*4 ただ、サポート契約を単独で提供している当社の場合には、この点を推し進めると「これだけ製品が安定していて問い合わせも日常的には発生しなくなったなら、もうサポート契約は不要なのでは?」とお客様が判断され、サポート契約が継続されなくなる、という可能性は出てきます。そのような状況においてのサポート契約は「いざ何かトラブルが起こったときのための保険」としての性質が強まってくるため、常日頃からどんなトラブルでもきちんと解決できるという信頼を積み重ねていく事がなおさら重要だと言えるでしょう。

*5 回答のスピードだけを気にして、未調査の事についてまで思い込みで不正確な回答をしてしまうと、その回答を信じて運用したお客様に不利益を与えてしまい、ひいては自社の信用も損ないます。また、もし知っていたら教えて欲しいという程度のつもりだった質問に対して、勝手な忖度を行って何十時間と先走った調査をして工数を消費してしまうと、それをインシデントとして消費されればお客様の不利益になりますし、かといってインシデント消費なしでの無償対応とすればやはり自社の不利益になります。最初に簡単な範囲で一次回答を返して以後の判断をゆだねるというのは、自社にとってもお客様にとっても最も不利益が発生しにくい方法ではないか、というのが当社の見解です。

*6 Microsoft社内では「Customer Service and Support」という呼び方になるそうです。

*7 残念ながら、これ自体は公開されておらず、製品としても販売されていないとのこと。

*8 そういう理由から「あこがれの職業」にもなりにくく、多くの会社でサポートエンジニアの採用や育成は難しい課題となっているようです。

2018-07-26

IBus の最新版を使って問題の切り分けを行うには

はじめに

ソフトウェアを使っていると、意図しない挙動に遭遇することがあります。 ソフトウェアによっては、ディストリビューションで配布されているものが最新とは限りません。 そのため、場合によってはアップストリームではすでに修正済みということもあります。*1 せっかく報告しても最新版ですでに直っていたりすると、悲しいですね。そんなことのないように最新版でも確認するのがおすすめです。

そこで今回は IBus を例に、不可解な挙動に遭遇したときに最新版の IBusを使っても再現するかどうか確認する方法を紹介します。

問題の詳細について

Ubuntu 18.04上のibus-mozcを使っているときに、プロパティパネルから文字種を切り替えると、文字種の変更が追従しないことがあることに気づきました。 Xfce4のパネル1に表示されるアイコンをクリックしたときに表示されるメニューから文字種を変更したときにはそのような挙動はありません。*2

Xfce4のパネル1に表示される IBus のメニューとプロパティパネルに表示されるメニューはどちらも同じ IBusProperty で実装されているので基本的には挙動に違いはないはずです。そこで IBus の最新版で挙動を確認することにしました。

IBus の最新版をビルドするには

まずは IBus が依存しているパッケージをインストールします。build-depを使うと依存関係をまるごとインストールできるので便利です。

$ sudo apt build-dep ibus

次に、 IBus が必要としているファイルをあらかじめダウンロードして配置します。*3

$ sudo mkdir -p /usr/share/unicode/ucd
$ wget https://www.unicode.org/Public/UNIDATA/NamesList.txt
$ wget https://www.unicode.org/Public/UNIDATA/Blocks.txt
$ sudo cp NamesList.txt Blocks.txt /usr/share/unicode/ucd

次に、 IBus のソースコードを入手して、./autogen.shを実行します。

$ git clone https://github.com/ibus/ibus.git
$ cd ibus
$ ./autogen.sh

最後に、make を実行した後にインストールします。

$ make
$ sudo make install

これで /usr/local 以下へと IBus の最新版がインストールされました。

最新版のIBusを動かす手順

パッケージで IBus をすでに導入している場合、古いライブラリーを見に行ってしまうことがあります。 そこであらかじめ、ライブラリーのパスを環境変数にて指定しておきます。

$ export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBLARY_PATH

これで準備ができたので最新版の IBus を試せるようになりました。

次のコマンドを実行すると、ibus-daemon を起動できます。

$ /usr/local/bin/ibus-daemon -x -r

今回追試したいのはパネル1上に表示されるメニューの挙動です。 それを有効にするには、次のコマンドを実行します。

$ /usr/local/libexec/ibus-ui-gtk3

これでパネル1に表示されるメニューの挙動を確認できるようになりました。 実際に試したところ、最新版でも同様の問題を抱えていたため、アップストリームに報告しておきました。

まとめ

今回は IBus の最新版を使って問題の切り分けを行う方法を紹介しました。

*1 細かいことをいうと、ディストリビューションが配布しているバージョンで特別にパッチを当てることで対応している場合もあります。

*2 実装が異なるibus-anthyやibus-skkにはこの問題はないようです。

*3 この作業を忘れるとautogen.shを実行したときにconfigureでエラーになります。

2018-07-25

リーダブルなコードを目指して:コードへのコメント(3)

1週間に1回ずつコメントできるといいなぁと思っていたけど3週間に1回のペースになっている須藤です。

リーダブルなコードを目指して:コードへのコメント(2)の続きです。前回はメイン関数の全体を読んでコメントしました。これに対し、その後、「シングルトンパターンはどういうときに使うのがよいだろう」というのを一緒に考えていました。

リポジトリー: https://github.com/yu-chan/Mario

今回のコメントに関するやりとりをするissue: https://github.com/yu-chan/Mario/issues/3

メインループ

それではメインループの中を読んでいきましょう。

	while(!InputInterface::isOn(KEY_INPUT_Q)) { //Qを押したら終了
		if(ProcessMessage() != 0) {
			break;
		}
		InputInterface::updateKey();
		Framerate::instance()->update();
		ClearDrawScreen();

		//ゲーム開始
		Sequence::Parent::instance()->update();

		ScreenFlip();
		Framerate::instance()->wait();
	}

短くまとまっています。このくらいの粒度で抽象化されていると、全体の流れがわかりやすくて読みやすいですね。このコードからは1回のループでは次の処理をしていることがすぐにわかります。

  • メッセージを処理(メッセージがなにかはわからないけど)
  • キー入力を処理
  • フレームレートを更新(「フレームレートを更新」というのがどういうことかわからいけど)
  • オフスクリーンの描画領域をクリアー
  • ゲーム開始(ループ毎に「ゲーム開始」というのがどういうことかわからないけど)
  • オフスクリーンの描画領域を使用
  • フレームレートを調整

処理の中身がイメージできないものもありますが、全体像はわかりました。

それでは、順に見ていきましょう。

InputInterface

以下の2つの処理が関連していそうなのでまとめて見ていきます。

	while(!InputInterface::isOn(KEY_INPUT_Q)) { //Qを押したら終了
		InputInterface::updateKey();

なぜ関連していそうかと思ったかというと同じクラスに属しているからです。関連しているものをまとめるためにクラスを使うのは読みやすくていいですね。

ただ、InputInterfaceという名前は大げさかなぁと思いました。というのは、現状、このクラスはキー入力しか扱っていません。キーだけでなく、マウスやタッチスクリーンなどいろんな入力も扱っているならInputInterfaceでもいいかもしれませんが、そうではないので、KeyboardInterfaceくらいの方がよさそうに思います。私ならInterfaceは冗長だと思うのでKeyboardにするかなぁ。

InputInterface.hは次のようになっています。

#ifndef INCLUDED_INPUTINTERFACE_H
#define INCLUDED_INPUTINTERFACE_H

class InputInterface {
public:
	static int key[];
	static bool isOn(int id);
	static void updateKey();
};

#endif

気になるのは次の2点です。

  • #ifndef INCLUDED_INPUTINTERFACE_H
  • すべてのメンバー関数が静的メンバー関数

最初の#ifndef ...からいきます。

これはヘッダーファイルを重複して読み込まないようにするための伝統的なガード方法なのですが、今どきはこれを実現するには次のようにします。

#pragma once

class InputInterface {
public:
	static int key[];
	static bool isOn(int id);
	static void updateKey();
};

#ifndef ...に指定する識別子を考えなくてもいいし、最後の#endifも書かなくていいのでかなりスッキリします。

「最近のコンパイラーでしか使えないんじゃないの。。。?互換性を考えると#ifndef ...の方がいいんじゃ。。。」と思うかもしれませんが、今どきのC/C++のコンパイラーなら全部使えるので気にしなくてよいです。Wikipediaのpragma onceのページによると使えないのはCray C and C++くらいのようです。(ドキュメントには使えると書いていない。)

次にすべてのメンバー関数が静的メンバー関数なことについてです。

すべて静的メンバー関数であればインスタンスは必要ありません。他のところではシングルトンパターンを使っているので、ここでも同じようにシングルトンパターンを使った方がよいでしょう。(私はインスタンスを作るようにする方が好みですが。)

同じことを実現するのに違うやり方を使っていると「どうしてここは違うやり方なんだろう?実は違うことを実現したいのかも?」と読んだ人が詰まってしまいます。同じプロジェクト内では、同じことを実現するときは同じやり方を使いましょう。

それでは順番に関数を見ていきましょう。

まずはInputInterface::isOn()です。boolを返す関数の名前にisをつけるのは読みやすくなっていいですね。boolを返す関数にisをつけるのはよく見る書き方なので、そういう書き方を知っている人は読みやすいです。isXXX以外にも真偽値を返す関数によく使われる名前があります。たとえばis_XXXXXX?(SchemeやRuby)やXXXp(Lisp)などです。それぞれの言語でよく使われている書き方を踏襲すると読みやすくなります。

それでは実装を見ていきましょう。

int InputInterface::key[256];
bool InputInterface::isOn(int id) {
	bool flag = false;
	//updateKey();
	if(key[id]) {
		flag = true;
	}
	return flag;
}

おそらく、InputInterface::keyにはキーが押されたら0でない値が入っているのでしょう。0以外の値が入っていたらflagtrueになるようになっています。

ここで気になることはflagという名前です。オン・オフを表しているのでフラグなのですが、一般的過ぎるので使わなくていいなら使わない方がよい名前だと私は思っています。私ならどう書くかというとこう書きます。

int InputInterface::key[256];
bool InputInterface::isOn(int id) {
	return key[id] != 0;
}

真偽値を返すならそもそもkey[id] != 0の結果を直接使えるので、ローカル変数は必要ないのです。

言語を問わず次のようなコードをちょいちょい見ます。

if(condition) {
	return true;
} else {
	return false;
}

こういうコードは次のようにしましょう。↑のようにifしてからtruefalsereturnしていると、読むときに「なにか特別なことをしているのかも?」とちゃんと確認しなければいけません。↓のように不必要なifを使わないことで「あぁ、この条件の結果を返すんだな。特別なことはしていないな。」と読むことができます。

return condition;

なお、コメントアウトして使っていない次のコードも削除しておいた方がよいです。コードをバージョン管理して必要ないコードは削除してしまいましょう。残っていると読むときに「どうしてコメントアウトしているんだろう?」と考えなければなりません。バージョン管理しておけば後から取り出すことができるので、思い切って消しましょう。

	//updateKey();

次はInputInterface::updateKey()を見てみましょう。

void InputInterface::updateKey() {
	char stateKey[256];
	GetHitKeyStateAll(stateKey);
	for(int i = 0; i < 256; i++) {
		if(stateKey[i] != 0) {
			key[i]++;
		} else {
			key[i] = 0;
		}
	}
}

DXライブラリのGetHitKeyStateAll()関数で押されているキーの情報を取得してInputInterface::keyの値を更新しています。押されていればインクリメントして押されていなければ0にしています。

stateKeykeyStatesの方がいいんじゃないかと思いました。複数形にすることで複数のキーの状態を保持していることがわかるからです。

同様にInputInterface::keykeysと複数形にした方がいいと思います。ただ、keysだとなにが入っているのか不明瞭なので、ちょっと長いですがkeyPressedCountsとかそういう方がいいんじゃないかと思います。

余談ですが、私は「何回」というのを表すときはn_XXXという名前を使っています。たとえばn_pressedです。英語の「the number of XXX」を略してn_XXXです。これはGLibで使われている名前のつけ方ですが、GLib以外でもよく見ます。ただ「何回」が複数個のときには使えないんですよねぇ。XXXの部分が名詞だと複数形になるからです。たとえば「要素数」ならn_elements(「the number of elements」)です。複数形をさらに複数形にできないので、n_elements_setとかn_elements_listとかにするんですが微妙だなぁと思っています。余計な情報が入ってしまうからです。setだと重複を許さないような気がするし、listだとリストで実現していそうな気がします。なので、keyPressedCountsかなぁと思いました。

ところで、keyの値はインクリメントする必要がないかもしれません。次のように単にboolを入れておけば十分な気がします。

key[i] = (stateKey[i] == 1);

この値を使っているところを見てみると、Game/Character.cppに次のようなコードがあるのですが、ここは押されたかどうかの情報だけでも十分なように見えます。

	if(InputInterface::key[keyIndex] == 1 && !isJump) {

あとはバッファーサイズは定数にしておきたいところです。

void InputInterface::updateKey() {
	const size_t nStates = 256;
	char stateKey[nStates];
	// ...
	for(size_t i = 0; i < nStates; i++) {
		// ...
	}
}

Visual C++では配列のサイズに変数を使えなかったような気がするので、こう書かないといけないかもしれません。

void InputInterface::updateKey() {
	char stateKey[256];
	const size_t nStates = sizeof(stateKey) / sizeof(*stateKey);
	// ...
	for(size_t i = 0; i < nStates; i++) {
		// ...
	}
}

ただ、これは少しわかりにくいのが難点です。こう書くときはだいたい次のようにマクロを使ってわかりやすくします。

#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*x))
void InputInterface::updateKey() {
	char stateKey[256];
	const size_t nStates = ARRAY_SIZE(stateKey);
	// ...
	for(size_t i = 0; i < nStates; i++) {
		// ...
	}
}

C++なので同じことをテンプレートでも実現できるのですが、テンプレートでの実装がわかりやすいかというと、うーん、という感じなので今回はやめておきます。(テンプレートを使うとARRAY_SIZEintの配列ではなくintのポインターを渡したときにエラーにできるという利点があります。)

まとめ

リーダブルコードの解説を読んで「自分が書いたコードにコメントして欲しい」という連絡があったのでコメントしています。今回はメインループ内で使っているInputInterfaceを読んでコメントしました。次回はメインループの違う処理を読んでいきます。

「リーダブルなコードはどんなコードか」を一緒に考えていきたい人はぜひ一緒にコメントして考えていきましょう。なお、コメントするときは「悪いところ探しではない」、「自分お考えを押し付けることは大事ではない」点に注意しましょう。詳細はリーダブルなコードを目指して:コードへのコメント(1)を参照してください。

つづき: 2018-08-13
2018-07-20

mozregressionを使って、いつFirefoxの機能が壊れたのかを調べる

見つけた不具合をFirefoxにフィードバックする時には、それが後退バグである場合、いつの時点から不具合が発生するようになったのかという情報を書き添えておく事が大事です。この記事では、Firefoxの後退バグの発生時期を割り出す事を支援するツールであるmozregressionの使い方を解説します。

後退バグとは?

後退バグ(regression)とは、今まで正常に動いていた機能が、別の箇所の変更の影響を受けて、意図せず壊れてしまった、というケースを言い表す言葉です。

規模の大きなソフトウェアや複雑なソフトウェアでは、気をつけていても後退バグがどうしても発生してしまいがちです。後退バグが発生した直後にすぐに気付ければいいのですが、普段あまり使わない機能だと、いつからかは分からないが気がついたらその機能がずっと壊れたままだった、という事も起こりえます。

このような場面でよく使われるのが、二分探索という手法です。履歴上の「確実に正常に動作していた時点」と「現在」との中間にあたる時点のコードを調べて、その時点でまだ後退バグが発生していなければそこから「現在」との間の中間を調べ直し、その時点でもう機能が壊れていればそこから「確実に正常に動作していた時点」との中間にあたる時点のコードを調べ直す……という要領で範囲を絞り込んでいく事で、当てずっぽうで調べたり虱潰しに調べたりするよりも遙かに効率よく後退バグの発生時点を割り出す事ができます。

Gitでバージョン管理されているプログラムであればgit bisectというコマンドを使ってそれを行えますし、Mercurialにも同様にhg bisectというコマンドがあります。ただ、Firefoxのように大規模なソフトウェアでは、二分探索でその都度ビルドするというのは現実的ではありませんし、「特定の時点のNightlyのビルド済みバイナリをダウンロードしてきて展開して起動して……」という事を繰り返すのも大変です。そこで登場するのがmozregressionなのです。

mozregression-guiの使い方

Quick Startのページに英語音声の動画での解説がありますが、ここではアドオンのサイドバーパネル上のツールチップの表示がおかしくなる不具合を例に、後退バグの発生時点を割り出してみる事にします*1

mozregressionの配布ページから辿ってGUI版のWindows用ビルドをダウンロードすると、「mozregression-gui.exe」というファイルを入手できます。これはインストーラで、ダブルクリックして実行すると自動的にC:\Program Files (x86)\mozregression-guiへインストールされます。起動用のショートカットは自動的には作成されないため、インストール先フォルダを開いて「mozregression-gui.exe」のショートカットをデスクトップなどに作成しておくと良いでしょう。

二分探索の開始

mozregression-guiを起動すると、3ペインのウィンドウが開かれます。

mozregression-guiのメイン画面

メニューバーの「File」をクリックし、「Run a new bisection(新しく二分探索を始める)」を選択すると、「Bisection wizard」というタイトルのウィザードが開かれて、どのような内容で二分探索を始めるかの設定が始まります。

基本設定の画面

今回は以下のように設定しました。

  • Application(アプリケーションの種類): firefox(この他に「fennec(Android版Firefox)」「Thunderbird」も選択できます)
  • Bits(バイナリの種別): 64(Windows版のバイナリは現在は64bit版が主流なので。32bit版特有の不具合であれば「32」を選択します)
  • Build Type(ビルドの種別):opt(この他に「debug」(詳細なデバッグログを出力できるビルド)と「pgo」(最適化ビルド)も選択できます)
  • Repository(リポジトリ):mozilla-central(この他に「mozilla-beta」などのブランチや「comm-release」などのThunderbird用リポジトリも選択できます)

「Next」ボタンをクリックすると、テスト時のFirefoxの状態を設定する画面になります。

プロファイル設定の画面

特定のプロファイルで起動したときだけ後退バグが再現するという場合、再現に必要なプロファイルのパスを「Profile」欄に入力します。「Profile persistence(プロファイルの永続性)」は初期状態では「clone」になっており、テスト実行のたびに元のプロファイルを複製した物を使い捨てする事になります。「reuse」を選択すると指定したプロファイルをそのまま使う事になります。「clone-first」は両者の中間で、最初のテスト実行時に元のプロファイルを複製した後、以後のテストではそれを再使用します。ただ、新規のプロファイルでも現象を再現できる場合は、「Profile」欄は空にしておくことをお勧めします。

「Custom preferences」には、テスト実行時にあらかじめ設定しておく設定値を記述します。Bug 1474784の当初の報告内容は「extensions.webextensions.remotefalseの時に再現する」という物ですので、そのように設定する事にします。「Add preference」をクリックすると行が追加されますので、「name」欄にextensions.webextensions.remote、「value」欄には"false"と引用符でくくらずにそのままfalseと入力しておきます。

「Custom addons」には、テスト実行時にあらかじめインストールしておくアドオンを登録します。Bug 1474784の当初の報告内容はツリー型タブを使用したときという説明になっているため、アドオンの配布ページの「Firefoxへ追加」ボタンを右クリックしてリンク先のファイル(アドオンのインストールパッケージ)をダウンロードし、mozregression-guiのウィザードの「Add addon」ボタンをクリックしてファイルを選択しておきます。

さらに「Next」ボタンをクリックすると、二分探索を行う範囲を指定する画面になります。

二分探索の範囲の設定

初期状態では、起動した日とその1年前の日の範囲で二分探索を行うように入力されています。「Last known good build」欄には最後に正常に動いていたと確認できているビルドの日付を、「First known bad build」欄には最初に異常に気づいたビルドの日付を入力します。この探索範囲は、狭ければそれだけ効率よく絞り込みを行えます。Bug 1474784は7月の1週目までは起こっていない問題だったので、ここでは2018年7月6日から2018年7月11日までを範囲として入力しました。

探索の範囲を入力したら、準備は完了です。「Finish」ボタンをクリックするとウィザードが終了し、二分探索が始まります。

二分探索が始まると、検証用として先ほど指定した範囲の日付の中からいずれかの日のNightlyビルドがダウンロードされ、新規プロファイルで起動されます。この時には、ウィザードで設定した設定やアドオンが反映された状態になっていますので、後退バグが発生しているかどうかを実際に確かめてみることができます。

二分探索が始まり、後退バグの発生を確認した状態

この例では、このビルドでは後退バグが発生している事を確認できています(スクリーンショット内のNightlyのウィンドウにおいて、サイドバー部分のツールチップが適切にスタイル付けされていない事が見て取れます)。検証用のFirefoxを終了した後、mozregression-guiのメインウィンドウ左上の領域にある「Testing (ブランチ名) build: (日付)」の項目の「good」「bad」の二つのボタンのうち「bad」の方をクリックしましょう。すると再び別のビルドのダウンロードが始まり、ダウンロードが完了するとまたそのビルドが新規プロファイルで起動します。

二分探索中に、後退バグが発生していないビルドに遭遇した状態

最初のビルドに続き2番目のビルドも後退バグが発生していましたが、3番目のビルドでは後退バグは発生していませんでした(スクリーンショット内のNightlyのウィンドウにおいて、サイドバー部分のツールチップが適切にスタイル付けされている事が見て取れます)。このような場合は、mozregression-guiのメインウィンドウ左上の項目の「good」「bad」の二つのボタンのうち「good」の方をクリックしましょう。

このようにして「good」と「bad」を振り分けていくと、やがて、次のビルドが起動されない状態になります。

二分探索が終了した状態

この状態になると、二分探索は終了ということになります。mozregression-guiのメインウィンドウの左上の領域に表示される項目のうち最も下にある緑色の行が「最後の正常ビルド(last good build)」、最も下にある赤色の行が「最初の異常ビルド(first bad build)」を表しており、行をクリックすると右上の領域にそのビルドの詳細が表示されます。この例では、「最後の正常ビルド」は以下の通りでした。

app_name: firefox
build_date: 2018-07-09 14:02:55.353000
build_file: C:\Users\clearcode\.mozilla\mozregression\persist\140937d55bd0--mozilla-inbound--target.zip
build_type: inbound
build_url: https://queue.taskcluster.net/v1/task/eyRSVJsJT4WGMysouGUC_w/runs/0/artifacts/public%2Fbuild%2Ftarget.zip
changeset: 140937d55bd0babaaaebabd11e171d2682a8ae01
pushlog_url: https://hg.mozilla.org/integration/mozilla-inbound/pushloghtml?fromchange=140937d55bd0babaaaebabd11e171d2682a8ae01&tochange=e711420b85f70b765c7c69c80a478250bc886229
repo_name: mozilla-inbound
repo_url: https://hg.mozilla.org/integration/mozilla-inbound
task_id: eyRSVJsJT4WGMysouGUC_w

また、「最初の異常ビルド」は以下の通りでした。

app_name: firefox
build_date: 2018-07-09
build_file: C:\Users\clearcode\.mozilla\mozregression\persist\2018-07-09--mozilla-central--firefox-63.0a1.en-US.win64.zip
build_type: nightly
build_url: https://archive.mozilla.org/pub/firefox/nightly/2018/07/2018-07-09-22-12-47-mozilla-central/firefox-63.0a1.en-US.win64.zip
changeset: 19edc7c22303a37b7b5fea326171288eba17d788
pushlog_url: https://hg.mozilla.org/mozilla-central/pushloghtml?fromchange=ffb7b5015fc331bdc4c5e6ab52b9de669faa8864&tochange=19edc7c22303a37b7b5fea326171288eba17d788
repo_name: mozilla-central
repo_url: https://hg.mozilla.org/mozilla-central

これらの情報をbugの報告に書き添えておくと、実際に修正を行おうとする人の調査の手間が大幅に軽減されます。

なお、最初の異常ビルドの「pushlog_url」欄に現れているURLを開くと、前のビルドからそのビルドまでの間に行われた変更の一覧が現れます。この例では40以上のコミットが一度にマージされた時点から後退バグが発生したという事が読み取れ、後はこの中のどの変更が原因だったかを割り出すという事になります。運がよければ、最初の異常ビルドでの変更が1コミットだけに絞り込める場合もあり、その場合は調査範囲が一気に限定されます。

まとめ

以上、mozregressionを使ってFirefoxの後退バグの発生時点を割り出す手順をご紹介しました。

後退バグの修正にあたっては、いつの時点から機能が壊れていたのか、どの変更の影響で機能が壊れたのか、という事を特定する事が重要です。どの変更でおかしくなったのかが分かれば、原因箇所を特定する大きな手がかりになります。また、新たな別の後退バグを生み出さないためには、後退バグの発生原因となった変更の意図を踏まえつつ対応策を検討する事が有効です。後退バグを報告する場合は、できる限り「どの変更から問題が発生するようになったか」を調べてから報告することが望ましいです。

ただ、このように二分探索で後退バグの発生時点を割り出すためには、各コミット段階で「問題が発生するかどうかを確実に確認できる事」が必須条件となります。ミスが原因で「そもそも起動すらしない」状態のコミットが途中に存在していると、動作を確認できるのは「正常に起動するコミット」の間だけになってしまい、二分探索を有効に行えません。GitHub Flowのようにmasterを常にいつでもリリースできる状態に保ったり、プルリクエストのマージには必ずCIが通る事を条件にしたりといった運用をとる事もセットで行う必要があります。後退バグが発生しても原因をすぐに特定しやすいよう、健全なプロジェクト運営を心がけたいものですね。

*1 このBug自体は実際には既に報告済みの他のBugと同じ原因であったことが分かったため、既にあった方のBugで続きをトラッキングするよう誘導されて閉じられています。

2018-07-18

2008|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|
タグ: