เมื่อเรียนรู้การเขียนโปรแกรม จะใช้เวลามากในการเขียนโค้ด นักพัฒนามือใหม่ส่วนใหญ่เชื่อว่านี่คือกิจกรรมในอนาคตของพวกเขา นี่เป็นเรื่องจริงบางส่วน แต่งานของโปรแกรมเมอร์ยังรวมถึงการบำรุงรักษาและการปรับโครงสร้างโค้ดใหม่ด้วย วันนี้เราจะพูดถึงการปรับโครงสร้างใหม่
ตัวอย่างลำดับชั้นที่ไม่ถูกต้อง:
การรีแฟคเตอร์ในหลักสูตร JavaRush
หลักสูตร JavaRush ครอบคลุมหัวข้อการปรับโครงสร้างใหม่สองครั้ง:- ความท้าทายครั้งใหญ่ในระดับ 5 ของภารกิจมัลติเธรด
- การบรรยายเรื่องการปรับ โครงสร้างใหม่ใน Intellij IDEA ที่ระดับ 9 ของภารกิจ Java Collections
การปรับโครงสร้างใหม่คืออะไร?
นี่คือการเปลี่ยนแปลงโครงสร้างของโค้ดโดยไม่เปลี่ยนฟังก์ชันการทำงาน ตัวอย่างเช่น มีวิธีที่เปรียบเทียบตัวเลข 2 ตัวและส่งกลับค่าจริงหากค่าแรกมากกว่า และคืนค่าเป็นเท็จ หากไม่เป็นเช่นนั้น:public boolean max(int a, int b) {
if(a > b) {
return true;
} else if(a == b) {
return false;
} else {
return false;
}
}
ผลลัพธ์ที่ได้คือโค้ดที่ยุ่งยากมาก แม้แต่ผู้เริ่มต้นก็ไม่ค่อยเขียนอะไรแบบนี้ แต่ก็มีความเสี่ยงเช่นกัน ดูเหมือนว่าเหตุใดจึงมีบล็อกอยู่ที่นี่if-else
หากคุณสามารถเขียนวิธีที่ 6 ให้สั้นลงได้:
public boolean max(int a, int b) {
return a>b;
}
ตอนนี้วิธีนี้ดูเรียบง่ายและสวยงาม แม้ว่าจะทำแบบเดียวกับตัวอย่างข้างต้นก็ตาม นี่คือวิธีการปรับโครงสร้างใหม่: เปลี่ยนโครงสร้างของโค้ดโดยไม่ส่งผลกระทบต่อสาระสำคัญ มีวิธีและเทคนิคการปรับโครงสร้างใหม่มากมาย ซึ่งเราจะพิจารณาในรายละเอียดเพิ่มเติม
เหตุใดจึงต้องมีการปรับโครงสร้างใหม่?
มีสาเหตุหลายประการ ตัวอย่างเช่นการแสวงหาความเรียบง่ายและรัดกุมของโค้ด ผู้เสนอทฤษฎีนี้เชื่อว่าโค้ดควรกระชับที่สุดเท่าที่จะเป็นไปได้ แม้ว่าจะต้องอาศัยคำอธิบายหลายสิบบรรทัดจึงจะเข้าใจได้ นักพัฒนารายอื่นเชื่อว่าโค้ดควรได้รับการปรับโครงสร้างใหม่เพื่อให้สามารถเข้าใจได้โดยมีจำนวนความคิดเห็นน้อยที่สุด แต่ละทีมเลือกตำแหน่งของตัวเอง แต่เราต้องจำไว้ว่า การปรับ โครงสร้าง ใหม่ไม่ใช่การลดลง เป้าหมายหลักคือการปรับปรุงโครงสร้างของโค้ด สามารถรวมวัตถุประสงค์หลายประการไว้ในเป้าหมายระดับโลกนี้ได้:- การปรับโครงสร้างใหม่ช่วยเพิ่มความเข้าใจในโค้ดที่เขียนโดยนักพัฒนารายอื่น
- ช่วยค้นหาและแก้ไขข้อผิดพลาด
- ช่วยให้คุณเพิ่มความเร็วของการพัฒนาซอฟต์แวร์
- ปรับปรุงองค์ประกอบของซอฟต์แวร์โดยรวม
“กลิ่นรหัส”
เมื่อโค้ดต้องมีการปรับโครงสร้างใหม่ พวกเขาบอกว่ามัน "มีกลิ่น" แน่นอนว่าไม่ใช่ตามตัวอักษร แต่โค้ดดังกล่าวดูไม่ดีนักจริงๆ ด้านล่างนี้เราจะพิจารณาเทคนิคการปรับโครงสร้างหลักในระยะเริ่มแรกองค์ประกอบขนาดใหญ่โดยไม่จำเป็น
มีคลาสและวิธีการที่ยุ่งยากซึ่งไม่สามารถทำงานได้อย่างมีประสิทธิภาพอย่างมีประสิทธิภาพเนื่องจากมีขนาดใหญ่ชั้นเรียนใหญ่
คลาสดังกล่าวมีโค้ดจำนวนมากและมีวิธีการที่แตกต่างกันมากมาย โดยปกติแล้วนักพัฒนาจะเพิ่มฟีเจอร์ให้กับคลาสที่มีอยู่ได้ง่ายกว่าการสร้างคลาสใหม่ ซึ่งเป็นสาเหตุที่ทำให้คลาสเติบโตขึ้น ตามกฎแล้ว ฟังก์ชันการทำงานของคลาสนี้มีการโอเวอร์โหลด ในกรณีนี้ การแยกส่วนของฟังก์ชันการทำงานออกเป็นคลาสที่แยกกันจะช่วยได้ เราจะพูดถึงเรื่องนี้โดยละเอียดมากขึ้นในส่วนเทคนิคการรีแฟคเตอร์วิธีการที่ยิ่งใหญ่
“กลิ่น” นี้เกิดขึ้นเมื่อนักพัฒนาเพิ่มฟังก์ชันการทำงานใหม่ให้กับวิธีการ “เหตุใดฉันจึงควรแยกการตรวจสอบพารามิเตอร์เป็นอีกวิธีหนึ่งหากฉันสามารถเขียนไว้ที่นี่ได้”, “เหตุใดจึงจำเป็นต้องแยกวิธีการค้นหาองค์ประกอบสูงสุดในอาร์เรย์ ปล่อยไว้ที่นี่เถอะ วิธีนี้ทำให้โค้ดชัดเจนขึ้น” และความเข้าใจผิดอื่นๆ มีกฎสองข้อในการปรับโครงสร้างวิธีการขนาดใหญ่:- เมื่อเขียนเมธอด หากคุณต้องการเพิ่มความคิดเห็นให้กับโค้ด คุณต้องแยกฟังก์ชันนี้ออกเป็นเมธอดที่แยกจากกัน
- หากวิธีหนึ่งใช้โค้ดมากกว่า 10-15 บรรทัด คุณควรระบุงานและงานย่อยที่วิธีนั้นดำเนินการ และพยายามแยกงานย่อยออกเป็นวิธีที่แยกจากกัน
- แยกส่วนของฟังก์ชันการทำงานของวิธีการออกเป็นวิธีการแยกต่างหาก
- ถ้าตัวแปรท้องถิ่นไม่อนุญาตให้คุณแยกส่วนของฟังก์ชันการทำงาน คุณสามารถส่งผ่านวัตถุทั้งหมดไปยังวิธีอื่นได้
การใช้ข้อมูลพื้นฐานหลายประเภท
โดยทั่วไป ปัญหานี้เกิดขึ้นเมื่อจำนวนเขตข้อมูลในการจัดเก็บข้อมูลในชั้นเรียนเพิ่มขึ้นเมื่อเวลาผ่านไป ตัวอย่างเช่น หากคุณใช้ประเภทดั้งเดิมแทนออบเจ็กต์ขนาดเล็กในการจัดเก็บข้อมูล (สกุลเงิน วันที่ หมายเลขโทรศัพท์ ฯลฯ) หรือค่าคงที่เพื่อเข้ารหัสข้อมูลใดๆ แนวปฏิบัติที่ดีในกรณีนี้คือการจัดกลุ่มฟิลด์ตามตรรกะและวางไว้ในคลาสที่แยกจากกัน (การเลือกคลาส) คุณยังสามารถรวมวิธีการประมวลผลข้อมูลนี้ในชั้นเรียนได้ด้วยรายการตัวเลือกยาว
ข้อผิดพลาดที่ค่อนข้างบ่อยโดยเฉพาะอย่างยิ่งเมื่อใช้ร่วมกับวิธีการขนาดใหญ่ มักจะเกิดขึ้นหากฟังก์ชันการทำงานของวิธีการโอเวอร์โหลด หรือวิธีการนี้รวมอัลกอริธึมหลายอย่างเข้าด้วยกัน รายการพารามิเตอร์ที่ยาวเป็นเรื่องยากที่จะเข้าใจ และวิธีการดังกล่าวไม่สะดวกในการใช้งาน ดังนั้นจึงเป็นการดีกว่าที่จะถ่ายโอนวัตถุทั้งหมด หากออบเจ็กต์มีข้อมูลไม่เพียงพอ ก็คุ้มค่าที่จะใช้ออบเจ็กต์ทั่วไปหรือแยกฟังก์ชันการทำงานของเมธอดเพื่อประมวลผลข้อมูลที่เกี่ยวข้องกับตรรกะกลุ่มข้อมูล
กลุ่มข้อมูลที่เกี่ยวข้องกันทางตรรกะมักจะปรากฏในโค้ด ตัวอย่างเช่น พารามิเตอร์การเชื่อมต่อกับฐานข้อมูล (URL, ชื่อผู้ใช้, รหัสผ่าน, ชื่อสคีมา ฯลฯ) หากไม่สามารถลบฟิลด์เดียวออกจากรายการองค์ประกอบได้ รายการนั้นก็คือกลุ่มของข้อมูลที่ต้องอยู่ในคลาสแยกต่างหาก (การเลือกคลาส)โซลูชั่นที่ทำให้แนวคิดของ OOP เสียไป
“กลิ่น” ประเภทนี้เกิดขึ้นเมื่อนักพัฒนาละเมิดการออกแบบ OOP สิ่งนี้จะเกิดขึ้นหากเขาไม่เข้าใจความสามารถของกระบวนทัศน์นี้อย่างถ่องแท้ ใช้มันอย่างไม่สมบูรณ์หรือไม่ถูกต้องการปฏิเสธการรับมรดก
หากคลาสย่อยใช้ฟังก์ชันของคลาสพาเรนต์เพียงเล็กน้อย ก็จะมีกลิ่นเหมือนลำดับชั้นที่ไม่ถูกต้อง โดยทั่วไป ในกรณีนี้ วิธีการที่ไม่จำเป็นจะไม่ถูกแทนที่หรือข้อยกเว้นถูกส่งออกไป หากคลาสนั้นสืบทอดมาจากคลาสอื่น นี่หมายถึงการใช้งานฟังก์ชันของมันเกือบทั้งหมด ตัวอย่างลำดับชั้นที่ถูกต้อง:

คำสั่งสลับ
มีอะไรผิดปกติกับตัวดำเนินการswitch
? มันแย่เมื่อการออกแบบซับซ้อนมาก if
รวมถึง บล็อก ที่ซ้อนกันหลายบล็อกด้วย
คลาสทางเลือกที่มีอินเทอร์เฟซต่างกัน
โดยพื้นฐานแล้วหลายคลาสทำสิ่งเดียวกัน แต่วิธีการของคลาสเหล่านั้นมีชื่อต่างกันสนามชั่วคราว
หากชั้นเรียนมีฟิลด์ชั่วคราวที่วัตถุต้องการเป็นครั้งคราวเท่านั้นเมื่อเต็มไปด้วยค่าและเวลาที่เหลือนั้นว่างเปล่าหรือพระเจ้าห้ามnull
ดังนั้นรหัสจะ "มีกลิ่น" และการออกแบบดังกล่าวน่าสงสัย การตัดสินใจ.
กลิ่นที่ทำให้การปรับเปลี่ยนทำได้ยาก
“กลิ่น” เหล่านี้รุนแรงมากขึ้น ส่วนที่เหลือทำให้ความเข้าใจโค้ดลดลงเป็นหลัก ในขณะที่สิ่งเหล่านี้ไม่สามารถแก้ไขได้ เมื่อแนะนำฟีเจอร์ใดๆ นักพัฒนาครึ่งหนึ่งจะลาออก และอีกครึ่งหนึ่งจะคลั่งไคล้ลำดับชั้นการสืบทอดแบบขนาน
เมื่อคุณสร้างคลาสย่อยของคลาส คุณต้องสร้างคลาสย่อยอื่นของคลาสอื่นการกระจายการพึ่งพาแบบสม่ำเสมอ
เมื่อทำการแก้ไขใดๆ คุณต้องค้นหาการขึ้นต่อกัน (การใช้งาน) ทั้งหมดของคลาสนี้ และทำการเปลี่ยนแปลงเล็กๆ น้อยๆ มากมาย การเปลี่ยนแปลงครั้งเดียว - การแก้ไขในหลายคลาสแผนผังการปรับเปลี่ยนที่ซับซ้อน
กลิ่นนี้ตรงกันข้ามกับกลิ่นก่อนหน้า: การเปลี่ยนแปลงส่งผลต่อวิธีการจำนวนมากในคลาสเดียวกัน ตามกฎแล้วการพึ่งพาโค้ดดังกล่าวจะเรียงซ้อน: เมื่อเปลี่ยนวิธีหนึ่งแล้วคุณจะต้องแก้ไขบางอย่างในอีกวิธีหนึ่งจากนั้นในวิธีที่สามเป็นต้น คลาสเดียว - การเปลี่ยนแปลงมากมาย“กลิ่นขยะ”
กลิ่นไม่พึงประสงค์ประเภทหนึ่งที่ทำให้เกิดอาการปวดหัว โค้ดเก่าไร้ประโยชน์ ไม่จำเป็น โชคดีที่ IDE และ linters สมัยใหม่ได้เรียนรู้ที่จะเตือนเกี่ยวกับกลิ่นดังกล่าวความคิดเห็นจำนวนมากในวิธีการ
วิธีการนี้มีคำอธิบายมากมายในเกือบทุกบรรทัด ซึ่งมักจะเกี่ยวข้องกับอัลกอริธึมที่ซับซ้อน ดังนั้นจึงเป็นการดีกว่าที่จะแบ่งโค้ดออกเป็นวิธีการเล็กๆ น้อยๆ และตั้งชื่อที่มีความหมายการทำสำเนารหัส
คลาสหรือเมธอดที่แตกต่างกันใช้บล็อคโค้ดเดียวกันชั้นเรียนขี้เกียจ
ชั้นเรียนใช้ฟังก์ชันการทำงานเพียงเล็กน้อย แม้ว่าจะมีการวางแผนไว้มากมายก็ตามรหัสที่ไม่ได้ใช้
คลาส วิธีการ หรือตัวแปรไม่ได้ใช้ในโค้ด แต่เป็น "น้ำหนักตาย"การมีเพศสัมพันธ์มากเกินไป
กลิ่นประเภทนี้มีลักษณะเฉพาะด้วยการเชื่อมต่อที่ไม่จำเป็นจำนวนมากในโค้ดวิธีการของบุคคลที่สาม
วิธีการใช้ข้อมูลของวัตถุอื่นบ่อยกว่าการใช้ข้อมูลของตัวเองความใกล้ชิดที่ไม่เหมาะสม
คลาสใช้ฟิลด์บริการและวิธีการของคลาสอื่นการโทรในชั้นเรียนที่ยาวนาน
คลาสหนึ่งเรียกอีกคลาสหนึ่งซึ่งร้องขอข้อมูลจากคลาสที่สาม จากคลาสที่สี่ และอื่นๆ สายโซ่การโทรที่ยาวเช่นนี้หมายถึงการพึ่งพาโครงสร้างคลาสปัจจุบันในระดับสูงคลาส-งาน-ดีลเลอร์
จำเป็นต้องมีชั้นเรียนเพื่อส่งงานไปยังชั้นเรียนอื่นเท่านั้น บางทีมันควรจะถูกลบออก?เทคนิคการปรับโครงสร้างใหม่
ด้านล่างนี้เราจะพูดถึงเทคนิคการปรับโครงสร้างเบื้องต้นซึ่งจะช่วยกำจัดกลิ่นโค้ดที่อธิบายไว้การเลือกชั้นเรียน
ชั้นเรียนมีฟังก์ชันมากเกินไป บางส่วนจำเป็นต้องย้ายไปยังชั้นเรียนอื่น ตัวอย่างเช่น มีคลาสHuman
ที่มีที่พักอาศัยด้วยและวิธีการระบุที่อยู่แบบเต็ม:
class Human {
private String name;
private String age;
private String country;
private String city;
private String street;
private String house;
private String quarter;
public String getFullAddress() {
StringBuilder result = new StringBuilder();
return result
.append(country)
.append(", ")
.append(city)
.append(", ")
.append(street)
.append(", ")
.append(house)
.append(" ")
.append(quarter).toString();
}
}
เป็นความคิดที่ดีที่จะวางข้อมูลและวิธีการที่อยู่ (พฤติกรรมการประมวลผลข้อมูล) ไว้ในคลาสที่แยกจากกัน:
class Human {
private String name;
private String age;
private Address address;
private String getFullAddress() {
return address.getFullAddress();
}
}
class Address {
private String country;
private String city;
private String street;
private String house;
private String quarter;
public String getFullAddress() {
StringBuilder result = new StringBuilder();
return result
.append(country)
.append(", ")
.append(city)
.append(", ")
.append(street)
.append(", ")
.append(house)
.append(" ")
.append(quarter).toString();
}
}
การเลือกวิธีการ
หากสามารถจัดกลุ่มฟังก์ชันการทำงานใดๆ ไว้ในวิธีการหนึ่งได้ ควรวางฟังก์ชันนั้นไว้ในวิธีที่แยกต่างหาก ตัวอย่างเช่น วิธีการคำนวณรากของสมการกำลังสอง:public void calcQuadraticEq(double a, double b, double c) {
double D = b * b - 4 * a * c;
if (D > 0) {
double x1, x2;
x1 = (-b - Math.sqrt(D)) / (2 * a);
x2 = (-b + Math.sqrt(D)) / (2 * a);
System.out.println("x1 = " + x1 + ", x2 = " + x2);
}
else if (D == 0) {
double x;
x = -b / (2 * a);
System.out.println("x = " + x);
}
else {
System.out.println("Equation has no roots");
}
}
ลองย้ายการคำนวณทั้งสามตัวเลือกที่เป็นไปได้ไปเป็นวิธีการแยกกัน:
public void calcQuadraticEq(double a, double b, double c) {
double D = b * b - 4 * a * c;
if (D > 0) {
dGreaterThanZero(a, b, D);
}
else if (D == 0) {
dEqualsZero(a, b);
}
else {
dLessThanZero();
}
}
public void dGreaterThanZero(double a, double b, double D) {
double x1, x2;
x1 = (-b - Math.sqrt(D)) / (2 * a);
x2 = (-b + Math.sqrt(D)) / (2 * a);
System.out.println("x1 = " + x1 + ", x2 = " + x2);
}
public void dEqualsZero(double a, double b) {
double x;
x = -b / (2 * a);
System.out.println("x = " + x);
}
public void dLessThanZero() {
System.out.println("Equation has no roots");
}
รหัสสำหรับแต่ละวิธีสั้นลงและชัดเจนขึ้นมาก
การถ่ายโอนวัตถุทั้งหมด
เมื่อเรียกเมธอดด้วยพารามิเตอร์ บางครั้งคุณจะเห็นโค้ดดังนี้:public void employeeMethod(Employee employee) {
// Некоторые действия
double yearlySalary = employee.getYearlySalary();
double awards = employee.getAwards();
double monthlySalary = getMonthlySalary(yearlySalary, awards);
// Продолжение обработки
}
public double getMonthlySalary(double yearlySalary, double awards) {
return (yearlySalary + awards)/12;
}
ในวิธีการนี้employeeMethod
จะมีการจัดสรรมากถึง 2 บรรทัดเพื่อรับค่าและเก็บไว้ในตัวแปรดั้งเดิม บางครั้งการออกแบบดังกล่าวอาจใช้เวลานานถึง 10 บรรทัด การส่งต่อออบเจ็กต์ไปยังเมธอดนั้นง่ายกว่ามาก โดยคุณสามารถดึงข้อมูลที่จำเป็นออกมาได้:
public void employeeMethod(Employee employee) {
// Некоторые действия
double monthlySalary = getMonthlySalary(employee);
// Продолжение обработки
}
public double getMonthlySalary(Employee employee) {
return (employee.getYearlySalary() + employee.getAwards())/12;
}
เรียบง่าย สั้น และกระชับ
การจัดกลุ่มฟิลด์ตามตรรกะและวางไว้ในคลาสที่แยกจากกัน
แม้ว่าตัวอย่างข้างต้นจะเรียบง่ายมากและเมื่อพิจารณาดูแล้ว หลายๆ คนอาจถามคำถามว่า "ใครเป็นคนทำสิ่งนี้จริงๆ" นักพัฒนาจำนวนมาก เนื่องจากไม่ตั้งใจ ไม่เต็มใจที่จะปรับโครงสร้างโค้ดใหม่ หรือเพียงแค่ "มันจะทำได้" ทำให้ ข้อผิดพลาดทางโครงสร้างที่คล้ายกันเหตุใดการรีแฟคเตอร์จึงมีประสิทธิภาพ
ผลลัพธ์ของการปรับโครงสร้างใหม่ที่ดีคือโปรแกรมที่มีโค้ดอ่านง่าย การปรับเปลี่ยนลอจิกของโปรแกรมไม่กลายเป็นภัยคุกคาม และการแนะนำคุณสมบัติใหม่ไม่ได้กลายเป็นการแยกวิเคราะห์โค้ด แต่เป็นกิจกรรมที่น่าพึงพอใจในสองสามวัน . ไม่ควรใช้การปรับโครงสร้างใหม่หากจะเขียนโปรแกรมใหม่ตั้งแต่ต้นได้ง่ายกว่า ตัวอย่างเช่น ทีมงานประมาณค่าใช้จ่ายด้านแรงงานสำหรับการแยกวิเคราะห์ การวิเคราะห์ และการปรับโครงสร้างโค้ดให้สูงกว่าการใช้ฟังก์ชันเดียวกันตั้งแต่เริ่มต้น หรือโค้ดที่ต้องปรับโครงสร้างใหม่มีข้อผิดพลาดมากมายที่แก้ไขยาก การรู้วิธีปรับปรุงโครงสร้างของโค้ดเป็นสิ่งจำเป็นในการทำงานของโปรแกรมเมอร์ ดีกว่าถ้าเรียนรู้การเขียนโปรแกรม Java ที่ JavaRush ซึ่งเป็นหลักสูตรออนไลน์ที่เน้นการฝึกฝน งานมากกว่า 1,200 งานพร้อมการตรวจสอบทันที มินิโปรเจ็กต์ประมาณ 20 งาน งานเกม ทั้งหมดนี้จะช่วยให้คุณรู้สึกมั่นใจในการเขียนโค้ด เวลาที่ดีที่สุดในการเริ่มต้นคือตอนนี้ :)
แหล่งข้อมูลสำหรับการดำน้ำเพิ่มเติมในการปรับโครงสร้างใหม่
หนังสือที่มีชื่อเสียงที่สุดเกี่ยวกับการรีแฟคเตอร์คือ “Refactoring” การปรับปรุงการออกแบบโค้ดที่มีอยู่” โดย Martin Fowler นอกจากนี้ยังมีสิ่งพิมพ์ที่น่าสนใจเกี่ยวกับการรีแฟคเตอร์ ซึ่งเขียนจากหนังสือเล่มก่อนๆ - “Refactoring with Patterns” โดย Joshua Kiriewski การพูดของแม่แบบ เมื่อทำการรีแฟคเตอร์ การทราบรูปแบบการออกแบบแอปพลิเคชันพื้นฐานจะมีประโยชน์มากเสมอ หนังสือดีๆ เหล่านี้จะช่วยในเรื่องนี้:- “รูปแบบการออกแบบ” - โดย Eric Freeman, Elizabeth Freeman, Kathy Sierra, Bert Bates จากซีรีส์ Head First;
- “โค้ดที่อ่านได้ หรือการเขียนโปรแกรมเป็นศิลปะ” - Dustin Boswell, Trevor Faucher
- “Perfect Code” โดย Steve McConnell ซึ่งสรุปหลักการของโค้ดที่สวยงามและสง่างาม
GO TO FULL VERSION