為さねば成らぬ

retia.verno@gmail.com

2023 抱負

あけましておめでとうございます。既に仕事初めの時期ですが新年の抱負です。

九州旅行にて車で観光する

社会人になる直前に免許を取り、それ以来10年ほどペーパードライバーです。 都内に住んでいてそこまで旅行好きでもなかったので不要だったのですが、妻と旅行するうちに、観光するなら必要だよねということになりました。

一時期は車を運転することの万が一のリスクが高すぎるとずっとペーパーでいることも考えていたのですが、割と趣味として楽しめそうであるのでそのリスクは呑もうと思っています。 もちろん安全運転を心がけますし、現状ではペーパードライバー講習に通い始めています。

現状のマイルストーンは以下のようになっています - 1月: ペーパードライバー講習10時間程度 - 毎月: レンタカーを借りてドライブ - 6月: 友人結婚式のための沖縄でドライブ - 夏あたり: 九州旅行

…となんだかんだ言っていますが、Driving My Wayをドライブ中にかけたいのが最初のモチベーションです。

www.youtube.com

弱い定義のEMとしてやっていく

長年一Androidエンジニアとしてやってきましたが、昨年からはマネージャーになることを念頭にマネジメント業務にチャレンジさせていただいています。 ただ、この年末年始の休暇でEMについて様々な記事に触れてみると、マネージャーは場面場面で求められることが本当に違いますし、マネージャーと名乗るからにはやるべきことがまだまだあると確認できました。

現状はピープルマネジメントの領域しかチャレンジできていませんが、まずはインプットを増やし弱い定義のEMとして出来るようになることが目標です。(プロジェクトマネジメント・プロダクトマネジメントは別メンバーが既にやってくれているのもある)

弱い定義として出来た後、自身の特性的にプロジェクトマネジメント・プロダクトマネジメント方向にスキルを広げるよりピープルマネジメントを更に強める方向に行く気がしてます。

DroidKaigi登壇

昨年から社のAndroidテックリードとして活動しています。 任命していただいてありがたいと思う反面、自身の技術力的にテックリードとしての能力が本当に備わっているか悩むことがあります。 それぞれのチームに所属するAndroidエンジニアの悩みを解決するというのが今の役割に近いところなので、必ずしも技術力が高ければいけないというわけでは無いとは思います。それでも名乗る以上はメンバーに納得してもらえるだけの成果は出したい・・・!

ということでわかりやすい目標としてDroidKaigi登壇を目指します。

そのための具体的な行動ですが、ブログ記事・勉強会登壇等のアウトプットを増やします。 また過去プロポーザルを出しても採択されていなかったので、そこの分析もあわせてやっていきます。

毎週のジム通い

昨年前半は「結婚式でお姫様抱っこする」という名目で毎週通ってました。 実際お姫様抱っこをやるのは式の前の前撮りでしたし、そもそも最初の段階で既にできていたので目標としては形骸化していましたが…

その名目がなくなった結婚式後は行かなくなってしまいました。

普段リモートワークで運動せずは流石にまずいので、特にこれと言った目標ではないですがジム通いは続けようとしています。

一応「子供が出来た時にかっこいい姿を見せる」も考えたのですが何年後の話だ・・・?となっています

終わりに

目標は立てるまではよいのですが、途中の振り返りが大事ですよね。仕事で立てる目標も期初に立てて改めて意識するのは評価の時期なんてザラですし・・・

そうならんように1ヶ月単位で報告していきたいですね

Android LintのDetectorを読む Day6: MergeMarkerDetector

ソースコード

https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/MergeMarkerDetector.java

Issue

category: CORRECTNESS priority: 8 severity: ERROR

背景

gitでconflictを起こした時の <<<<<<< ======= >>>>>>> が残っていないかをチェックする

適用例

  • analyzeされないライブラリ ではない
  • .gradle .kts .properties .xml
    • 特にコンパイルで壊れないリソースファイルでチェックする目的がある

着目点

様々なファイルを対象にする時、 OtherFileScanner を実装する

getApplicableFiles run を実装する。 getApplicableFiles の返り値として Scope を指定する。 Other と指定しているのは、merge markerが存在するとコンパイルでコケるはずのソースコードを除外する目的があると思われる。

Android LintのDetectorを読む Day5: CallSuperDetector

ソースコード

https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CallSuperDetector.kt

背景

メソッドがoverrideされるときに、親クラスのメソッドを呼ぶように示すアノテーション @CallSuper が存在する。 ただしく実装されているかをチェックするLint。

適用例

以下の条件をすべて満たしているときにエラーが出る。

  • @CallSuperがついているメソッドをoverrideする
  • View.onDetachedFromWindow
    • Android 5からこのメソッドでの実際の処理は onDetachedFromWindowInternal で行われるようになったため、onDetachedFromWindow をsuperを呼ばずにoverrideしても問題なくなった。実際 View.onDetachedFromWindow の実装は空になっている。
    • が、それ以前では View.onDetachedFromWindow に実装が入っておりsuperを呼ぶ必要があり、また @CallSuper は存在していない時代だったため、特殊ケースとして条件が存在する。
  • android.support.wearable.watchface.WatchFaceService.Engine.onVisibilityChanged
    • サポートライブラリ側で @CallSuper 対応するまでは特殊ケースとしてチェックしている。

また、onCreate内でsuperを複数回呼ぶとクラッシュの危険性があるので、その場合もチェックする。 この際、複数回呼ばれない構造であればエラーが検出されない。

例えば以下のようなケースでは検出されない

override fun onCreate(savedInstanceState: Bundle){
  if(hoge){
    super.onCreate(savedInstanceState)
  } else {
    super.onCreate(savedInstanceState)
  }
}

気づき

SuperCallVisitorで superの呼び出しをチェック

特定のメソッドの中で呼び出したsuperのメソッドのついての情報を収集している。 skipParenthesizedExprUp は簡単に言うとsuperのメソッドを取得しており、targetとなるメソッドと名前が一致していれば配列に入れておくようになっている。この配列が0ならsuperが呼ばれていないのでエラーが出る。

        override fun visitSuperExpression(node: USuperExpression): Boolean {
            anySuperCallCount++
            val parent = skipParenthesizedExprUp(node.uastParent)
            if (parent is UReferenceExpression) {
                val resolved = parent.resolve()
                if (resolved == null || // Avoid false positives for type resolution problems
                    targetMethod.isEquivalentTo(resolved)
                ) {
                    superCalls.add(node)
                }
            }

            return super.visitSuperExpression(node)
        }

その他感想

CallSuperDetector という名前的には合っているが、 CallSuper のメソッドでちゃんとsuperが呼んでいるか、とonCreateが複数回呼んでいないかは別のクラスに責務分けたほうが良くないか?とは思う。 また、onCreate複数回呼んでいないかについて、クラスの判定を行っていない。複数回呼んでクラッシュするというのは Activity での話であり、これを条件に入れたほうが良さそう。

[Android] Android LintのDetectorを読む Day4: AppCompatCallDetector

ソースコード

https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AppCompatCallDetector.java

背景

AppCompatのメソッドを使う時、 通常のものではなく support のものを使うこと。 ちなみに現在SupportLibraryはAndroidXに役割を渡しており新しい開発についてもそちらで行われる。

なおAndroidXのLintについて、actionaBarは

https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:appcompat/appcompat-lint/src/main/kotlin/androidx/appcompat/app/SetActionBarDetector.kt

でチェックしている。

気づき

LintFixする

自動でlintが修正したい場合には、 LintFix を用いる。

                String message =
                        String.format("Should use `%1$s` instead of `%2$s` name", replace, name);
                LintFix fix =
                        fix().name("Replace with " + replace + "()")
                                .replace()
                                .text(name)
                                .with(replace)
                                .build();
                context.report(ISSUE, node, context.getLocation(node), message, fix);

メソッドが特定のクラスのサブクラスににあることを判定

isMemberInSubClassOf の第3引数のbooleanは、厳密にサブクラスかを判定している。 この場合 false のため、サブクラスではないその特定のクラスにメソッドが生えていてもtrueになる。

override fun visitMethodCall(context: JavaContext, node: UCallExpression, method:PsiMethod){
    val answer = context.getEvaluator().isMemberInSubClassOf(method, CLASS, false)
}
  
  

[Android] (WIP) Android LintのDetectorを読む Day3: AllCapsDetector

ソースコード

https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AllCapsDetector.kt

背景

TextViewでtextAllCaps=trueだとテキストに含まれるマークアップが落ちるためチェックしている。 が、実際マークアップが落ちるが何を指しているのかよくわからなかった。要継続調査。

  1. TextView.getTextしたときにマークアップのタグがなくなる?
  2. textAllCapsを変えても、タグが消えるのは同じ

  3. マークアップが効かなくなる?(指定しても太字じゃなくなる)

  4. textAllCaps=trueにしてもマークアップは効いていた

更に、マークアップのテキストをResourceに入れて渡すとこのlintに引っかかるのにResourceに入れずに直だと引っかからなかった。 Resourceに入れずに直だと別のlintで引っかかるため?

気づき

Layoutに対してはLayoutDetectorを用いる

LayoutDetector -> ResourceXmlDetector -> XMLScanner と継承されており、XMLの各要素にアクセス出来るようになっている LayoutDetector を継承し判定を行う。

attributeを判定するときはNSも確認する

    override fun visitAttribute(context: XmlContext, attribute: Attr) {
        if (ANDROID_URI != attribute.namespaceURI) {
            return
        }

attr名が合っててもNameSpaceが異なることがあるのでチェックする

resourceの値を取得する方法

        val client = context.client
        val full = context.isGlobalAnalysis()
        val project = if (full) context.mainProject else context.project
        val repository = client.getResources(project, ALL_DEPENDENCIES)
        val items = repository.getResources(ResourceNamespace.TODO(), url.type, url.name)
        if (items.isEmpty()) {
            return
        }
        val resourceValue = items[0].resourceValue ?: return
        val rawXmlValue = resourceValue.rawXmlValue ?: return

TODOとなっているが実態はRES_AUTOで、これは http://schemas.android.com/apk/res-auto のこと。 名前空間の追跡を用意にするためにログを追加で出してるだけっぽい。

rawXmlValueにテキストが入っているので、そのテキストに対してマークアップがあるか ( < が含まれているか) で判定している。

Android LintのDetectorを読む Day2: AlarmDetector

ソースコード

https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AlarmDetector.kt

背景

高頻度のアラームはバッテリーに悪いため、API 22からは最低でも5秒かかるし、インターバルも最低60秒になっている。 5秒以内に実行したい場合はメッセージを遅延させて送るか runnableを使うこと。

適用例

AlarmManager.setRepeatingにおいて、第2引数が5000、第3引数が60000未満であれば適用

気づき

特定のメソッドについて着目する場合、メソッド名だけで判定しない

getApplicableMethodNames でメソッド名を指定しつつ、更に引数の型・引数の数で判定している。 現在 setRepeatingオーバーロードが存在せず名前だけで一意に指定できるので、現状意味をなしていない判定である。 おそらく今後他の引数を持つ同名メソッドが増えた時にそちらに適用されないようにしているのだろう

override fun getApplicableMethodNames(): List<String> = listOf("setRepeating")

override fun visitMethodCall(
        context: JavaContext,
        node: UCallExpression,
        method: PsiMethod
    ) {
        val evaluator = context.evaluator
        if (evaluator.isMemberInClass(method, "android.app.AlarmManager") &&
            evaluator.getParameterCount(method) == 4
        ) {
            ensureAtLeast(context, node, 1, 5000L)
            ensureAtLeast(context, node, 2, 60000L)
        }
    }

変数・定数を考慮した値の取得の仕方

このLintでは引数の値が一定以下のときに引っかかるようになっている。 引数に渡される値も、数値直の場合もあるが、変数・定数でその箇所だけではわからない場合がある。

ConstantEvaluatorを用いて、visitMethodCallからは以下のように取得できる。

override fun visitMethodCall(
        context: JavaContext,
        node: UCallExpression,
        method: PsiMethod
    ) {
        val evaluator = context.evaluator
        val numberOfTargetParam // 取得したい引数が第何引数か
        val argument = node.valueArguments[numberOfTargetParam]
        val value = ConstantEvaluator.evaluate(context, argument)
        
        // Long
        value.toLong() 
        // String
        value.toString()
    }

Android LintのDetectorを読む Day1: AddJavascriptInterfaceDetector

https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/AddJavascriptInterfaceDetector.kt

背景

WebView#addJavascripInterface によりアプリからオブジェクトを埋め込む関数だが、WebView中のJavaScriptがリフレクション使ってアプリ側のフィールドにアクセス出来る脆弱性があるため。

API17移行では @JavascriptInterface がついているメソッドしかJavaScriptからアクセスできないが、それ以下だとすべてのメソッドにアクセス可能。

support.google.com

条件

  1. minSdkVerが17未満
  2. WebView.addJavascriptInterface(objct, string)
  3. TargetApi RequiresApiアノテーションで17以上がついていない

コード読んだ上での気づき

メソッドについて検出の仕方

メソッドに対してLintで検出したい場合、 SourceCodeScanner を継承し getApplicableMethodNames visitMethodCall を実装すること。

class AddJavascriptInterfaceDetector : Detector(), SourceCodeScanner {

    // ---- implements SourceCodeScanner ----

    override fun getApplicableMethodNames(): List<String> = listOf(ADD_JAVASCRIPT_INTERFACE)

    override fun visitMethodCall(
        context: JavaContext,
        node: UCallExpression,
        method: PsiMethod
    ) {
   }
}

#疑問
minSdkに関係する判定が3度入っていて処理が重複していそうなものだけれどどうなんだろう。

一つ目はわかりやすい。
    if (context.project.minSdk >= 17) {
        return
    }
2つ目について、
    if (isSuppressed(context, API_17, node, context.project.minSdkVersions)) {
        return
    }
でありisSuppressedの冒頭が以下なので、1つ目の判定が同時に行われていそうではある。
処理としては重複しているが、意味合いが異なる (こちらはsuppressされてるかのチェック)のでよさそう。
    fun isSuppressed(
        context: JavaContext,
        api: ApiConstraint,
        element: UElement,
        minSdk: ApiConstraint
    ): Boolean {
        if (minSdk.isAtLeast(api)) {
            return true
        }
3つ目について、17未満でレポートするようなコードになっている。
reportについてのコメントでは、
context.projectで取れるのはソースコードのプロジェクト、つまりライブラリだったらライブラリ。
ライブラリのminSdkVerが17未満でも使っているメインのPJで17以上だったら問題ないので、そのための紀伊術。

ちなみにlintの仕組み的に
1. ライブラリのソースファイルからlintチェックを行い、潜在的なエラーを報告する
2. appモジュールなど下流のモジュールでレポートを生成する時、そこでのminSdkVerなどでフィルタリングする

となっているので、 判定に `context.main`  を使うのは違うようだ。
    context.report(incident, minSdkLessThan(17))