#前置き
チームでAndroid開発していて、コーディング規約作りました。
コードチェックはそれを指標としてやってもらっています。
ただ、最近、コードチェックがコーディング規約を守っているか監視する作業みたいな状態になってしまいました・・・。
コードチェックってクラスの設計だったり、メソッド名のわかりやすさだったり、そういう人間にしか出来ないことをチェックすべきで、メンバー変数がmから始まっていないとか。ifのネストの数が多すぎるとかそういうのは機械的なチェックで行いたくなりました。
そこで、AndroidStudioのInspectionを使って、開発中にセルフコードチェックさせようと考え、Inspectionの調査をし始めました。
AndroidStudioのInspectionでコードチェック楽にしようと思ったのだけど、項目多すぎwすで読み始めて半日以上たってる。まだ半分以上ある・・・。#AndroidStudio
— しらじ (@shiraj_i) 2015, 5月 8
はい。機能ありすぎ・・・。
だからある程度かいつまんでこれ使えそうだ!と思ったやつを紹介してみます。
(すでにデフォルトで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のチェックは外さないこと。
こうすると、PROJECT_ROOT/.idea/inspectionProfiles
ができ、そこに設定が格納されます。
このファイルを共有すればチームにこのInspectionを強制できます。
.gitignoreで.ideaを入れちゃっている人は、設定の変更をする必要があります。
#マジックナンバー禁止
スクリーンショット
設定方法
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利用禁止
スクリーンショット
設定方法
Java > Code manurity issues > Use of System.out or System.err
メソッドコールを一行で複数回行うのを禁止
設定方法
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
Constant on right side of comparison
設定方法
右側に配置させる
Java > Code style issues > Constant on left side of comparison
左側に配置させる
Java > Code style issues > Constant on right side of comparison
説明
なんかJavascriptとかでミスらないように、固定値は左側強制という人がチームにいて、明記したルール。
個人的にこの設定好きではない。どちらでもいいじゃんって。Javaなんだし。
{}の省略を禁止
スクリーンショット
設定方法
Java > Code style issues > Control flow statement without braces
説明
{}を省略していた場合、レポートさせる。対象はif/while/for
Stringの固定値を比較する場合、"固定値".equals(string)と強制する
スクリーンショット
設定方法
Java > Code style issues > 'expression.equals("literal")' rather than "literal".equals(expression)'
説明
比較対象がnullだった場合、NullPointerExceptionを発生させ得る、string.equals("固定値")を避けるため、"固定値".equals(string)を強制させる。
条件文を&&や||を使ってシンプルにする
設定方法
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指定の強制
スクリーンショット
設定方法
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変数はつけてもいいけど、最初からエラーエラーエラーって言い続けられるのはなかなか苦痛です。
複数の変数の定義を一行で行うことを禁止
スクリーンショット
設定方法
Java > Code style issues > Multiple variables in one declaration
Options
選択肢 | 説明 |
---|---|
Ignore 'for' loop declarations | forループ内での定義は無視 |
Enumのswitch時にcaseが全て羅列することを強制
スクリーンショット
設定方法
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条件が否定を使わないように強制
スクリーンショット
設定方法
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チェック禁止
スクリーンショット
設定方法
Java > Control flow issue > Unnecessary 'null' check before 'instanceof' expression
説明
instanceofは左辺がnullだった場合、必ずfalseを返す。そのため、nullチェックをする必要がない。
ローカル変数の再利用禁止
スクリーンショット
設定方法
Java > Data flow issues > Reuse of local variable
メソッド継承時は@Overrideをつけることを強制
スクリーンショット
設定方法
Java > Inheritance issues > Missing @Override annotation
複雑すぎるメソッドの禁止
スクリーンショット
設定方法
Java > Method matrics > Overly complex method
Options
選択肢 | 説明 |
---|---|
Method complexity limit | 複雑度 - デフォルトは10 |
長すぎるメソッドの禁止
スクリーンショット
設定方法
Java > Method matrics > Overly long method
Options
選択肢 | 説明 |
---|---|
Non-comment source statement limit | コメント抜きで、実施ステップ数。デフォルトは30 |
説明
メソッドの長さを制限する。複雑度の設定かこちらかで選ぶと良いかも?コメントはメソッドの長さには含まれない。
ネストしすぎるメソッドの禁止
スクリーンショット
設定方法
Java > Method matrics > Overly nested method
Options
選択肢 | 説明 |
---|---|
Nesting depth limit | 最大のネストの深さ。デフォルトは5 |
説明
if文などのネストをどこまで許容するか。
自分の開発チームは勉強させるため深さ1を強制しています。(案外慣れればなんとかなる。)
booleanが戻り値のメソッドの名前の接頭語を特定のものに強制(boolean以外の戻り値のメソッドはその接頭語を禁止)
スクリーンショット
設定方法
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クラスのインスタンス化の禁止
スクリーンショット
設定方法
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()を作成時は全てのメンバーを記載することを強制
スクリーンショット
サンプルコード
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
ここに、置換対象となるテンプレートと置換後のテンプレートを定義します。
実際に、Timberのlintと同じようなLogクラスを使うときにTimber使えや!っていうレポートをあげるサンプルを作ってみます。
(dメソッドのみ。i, e, w, wtfは省略。)
Optionsの下のほうにある+ボタンを押します。
Add replace templates...を選択します
Structural Replaceというポップアップが出てくるので、Search template:を以下を入力する。
Log.d($Parameter$)
Replacement template:を以下に入力する。
Timber.d($Parameter$)
「OKボタン」をクリックする。
Save Templateというポップアップがでてくるので、名前をつける。
この名前は非常に重要で、かつ、修正をするのはAndroid Studioの設定では出来ないので慎重に決める。この名前がワーニングなどのレポートで表示されることになる。
「OKボタン」をクリックする。
スクリーンショット
これで他のInspection同様、alt+enterすれば、Timber.dに置換されます。
設定方法(テンプレート名の変更方法)
結構色々探したのですが、Android Studioの設定になかったので、直接ファイルを修正し、カスタムtemplate名を変更します。
$PROJECT_HOME/.idea/inspectionProfiles/Shiraji.xmlを開きます。(最初にコピーしたprofile名.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の値を変更します。
<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>
キャッシュがきいているのか、名前の変更がされない場合はOptions内の対象テンプレートをダブルクリックし、「OKボタン」をクリックすれば変更されます。(キリッ!
説明
ちょっとしたカスタムInspectionを作ることができる。
ただし、条件があって
- Severityは全てのちょっとしたカスタムInspectionで同じ
- 複雑な条件のInspectionは作れない。
だからちょっとしたカスタムInspectionと勝手に命名しました。公式ではCustom Inspectionと謳っているけど条件が微妙すぎる。(または自分のスキルが足らない。)
個人的にそこまで有効性ないかな+名前の変更方法がわからない!って思っていたのだけど、せっかくなので追記しました。
名前の正しい修正方法を知っている方いらっしゃいましたらご指摘下さい。
これでも痒いところに手が届かない場合は、Annotatorを独自実装する必要があります。
他にもInspectionのプラグインを作成するという手もあります。実際に作成してみました。
AndroidStudioで独自Inspectionを作って、Activity.createIntent/Fragment.newInstanceを強制してみた
作ったPlugin Inspection
最後に
絞ってリストしただけでもこれだけあります・・・。絞りきれない。
さらに、すでにdefaultで設定されているwarning設定だけど、error設定にしたほうがいいようなものもちらほら。
Inspection全部から設定するのが一番良いですが、時間かかるため、コーディング規約からInspectionに落とし込むほうがはやいです。
その後、この書き方も強制したいーと随時追加する方向です。
一旦作ってしまえば、チーム全体で共通した書き方ができますので、最初に作る人ががんばればいいと思う。(自分含め)