AndroidStudioのInspectionでコードチェックを楽にしてみた。

More than 1 year has passed since last update.

前置き

チームでAndroid開発していて、コーディング規約作りました。
コードチェックはそれを指標としてやってもらっています。
ただ、最近、コードチェックがコーディング規約を守っているか監視する作業みたいな状態になってしまいました・・・。
コードチェックってクラスの設計だったり、メソッド名のわかりやすさだったり、そういう人間にしか出来ないことをチェックすべきで、メンバー変数がmから始まっていないとか。ifのネストの数が多すぎるとかそういうのは機械的なチェックで行いたくなりました。

そこで、AndroidStudioのInspectionを使って、開発中にセルフコードチェックさせようと考え、Inspectionの調査をし始めました。

はい。機能ありすぎ・・・。
だからある程度かいつまんでこれ使えそうだ!と思ったやつを紹介してみます。
(すでにデフォルトでwarningレベルの設定があるものは省略します。)

環境

AndroidStudio v1.2.1.1
Mac OS X 10.10.3

Inspectionとは

そもそもInspectionってなによ?ってやつですが。

Android Studio > Preferences... > Editor > Inspections

で選択可能な特定のコードに対してワーニングやエラーとして表示してくれる機能です。
ただし、このワーニングやエラーがあってもコンパイルは通ります。
(コンパイル出来ないコードなら当然コンパイル出来ない。)

Inspectionを変える前に

デフォルトのprofileを変更してもいいのですが、念のため、デフォルトのprofileのコピーを取ります。

Android Studio > Preferences... > Editor > Inspectionsへ遷移し、ProfileをProject Defailtを選択します。(Defaltでも変更していないのであれば、可)

そこから、Manage > Copy > 任意の名前をつける(この記事ではShirajiとしました。)
Share to team memberのチェックは外さないこと。

profile

こうすると、PROJECT_ROOT/.idea/inspectionProfilesができ、そこに設定が格納されます。
このファイルを共有すればチームにこのInspectionを強制できます。

.gitignoreで.ideaを入れちゃっている人は、設定の変更をする必要があります。

マジックナンバー禁止

スクリーンショット

magic_number.png

設定方法

Android Studio > Preferences... > Editor > Inspections > Java > Abstraction Issue > Magic number

(以降はInspectionsまでの遷移を省略します。)

Options

選択肢 説明
Ignore constants in 'hashCode()' methods hashCode()内では無視する
Ignore in annotations アノテーション内では無視する
Ignore initial capacity for StringBuilder and Collections StringBuilderとCollection関連の初期値は無視する

説明

固定値として宣言された値以外の数値だった場合通知する。
ただし、0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 100, 1000, 0L, 1L, 2L, 0.0, 1.0, 0.0F, 1.0Fは対象外。

System.out/System.err利用禁止

スクリーンショット

sys.png

設定方法

Java > Code manurity issues > Use of System.out or System.err

メソッドコールを一行で複数回行うのを禁止

スクリーンショット
chain_method_call.png

設定方法

Java > Code style issues > Chained method calls

Options

選択肢 説明
Ignore chained method calls in field initializers フィールド初期化時には無視する
Ignore chained method calls in 'this()' and 'super()' calls this()/super()をコールした時は無視する

説明

メソッドコールの結果を再度メソッドコールすることをした場合、レポートさせる。
巷でよく聞く、ドット(.)の数を1行1つまでにするこをを強制させる時に利用。

比較条件の固定値を右(または左)側に配置させる

スクリーンショット

Constant on left side of comparison
const_left.png

Constant on right side of comparison
const_right.png

設定方法

右側に配置させる

Java > Code style issues > Constant on left side of comparison

左側に配置させる

Java > Code style issues > Constant on right side of comparison

説明

なんかJavascriptとかでミスらないように、固定値は左側強制という人がチームにいて、明記したルール。
個人的にこの設定好きではない。どちらでもいいじゃんって。Javaなんだし。

{}の省略を禁止

スクリーンショット

without_braces.png

設定方法

Java > Code style issues > Control flow statement without braces

説明

{}を省略していた場合、レポートさせる。対象はif/while/for

Stringの固定値を比較する場合、"固定値".equals(string)と強制する

スクリーンショット

string_equals.png

設定方法

Java > Code style issues > 'expression.equals("literal")' rather than "literal".equals(expression)'

説明

比較対象がnullだった場合、NullPointerExceptionを発生させ得る、string.equals("固定値")を避けるため、"固定値".equals(string)を強制させる。

条件文を&&や||を使ってシンプルにする

condition_simplify.png

設定方法

Java > Code style issues > 'if' statement may be replaced with && or || expression

説明

if(foo) {
    return bar;
} else {
    return false;
}

こういうコードの時はreturn foo && bar;

if(foo) {
    return true;
} else {
    return bar;
}

この場合は、return foo || barに置換できるので、それをレポートさせる

final指定可能な変数をfinal指定の強制

スクリーンショット

Report local variables
final_local_variables.png

Report method parameters
final_method_parameters.png

Report catch parameters
final_catch_parameters.png

Report foreach parameters
final_foreach_parameter.png

設定方法

Java > Code style issues > Local variable or parameter can be final

Options

選択肢 説明
Report local variables ローカル変数を対象にする
Report method parameters メソッドの引数を対象にする
Report catch parameters catchしたパラメータを対象にする
Report foreach parameters foreachのパラメータを対象にする

説明

Optionsを設定することにより、この設定は有効になる。
final指定可能なimmutableな変数のみが対象。

local変数はつけてもいいけど、最初からエラーエラーエラーって言い続けられるのはなかなか苦痛です。

複数の変数の定義を一行で行うことを禁止

スクリーンショット

one_declaration.png

設定方法

Java > Code style issues > Multiple variables in one declaration

Options

選択肢 説明
Ignore 'for' loop declarations forループ内での定義は無視

Enumのswitch時にcaseが全て羅列することを強制

スクリーンショット

missing_switch_case_enum.png

設定方法

Java > Control flow issues > Enum 'switch' statement that misses case

説明

以下のように、enumをswitch文で利用する時に、全てのenumをcaseに入れるように強制する。

enum ShirajiType {
    GOOD,
    BAD;
}

private void foo(ShirajiType type) {
    switch (type) { 
        case GOOD:
            Log.d("tag", "GOOD!");
        default:
            Log.d("tag", "default");
    }
}

Objective-Cでそういうエラーチェックが入るので(defaultがない場合)、Androidもという人のために。

elseが存在するif条件が否定を使わないように強制

スクリーンショット

if_negated.png

設定方法

Java > Control flow issues > 'if' statement with negated condition

Options

選択肢 説明
Ignore '!= null' comparisons != null比較時は無視
Ignore '!= 0' comparisons != 0比較時は無視

説明

以下のような条件式の場合、if/elseの中身を交換して、conditionの!を外すようにする。

if(!condition) {
    Log.d("tag", "AAA");
} else {
    Log.d("tag", "BBB");
}

以下が結果、こうなるとconditionをすんなり見ることができるので、わかりやすいコードとなる。

if(condition) {
    Log.d("tag", "BBB");
} else {
    Log.d("tag", "AAA");
}

instanceofの前のnullチェック禁止

スクリーンショット

unnecessary_null_check.png

設定方法

Java > Control flow issue > Unnecessary 'null' check before 'instanceof' expression

説明

instanceofは左辺がnullだった場合、必ずfalseを返す。そのため、nullチェックをする必要がない。

ローカル変数の再利用禁止

スクリーンショット

reuse_local_variable.png
(重要なことだから?)

設定方法

Java > Data flow issues > Reuse of local variable

メソッド継承時は@Overrideをつけることを強制

スクリーンショット

missing_override.png

設定方法

Java > Inheritance issues > Missing @Override annotation

複雑すぎるメソッドの禁止

スクリーンショット

method_too_complex.png

設定方法

Java > Method matrics > Overly complex method

Options

選択肢 説明
Method complexity limit 複雑度 - デフォルトは10

長すぎるメソッドの禁止

スクリーンショット

method_too_long.png

設定方法

Java > Method matrics > Overly long method

Options

選択肢 説明
Non-comment source statement limit コメント抜きで、実施ステップ数。デフォルトは30

説明

メソッドの長さを制限する。複雑度の設定かこちらかで選ぶと良いかも?コメントはメソッドの長さには含まれない。

ネストしすぎるメソッドの禁止

スクリーンショット

method_nested.png

設定方法

Java > Method matrics > Overly nested method

Options

選択肢 説明
Nesting depth limit 最大のネストの深さ。デフォルトは5

説明

if文などのネストをどこまで許容するか。
自分の開発チームは勉強させるため深さ1を強制しています。(案外慣れればなんとかなる。)

booleanが戻り値のメソッドの名前の接頭語を特定のものに強制(boolean以外の戻り値のメソッドはその接頭語を禁止)

スクリーンショット

boolean_method_prefix.png

設定方法

booleanメソッド
Java > Naming conventions > Boolean method name must start with question word

booleanメソッド以外
Java > Naming conventions > Non-Boolean method name must not start with question word

Options

選択肢 説明
Boolean method name prefix 許可される接頭語のリスト。デフォルトでは、is, can, has, should, could will, shall, check,. contains, equals, add, put, remove, startsWith, endsWith
Ignore methods with Boolean return type Booleanクラスが戻り値の型の場合、無視する
Ignore boolean methods in an @interface @interfaceがついた、booleanが戻り値のメソッドは無視する
Ignore methods overriding/implemnting a super method 親のメソッドの継承や実装の場合は無視する

クラス名・Enum名・メソッド名などのネーミングルールを強制する

設定方法

Java > Naming conventions >

  • Class name same as ancestor name
  • Class naming convention
  • Enumerated class naming convention
  • Enumerated constant naming convention
  • Instance field naming convention
  • Instance method naming convention
  • Interface naming convention
  • Local variable naming convention
  • Method name same as class name
  • Method name same as parent class name
  • Method names differing only by case
  • Method parameter naming convention
  • Non-constant field with upper-case name
  • Package naming convention
  • Questionable name
  • Standard variable names
  • 'static' field naming convention
  • 'static' method naming convention
  • Use of '$' in identifier

Options

だいたい以下のオプションが存在している。

選択肢 説明
Pattern 正規表現を利用した名前のパターン
Min length 名前の長さの最小値。0だった場合、このチェックは無視する
Max length 名前の長さの最大値。

Max lengthを0に設定し、Patternが間違っていた場合、エラーメッセージに"xxx name too long"というものが出るバグがあるようです。
Patternが正しかった場合、too long設定が0だった場合、このエラーメッセージは表示されなくなります。

よく使われるメソッドのtypoを検知する

設定方法

Java > Probably bugs >

  • 'compareto()' instead of 'compareTo()'
  • 'eqaul()' instead of 'eqauls()'
  • 'hashcode()' instead of 'hashCode()'
  • 'tostring()' instead of 'toString()'

Utilityクラスのインスタンス化の禁止

スクリーンショット

instantiate_util_class.png

設定方法

Java > Probably bugs > Instantiation of utility class

説明

staticメンバーとメソッドのみしかないUtilityクラスとみなし、そのクラスのインスタンス化をするようなコードをレポートさせる。

Nullチェックしたif文内でそのチェックしたreferenceを使わないことを禁止

設定方法

Java > Probably bugs > Reference checked for 'null' is not used inside 'if'

説明

if(savedInstanceState != null) {
    Log.d("tag", "FOO");
}

こういうコードのとき、本番では必要ない、デバッグ用コードであったり、ifが必要なかったり、typoの可能性があるため、ifでnullチェックしたreferenceを使わないときはレポートさせる。

toString()を作成時は全てのメンバーを記載することを強制

スクリーンショット

field_not_used_in_toString.png

サンプルコード

int foo;
String bar;

@Override
public String toString() {
    return "EntityClass{" +
            " bar='" + bar + '\'' +
            '}';
}

設定方法

Java > toString() issues > Field not used in 'toString()' method

インデントのタブ禁止(空白禁止)

設定方法

General > Problematic whitespace

説明

code styleに準拠し、

  • only tabsを設定した場合、インデントの空白をレポートさせる。
  • only spacesを設定した場合、インデントのタブをレポートさせる。

field名と同じ変数名の禁止

設定方法

Java > Visibility issues

  • Field name hides field in superclass
  • inner class fields hides outer class field
  • Local variable hides field
  • Parameter hides field

privateアクセス修飾子のメンバーにOuterクラスやInnerクラスからアクセスすることを禁止

設定方法

J2ME issues > Private member access between outer and inner classes

説明

Jakeさんが投げたpull requestのコメントから。
不必要なメソッドが自動生成されるのを防ぐためのものです。
この修正に関しては別ブログで日本語の解説してあるので詳しくはそちらご確認下さい。

ちょっとしたカスタムInspection

設定方法

General > Structural Search Inspection

Options

ここに、置換対象となるテンプレートと置換後のテンプレートを定義します。

実際に、Timberlintと同じようなLogクラスを使うときにTimber使えや!っていうレポートをあげるサンプルを作ってみます。
(dメソッドのみ。i, e, w, wtfは省略。)

Optionsの下のほうにある+ボタンを押します。

custom_inspection_1.png

Add replace templates...を選択します

Structural Replaceというポップアップが出てくるので、Search template:を以下を入力する。

Log.d($Parameter$)

Replacement template:を以下に入力する。

Timber.d($Parameter$)

log_to_timber.png

「OKボタン」をクリックする。

Save Templateというポップアップがでてくるので、名前をつける。
この名前は非常に重要で、かつ、修正をするのはAndroid Studioの設定では出来ないので慎重に決める。この名前がワーニングなどのレポートで表示されることになる。

save_template.png

「OKボタン」をクリックする。

スクリーンショット

logd_ban.png

これで他のInspection同様、alt+enterすれば、Timber.dに置換されます。

設定方法(テンプレート名の変更方法)

結構色々探したのですが、Android Studioの設定になかったので、直接ファイルを修正し、カスタムtemplate名を変更します。

$PROJECT_HOME/.idea/inspectionProfiles/Shiraji.xmlを開きます。(最初にコピーしたprofile名.xmlが対象のファイル名です。)

Shiraji.xml
    <inspection_tool class="SSBasedInspection" enabled="true" level="ERROR" enabled_by_default="true">
      <replaceConfiguration name="Log.dが許されるのは小学生までだよねー" text="Log.d($Parameter$)" recursive="false" caseInsensitive="false" type="JAVA" reformatAccordingToStyle="true" shortenFQN="true" useStaticImport="true" replacement="Timber.d($Parameter$)">
        <constraint name="Instance" minCount="0" within="" contains="" />
        <constraint name="MethodCall" target="true" within="" contains="" />
        <constraint name="Parameter" minCount="0" maxCount="2147483647" within="" contains="" />
      </replaceConfiguration>
    </inspection_tool>

この、nameの値を変更します。

Shiraji.xml
    <inspection_tool class="SSBasedInspection" enabled="true" level="ERROR" enabled_by_default="true">
      <replaceConfiguration name="Log.dは使用禁止です。Timber.dを使って下さい。" text="Log.d($Parameter$)" recursive="false" caseInsensitive="false" type="JAVA" reformatAccordingToStyle="true" shortenFQN="true" useStaticImport="true" replacement="Timber.d($Parameter$)">
        <constraint name="Instance" minCount="0" within="" contains="" />
        <constraint name="MethodCall" target="true" within="" contains="" />
        <constraint name="Parameter" minCount="0" maxCount="2147483647" within="" contains="" />
      </replaceConfiguration>
    </inspection_tool>

logd_ban2.png

キャッシュがきいているのか、名前の変更がされない場合はOptions内の対象テンプレートをダブルクリックし、「OKボタン」をクリックすれば変更されます。(キリッ!

説明

ちょっとしたカスタムInspectionを作ることができる。
ただし、条件があって

  • Severityは全てのちょっとしたカスタムInspectionで同じ
  • 複雑な条件のInspectionは作れない。

だからちょっとしたカスタムInspectionと勝手に命名しました。公式ではCustom Inspectionと謳っているけど条件が微妙すぎる。(または自分のスキルが足らない。)

個人的にそこまで有効性ないかな+名前の変更方法がわからない!って思っていたのだけど、せっかくなので追記しました。
名前の正しい修正方法を知っている方いらっしゃいましたらご指摘下さい。

これでも痒いところに手が届かない場合は、Annotatorを独自実装する必要があります。

他にもInspectionのプラグインを作成するという手もあります。実際に作成してみました。
AndroidStudioで独自Inspectionを作って、Activity.createIntent/Fragment.newInstanceを強制してみた

作ったPlugin Inspection

最後に

絞ってリストしただけでもこれだけあります・・・。絞りきれない。
さらに、すでにdefaultで設定されているwarning設定だけど、error設定にしたほうがいいようなものもちらほら。

Inspection全部から設定するのが一番良いですが、時間かかるため、コーディング規約からInspectionに落とし込むほうがはやいです。
その後、この書き方も強制したいーと随時追加する方向です。

一旦作ってしまえば、チーム全体で共通した書き方ができますので、最初に作る人ががんばればいいと思う。(自分含め)