วิธีที่สะอาดที่สุดในการเขียนซอฟต์แวร์ที่มีขั้นตอนตามหลักเหตุผลในภาษา OO


12

ฉันเป็นวิศวกรไฟฟ้าและฉันไม่รู้ว่าฉันทำอะไรอยู่ โปรดบันทึกผู้ดูแลรหัสของฉันในอนาคต

เมื่อเร็ว ๆ นี้ฉันได้ทำงานกับโปรแกรมขนาดเล็กจำนวนมาก (ใน C #) ซึ่งฟังก์ชั่นการใช้งานเป็น "ขั้นตอน" ตามหลักเหตุผล ตัวอย่างเช่นหนึ่งในนั้นคือโปรแกรมที่รวบรวมข้อมูลจากฐานข้อมูลต่าง ๆ ใช้ข้อมูลนั้นเพื่อสร้างเรียงลำดับของหน้าสรุปพิมพ์และจากนั้นออก

ตรรกะที่จำเป็นสำหรับทั้งหมดนั้นอยู่ที่ประมาณ 2,000 บรรทัด แน่นอนฉันไม่ต้องการสิ่งทั้งหมดในที่เดียวmain()แล้ว "ทำความสะอาด" กับ#regions เป็นนักพัฒนาก่อนหน้านี้ได้ทำ (ตัวสั่น)

นี่คือบางสิ่งที่ฉันได้ลองไปแล้วโดยที่ไม่พึงพอใจมาก:

สร้างยูทิลิตี้แบบคงที่สำหรับการใช้งานแต่ละบิตอย่างคร่าวๆเช่น DatabaseInfoGetter, SummaryPageGenerator และ PrintUtility ทำให้ฟังก์ชั่นหลักดูเหมือนว่า:

int main()
{
    var thisThing = DatabaseInfoGetter.GetThis();
    var thatThing = DatabaseInfoGetter.GetThat();
    var summary = SummaryPageGenerator.GeneratePage(thisThing, thatThing);

    PrintUtility.Print(summary);
}

สำหรับโปรแกรมหนึ่งฉันยังไปกับอินเตอร์เฟส

int main()
{
    /* pardon the psuedocode */

    List<Function> toDoList = new List<Function>();
    toDoList.Add(new DatabaseInfoGetter(serverUrl));
    toDoList.Add(new SummaryPageGenerator());
    toDoList.Add(new PrintUtility());

    foreach (Function f in toDoList)
        f.Do();
}

สิ่งนี้ไม่ถูกต้องนัก เมื่อโค้ดยาวขึ้นวิธีการทั้งสองนี้เริ่มน่าเกลียด

เป็นวิธีที่ดีในการจัดโครงสร้างสิ่งเช่นนี้คืออะไร?


2
ผมไม่แน่ใจว่านี้เป็นอย่างเคร่งครัดซ้ำ แต่โปรดอ่านวิธีการเดียวกับปัจจัยหลายประการเทียบกับวิธีการมากมายที่จะต้องเรียกว่าในการสั่งซื้อ


@Snowman ในฐานะคนที่ยังใหม่กับการเขียนโค้ดขนาดใหญ่มันจึงมั่นใจที่จะเห็นว่าฉันไม่ใช่คนเดียวที่พบปัญหาประเภทนี้ ฉันชอบคำตอบของคุณสำหรับคำถามนั้น มันช่วย.
ลอมบาร์ด

@ Lombard ความคิดในการลดความซับซ้อนให้อยู่ในระดับที่จัดการได้นั้นเป็นทักษะที่ดีมากในการพัฒนา ไม่มีใครสามารถเข้าใจฐานรหัสขนาดใหญ่ได้แม้กระทั่ง Superman (อาจเป็น Batman) การค้นหาวิธีการแสดงความคิดเห็นอย่างง่าย ๆ และจัดทำเอกสารด้วยตนเองนั้นเป็นสิ่งสำคัญ

"เมื่อเร็ว ๆ นี้ฉันได้ทำงานกับโปรแกรมขนาดเล็กจำนวนมาก (ใน C #) ซึ่งฟังก์ชั่นการทำงานเป็น" ขั้นตอน "แบบลอจิคัลตัวอย่างเช่นหนึ่งในนั้นคือโปรแกรมที่รวบรวมข้อมูลจากฐานข้อมูลที่แตกต่างกัน พิมพ์หน้าแล้วออกจาก ": คุณแน่ใจหรือไม่ว่าคุณไม่สับสนกับ" ขั้นตอน "กับ" คำสั่ง " ทั้งโพรซีเดอร์และออบเจ็กต์เป็นสิ่งจำเป็น (สามารถ) ได้เช่นพวกเขาสามารถแสดงการดำเนินการที่จะดำเนินการในลำดับ
Giorgio

คำตอบ:


18

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

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

หากเซ็กเมนต์รหัสยาวเกินไปให้แบ่งออกเป็นส่วนขนาดที่มีการกัดแต่ละส่วนจะมีฟังก์ชันของตัวเอง หากสั้นเกินไปให้พิจารณาใช้โมดูลน้อยลงและเพิ่มจำนวนมากขึ้น

---- Postscript: OO Design Traps

การทำงานกับโปรแกรม OO ได้สำเร็จอาจเป็นเรื่องยุ่งยาก มีบางคนที่พิจารณาว่าโมเดลทั้งหมดมีข้อบกพร่อง มีหนังสือที่ดีมากที่ฉันใช้เมื่อเรียนรู้การเขียนโปรแกรม OO ครั้งแรกที่เรียกว่าการคิดใน Java (ตอนนี้อยู่ในรุ่นที่ 4) ผู้เขียนคนเดียวกันมีหนังสือที่เกี่ยวข้องสำหรับ C ++ มีจริงคำถามอื่นเกี่ยวกับการเขียนโปรแกรมการจัดการกับข้อผิดพลาดที่พบบ่อยในการเขียนโปรแกรมเชิงวัตถุ

ข้อผิดพลาดบางอย่างมีความซับซ้อน แต่มีหลายวิธีในการสร้างปัญหาในรูปแบบพื้นฐานมาก ตัวอย่างเช่นเมื่อสองสามปีก่อนมีฝึกงานที่ บริษัท ของฉันที่เขียนซอฟต์แวร์รุ่นแรกที่ฉันสืบทอดและเขาสร้างส่วนต่อประสานสำหรับทุกสิ่งที่อาจสักวันมีการใช้งานหลายอย่าง แน่นอนว่าใน 98% ของกรณีมีเพียงการใช้งานเพียงครั้งเดียวดังนั้นโค้ดจึงถูกโหลดด้วยอินเตอร์เฟสที่ไม่ได้ใช้ซึ่งทำให้การดีบักน่ารำคาญมากเพราะคุณไม่สามารถย้อนกลับผ่านการโทรอินเตอร์เฟสได้ดังนั้นคุณจึงต้องทำ ค้นหาข้อความสำหรับการใช้งาน (แม้ว่าตอนนี้ฉันใช้ IntelliJ ก็มีคุณสมบัติ "แสดงการใช้งานทั้งหมด" แต่ย้อนกลับไปในวันที่ฉันไม่มี) กฎที่นี่เป็นเช่นเดียวกับการเขียนโปรแกรมขั้นตอน: hardcode สิ่งเดียวเสมอ เฉพาะเมื่อคุณมีสองสิ่งขึ้นไปให้สร้างสิ่งที่เป็นนามธรรม

ความผิดพลาดในการออกแบบที่คล้ายกันสามารถพบได้ใน Java Swing API พวกเขาใช้แบบจำลองการสมัครสมาชิกสำหรับระบบเมนูสวิง สิ่งนี้ทำให้การสร้างและการดีบักเมนูใน Swing เป็นฝันร้ายที่สมบูรณ์ ประชดคือมันไม่มีจุดหมายอย่างสมบูรณ์ ไม่เคยมีสถานการณ์ที่ต้องใช้ฟังก์ชั่นหลายอย่างในการ "สมัคร" เพื่อคลิกเมนู นอกจากนี้การเผยแพร่การสมัครรับข้อมูลก็เป็นความเข้าใจผิดที่สมบูรณ์เพราะระบบเมนูมักจะถูกใช้งานอยู่เสมอ มันไม่เหมือนฟังก์ชั่นที่สมัครและยกเลิกการสมัครแบบสุ่ม ความจริงที่ว่านักพัฒนา "มืออาชีพ" ที่ซันทำผิดพลาดแบบนี้ก็แสดงให้เห็นว่ามันง่ายเพียงใดสำหรับมืออาชีพที่จะสร้างสกรูขนาดใหญ่ในการออกแบบ OO

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


1
+1 สำหรับ "ความชัดเจนสำคัญกว่า [ประสิทธิภาพมากกว่า]"
Stephen

คุณช่วยชี้ให้ฉันไปที่ลิงก์ที่อธิบายสิ่งที่ถือเป็น "โปรแกรม OO ที่เขียนไม่ดี" หรือเพิ่มข้อมูลเพิ่มเติมเกี่ยวกับสิ่งนั้นในการแก้ไขได้ไหม
ลอมบาร์ด

@ ลอมบาร์ดฉันทำอย่างนั้น
Tyler Durden

1

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

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

หากคุณยังคงดิ้นรนกับความซับซ้อนคุณอาจจำเป็นต้องแยกโปรแกรมของคุณออกไปอีก สร้างระดับเพิ่มเติมในลำดับชั้น - อาจDatabaseInfoGetterจำเป็นต้องอ้างอิงคลาสอื่นเช่นProductTableEntryหรือบางสิ่งบางอย่าง คุณกำลังเขียนโค้ดขั้นตอน แต่จำไว้ว่าคุณกำลังใช้ C # และ OOP มอบเครื่องมือมากมายเพื่อลดความซับซ้อนเช่น:

int main() 
{
    var thisthat = Database.GetThisThat();
}

public class ThisThatClass
{
    public String This;
    public String That;
}

public static class Database 
{
    public ThisThatClass GetThisThat()
    {
        return new ThisThatClass 
        {
            This = GetThis(),
            That = GetThat()
        };
    }

    public static String GetThis() 
    { 
        //stuff
    }
    public static String GetThat() 
    { 
        //stuff
    }

}

นอกจากนี้ควรระวังด้วยคลาสที่คงที่ คลาสฐานข้อมูลเป็นตัวเลือกที่ดี .. ตราบใดที่คุณมีฐานข้อมูลเดียว .. คุณจะได้แนวคิด

ท้ายที่สุดถ้าคุณคิดว่าฟังก์ชั่นทางคณิตศาสตร์และ C # ไม่เหมาะกับสไตล์ของคุณลองบางอย่างเช่น Scala, Haskell และอื่น ๆ พวกเขาก็เท่ห์เช่นกัน


0

ฉันกำลังตอบสนองช้า 4 ปี แต่เมื่อคุณพยายามทำสิ่งต่าง ๆ ในภาษา OO คุณก็กำลังทำงานกับฝ่ายตรงข้าม นั่นไม่ใช่สิ่งที่คุณควรทำ! ฉันพบบล็อกที่จัดการกับ antipattern นี้และแสดงวิธีแก้ปัญหา แต่ก็ต้องมีการปรับโครงสร้างจำนวนมากและการเปลี่ยนความคิด ดูรหัสขั้นตอนในรหัสเชิงวัตถุสำหรับรายละเอียดเพิ่มเติม
เมื่อคุณพูดถึง "นี่" และ "ที่" คุณจะแนะนำให้คุณมีสองคลาสที่แตกต่างกัน A คลาสนี้ (ชื่อไม่ดี!) และคลาสนั้น และคุณสามารถแปลสิ่งนี้เช่น:

var summary = SummaryPageGenerator.GeneratePage(thisThing, thatThing);

เป็นนี้

var summary = SummaryPageGenerator.GeneratePage(new This(DatabaseInfoGetter), new That(DatabaseInfoGetter));

ตอนนี้มันเป็นเรื่องที่น่าสนใจที่จะเห็นบรรทัดรหัส 2,000+ ขั้นตอนเหล่านั้นและพิจารณาว่าส่วนต่าง ๆ สามารถจัดกลุ่มเข้าด้วยกันเป็นคลาสแยกและย้ายขั้นตอนต่าง ๆ ลงในคลาสเหล่านั้นได้อย่างไร
แต่ละชั้นเรียนควรเรียบง่ายและเน้นไปที่การทำสิ่งหนึ่ง และสำหรับฉันแล้วดูเหมือนว่าบางส่วนของรหัสนั้นเกี่ยวข้องกับซูเปอร์คลาสแล้วซึ่งจริง ๆ แล้วมันใหญ่เกินไปที่จะเป็นประโยชน์ ตัวอย่างเช่น DatabaseInfoGetter จะทำให้เกิดความสับสน อะไรคือสิ่งที่ได้รับต่อไป? มันฟังดูธรรมดาเกินไป แต่ SummaryPageGenerator นั้นเฉพาะเจาะจงมาก! และ PrintUtility กำลังเป็นเรื่องธรรมดาอีกครั้ง
ดังนั้นในการเริ่มต้นคุณควรหลีกเลี่ยงชื่อสามัญสำหรับชั้นเรียนของคุณและเริ่มใช้ชื่อเฉพาะเพิ่มเติมแทน ตัวอย่างเช่นคลาส PrintUtility จะเป็นคลาส Print ที่มีคลาส Header, Footer class, TextBlock class, Image class และอาจเป็นวิธีที่บ่งบอกว่าพวกมันจะไหลในการพิมพ์อย่างไรทั้งหมดนี้อาจอยู่ใน PrintUtility namespaceแม้ว่า
สำหรับฐานข้อมูลเรื่องที่คล้ายกัน แม้ว่าคุณจะใช้ Entity Framework สำหรับการเข้าถึงฐานข้อมูล แต่คุณควร จำกัด ให้เฉพาะสิ่งที่คุณใช้และแบ่งเป็นกลุ่มตรรกะ
แต่ฉันสงสัยว่าคุณยังคงจัดการกับปัญหานี้หลังจาก 4 ปี ถ้าเป็นเช่นนั้นมันเป็นความคิดที่จะต้องเปลี่ยนไปจาก "แนวคิดเชิงปฏิบัติ" และเป็น "แนวคิดเชิงวัตถุ" สิ่งใดที่ท้าทายถ้าคุณไม่มีประสบการณ์ ...


-3

ฉันคิดว่าคุณควรเปลี่ยนรหัสของคุณเพื่อให้ง่ายสอดคล้องและอ่านได้

ฉันเขียนบทความบล็อกในหัวข้อนี้และเชื่อฉันว่าพวกเขาเข้าใจง่าย: รหัสที่ดีคืออะไร

เรียบง่าย: เหมือนแผงวงจรที่มีชิ้นส่วนต่าง ๆ แต่ละอันมีความรับผิดชอบ รหัสควรแบ่งออกเป็นส่วนที่เล็กและง่ายกว่า

สอดคล้อง: ใช้ patters ซึ่งเป็นมาตรฐานสำหรับองค์ประกอบการตั้งชื่อและสิ่งต่างๆ คุณชอบเครื่องมือที่ทำงานในลักษณะเดียวกันตลอดเวลาและคุณต้องการทราบว่าจะหาได้ที่ไหน มันเป็นรหัสเดียวกัน

อ่านได้: อย่าใช้คำย่อยกเว้นว่ามีการใช้อย่างกว้างขวางและเป็นที่รู้จักในโดเมนที่ซอฟต์แวร์กำหนดเป้าหมาย (ด้าน) ให้เลือกชื่อที่ออกเสียงที่ชัดเจนและเข้าใจง่าย


1
คำย่อเป็นที่ยอมรับหากมีการใช้กันอย่างแพร่หลายและเป็นที่รู้จักในโดเมนซอฟต์แวร์ที่มีการกำหนดเป้าหมาย ลองเขียนซอฟต์แวร์ Air Traffic Control โดยไม่ใช้ - เรามีคำย่อที่ทำขึ้นจากตัวย่อ - ไม่มีใครรู้ว่าคุณกำลังพูดถึงอะไรคือคุณใช้ชื่อเต็ม สิ่งต่างๆเช่น HTTP สำหรับเครือข่าย, ABS ในยานยนต์ ฯลฯ เป็นที่ยอมรับอย่างสมบูรณ์
mattnz
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.