นี่คือรายการแนวทางการเข้ารหัส Java 10 รายการที่ลึกซึ้งยิ่งกว่ากฎ Java ที่มีประสิทธิภาพของ Josh Bloch เมื่อเทียบกับรายการของ Josh Bloch ที่ง่ายต่อการเรียนรู้และมุ่งเน้นไปที่สถานการณ์ประจำวันรายการนี้จะมีสถานการณ์ที่เกี่ยวข้องกับสิ่งที่ผิดปกติในการออกแบบ API/SPI ซึ่งอาจมีผลกระทบอย่างมาก
ฉันพบสิ่งเหล่านี้ในขณะที่เขียนและบำรุงรักษา Jooq (SQL แบบจำลอง DSL ภายในใน Java) ในฐานะที่เป็น DSL ภายใน Jooq ท้าทายคอมไพเลอร์ Java และ Generics ในระดับที่ยิ่งใหญ่ที่สุดรวมการรวมยาชื่อสามัญพารามิเตอร์ที่ไม่แน่นอนและโอเวอร์โหลดซึ่ง Josh Bloch อาจไม่แนะนำสำหรับ API ที่กว้างเช่นนี้
ให้ฉันแบ่งปันกับคุณ 10 แนวทางปฏิบัติที่ดีที่สุดสำหรับการเข้ารหัส Java:
1. จำไว้ว่า destructor ของ C ++
จำ Destructor ของ C ++ ได้หรือไม่? จำไม่ได้? ถ้าอย่างนั้นคุณก็โชคดีมากเพราะคุณไม่ต้องดีบักการรั่วไหลของหน่วยความจำเนื่องจากหน่วยความจำที่จัดสรรหลังจากการลบวัตถุไม่ได้ถูกปล่อยออกมา ขอบคุณ Sun/Oracle สำหรับกลไกการรวบรวมขยะ!
อย่างไรก็ตาม Destructor มีคุณสมบัติที่น่าสนใจ เข้าใจคำสั่งการจัดสรรแบบผกผันเพื่อหน่วยความจำฟรี โปรดจำไว้ว่านี่เป็นกรณีใน Java เมื่อคุณใช้งานไวยากรณ์ Destructor ในชั้นเรียน:
มีกรณีการใช้งานอื่น ๆ อีกมากมาย นี่คือตัวอย่างที่เป็นรูปธรรมของวิธีการใช้งาน SPI ของผู้ฟังเหตุการณ์บางอย่าง:
@OverridePublic เป็นโมฆะก่อนหน้า (EventContext e) {super.beforevent (e); // รหัสซุปเปอร์ก่อนรหัสของฉัน} @OverridePublic เป็นโมฆะ AfterEvent (EventContext E) {// Super Code หลังจากรหัสของฉัน super.afterevent (e);} ปัญหาการรับประทานอาหารของนักปรัชญาที่น่าอับอายเป็นอีกตัวอย่างที่ดีว่าทำไมมันถึงสำคัญ สำหรับคำถามเกี่ยวกับการรับประทานอาหารของนักปรัชญาโปรดตรวจสอบลิงก์:
http://adit.io/posts/2013-05-11-the-dining-philosophers-problem-with-ron-swanson.html
กฎ : เมื่อใดก็ตามที่คุณใช้ก่อน/หลังจัดสรร/ฟรีใช้/ส่งคืนความหมายเพื่อใช้ตรรกะให้พิจารณาว่าจะดำเนินการหลัง/ฟรี/ส่งคืนตามลำดับย้อนกลับ
จัดหาวิธีการ SPI ให้กับลูกค้าที่ทำให้ง่ายต่อการฉีดพฤติกรรมที่กำหนดเองลงในห้องสมุด/รหัสของคุณ ระวังว่าการตัดสินวิวัฒนาการ SPI ของคุณอาจทำให้คุณสับสนทำให้คุณคิดว่าคุณ (ไม่) ตั้งใจที่จะต้องการพารามิเตอร์เพิ่มเติม แน่นอนฟังก์ชั่นไม่ควรเพิ่มเร็วเกินไป แต่เมื่อคุณเผยแพร่ SPI ของคุณเมื่อคุณตัดสินใจที่จะติดตามการกำหนดเวอร์ชันความหมายคุณจะเสียใจที่เพิ่มพารามิเตอร์เดียวที่โง่ใน SPI ของคุณเมื่อคุณตระหนักว่าในบางกรณีคุณอาจต้องใช้พารามิเตอร์อื่น:
อินเทอร์เฟซ EventListener {// ข้อความโมฆะไม่ดี (ข้อความสตริง);}ถ้าคุณต้องการรหัสข้อความและแหล่งข้อความ Evolution API จะป้องกันไม่ให้คุณเพิ่มพารามิเตอร์ลงในประเภทข้างต้น แน่นอนด้วย Java 8 คุณสามารถเพิ่มวิธีการของผู้พิทักษ์ที่ "ปกป้อง" การตัดสินใจออกแบบที่ไม่ดีของคุณในช่วงแรก ๆ :
อินเตอร์เฟส EventListener {// ข้อความโมฆะเริ่มต้นที่ไม่ดี (ข้อความสตริง) {ข้อความ (ข้อความ, null, null); } // ดีกว่า? ข้อความโมฆะ (ข้อความสตริง, ID จำนวนเต็ม, MessagesOrce Source);} โปรดทราบว่าน่าเสียดายที่วิธีการของผู้พิทักษ์ไม่สามารถใช้ตัวดัดแปลงสุดท้ายได้
แต่มันจะดีกว่ามากที่จะใช้วัตถุบริบท (หรือวัตถุพารามิเตอร์) มากกว่าที่จะทำให้ SPI ของคุณมลพิษโดยใช้วิธีการมากมาย
อินเตอร์เฟส messageContext {ข้อความสตริง (); ID จำนวนเต็ม (); MessagesOrce Source ();} อินเตอร์เฟส EventListener {// สุดยอด! โมฆะข้อความ (บริบท MessageContext);} คุณสามารถพัฒนา MessageContext API ได้ง่ายกว่า EventListner SPI เพราะผู้ใช้เพียงไม่กี่คนจะนำไปใช้งาน
กฎ : เมื่อใดก็ตามที่ระบุ SPI ให้พิจารณาใช้วัตถุบริบท/พารามิเตอร์แทนวิธีการเขียนด้วยพารามิเตอร์คงที่
หมายเหตุ : นอกจากนี้ยังเป็นความคิดที่ดีในการแลกเปลี่ยนผลลัพธ์ผ่านประเภท Messageresult เฉพาะซึ่งสามารถสร้างขึ้นได้โดยใช้ตัวสร้าง API สิ่งนี้จะเพิ่มความยืดหยุ่นของวิวัฒนาการ SPI อย่างมาก
โปรแกรมเมอร์สวิงมักจะกดปุ่มทางลัดเพียงไม่กี่ปุ่มเพื่อสร้างชั้นเรียนที่ไม่ระบุชื่อหลายร้อยหรือหลายพัน ในกรณีส่วนใหญ่มันไม่เจ็บที่จะทำเช่นนั้นตราบใดที่อินเทอร์เฟซตามมาและวงจรชีวิตย่อย SPI จะไม่ถูกละเมิด แต่อย่าใช้คลาสที่ไม่ระบุชื่อท้องถิ่นหรือภายในบ่อยครั้งด้วยเหตุผลง่ายๆ - พวกเขาจะบันทึกการอ้างอิงไปยังคลาสภายนอก เพราะไม่ว่าพวกเขาจะไปที่ไหนหมวดหมู่ภายนอกจึงต้องปฏิบัติตาม ตัวอย่างเช่นหากการทำงานภายนอกของคลาสท้องถิ่นไม่เหมาะสมกราฟวัตถุทั้งหมดจะได้รับการเปลี่ยนแปลงเล็กน้อยซึ่งอาจทำให้หน่วยความจำรั่วไหล
กฎ: ก่อนที่จะเขียนชั้นเรียนที่ไม่ระบุชื่อท้องถิ่นหรือภายในโปรดคิดสองครั้งว่าคุณสามารถแปลงเป็นชั้นเรียนระดับสูงแบบคงที่หรือธรรมดาได้หรือไม่
หมายเหตุ: ใช้เครื่องมือจัดฟันแบบหยิกคู่เพื่อเริ่มต้นวัตถุง่าย ๆ :
ใหม่ hashmap <string, string> () {{put ("1", "a"); ใส่ ("2", "b");}}วิธีการนี้ใช้อินสแตนซ์ initializer ที่อธิบายไว้ในข้อกำหนด JLS §8.6 มันดูดีบนพื้นผิว แต่ไม่แนะนำในความเป็นจริง เพราะหากคุณใช้วัตถุ HashMap อิสระอย่างสมบูรณ์อินสแตนซ์จะไม่เก็บข้อมูลอ้างอิงไปยังวัตถุภายนอกเสมอไป นอกจากนี้สิ่งนี้จะช่วยให้ตัวโหลดคลาสสามารถจัดการคลาสได้มากขึ้น
ก้าวของ Java8 กำลังใกล้เข้ามา ด้วย Java 8 นำการแสดงออกของแลมบ์ดาไม่ว่าคุณจะชอบหรือไม่ก็ตาม แม้ว่าผู้ใช้ API ของคุณอาจชอบ แต่คุณควรตรวจสอบให้แน่ใจว่าพวกเขาสามารถใช้งานได้บ่อยที่สุดเท่าที่จะทำได้ ดังนั้นหาก API ของคุณได้รับประเภท "สเกลาร์" อย่างง่ายเช่น int, ยาว, สตริงและวันที่ให้ API ของคุณได้รับแซมบ่อยที่สุดเท่าที่จะทำได้
แซมคืออะไร? SAM เป็นวิธีนามธรรมเดียว [ประเภท] หรือที่เรียกว่าอินเทอร์เฟซของฟังก์ชั่นมันจะมีคำอธิบายประกอบเป็น @functionalInterface ในไม่ช้า สิ่งนี้ตรงกับกฎข้อ 2 EventListener เป็น SAM จริง ๆ SAM ที่ดีที่สุดมีพารามิเตอร์เดียวเท่านั้นเนื่องจากจะทำให้การเขียนนิพจน์แลมบ์ดาง่ายขึ้น ลองนึกภาพการเขียน
listeners.add (c -> system.out.println (c.message ()));
เพื่อแทนที่
Listeners.add (ใหม่ EventListener () {@Override ข้อความโมฆะสาธารณะ (MessageContext C) {System.out.println (C.Message ())); -ลองนึกภาพการจัดการ XML ใน Joox Joox มีแซมจำนวนมาก:
$ (เอกสาร) // ค้นหาองค์ประกอบที่มี ID .find (c -> $ (c) .id ()! = null) // ค้นหาองค์ประกอบลูก ๆ ของพวกเขา. เด็ก (c -> $ (c) .tag (). เท่ากับ ("คำสั่งซื้อ") // พิมพ์การแข่งขันทั้งหมด.กฎ: ดีกับผู้ใช้ API ของคุณเริ่มเขียนอินเทอร์เฟซ SAM/ฟังก์ชั่นนับจาก นี้เป็นต้นไป
หมายเหตุ: มีบล็อกที่น่าสนใจมากมายเกี่ยวกับการแสดงออกของ Java8 Lambda และการปรับปรุงคอลเลกชัน API:
ฉันได้เขียนบทความ 1 หรือ 2 เรื่องเกี่ยวกับ Java Nulls และยังอธิบายถึงการแนะนำชั้นเรียนเสริมใหม่ใน Java 8 จากมุมมองทางวิชาการหรือการปฏิบัติหัวข้อเหล่านี้ค่อนข้างน่าสนใจ
แม้ว่า Null และ Nullpointerexception ยังคงเป็นข้อบกพร่องใน Java ในขั้นตอนนี้ แต่คุณยังสามารถออกแบบ API ที่จะไม่มีปัญหาใด ๆ เมื่อออกแบบ APIs คุณควรหลีกเลี่ยงการคืนค่า null ให้มากที่สุดเพราะผู้ใช้ของคุณอาจเรียกวิธีการในห่วงโซ่:
Initialise (someargument). calculate (data) .dispatch ();
ดังที่เห็นได้จากรหัสข้างต้นไม่มีวิธีใดที่ควรส่งคืนค่า NULL ในความเป็นจริงการใช้ null มักจะถือว่าเป็นความหลากหลายที่เทียบเคียงได้ ห้องสมุดเช่น JQuery หรือ Joox ได้ละทิ้ง Null อย่างสมบูรณ์ในวัตถุที่ติดอันดับ
NULL มักจะใช้ในการเริ่มต้นล่าช้า ในหลายกรณีควรหลีกเลี่ยงการเริ่มต้นล่าช้าโดยไม่ส่งผลกระทบอย่างรุนแรงต่อประสิทธิภาพ ในความเป็นจริงหากโครงสร้างข้อมูลที่เกี่ยวข้องมีขนาดใหญ่เกินไปควรใช้การเริ่มต้นล่าช้าด้วยความระมัดระวัง
กฎ: วิธีการควรหลีกเลี่ยงการส่งคืนค่าว่างเมื่อใดก็ตามที่พวกเขาเป็น NULL ใช้เพื่อแสดงถึงความหมายของ "ไม่ได้รับการกำหนดค่า" หรือ "ไม่มีอยู่"
แม้ว่ามันจะโอเคที่จะส่งคืนวิธีการที่มีค่าว่างในบางกรณีอย่าส่งคืนอาร์เรย์เปล่าหรือคอลเลกชันที่ว่างเปล่า! โปรดดูวิธี java.io.file.list () มันได้รับการออกแบบเช่นนี้:
วิธีนี้ส่งคืนอาร์เรย์ของสตริงสำหรับไฟล์หรือไดเรกทอรีทั้งหมดในไดเรกทอรีที่ระบุ หากไดเรกทอรีว่างเปล่าอาร์เรย์ที่ส่งคืนจะว่างเปล่า ส่งคืนค่า null หากเส้นทางที่ระบุไม่มีอยู่หรือเกิดข้อผิดพลาด I/O
ดังนั้นวิธีนี้มักจะใช้เช่นนี้:
ไดเรกทอรีไฟล์ = // ... ถ้า (directory.isdirectory ()) {string [] list = directory.list (); if (list! = null) {สำหรับ (ไฟล์สตริง: รายการ) {// ... }}}คุณคิดว่าการตรวจสอบ NULL เป็นสิ่งจำเป็นหรือไม่? การดำเนินการ I/O ส่วนใหญ่จะผลิต iOexceptions แต่วิธีนี้จะส่งคืนค่า NULL เท่านั้น NULL ไม่สามารถจัดเก็บข้อความแสดงข้อผิดพลาด I/O ดังนั้นการออกแบบดังกล่าวจึงมีข้อบกพร่องสามประการดังต่อไปนี้:
หากคุณดูปัญหาจากมุมมองของคอลเลกชันอาร์เรย์หรือคอลเลกชันที่ว่างเปล่าคือการใช้งานที่ดีที่สุดของ "ไม่มีอยู่" การส่งคืนอาร์เรย์หรือคอลเลกชันที่ว่างเปล่านั้นมีความสำคัญในทางปฏิบัติเพียงเล็กน้อยเว้นแต่จะใช้สำหรับการเริ่มต้นล่าช้า
กฎ: อาร์เรย์หรือคอลเลกชันที่ส่งคืนไม่ควรเป็นโมฆะ
ประโยชน์ของ HTTP นั้นไร้สัญชาติ รัฐที่เกี่ยวข้องทั้งหมดจะถูกถ่ายโอนในแต่ละคำขอและการตอบสนอง นี่คือสาระสำคัญของการตั้งชื่อ REST: การถ่ายโอนสถานะ (การโอนสถานะการเป็นตัวแทน) นอกจากนี้ยังเป็นการดีที่จะทำสิ่งนี้ใน Java คิดเกี่ยวกับสิ่งนี้จากมุมมองของกฎข้อ 2 เมื่อวิธีการได้รับวัตถุพารามิเตอร์สถานะ หากสถานะถูกส่งผ่านวัตถุดังกล่าวแทนที่จะใช้สถานะจากภายนอกสิ่งต่าง ๆ จะง่ายขึ้น ใช้ JDBC เป็นตัวอย่าง ตัวอย่างต่อไปนี้อ่านเคอร์เซอร์จากโปรแกรมที่เก็บไว้
Callablestatement S = Connection.preparecall ("{? = ... }"); // verbose การจัดการสถานะคำสั่ง: S.Registeroutparameter (1, Cursor); S.Setstring (2, "ABC"); S.Execute ()สิ่งนี้ทำให้ JDBC API แปลกมาก แต่ละวัตถุมีสถานะและใช้งานยาก โดยเฉพาะมีสองปัญหาหลัก:
กฎ: การใช้งานเพิ่มเติมในรูปแบบการทำงาน ถ่ายโอนสถานะผ่านพารามิเตอร์วิธีการ รัฐวัตถุปฏิบัติการน้อยมาก
นี่เป็นวิธีที่ค่อนข้างง่ายในการใช้งาน ในระบบวัตถุที่ค่อนข้างซับซ้อนคุณสามารถบรรลุการปรับปรุงประสิทธิภาพที่สำคัญตราบใดที่คุณตัดสินใจอย่างเท่าเทียมกันในวิธีการเท่ากัน () ของวัตถุทั้งหมด:
@Overridepublic บูลีนเท่ากับ (วัตถุอื่น ๆ ) {ถ้า (นี่ == อื่น ๆ ) กลับมาจริง; // ตรรกะการตัดสินความเท่าเทียมกันอื่น ๆ ... }โปรดทราบว่าการตรวจสอบลัดวงจรอื่น ๆ อาจเกี่ยวข้องกับการตรวจสอบค่า NULL ดังนั้นควรเพิ่ม:
@Overridepublic บูลีนเท่ากับ (วัตถุอื่น ๆ ) {ถ้า (นี่ == อื่น ๆ ) กลับมาจริง; ถ้า (อื่น == null) ส่งคืนเท็จ; // ส่วนที่เหลือของตรรกะความเท่าเทียม ... }กฎ: ใช้วงจรสั้น ๆ ในวิธีการทั้งหมดของคุณ () เพื่อปรับปรุงประสิทธิภาพ
บางคนอาจไม่เห็นด้วยกับเรื่องนี้เพราะการทำให้วิธีการเริ่มต้นเป็นครั้งสุดท้ายนั้นตรงกันข้ามกับนิสัยของนักพัฒนา Java แต่ถ้าคุณควบคุมรหัสได้อย่างสมบูรณ์มันจะถูกต้องอย่างแน่นอนที่จะทำให้วิธีการเริ่มต้นเป็นขั้นสุดท้าย:
เหมาะอย่างยิ่งสำหรับวิธีการคงที่ซึ่งในกรณีนี้ "ซ้อนทับ" (การบดเคี้ยวจริง) แทบจะไม่ทำงาน ฉันเพิ่งเจอตัวอย่างของวิธีการแรเงาที่แย่มากใน Apache Tika ลองดู:
TikainputStream ขยาย TaggedInputStream เพื่อปกปิดวิธีการรับ () ของมันด้วยการใช้งานที่ค่อนข้างแตกต่างกัน
ซึ่งแตกต่างจากวิธีการทั่วไปวิธีการคงที่ไม่สามารถเขียนทับกันได้เนื่องจากการโทรถูกผูกไว้กับการเรียกวิธีการคงที่ในเวลาที่รวบรวม หากคุณโชคร้ายคุณอาจได้รับวิธีที่ผิดโดยไม่ตั้งใจ
กฎ: หากคุณสามารถควบคุม API ของคุณได้อย่างสมบูรณ์ให้ทำวิธีการมากที่สุดเท่าที่จะเป็นไปได้
ในโอกาสพิเศษคุณสามารถใช้วิธีพารามิเตอร์ตัวแปร "ยอมรับทั้งหมด" เพื่อรับวัตถุ ... พารามิเตอร์:
เป็นโมฆะยอมรับ (วัตถุ ... ทั้งหมด);
การเขียนวิธีการดังกล่าวทำให้ความรู้สึกของ JavaScript เล็กน้อยไปยังระบบนิเวศ Java แน่นอนคุณอาจต้องการ จำกัด ประเภทจริงตามสถานการณ์จริงเช่นสตริง…. เนื่องจากคุณไม่ต้องการ จำกัด มากเกินไปคุณอาจคิดว่าเป็นความคิดที่ดีที่จะแทนที่วัตถุด้วยทั่วไป T:
เป็นโมฆะยอมรับ (t ... ทั้งหมด);
แต่ไม่ใช่ t จะถูกอนุมานเป็นวัตถุเสมอ ในความเป็นจริงคุณอาจคิดว่าไม่สามารถใช้ยาชื่อสามัญในวิธีการข้างต้น ที่สำคัญคุณอาจคิดว่าคุณสามารถโอเวอร์โหลดวิธีการข้างต้นได้ แต่คุณทำไม่ได้:
เป็นโมฆะยอมรับ (t ... ทั้งหมด); เป็นโมฆะยอมรับ (ข้อความสตริง, t ... ทั้งหมด);
ดูเหมือนว่าคุณสามารถส่งข้อความสตริงไปยังวิธีการ แต่จะเกิดอะไรขึ้นกับสายนี้?
ยอมรับ ("ข้อความ", 123, "ABC");คอมไพเลอร์ infers t เป็น <? ขยาย serializable & เปรียบเทียบได้ <? >> ซึ่งจะทำให้การโทรไม่ชัดเจน!
ดังนั้นเมื่อใดก็ตามที่คุณมีลายเซ็น "ยอมรับทั้งหมด" (แม้ว่าจะเป็นทั่วไป) คุณจะไม่สามารถพิมพ์โอเวอร์โหลดได้อย่างปลอดภัย ผู้บริโภค API อาจปล่อยให้คอมไพเลอร์ "บังเอิญ" เลือกวิธี "ถูกต้อง" เมื่อพวกเขาโชคดี แต่ก็เป็นไปได้ที่จะใช้วิธีการยอมรับทั้งหมดหรือไม่เรียกวิธีการใด ๆ
กฎ : หลีกเลี่ยงลายเซ็น "ยอมรับทั้งหมด" หากเป็นไปได้ ถ้าไม่อย่าโอเวอร์โหลดวิธีการดังกล่าว
Java เป็นสัตว์ร้าย ซึ่งแตกต่างจากภาษาในอุดมคติอื่น ๆ มันค่อยๆพัฒนาไปสู่สิ่งที่เป็นอยู่ทุกวันนี้ นี่อาจเป็นสิ่งที่ดีเพราะด้วยความเร็วในการพัฒนา Java มีคำเตือนหลายร้อยคำเตือนและคำเตือนเหล่านี้สามารถเข้าใจได้ผ่านประสบการณ์มานานหลายปี
คอยติดตามรายการสิบอันดับแรกในหัวข้อนี้!
ลิงค์ต้นฉบับ: JOOQ Translation: importNew.com - ligen
ลิงค์การแปล: http://www.importnew.com/10138.html
[โปรดเก็บแหล่งที่มาดั้งเดิมนักแปลและลิงค์การแปลเมื่อพิมพ์ซ้ำ -