เมื่อคุณเรียนรู้วิธีการเขียนโปรแกรม คุณใช้เวลามากมายในการเขียนโค้ด นักพัฒนามือใหม่ส่วนใหญ่เชื่อว่านี่คือสิ่งที่พวกเขาจะทำในอนาคต นี่เป็นความจริงบางส่วน แต่งานของโปรแกรมเมอร์ยังรวมถึงการบำรุงรักษาและการปรับโครงสร้างโค้ดด้วย วันนี้เราจะมาพูดถึงการปรับโครงสร้างใหม่
ตัวอย่างลำดับชั้นที่ไม่ถูกต้อง

การปรับโครงสร้างใหม่บน CodeGym
การปรับโครงสร้างใหม่ครอบคลุมสองครั้งในหลักสูตร CodeGym:- งานใหญ่ในระดับ 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 ที่เหมาะสม สิ่งนี้เกิดขึ้นเมื่อเขาหรือเธอไม่เข้าใจความสามารถของ OOP อย่างถ่องแท้ และล้มเหลวในการใช้อย่างเต็มที่หรืออย่างเหมาะสมไม่สามารถใช้การสืบทอด
หากคลาสย่อยใช้เพียงชุดย่อยเล็กๆ ของฟังก์ชันของคลาสพาเรนต์ ก็จะมีกลิ่นของลำดับชั้นที่ไม่ถูกต้อง เมื่อสิ่งนี้เกิดขึ้น โดยปกติแล้ววิธีการที่ฟุ่มเฟือยจะไม่ถูกแทนที่หรือโยนข้อยกเว้นออกไป คลาสหนึ่งสืบทอดอีกคลาสหนึ่งแสดงว่าคลาสลูกใช้ฟังก์ชันเกือบทั้งหมดของคลาสพาเรนต์ ตัวอย่างลำดับชั้นที่ถูกต้อง

เปลี่ยนคำสั่ง
มีอะไรผิดปกติกับswitch
คำสั่ง? มันไม่ดีเมื่อมันซับซ้อนมาก if
ปัญหาที่เกี่ยวข้องคือ คำสั่ง ที่ซ้อนกันจำนวนมาก
คลาสทางเลือกที่มีอินเตอร์เฟสต่างกัน
หลายคลาสทำสิ่งเดียวกัน แต่เมธอดมีชื่อต่างกันสนามชั่วคราว
หากคลาสมีฟิลด์ชั่วคราวที่ออบเจกต์ต้องการเพียงบางครั้งเมื่อค่าของมันถูกตั้งค่า และฟิลด์นั้นว่างเปล่าหรือnull
เวลาที่เหลือ โค้ดนั้นมีกลิ่น นี่เป็นการตัดสินใจในการออกแบบที่น่าสงสัย
กลิ่นที่ทำให้การดัดแปลงทำได้ยาก
กลิ่นเหล่านี้รุนแรงมากขึ้น กลิ่นอื่น ๆ ส่วนใหญ่ทำให้เข้าใจรหัสได้ยากขึ้น แต่สิ่งเหล่านี้ทำให้คุณไม่สามารถแก้ไขได้ เมื่อคุณพยายามแนะนำคุณลักษณะใหม่ๆ นักพัฒนาครึ่งหนึ่งเลิกใช้ และอีกครึ่งหนึ่งก็บ้าไปแล้วลำดับชั้นการสืบทอดแบบคู่ขนาน
ปัญหานี้เกิดขึ้นเมื่อการจัดคลาสย่อยทำให้คุณต้องสร้างคลาสย่อยอื่นสำหรับคลาสอื่นการพึ่งพาอาศัยกันแบบกระจาย
การแก้ไขใด ๆ คุณจะต้องค้นหาการใช้งานของคลาสทั้งหมด (การพึ่งพา) และทำการเปลี่ยนแปลงเล็กน้อยจำนวนมาก การเปลี่ยนแปลงเพียงครั้งเดียว — แก้ไขได้หลายชั้นเรียนต้นไม้ที่ซับซ้อนของการดัดแปลง
กลิ่นนี้ตรงกันข้ามกับกลิ่นก่อนหน้า: การเปลี่ยนแปลงส่งผลต่อเมธอดจำนวนมากในคลาสเดียว ตามกฎแล้วโค้ดดังกล่าวมีการพึ่งพาอาศัยกัน: การเปลี่ยนวิธีหนึ่งคุณต้องแก้ไขบางอย่างในอีกวิธีหนึ่ง จากนั้นในวิธีที่สามเป็นต้น คลาสเดียว - การเปลี่ยนแปลงมากมาย"กลิ่นขยะ"
กลิ่นไม่พึงประสงค์ประเภทหนึ่งที่ทำให้ปวดหัว รหัสเก่าไร้ประโยชน์ ไม่จำเป็น โชคดีที่ IDEs และ 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) {
// Some actions
double yearlySalary = employee.getYearlySalary();
double awards = employee.getAwards();
double monthlySalary = getMonthlySalary(yearlySalary, awards);
// Continue processing
}
public double getMonthlySalary(double yearlySalary, double awards) {
return (yearlySalary + awards)/12;
}
มีemployeeMethod
2 บรรทัดทั้งหมดสำหรับรับค่าและเก็บไว้ในตัวแปรดั้งเดิม บางครั้งโครงสร้างดังกล่าวอาจใช้เวลาถึง 10 บรรทัด มันง่ายกว่ามากที่จะส่งผ่านวัตถุและใช้เพื่อดึงข้อมูลที่จำเป็น:
public void employeeMethod(Employee employee) {
// Some actions
double monthlySalary = getMonthlySalary(employee);
// Continue processing
}
public double getMonthlySalary(Employee employee) {
return (employee.getYearlySalary() + employee.getAwards())/12;
}
ง่าย สั้น และกระชับ
การจัดกลุ่มฟิลด์อย่างมีเหตุผลและย้ายฟิลด์เหล่านั้นไปแยกจากกันclassDespite
ข้อเท็จจริงที่ว่าตัวอย่างข้างต้นนั้นง่ายมาก และเมื่อคุณดูที่ฟิลด์เหล่านี้ พวกคุณหลายคนอาจถามว่า "ใครเป็นคนทำสิ่งนี้" นักพัฒนาหลายคนทำข้อผิดพลาดเกี่ยวกับโครงสร้างเช่นนี้เพราะความประมาทเลินเล่อ ไม่เต็มใจที่จะปรับโครงสร้างรหัสใหม่หรือเพียงแค่ทัศนคติว่า "ดีพอ"
เหตุใดการปรับโครงสร้างใหม่จึงมีประสิทธิภาพ
ผลจากการปรับโครงสร้างที่ดี ทำให้โปรแกรมมีโค้ดที่อ่านง่าย โอกาสที่จะเปลี่ยนตรรกะนั้นไม่ใช่เรื่องน่ากลัว และการแนะนำคุณสมบัติใหม่ไม่ได้กลายเป็นนรกของการวิเคราะห์โค้ด แต่เป็นประสบการณ์ที่น่าพึงพอใจแทนสำหรับสองสามวัน . คุณไม่ควรรีแฟคเตอร์หากการเขียนโปรแกรมตั้งแต่เริ่มต้นจะง่ายกว่า ตัวอย่างเช่น สมมติว่าทีมของคุณประเมินว่าแรงงานที่ต้องใช้ในการทำความเข้าใจ วิเคราะห์ และรีแฟคเตอร์โค้ดจะมากกว่าการใช้ฟังก์ชันเดียวกันตั้งแต่เริ่มต้น หรือหากโค้ดที่จะรีแฟคเตอร์มีปัญหามากมายที่แก้ไขจุดบกพร่องได้ยาก การรู้วิธีปรับปรุงโครงสร้างของรหัสเป็นสิ่งสำคัญในการทำงานของโปรแกรมเมอร์ และการเรียนรู้การเขียนโปรแกรมใน Java ทำได้ดีที่สุดใน CodeGym ซึ่งเป็นหลักสูตรออนไลน์ที่เน้นการฝึกฝน งานมากกว่า 1,200 รายการพร้อมการยืนยันทันที ประมาณ 20 มินิโปรเจกต์ งานเกม — ทั้งหมดนี้จะช่วยให้คุณมั่นใจในการเขียนโค้ด เวลาที่ดีที่สุดในการเริ่มต้นคือตอนนี้ :)แหล่งข้อมูลเพื่อดื่มด่ำกับการปรับโครงสร้างใหม่
หนังสือที่มีชื่อเสียงที่สุดเกี่ยวกับการปรับโครงสร้างใหม่คือ "การปรับโครงสร้างใหม่ การปรับปรุงการออกแบบรหัสที่มีอยู่" โดย Martin Fowler นอกจากนี้ยังมีสิ่งพิมพ์ที่น่าสนใจเกี่ยวกับการปรับโครงสร้างใหม่ โดยอิงจากหนังสือก่อนหน้านี้: "การปรับโครงสร้างใหม่โดยใช้รูปแบบ" โดย Joshua Kerievsky การพูดของรูปแบบ... เมื่อทำการปรับโครงสร้างใหม่ การรู้รูปแบบการออกแบบขั้นพื้นฐานจะมีประโยชน์มากเสมอ หนังสือที่ยอดเยี่ยมเหล่านี้จะช่วยในเรื่องนี้: การพูดของรูปแบบ... เมื่อทำการปรับโครงสร้างใหม่ การรู้รูปแบบการออกแบบขั้นพื้นฐานจะมีประโยชน์มากเสมอ หนังสือที่ยอดเยี่ยมเหล่านี้จะช่วยในเรื่องนี้:- "รูปแบบการออกแบบ" โดย Eric Freeman, Elizabeth Robson, Kathy Sierra และ Bert Bates จากซีรี่ส์ Head First
- "ศิลปะแห่งรหัสที่อ่านได้" โดย Dustin Boswell และ Trevor Foucher
- "รหัสสมบูรณ์" โดย Steve McConnell ซึ่งกำหนดหลักการสำหรับรหัสที่สวยงามและสง่างาม
GO TO FULL VERSION