読者です 読者をやめる 読者になる 読者になる

Connehito開発者ブログ

Connehito Engineer's Blog

「JQL」は、mixpanelの抱える1つの限界を打ち砕く夢である

こんにちは、PHPなどを書いている金城 (@o0h_) です。ここ暫くはファミリーマートのリーフレタスと大根のサラダセブン-イレブンの15品目のサラダボウル(和風ドレ)が私の食生活においてヘビロテしています。*1

f:id:o0h:20150214152429j:plain

話は変わるのですが、コネヒトでは、ログの収集+データの分析用途のツールの1つとしてmixpanelを利用しています。

*1:[ちょっと宣伝]コンビニサラダについて一家言ある方を、職種とわず募集しています。 ウォンテッドリーからどうぞ!

続きを読む

小規模PHPアプリケーションをDeployerでサッとデプロイする話

f:id:fortkle:20160728164803g:plain

こんにちは! 東京なのに周辺にポケストップが1つもない田舎に住んでいます、@fortkle です(世田谷公園の徒歩圏内に引っ越したいです)。

さて、みなさんはPHPアプリケーションのデプロイツールに何を使っているでしょうか。
今回コネヒトで Deployer というPHPで書かれたデプロイツールを採用したのですが、なぜDeployerなのかという理由と、実際に使っているおすすめの設定をいくつか共有したいと思います。
※ Deployerのインストール方法や基本的な使い方を知りたい場合は既にインターネット上に良い記事がたくさんあるのでそちらを参考にしてみてください。

続きを読む

新人デザイナーさんのディレクションから学んだコミュニケーションの心得

Design

f:id:connehito:20160711094503p:plain

こんにちは、デザイナーのきよえし(@kiyoe_furuichi)です。

先日、ついにコネヒトデザインチームが結成されまして、チーフデザイナーを担当することになりました! 手探りながらチーフとしての役割を担っていて一番感じるのは、自分でつくるより何倍もつくってもらうほうが難しいということです。日々多くの発見や学びがあるなあと勉強させていただいてます。

今回は、私が新人デザイナーさんをディレクションするなかで、より良いコミュニケーションをとるために実践している"心得"をいくつかご紹介したいと思います。

制作の流れ

まず、デザインを制作する流れとして私たちは以下のように制作を行っています。

  1. タスク依頼
  2. スケジュール調整〜制作
  3. フィードバック
  4. リリース・振り返り

私が担っているものは具体的に、タスクの依頼を受けて担当デザイナーを決めることと、スケジュール管理・フィードバックを行いながら完成までフォローをすることです。 最終的なチェックはディレクターさんが行うので、なるべく手戻りが無いようクオリティの担保を行っています。
そんな役割から学んだコミュニケーションの”心得”を、それぞれのフェーズごとにまとめていきたいと思います。

1. タスク依頼 の心得

依頼するタスクはユーザーさんにとってどのようなインパクトがあるのかを伝えること

依頼するにあたって一番気をつけないといけないなと思うのは、単に"タスクをこなす"という意識を持たせないようにするということです。このタスクはなぜ必要なのか、どんなユーザーさんにどんなインパクトを与えられるのかといったところまでを想像してデザインに落とし込めるように、背景から内容までしっかり伝えるようにしています。
そのために行っていることは、依頼を文字に落として伝えるようにしています。口頭では表現でのズレが起こりやすいのでなるべく文字化して伝えるほうが良いと思っています。
最初は文字に落とすことに手間がかかるし省いたほうが良いと思っていましたが、これによって制作途中の大きな手戻りが少なくなりました。担当者がしっかりタスクの内容から想像して意図を持ったデザインが行えているためです。

2. スケジュール調整〜制作 の心得

自分の見積もり工数 + バッファをとった工数でスケジュールを立てること

スケジュールは担当デザイナーさんと一緒に立てていきます。どのくらいで制作できるかを見積もってもらい、それにバッファを足した工数でスケジュールの締め日を決めます。バッファを取るのは、万が一手戻りがあった場合の修正で締め日に間に合わなくなることを防ぐためです。
私自身、自分の工数の見積りは慣れないうちはとても難しくズレが起こることが多々ありました。はじめのうちはバッファを多めに取って、見立てが合うようになるまでの補助として持っておくのが良いと思います。

締め日を決めて、逆算して細かくスケジュールを切ること

締め日が決まったら、さらに逆算してスケジュールを立てていきます。
例えばwebサイトにバナーを掲載するタスクなら、バナーのラフ案はいつまでに上げるか、バナーの制作の期限、バナーをサイトに掲載する実装や実装したコードをレビュワーに確認を取る期限はいつまでか、などです。
細かく決めておくことによってスケジュールの巻きや遅れがより明確になり、締め日までのズレが少なくなります。スケジュールの流れを意識して着手してもらうように、切ったスケジュールはカレンダーで管理をしてリマインドを行ってあげるなどでフォローします。

3. フィードバックの心得

制作物に対してのフィードバックをする際の心得です。

まず、肯定すること

タスクの意図を想像して制作していただいた制作物をもっとより良くするためのフィードバックとなるように、まずは肯定するようにします。
フィードバックは"修正"ではなく"改善"であると考えていて、これは私の経験が大きいのですが指摘をいくつも言われる前にまず「基本良いと思います。」の言葉があるだけで、ポジティブな気持ちでフィードバックを受けることができます。マイナス点の指摘ではなく、その制作物をもっとより良くするためのフィードバックをすることがデザイナーさんとのより良いコミュニケーションにはすごく大切だと思っています。

f:id:connehito:20160710181153p:plain

なるべく文字化すること

フィードバックにおいても、タスクを依頼する際と同じようになるべく制作物のフィードバック点は文字に起こすようにします。あとから読み返せるという点と、口頭で伝える時の言葉のニュアンスによってズレがないようにするためです。(もちろん口頭でも補足は行います)
また、担当デザイナーさんに意図を説明していただくときも文字化してもらい、制作物のロジックを確認し、しっかりユーザーファーストに考えて制作できているかを見るようにしています。
文字化にすることが良いかどうかはフィードバックの内容や場合によって変わりますが、基本的にはフィードバックの回数が減ったり、同じようなやりとりを何度もすることがなくなりました。

答えは言わず、考えてもらうようにすること

フィードバック点は"こうすればいいよ"という答えではなく、答えまでの道筋を伝えるようにします。
初めのうちはもどかしいこともありますが、思考してもらうことが次の学びに繋がると思うのでなるべくそうするようにしています。例えばカラーリングのフィードバックの場合、その配色・トーンから感じる印象はそのクリエイティブの意図したい印象と合っている?どういった配色にするともっとユーザーさんに意図が伝わるか?といったようなフィードバックを行っています。

4. リリース・振り返り の心得

労いの言葉は必ずかけること

完成やリリースをしたら全力で”おつかれさま!”の言葉を伝えます。これは社内全体でそういった文化があり、リリースをした際に周知するSlackのチャンネルにて、みんなでその方の頑張りを労います。
f:id:connehito:20160711103012p:plain

見積もり工数と実工数との差分は必ず振り返ること

見積もってもらったスケジュールの反省は必ず行います。初めのうちはスケジュールの見積もりは上手く行くことが少ないです。次に生かしていくために、見積もっていた工数と実工数の比較とどこに時間がかかってしまったのかを振り返り、見積もりの精度を上げていきます。
ここでなるべく細かく振り返りができるように、都度感じたことを都度メモを取っておくようにしています。

メモ
【◯◯特集記事のバナー作成】
・制作のスケジュールから +0.5 取っての完成
・写真の選定で +0.2〜3 ほど時間を取っていた
・配色のフィードバックが多かった、修正で +0.3 ほど時間を取った

[改善するには]
・写真選定の時間を短縮したい -> 写真素材の探し方のアドバイスをする
・セレクトしてもらった写真がバナーに使いづらそう -> 写真の選び方のアドバイスをする
・フォントの選定・文字組みに苦手意識がありそう -> 参考になりそうなバナーのインプット時間を多めに取ってもらう


まとめ

いかがでしたでしょうか。以下にご紹介した心得8か条をまとめました!

1. タスク依頼
・依頼するタスクはユーザーさんにとってどのようなインパクトがあるのかを伝えること

2. スケジュール調整〜制作 の心得
・自分の見積もり工数 + バッファをとった工数でスケジュールを立てること
・締め日を決めて、逆算して細かくスケジュールを切ること

3. フィードバックの心得
・まず、肯定すること
・なるべく文字化すること
・答えは言わず、考えてもらうようにすること

4. リリース・振り返り
・労いの言葉は必ずかけること
・見積もり工数と実工数との差分は必ず振り返ること

新人デザイナーさんとのコミュニケーションは、なるべく手厚めにフォローをする・気づきから成長してもらうための工夫を入れたりしています。タスクやスケジュール感によってはできる・できない・やらないことはあると思いますが、これらの"心得"を持っておくことによってデザインチームとして、お互いに気持ち良く制作ができると思います。

私はまだディレクションを任されて3ヶ月目ですが、冒頭でも書いたように本当に"自分でつくるよりも何倍もつくってもらうほうが難しい"ということを実感しました。
この記事をまとめている際に過去を振り返っていて、こうしてまとめている心得はすべて私がこれまで実際にディレクターさんや先輩エンジニアさんからしていただいて嬉しかったコミュニケーションを参考にしていることに気がつきました。私も、嬉しいと思っていただけるようなディレクションができるようになるのが目標です。

まだまだ完璧にこなすことは難しいですが、より良いコミュニケーションがより良いプロダクトをつくると思っているので、これからも頑張ります!

Flux Utilsで始めるReact + Fluxアプリケーション開発

javascript react flux

f:id:dachi023:20160628181620p:plain

こんにちは。エンジニアの安達(@ry0_adachi)です。

今回はReact + FluxをFlux Utilsを使って導入するための話をしたいと思います。

この記事を書こうと思った理由

普段ちょっとしたツールなんかをReactを使って実装したりするのですが、その時にReduxとかでやっているとファイル増えたりしてすごく冗長だなあと感じて、もっと薄く実装できるライブラリを使おうと思った時に手に取ったのがFlux Utilsでした。

この記事ではFlux UtilsにおけるFluxアプリケーションの実装方法に加えて、そもそもReactやFluxって今までのライブラリやフレームワークと比べて何がいいのか?について説明していきます。初歩的な書き方やjsxだったりについては話さないのでドキュメントなどを読んでいただければと思います。

ドキュメント

ReactとFluxを導入するにあたって

まず、React + Fluxの構成で今まで他のライブラリやフレームワークを使っていて感じたフロントエンドにおける課題がそもそも解決できるのかを先に考えてみます。

Reactとコンポーネント指向

ReactはFacebookが開発しているViewライブラリで、UI(画面の要素: ヘッダ, メニュー etc...)ごとにViewを分割し、複数のViewによって1つの画面を構成します。これによってUIの管理や実装が小さい単位で行えるので複雑になりにくいです。

jQueryなどで実装していると画面内のUIが大量に詰め込まれたファイルが出来上がることがありましたが、ReactではUIごとに分割するアプローチをとることでUIの実装が膨らんできても各ファイルの中身はシンプルでコンポーネントの管理が非常に楽です。

コンポーネント間での状態管理

ReactによってUIの管理は楽になりますが、アプリケーションで扱う状態の管理をどうするか?という問題があります。ReactはViewの開発をよりよくするためのアプローチなので別の手段が必要です。UIに関係しないコード(APIとの通信, データの登録, 更新, 削除)が紛れてしまえばシンプルに実装されたViewも結局は複雑で管理しにくいコードになってしまいます。

そこで、上記の問題を解決するためにFluxを利用します。

Fluxの状態管理と責務

f:id:dachi023:20160627111929p:plain

FluxはFacebookによって考案されたアーキテクチャです。Fluxの考え方に「状態の流れは常に一方向」というものがあり、これは「UI操作に伴う複雑な状態の遷移を分かりやすくするもの」だと私は思っています。

状態の流れを意識しないで実装していると、どこで状態が変化したかが追いづらいコードになります。ユーザの入力によって複数のイベントを発火させたりすることもあり、それを管理し把握できていないことで複雑な実装になりがちな上に意図していない挙動を引き起こすことも多いです。

Fluxではデータに関する責務を下記のように分割します。

  • Action (Action Creator)
  • Dispatcher
  • Store
  • View

これによって状態管理をReactが担当するViewの外に出し、Viewは外部からデータを受け取りUIを表示する、といったシンプルな実装を実現することができます。

また、状態が流れる方向は上記で記述した順番で固定されており、Fluxのルール通りに実装していれば逆流させることはありません。

Flux Utilsで実装する

Fluxと同じリポジトリに入っているFluxを実装するための最低限の機能を備えたライブラリです。Flux Utilsがカバーする範囲はStoreとViewのみですが、これまた同じリポジトリにDispatcherの実装が入っているのでこれも使います。

今回は入力した文字列をボタンをクリックしたら保存して入力された文字列全てを表示するというアプリを作ってみます。

Action (Action Creator)

Actionがやることは主に下記の3つになると思います。

  • 外部(API)との通信
  • データの加工
  • Dispatcherにtypeと値を渡す

Viewからの要求を処理してその結果をStoreに渡すのがActionの役割です。外部との通信は全てここで行い、Store内で更新のためのロジックが複雑にならないようにデータの加工とかが必要なのであればActionで加工してしまった方がStoreがスッキリするので良いと思います。データをStoreに渡す準備ができたらDispatcherに対してdispatchします。

import { dispatch } from '../dispatcher/Dispatcher'

const Actions = {
  changeText: (text) => {
    dispatch({
      type: 'change_text',
      text: text
    })
  }
}

export default Actions

dispatchする時に入力値などの他にtypeを必ず指定します。Dispatcherの先にいるStoreはtypeを見てどのコールバックを実行するかを決めるのでtypeがなければ適切に処理することができなくなります。

Dispatcher

DispatcherはActionsからdispatchされ、自身に登録されているコールバックに情報を渡す、という役割があります。また、複数のコールバックを実行する際にはその順番の制御を行うためのインタフェースも提供します。

import { Dispatcher } from 'flux'

const instance = new Dispatcher()
export default instance

export const dispatch = instance.dispatch.bind(instance);

今はDispatcherの基本実装だけで事足りるのでインスタンスをそのまま作って準備OKです。ただ、Actionから呼び出す時に毎回Dispatcher.dispatch()という書き方だと長いのでdispatchだけで呼べるように別途exportしておきます。

Store

Storeは状態(State)を管理するために下記のような役割を持っています。

  • Viewがアクセスするためのgetterの提供
  • Dispatcherに対するコールバックの登録
  • Stateの管理と生成

StoreはStateの更新を行います。更新はDispatcherに登録したコールバックからのみ実行します。そうすることで状態の流れる方向が保たれます。

今回はFlux Utilsの提供する3つのStoreの中の1つであるReduceStoreを使ったサンプルを実装します。

import { ReduceStore } from 'flux/utils'
import Dispatcher from '../dispatcher/Dispatcher'

class TextStore extends ReduceStore {
  getInitialState() {
    return []
  }

  reduce(state, action) {
    switch (action.type) {
      case 'change_text':
        state.push({ text: action.text })
        return state.slice(0)

      default:
        return state
    }
  }
}

const instance = new TextStore(Dispatcher)
export default instance

Flux Utilsではもともと保持していたStateと比較して別オブジェクトであれば再描画を行う、という仕組みのため渡ってきたstateをそのまま使うと差分がないとみなされて再描画が実行されません。なので今回はArray.sliceでやってますが、Immutable.jsなどを使ってStateの生成を行うのが良いと思います(毎回sliceとかconcatとか書きたくないですし...)。

View

Flux UtilsではContainerというReact Componentのラッパーがあります。ContainerとReact Componentをそれぞれ説明していきます。

Container

ContainerはStoreからStateを受け取り、そのStateが更新されていれば自身の配下にいるComponentに変更を通知し、再描画が実行されます。

import { Container } from 'flux/utils';
import React from 'react'
import TextStore from '../../store/TextStore'
import TextInput from '../../components/TextInput'
import TextList from '../../components/TextList'

class TextApp extends React.Component {
  static getStores() {
    return [TextStore]
  }

  static calculateState() {
    return { items: TextStore.getState() }
  }

  render() {
    return (
      <div>
        <TextInput save={this.save} />
        <TextList items={this.state.items} />
      </div>
    )
  }
}

const TextAppContainer = Container.create(TextApp)
export default TextAppContainer

TextStoreの管理するStateが変更された時にTextContainerはTextInputとTextListに変更を通知し、再描画させます。React単体だとStateにsetしたりと色々と管理することが多くViewの中が汚れてしまったりすることもありますがContainerから伝搬されてくるもの、という状態を守っていれば管理は楽になるはずです。

React Component

ComponentはContainerから受け取ったStateからUIの表示を行います。また、各イベントとactionを紐づけることでFluxのサイクルで状態が更新されます。これによって外部から与えられるStateについての管理や親にコールバックを戻すといった複雑な処理が減り、ComponentはよりシンプルにUIに集中することができます。

// TextInput

import React from 'react'
import Actions from '../../actions/Actions'

class TextInput extends React.Component {
  constructor(props) {
    super(props)
    this.state = { text: '' }
  }

  handleChange(e) {
    this.setState({ text: e.target.value })
  }

  handleClick(e) {
    e.preventDefault()
    this.setState({ text: '' })
    Actions.changeText(this.state.text)
  }

  render() {
    return (
      <div>
        <input
          type="text"
          value={this.state.text}
          onChange={this.handleChange.bind(this)}
        />
        <button onClick={this.handleClick.bind(this)}>
          Save
        </button>
      </div>
    )
  }
}

export default TextInput
// TextList

import React from 'react'

class TextList extends React.Component {
  render() {
    const items = this.props.items.map((item, index) => {
      return <p key={index}>{item.text}</p>
    })
    return (
      <div>{items}</div>
    )
  }
}

export default TextList

サンプルリポジトリ

コード全体を確認したり、実際に動かしたりするためにwebpack-dev-serverを組み込んだリポジトリを用意しました。こちらのコードも合わせて読んでみてください。

dachi023/flux-utils-example

まとめ

Reduxなどを使って実装するとどうしても最初のコード量やファイル数が増えとっつきにくい(そもそもどの位実装したらこのコストをペイできるのか、という疑問もあり...)、という方でもFlux Utilsなら実装がしやすいと思います。また、ちょっとしたアプリケーションにも向いていると思います。

ただ、Flux Utilsは最低限のFlux実装を行うものであり大規模であったり複雑な実装を求められる場合に別の手段を用いる必要もあるんじゃないかな、という不安もあります。

Connehitoとしてもこれから使っていくぞ!というところなのでまだまだ実務での知見は多くないです。これから実装を進めていく中で壁にぶつかったり何かいい手段を見つけたらこちらでまた共有しようと思います。

自分たちがユーザーでないのにどうやってママ向けプロダクトを創っているのか?に対する3つの施策とその回答

f:id:tatsushim:20160613192719j:plain

こんにちは!

最近新しいTシャツを着ると必ずメンバーに突っ込まれます。CTOの島田(@tatsushim)です。
今日はよく面接で質問をいただく「自分たちがユーザーでないのにどうやってママ向けプロダクトを創っているのか?」という疑問にお答えしたいと思います。

データだけでは生まれない「改善案」

私を含む開発チームのメンバーは全員「ママ」ではありません。 そこでデータドリブンで開発することにより、定量的に機能の良し悪しを判断することで改善のサイクルを回しています。 ( 独身男性のためのデータドリブン講座 // Speaker Deck )
しかし、データから異常値を発見しても、それが実際何を意味するのかが理解できないとプロダクトの改善はできません。例えば
「恐らくユーザーさんはお子さんを一旦寝かしつけて、落ち着いたところで僕らのサービスを使ってくれたんだな。だから22時頃がピークタイムになるんだ」
と想像をした上で、改善案を練る必要があります。
そこで、具体的な改善案を出したり、エンジニア自身が想像力を身につけるために会社で行っている3つの施策についてご紹介したいと思います。

プロダクトの改善案を生み出す3つの施策

続きを読む

Google I/O 2016で発表されたFirebase Analyticsを使ってみた

f:id:Utmrer:20160519231110j:plain

こんにちは、最近AbemaTVでフリースタイルダンジョンを深夜に見る習慣が出来て寝不足気味の田村(@Utmrer)です。

みなさんはアプリのトラッキングにどのAnalytics Toolを使っていますか?Google AnalyticsやFlurry, Facebook Analyticsなどいろいろありますが、弊社では主にMixpanelを使ってトラッキングしています。
Mixpanelは一定のデータ量までは無料で使う事ができるので最初は良かったのですが、サービスが成長してくると結構な金額になってきてます。
そこで日々良いツールは無いかと探していたのですが、Google I/Oで発表されたFirebase Analyticsが無料という話を聞いたので試しに使ってみることにしました。

簡単なトラッキング

プロジェクトの作成やアプリの追加は公式のドキュメントを参考にしてください。Console画面はほとんど日本語化されているのでとてもわかりやすいです。

FirebaseはCocoaPodsに対応しているのでpod 'Firebase'を追加してpod installするだけでインストール出来ます。
それではユーザーの会員登録イベントをトラッキングしてみます。

// AppDelegate
override func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
    FIRApp.configure()
}
// RegisterViewController
func register(sender: AnyObject) {
    FIRAnalytics.logEventWithName(kFIREventSignUp, parameters: nil)
}

AppDelegateでAnalyticsの設定を行って、ViewControllerで登録イベントを発火させただけのシンプルなイベントトラッキングです。
FIRAnalytics.logEventWithNameの最初の引数がイベント名で2番目の引数がそのイベントのパラメータになります。
kFIREventSignUpというのはFirebase SDKが定数として持っているイベント名で「こういうのトラックするといいんじゃない?」という声が聞こえてきそうです。 他にもkFIREventTutorialBeginkFIREventUnlockAchievementなど重要そうなイベント名の定数が設けられています。

もちろんイベント名は定数だけでなく、オリジナルなイベント名も入れられます。

func watch(sender: AnyObject) {
    FIRAnalytics.logEventWithName("WatchAnimation", parameters: ["Series": "LoveLive"])
}

これで「ラブライブを観た」というイベントがトラック出来ました。

ユーザー情報の設定

Google AnalyticsのようにユーザーIDやパラメータが設定できます。ユーザーIDを設定した時のAnalyticsでの挙動みたいな文言は見つからなかったのですが、ユニバーサル アナリティクスのようにUUの精度が上がったりするのでしょうか。

// AppDelegate
override func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
    FIRApp.configure()
    FIRAnalytics.setUserPropertyString("のぞみ", forName: "name")
    FIRAnalytics.setUserID("9")
}

AppDelegateのハンドリング

FirebaseにはAppDelegateに関するトラッキングが3つ用意されています

  • + handleEventsForBackgroundURLSession:completionHandler:
  • + handleOpenURL:
  • + handleUserActivity:

これらは自動で行われるので実装する必要はなく、手動でやりたい場合は設定ファイルに書いて自分で実装するようです。
これらのメソッドが何をやるのか詳しくは書いてないですが、Universal LinkやAppIndexing, Spotlightなどのアプリ起動のReferrerに関するトラッキングをしてくれそうな気がします。

使ってみて

このライブラリで開発者がやることはFIRAnalytics.logEventWithNameぐらいです。かなりクラス数やAPI数が少なく、すぐ使えると思います。
逆に言うと、Firebase Analyticsだけではあまり機能が無い、とも言えます。Console画面もMixpanelに比べると

  • ユーザー継続率のコホートしかなく、カスタムイベントのRetentionが見れない
  • Formulasがない(Register/Launchで新規率をグラフにしたりする機能)
  • iOSとAndroidの数値を合算して表示したりできない(Google Analyticsの様に複数のOSを1つのプロパティで管理できない)

などがデメリットでMixpanelからスイッチすることは難しいな、という感じでした。
一方で、Firebase AnalyticsのログはBigQueryに吐けるらしいのでお手製で解析ツールを作れるのなら自由度は上がると思います。
また、NotificationsやRealtime Database, Crash Reportingなど他のFirebaseサービスと連携するならロックインされた方がメリットが大きかもしれませんね。
Google AnalyticsのSDKより断然使いやすいので1度試してみてはどうでしょう!

Slackにハッシュタグ的な「ゆるく情報をまとめる方法」が欲しかった話

Slackの話をします

ご無沙汰しております。
コネヒトでPHPを書いている金城 / @o0h_です。

f:id:connehito:20160517000227p:plain:w290

突然ですが、皆様Slack大好きですか。
起床すると真っ先にSlackの赤い丸を消しに行く生活を送っていますか。
コネヒトではSlack大好き従業員・役員が多く、この中で日々色々なやりとりが繰り広げられています。

そんな風にSlackを使っていると、「もっとコレしたい」「アレできないの」が溢れてきます。
先日の島田の記事でも、その一例を紹介いたしました。

Slackの情報が「まとめにくい」問題

非常にフロー型の情報・交流に適したツールだな、と思うわけです。
パッと言える。スッと目に入る。
そうすると、「その場で思いついたアイディア」などを刹那的に投げつけていきたくなります。
・・・が、その速度感をもった利便性ゆえに「後から探しにくい」「どこいったっけ!」といった状況を招きやすく、私は良く悩まされました。

今回は、このフロー/ストック問題に対する1つのアプローチを紹介します。
ハッシュタグ(的な)機能 です。

f:id:connehito:20160516222937g:plain ↑「発言にサインをつけると、1箇所にまとめられる〜」というものを実装したのです

なにをやったの

(TwitterやFacebook、Instagramのような) 「通常のフィードに投稿しつつ、串刺し的に情報をつなげられる」という世界観を実現したいと思いました。*1
エッセンスを自分なりに整理してみて、欲しいのは「特定のサインで発火して」「分類項目ごとに手軽に一覧できる」ものです。

利用方法が簡便であることが必須

前者に関しては、

  1. シンプルであること
  2. 直感的であること(奇抜でないもの、どこかで見たことがあるもの)
  3. 当然ながら、Slackの標準の記法とぶつからないこと

という要件を満たす必要がありました。
例えば、

  • 1に対して言うと「カスタムコマンド(/command)」とすれば発言のパースこそ簡単になりますが、普通の発言操作とは異質すぎてシンプルさに欠けるように思います。
  • 2に対して言うと、エンジニア的には >> とかすると「出力先を変更する」という事で分かりやすいですが、普段Linuxを使っていない人からすれば「見慣れない」ものに思います。
  • 3に対して言うと、 #hashtag が出来ると良いのですが・・・これは既存のチャンネル指定の記法とぶつかるため、扱えません。

諸々を鑑みて、 #_hashtag というサインを決めました。ハッシュ+アンダースコア+キーワード。

アウトプットをどうするか

「手軽に一覧できる」というのをどう実現するか、というのが今回の1番の壁だったように感じます。
が、結論からいうと「チャンネルを柔軟に作成し、そこに発言を放り込んでいく」という手法をとりました。
これに関しては、先駆者がいらっしゃって非常に参考にさせていただいたのです!

Slackにスレッド機能を追加する - Qiita
この記事と巡り会えたのが最大のブレイクスルーだったし、おかげで完全に道が開けた・・・と思っています。

実際の動き

改めて先に貼った画像を挙げて、例として紹介してみます。*2

f:id:connehito:20160516222937g:plain

「会社の近くの夕飯情報」「MySQLに関するキャッチアップすべき情報」「後でやること」とそれぞれの情報が蓄積されていきます。 また、 #_yorumeshi チャンネルを開けば、その発言が(チャンネルのUIで)一覧できるわけです。

より実践的な例で言えば、コネヒトでは「技術的な情報を共有するチャンネル: #article-dev」を設置していますが、 この中で(コネヒトのプロダクトに採用しているWAFである)CakePHP関連の情報を投げる時に #_cakephp と付けておけば関連情報を後から見返すことができます。
また実際にあった例としては、 BIツールのTableauに関する情報は当初 #_tableau としてテンポラリーに運用していたものが、事後的に #article-tableau に「格上げ」されました。

実装の概説

SlackにHubotを住まわせているので、これを拡張する形で組み込みました。
以下の挙動を備えます

STEP 1. /#(_[0-9a-zA-Z\-_]+)/ というパターンで発言を待ち受ける

「ハッシュタグ」を抽出します。以下、抜き出したハッシュタグをそのままチャンネル名として扱います

STEP 2. 対象チャンネルの存在をチェック・設置

#_ハッシュタグ チャンネルが存在しているかをチェックします。
存在チェックは、hubot-slackであれば存在するチャンネル一覧をストレージに保持しているので、channels.list APIを叩く必要はありません。

channels = robot.adapter.client.channels

もし存在していなかったら、チャンネルを作成します。*3 api.slack.com

STEP 3. 発言ユーザーが当該チャンネルにjoinしているかをチェック・招待

チャンネルができたら、そのチャンネルのメンバー一欄を取得して参加状況をチェックします。 api.slack.com 不参加だったら、(チャンネルを作成した権限を持つアカウントで)招待を行います*4api.slack.com

STEP.4 発言のコピーを行う

あとは、キャッチした発言をハッシュタグのチャンネルに再投稿して完了です。 「発言したユーザー名」「発言したユーザーのアイコン」「本文」「元発言のパーマリンク及びチャンネル名」を、
投稿するようにしています。
イメージとしては、次のような処理です。

# channel: STEP 2のチャンネル情報
# msg: 元発言
# user: 元発言のユーザー
# slack: [slack-node](https://www.npmjs.com/package/slack-node)((hubotのSlackアダプターとは別に、直でREST APIを叩くために用意しています))のインスタンス。  
#    (STEP 2で出てきた)チャンネルオーナーとして振る舞わせるアカウント
data =
  channel: channel.id
  text: (msg.message.text.replace "##{msg.match[1]}", '').trim()
  username: user.name
  icon_url: user.slack.profile.image_72
  attachments: JSON.stringify [
    title: "in ##{msg.message.room}"
    title_link: getChatPermalink msg.message
  ]
  slack.api "chat.postMessage", data

これで、次のようにコピーができました。
#kaish-blog-netaチャンネルでの発言
f:id:connehito:20160517000841p:plain

#_yorumeshiチャンネルにアイコン等を模倣して発言が投稿される
f:id:connehito:20160516231914p:plain

やあやあ!「ハッシュタグ機能」の完成です!

おまけ1: 日報を書きやすくする

コネヒトでは職種にかかわらず行動記録を含む日報を毎日提出しています。
個人的には、これまでの経験上「日報はあったほうが良い」と思っているのですが、如何せんこの行動記録は面倒に感じていました。 怠惰。
が!このハッシュタグ機能を用いると、#金城の行動記録 も実現可能だな・・・!と実装しながら気づいたのです。 「ごく短いエイリアスを当てて、自分専用のハッシュタグができればいい」のような。

この行動記録用途には、特殊なサイン #___ を定めました。 これを内部的に #__mylog_{ユーザー名} として扱うエイリアスとしたのです。
実際の利用イメージはこんな感じ。 f:id:connehito:20160516233506g:plain

実装的には、上記STEP 1でチャンネル名を取得していた箇所で

channel_name = "#__mylog_#{msg.user.name}" if msg.match[1] is `___`

という処理を挟んでいるだけです。*5

これさえ付ければSlack上での通常の会話(今から対応します〜的な発言など)を日報に記しておく事もできますし、ChatOps的な運用をしているチームであればdeployのタイミングを記しておく!といった事も可能になります。

おまけ2: 日報を更に書きやすくする

記録を貯めることはできた、これで出力・整形までできれば・・・!ということで、ダンプコマンドも用意しました。 hubot相手に dump mylogと呟くと、直近24H以内の自分の日報チャンネルのログをコンパクトにしてDMしてくれます。

f:id:connehito:20160516234509g:plain

この内容をコピーしてそのまま日報に貼り付け、という使い方をしています。

導入後

実際に帰るのが10分*6早くなった!!!ということで、金城の生活に直接的に恩恵をもたらすプログラムとなりました・・・
社内でもじわじわと利用ユーザーを増やしており、しめしめ😏と思っている毎日です。

個人的にはbotなどを用いた「地味に役立つ」系の改善やアイディアは大好物なので、これからも続けたいなーと思っている日々です!
Slackが公式にスレッド機能を考えている!という情報もありましたし*7、どんどん進化していって欲しいですね・・・!

BotとかSlackとかの話できる人とメッチャ話したい今日このごろ。金城がお送りしました。

*1:最低限の状態を考えると、(当初のTwitterのように)「特定のキーワードや記号を決めて、検索でどうにかする」で充分なのですが、いまいちSlackの検索は使いにくい・・後から見返すにはつらい・・・という欠点があると感じています。

*2:記事中のSlack画像作成には、実際の業務に用いているものを出すのが難しかったので全てダミーのチームを利用しています

*3:通常、Hubotに喰わせる様なトークンですと制限した権限しか付与していないと思うので、別途用意するのが良さそうです

*4:restricted accountは不可、など招待されるアカウト側にも制約有り

*5:やっていることは単純なので、日報以外にも「アイディアメモ」「何かを見ての感想」といったものにも応用できるな、と思っています

*6:10分、という単位で分かりにくければ「1本前の電車」。

*7:http://www.theverge.com/2016/4/13/11418594/slack-threaded-messaging-chat-app-feature