リリースされていないコードをリリースすると価値が2倍以上になるという思想

「そういう価値観もあるよね」という生暖かい気持ちで読んで欲しい

すでにリリースされているコードの価値

リリースされたコードの価値はそのコードが達成すべき目標に対する指標で測る。例えば、売上やユーザー数、PVです。本来の目的にそった指標で測ることによって目的に最短で達成できると考えるからです。

リリースされていないコードの価値

f:id:akuraru:20200921121132j:plain 新しい機能は指標をどれだけ上昇されると予測されているかで評価されるべきです。この記事ではこの指標を予想評価値と呼ぶことにします。各機能は予想評価値と作成時間を考慮して、優先順位を決めます。

あとはリリースするだけのコード

 完全に出来上がっていてデプロイボタンを押せばリリースされるコードの価値は、リリースされていないので無価値とも言えるし、ほとんど予想評価値とも言えます。間をとって、予想評価値の半分の価値だとします。リリース日が決まっているとかあると思うが、デプロイすれば予想評価値が二倍になるので、リリース日にデプロイボタンを押す必要のない作り方をした方が素早く価値を高めれます。

まだアイディア

 予想評価値が付けれる程度の状態。まだ、作成時間も予想していない状態。

 コードとしては価値がある物は一切ないのでほとんど無価値だと言えます。

 アイディアがなければ良いプロダクトもないのでアイディアにはには別の価値を与えた方が良いです。

アイディアからリリースまで価値

 1つの機能を作るためには、複数のサブ機能を作り組み合わせることで作っていくと思います。サブ機能ごとに仕様を考えて作っていきます。仕様に沿ったものを作ってはマージしていきます。仕様からマージするまでの段階を経るごとに2倍以上の価値を持つと考えています。例えば、レビューされているコードはレビューされていないコードの2倍以上の価値があし、マージされいるコードはレビューされているコードの二倍以上の価値があります。 f:id:akuraru:20200921122602j:plain

この思想の意味

 アイディアからリリースするまでに大切なこととしては、早く作りリリースすることです。段階を分割することによって各段階の時間を計測して最適化することができます。各段階の価値は後半になっていくにつれ増大します。新しく機能を作り始めるよりも、作りかけのものをリリースする方が価値が高いということです。後半の作業は価値が高いため自動化の価値が高いです。例えば自動デプロイや自動テストをすることで、自動的にコードの価値の増加させることができます。

 サブ機能についても同じです。新しいコードを書くよりもレビューすることの方が価値が高く、レビューを簡単にするための努力は価値があるということになります。早くレビューを通すために分割や説明文を書くことは価値があります。フォーマッタやlinter、自動テストによる動作の保証はすることはレビューの妨げの排除は価値があります。

まとめ

  • リリースするまでの時間を短くすることは価値
  • リリースするための段階は後半になるにつれ価値が高くなる
  • レビューやレビューしやすくすることは価値が高い。

モバイルクロスプラットフォーム開発の夢と現実

iOSAndroidをまとめて開発したいという要望があり、その夢と現実の歴史。ゲームは知りません。

3行まとめ

  • 流行りと衰退を繰り返している
  • それぞれの差分が辛い
  • Flutterが流行りつつある(?)

ネイティブアプリ

iPhoneAndroidが2007年に発表され、今後の携帯電話のスタンダードになっていくことが予見された。当時の開発言語はObjective-CJavaであり、ほぼ同じロジックを二つの言語で書く必要があった。現在はSwiftとKotlinに取って代わられつつあるが2つの言語で書く必要があるのは変わらない。面倒くさいよね。

クロスプラットフォーム

  • 理論上、半分の時間でできる。OSごとで何かあって半分にはならない程度
  • OSのアップデート等で不具合が発生したりするのはネイティブでも同じだが、プラットフォームがアップデートしないと対応できない場合がある
  • 早くなる分人間より安い程度の利用料がかかったりする。

RubyMotion

Rubyでかけるやつ。iOS 13.2. Android 8.1が最新なので察してあげてください。

Adobe AIR

Flashをベース。AcrionScript3+MXMLで開発ができる。Flash職人がアプリ開発に使っていたという印象があります。一時期の郵便局年賀状アプリや初期の艦これAndroid版はAirで動いていたらしい。HARMAN に移行され、HARMANに連絡しないとアップデートが受けられないという実質的な死を迎えました。Apache Flexとその派生のApache Royaleもありましたが、Apache財団に移管済に移管済みだそうです

Titanium Mobile

JSでかけるやつ。最古参でまだ定期的にアップデートされている。最近は聞かない。

Delphi

よく知らない。Object Pascal言語(Delphi)で書くらしい。アップデートはあるようだけど、使っている話はあまり聞いたことがない Delphi: 概要 - エンバカデロ・テクノロジーズ

Ratchet

珍しくOSS。WebView 動く。5年前からアップデートがない。 Ratchet

Phone Gap系

Phone GapはAdobeが次世代Flashになると見込んで開発元を買収した。 WebViewでUI作って、ネイティブの機能をブリッジして使えるようにしたもの。当初Web系の人たちの使いやすさもあって流行ったが、iPhoneAndroidのギャップにやられる。Phone GapはApache Cordovaとして寄贈され、Phone Gapとしての終焉を迎えました。 CordovaからはMonacaに派生があります。 IonicはCordvaをベースに独自のプラットフォームを構成し、今はCordva相当を自作して置き換えた。

SCADE

Swiftでクロスプラットフォームの開発を行えるようにしたもの。 チェンジログが3年前のベータ版リリースだったり、動きが全く掴めない。少なくとも流行ってはなさそう。

SCADE

Xamarin

Xamarinはマイクロソフトが開発した。 C#で開発できる。マイクロソフトは激推ししているのでしばらくは死にそうにない。接触確認アプリで使われている。ちゃんと動く印象。C#に使い慣れている人が多ければ採用される可能性はある印象。 Windows Phoneなんてものもありました。

Xamarin ドキュメント - Xamarin | Microsoft Docs

React Native

React NativeはFacebookが開発した。 JSCかhermesを通してNative UIでレンダリングしてる。Reactはが得意ならあり得る。WebでいいならアプリとしてリリースせずにWebにしとけばいいじゃね?

React Native · A framework for building native apps using React

Flutter

FlutterはGoogleが開発している。 Dartで開発する。どうしてもGoogleのアプリっぽくなる。ここ最近(2020年8月)は割と使われているらしいということを聞く。

Flutter - Beautiful native apps in record time

UIはないタイプ

UIはそれぞれの言語で書いて、ドメインモデルなどの共通のロジック部分は一つの言語で各タイプ。

C言語

JavaからもObjective-Cからも無理なく呼び出せる。実際使っているという話は聞いたことがない。どこかにはいそう。

Kotlin Native

Kotlinで書けるのでAndroidはもちろん動く。iOSの方はなんかいろいろやって、frameworkに固めてインポートできるようにしているらしい。

その他いろいろ

C言語と同じく、JavaからもObjective-Cからも呼び出せれば何でもいいので、かなりの数の言語が理論上はできるということになっている。実際のアプリでやっているのはごく少数だとは思う。

私感

5年以上栄えているクロスプラットフォームは今のところなく、繁栄と衰退を繰り返している。完全に死んでいるのは少ない。最近流行っている物はわからないが、正直長く続けるとわかってるならネイティブで開発することをお勧めする。スタートアップで高速でリリースしなければいけないときや、どうしてもアプリエンジニアを採用できない時、Xamarin,React Native,Flutterが今までの夢の歴史を終わらせれると思った時などに採用すべきだろう。

アジャイルと目標設定

4行まとめ

  • 本人の納得して、明確で高い難易度と高速なフィードバックがある目標設定が効果的
  • 人事評価は知らん
  • アジャイル宣言の「計画に従うことよりも変化への対応」を思いだせ
  • アジャイルなら適切な長さの目標設定を考えて欲しい

目標設定とは

モチベーション向上につながる 目標の存在はモチベーションを上昇させ、パフォーマンスを上げます。 1960年代にEdwin Lockeが提唱した、Goal-settig theory of motivationにおいて、目標設定は業務へのモチベーションを高く保ち、パフォーマンスを向上させるために必要不可欠とされています。Goal Setting Theory of Motivation

事実、金銭的なインセンティブがない状態にも関わらず、目標の設定を行ったグループの方が、そうでないグループに比べて、12〜15%パフォーマンスが高い事が研究によって明らかにされています。The Impact of Goal-setting on Worker Performance - Empirical Evidence from a Real-effort Production Experiment

本人が納得している目標については、曖昧な目標よりは明確な目標のほうが、また難易度の低い目標よりは難易度の高い目標のほうが、フィードバックが遅いより早い方が結果としての業績は高い、ということが確認されている。本人が納得していない目標は逆に業績の低下を招く可能性もある。 チームでも同じように、明確で高い難易度と高速なフィードバックに納得していれば、結果として高い業績を上げることができると思われる。

人事評価と目標

 人事評価として、目標設定を行う場合は大抵の場合はジレンマが発生する。目標を達成しなければ給料が下がるが、低い目標を立てては意味がない。メンバーは給料を優先するため低い目標を立てようとして、高い目標への本人の納得を得ることが難しくなる。何とかするのがマネージャーの手腕かもしれないが、少なくとも今まで全ての人に納得できる目標を立てさせたマネージャーにあっていないので9割のマネージャーが本人が納得していないか、緩い目標を立てていると思われる。

 可能であれば人事評価を本質的な能力の有無によって決めて目標設定と切り離し、目標設定をメンバーのモチベーションの向上にフォーカスするべきです。

アジャイルで目標

 長期的な未来を予測することは難しいということを前提にアジャイルをしている。アジャイルは計画をしないわけでも目標も定めないわけでもない。1年先の目標を立てたとして、52週間も先に結果がわかることになる。1週間スプリントなら52回も先で1ヶ月スプリントでも12回も先の話である。その頃には目標で立てていたことは、十分にできているか、目指すべき目標になっているはずだ。1年は明らかに長すぎる。アジャイル宣言でも、「計画に従うことよりも変化への対応」と言っている。陳腐化しているか間違っている目標を1年間も掲げるよりも変化への対応を行って柔軟に切り替えていくべきである。柔軟に切り替えていくことができない環境の場合は一年もの先を見通せないので、年間目標を立てたとしても曖昧な目標を立てることになる。曖昧な目標は、目標設定としては意味がないわけではないが効果的ではない。

 最適な長さは、スプリントと同じ程度だと思われる。良い目標であれば次のスプリントでも続ければ良い。そのときそのときに集中すべき目標は何なのかを考え続けましょう。目標は明確で高い難易度と高速なフィードバックが可能で、他の注力すべきでない目標から後ろ髪を引かれないようにすべきだろう。

RxSwiftわからん

メモ書き

ObserverとObserable

世界観として、観察者と観察対象がある。ObserableからObserverに対して複数回状態を送信する。UILabelとかUITableViewはObserberとなり、Obserableの値を観察を行って変更があった場合にそれに合わせて表示を変更を行う。ObserableはDBの値やサーバーで取ってくる値、UITextFieldやUIButtonなどがある。

Hot/Cool

obserableは基本的にHotなobservableでないと複数から観察できない。 * Subject * multicast, publish/replay/replayAll, share

少し古いが、このスライドがわかりやすかった。shareが便利

今日こそ理解するHot / Cold @社内RxSwift勉強会

Sink

内部的に使われているもの。ObserverでありObservableでもある。FilterとかMapで返ってくる

46pあたりから説明がありわかりやすかった。 RxSwift コードリーディングの勘所@社内RxSwift勉強会

deferred/Deferred

まだよくわからない

amb/Amb

buffer/BufferTimeCount, debounce

入力される最後の通知からtimeinterval の時間の後にcount分だけ通知を一気に流す。 検索をするコストがそこそこある場合に、入力されるたびではなくUISearchBarからの最後の入力から0.5秒後に検索を行いたい時に用いるのが良い。 debounceはcountを1にしたもの

throttle

連続して大量に繰り返される処理を一定間隔で間引くものです。 よく使われるのはscrollイベントです。スクロールイベントをすべてハンドリングすると処理回数が多くなり、場合によってはスクロールがもっさりしてしまいますよね。それを防ぎます。

throttleとdebounce

materialize, dematerialize

Eventに変換と元に戻す

【RxSwift】materialize, dematealizeを使ってみた

concat/Concat , combineLatest/ CombineLatestN

複数のObservableをまとめて、一つのObservableとして扱うもの

distinctUntilChanged/DistinctUntilChanged

通知の内容が前回と変更があった場合に通知を次に流す

concat , merge

複数のストリームを混ぜ合わせられる

【RxSwift】concatとmergeの違いをサンプルを元に整理してみる – su- tech blog

ObservableType

RxSwiftのObertvableな物についている

Obserable

最もシンプルなObservableType。ObservableType に実装されているところ以外はほとんど能力はない

SharedSequence

ConnectableObservableType

subscribeを完了させた後にconnect()を行って、接続を完了させる。

iOSで実装されているカレンダーの解説

この記事を読んでいる人は、日本語で書いているので概ね西暦か和暦を使っていると思います。しかし、世界には西暦では過ごしていない国や地域が数多くある。もしグローバル展開する機会があればそれぞれの国で正しく計算されたり表示される助けになればと思います。

カレンダーの隣の日付はグレゴリアン暦2020年3月14日をそれぞれのカレンダーで表示したもの。

太陽暦

太陽暦とは

太陽の変化によって一年を決める方法。太陽を地球が一周するのに365.25日程度かかるため、1年を365日と4年に1回の閏年で調整を行う暦。月と季節は同期する。

グレゴリオ暦

グレゴリオ暦と同じ日付を採用する暦をグレゴリオ暦系とした。 太陽年は365.25日ではなく誤差があり、400年間に(100回ではなく)97回の閏年置いて調整を行っている。

グレゴリオ暦

iso8601 Saturday, March 14, 2020 gregorian Saturday, March 14, 2020 iso8601 にも定義されているように国際標準の暦として採用されている。世界的にも多く採用されている。 キリスト紀元として、キリストが誕生した年を1年とした。多くの暦は紀元1年の前年は紀元前1年であり、0年は存在しない。

タイ仏暦

buddhist Saturday, March 14, 2563 BE 釈迦が入滅した翌年を元年とする紀年法である。キリスト紀元に543を加えた値が仏滅紀元となる。

中華民国暦

republicOfChina Saturday, March 14, 109 Minguo

中華民国が成立した1912年を元年とする紀年法である。

和暦

japanese ここで紹介する全ての暦の中で最も変わった紀元をもつ。改元を行う暦である。多くの暦は紀元を基準にするか紀元前と紀元後しか存在しない。一方、和暦は237個の元号が存在し、30年程度で改元が行われる。改元される理由は近年では天皇陛下が御即位されることによって改元され、直近にならないといつ改元されるかは予測不可能である。平成31年4月30日の翌日は、令和1年5月1日である。つまり、時代(era)が一つ増え、年が1に戻り、月以下は通常通りである。平成31年と令和1年はeraとyearが異なっているが同じ年として扱われる。

海外では改元されることを想定していないOSSが多く存在するため注意が必要である。

イラン暦

persian Saturday, Esfand 24, 1398 AP

紀元を預言者ムハンマドヒジュラの年(西暦622年)を紀元としておく。 グレゴリオ暦と日付が異なるが年が多少ずれるものの、グレゴリオ暦と完全同期している。 イラン暦 - Wikipedia

コプト暦系

1年は13ヶ月あり、初めの12ヶ月は各30日から成り、13月に追加日5日(閏年は6日)を足した365日(366日)である。 太陽年との誤差を修正していない365.25日を1年の長さとしている。通常は1年を365日として4年ごとに閏年をせってしている。ユリウス暦と同期しており、グレゴリアン暦に比べて400年で3日進むため、徐々にズレが生じる。

コプト

coptic Saturday, Baramhat 5, 1736 ERA1 ディオクレティアヌス紀元を紀元とている。ディオクレティアヌス紀元は、ディオクレティアヌスが即位した年の年初(ユリウス暦284年8月29日)を紀元とする

エチオピア

ethiopicAmeteMihret Saturday, Megabit 5, 2012 ERA1

キリストの生誕を基準にしている。

ethiopicAmeteAlem Saturday, Megabit 5, 7512 ERA0

神はキリストの生誕の5500年前に世界を創造したとして、世界創造暦を基準としている。

太陰太陽暦

1月を29.5日、12ヶ月を1年として、季節の連れを修正するために、3年に1回程度1ヶ月追加される。追加される月のことを「閏月」と呼ぶ。

インド国定暦

indian Saturday, Phalguna 24, 1941 Saka

サカ紀元の紀元に当たる西暦78年を「インド国定暦0年」として数える。

中国暦

chinese Saturday, Second Month 21, 2020(geng-zi)

ユダヤ暦

hebrew Saturday, 18 Adar 5780 at 12:55:29 AM Japan Standard Time

太陰暦

1ヶ月を29.5日、12ヶ月で1年としたもの。

イスラム暦

islamic Saturday, Rajab 20, 1441 AH islamicCivil Saturday, Rajab 19, 1441 AH islamicTabular Saturday, Rajab 20, 1441 AH islamicUmmAlQura Saturday, Rajab 19, 1441 AH

イスラム暦の少しづつ違うようだが細かくは調べきれていない。分かる人コメントください。

参照

The Ethiopic Calendar | An Ethiopian Journal

iOSでユーザーが文字を入力する方法

UITextFieldなどに文字を入力する方法が、キーボードだけではないのでまとめておこうと思います。

ソフトウェアキーボード

もっとも一般的な入力方法。 日本語の変換があるので確定していない文字が存在する。確定した時点で、複数の文字が入れ替わったりすることもある。

外部キーボード

Bluetoothなどでつながったキーボードを使って、文字を入力することができる。ほとんどは、ローマ字入力だが、バーコードリーダーなどの特殊な入力をするようなキーボードも存在する

カットやペースト

 大量の文字を一度に削除したり、入力することになる。必ず一文字ずつ入力していると考えているとバグの原因になる。 数字をペーストをする際は前後にスペースが入ったりする。

元に戻すとやり直す

シェイクしたりアラートを表示したり、iPadではキーボードにボタンをタップするなど、元に戻すを行うことができます。もちろんやり直すこともできる。 ユーザーの入力を変更していたりすると、クラッシュする場合がある。 https://qiita.com/akuraru/items/15e629bfe472f83f45f6

他のテキストからドラッグする

選択している状態で、ドラッグすると文字がそのまま別のテキストに貼り付けることができます。結果としてペーストと同じような挙動になります。

f:id:akuraru:20200326223617p:plain

他にも入力方法を知っていたらコメントください。

あなたの文字制限をしているUITextFieldはクラッシュしているかもしれない

UITextFieldで入力した文字に対して何らかの制限を加えたい時はよくあると思います。文字数を制限したり、数字のみを入力させたい場合などです。

サンプルコードを用意しました。 https://github.com/akuraru/CrashTextField

数字のみを入力制限させたい場合の雑なサンプルは以下のようなものになると思います。このコードはクラッシュします。

extension ViewController: UITextFieldDelegate {
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        let b = string.isEmpty || string == String(Int(string) ?? 0)
        return b
    }
}

クラッシュの再現方法

  1. UITextFieldに制限される文字列をペーストする
  2. 端末をシェイクして取り消しアラートを表示する(シミュレータだと⌘+⌃+Z)
  3. 取り消す
  4. クラッシュ

対策1

入力を受け付けない場合、UndoManagaerをリセットします。これで、取り消しができなくなります。

extension ViewController: UITextFieldDelegate {
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        let b = string.isEmpty || string == String(Int(string) ?? 0)
        
        if !b {
            textField.undoManager?.removeAllActions()
        }
        
        return b
    }
}

対策2

シェイクで編集する機能を無効にします。この方法はアプリ全体で無効になってしまうのでできればやらないようにしたほうがいいでしょう。

application.applicationSupportsShakeToEdit = false

クラッシュの内容(おまけ)

アプリケーションのどこで起こったクラッシュなのか全くわからないクラッシュログが発生する

2018-05-21 22:20:05.268665+0900 CrashTextField[47789:859156] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[NSBigMutableString substringWithRange:]: Range {0, 38} out of bounds; string length 0'
*** First throw call stack:
(
    0   CoreFoundation                      0x00000001127fd1e6 __exceptionPreprocess + 294
    1   libobjc.A.dylib                     0x000000010ec76031 objc_exception_throw + 48
    2   CoreFoundation                      0x0000000112872975 +[NSException raise:format:] + 197
    3   Foundation                          0x000000010e663e72 -[NSString substringWithRange:] + 131
    4   UIKit                               0x0000000110360b1e -[NSTextStorage(UIKitUndoExtensions) _undoRedoAttributedSubstringFromRange:] + 136
    5   UIKit                               0x0000000110360f7d -[_UITextUndoOperationReplace undoRedo] + 319
    6   Foundation                          0x000000010e711695 -[_NSUndoStack popAndInvoke] + 280
    7   Foundation                          0x000000010e711424 -[NSUndoManager undoNestedGroup] + 433
    8   UIKit                               0x000000010f5225aa __58-[UIApplication _showEditAlertViewWithUndoManager:window:]_block_invoke.2495 + 31
    9   UIKit                               0x000000010f8db425 -[UIAlertController _invokeHandlersForAction:] + 105
    10  UIKit                               0x000000010f8dbe2a __103-[UIAlertController _dismissAnimated:triggeringAction:triggeredByPopoverDimmingView:dismissCompletion:]_block_invoke.461 + 16
    11  UIKit                               0x000000010f683d02 -[UIPresentationController transitionDidFinish:] + 1346
    12  UIKit                               0x000000010f687b72 __56-[UIPresentationController runTransitionForCurrentState]_block_invoke.436 + 183
    13  UIKit                               0x000000011026b274 -[_UIViewControllerTransitionContext completeTransition:] + 102
    14  UIKit                               0x000000010f5d410d -[UIViewAnimationBlockDelegate _didEndBlockAnimation:finished:context:] + 859
    15  UIKit                               0x000000010f5a6f09 -[UIViewAnimationState sendDelegateAnimationDidStop:finished:] + 343
    16  UIKit                               0x000000010f5a754c -[UIViewAnimationState animationDidStop:finished:] + 293
    17  UIKit                               0x000000010f5a7600 -[UIViewAnimationState animationDidStop:finished:] + 473
    18  QuartzCore                          0x00000001160117a9 _ZN2CA5Layer23run_animation_callbacksEPv + 323
    19  libdispatch.dylib                   0x000000011395d848 _dispatch_client_callout + 8
    20  libdispatch.dylib                   0x000000011396892b _dispatch_main_queue_callback_4CF + 628
    21  CoreFoundation                      0x00000001127bfc99 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
    22  CoreFoundation                      0x0000000112783ea6 __CFRunLoopRun + 2342
    23  CoreFoundation                      0x000000011278330b CFRunLoopRunSpecific + 635
    24  GraphicsServices                    0x0000000115189a73 GSEventRunModal + 62
    25  UIKit                               0x000000010f5120b7 UIApplicationMain + 159
    26  CrashTextField                      0x000000010e36db77 main + 55
    27  libdyld.dylib                       0x00000001139da955 start + 1
)

参考

https://www.jianshu.com/p/b302a7c66741