tomoima525's blog

Androidとか技術とかその他気になったことを書いているブログ。世界の秘密はカレーの中にある!サンフランシスコから発信中。

中規模チームスプリントでSlack上のコミュニケーションが抜群に取りやすくなったひとつの方法

チーム規模が大きくなってくると、メンバーでコミュニケーションを取る際のストレスがだんだん上がってきます。特にSlack上でのやりとりは関係者が増えると複雑になりがちです。この点に関してここ最近、明らかにコミュニケーションの質が向上したチームの取り組みがあったのでシェアします。

中規模チームにおけるSlackコミュニケーション課題

プロダクトチームはPM、デザイナー、エンジニア、データサイエンティスト含めて30人弱。同じスプリントを回すチームとしては少々大きいサイズです。結果としてチームとしては1単位ですが、あらゆるその中でタスクが並行して走っており、それぞれが色々な粒度のタスクを掛け持つような形になっています。
チームで使っているSlackのチャンネルは以下のような感じで分かれています。

  • product-channel: プロダクト全般の仕様
  • dev-channel: 開発レベルの仕様
  • design-channel: デザイン系
  • dev-*-channel: 各プラットホーム(Android, iOS, サーバーサイド)の技術検討

f:id:tomoima525:20170924150517p:plain:w400

その他リリースやアラート専用のチャンネルもありますが大体上記が全てです。
ここで発生していたコミュニケーション問題が、複数のタスクの内容が並行する問題です。

  • AさんとBさんがXの仕様を話していて、一方でCさんとDさんがYの仕様を話すと混線する。スレッドを使うと、議論が埋もれてしまう(Xに関係しているEさんが気付かない)
  • 一つのタスクに関しての議論が飛び飛びになる。例えばSlack上だと
(Aの内容の議論1)
(Bの内容の議論)
(Cの内容の議論)
(Aの内容の議論2)

と言ったように話し合いがとぎれとぎれになるので、あとでもう一度確認したい時にとてもストレスフルになる。

小タスク単位でチャンネルを個別に作る

上記のような状況に対して、小さなタスク単位でSlackチャンネルを作ってみました。具体的にチャンネルを作る基準としては、1スプリント単位で完了するタスク(機能)レベル以上にしています。現チームのスプリントは2週間なので、その期間で完了する見込みなタスクがスタートするとチャンネルを作ります。

pj-A-channel
pj-B-channel
...

f:id:tomoima525:20170924150555p:plain:w400

チャンネルに招待されるのはそのタスクのステークホルダーです。うちの場合ですと、エンジニア(クライアント、サーバー)、PM、QA、デザイナーで平均6人程のチャンネルになります。チャンネルのトピックにはConfluenceの仕様リンクを貼っています。ここで行われる議論はどう機能を作っていくかにフォーカスします。

  • 進捗共有
  • 仕様の詳細な認識合わせ
  • リリーススケジュールの共有

毎日のスタンドアップは全体で行い、小チーム単位では行いません。その他、直接会話した方が早いような話ではもちろん対面で話したり、メンバーがリモートにいればハングアウトなどを行います。その内容もSlackのチャンネルに議事録的に貼ったりします。↓

f:id:tomoima525:20170924154205p:plain:w400

チャンネルは機能のリリース完了後には閉じます。

このSlack運用に変えた結果以下のような効果がありました。

  • 議論がスレッド上でクリアになるのでコミュニケーションの負荷が下がる
    議論はそのチャンネルに集約されるので、どこで話しあったっけ、という問題はなくなります。
  • 自分のタスクに関する集中力があがる
    今までは自分と関係ないタスクに関するポストも全て dev-channelproduct-channelに投稿されていたので、通知に煩わされたり、あるいは重要なポストを見過ごすということがしばしばありました。一方で今はタスクチャンネルはアサインされたメンバーしかいないので、何かコメントがあれば必ず自分に関係する内容になります。結果として必要な時に必要な情報が得られるので、タスクに対する集中力が上がりました。

チャンネル増えすぎる問題

一方で発生する問題として、Slackでありがちなチャンネル増えすぎ問題があります。コレに関してはトレードオフな部分もあるのですが、なるべく関係ない人は招待しないことで少しは緩和できるのかなと考えています。例えばトラッキング等メンバーとちょっと会話できれば済むようなことはpj-チャンネルに呼ぶことはせず、devで済ますようにするなどの配慮をするようにしています。

まとめ

結局のところ、Slackにおけるコミュニケーションの問題はいかに粒度を小さく出来るかが重要なのかなという感じです。もちろん1to1のコミュニケーションが一番良いですが、それだとチームとしてスケールしません。なので、適切なサイズを見計らってチャンネルを動的に作っていくのが大事なのかなと思いました。

皆既日食 in アメリカ 2017

8/21/2017の皆既日食を見てきました。興奮冷めやらぬうちに旅行録を写真とともに残しておきます。

日食を見るまで

皆既日食アイダホ州のWeiserという地域の近くにあるMann Creekというところで観測しました。ここは奥さんの知人の天文ファン(本人曰く”めちゃくちゃ天文マニア”)の方が一年前から天候、日食経路、周辺環境を踏まえて選定した場所です。

f:id:tomoima525:20170823161415p:plain:w400

アメリカで皆既日食が見られるのは38年ぶりで非常な混雑が予想されるということもあり、日食の前々日に現地最寄り(と言っても100km離れている)のボイジというところに入りました。

どこに行っても「日食見に来たの?」と言われ、日食グッズもそこかしこで売られていました。

f:id:tomoima525:20170819181241j:plain:w400

前日はやはり混雑を懸念し、早朝にはボイジを出てMann Creekに向かったのですが、思った以上に空いていました。多くの人がより気軽に鑑賞できる場所に向かったようです。場所の選定大事ですね。

現地に朝9時半頃に到着しテントを立てた後は、合流した方たちとBBQをしたり本を読んだり過ごしました。気温は36度前後あり、乾燥していてとにかく暑い。テント場なので周りにもいくつもテントやキャンピングカーがあり、目の前の池で泳いだりしてのんびり過ごしていました。

キャンプ場
f:id:tomoima525:20170821103711j:plain:w400

テントだけだと蒸し風呂なのでタープを貼って日よけ
f:id:tomoima525:20170820102459j:plain:w400

$5の使い捨てグリルでBBQ
f:id:tomoima525:20170820162709j:plain:w400

観測器具セットアップ
f:id:tomoima525:20170820102505j:plain:w400

日が暮れにはとても美しい夕焼け空と夜には綺麗な天の川が空にかかっているのが見えました。
f:id:tomoima525:20170821115824j:plain:w400
f:id:tomoima525:20170821154351j:plain:w400

いざ皆既日食

皆既日食にはサロスと呼ばれるいくつかの周期があるそうです。今回のサロスは比較的短いもので、最長約2分40秒の皆既日食です。なお日食は英語で Solar Eclipse ですが、皆既日食は特にTotal Solar Eclipseあるいは Totality と言います。Totalityって響きがカッコイイですね。

最初の部分日食は現地時間AM10時15分ごろからスタートしました。この段階ではまだ実感はほとんどわかず、観測している人たちもワイワイしています。

f:id:tomoima525:20170821112343j:plain:w400

部分食は日食グラス越しでないと欠けている様子はわかりません。しかし11時頃になるといよいよ太陽が欠けてきて、空は青みが増し、気温が明らかに下がってきます。さっきまで飛んでいた鳥がいなくなり、静かになったのが印象的でした。 ピンホール越しに光を映すと、月形に影が変形しているのがわかります。

f:id:tomoima525:20170821110759j:plain:w400
f:id:tomoima525:20170821111536j:plain:w400

そして11時33分。いよいよ皆既日食の瞬間がやってきました。

あっという間に周囲が暗くなり、太陽は周辺部のみゆらゆらと輝きを放ちます。一方地平線は赤みを増し、まるで夕焼けのよう。

とにかく不可思議な風景に「ヤバイ」しか言葉が出てきませんでした。カメラを準備していたのですが、とてもちゃんと撮影する余裕などなかったです。息を呑むことしかできないほどあっという間の美しい自然現象でした。

観終わった後

日食を堪能した後は再び気温も上がってきたので、早々にテントを畳んでボイジへ向かいました。同じような帰り道をたどるキャンパーたちのためか通常より1.5時間くらい長くかかりましたが、他の観測地では通常2時間の道が7時間になったそうです。比較的空いている場所でよかった。そのままボイジに一泊して、次の日サンフランシスコへの帰路につきました。

ところでアイダホ州は白人が84%もいるらしく、レストランやお店に入っても自分たち以外全員白人ということが何度もありました。会う人会う人とても親切でフレンドリーだったのが印象的でした。

f:id:tomoima525:20170822094542j:plain:w400
The Potato Capital of the U.S.

旅費

以下に忘備録的に経費を残しておきます。奥さんの分のキャンプ道具を揃えたりテントを買ったりしたこともあり、$500程使いましたが旅行自体は二人で$1200程度でした。飛行機を5ヶ月前に予約していたので、値段が高騰する前に買えたのが良かったのかもしれません。

飛行機 * 2人分 $434 レンタカー $280 ガス $20 (約200km分) Airbnb $80 Hotel $180 外食 $100 キャンプ場での雑費(BBQセットその他) $70 キャンプ道具(テントその他) $500 雑費(お茶等) $50

合計 $1714

Kotlinの標準ライブラリ(let, apply, with etc)を何となく使うから卒業する

f:id:tomoima525:20170804154058p:plain:w400

Kotlinの標準ライブラリ便利ですよね。Kotlinを書き始めて T.?let{ } の便利さには感動しました。標準ライブラリ色々ありますが、どんなものがあり、どういう挙動をするのかよく把握していなかったので、自分の勉強のために*1まとめてみました。

*1:多分Qiitaにはまとめてあるのだろうけど

続きを読む

React Native Meetup Portlandでハイブリットアプリ開発について話しました

先日React Native Meetup Portlandという勉強会でReactNativeとNativeのハイブリットアプリ開発について発表をしてきました。アメリカの自社開催以外のの勉強会で発表することは初めてだったので、知見と反省をメモしておこうと思います。

続きを読む

Project Fi 2ヶ月使ってみた所感

f:id:tomoima525:20170604235902j:plain:w400
通信キャリアをT-mobileから Googleが提供するモバイル通信サービスであるProject Fiに乗り換えて2ヶ月ほど使ってみたので、その感想です。

Project Fiとは

いわゆる格安SIMのグローバル版のようなものです。特徴としては

  • 複数のキャリアの中から接続が良好なものを自動的に切替え、品質の高いモバイル通信が可能
  • 世界135カ国で利用可能な電話番号、モバイル通信が提供される
  • モバイル通信料は一律 $10/1GBで、利用した分だけ課金される(例 0.65GB = $ 6.5)

少々古いのですが、Gigazineで紹介された記事があります。

gigazine.net

複数のキャリアから良好なものを選べるという点はGoogleならではな特徴だと思います。

続きを読む

Grokking Algorithms アルゴリズム初学者、学び直したい人に勧めたい洋書技術本

f:id:tomoima525:20170529231655j:plain:w400
最近同僚に Grokking Algorithms というアルゴリズムについて書いた本が楽しく読めたと教えてもらい、自分も一通り読んだらアルゴリズム再学習に非常に良かったのでブログに書きます。

ざっくり内容

Grokking Algorithms: An Illustrated Guide for Programmers and Other Curious People

Grokking Algorithms: An Illustrated Guide for Programmers and Other Curious People

Grokking(わかる、意味を知る) アルゴリズムという名の通り、アルゴリズムの基本を知ることが出来る一冊になっています。この本の特徴としては、400枚にも及ぶ豊富なイラストでアルゴリズムの解説がなされている事が挙げられます。著者のAditya氏はEtsyのエンジニアですが、アートについても見識がある方だそうです。内容については、アルゴリズムの基本(計算量とは等)やソートに始まりk近傍法といった中級レベルのアルゴリズムまでを網羅しています。

続きを読む

ReactNativeで理解しておくと良いReduxとMiddlewareのフローを理解する

f:id:tomoima525:20170424163521p:plain

(5/29/2017追記 ‘必要不可欠’ とタイトルに書いていたら'必要不可欠でない'と指摘を受けました。なんらかのデータフローの仕組みは必要だけどReduxである必要はないのでタイトル修正しました)
最近ReactNativeをちょこちょこ書いています。アプリ向けのReactNativeを書くにあたって理解がのぞましいのがデータフローの仕組みであるRedux、及び様々な処理を仲介するMiddlewareです。小さなアプリをつくってみて一通り把握したので、整理も兼ねて初めてReact-Reduxを触れる時にどの辺を見ればよいかまとめてみます。
作ったのはChuckNorris FactsのJokeを検索して表示するアプリです。

github.com

デモ動画

ざっくり仕様を述べると

  • ReactNative + Redux + RxJs
  • 検索ワードを投げる、ローディング、結果表示のステートをReduxで管理
  • https://api.chucknorris.io/APIをMiddlewareであるredux-observable経由で叩いて取得
  • APIレスポンスは RxJsでストリームをハンドル

という風になっています。

Reduxとは

すでにご存知な方も多いですが一応述べておくと、ステート管理のフレームワーク及びそれを実現するライブラリです。 ここで色々説明するよりRedux本家のページが分かりやすいです。
チュートリアルも充実しています。但しReactNativeではなくReactで書かれているため、自分のようにJavascript自体があまり馴染みが無い場合、ReactNativeとの違いに戸惑うこともあるかもしれません。

あとこちらのマンガでわかるReduxもおすすめです。

code-cartoons.com

Reduxの大まかな流れは以下になります。

(1) ReactコンポーネントからActionをコール
(2) ActionをStoreにあるReducerに渡す(dispatchと呼ばれる)
(3) Reducerがステートを更新してReactに返す(React側はコンテナを経由してコンポーネントにステートを渡す)

Reduxが優れているところ

上記の流れを見ると分かる通り、ステートで挙動が制御されるので、Atomic Design pattern である Reactと非常に相性が良いです。
React(ReactNative)はUIコンポーネントにすぎず、ある状態を渡すとそれを愚直に表示するだけのものです。その状態を綺麗に管理できるのがReduxになります。

余談ですが、ReduxのAPIリファレンスをみると登場するメソッドが6つしかないです。インタフェースよりも仕組みを理解することが重要なライブラリですね。

Middlewareとは

Reduxの非常に優れた仕様の一つで、ActionからReducerの間に発生する処理をつかさどる部分になります。上の流れで言うと(2)の部分で発生する処理をハンドルします。例えばAPIリクエストであったり、ログ処理を行ったりする部分です。
Middlewareの特徴としてはReduxを離れて様々なサードパーティのエクステンション等に処理を託すことができる点があります。サードパーティのMiddlewareとしてはredux-observable,redux-saga,redux-thunkなどそれぞれ色々な特徴を持ったものがあります。

ReactNative + Redux プロジェクトで見るべきところ

上記を踏まえた上で、ReactNativeのコードを追う場合に見るべきところは以下になるかなと思います。

トップのindex.js

ここではReducerとMiddlewareからStoreを生成し、アプリに設定するコードがあります。自分のアプリですと、index.anroid.js にあたります。

import chuckNorris from './js/reducers';
import chuckEpic from './js/epics';

// Middlewareを生成
const epicMiddleware = createEpicMiddleware(chuckEpic);
// Storeを生成 
const store = createStore(chuckNorris, applyMiddleware(epicMiddleware));

export default class ChuckNorrisViewer extends Component {
  render() {
    return (
      <Provider store={store}>// Appに設定
        <App/>
      </Provider>
    );
  }
}

Epics

Middlewareに託された各処理のことをEpicと呼びます。Epicsフォルダを見るとどのような処理がMiddlewareでハンドルされているかわかります。./js/epics/index.js を見ると

import { getNorris } from './posts';
const chuckEpic = combineEpics(getNorris);

と書かれていて、このアプリでは一つのEpicしか設定されてないとわかります。 さらにimportされた post.js を見てみると、

import {receivePosts} from '../actions'; //Action Creator
import {REQUEST_POSTS} from '../actions'; // Action Type
export const getNorris = action$ => {
  return action$.ofType(REQUEST_POSTS).mergeMap(action => {
    return ajax.getJSON(`https://api.chucknorris.io/jokes/search?query=${action.payload}`).map(response => receivePosts(response));
  });
};

ActionがREQUEST_POSTSであった場合にAPIリクエスト処理を行い、それを再度Action Creatorに返しているのがわかります。

Reducers

今度はReducersを見てみます。Reducersにはステートがどのように更新されているかが書かれています。./js/reducers/index.js を覗いてみると

const chuckNorris = combineReducers({
  searchResult,
  postByKey,
  visibilityFilter,
});

3つのReducerが登録されていることがわかります。例えばsearchResult.jsを見てみると

const searchResult = (state = {}, action) => {
  switch (action.type) {
    case RECEIVE_POSTS:
      return {
        ...state,
        items: action.payload
      };
  }
};

となっていて、RECEIVE_POSTS というActionを受け取った場合はpayload入れたステートを再度生成して返しているとわかります。なお ...stateObjectSpreadと呼ばれるSyntaxで、要は元のステートをコピーして新しいステートを生成しています。

Action

(2) ActionをStoreにあるReducerに渡す(dipatchと呼ばれる) の部分にあたります。どのようなActionがあるかというのも全体像を知る上で重要です。actions/index.jsを見てみると

export const RECEIVE_POSTS = 'RECEIVE_POSTS';
export const REQUEST_POSTS = 'REQUEST_POSTS';

//リクエストする時のAction
export const requestPosts = ( key ) => {
  return {
    type: REQUEST_POSTS,
    payload: key,
    isFetching: true,
  }
};

//結果を受け取った時のAction
export const receivePosts = ( response ) => {
  return {
    type: RECEIVE_POSTS,
    payload: response.result,
    isFetching: false,
  }
};

ActionTypes.jsとAction.jsを分ける場合もあるようですが、今回は同じファイルに書いています。 Actionにどのようなタイプがあり、それぞれのActionが取る値がわかるかと思います。

React Container

今度は各画面でどのようにReact側にステートが渡されて描画されるかを見ます。ContainerはReactにおいてどのように処理が行われるか(そして実際のUIコンポーネントに渡すか)を決めます。詳しくはここがわかりやすいです。
“どのように処理が行われるか” という言葉からも分かる通り、Reduxとも縁が深いところになります。

検索ワードを入力するContainerである containers/Input.js を見てみると

import { requestPosts } from '../actions';
...
const mapDispatchToProps = (dispatch) => {
  return {
    onButtonPress: bindActionCreators(requestPosts,dispatch),
  };
}
export default connect(null, mapDispatchToProps)(Input);

という処理があります。(1) ReactコンポーネントからActionをコールの部分で、これがActionを呼ぶ時のおまじないになっています。Storeに渡すdispatchにpropsで定義された値を接続しているわけです。ここでは onButtonPress という値が requestPosts をコールするようになります。
次に検索結果を表示するcontainers/ResultList.jsを見てみると、今度は

const mapStateToProps = (state) => {
  return {
    result: state.searchResult,
    visibilityFilter: state.visibilityFilter,
  }
}
export default connect(mapStateToProps)(ResultList);

という処理が見えます。これが(3) Reducerがステートを更新してReactに返すの部分で、Reducerから受け取ったステート更新をpropsに渡して描画を行うための手続きになります。ここでは searchResultvisibilityFilter という2つのReducerによるステートを受け取っています。

これでアプリ内の一通りの処理が追えたかと思います。

まとめ:Reduxをどう理解していけばよいか

自分にとってReduxの最初のつまづきははステートがどこから渡されて、どう処理されて更新していくのかが初見だと理解しにくい点でした。一度アプリを作ってconsoleにログを出しつつステートの変化を追うと、理解が深まったのでおすすめです。