JavaRush /Blog Jawa /Random-JV /FindBugs mbantu sampeyan sinau Jawa luwih apik
articles
tingkat

FindBugs mbantu sampeyan sinau Jawa luwih apik

Diterbitake ing grup
Penganalisis kode statis populer amarga mbantu nemokake kesalahan amarga kecerobohan. Nanging sing luwih menarik yaiku mbantu mbenerake kesalahan sing ditindakake amarga ora ngerti. Malah yen kabeh wis ditulis ing dokumentasi resmi kanggo basa, iku ora kasunyatan sing kabeh programer wis maca kanthi teliti. Lan programer bisa ngerti: sampeyan bakal kesel maca kabeh dokumentasi. Ing babagan iki, analisa statis kaya kanca sing berpengalaman sing lungguh ing jejere sampeyan lan nonton sampeyan nulis kode. Dheweke ora mung ngandhani: "Iki sampeyan nggawe kesalahan nalika sampeyan nyalin lan nempel," nanging uga ujar: "Ora, sampeyan ora bisa nulis kaya ngono, deleng dokumentasi dhewe." Kanca kuwi luwih migunani tinimbang dokumentasi dhewe, amarga dheweke mung nyaranake perkara sing sampeyan temoni ing karya sampeyan, lan meneng babagan sing ora bakal migunani kanggo sampeyan. Ing kirim iki, aku bakal ngomong babagan sawetara seluk-beluk Jawa sing aku sinau saka nggunakake FindBugs static analyzer. Mbokmenawa sawetara perkara sing ora dikarepake kanggo sampeyan uga. Penting kabeh conto ora spekulatif, nanging adhedhasar kode nyata.

Operator Ternary?:

Iku bakal katon sing ana apa-apa prasaja saka operator ternary, nanging wis pitfalls sawijining. Aku percaya yen ora ana prabédan dhasar ing antarane desain Type var = condition ? valTrue : valFalse; lan Type var; if(condition) var = valTrue; else var = valFalse; ternyata ana subtlety ing kene. Wiwit operator ternary bisa dadi bagean saka ekspresi kompleks, asile kudu dadi jinis konkrit sing ditemtokake ing wektu kompilasi. Mulane, ucapake, kanthi kondisi sing bener ing wangun if, compiler ndadékaké valTrue langsung menyang jinis Tipe, lan ing wangun operator ternary, pisanan ndadékaké kanggo jinis umum valTrue lan valFalse (senadyan kasunyatan sing valFalse ora dievaluasi), banjur asil ndadékaké menyang jinis Tipe. Aturan casting ora tanggung ora pati penting yen expression melu jinis primitif lan wrappers liwat wong (Integer, pindho, etc.) Kabeh aturan diterangake ing rinci ing JLS 15.25. Ayo katon ing sawetara conto. Number n = flag ? new Integer(1) : new Double(2.0); Apa bakal kelakon kanggo n yen flag disetel? Obyek kaping pindho kanthi nilai 1.0. Compiler nemokake upaya kikuk kita kanggo nggawe obyek sing lucu. Wiwit argumen kaloro lan katelu minangka pambungkus saka macem-macem jinis primitif, kompiler mbukak lan ngasilake jinis sing luwih tepat (ing kasus iki, kaping pindho). Lan sawise nglakokake operator ternary kanggo tugas kasebut, tinju ditindakake maneh. Ateges kode padha karo iki: Number n; if( flag ) n = Double.valueOf((double) ( new Integer(1).intValue() )); else n = Double.valueOf(new Double(2.0).doubleValue()); Saka sudut pandang kompilator, kode kasebut ora ana masalah lan kompilasi kanthi sampurna. Nanging FindBugs menehi bebaya:
BX_UNBOXED_AND_COERCED_FOR_TERNARY_OPERATOR: Nilai primitif dibukak kothak lan dipeksa kanggo operator ternary ing TestTernary.main (String []) Nilai primitif kebungkus unboxed lan diowahi kanggo jinis primitif liyane minangka bagéan saka evaluasi operator ternary kondisional (operator e2 b e1: operator? ). Semantik Jawa nduweni mandat yen e1 lan e2 kebungkus angka numerik, nilai-nilai kasebut ora dibubarake lan diowahi/dipeksa menyang jinis umume (contone, yen e1 jinis Integer lan e2 jinis Float, banjur e1 dibukak kothak, diowahi kanggo nilai floating point, lan kothak. Waca JLS Section 15.25. Mesthi, FindBugs uga ngelingake yen Integer.valueOf (1) luwih efisien tinimbang Integer anyar (1), nanging kabeh wong wis ngerti.
Utawa conto iki: Integer n = flag ? 1 : null; Penulis pengin sijine null ing n yen flag ora disetel. Apa sampeyan mikir bakal bisa digunakake? ya wis. Nanging ayo dadi rumit: Integer n = flag1 ? 1 : flag2 ? 2 : null; Kayane ora ana bedane. Nanging, saiki yen loro gendera cetha, baris iki mbuwang NullPointerException. Opsi kanggo operator ternary tengen int lan null, supaya jinis asil Integer. Pilihan kanggo kiwa iku int lan Integer, supaya miturut aturan Jawa asil int. Kanggo nindakake iki, sampeyan kudu nindakake unboxing kanthi nelpon intValue, sing mbuwang pengecualian. Kode kasebut padha karo iki: 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()); } Ing kene FindBugs ngasilake rong pesen, sing cukup kanggo curiga ana kesalahan:
BX_UNBOXING_IMMEDIATELY_REBOXED: Nilai kotak dibukak kothak banjur langsung reboxed ing TestTernary.main(String[]) NP_NULL_ON_SOME_PATH: Kemungkinan null pointer dereference saka null ing TestTernary.main(String[]) Ana cabang saka statement sing, yen dieksekusi sing dileksanakake. Nilai null bakal dereferenced, kang bakal generate NullPointerException nalika kode wis kaleksanan.
Inggih, siji conto pungkasan ing topik iki: double[] vals = new double[] {1.0, 2.0, 3.0}; double getVal(int idx) { return (idx < 0 || idx >= vals.length) ? null : vals[idx]; } Iku ora ngageti sing kode iki ora bisa: carane fungsi bali jinis primitif bali null? Kaget, kompilasi tanpa masalah. Inggih, sampeyan wis ngerti kok kompilasi.

Format Tanggal

Kanggo ngowahi format tanggal lan wektu ing Jawa, disaranake nggunakake kelas sing ngetrapake antarmuka DateFormat. Contone, katon kaya iki: public String getDate() { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()); } Asring kelas bakal nggunakake format sing padha bola-bali. Akeh wong bakal nemokake ide optimasi: kenapa nggawe obyek format saben-saben sampeyan bisa nggunakake conto umum? private static final DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public String getDate() { return format.format(new Date()); } Iku apik banget lan kelangan, nanging sayangé ora bisa. Luwih tepate kerjane, nanging sok-sok rusak. Kasunyatane yaiku dokumentasi kanggo DateFormat ujar:
Format tanggal ora disinkronake. Disaranake nggawe conto format sing kapisah kanggo saben thread. Yen sawetara utas ngakses format bebarengan, iku kudu diselarasake externally.
Lan iki bener yen sampeyan ndeleng implementasi internal SimpleDateFormat. Sajrone eksekusi metode format (), obyek kasebut nulis menyang lapangan kelas, supaya panggunaan SimpleDateFormat simultan saka rong benang bakal nyebabake asil sing ora bener kanthi kemungkinan. Mangkene apa sing ditulis FindBugs babagan iki:
STCAL_INVOKE_ON_STATIC_DATE_FORMAT_INSTANCE: Telpon kanggo cara statis java.text.DateFormat ing TestDate.getDate() Minangka JavaDoc nyatakake, DateFormats sipate ora aman kanggo nggunakake multithreaded. Detektor wis nemokake telpon menyang conto DateFormat sing dipikolehi liwat kolom statis. Iki katon curiga. Kanggo informasi luwih lengkap babagan iki, deleng Sun Bug #6231579 lan Sun Bug #6178997.

Pitfalls saka BigDecimal

Sawise sinau sing kelas BigDecimal ngijini sampeyan kanggo nyimpen nomer pecahan saka presisi kasepakatan, lan weruh sing wis konstruktor kanggo pindho, sawetara bakal mutusaké sing kabeh cetha lan sampeyan bisa nindakake iku kaya iki: System.out.println (BigDecimal anyar ( 1.1)); Ora ana sing nglarang nindakake iki, nanging asile bisa uga ora dikarepke: 1.100000000000000088817841970012523233890533447265625. Iki kedadeyan amarga dobel primitif disimpen ing format IEEE754, sing ora bisa makili 1.1 kanthi akurat (ing sistem nomer binar, fraksi periodik tanpa wates dipikolehi). Mulane, nilai sing paling cedhak karo 1.1 disimpen ing kono. Kosok baline, konstruktor BigDecimal(dobel) dianggo persis: iku sampurna ngowahi nomer tartamtu ing IEEE754 kanggo wangun desimal (fraksi biner final tansah representable minangka desimal final). Yen sampeyan pengin makili persis 1.1 minangka BigDecimal, sampeyan bisa nulis BigDecimal anyar ("1.1") utawa BigDecimal.valueOf (1.1). Yen sampeyan ora nuduhake nomer langsung, nanging nindakake sawetara operasi karo, sampeyan bisa uga ora ngerti ngendi kesalahan teka saka. FindBugs ngetokake bebaya DMI_BIGDECIMAL_CONSTRUCTED_FROM_DOUBLE, sing menehi saran sing padha. Punika bab liyane: BigDecimal d1 = new BigDecimal("1.1"); BigDecimal d2 = new BigDecimal("1.10"); System.out.println(d1.equals(d2)); Ing kasunyatan, d1 lan d2 makili nomer padha, nanging padha bali palsu amarga mbandhingaké ora mung ing Nilai saka nomer, nanging uga urutan saiki (nomer panggonan desimal). Iki ditulis ing dokumentasi, nanging sawetara wong bakal maca dokumentasi kanggo kuwi cara menowo minangka witjaksono. Masalah kaya ngono bisa uga ora langsung muncul. FindBugs dhewe, sayangé, ora ngelingake babagan iki, nanging ana ekstensi populer - fb-contrib, sing njupuk bug iki:
MDM_BIGDECIMAL_EQUALS padha () disebut kanggo mbandhingaké loro java.math.BigDecimal nomer. Iki biasane salah, amarga rong obyek BigDecimal mung padha yen padha karo nilai lan skala, supaya 2.0 ora padha karo 2.00. Kanggo mbandhingake obyek BigDecimal kanggo kesetaraan matematika, gunakake compareTo ().

Garis putus lan printf

Asring programer sing ngalih menyang Jawa sawise C seneng nemokake PrintStream.printf (uga PrintWriter.printf , etc.). Kaya, apik, aku ngerti, kaya ing C, sampeyan ora perlu sinau sing anyar. Bentenane ana bedane. Salah sijine yaiku terjemahan baris. Basa C nduweni divisi dadi teks lan aliran biner. Outputing karakter '\n' menyang stream teks kanthi cara apa wae bakal otomatis diowahi dadi baris anyar sing gumantung sistem ("\r\n" ing Windows). Ora ana pemisahan kasebut ing Jawa: urutan karakter sing bener kudu dikirim menyang aliran output. Iki rampung kanthi otomatis, contone, kanthi cara saka kulawarga PrintStream.println. Nanging nalika nggunakake printf, ngliwati '\n' ing string format mung '\n', dudu baris anyar sing gumantung sistem. Contone, ayo kang nulis kode ing ngisor iki: System.out.printf("%s\n", "str#1"); System.out.println("str#2"); Duwe redirected asil menyang file, kita bakal weruh: FindBugs mbantu sampeyan sinau basa Jawa luwih apik - 1 Mangkono, sampeyan bisa njaluk kombinasi aneh baris break ing siji thread, kang katon ora apik lan bisa jotosan pikiran saka sawetara parser. Kesalahan kasebut bisa uga ora dingerteni nganti suwe, utamane yen sampeyan nggunakake sistem Unix. Kanggo nglebokake baris anyar sing bener nggunakake printf, karakter format khusus "%n" digunakake. Mangkene apa sing ditulis FindBugs babagan iki:
VA_FORMAT_STRING_USES_NEWLINE: Format string kudu nggunakake %n tinimbang \n ing TestNewline.main(String[]) String format iki kalebu karakter baris anyar (\n). Ing format strings, umume luwih becik nggunakake %n, sing bakal ngasilake pemisah baris khusus platform.
Mbok menawa, kanggo sawetara sing maca, kabeh sing kasebut ing ndhuwur wis dikenal kanggo dangu. Nanging aku meh yakin manawa ana bebaya sing menarik saka penganalisa statis, sing bakal mbukak fitur-fitur anyar saka basa pamrograman sing digunakake.
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION