みずぴー日記

人間の再起動ボタンはハワイのビーチにある

🚀bitrise.io for macOS app

macOSアプリのCIとしてbitrise.ioを使いはじめた。

❌ 署名エラー

初期状態でテストを実行すると、アプリケーションに署名するための証明書がbitrise.ioに登録されていないため、エラーとなる。

❌  error: No signing certificate "Mac Development" found: No "Mac Development" signing certificate matching team ID "XXXX" with a private key was found. (in target 'Tests')

✨証明書の設定

デバッグビルドで利用する Mac Developer証明書は、開発用のデバイスで動かすための証明書なので、CI上で利用できない。 そこで、Mac App Store以外でアプリケーションを配布するための Developer ID Application証明書を利用する。 (参考: Managing Your Developer Account Team)

具体的にやったことはhttps://github.com/mzp/EmojiIM/pull/5にまとめてある。

手順1: "Developer ID Application"で署名する。

automatic signingを無効にし、"Developer ID Application"証明書で署名するよう設定する。同じ設定をテスト用のターゲットに対しても行なう。

f:id:mzp:20171009114602p:plain

手順2: 秘密鍵をbitriseにアップロードする。

XcodeのAccountsタブから、"Developer ID Application"証明書をエクスポートする。

f:id:mzp:20171009122128p:plain

パスワードを設定する。

f:id:mzp:20171009114753p:plain

エクスポートされたp12ファイルをワークフローエディタからアップロードする。エクスポート時に指定したパスワードもここで指定する。

f:id:mzp:20171009114835p:plain

手順3: リリースビルドでテストする

リリースビルドに対してテストを行なうようScanfileで設定する。

workspace 'EmojiIM.xcworkspace'
scheme 'EmojiIM'
configuration 'Release'

手順4: Bitriseでfastlaneを使う

fastlane tools integration - Bitrise DevCenterにあるようfastlane stepをworkflowに追加する。

🏷 その他

"code object is not signed at all" errorエラーの回避

2回に1回くらい codesign コマンドが"code object is not signed at all"というエラーで失敗する。 詳細は分からないがStack Overflowにあるように--deepフラグを渡したら解決した。

f:id:mzp:20171009115258p:plain

デバッグ時にautomatic signingを使う

Build settingsの Code signing style から指定すれば、デバッグ時にのみautomatic signingを使える。

f:id:mzp:20171009115239p:plain

💥様子

30回くらい失敗を連続させた。

f:id:mzp:20171009120556p:plain

⚡️ReactiveInputMethod

🍣入力メソッドを拡張し、テキストを入力し、Enterで確定できるようにした。

f:id:mzp:20171001221958g:plain

コード

https://github.com/mzp/EmojiIM/tree/marked

未確定文字列の挿入

未確定文字列は、入力セッションの一部としてマークされている文字列なのでmarked textと呼ばれる。

IMKTextInput プロトコルsetMarkedText で設定できる。例えば"あいうえ"を未確定文字列として表示したい場合は、以下のコードになる。

let notFound = NSRange(location: NSNotFound, length: NSNotFound)
client.setMarkedText("あいうえ", selectionRange: notFound, replacementRange: notFound)

クリアしたい場合は第一引数に空文字列を渡す。nilを渡すとクラッシュする。

状態遷移

未確定文字列の入力した後、入力を確定するためには、入力メソッド内で状態遷移を管理する必要がある。

未確定文字列を持たない初期状態と、未確定文字列を持つ入力中状態があり、キー入力とEnterキーの入力で遷移するので 状態遷移図は以下のようになる。

f:id:mzp:20171001222314p:plain

ReactiveAutomaton

状態遷移を記述するためにReactiveAutomatonを用いる。

これはReactiveCocoaで状態遷移機械を書くためのライブラリである。アイデアは以下のスライドで解説されいる。

このライブラリを使い、入力メソッドの状態遷移および遷移時のアクションを定義する。

入力

ユーザの入力はテキスト入力とEnterの押下の2種類とする。 以下のenumで定義する。テキスト入力は入力された文字列をパラメータに持つようにする。

public enum UserInput {
  case input(text: String)
  case enter
}

状態

入力メソッドの状態は「通常状態」と「入力中状態」の2種類とする。以下のenumで定義する。

public enum InputMethodState {
  case normal
  case composing
}

遷移

ReactiveAutomatonのDSLを用いて状態遷移を定義する。

static func isInput(_ state: UserInput) -> Bool {
  switch state {
  case .input:
    return true
  default:
    return false
  }
}

let mappings: [ActionMapping<InputMethodState, UserInput>] = [
  /*  Input <|> fromState => toState */
  /* --------------------------------*/
  isInput <|> .normal => .composing,
  isInput <|> .composing => .composing,
  .enter  <|> .composing => .normal
]

遷移が発生した際に実行するアクションを指定できるようにDSLを拡張する。 (ReactiveAutomaton+Action.swift) そして、遷移時のアクションで確定文字列、未確定文字列を更新する。

let (text, textObserver) = Signal<String, NoError>.pipe()
let markedTextProperty = MutableProperty<String>("")

let mappings: [ActionMapping<InputMethodState, UserInput>] = [
  /*  Input <|> fromState => toState <|> action */
  /* -------------------------------------------*/
  isInput <|> .normal => .composing <|> {
    switch $0 {
    case .input(text: let text):
      markedTextProperty.swap(text)
    default:
      ()
    }
  },
  isInput <|> .composing => .composing <|> {
    switch $0 {
    case .input(text: let text):
      markedTextProperty.modify { $0.append(text) }
    default:
      ()
    }
  },
  .enter <|> .composing => .normal <|> { _ in
    textObserver.send(value: markedTextProperty.value)
    markedTextProperty.swap("")
  }
]

この状態遷移をもとに、オートマトンを作る。

let (inputSignal, observer) = Signal<UserInput, NoError>.pipe()
self.automaton = Automaton(state: .normal, input: inputSignal, mapping: reduce(mappings))

入力コントローラとの接続

イベントの処理

オートマトンと入力コントローラを接続するために、オートマトンにイベントを送るメソッドを作る。 入力コントローラの仕様にあわせ、状態遷移が発生した場合は真を、そうでない場合は偽を返すようにする。

init() {
  ...
  automaton.replies.observeValues {
    switch $0 {
    case .success:
      self.handled = true
    default:
      ()
    }
  }
}

func handle(_ input: UserInput) -> Bool {
  handled = false
  observer.send(value: input)
  return handled
}

入力コントローラで、キー入力に応じて、このメソッドを呼びだす。

public override func handle(_ event: NSEvent!, client sender: Any!) -> Bool {
  if event.keyCode == 36 {
    return automaton.handle(.enter)
  } else if event.keyCode == 51 {
    return automaton.handle(.backspace)
  } else if let text = event.characters {
    return automaton.handle(.input(text: text))
  } else {
    return false
  }
}

未確定文字列・確定文字列の反映

オートマトンが持つ入力文字列、未確定文字列を監視し、更新があった際にクライアントアプリケーションに反映するようにする。これはReaciveSwiftのイベント監視の仕組みを用いる。

public override init!(server: IMKServer!, delegate: Any!, client inputClient: Any!) {
  super.init(server: server, delegate: delegate, client: inputClient)

  guard let client = inputClient as? IMKTextInput else {
    return
  }
  // 未確定文字列の反映
  automaton.markedText.signal.observeValues { text in
    let notFound = NSRange(location: NSNotFound, length: NSNotFound)
    client.setMarkedText(text, selectionRange: notFound, replacementRange: notFound)
  }
  // 確定文字列の挿入
  automaton.text.observeValues {
    let notFound = NSRange(location: NSNotFound, length: NSNotFound)
    client.insertText($0, replacementRange: notFound)
  }
}

🔬Appleの非公開APIの調べ方

入力メソッドからタッチバーを使う方法を調べるために使った各種ツールの使い方をメモしておく。

class-dump

実行ファイルからObjective-Cのヘッダファイルを生成する。 homebrewでインストールできた。

$ class-dump /System/Library/Frameworks/InputMethodKit.framework/InputMethodKit
//
//     Generated by class-dump 3.5 (64 bit).
//
//     class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2015 by Steve Nygard.
//

.....

@interface IMKServer : NSObject <IMKServerProxy>
{
    IMKServerPrivate *_private;
}

+ (id)inputDelegateClassNameFor:(id)arg1;
+ (id)inputControllerClassNameFor:(id)arg1;
+ (id)connectionNameFor:(id)arg1;
+ (id)_clientWrapperForXPCConn:(id)arg1;
+ (id)_clientWrapperForDOProxy:(id)arg1;
+ (id)imkServerSingleton;
- (id)keyBindingManager;
...

lldb

class-dumpでは、だいたいの箇所がid型になってて詳細な使い型がわからない。 そのライブラリを使っていそうなプロセスにlldbでattachして使い方を調べた。

まずはSIPが有効だとプロセスにattachできないので、Mac OS X El CapiptanでSIPを無効化する - Qiitaなどを参考に無効化する。

目的のプロセスを調べて、attachする。 入力メソッドにattachする場合はそのマシンの入力が死ぬので、別マシンからsshするといい。

$ ps ax | grep '[E]mojiFunctionRowIM'
  402   ??  Ss     0:03.51 /System/Library/Input Methods/EmojiFunctionRowIM.app/Contents/PlugIns/EmojiFunctionRowIM_Extension.appex/Contents/MacOS/EmojiFunctionRowIM_Extension
$ lldb
(lldb) attach 402

あとは各種コマンドを使って挙動を調べていく。 だいたい以下のコマンドを使った。

ブレイクポイントの設定

特定のセレクタにブレイクポイントを設定する。

br set --selector inputDelegateClassNameFor:

特定のクラスの全メソッドにブレイクポイントを設定する。

br set -r '\[ClassName .*\]$'

表示

# オブジェクトの表示
po $obj

# 値の表示
p (BOOL)[$obj isHoge]

メソッド呼び出し時に使われるレジスタ

  • self: $rdi
  • _cmd $rsi

引数

  1. $rdx
  2. $rcx
  3. $r8
  4. $r9
  5. スタックにつまれる。 po ((id)$rsp+1)

変数への代入

expr id $foo = [$obj foo]

otool

それでも分からないやつは逆アセンブルした。 どのメソッド呼んでいるかなどを主に見た。

$ otool -tV /System/Library/Frameworks/InputMethodKit.framework/InputMethodKit
/System/Library/Frameworks/InputMethodKit.framework/InputMethodKit:
(__TEXT,__text) section
-[IMKUICandidateController init]:
0000000000001d0c        pushq   %rbp
0000000000001d0d        movq    %rsp, %rbp
0000000000001d10        pushq   %r15
0000000000001d12        pushq   %r14
0000000000001d14        pushq   %r12
0000000000001d16        pushq   %rbx
0000000000001d17        subq    $0x10, %rsp
0000000000001d1b        leaq    -0x30(%rbp), %rax
0000000000001d1f        movq    %rdi, (%rax)
0000000000001d22        movq    0xd6fc7(%rip), %rcx ## Objc class ref: IMKUICandidateController
0000000000001d29        movq    %rcx, 0x8(%rax)
0000000000001d2d        movq    0xd2dc4(%rip), %r14 ## Objc selector ref: init
0000000000001d34        movq    %rax, %rdi
0000000000001d37        movq    %r14, %rsi
0000000000001d3a        callq   0x89b5c ## Objc message: -[[%rdi super] init]
0000000000001d3f        movq    %rax, %rbx
0000000000001d42        testq   %rbx, %rbx
0000000000001d45        je      0x1de9
....

🐙Githubブラウザ for iOS11

iOS11で追加されたFilesアプリから、Githubレポジトリを見るための拡張を作った。 AppStoreから入手できる。

OctoEye

OctoEye

  • HIROKI MIZUNO
  • Productivity
  • Free

Github->Octocat->Octopusという連想で、OctoEyeという名前にした。

⭐️使い方

OctoEyeをインストール・初期設定をすると、FilesのLocationsにGithubが追加される。

f:id:mzp:20170913210709p:plain

ここからレポジトリの内容をできる。

f:id:mzp:20170913211021p:plain

さらに、Textasticといった別アプリから開くこともできる。 ただし、保存はサポートしていない。

f:id:mzp:20170913211049p:plain

📦ソースコード

Githubブラウザなので、Githubに置いてある。

http://github.com/mzp/OctoEye

🎨デザイン

Sketch

最近Sketchを買ったので、事前にデザインを描いた。

f:id:mzp:20170913220733p:plain

今までは適当な紙に落書きするだけだったが、Sketchで描くと考慮漏れに気付けたり、単純に楽しかったりしてよかった。

アイコン

アイコンは最初いらすと屋のタコを使い、途中で自分で描いたのに差し替えた。

f:id:mzp:20170913212158p:plain

GithubなのでOctocatを使いたかったが、そのものを使うわけにはいかないので、一部だけ+シルエットにした。 猫耳だけを拡大したバージョンも作ったが今ひとつだったので段ボールバージョンを採用した。

f:id:mzp:20170913212447p:plain

ウォークスルー

初回起動時に出すウォークスルーを作った。 これは結構大変だった。

EAIntroViewの動きの確認。

iPadProで下書きをした。

f:id:mzp:20170913213039p:plain

雰囲気が分かったのでSketchでいろいろ描いて、デザインを検討した。

f:id:mzp:20170913213411p:plain

実装した。

f:id:mzp:20170913213450p:plain

🛠開発

Github Graphql

Githubから情報を取得するために、GraphqlAPIを利用している。 FileProviderExtensionではネットワークアクセスの回数を減らしたかったのでちょうどよかった。

ただバイナリデータを取得することができなかったので、そこだけはREST APIを使っている。

ReactiveCocoa/ReactiveSwift

レポジトリ追加画面などではReactiveSwiftを使っている。最初はRxSwiftを使おうとしたが、@にReactiveSwiftのほうが語彙がSwiftっぽいと言われたので、ReactiveSwiftにした。

イベントをmap等で加工するのは分かるが、画面上の要素とバインドする部分がよく分かんないまま書いていた。

スクリーンショット

AppStoreに提出用のスクリーンショットはfastlaneのsnapshotとframeitで生成した。

snapshotで各デバイス用のスクリーンショットを準備し、frameitでiPhone/iPadの枠をつけ上部に文字を入れた。

Xcode9からUITestが複数アプリに対応したので、Filesアプリの撮影も自動化できた。(がMetadata Rejectになったので、このスクショは削除した)

f:id:mzp:20170913220059p:plain

その他

  • CIはTravisCIを使った。 Xcodeの新しいベータが出て3〜4日でTravis側も更新されててすごかった。
  • ベータ版のOSにはスクリーンショットを外部に出せない制約が付くので、プルリクエストに画像を貼れなかった。

🚀今後の予定

  • Filesにあるタグ機能やお気に入り機能をサポートしてないのでやりたい。
  • masterブランチ以外は見れないので、切り替え機能をつけたい。
  • 書き込み機能もつけたいが、コミットメッセージをどうするかが難しいと思っている。

🍣入力メソッド

すべてのキー入力に対して🍣を入力する入力メソッドを作った。

コード

https://github.com/mzp/EmojiIM

入力メソッドの構成

入力メソッドによるテキスト入力は、各アプリケーションと入力メソッドが通信することで実現されている。アプリケーションはキー入力を入力メソッドに送信し、入力メソッドがアプリケーションに対してテキスト入力を行なう。

この通信のために入力メソッドはIMKServerクラスを用いてサーバーを起動する。そして各アプリケーションはクライアントとなり、サーバーと通信する。この通信を入力セッション(input session)と呼ぶ。

サーバーはクライアントごとに入力コントローラーを生成する。この入力コントローラーがキー入力からテキスト入力を生成する。

f:id:mzp:20170917183745p:plain

プロジェクトの作成

Xcodeでプロジェクトを作成する。Cocoa Appテンプレートを選ぶ。 Product nameはなんでもいいが、Bundle identifierにinputmethodという文字列を含める必要がある。

f:id:mzp:20170917183932p:plain

起動時の処理

入力メソッドが起動時に各アプリケーションと通信するための IMKServer を起動する。このとき、クライアントがサーバーに接続する際に使う名前を指定する。

import Cocoa
import InputMethodKit

private var server: IMKServer?

@NSApplicationMain
internal class AppDelegate: NSObject, NSApplicationDelegate {

    @IBOutlet private weak var window: NSWindow!

    func applicationDidFinishLaunching(_ aNotification: Notification) {
        server = IMKServer(name: "EmojiInputSession", bundleIdentifier: Bundle.main.bundleIdentifier)
    }
}

入力コントローラ

入力コントローラとして使うクラスを定義する。このクラスは IMKInputController の派生クラスにする必要がある。

このクラスの名前をInfo.plistで指定す。そのため、objc 属性を用いて、Swiftコンパイラによってマングリングされないようにする。

@objc(EmojiInputController)
open class EmojiInputController: IMKInputController {

キー入力時に inputText メソッドが呼ばれる。キー入力を処理したかどうかを真偽値で返す。

func inputText(_ string: String!, client sender: Any!) -> Bool

テキスト入力をしたいアプリケーションは client 引数として渡される。 ただし、 Any 型のままではテキスト入力を行なえないのでIMKTextInput プロトコルにキャストする。 キャストに失敗した場合は、入力メソッドが処理しなかったことを示すために偽を返す。

guard let client = sender as? IMKTextInput else {
  return false
}

IMKTextInputinsertText メソッドを用いて、入力したい文字列を挿入する。

client.insertText("🍣", replacementRange: NSRange(location: NSNotFound, length: NSNotFound))

Info.plist

Info.plistで入力セッションに使う名前や入力コントローラとして使うクラスの名前を指定する。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleDevelopmentRegion</key>
    <string>$(DEVELOPMENT_LANGUAGE)</string>
    <key>CFBundleExecutable</key>
    <string>$(EXECUTABLE_NAME)</string>
    <key>CFBundleIconFile</key>
    <string></string>
    <key>CFBundleIdentifier</key>
    <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>$(PRODUCT_NAME)</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>1.0</string>
    <key>CFBundleVersion</key>
    <string>1</string>
    <key>LSBackgroundOnly</key>
    <string>YES</string>
    <key>LSHasLocalizedDisplayName</key>
    <false/>
    <key>LSMinimumSystemVersion</key>
    <string>$(MACOSX_DEPLOYMENT_TARGET)</string>
    <key>NSHumanReadableCopyright</key>
    <string>Copyright © 2017 mzp. All rights reserved.</string>
    <key>NSMainNibFile</key>
    <string>MainMenu</string>
    <key>NSPrincipalClass</key>
    <string>NSApplication</string>
    <!-- 入力セッションに使う名前 -->
    <key>InputMethodConnectionName</key>
    <string>EmojiInputSession</string>
    <!-- 入力コントローラのクラス名 -->
    <key>InputMethodServerControllerClass</key>
    <string>EmojiInputController</string>
    <!-- 入力メソッドのアイコンとして使う画像 -->
    <key>tsInputMethodIconFileKey</key>
    <string>InputMethodIcon.tiff</string>
    <!-- 入力ソース(入力ソースの詳細はよくわからん) -->
    <key>TISIntendedLanguage</key>
    <string>ja</string>
   <!-- 入力ソースに対する識別子 -->
    <key>TISInputSourceID</key>
    <string>jp.mzp.inputmethod.EmojiIM</string>
    <!-- 入力メソッドが対応している言語のリスト -->
    <key>tsInputMethodCharacterRepertoireKey</key>
    <array>
        <string>Hira</string>
        <string>Kana</string>
        <string>Latn</string>
    </array>
</dict>
</plist>

インストール

ビルドして生成された EmojiIM.app~/Library/Input Methods にコピーする。Workspace settingsからDerivedDataをworkspace relativeにしておくと楽。 こんな感じでコピーする。

cp -r DerivedData/EmojiIM/Build/Products/Debug/EmojiIM.app ~/Library/Input\\ Methods

System PreferencesのInput Sourcesから追加できる。うまく反映されない場合は、いったんログアウトするとよい。

f:id:mzp:20170917185308p:plain

🙋つらい時に挙げる札

こういうのを作った。元ツイートが消えているので出典が示せないが、 死にたいにゃんシールが一番近いと思う。

f:id:mzp:20170715121106j:plain

🛒材料

Seriaでそれっぽいのを買った。

  • PPシート 不透明
  • 家具用のアルミパイプ

f:id:mzp:20170715112541j:plain

✂️制作風景

TextEditで縦書きテキストの画像を作った。フォントはヒラギノ明朝が一番それっぽかった。

f:id:mzp:20170827120227p:plain

この画像を調整して紙に印刷した。

f:id:mzp:20170715114718j:plain

PPシートに貼って、札の形に切った。両面テープがあると思ったらなかったので、糊で貼りつけた。

f:id:mzp:20170715120553j:plain

アルミパイプをガムテープで貼り付けて完成。

f:id:mzp:20170715121150j:plain

🚨利用例

作ったはいいけど使い道がなかったのでオフィスに持っていったら、肥大化したCSSを修正するときに使われていた。 「我々のオフィスにはカンバンはあるがアンドンはなかった。それがこれにより補完される」とかいう話をしてた気もする。

📝論文紹介: Software Development Waste(ソフトウェア開発におけるムダ)

ICSE 2017 勉強会のために論文を眺めてたら、リーンソフトウェア開発におけるムダについて調査した論文*1がおもしろそうだったので読んだ。

🐾背景: リーンソフトウェア開発

Womackはトヨタ生産方式を分析し、リーン思考を提案した*2。これは、「資源を消費し、価値を生まない活動」であるムダ(waste)の発見と除去を基本原則する。

リーンソフトウェア開発は、リーン思考とトヨタ生産方式をソフトウェア開発に適用した手法である。 リーンソフトウェア開発ではムダとして以下のものが挙げている。*3

トヨタ生産方式におけるムダ リーンソフトウェア開発におけるムダ
在庫 未完成の作業
加工しすぎ 再学習
作りすぎ 使われないコードや余分な機能
物の運搬 作業の引き継ぎ
手待ち 開発の遅れ
人の動作 作業の切り替え
不良・手直し 欠陥
価値 なし
活用されない才能 なし

しかし、ここでムダとされるものが妥当かを検証した研究はない。

🎯目的: ムダの発見

リーンソフトウェア開発には、どのような種類のムダが存在するかを調べる。

🔬調査方法: Pivotalにおける調査

Pivotalで行なわれたソフトウェア開発を対象とし、グラウンデッド・セオリーを構築した。

以下の3つの方法で、データを収集した。

  1. Pivotal Labのプロジェクトに参加し、観察した。 2年と5ヶ月間をかけて8個のプロジェクトに参加した。
  2. Pivotalの社員33人にインタビューを行なった。これにはソフトウェアエンジニア、インタラクションデザイナー、プロダクトマネージャが含まれる。
  3. レトロスペクティブ(ふりかえり)のトピックを分析した。 1年間の間に行なわれた91のミーティングを対象とした。

📊発見した9種類のムダ

調査の結果、9種類のムダを発見した。

誤った機能や製品の作成

誰も必要としない機能や製品を作ると、参加している全員の時間や労力が無駄になる。 これは、チームの士気やオーナーシップ、顧客満足度に影響する。

調査したプロジェクトにあった実例:

  • ペルソナにもとづいて開発をしたが、その後の調査でペルソナがあやまっていることがわかった。
  • マーケティングのために、ユーザの必要としない機能を追加した。

バックログ管理の失敗

プロダクトバックログの管理を失敗すると、プロジェクトの遅延や生産性の低下につながる。

調査したプロジェクトにあった実例:

  • 一度に複数の作業を進めることを優先したため、最初のリリースでは一部の作業が未完了のままだった。その機能は無効化されてリリースされた。
  • ビジネス側の要求に応じてユーザ登録プロセスを何度も変更したため遅延した。
  • プロダクトマネージャーが変更をくり返したため遅延した。

作業のやり直し

作業の遣り直しがムダなのは自明である。 これは以下の原因で発生する。

  • 技術負債。調査したプロジェクトの中には、リリース日を優先したため、リリース後に数週間にわたるやり直しが必要になったものがあった。
  • 作業中における欠陥の発。 調査したプロジェクトの中には、いくつかの画面を作ったのちに、モバイル対応が必要であることが発覚したものがあった。
  • ストーリーの拒否。実装が不十分のために、プロダクトマネージャーが受け入れを拒否することがある。
  • 完了条件が不明瞭なストーリー。調査したプロジェクトの中には、開発者が実装を完了したのち、ストーリーとモックアップにインタラクションが不足していることが判明したものがあった。

不必要に複雑な解決策

機能が不必要に複雑だと、ユーザの時間を浪費する。

調査したプロジェクトにあった実例:

  • 操作フローが、一部画面では左から右になっているのに、別の画面では上から下になっていた。
  • 複数のインタラクションデザイナーがいるため、レイアウト、リスト、警告、ボタンなどが複数作られた。
  • インタラクションデザイナーが2種類のフォームデザインを作ったために、複数のCSSが必要になった。
  • 顧客側の開発者が「複雑であればあるほど、重要な仕事をしていると実感できるのでよい」という態度でいることに開発者が不満をもらした。

本質でない認知負荷

認知負荷理論(Cognitive Load Theory)は、人間の作業記憶には限りがあるため、負荷が多すぎると学習や問題解決が阻害されるとしている。本質的な認知負荷とは、タスクをこなす上で避けられないものを指す。 一方で本質的でない認知負荷とは周囲の環境などに追加されるものを指す。*4

ソフトウェア開発には認知負荷が高いもの多いため、開発者の思考力は貴重な資源である。そのため、本質でない認知負荷はムダであるとみなす。 これは以下の原因がある。

  • 過度に複雑なストーリー。不必要に長く複雑で不明瞭なストーリーは開発者の作業記憶を消費してしまう。
  • 非効率なツール。 機能不足だったり複雑なライブラリや貧弱な開発環境、貧弱な開発プロセスなども含む。
  • 技術負債。 技術負債はコードの理解や変更を難しくする。 調査したプロジェクトの中には、テストスイートを実行すると大量の警告が出力され重要な情報が埋もれてしまうものがあった。
  • マルチタスク。複数のタスクを同時にやろうとすると、作業記憶に多数の情報を保持する必要があるため、認知負荷が増加する。

精神的苦痛

精神的苦痛は貴重なリソースである開発者を消費してしまう。 精神的苦痛は生産性の低下や燃え尽き症候群や欠勤、健康問題などにつながる。

調査したプロジェクトの中には、機能とリリース日が変更不能なプロジェクトがあった。 このプロジェクトでは、チームの士気や一体感が低下したり、メンバー間の問題解決に時間がかかった。

待機/マルチタスク

優先度の高い機能に着手できない場合、開発者は待ったり、優先度の低い機能に着手してしまう。 これはプロジェクトの遅延につながる。

調査したプロジェクトにあった実例:

  • 受け入れ環境が不安定だったため、プロダクトマネージャーが受け入れ作業を放置して別の作業を始めた。
  • ビデオ会議設備が不足しており待ちが発生した。
  • テストの実行に17分かかるため、開発者が別の作業をはじめてしまった。

知識の損失

知識の損失は、特定の知識をもったチーメメンバーがいなくなったときに発生する。 そして失なわれた知識を再獲得が必要になってしまう。

調査したプロジェクトの中には、全メンバーが入れ替わったプロジェクトがあった。 システムの理解に数ヶ月かかり、その間のベロシティはほぼ0になった。

非効率なコミュニケーション

非効率なコミュニケーションは不完全だったり誤解を産むコミュニケーションである。 チームの規模や非同期コミュニケーション、非対称なコミュニケーションが増えると、チームの生産性が低下する。

調査したプロジェクトにあった実例:

  • 移動に一時間かかるオフィスにチームが分割された。 リモートコミュニケーションを活用したが、次第にレトロスペクティブで議題にのぼるようになっていった。
  • ミーティングを特定の人が支配し、おとなしい人が意見を言えないようになった。
  • iOSチームが自身のプロセスにチームの決定をうまく反映できなかった。レトロスペクティブを繰替えすことで改善した。

🔁既存のリーンソフトウェア開発におけるムダとの比較

リーンソフトウェア開発のムダとされているものとは、以下の違いがあった。

  • 「引き継ぎのムダ」とされるものは観察できなかった。
  • 新規で 「不必要に複雑な解決策」「本質でない認知負荷」「精神的苦痛」「非効率なコミュニケーション」の4つを観察した。
  • それ以外は類似のものがあった。

まとめ

この論文ではソフトウェア開発におけるムダとその原因をエビデンスに基づいて示した。 そのために2年と5ヶ月をかけてデータを集め、グラウンデッド・セオリーを構築した。

その結果、既存のリーンソフトウェア開発におけるムダは支持されたが、いくつかの点で異なっていた。新規で4つ導入した一方、引き継ぎのムダは支持できなかった。

*1:Todd Sedano, Paul Ralph, and Cécile Péraire. 2017. Software development waste. In Proceedings of the 39th International Conference on Software Engineering (ICSE ‘17). IEEE Press, Piscataway, NJ, USA, 130-140. DOI: https://doi.org/10.1109/ICSE.2017.20

*2:J. P. Womack and D. T. Jones, Lean thinking: banish waste and create wealth in your corporation. Simon and Schuster, 1996.

*3:TABLE II: Comparison of Manufacturing Waste with Lean Software Development Wasteより引用

*4:http://miwalab.cog.human.nagoya-u.ac.jp/database/resume/2016-10-25.pdf を参考にした