品質と新規開発のバランスというタイトルで登壇してきました

こんにちはカミナシでアプリケーションエンジニアをやっている沼田( @keinuma15 )です。 先日行われた Startup Issue Gym #1【開発プロセスのIssue】 にて「品質と新規開発のバランス」というタイトルで発表してきました。

概要

スタートアップのプロダクトは成長していくにつれて様々な課題にぶつかります。 その中でも0→1から1→10フェーズに変化すると既存機能を改善しつつ新機能を開発していく課題が出てきます。 今回はカミナシがこの課題どう立ち向かっているのか共有させていただきました。

speakerdeck.com

プロダクトの成長による課題の変化

Image from Gyazo

プロダクトはフェーズに応じて目指しているゴールと課題が変わってきます。

カミナシは2019年12月にピボットして今のプロダクトの開発を開始しました。 当時は0→1のフェーズだったため、顧客のどの課題を解くのかやソリューションを考え実現する必要がありました。 そこから2020年6月に正式リリースしたのが「カミナシ」です。

Image from Gyazo

カミナシは現場で働いてる全ての業界を対象としたアプリで記録業務をできるプロダクトです。 開発を開始して半年ほど経つと次第にユーザーが増え始め、既存機能だけではカバーできていない領域が増えてきました。 ユーザーにより価値を届けるためには新機能を開発しないといけないが、すでに提供している機能を改善する必要も出てきました。

狩野モデルとプロダクト開発

新機能と既存機能の開発のバランスをどうとってきたかを説明するために狩野モデルについて紹介させていただきます。

Image from Gyazo

狩野モデルは品質の種類とそれらの顧客の満足度の関係性を示したものです。 グラフには3種類の品質が書いてますが、元のモデルには5種類の品質が定義されています。

名前 充実した時の満足度 不足した時の満足度
魅力的品質 満足 不満足にはならない
一次元的品質 満足 不満足
当たり前品質 満足にはならない 不満足
無関心品質 満足にはならない 不満足にはならない
逆品質 不満足 満足

狩野モデルの品質をプロダクトに合わせて考えると魅力的品質が新機能、当たり前品質が既存機能と見ることができます。

カミナシにとって新機能は前例のプロダクトが少なく、ユーザーに提供してから業務に機能が組み込まれフィードバックが来るのに1,2ヶ月かかります。 一方で当たり前品質は、カミナシを導入する前は紙を利用して業務を行なっているため運用が止まらないことが前提になりやすいです。そのため高い当たり前品質を維持する必要があります。

これらの特徴を踏まえた上で、カミナシがそれぞれの品質にどうアプローチしてきたか紹介させていただきます。

カミナシの戦い方

カミナシは魅力品質と当たり前品質において以下の作戦を立てていました。

  • 魅力的品質:隣接した業務領域をカバーする
  • 当たり前品質:機能の再構築

魅力的品質

カミナシのメイン機能がカバーしている業務領域は作業後のチェック業務です。

Image from Gyazo

チェック業務はその後の集計、改善まで含めて効果を発揮します。 チェック業務だけカバーしていても紙の電子化に留まってしまい、ユーザーに価値を届けきれてないです。 そのため、チェック業務の次の工程にある集計業務を新機能として開発しました。

ここからは新機能を開発するフローと同じです。 カミナシでは最初にユーザーに現状の業務と課題をヒアリングし、どこに痛みがあるのかを把握します。 その結果、集計業務では分析・報告書・システム間連携の主に3種類あり、紙からEXCELファイルに転記しているため作業量が多い課題が存在することがわかりました。

次に3種類の業務をどういうソリューションで解決するか検討します。 当時はそれぞれの業務ごとに機能を開発することと業務に関係なくユーザーがカスタマイズできる機能が候補にありました。 魅力的品質は改革の性質を持っていますが、カミナシではなるべく元の業務とのギャップを小さくするようにしています。 そのため、集計業務として記録したデータを任意のEXCELで出力できるEXCEL変換機能をリリースしました。

当たり前品質

当たり前品質を下げる大きな要因として内部品質とUX品質があります。 UX品質はスタートアップのプロダクトの場合、作りっぱなしで放置されていると低くなりやすいです。

開発当初に想定していた仮説をもとに開発したものの、実際に使い初めてもらうと思っていたものと違うと感じる時があります。 カミナシでは当初想定していた仮説の間違いを正して、機能を再構築してきました。

具体例にダッシュボード機能を上げさせていただきます。

Image from Gyazo

カミナシのダッシュボードは予定されていた記録が行われているか確認する機能です。

当初の仮説では記録できていない数を知りたいと仮説をおいていたのですが、使いこなしてくれるユーザーが少ない状況でした。 そこでユーザーがどういう情報を知りたいのか声を集めたところ、どの現場でいつできてないのかを知りたいことがわかりました。 この学習をもとにダッシュボード機能を再構築し、どの現場でいつできてるかをOX表で表現してリリースしました。

当初はできてない現場が多くXが増えるのではないかという懸念がありましたが、現場の状況がわかりやすくなったというフィードバックをいただいてます。

次の課題

ここまで業務領域を広げて魅力品質を高めつつ、機能を再構築して当たり前品質を上げる例を紹介させていただきました。 今のカミナシはまた別な課題に向き合っています。

一つはプロダクトがカバーできる領域が1プロダクトの範囲を超えてきたことです。 カミナシは複数の業界にプロダクトを提供しているため、業界特化の業務に対応しきれていません。 これらを解決するために新プロダクトを開発していく予定です。

もう一つはプロダクトの内部品質の課題が増えてきたことです。 コードの自動テストを追加したりインフラの刷新をしていかないといけません。

これらの課題に興味がある方はEM/アプリエンジニア/SREと幅広く募集しているのでぜひお話しましょう。

open.talentio.com open.talentio.com open.talentio.com

react-native-svgで手書きアプリを作ろう

f:id:kaminashi-developer:20210428102834p:plain カミナシの浦岡です。弊社が開発している「カミナシ」には、 以下のような用途を想定して、手書きメモ機能を組み込んでいます。

  • カメラで撮影した写真の上に矢印マークやメモを追加したい
  • キーボード入力に不慣れなユーザーでも素早く簡単にメモを録りたい
  • 手書きで入力できる署名欄

ちょっとした機能ですが、弊社アプリのように現場作業で使われるケースにおいて何かと重宝されている機能です。

今回、react-native-svgを使った実装について紹介します。

手書き線の表現

手書きされた一筆、一筆をsvgのpathタグを使用して表現することにします。

pathタグはd属性に指定された座標群の情報を基に線を描画してくれます。

https://developer.mozilla.org/ja/docs/Web/SVG/Attribute/d

例えば、以下のように3点の座標を指定すると、それらを結ぶ線ができあがります。

gyazo.com

手書きイベントの取得

手書きの際のジェスチャーイベントの取得にはreact-nativeのPanResponderを使用します。

手書き対象のviewにPanResponderを設定することで以下の図のような順でイベントが取得できます。

gyazo.com

  1. 画面へのタッチ開始を起点に、pathタグを生成します。
  2. 指が移動している最中も、先ほどのd属性の値を指の軌跡に沿って更新することで表現できます。
  3. この時点で1つのpathタグを表現するための座標群が確定するので、確定情報として保持します。

コード

import React from 'react'
import { View, PanResponder, StyleSheet, PanResponderInstance, GestureResponderEvent } from 'react-native'
import Svg, { G, Path } from 'react-native-svg'

const pointsToSvg = (points: { x: number; y: number }[]) => {
  // 筆跳ね防止のための閾値
  const distanceThreshold = 40

  const filteredPoints = points.filter((point, index) => {
    if (!points[index - 1]) return true
    const distance = Math.sqrt(Math.pow(points[index - 1].x - point.x, 2) + Math.pow(points[index - 1].y - point.y, 2))
    return distance < distanceThreshold
  })

  if (!filteredPoints.length) {
    return ''
  }

  // svg-pathのd属性を生成
  let path = `M ${filteredPoints[0].x},${filteredPoints[0].y}`
  for (let point of filteredPoints) {
    path = `${path} L ${point.x},${point.y}`
  }

  return path
}

export default function App() {
  // 現在、一筆書きしている最中のパスの座標郡
  const [points, setPoints] = React.useState<{ x: number; y: number }[]>([])
  // 書き終わったパス情報の配列
  const [paths, setPaths] = React.useState<{ d: string }[]>([])

  const panResponder: PanResponderInstance = PanResponder.create({
    onStartShouldSetPanResponder: () => true,
    onMoveShouldSetPanResponder: () => true,
    onPanResponderGrant: (event: GestureResponderEvent) => {
      if (!event.nativeEvent.touches.length) {
        return
      }
      setPoints([...points, { x: event.nativeEvent.locationX, y: event.nativeEvent.locationY }])
    },
    onPanResponderMove: (event: GestureResponderEvent) => {
      if (!event.nativeEvent.touches.length) {
        return
      }
      setPoints([...points, { x: event.nativeEvent.locationX, y: event.nativeEvent.locationY }])
    },
    onPanResponderRelease: () => {
      setPoints([])
      setPaths([
        ...paths,
        {
          d: pointsToSvg(points),
        },
      ])
    },
  })

  return (
    <View style={styles.container} {...panResponder.panHandlers}>
      <Svg width="100%" height="100%" preserveAspectRatio="none">
        <G>
          {/** 書き終わったパス情報の描画 */}
          {paths.map((path, index) => (
            <Path
              key={index}
              d={path.d}
              stroke="black"
              strokeWidth="3"
              strokeLinecap="round"
              strokeLinejoin="round"
              fill="none"
            />
          ))}
          {/** 現在、一筆書きしている最中のパスの描画 */}
          <Path
            d={pointsToSvg(points)}
            stroke="red"
            strokeWidth="3"
            strokeLinecap="round"
            strokeLinejoin="round"
            strokeDasharray="4"
            fill="none"
          />
        </G>
      </Svg>
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'flex-start',
    width: '100%',
    height: '100%',
  },
})

動作イメージ

gyazo.com

最後に

以上、シンプルな手書きアプリができました。 もっと多機能にしてみたい!と興味持ってもらえた方がいたら、ぜひ自分向けの手書きアプリにカスタマイズしてみてください!

【Expo Go】アプリケーションがクラッシュして解決するまでの話

f:id:kaminashi-developer:20210426191004j:plain

こんにちは、株式会社カミナシのエンジニア @ImuKnskです。

はじめに

アプリケーションが急にクラッシュすることありますよね? 昨日まで動いていたのに何故かクラッシュ…。 アプリケーションがOSSの場合、特に切り分けが大変ですよね。

カミナシでは『Expo Go』を使ったアプリケーションの開発を行っており、急にアプリケーションがクラッシュしてしまい解決に至るまでの話をしてみようと思います。

突然クラッシュするアプリケーション

まずはこちらをご覧ください。

gyazo.com

ローカル環境でExpoを起動した後、iPadQRコード読み込んでアプリケーションが起動する間もなくクラッシュしています。始めは古いキャッシュでも残っているのかな?と思って、再起動やアプリケーションの削除を行いましたが、この事象は直りませんでした。

何かしら問題が起きていると思い調査を始めました!

どういう調査をしたか
  • シミュレーター等の起動確認

問題なく起動し、設定等は問題ないと思っていました。(最終的にはここに問題であることを知ります)Expo Publish(OTA Update)の確認や、developmentモードでの起動やリリースに問題ありませんでした。

  • 利用しているPackage問題

まっさらなExpoプロジェクトを作成して、『カミナシ』で使っている『package.json』を入れて起動してみると起動できる…。どうやら『package.json』が悪さをしているわけじゃない。

  • OSSGithub issueやアプリケーションのバージョンを確認

『Expo Go』側に問題があるのではと思って確認したところ、2日前に『Expo Go』がバージョンアップしていることを知り、これが影響しているのではないかと思い始めました。Github issueを検索したり、インターネットで『Expo Go Crash』等で検索してみたりしました。最終的には有益な情報はなく、ひたすらクラッシュしていたので開発者にクラッシュレポート届け!と思っていました。

  • カミナシのアプリケーションの修正履歴

アプリケーションがクラッシュしているので設定かなと思い、設定ファイルの履歴を洗いましたが直近での変更はなく、アプリケーションの問題じゃなさそう…と決めつけていました。早く『Expo Go』の変更リリースないかなと思う日々でした。

ある日舞い降りてきた解決の糸口

エンジニアメンバーにクラッシュするんですよね…早く変更リリースないですかねー。と会話していたとき、クラッシュした様子を見ると『Splash Screen』が表示される前にクラッシュしてないかと思いました。

『app.json』でファイルを参照しているところは『Splash ScreenとGoogleService-Info.plist』でした。

まずはファイルを読み込んでいるプロパティを削除したところ…

起動した!!!

ということは、ファイルがおかしいと思い1つずつ確認したところ『GoogleService-Info.plist』ファイルが悪さをしていることが分かりました。

『GoogleService-Info.plist』ファイルの確認

ローカル起動の場合、ファイルはダミー値を入れて利用していました。

  • developmentで使っているファイルに差し替える

当然ではありますが、起動しました。

  • localとdevelopmentの差分を確認

ローカルとdevelopmentでプロパティの不足があり、ダミー値を追加しましたが起動しませんでした。

よく見ると一部プロパティ名が…

f:id:kaminashi-developer:20210423091333p:plain

『痛恨のスペルミス』

ここにたどり着くまで時間が掛かりましたが、完全に思い込みでした。これまでは起動していたので設定ファイルのミスはない、と思っていたが実は最初から起きてもおかしくなかった事象でした。 (それなら『Expo Go』で最初からクラッシュして欲しかった気持ちはありますが、OSSなので利用する側の問題ですから何にも言えませんね…)

スペルミスを修正すればちゃんと起動しました(笑)

おわりに

思い込みから入る調査は時間が掛かることが身にしみて分かりました。まずは何事も疑うことから始めて調査に取り組みたいと思いました!個人的にはこういった調査は好きなので苦痛じゃなかったですが(笑)

解決してスッキリです!

最後に弊社ではエンジニアを募集しております。 興味がある、話を聞いてみたい、応募したいという方はお気軽にご応募ください!

open.talentio.com

StoryShotsの可能性を探る

こんにちは、カミナシの@tomiです。

前回は、Expo ReactNativeにStorybookの導入を行いました。

今回は、StoryShotsを入れてみたいと思います。

StoryShotsとは、自動スナップショットテストができるStorybookのアドオンです。 内部的には、JestのSnapshotが動作し、UI が予期せず変更されていないかを確かめるツールです。

なぜ入れてみようと思ったかというと、ただStorybookを導入しただけでは、メンテナンスされずに腐ってしまうのが目に見えているので、楽に忘れることなくstoryの更新ができる方法ないかなと調べていると、よくStoryShotsを目にしたので試してみることにしました。

StoryShotsの実際の動作を見て、今後の運用に必要かどういったときに役に立ちそうかを体験したいと思います。

StoryShotsを導入する

Storybookの導入は前回の記事で済んでいるので、StoryShotsのアドオンを入れます。

yarn add -D @storybook/addon-storyshots@^5.3
# monorepoの場合↓
# lerna add @storybook/addon-storyshots@^5.3 --scope=mobile --dev

ReactNativeに対応しているStorybookのバージョンに合わせて5.3をインストールしました。

/storybookディレクトリにStoryshots.test.tsファイルを作成。

// Storyshots.test.ts
import path from 'path';
import initStoryshots from '@storybook/addon-storyshots';

initStoryshots({ configPath: path.resolve(__dirname, '../storybook') });

ドキュメントでは、initStoryshots()だけで動くようでしたが、monorepo構成だからかconfigPathでStorybookの設定を行っている場所を指定してあげる必要がありました。

次に、StoryShotsの実行用のjest.configを設定します。

jest.config.jsと同じ場所にjest.config.storyshots.jsを作成。

// jest.config.storyshots.js
const path = require('path')
const baseConfig = require('./jest.config')

module.exports = {
  ...baseConfig,
  testMatch: ['**/packages/mobile/storybook/Storyshots.test.ts']
}

testMatchの部分を、StoryShots用のテストだけを読み込むように設定します。

こうすることで、ユニットテストと自動スナップショットテストを別で実行できるようにしています。

StoryShotsを実行するscriptを設定します。

// package.json
"scripts": {
  "storyshots": "jest -c jest.config.storyshots.js"
}

StoryShotsを実行する

では、実際にStoryShotsを実行するとどういう結果がもらえるのかを見てみます。

yarn storyshots

f:id:kaminashi-developer:20210422135244p:plain

*.stories.jsが存在する3件のSnapshotsが生成に成功しました。

そして、__snapshots__/Storyshots.test.ts.snapというファイルが作成されました。

中身は、Componentファイルがレンダリングされた形のようです

f:id:kaminashi-developer:20210422124254p:plain

差分がでるとどうなるのか

スナップショットが作成されたので、次にコンポーネントを更新して差分を出すとどういう結果がでるかを確認しようと思います。

Welcome/index.jsというコンポーネントに適当にテキストを追加して、再度SnapShotsを実行してみます。

f:id:kaminashi-developer:20210422124334p:plain

すると1件のスナップショットが失敗しました。

失敗した内容として今回変更した部分が表示されます。

f:id:kaminashi-developer:20210422124401p:plain

これが意図した変更の場合はスナップショットを更新しましょう。実行コマンドに-uオプションを付けてあげます。

yarn storyshots -u

1件が更新されました。

f:id:kaminashi-developer:20210422124435p:plain

なるほど、なるほど。 StoryShotsがどういうものか掴めてきました。(いやな予感がする・・・)

次に、Welcome/index.jsを使っているコンポーネントWelcome/Wrap.tsxを作成してみます。

// Welcome/Wrap.tsx
import React from 'react'
import Welcome from './index'

const Wrap: React.FC = () => {
  return <Welcome />
}

export default Wrap

Storyにもこのコンポーネントを追加します。

// Welcome/Welcome.stories.jsに追記
import Wrap from './Wrap'

storiesOf('Welcome', module).add('to Storybook Wrap', () => <Wrap showApp={linkTo('Button')} />);

そして、StoryShotsを実行します。

yarn storyshots

1件追加されました。

f:id:kaminashi-developer:20210422124518p:plain

では、ここでWelcome/index.jsを更新したらどうなるでしょうか?

以下が実行結果です。

f:id:kaminashi-developer:20210422124545p:plain

2件のスナップショットにエラーがでました。

失敗したスナップショットはWelcome/index.js自身とそれを使用しているWelcome/Wrap.tsxです。

StoryShotsによって、コードを変更した際の影響範囲がわかることで、予期せぬところで使われていて画面が崩れてしまったといったミスを防ぐことができそうです。

StoryShotsの効果

StoryShotsを実際に触ってみて、受けれる恩恵は大きく2点かなと感じました。

Storybookの更新漏れを防げる

ただのUI変更ではほぼStoryShotsは必要ないでしょう。

ですが、例えばButtonコンポーネントにcolorというプロパティを追加したとき、StoryShotsを入れておくことで、storyの更新もしなければと気づくことができます。

ただし、プロパティを追加してyarn storyshots -uで更新してみましたが、テストが通ってしまったので完全にStorybookの更新漏れを防ぐことができないかもしれません。

f:id:kaminashi-developer:20210422124602p:plain
Warningは出ているが、StoryShotsは更新される

予期せぬ更新を防げる

変更したコンポーネントが想定しているところ以外に使われていることに気づけるので、レイアウトの崩れや動作しないといった不具合を減らすことができそうです。

ただし、Storybookが入っているところだけが検知できるので、すべてのコンポーネントにStorybookが入っていないとあまり意味がなさそうです。

まとめ

StoryShotsを導入して、どういう効果があるかを確認してみました。

結果的にいまのフェーズでStoryShotsを入れるのは、あまり効果が期待できないと感じました。

Storybook導入の目的が新デザインガイドラインの運用・管理なので、基本となるコンポーネントにしかstoryを追加しないため、恩恵を受けづらそうです。

プロジェクトのはじめからStorybookを入れており、全コンポーネントのstoryを作成していたのであれば、非常に重要なツールかなと思います。

カミナシにおけるテックリードについて

f:id:kaminashi-developer:20210421002254j:plain

エンジニアの@nasum(id:Tomato-360)です。

最近テックリードに任命されました。エンジニアのマネジメントキャリアパスの第一歩といった感じです。

カミナシではテックリード職ができたのは初めてでまだ職務に関しては曖昧です。テックリード職に任命された自分はそこを固めていくことも期待されていると思っています。

そこで今回は直近やっている仕事を通じてカミナシにおけるテックリードの業務を紹介していこうと思います。

本当はRaspberry Pik8sクラスタを作る続きを書こうと思いましたがうっかり間に合いませんでした。

テックリードとは

そもそもテックリードとはどういう存在なのでしょうか。最近読み始めてかなり名著だと感じている「エンジニアのためのマネジメントキャリアパス」で引用されている「Talking with Tech Leads」を孫引きすると。

[テックリードとは](ソフトウェアの)開発チームに対する責任を担い、最低でも自身の職務時間の3割はチームと共にコードを書く作業に充てているリーダーのこと。

とあります。これ以上はないぐらいの簡潔な文章だと思います。各会社はこれをベースにテックリード職を定義したらいいかもしれません。

ではカミナシのテックリード職がどういう仕事をしているか書いていきたいと思います。

カミナシのテックリード職のお仕事

カミナシにおけるテックリード職でやっている仕事を図にしてみました

f:id:kaminashi-developer:20210421002405p:plain

大きく分けると採用と開発の二つの仕事があります。

採用

採用に関してはJobDescriptionの作成と面談、採用試験の作成があります。

採用はどちらかというとEngineer ManagerやCTOが行うものだと思います。しかしカミナシはまだそれほど大きくない組織であり、チームメンバーの採用は即現開発チームに影響してくるのでチームのための仕事と解釈して執り行っています。

書類選考や面談などの相談なども人事のメンバーと相談しながら行っています。

業務全体の1割ほどをこの時間に充てています。

開発

開発は通常の開発業務にプラスして、チームに関する業務である、スクラムマスター・チーム改善・チーム代表があります。

スクラムマスターはわかりやすいです。カミナシではスクラムで開発業務を回しているので開発チームがスクラム開発を滞りなく行うために支援を行います。

カミナシではいったんスクラム開発の基本に立ち返ってスクラムイベントを正しくこなすように動いています。

チーム改善はチームメンバーが抱えている問題や、認識している課題(プロダクトやチームについて)の抽出を行い改善していく動きをしていきます。主に1on1などを通じて抽出していきます。

実はまだ任命されて日が浅いのでまだちゃんとできていないのですが、1sprintに一回は行う予定です。

チーム代表は、チームの代表として事業開発との折衝を行ったり、今後開発される機能の要件把握や、作られたバックログの精査検討を行います。

割とこの業務は重くて主にPOとともに開発項目を検討するのは骨が折れます。テックリード職に任命されてい今一番頑張ってるところです。

以上のチームに関する業務を6割行い、開発業務を残りの3割で行います。

まとめ

以上ざっくりと「カミナシにおけるテックリード職=@nasumの行っている業務」を書きました。採用:チーム業務:開発を1:6:3で行っていくイメージです。

まだ任命されて日も浅く、把握できている業務に限っているため、あくまで現時点でのテックリード職はこんな仕事をしてますよぐらいの紹介でした。来年には変わってるかも知れませんが、チームの仕事を7割というスタンスはおそらく変わらないと思います。

可能であれは他の会社のテックリードやテックリード経験者にお話を聞いてみたいところです。

次回こそはRaspberry Pik8sクラスタを作ります。

ABDでSCRUM BOOT CAMPの輪読会を開催しました

f:id:kaminashi-developer:20210419082430j:plain

カミナシのエンジニア@Takuです。

先日エンジニアチームにてABD(アクティブ・ブック・ダイアローグ)という形式で輪読会を開催したところ、とても良かったので共有させていただこうと思います。

はじめに ABD(アクティブ・ブック・ダイアローグ)とは

私もこの度初めて知りましたので簡単にご紹介

読書手法の1つでして、やり方を簡単に書くと、

1. 1冊の本を参加者で分担して読んでまとめる
2. まとめた内容を発表
3. 読んだ・発表を聞いた際に得た気づきを共有・対話する

といったものとなります。

公式サイトの説明は以下のようにありましたので、こらも一読いただくとよりイメージがつきやすいかと思います。

アクティブ・ブック・ダイアローグ®は、読書が苦手な人も、本が大好きな人も、短時間で読みたい本を読むことができる全く新しい読書手法です。

1冊の本を分担して読んでまとめる、発表・共有化する、気づきを深める対話をするというプロセスを通して、著者の伝えようとすることを深く理解でき、能動的な気づきや学びが得られます。

またグループでの読書と対話によって、一人一人の能動的な読書体験を掛け合わせることで学びはさらに深まり、新たな関係性が育まれてくる可能性も広がります。アクティブ・ブック・ダイアローグ®という、一人一人が内発的動機に基づいた読書を通して、より良いステップを踏んでいくことを切に願っております

輪読会開催のきっかけ

開発チームの課題を解決するための勉強会を開催したいというのが発端です。

カミナシでは 開発をスクラムを用いて実施していますが、我流で実施している部分が多くありました。 現在はそこの是正に取り組んでおり、スクラムマスターの指導の元、一度基本に立ち返るための勉強会を開催したいとなり、SCRUM BOOT CAMPを読むこととなりました。 www.shoeisha.co.jp

輪読会をABDをやってみる

輪読会ですが、皆さん普段の業務等あり事前に読む形式だと難しいよねということで、準備のいらないADB形式で開催することとしました。

実際にどのような形でやったかというと以下のような形になります。

1. 1時間枠を確保
2. 最初30分で各自割り振られた箇所を読む
3. 残り30分で発表・気づきの対話を行う

1つの会でどこまでやるかは参加者の人数によって決め、今回のSCRUM BOOT CAMPですと1人3章を割当、残りの章は次回に回すようにしておりました。

また、輪読会は業務時間中に実施してますが、各自のタスクや予定もあるため基本的に任意参加としております。

現在は週1〜2回の出社日を除いてリモート勤務となっているため、輪読会もリモートで、

  • 開始・発表はmeet
  • まとめはNotion

を利用して実施しました。

ABDをやってみて

ABDをやってみて良いと感じた点は以下です。

  • 事前準備が不要で参加することができるのでハードルが低い
  • 読む時間が決まっているのと、発表しないといけないので短時間で集中して本が読める
  • チームでやることで、現在できているところ・できていないところ、自チームでこれは使える・・などのケーススタディのような議論ができる

上記はABDの公式サイトにも記載されていることですが、実際にやってみて改めて実感することができました。

今回Notionを使ってまとめを行ったのも良かったと思い、リアルタイムでコメントをつけながら対話することができてやりやすかったです。

f:id:kaminashi-developer:20210419004937p:plain
Notionでのまとめ
※ただし、コメントはResolveしてしまうと消えてしまうので押さないように注意

個人的にすごく良かったのがやはり発表後の対話でして、 自分たちはどうやってる?どうしたら良い?といった議論ができるため一人で本を読むだけでは得られない学びが得られました。

メンバー間での知識の平坦化や、考えの共有の場にも使えると思いました。

逆に物足りなかった点としては、やはり自分が読まなかった部分は要点を聞くのみなので、また時間のある際に個人でも読み返したいなと思いました。

ABDをやってみて

最近の新たな取り組みとして輪読会をやってみたところ学びが多かっため、引き続き様々なテーマの本を元に週1ぐらいのペースでやっていきたいと思っています。

カミナシ では次のテーマとして、

  • テスト手法
  • UI / UX

などが挙がっておりまして、チーム内で課題として挙がっているものについてについて皆で学んでいけたらいいなと思ってます。

最後までご覧いただきありがとうございました。 輪読会やってみたいけどどのようにすれば良いかわからない・・といった方の参考になれば幸いです。

Expo ReactNativeのStorybookを0から入れ直す

こんにちは、カミナシの@tomiです。

本日は、ExpoでのStorybookの導入について書いていきます。

StorybookのサイトにReactNativeでの導入方法が記載されていますが、それ通りだけでは上手く動かなかったので、ExpoでStorybookを使いたい人の参考になればと思います。

なぜStorybookを入れようと思ったのか

現在カミナシのデザインチームでは、ブランドガイドラインを固めてくれています。それを基に新たに0からデザインシステムを作り、現行デザインをリプレイスする予定です。

デザインシステムができてからエンジニアが動き出しては、時間がなくて殴り書きのコードができあがってしまうのが、予想できます。。

せっかくデザインシステムを作ったのに、それが守られなかったりデザインシステムに振り回されては本末転倒なので、今のうちから準備しておこうというのと、Storybook使ったことなかったので勉強のために導入してみました。

さよなら古のStorybook・・・😭

以前にも導入した形跡がありましたが、1年以上前からメンテナンスが止まっています。

どうやらまだサービスがローンチされる前に、モックとしてお客様にお見せするために使っていたようです。

コードの中身も大分変わってしまい動かなくなっていたので、Storybookをまるっと入れ直したいと思います。

f:id:kaminashi-developer:20210406085003p:plain

ようこそNew Storybook

ベースはこちらドキュメントに沿って、導入していきます。

https://storybook.js.org/tutorials/intro-to-storybook/react-native/en/get-started/

npx -p @storybook/cli sb init --type react_native

早速パッケージをインストールしようとしたら、エラーが発生。

f:id:kaminashi-developer:20210406085142p:plain

yarnのバージョンが1.22.4だとエラーが起こっているぽかったので、1.19.0に落としてインストールを進める。

yarn policies set-version 1.19.0

npx -p @storybook/cli sb init --type react_native

こちらのバージョンでは問題なくインストールできました。

yarn policiesで追加された変更(.yarn/releases.yarnrc内のyarn-path)を削除して、元の1.22.4のバージョンでに戻して、再度パッケージをインストールする。

rm -f .yarn/releases
rm -f .yarnrc
yarn

無事サンプルのStoryBookが追加されました。

f:id:kaminashi-developer:20210406085212p:plain

サンプルが動くか確認するため起動しようとしたが、エラー発生。

yarn storybook

f:id:kaminashi-developer:20210406085244p:plain

https://github.com/storybookjs/storybook/issues/5919と似ていたので、@storybook/theming をインストールする。

バージョンも他の依存パッケージと合わせる。

yarn add -D @storybook/theming@^5.3

改めて起動。

yarn storybook

f:id:kaminashi-developer:20210406085447p:plain

無事起動したかと思いきや、、、なにかおかしい。

サイドメニューが表示されない。

f:id:kaminashi-developer:20210406085505p:plain

アプリも起動しないといけないようでした。

yarn start

f:id:kaminashi-developer:20210406090432p:plain
メニューは表示されたが、右側の表示が切り替わらない。。

スマホに依存しているパッケージが入っていてExpo Webで起動できないのが原因で、ブラウザでは確認できないのかも。

アプリでStorybookを確認する

どうやらアプリ側のApp.tsxで、Storybookの自動生成されたコンポーネント./storybook/index.jsを呼び出すと、アプリで確認が可能になるようなので試してみます。

環境変数でStorybookを起動するかアプリを起動するかを切り替えられるようにする↓

STORYBOOK_ENABLED=1 yarn start --ios

app.config.js

export default {
  extra: {
    storybookEnabled: process.env.STORYBOOK_ENABLED,
  },
}

App.tsx

import Storybook from './storybook'
...
export default Constants.manifest.extra.storybookEnabled ? Storybook : App

さらに、Expoを使っているので./storybook/index.jsのコメントに書いてある通り少し編集します。

2箇所、Expoでは不要なコードがあるのでコメントアウトしておく。

// if you use expo remove this line
// import { AppRegistry } from 'react-native';

// If you are using React Native vanilla and after installation you don't see your app name here, write it manually.
// If you use Expo you should remove this line.
// AppRegistry.registerComponent('%APP_NAME%', () => StorybookUIRoot);

これでStorybookを起動。

STORYBOOK_ENABLED=1 yarn start

無事シミュレータ内で起動しました!

f:id:kaminashi-developer:20210406085524p:plain

welcomeページから切り替わらないよ、という場合はエディタで適当な*.stories.jsを開いて保存すると表示されるようになるかと思います。

また、ブラウザで開かれている方は相変わらず表示されませんが、サイドメニューを操作するとアプリ側にも同期されるので、プロパティの変更などには使えます。

f:id:kaminashi-developer:20210406090500p:plain
ブラウザでHello KAMINASHI !と変更したものがシミュレータに反映された

最後に

今回は一旦起動まで。

storyディレクトリに*.stories.js があると絶対にメンテナンスされなくなるので、コンポーネントが置かれている場所に置いて管理するのと、StoryShotsで自動スナップショットテストの導入などもやりたかったです。

yarnのバージョンによってはインストールできなかったり、start-storybookが動かずドキュメント通りにはいかなかったり、想定よりも導入に時間がかかってしまいました。

今度改めて、より使いやすいStorybookを目指して改良していこうと思います。

(書いてる途中で思ったけど、Expoのフォルダ内で管理するよりも、WebUIとアプリUI用のリポジトリを作成して、そこにStorybookを導入した方が良いかも。)