บทเรียน Java สามบทเรียนที่ฉันได้เรียนรู้อย่างยากลำบาก
ที่มา: Medium Learning Java นั้นยาก ฉันเรียนรู้จากความผิดพลาดของฉัน ตอนนี้คุณก็สามารถเรียนรู้จากความผิดพลาดและประสบการณ์อันขมขื่นของฉันได้เช่นกัน ซึ่งคุณไม่จำเป็นต้องมี
1. แลมบ์ดาอาจทำให้เกิดปัญหาได้
Lambdas มักจะมีโค้ดยาวเกิน 4 บรรทัดและมีขนาดใหญ่เกินกว่าที่คาดไว้ นี่เป็นภาระของหน่วยความจำในการทำงาน คุณจำเป็นต้องเปลี่ยนตัวแปรจาก lambda หรือไม่? คุณไม่สามารถทำอย่างนั้นได้ ทำไม หากแลมบ์ดาสามารถเข้าถึงตัวแปรไซต์การโทร ปัญหาเธรดอาจเกิดขึ้นได้ ดังนั้นคุณไม่สามารถเปลี่ยนตัวแปรจากแลมบ์ดาได้ แต่เส้นทางแห่งความสุขในแลมบ์ดาก็ใช้ได้ดี หลังจากรันไทม์ล้มเหลว คุณจะได้รับคำตอบนี้:at [CLASS].lambda$null$2([CLASS].java:85)
at [CLASS]$$Lambda$64/730559617.accept(Unknown Source)
การติดตามสแต็กแลมบ์ดาเป็นเรื่องยาก ชื่อเหล่านี้สร้างความสับสนและยากต่อการติดตามและแก้ไขข้อบกพร่อง แลมบ์ดามากขึ้น = มีการติดตามสแต็กมากขึ้น วิธีที่ดีที่สุดในการดีบักแลมบ์ดาคืออะไร ใช้ผลลัพธ์ระดับกลาง
map(elem -> {
int result = elem.getResult();
return result;
});
อีกวิธีที่ดีคือการใช้เทคนิคการดีบัก IntelliJ ขั้นสูง ใช้ TAB เพื่อเลือกโค้ดที่คุณต้องการแก้ไขและรวมเข้ากับผลลัพธ์ระดับกลาง “เมื่อเราหยุดที่บรรทัดที่มี lambda ถ้าเรากด F7 (ก้าวเข้าไป) IntelliJ จะเน้นส่วนที่จำเป็นต้องแก้ไขจุดบกพร่อง เราสามารถเปลี่ยนบล็อกเป็นการแก้ไขข้อบกพร่องโดยใช้ Tab และเมื่อเราตัดสินใจได้แล้ว ให้กด F7 อีกครั้ง” จะเข้าถึงตัวแปรไซต์การโทรจากแลมบ์ดาได้อย่างไร คุณสามารถเข้าถึงได้เฉพาะตัวแปรสุดท้ายหรือตัวแปรสุดท้ายเท่านั้น คุณต้องสร้าง wrapper รอบๆ ตัวแปรตำแหน่งการโทร ไม่ว่าจะใช้AtomicTypeหรือใช้ประเภทของคุณเอง คุณสามารถเปลี่ยน wrapper ตัวแปรที่สร้างขึ้นด้วย lambda วิธีแก้ปัญหาการติดตามสแต็ก? ใช้ฟังก์ชันที่มีชื่อ ด้วยวิธีนี้ คุณสามารถค้นหาโค้ดที่รับผิดชอบ ตรวจสอบตรรกะ และแก้ไขปัญหาได้อย่างรวดเร็ว ใช้ฟังก์ชันที่มีชื่อเพื่อตัดการติดตามสแต็กที่เป็นความลับ แลมบ์ดาตัวเดียวกันซ้ำแล้วซ้ำอีกหรือไม่? วางไว้ในฟังก์ชันที่มีชื่อ คุณจะมีจุดอ้างอิงเพียงจุดเดียว แลมบ์ดาแต่ละตัวได้รับฟังก์ชันที่สร้างขึ้น ซึ่งทำให้ยากต่อการติดตาม
lambda$yourNamedFunction
lambda$0
ฟังก์ชันที่มีชื่อช่วยแก้ปัญหาอื่น แลมบ์ดาตัวใหญ่ ฟังก์ชันที่มีชื่อจะแยกแลมบ์ดาขนาดใหญ่ สร้างโค้ดขนาดเล็ก และสร้างฟังก์ชันที่เสียบได้
.map(this::namedFunc1).filter(this::namedFilter1).map(this::namedFunc2)
2. ปัญหาเกี่ยวกับรายการ
คุณต้องทำงานกับรายการ ( Lists ) คุณต้องมีHashMapสำหรับข้อมูล สำหรับบทบาท คุณจะต้องมีTreeMap รายการดำเนินต่อไป และไม่มีทางที่คุณจะหลีกเลี่ยงการทำงานกับคอลเลกชันได้ จะทำรายการได้อย่างไร? คุณต้องการรายการประเภทใด? มันควรจะไม่เปลี่ยนรูปหรือไม่แน่นอน? คำตอบทั้งหมดเหล่านี้ส่งผลต่ออนาคตของโค้ดของคุณ เลือกรายการที่ถูกต้องล่วงหน้าเพื่อที่คุณจะได้ไม่เสียใจในภายหลัง Arrays::asListสร้างรายการ "จากต้นทางถึงปลายทาง" คุณไม่สามารถทำอะไรกับรายการนี้ได้? คุณไม่สามารถปรับขนาดได้ เขาไม่เปลี่ยนแปลง คุณสามารถทำอะไรที่นี่? ระบุองค์ประกอบ การเรียงลำดับ หรือการดำเนินการอื่นๆ ที่ไม่ส่งผลต่อขนาด ใช้Arrays::asListอย่างระมัดระวังเนื่องจากขนาดของมันไม่เปลี่ยนรูปแต่เนื้อหาไม่เปลี่ยนรูป new ArrayList()สร้างรายการ "ไม่แน่นอน" ใหม่ รายการที่สร้างขึ้นรองรับการดำเนินการใดบ้าง เพียงเท่านี้และนี่คือเหตุผลที่ต้องระวัง สร้างรายการที่ไม่แน่นอนจากรายการที่ไม่เปลี่ยนรูปโดยใช้new ArrayList( ) List::ofสร้างคอลเลกชันที่ “ไม่เปลี่ยนรูป” ขนาดและเนื้อหาไม่เปลี่ยนแปลงภายใต้เงื่อนไขบางประการ หากเนื้อหาเป็นข้อมูลพื้นฐาน เช่นintรายการนั้นจะไม่เปลี่ยนรูป ลองดูตัวอย่างต่อไปนี้@Test
public void testListOfBuilders() {
System.out.println("### TESTING listOF with mutable content ###");
StringBuilder one = new StringBuilder();
one.append("a");
StringBuilder two = new StringBuilder();
two.append("a");
List<StringBuilder> asList = List.of(one, two);
asList.get(0).append("123");
System.out.println(asList.get(0).toString());
}
### รายการทดสอบ OF พร้อมเนื้อหาที่ไม่แน่นอน ### a123
คุณต้องสร้างวัตถุที่ไม่เปลี่ยนรูปและแทรกลงในList:: of อย่างไรก็ตามList::ofไม่ได้ให้การรับประกันใดๆ ของการไม่เปลี่ยนรูป List::ofให้ความไม่เปลี่ยนรูป ความน่าเชื่อถือ และความสามารถในการอ่าน รู้ว่าเมื่อใดควรใช้โครงสร้างที่ไม่เปลี่ยนรูป และเมื่อใดควรใช้โครงสร้างที่ไม่เปลี่ยนรูป รายการอาร์กิวเมนต์ที่ไม่ควรเปลี่ยนแปลงควรอยู่ในรายการที่ไม่เปลี่ยนรูป รายการที่ไม่แน่นอนสามารถเป็นรายการที่ไม่แน่นอนได้ ทำความเข้าใจว่าคุณต้องการคอลเลกชันใดเพื่อสร้างโค้ดที่เชื่อถือได้
3. คำอธิบายประกอบทำให้คุณช้าลง
คุณใช้คำอธิบายประกอบหรือไม่? คุณเข้าใจพวกเขาไหม? คุณรู้ไหมว่าพวกเขาทำอะไร? หากคุณคิดว่า คำอธิบาย ประกอบที่บันทึกไว้เหมาะสำหรับทุกวิธี แสดงว่าคุณคิดผิด ฉันใช้Loggedเพื่อบันทึกอาร์กิวเมนต์ของวิธีการ ฉันแปลกใจที่มันไม่ได้ผล@Transaction
@Method("GET")
@PathElement("time")
@PathElement("date")
@Autowired
@Secure("ROLE_ADMIN")
public void manage(@Qualifier('time')int time) {
...
}
เกิดอะไรขึ้นกับรหัสนี้? มีรายละเอียดการกำหนดค่ามากมายที่นี่ คุณจะพบสิ่งนี้หลายครั้ง การกำหนดค่าผสมกับโค้ดปกติ ไม่เลวในตัวเอง แต่มันดึงดูดสายตาของคุณ จำเป็นต้องมีคำอธิบายประกอบเพื่อลดโค้ดสำเร็จรูป คุณไม่จำเป็นต้องเขียนตรรกะการบันทึกสำหรับแต่ละปลายทาง ไม่ต้องตั้งค่าธุรกรรมใช้@Transactional คำอธิบายประกอบลดรูปแบบโดยการแยกโค้ด ไม่มีผู้ชนะที่ชัดเจนที่นี่เนื่องจากทั้งคู่อยู่ในเกม ฉันยังคงใช้ XML และคำอธิบายประกอบ เมื่อคุณพบรูปแบบที่ซ้ำกัน วิธีที่ดีที่สุดคือย้ายตรรกะไปไว้ในคำอธิบายประกอบ ตัวอย่างเช่น การบันทึกเป็นตัวเลือกคำอธิบายประกอบที่ดี คุณธรรม: อย่าใช้คำอธิบายประกอบมากเกินไปและอย่าลืม XML
โบนัส: คุณอาจประสบปัญหากับตัวเลือกเสริม
คุณจะใช้orElseจากOptional พฤติกรรมที่ไม่พึง ประสงค์เกิดขึ้นเมื่อคุณไม่ผ่าน ค่าคงที่ orElse คุณควรตระหนักถึงสิ่งนี้เพื่อป้องกันปัญหาในอนาคต ลองดูตัวอย่างบางส่วน เมื่อgetValue(x)ส่งคืนค่าgetValue(y) จะถูกดำเนิน การ วิธีการในorElseจะถูกดำเนินการหากgetValue(x) ส่งคืนค่า Optionalที่ไม่ว่างเปล่าgetValue(x).orElse(getValue(y)
.orElseThrow(() -> new NotFoundException("value not present")));
public Optional<Value> getValue(Source s)
{
System.out.println("Source: " + s.getName());
// returns value from s source
}
// when getValue(x) is present system will output
Source: x
Source: y
ใช้orElseGet _ มันจะไม่รันโค้ดสำหรับOptionals ที่ไม่ว่าง เปล่า
getValue(x).orElseGet(() -> getValue(y)
.orElseThrow(() -> new NotFoundException("value not present")));
public Optional<Value> getValue(Source s)
{
System.out.println("Source: " + s.getName());
// returns value from s source
}
// when getValue(x) is present system will output
Source: x
บทสรุป
การเรียนรู้ Java เป็นเรื่องยาก คุณไม่สามารถเรียนรู้ Java ได้ภายใน 24 ชั่วโมง ฝึกฝนทักษะของคุณ ใช้เวลา เรียนรู้ และเก่งในงานของคุณวิธีใช้หลักการ SOLID ในโค้ด
ที่มา: Cleanthecode การเขียนโค้ดที่เชื่อถือได้ต้องใช้หลักการ SOLID เมื่อถึงจุดหนึ่งเราทุกคนก็ต้องเรียนรู้วิธีการเขียนโปรแกรม และขอพูดตรงๆ พวกเราโง่มาก และรหัสของเราก็เหมือนกัน ขอบคุณพระเจ้าที่เรามี SOLID
หลักการที่มั่นคง
แล้วคุณจะเขียนโค้ด SOLID ได้อย่างไร? จริงๆแล้วมันง่าย คุณเพียงแค่ต้องปฏิบัติตามกฎห้าข้อเหล่านี้:- หลักการความรับผิดชอบเดียว
- หลักการเปิด-ปิด
- หลักการทดแทน Liskov
- หลักการแยกส่วนต่อประสาน
- หลักการผกผันการพึ่งพา
หลักการความรับผิดชอบเดียว
ในหนังสือของเขา โรเบิร์ต ซี. มาร์ตินบรรยายหลักธรรมนี้ดังนี้ “ชั้นเรียนควรมีเหตุผลเดียวเท่านั้นในการเปลี่ยนแปลง” ลองดูสองตัวอย่างด้วยกัน1. สิ่งที่ไม่ควรทำ
เรามีคลาสที่เรียกว่าUserซึ่งอนุญาตให้ผู้ใช้ทำสิ่งต่อไปนี้:- ลงทะเบียนบัญชี
- เข้าสู่ระบบ
- รับการแจ้งเตือนในครั้งแรกที่คุณเข้าสู่ระบบ
2. จะทำอย่างไร
ลองนึกภาพคลาสที่ควรแสดงการแจ้งเตือนให้กับผู้ใช้ใหม่FirstUseNotification จะประกอบด้วย 3 ฟังก์ชั่น คือ- ตรวจสอบว่ามีการแสดงการแจ้งเตือนแล้วหรือไม่
- แสดงการแจ้งเตือน
- ทำเครื่องหมายการแจ้งเตือนตามที่แสดงไว้แล้ว
หลักการเปิด-ปิด
หลักการเปิด-ปิดกำหนดโดย Bertrand Meyer ว่า “วัตถุซอฟต์แวร์ (คลาส โมดูล ฟังก์ชัน ฯลฯ) ควรเปิดเพื่อขยาย แต่ปิดเพื่อแก้ไข” หลักการนี้จริงๆ แล้วง่ายมาก คุณต้องเขียนโค้ดของคุณเพื่อให้สามารถเพิ่มคุณสมบัติใหม่ได้โดยไม่ต้องเปลี่ยนซอร์สโค้ด ซึ่งจะช่วยป้องกันสถานการณ์ที่คุณต้องเปลี่ยนคลาสที่ขึ้นอยู่กับคลาสที่คุณแก้ไข อย่างไรก็ตามหลักการนี้นำไปปฏิบัติได้ยากกว่ามาก เมเยอร์แนะนำให้ใช้มรดก แต่มันนำไปสู่ความสัมพันธ์ที่แน่นแฟ้น เราจะหารือเรื่องนี้ในหลักการของการแยกส่วนต่อประสานและหลักการของการผกผันการพึ่งพา ดังนั้น Martin จึงเกิดแนวทางที่ดีกว่า: ใช้ความหลากหลาย แทนที่จะใช้การสืบทอดแบบดั้งเดิม วิธีการนี้ใช้คลาสฐานเชิงนามธรรม ด้วยวิธีนี้ ข้อมูลจำเพาะของการสืบทอดสามารถนำกลับมาใช้ใหม่ได้ในขณะที่ไม่จำเป็นต้องดำเนินการ สามารถเขียนอินเทอร์เฟซได้เพียงครั้งเดียวแล้วปิดเพื่อทำการเปลี่ยนแปลง ฟังก์ชั่นใหม่จะต้องใช้อินเทอร์เฟซนี้และขยายออกไปหลักการทดแทน Liskov
หลักการนี้คิดค้นโดย Barbara Liskov ผู้ชนะรางวัลทัวริงจากการมีส่วนร่วมในภาษาการเขียนโปรแกรมและวิธีการซอฟต์แวร์ ในบทความของเธอ เธอได้กำหนดหลักการของเธอไว้ดังนี้: “ออบเจ็กต์ในโปรแกรมควรถูกแทนที่ด้วยอินสแตนซ์ของชนิดย่อยได้ โดยไม่กระทบต่อการทำงานของโปรแกรมที่ถูกต้อง” ลองดูหลักการนี้ในฐานะโปรแกรมเมอร์ ลองนึกภาพเรามีสี่เหลี่ยมจัตุรัส มันอาจเป็นสี่เหลี่ยมจัตุรัส ซึ่งฟังดูสมเหตุสมผลเพราะสี่เหลี่ยมจัตุรัสเป็นรูปทรงพิเศษของสี่เหลี่ยมมุมฉาก นี่คือจุดที่หลักการทดแทน Liskov เข้ามาช่วยเหลือ เมื่อใดก็ตามที่คุณคาดว่าจะเห็นสี่เหลี่ยมในโค้ดของคุณ ก็เป็นไปได้ที่สี่เหลี่ยมจะปรากฏเช่นกัน ทีนี้ลองนึกภาพ สี่เหลี่ยมผืนผ้าของคุณมี วิธี SetWidthและSetHeight ซึ่งหมายความว่าสแควร์ก็ต้องการวิธีการเหล่านี้เช่นกัน น่าเสียดายที่สิ่งนี้ไม่สมเหตุสมผลเลย ซึ่งหมายความว่าหลักการทดแทน Liskov ถูกละเมิดที่นี่หลักการแยกส่วนต่อประสาน
เช่นเดียวกับหลักการทั้งหมด หลักการของการแยกอินเทอร์เฟซนั้นง่ายกว่าที่คิดไว้มาก: “อินเทอร์เฟซเฉพาะไคลเอนต์จำนวนมากดีกว่าอินเทอร์เฟซทั่วไปเพียงอินเทอร์เฟซเดียว” เช่นเดียวกับหลักการความรับผิดชอบเดียว เป้าหมายคือการลดผลข้างเคียงและจำนวนการเปลี่ยนแปลงที่จำเป็น แน่นอนว่าไม่มีใครเขียนโค้ดดังกล่าวโดยตั้งใจ แต่ก็พบเจอได้ง่าย จำจัตุรัสจากหลักการเดิมได้ไหม? ตอนนี้ลองจินตนาการว่าเราตัดสินใจที่จะดำเนินการตามแผนของเรา: เราสร้างสี่เหลี่ยมจัตุรัสจากสี่เหลี่ยมผืนผ้า ตอนนี้เรากำลังบังคับให้ Square ใช้setWidthและsetHeightซึ่งอาจจะไม่ทำอะไรเลย หากพวกเขาทำอย่างนั้น เราอาจจะทำลายบางสิ่งบางอย่างเพราะความกว้างและความสูงไม่ใช่สิ่งที่เราคาดหวัง โชคดีสำหรับเรา นี่หมายความว่าเราไม่ได้ละเมิดหลักการแทนที่ Liskov อีกต่อไป เนื่องจากตอนนี้เราอนุญาตให้ใช้สี่เหลี่ยมจัตุรัสได้ทุกที่ที่เราใช้สี่เหลี่ยมผืนผ้า อย่างไรก็ตาม สิ่งนี้สร้างปัญหาใหม่: ขณะนี้เรากำลังละเมิดหลักการของการแยกอินเทอร์เฟซ เราบังคับให้คลาสที่ได้รับใช้ฟังก์ชันการทำงานที่คลาสนั้นเลือกที่จะไม่ใช้หลักการผกผันการพึ่งพา
หลักการสุดท้ายนั้นเรียบง่าย: โมดูลระดับสูงควรสามารถนำมาใช้ซ้ำได้ และไม่ควรได้รับผลกระทบจากการเปลี่ยนแปลงโมดูลระดับต่ำ- A. โมดูลระดับสูงไม่ควรขึ้นอยู่กับโมดูลระดับต่ำ ทั้งสองต้องขึ้นอยู่กับนามธรรม (เช่นอินเทอร์เฟซ)
- B. นามธรรมไม่ควรขึ้นอยู่กับรายละเอียด รายละเอียด (การใช้งานคอนกรีต) จะต้องขึ้นอยู่กับนามธรรม
- โมดูลระดับสูง ขึ้นอยู่กับนามธรรม
- โมดูลระดับต่ำขึ้นอยู่กับสิ่งที่เป็นนามธรรมเดียวกัน
GO TO FULL VERSION