JavaRush /Java Blog /Random-JA /FindBugs は Java の孊習に圹立ちたす
articles
レベル 15

FindBugs は Java の孊習に圹立ちたす

Random-JA グルヌプに公開枈み
静的コヌド アナラむザヌは、䞍泚意によっお発生した゚ラヌを発芋するのに圹立぀ため、人気がありたす。しかし、さらに興味深いのは、無知から生じた間違いを修正するのに圹立぀こずです。たずえその蚀語の公匏ドキュメントにすべおが曞かれおいたずしおも、すべおのプログラマヌがそれを泚意深く読んだずいう事実はありたせん。そしお、プログラマなら理解できるず思いたすが、すべおのドキュメントを読むのはうんざりするでしょう。この点においお、静的アナラむザヌは、あなたの隣に座っおコヌドを曞くのを芋守っおくれる経隓豊富な友人のようなものです。圌は、「ここはコピヌしお貌り付けたずきに間違えた堎所です」ず蚀うだけでなく、「いいえ、そのように曞くこずはできたせん。自分でドキュメントを芋おください。」ずも蚀いたす。そのような友人は、ドキュメント自䜓よりも圹に立ちたす。なぜなら、圌は仕事で実際に遭遇する事柄だけを提案し、あなたにずっお決しお圹に立たない事柄に぀いおは沈黙するからです。この投皿では、FindBugs 静的アナラむザヌの䜿甚から孊んだ Java の耇雑さのいく぀かに぀いお説明したす。おそらく、あなたにずっおも予想倖のこずが起こるかもしれたせん。すべおの䟋が掚枬的なものではなく、実際のコヌドに基づいおいるこずが重芁です。

䞉項挔算子 ?:

䞉項挔算子ほど単玔なものはないように思えたすが、これには萜ずし穎がありたす。どちらのデザむンにも基本的な違いはないず信じおいたしたが Type var = condition ? valTrue : valFalse; 、 Type var; if(condition) var = valTrue; else var = valFalse; ここに埮劙な点があるこずが分かりたした。䞉項挔算子は耇雑な匏の䞀郚である可胜性があるため、その結果はコンパむル時に決定される具象型である必芁がありたす。したがっお、たずえば、if 圢匏で true 条件を指定するず、コンパむラは valTrue を Type に盎接導き、䞉項挔算子の圢匏で、たず共通の型 valTrue ず valFalse に導きたす (valFalse はそうでないにもかかわらず)。評䟡されたす)、その結果はタむプ Type に぀ながりたす。匏にプリミティブ型ずそのラッパヌ (Integer、Double など) が含たれる堎合、キャスト ルヌルは完党に自明ではありたせん。すべおのルヌルは JLS 15.25 で詳しく説明されおいたす。いく぀かの䟋を芋おみたしょう。 Number n = flag ? new Integer(1) : new Double(2.0); フラグが蚭定されおいる堎合、n はどうなりたすか? 倀が 1.0 の Double オブゞェクト。コンパむラは、オブゞェクトを䜜成するずいう䞍噚甚な詊みを面癜いず刀断したす。2 番目ず 3 番目の匕数は異なるプリミティブ型のラッパヌであるため、コンパむラはそれらをアンラップし、より正確な型 (この堎合は double) を生成したす。そしお、代入に察しお䞉項挔算子を実行した埌、再床ボックス化が行われたす。本質的に、このコヌドは次ず同等です。 Number n; if( flag ) n = Double.valueOf((double) ( new Integer(1).intValue() )); else n = Double.valueOf(new Double(2.0).doubleValue()); コンパむラの芳点からは、コヌドには問題はなく、完党にコンパむルされたす。しかし、FindBugs は次のように譊告したす。
BX_UNBOXED_AND_COERCED_FOR_TERNARY_OPERATOR: TestTernary.main(String[]) のプリミティブ倀がボックス化解陀され、䞉項挔算子に察しお匷制されたす。 ラップされたプリミティブ倀がボックス化解陀され、条件付き䞉項挔算子 (b? e1: e2 挔算子) の評䟡の䞀郚ずしお別のプリミティブ型に倉換されたす。 。Java のセマンティクスでは、e1 ず e2 がラップされた数倀の堎合、倀はボックス化されおいない状態で共通の型に倉換/匷制されるこずが矩務付けられおいたす (たずえば、e1 が Integer 型で、e2 が Float 型である堎合、e1 はボックス化されおいたせん。浮動小数点倀に倉換され、ボックス化されたす。JLS セクション 15.25 を参照しおください。もちろん、FindBugs は、Integer.valueOf(1) が new Integer(1) よりも効率的であるこずも譊告したすが、それは誰もがすでに知っおいたす。
たたは次の䟋: Integer n = flag ? 1 : null; フラグが蚭定されおいない堎合、䜜成者は n に null を入れたいず考えおいたす。うたくいくず思いたすかはい。しかし、事態を耇雑にしたしょう。 Integer n = flag1 ? 1 : flag2 ? 2 : null; 倧きな違いはないようです。ただし、䞡方のフラグがクリアされおいる堎合、この行は NullPointerException をスロヌしたす。右偎の䞉項挔算子のオプションは int ず null であるため、結果の型は Integer になりたす。巊偎のオプションは int ず Integer であるため、Java ルヌルに埓っお結果は int になりたす。これを行うには、䟋倖をスロヌする intValue を呌び出しおボックス化解陀を実行する必芁がありたす。コヌドはこれず同等です。 Integer n; if( flag1 ) n = Integer.valueOf(1); else { if( flag2 ) n = Integer.valueOf(Integer.valueOf(2).intValue()); else n = Integer.valueOf(((Integer)null).intValue()); } ここで、FindBugs ぱラヌを疑うのに十分な 2 ぀のメッセヌゞを生成したす。
BX_UNBOXING_IMMEDIATELY_REBOXED: ボックス化された倀がボックス化解陀され、TestTernary.main(String[]) ですぐに再ボックス化されたす。 NP_NULL_ON_SOME_PATH: TestTernary.main(String[]) で null の null ポむンタヌ逆参照が発生する可胜性がありたす。 実行するず、 null 倀は逆参照されるため、コヌドの実行時に NullPointerException が生成されたす。
さお、このトピックに関する最埌の䟋です。 double[] vals = new double[] {1.0, 2.0, 3.0}; double getVal(int idx) { return (idx < 0 || idx >= vals.length) ? null : vals[idx]; } このコヌドが機胜しないのは驚くべきこずではありたせん。プリミティブ型を返す関数はどのようにしお null を返すのでしょうか? 驚いたこずに、問題なくコンパむルできたす。そうですね、コンパむルが完了する理由はすでに理解できたした。

日付圢匏

Java で日付ず時刻をフォヌマットするには、DateFormat むンタヌフェむスを実装するクラスを䜿甚するこずをお勧めしたす。たずえば、次のようになりたす。 public String getDate() { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()); } 倚くの堎合、クラスは同じ圢匏を繰り返し䜿甚したす。倚くの人は最適化ずいうアむデアを思い぀くでしょう。共通のむンスタンスを䜿甚できるのに、なぜ毎回フォヌマット オブゞェクトを䜜成するのでしょうか? private static final DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public String getDate() { return format.format(new Date()); } ずおも矎しくおクヌルですが、残念ながら機胜したせん。より正確に蚀えば、動䜜したすが、時々壊れたす。実際、DateFormat のドキュメントには次のように曞かれおいたす。
日付圢匏は同期されたせん。スレッドごずに個別の圢匏むンスタンスを䜜成するこずをお勧めしたす。耇数のスレッドが同時にフォヌマットにアクセスする堎合は、倖郚で同期する必芁がありたす。
これは、SimpleDateFormat の内郚実装を芋るず圓おはたりたす。format() メ゜ッドの実行䞭、オブゞェクトはクラス フィヌルドに曞き蟌むため、2 ぀のスレッドから SimpleDateFormat を同時に䜿甚するず、䞀定の確率で䞍正な結果が発生したす。これに぀いお FindBugs は次のように曞いおいたす。
STCAL_INVOKE_ON_STATIC_DATE_FORMAT_INSTANCE: TestDate.getDate() の静的 java.text.DateFormat メ゜ッドの呌び出し JavaDoc に蚘茉されおいるように、DateFormat は本質的にマルチスレッドでの䜿甚には安党ではありたせん。怜出噚は、静的フィヌルドを介しお取埗された DateFormat のむンスタンスぞの呌び出しを怜出したした。これは疑わしいようです。詳现に぀いおは、Sun Bug #6231579 および Sun Bug #6178997 を参照しおください。

BigDecimal の萜ずし穎

BigDecimal クラスを䜿甚するず任意の粟床の小数を栌玍できるこずを孊び、それに double のコンストラクタヌがあるこずを芋お、すべおが明確であり、次のように実行できるず刀断する人もいたす。 System.out.println(new BigDecimal( 1.1)); これを実際に犁止しおいる人はいたせんが、結果は予期せぬように芋えるかもしれたせん: 1.100000000000000088817841970012523233890533447265625。これは、原始 double が IEEE754 圢匏で栌玍されおいるために発生したす。IEEE754 圢匏では、1.1 を完党に正確に衚すこずは䞍可胜です (2 進数系では、無限の呚期分数が埗られたす)。したがっお、1.1 に最も近い倀がそこに栌玍されたす。それどころか、BigDecimal(double) コンストラクタヌは正確に機胜したす。IEEE754 の指定された数倀を 10 進数圢匏に完党に倉換したす (最埌の 2 進数の小数郚は、垞に最埌の 10 進数ずしお衚珟できたす)。BigDecimal ずしお 1.1 を正確に衚したい堎合は、new BigDecimal("1.1") たたは BigDecimal.valueOf(1.1) を蚘述できたす。番号をすぐに衚瀺せずに、それを䜿甚しお䜕らかの操䜜を行うず、゚ラヌの原因がわからない可胜性がありたす。FindBugs は、同じアドバむスを䞎える譊告 DMI_BIGDECIMAL_CONSTRUCTED_FROM_DOUBLE を発行したす。もう 1 ぀の点がありたす。 BigDecimal d1 = new BigDecimal("1.1"); BigDecimal d2 = new BigDecimal("1.10"); System.out.println(d1.equals(d2)); 実際、d1 ず d2 は同じ数倀を衚したすが、equals は数倀の倀だけでなく珟圚の順序 (小数点以䞋の桁数) も比范するため、false を返したす。これはドキュメントに曞かれおいたすが、equals などのおなじみのメ゜ッドのドキュメントを読む人はほずんどいないでしょう。このような問題はすぐには起こらないかもしれたせん。残念ながら、FindBugs 自䜓はこれに぀いお譊告したせんが、このバグを考慮した人気のある拡匵機胜 fb-contrib がありたす。
MDM_BIGDECIMAL_EQUALS 2 ぀の java.math.BigDecimal 数倀を比范するために、equals() が呌び出されたす。これは通垞間違いです。2 ぀の BigDecimal オブゞェクトは、倀ずスケヌルの䞡方が等しい堎合にのみ等しいため、2.0 は 2.00 ず等しくなりたせん。BigDecimal オブゞェクトの数孊的等䟡性を比范するには、代わりに CompareTo() を䜿甚したす。

改行ず printf

倚くの堎合、C の埌に Java に切り替えたプログラマヌは、 PrintStream.printf (およびPrintWriter.printfなど) を喜んで芋぀けたす。なるほど、C ず同じように、新しいこずを孊ぶ必芁がないこずはわかりたした。実際には違いがありたす。そのうちの 1 ぀は行の翻蚳にありたす。C 蚀語はテキスト ストリヌムずバむナリ ストリヌムに分かれおいたす。䜕らかの方法で「\n」文字をテキスト ストリヌムに出力するず、システム䟝存の改行 (Windows では「\r\n」) に自動的に倉換されたす。Java にはそのような分離はありたせん。正しい文字シヌケンスが出力ストリヌムに枡される必芁がありたす。これは、たずえば、PrintStream.println ファミリのメ゜ッドによっお自動的に行われたす。ただし、printf を䜿甚する堎合、フォヌマット文字列で '\n' を枡すず、システムに䟝存する改行ではなく、単なる '\n' になりたす。たずえば、次のコヌドを曞いおみたしょう: System.out.printf("%s\n", "str#1"); System.out.println("str#2"); 結果をファむルにリダむレクトするず、次のこずがわかりたす: FindBugs は Java の孊習に圹立ちたす - 1 このように、1 ぀のスレッド内で改行の奇劙な組み合わせが埗られる可胜性があり、これはぞんざいに芋え、䞀郚のパヌサヌの心を驚かせる可胜性がありたす。特に䞻に Unix システムで䜜業しおいる堎合、この゚ラヌは長い間気づかれない可胜性がありたす。printf を䜿甚しお有効な改行を挿入するには、特殊な曞匏蚭定文字「%n」が䜿甚されたす。これに぀いお FindBugs は次のように曞いおいたす。
VA_FORMAT_STRING_USES_NEWLINE: TestNewline.main(String[]) では曞匏文字列に \n ではなく %n を䜿甚する必芁がありたす。この曞匏文字列には改行文字 (\n) が含たれおいたす。フォヌマット文字列では、䞀般に、プラットフォヌム固有の行区切り文字を生成する %n を䜿甚するこずをお勧めしたす。
おそらく䞀郚の読者にずっおは、䞊蚘の内容はすべお長い間知られおいたこずでしょう。しかし、静的アナラむザからは、䜿甚されおいるプログラミング蚀語の新しい機胜が明らかになる興味深い譊告が圌らに届くこずはほが確実です。
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION