Upgrade to Pro — share decks privately, control downloads, hide ads and more …

俺が今までやらかした失敗事例、やらかしそうになったヒヤリハット事例を紹介する

 俺が今までやらかした失敗事例、やらかしそうになったヒヤリハット事例を紹介する

Androidアプリ開発を長くやっていると、当然、とんでもない失敗をたくさんやらかします。

リリースの翌日大炎上。クラッシュ数グラフがうなぎ登り。低評価レビューの嵐。そこまで行かずとも、危うくとんでもないバグ付きアプリをリリースするところだったぜ、というヒヤリハット事例もたくさん。皆さんもそんな経験ありますよね。
このセッションでは、そんなAndroidアプリを開発していると遭遇する可能性のある失敗事例、失敗に繋がってしまった間違った設計、それに対してどのように対策をしたのか等を私の経験から紹介していこうと思います。
「あるある」「すでにやらかしたわ」と盛り上がりつつ「え、そんなことあるのか」「なるほどそうすればいいのか」という発見が一つでもあればと思います。

大前良介 (OHMAE Ryosuke)

February 21, 2020
Tweet

More Decks by 大前良介 (OHMAE Ryosuke)

Other Decks in Technology

Transcript

  1. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. 自己紹介 •大前良介(OHMAE

    Ryosuke) • https://github.com/ohmae • twitter: ryo_mm2d • qiita: ryo_mm2d •ヤフー株式会社 @グランフロント大阪 • Androidアプリエンジニア • Yahoo!天気アプリ担当
  2. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. 過去のDroidKaigi •DroidKaigi

    2018 • タッチイベントを捕まえよう •DroidKaigi 2019 • Chrome Custom Tabsの仕組みから学ぶプロセス間通信 •DroidKaigi 2020 • 俺が今までやらかした失敗事例、やらかしそうになったヒヤリハット事例を紹介する イマココ
  3. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. 本発表には以下の成分が含まれます •

    脈絡のない失敗事例の列挙 • Android開発で発生する問題あるある • 合計◦◦円分の損失を出しました、などの内容は含まれません • 突如始まるエモい話
  4. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. 予防線 •

    本発表は個人の見解であって、所属組織を代表するもので はありません • あくまであるある事例の紹介 • やらかしたのか、ヒヤリハットですんだのかも秘密 察してください(重要)
  5. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. ActivityNotFoundException •Intentを受け取れるActivityが見つからない

    • 外部特定アプリを起動するIntentで仕様がかわった • 暗黙的Intentを投げたが通常あるであろうアプリがインストール されていない • システム設定を呼び出したが、特定メーカーのカスタム設定画面 は呼び出せなかった •ほとんどの環境、テスト環境でも問題無い
  6. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. 対策 •try/catchしましょう

    • 特に外部アプリを起動するときは必ずcatchするクセを fun Context.startActivitySafely(intent: Intent) { runCatching { startActivity(intent) } }
  7. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. android.os.TransactionTooLargeException at

    android.os.BinderProxy.transactNative(Native Method) at android.os.BinderProxy.transact(Binder.java:496)
  8. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. TransactionTooLargeException •トランザクションデータが大きすぎる

    • プロセス内のトランザクションバッファ1MB • プロセス内の合計が超えるとアウト • IntentのExtra/onSaveInstanceStateのBundle •正直制限が厳しい • 誰だよこんなデータ突っ込んだの (レガシーコードあるある)
  9. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. 対策 •IntentやsavedStateに大きなデータを置かない、

    キーとなるパラメータだけを置く • Activity内 :ViewModel • Activity間 :キャッシュ・永続化データ・再取得 • プロセス間 :AIDL
  10. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. アップデートとともに消える ウィジェット

    • ウィジェットが消えた! • 表示できませんに変わった! ※OSやホームアプリによって症状が違う
  11. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. 原因 •リファクタリングでWidgetProviderの

    ComponentNameが変わった • WidgetProviderのComponentNameに変化があると 設置済みのウィジェットと紐付けができなくなる
  12. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. 原因 •リファクタリングでWidgetProviderの

    ComponentNameが変わった • WidgetProviderのComponentNameに変化があると 設置済みのウィジェットと紐付けができなくなる 消えるのは必然
  13. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. ホーム画面に作成したショートカットが! •消えた!

    •タップしてもアプリが起動しない! ※追従してくれる場合もある
  14. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. 原因 •リファクタリングでActivityの

    ComponentNameが変わった • アプリドロワーはIntentFilterから列挙 • ショートカットはComponentNameで管理 •ComponentNameが変わると紐付けが外れる
  15. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. デフォルトアプリ •セレクターを経由しないで起動する

    • ブラウザーやホームアプリにとって重要 • アプリ側からデフォルトにすることは不可 •せっかく設定してもらえたのに はずれた!?
  16. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. 原因 •リファクタリングでActivityの

    CompnentNameが(ry • デフォルトアプリもCompnentNameで識別 •ComponentNameが変わると紐付けが外れる
  17. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. AndroidManifest •アプリの外部仕様の宣言

    • データを扱うのはアプリ外 • 削除と追加はできてもマイグレーションは不可能 •Manifestへの変更はプロトコルの変更である • 過去・未来・外部アプリからの影響を考慮する
  18. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. 対策 •Manifestの変更を極力回避する

    • 変更しなくてよいように新規作成、追加時に検討する •リファクタリング・アーキテクチャ変更 • Activity:activity-alias • その他:ロジックの乗らない踏み台となる層を残す class OldService: NewService() class OldReceiver: NewReceiver()
  19. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. Android 4.3以下で

    ClassNotFoundException •Objects • Java7で追加されたクラス Objects.requireNonNull(hoge)
  20. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. Android 4.3以下で

    ClassNotFoundException •Objects • Java7で追加されたクラス • デバッグビルドだと動く Objects.requireNonNull(hoge)
  21. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. Android 4.3以下で

    ClassNotFoundException •Objects • Java7で追加されたクラス • デバッグビルドだと動く • リリースビルドするとClassNotFoundException Objects.requireNonNull(hoge)
  22. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. Android 4.3以下で

    ClassNotFoundException •Objects • Java7で追加されたクラス • デバッグビルドだと動く • リリースビルドするとClassNotFoundException なんでや! Objects.requireNonNull(hoge)
  23. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. Android 4.3以下でClassCastException

    Caused by: java.lang.ClassCastException: android.content.res.XmlBlock$Parser cannot be cast to java.lang.AutoCloseable context.resources.getXml(resId).use { loadFromResource(context, it, target) }
  24. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. Android 4.3以下でClassCastException

    Caused by: java.lang.ClassCastException: android.content.res.XmlBlock$Parser cannot be cast to java.lang.AutoCloseable context.resources.getXml(resId).use { loadFromResource(context, it, target) }
  25. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. public interface

    XmlResourceParser extends XmlPullParser, AttributeSet, AutoCloseable { String getAttributeNamespace (int index); public void close(); } API 19
  26. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. public interface

    XmlResourceParser extends XmlPullParser, AttributeSet, AutoCloseable { String getAttributeNamespace (int index); public void close(); } public interface XmlResourceParser extends XmlPullParser, AttributeSet { public void close(); } API 18 API 19
  27. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. Kotlinの罠 •jdk7の意味

    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
  28. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. Kotlinの罠 •jdk7の意味

    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" public inline fun <T : AutoCloseable?, R> T.use(block: (T) -> R): R { var exception: Throwable? = null try { return block(this) } catch (e: Throwable) { exception = e throw e } finally { this.closeFinally(exception) } } https://github.com/JetBrains/kotlin/blob/master/libraries/stdlib/jdk7/src/kotlin/AutoClo seable.kt
  29. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. 対策 •Android

    4.xのサポートを切る • サポートを維持するコストとリスクを把握しましょう •Java7/8の機能を使うときは要注意 • 使えないクラスを使っても警告が出ない場合がある compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 }
  30. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. なにが起こっていたか •9-patchを無駄なく1pxの画像にした

    •xxhdpiしか用意していなかった • 縮小方向なら品質的な問題も起こりにくい
  31. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. なにが起こっていたか •9-patchを無駄なく1pxの画像にした

    •xxhdpiしか用意していなかった • 縮小方向なら品質的な問題も起こりにくい •1pxの画像を縮小すると? \(^o^)/
  32. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. getStringExtraでClassNotFoundException? Caused

    by: java.lang.RuntimeException: Parcelable encountered ClassNotFoundException reading a Serializable object (name = xxx) at android.os.Parcel.readSerializable(Parcel.java:2378) at android.os.Parcel.readValue(Parcel.java:2197) at android.os.Parcel.readArrayMapInternal(Parcel.java:2479) at android.os.BaseBundle.unparcel(BaseBundle.java:221) at android.os.BaseBundle.getString(BaseBundle.java:918) at android.content.Intent.getStringExtra(Intent.java:4816) at ...
  33. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. getStringExtraでClassNotFoundException? Caused

    by: java.lang.RuntimeException: Parcelable encountered ClassNotFoundException reading a Serializable object (name = xxx) at android.os.Parcel.readSerializable(Parcel.java:2378) at android.os.Parcel.readValue(Parcel.java:2197) at android.os.Parcel.readArrayMapInternal(Parcel.java:2479) at android.os.BaseBundle.unparcel(BaseBundle.java:221) at android.os.BaseBundle.getString(BaseBundle.java:918) at android.content.Intent.getStringExtra(Intent.java:4816) at ... Enum?
  34. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. val intent

    = Intent(ACTION_HOGE) intent.putExtra(EXTRA_KEY_FUGA, VALUE_FUGA) val pendingIntent = PendingIntent.getBroadcast(this, 1, intent, 0) val value = intent.getStringExtra(EXTRA_KEY_FUGA)
  35. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. val intent

    = Intent(ACTION_HOGE) intent.putExtra(EXTRA_KEY_FUGA, VALUE_FUGA) val pendingIntent = PendingIntent.getBroadcast(this, 1, intent, 0) val value = intent.getStringExtra(EXTRA_KEY_FUGA) Stringを設定
  36. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. val intent

    = Intent(ACTION_HOGE) intent.putExtra(EXTRA_KEY_FUGA, VALUE_FUGA) val pendingIntent = PendingIntent.getBroadcast(this, 1, intent, 0) val value = intent.getStringExtra(EXTRA_KEY_FUGA) Stringを設定 Stringを読み出し
  37. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. val intent

    = Intent(ACTION_HOGE) intent.putExtra(EXTRA_KEY_HOGE, HogeEnum.HOGE) val pendingIntent = PendingIntent.getBroadcast(this, 1, intent, 0) val value = intent.getSerializableExtra(EXTRA_KEY_HOGE) 前バージョン
  38. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. val intent

    = Intent(ACTION_HOGE) intent.putExtra(EXTRA_KEY_HOGE, HogeEnum.HOGE) val pendingIntent = PendingIntent.getBroadcast(this, 1, intent, 0) val value = intent.getSerializableExtra(EXTRA_KEY_HOGE) 前バージョン Enumを設定
  39. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. val intent

    = Intent(ACTION_HOGE) intent.putExtra(EXTRA_KEY_HOGE, HogeEnum.HOGE) val pendingIntent = PendingIntent.getBroadcast(this, 1, intent, 0) val value = intent.getSerializableExtra(EXTRA_KEY_HOGE) 前バージョン Enumを設定 Serializableとして読み出し
  40. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. val intent

    = Intent(ACTION_HOGE) intent.putExtra(EXTRA_KEY_HOGE, HogeEnum.HOGE) val pendingIntent = PendingIntent.getBroadcast(this, 1, intent, 0) val value = intent.getSerializableExtra(EXTRA_KEY_HOGE) 前バージョン Enumを設定 Serializableとして読み出し これが見つからない?
  41. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. val intent

    = Intent(ACTION_HOGE) intent.putExtra(EXTRA_KEY_HOGE, HogeEnum.HOGE) val pendingIntent = PendingIntent.getBroadcast(this, 1, intent, 0) val value = intent.getSerializableExtra(EXTRA_KEY_HOGE) 前バージョン Enumを設定 Serializableとして読み出し これが見つからない? Keyも違うよ?
  42. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. ソースを追ってみる public

    @Nullable String getStringExtra(String name) { return mExtras == null ? null : mExtras.getString(name); } Intent
  43. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. ソースを追ってみる public

    @Nullable String getStringExtra(String name) { return mExtras == null ? null : mExtras.getString(name); } @Nullable public String getString(@Nullable String key) { unparcel(); final Object o = mMap.get(key); try { return (String) o; } catch (ClassCastException e) { typeWarning(key, o, "String", e); return null; } } Intent Bundle
  44. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. ソースを追ってみる void

    unparcel() { synchronized (this) { final Parcel source = mParcelledData; if (source != null) { initializeFromParcelLocked(source, true, mParcelledByNative); } else { if (DEBUG) { Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this)) + ": no parcelled data"); } } } } Bundle
  45. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. IntentのgetXXXの挙動 •Bundle(Extras)から読み出す

    •Bundleは内部データがunparcel前であれば、 内部データ全体をunparcelする
  46. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. IntentのgetXXXの挙動 •Bundle(Extras)から読み出す

    •Bundleは内部データがunparcel前であれば、 内部データ全体をunparcelする •Extraの中に一つでもunparcel/deserializeでき ないデータが含まれているとException
  47. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. 対策 •PendingIntentを含む、アプリ外へ出て行く

    Intentに独自クラスを入れることを原則禁止 •先にActionなどで分岐 •アプリ外から受け取るIntentのExtraに不用意にア クセスしない • 読み出す場合はtry/catch必須
  48. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. try/catch付きの拡張関数を用意するとか fun

    Intent.getBooleanExtraSafely(key: String, default: Boolean): Boolean = runCatching { getBooleanExtra(key, default) }.getOrDefault(default) fun Intent.getIntExtraSafely(key: String, default: Int = 0): Int = runCatching { getIntExtra(key, default) }.getOrDefault(default)
  49. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. 可能性の話 •悪意あるIntentを投げることもできてしまう

    • 適当なSerializableをIntentにいれて投げるだけ • 対策をしていなければクラッシュ • 意図せず加害者になる可能性も
  50. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. 可能性の話 •悪意あるIntentを投げることもできてしまう

    • 適当なSerializableをIntentにいれて投げるだけ • 対策をしていなければクラッシュ • 意図せず加害者になる可能性も 万全の対策を!
  51. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. SharedPreferencesで ClassCastException

    !? java.lang.ClassCastException: java.lang.Boolean cannot be cast to java.lang.Integer at android.app.SharedPreferencesImpl.getInt( SharedPreferencesImpl.java:302)
  52. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. 何が起こったか? sharedPreferences

    .getBoolean("HOGE_HOGE", false) sharedPreferences .getInt("HOGE_HOGE", 0) 削除 過去バージョン 新バージョン
  53. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. 何が起こったか? sharedPreferences

    .getBoolean("HOGE_HOGE", false) sharedPreferences .getInt("HOGE_HOGE", 0) 削除 同じKey! 過去バージョン 新バージョン
  54. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. 要するに •同バージョン内では整合性がとれている

    • ユニットテストなどでは問題なし •前バージョンには存在しないキー • アップデートを含むシナリオテストでも問題なし
  55. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. 要するに •同バージョン内では整合性がとれている

    • ユニットテストなどでは問題なし •前バージョンには存在しないキー • アップデートを含むシナリオテストでも問題なし •一般ユーザー:削除前からのユーザー多数 \(^o^)/
  56. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. SharedPreferences •自由度が高すぎ、型安全でない

    private Map<String, Object> mMap; public String getString(String key, @Nullable String defValue) { synchronized (mLock) { awaitLoadedLocked(); String v = (String)mMap.get(key); return v != null ? v : defValue; } }
  57. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. SharedPreferences •Keyを適切に管理する責任はアプリ側にある

    •内容はアプリをアップデートしても残り続ける •無法地帯化しやすい
  58. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. class Preferences<K>(

    context: Context, kClass: KClass<K> ) where K : Enum<*>, K : Key { private val sharedPreferences: SharedPreferences = context.getSharedPreferences(kClass.simpleName, MODE_PRIVATE) fun writeBoolean(key: K, value: Boolean) { if (BuildConfig.DEBUG) key.checkSuffix(value) sharedPreferences.edit().putBoolean(key.name, value).apply() } fun readBoolean(key: K, defaultValue: Boolean): Boolean = ... fun writeInt(key: K, value: Int): Unit = ... fun readInt(key: K, defaultValue: Int) = ... 1. KeyをEnumで管理
  59. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. interface Key

    { enum class Main : Key { PREFERENCES_VERSION_INT, KEY_BOOLEAN, KEY_INT, KEY_LONG, KEY_FLOAT, @Deprecated("removed:v1.1.1") KEY_STRING, ... } enum class Temp : Key { PREFERENCES_VERSION_INT, ...
  60. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. interface Key

    { enum class Main : Key { PREFERENCES_VERSION_INT, KEY_BOOLEAN, KEY_INT, KEY_LONG, KEY_FLOAT, @Deprecated("removed:v1.1.1") KEY_STRING, ... } enum class Temp : Key { PREFERENCES_VERSION_INT, ... 2. Key名に型名をつける カッコ悪い……
  61. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. interface Key

    { enum class Main : Key { PREFERENCES_VERSION_INT, KEY_BOOLEAN, KEY_INT, KEY_LONG, KEY_FLOAT, @Deprecated("removed:v1.1.1") KEY_STRING, ... } enum class Temp : Key { PREFERENCES_VERSION_INT, ... 2. Key名に型名をつける 3. 使わなくなったキーを削除 しないことをルール化 カッコ悪い……
  62. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. interface Key

    { enum class Main : Key { PREFERENCES_VERSION_INT, KEY_BOOLEAN, KEY_INT, KEY_LONG, KEY_FLOAT, @Deprecated("removed:v1.1.1") KEY_STRING, ... } enum class Temp : Key { PREFERENCES_VERSION_INT, ... 2. Key名に型名をつける 4. 過去を清算しやすく • バージョンをつける • 一時的設定値を分離 3. 使わなくなったキーを削除 しないことをルール化 カッコ悪い……
  63. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. class PreferenceService(

    private val main: Preferences<Main>, private val temp: Preferences<Temp> ) { fun getHoge(): Int = main.readInt(Main.KEY_INT, 0) fun setHoge(value: Int) = main.writeInt(Main.KEY_INT, value) 5. アプリ全体からはKeyを隠蔽
  64. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. internal fun

    Enum<*>.checkSuffix(value: Any) { when (value) { is Boolean -> require(name.endsWith(SUFFIX_BOOLEAN)) is Int -> require(name.endsWith(SUFFIX_INT)) is Long -> require(name.endsWith(SUFFIX_LONG)) is Float -> require(name.endsWith(SUFFIX_FLOAT)) is String -> require(name.endsWith(SUFFIX_STRING)) } } fun writeBoolean(key: K, value: Boolean) { if (BuildConfig.DEBUG) key.checkSuffix(value) sharedPreferences.edit().putBoolean(key.name, value).apply() } 6. 名前ルールは実行時チェック
  65. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. 永続化データのマイグレーションミス •忘れがち

    • バージョンは一つ一つ上がるわけではない • 1→2、2→3のマイグレーションを想定していても1→3が正しく 動作しているか? •過去の清算も計画的に • マイグレーションを実装するときに、いつまでマイグレーションをサ ポートするかを決めておく • 一定以上古い場合は全クリアした方が安全
  66. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. shrinkResource •リソースをID参照していない

    • getIdentifier • パス参照 resources.getIdentifier( "ic_launcher", "drawable", packageName) "file:///android_res/drawable/ic_launcher.png"
  67. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. getIdentifierの罠 •連番のついたリソース

    • どのリソースが使われているのか分からない! (1..9).map { resources.getIdentifier( "ic_$it", "drawable", packageName) }
  68. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. 対策 •getIdentifierを原則禁止

    • ローカルリソースは必ずIDで参照する • 連番リソースもarrayやmapなどで保持する •パス参照 • ID参照にする • assetsに移動
  69. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. SlideでInvalid slide

    direction? java.lang.IllegalArgumentException: Invalid slide direction at android.transition.Slide.setSlideEdge(Slide.java:165) at android.transition.Slide.<init>(Slide.java:112) at ...
  70. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. 何が起こっている? if

    (animate) { val transition = Slide(Gravity.END) .setDuration(150L) .setInterpolator(DecelerateInterpolator()) fragment.enterTransition = transition } activity.supportFragmentManager.beginTransaction() .replace(R.id.server_detail_container, fragment) .commitAllowingStateLoss()
  71. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. 何が起こっている? if

    (animate) { val transition = Slide(Gravity.END) .setDuration(150L) .setInterpolator(DecelerateInterpolator()) fragment.enterTransition = transition } activity.supportFragmentManager.beginTransaction() .replace(R.id.server_detail_container, fragment) .commitAllowingStateLoss() ここ?
  72. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. public void

    setSlideEdge(@GravityFlag int slideEdge) { switch (slideEdge) { case Gravity.LEFT: mSlideCalculator = sCalculateLeft; break; case Gravity.TOP: mSlideCalculator = sCalculateTop; break; case Gravity.RIGHT: mSlideCalculator = sCalculateRight; break; case Gravity.BOTTOM: mSlideCalculator = sCalculateBottom; break; case Gravity.START: mSlideCalculator = sCalculateStart; break; case Gravity.END: mSlideCalculator = sCalculateEnd; break; default: throw new IllegalArgumentException("Invalid slide direction"); public Slide(@GravityFlag int slideEdge) { setSlideEdge(slideEdge); }
  73. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. public void

    setSlideEdge(@GravityFlag int slideEdge) { switch (slideEdge) { case Gravity.LEFT: mSlideCalculator = sCalculateLeft; break; case Gravity.TOP: mSlideCalculator = sCalculateTop; break; case Gravity.RIGHT: mSlideCalculator = sCalculateRight; break; case Gravity.BOTTOM: mSlideCalculator = sCalculateBottom; break; case Gravity.START: mSlideCalculator = sCalculateStart; break; case Gravity.END: mSlideCalculator = sCalculateEnd; break; default: throw new IllegalArgumentException("Invalid slide direction"); public Slide(@GravityFlag int slideEdge) { setSlideEdge(slideEdge); } 問題無い?
  74. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. public void

    setSlideEdge(@GravityFlag int slideEdge) { switch (slideEdge) { case Gravity.LEFT: mSlideCalculator = sCalculateLeft; break; case Gravity.TOP: mSlideCalculator = sCalculateTop; break; case Gravity.RIGHT: mSlideCalculator = sCalculateRight; break; case Gravity.BOTTOM: mSlideCalculator = sCalculateBottom; break; case Gravity.START: mSlideCalculator = sCalculateStart; break; case Gravity.END: mSlideCalculator = sCalculateEnd; break; default: throw new IllegalArgumentException("Invalid slide direction"); public Slide(@GravityFlag int slideEdge) { setSlideEdge(slideEdge); } 問題無い? API Level?
  75. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. public void

    setSlideEdge(int slideEdge) { switch (slideEdge) { case Gravity.LEFT: mSlideCalculator = sCalculateLeft; break; case Gravity.TOP: mSlideCalculator = sCalculateTop; break; case Gravity.RIGHT: mSlideCalculator = sCalculateRight; break; case Gravity.BOTTOM: mSlideCalculator = sCalculateBottom; break; default: throw new IllegalArgumentException("Invalid slide direction"); }
  76. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. public void

    setSlideEdge(int slideEdge) { switch (slideEdge) { case Gravity.LEFT: mSlideCalculator = sCalculateLeft; break; case Gravity.TOP: mSlideCalculator = sCalculateTop; break; case Gravity.RIGHT: mSlideCalculator = sCalculateRight; break; case Gravity.BOTTOM: mSlideCalculator = sCalculateBottom; break; default: throw new IllegalArgumentException("Invalid slide direction"); } API Level 21
  77. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. 時系列 •

    GravityにSTART/ENDが追加された :API 14 • LayoutでStart/End指定が追加された:API 17 • android.transion.Slideが追加された:API 21 • SlideがSTART/ENDに対応した :API 22
  78. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. おまけ:start/endとleft/rightは違うよ •当たり前だけどね

    • コード上から操作するときも、start/endとleft/rightの使い分 けきちんとできてる? • setPaddingRelative • setPadding • setCompoundDrawablesRelative • setCompoundDrawables
  79. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. エモい話 •やらかさない人はいない

    • 人間はミスを犯すもの • 開発はルーチンワークではなく、チャレンジの連続 •開発は必ずしも万全の状態で行えない • 時間が無い・人員が足りない • スキルが足りない・秘伝のタレ •失敗を過度に恐れない、失敗から学べばよい • どんな凄腕エンジニアもはじめは初心者
  80. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. エモいまとめ •慎重になるべき部分を見極める

    • 問題が出やすい部分 • 問題が出たときに取り返しがつかない部分 •特に注意するべき部分 • 外部とのプロトコル • 永続化データ
  81. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. エモいまとめ •コードを改善すればミスも減る

    • コードの状態は伝搬する • 悪いコードは悪いコードを増やす • よいコードはよいコードを増やす • リファクタリングはエンジニアの当然の義務 •過去を清算できる仕組みを仕込む • いつサポートを切るか?
  82. Copyright© 2020 Yahoo Japan Corporation. All Rights Reserved. エモいまとめ •失敗してしまったら

    • 何が問題だったか、現実的な再発防止策を考える • 「再発防止策を行わない」という結論も重要 •失敗を乗り越えたエンジニアは強い! • 人の失敗事例は他山の石とする