รูปแบบ C # เพื่อจัดการ "ฟังก์ชั่นฟรี" อย่างหมดจดหลีกเลี่ยงคลาสแบบคงที่ "ยูทิลิตี้ถุง" แบบคงที่


9

เมื่อไม่นานมานี้ฉันได้ตรวจสอบคลาสแบบคงที่ "ยูทิลิตี้กระเป๋า" คลาสแบบคงที่ที่ลอยอยู่รอบ ๆ โค้ดโค้ด C # ขนาดใหญ่บางตัวที่ฉันทำงานด้วย

// Helpers.cs

public static class Helpers
{
    public static void DoSomething() {}
    public static void DoSomethingElse() {}
}

วิธีการเฉพาะที่ฉันตรวจสอบคือ

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

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

สำหรับคำถามนี้ฉันจะอ้างถึงวิธีการชนิดนี้เป็น GLUM (วิธีการใช้งานที่มีน้ำหนักเบาทั่วไป) ความหมายเชิงลบของ "กลัม" มีวัตถุประสงค์บางส่วน ฉันขอโทษถ้าสิ่งนี้เจอในฐานะปุนโง่

แม้แต่การทิ้งความสงสัยเริ่มต้นของฉันเองเกี่ยวกับ GLUMs ฉันไม่ชอบสิ่งต่อไปนี้เกี่ยวกับเรื่องนี้:

  • คลาสแบบสแตติกกำลังถูกใช้เป็นเนมสเปซ แต่เพียงผู้เดียว
  • ตัวระบุคลาสแบบสแตติกนั้นไม่มีความหมาย
  • เมื่อเพิ่ม GLUM ใหม่คลาส (a) คลาส "bag" นี้จะไม่ได้รับการสัมผัสโดยไม่มีเหตุผลหรือ (b) คลาส "bag" ใหม่จะถูกสร้างขึ้น (ซึ่งโดยปกติแล้วมันจะไม่เป็นปัญหา คลาสแบบสแตติกมักจะทำซ้ำปัญหาที่ไม่เกี่ยวข้อง แต่มีวิธีการน้อยกว่า)
  • เมตาตั้งชื่อคือหลีกเลี่ยงไม่ได้อันยิ่งใหญ่ที่ไม่ได้มาตรฐานและมักจะไม่สอดคล้องกันภายในไม่ว่าจะเป็นHelpers, Utilitiesหรืออะไรก็ตาม

อะไรคือรูปแบบที่ดีและเรียบง่ายที่มีเหตุผลสำหรับการรีแฟคเจอริ่งนี้โดยเน้นที่ความกังวลข้างต้นและควรสัมผัสเบาที่สุดเท่าที่จะทำได้

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


1
เกี่ยวกับ GLUMs ™: ฉันคิดว่าตัวย่อสำหรับ "เบา" สามารถลบออกได้เนื่องจากวิธีการที่ควรจะมีน้ำหนักเบาโดยปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุด;)
Fabio

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

คำตอบ:


17

ฉันคิดว่าคุณมีปัญหาสองข้อที่เกี่ยวข้องกัน

  • คลาสแบบสแตติกประกอบด้วยฟังก์ชันที่ไม่เกี่ยวข้องเชิงตรรกะ
  • ชื่อของคลาสสแตติกไม่ได้ให้ข้อมูลที่เป็นประโยชน์เกี่ยวกับความหมาย / บริบทของฟังก์ชันที่มีอยู่

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

ความกังวลและแนวทางแก้ไขที่เป็นไปได้

วิธีการส่วนใหญ่ไม่เกี่ยวข้องกัน - ปัญหา รวมวิธีการที่เกี่ยวข้องในคลาสคงที่หนึ่งและย้ายวิธีที่ไม่เกี่ยวข้องไปยังคลาสแบบคงที่ของตนเอง

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

วิธีการมีขนาดเล็ก - ไม่ใช่ปัญหา - วิธีการควรมีขนาดเล็ก

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

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

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

เมื่อเพิ่ม GLUM ใหม่ทั้งคลาส (a) คลาส "bag" นี้จะได้รับการสัมผัส (ความโศกเศร้าของ SRP) - ไม่ใช่ปัญหาหากคุณทำตามแนวคิดที่ว่าวิธีการที่เกี่ยวข้องกับเหตุผลเท่านั้นที่ควรอยู่ในคลาสแบบสแตติกหนึ่งคลาส SRPไม่ได้ละเมิดที่นี่เพราะหลักการควรนำไปใช้สำหรับการเรียนหรือวิธีการ ความรับผิดชอบเดี่ยวของคลาสสแตติกหมายถึงมีวิธีการต่าง ๆ สำหรับ "ความคิด" ทั่วไป แนวคิดนั้นอาจเป็นคุณลักษณะหรือการแปลงหรือการวนซ้ำ (LINQ) หรือการทำให้เป็นมาตรฐานของข้อมูลหรือการตรวจสอบความถูกต้อง ...

หรือน้อยกว่า (b) คลาส "bag" ใหม่ถูกสร้างขึ้น (เพิ่มจำนวนถุง) - ไม่ใช่ปัญหา คลาส / สแตติกคลาส / ฟังก์ชัน - เป็นเครื่องมือ (นักพัฒนา) ของเราซึ่งเราใช้สำหรับการออกแบบซอฟต์แวร์ ทีมของคุณมีข้อ จำกัด ในการใช้คลาสหรือไม่? ขีด จำกัด สูงสุดสำหรับ "ถุงเพิ่มเติม" คืออะไร การเขียนโปรแกรมเป็นเรื่องเกี่ยวกับบริบทหากสำหรับวิธีการแก้ปัญหาในบริบทเฉพาะของคุณคุณจบลงด้วย 200 คลาสแบบคงที่ซึ่งมีชื่อที่เข้าใจได้วางไว้ในตรรกะใน namespaces / โฟลเดอร์ที่มีลำดับชั้นตรรกะ - มันไม่ใช่ปัญหา

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


มันไม่ใช่การละเมิด SRP แต่ชัดเจนว่าเป็นการละเมิดหลักการเปิด / ปิด ที่กล่าวว่าโดยทั่วไปฉันได้พบ Ocp เป็นปัญหามากกว่าที่คุ้มค่า
Paul

2
@ พอลหลักการ Open / Close ไม่สามารถใช้กับคลาสแบบคงที่ได้ นั่นคือประเด็นของฉันว่าหลักการ SRP หรือ OCP ไม่สามารถนำไปใช้สำหรับคลาสแบบคงที่ คุณสามารถใช้มันสำหรับวิธีการคงที่
Fabio

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

1
เรื่อง "คลาสคงที่เป็นเนมสเปซ" ความจริงแล้วมันเป็นรูปแบบทั่วไปไม่ได้หมายความว่าไม่ใช่รูปแบบการต่อต้าน วิธีการ (ซึ่งโดยพื้นฐานแล้วคือ "ฟังก์ชั่นอิสระ" เพื่อขอยืมคำศัพท์จาก C / C ++) ภายในชั้นเรียนแบบคงที่ที่มีการทำงานร่วมกันต่ำโดยเฉพาะนี้เป็นสิ่งที่มีความหมายในกรณีนี้ ในบริบทนี้ดูเหมือนว่าโครงสร้างจะดีกว่าที่จะมี sub-namespace ที่Aมีคลาสแบบคงที่A100 วิธีเดียว(ใน 100 ไฟล์) กว่าคลาสแบบคงที่ที่มี 100 วิธีในไฟล์เดียว
วิลเลียม

1
ฉันทำความสะอาดคำตอบของคุณเป็นหลัก ฉันไม่แน่ใจว่าย่อหน้าสุดท้ายของคุณเกี่ยวกับอะไร ไม่มีใครกังวลเกี่ยวกับวิธีที่พวกเขาทดสอบรหัสที่เรียกMathคลาสคงที่
Robert Harvey

5

เพียงยอมรับการประชุมที่ใช้คลาสคงที่เช่นเนมสเปซในสถานการณ์เช่นนี้ใน C # Namespaces ได้รับชื่อที่ดีดังนั้นคลาสเหล่านี้ควร using staticคุณสมบัติของ C # 6ทำให้ชีวิตเล็ก ๆ น้อย ๆ ได้ง่ายขึ้นอีกด้วย

ชื่อคลาสที่มีกลิ่นเหม็นเช่นHelpersสัญญาณว่าวิธีการนั้นควรแบ่งออกเป็นคลาสแบบสแตติกที่ดีกว่าถ้าเป็นไปได้ แต่หนึ่งในสมมติฐานที่เน้นของคุณคือวิธีที่คุณจัดการด้วยคือ "pairwise unrelated" ซึ่งหมายถึงการแยก คลาสแบบสแตติกใหม่หนึ่งต่อวิธีที่มีอยู่ นั่นอาจจะดีถ้า

  • มีชื่อที่ดีสำหรับคลาสแบบคงที่ใหม่และ
  • คุณตัดสินว่ามันจะเป็นประโยชน์ต่อการบำรุงรักษาของโครงการ

เป็นตัวอย่างที่วางแผน, อาจจะกลายเป็นสิ่งที่ชอบHelpers.LoadImageFileSystemInteractions.LoadImage

ถึงกระนั้นคุณสามารถจบลงด้วยวิธีการเรียนถุงคงที่ วิธีนี้สามารถเกิดขึ้นได้:

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

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


หากคุณเห็นข้อได้เปรียบในการมีวิธีการเหมือน "ฟังก์ชั่นอิสระ" ในไฟล์ของตัวเอง (ซึ่งดีและคุ้มค่าถ้าคุณตัดสินโดยสุจริตว่าเป็นประโยชน์ต่อการบำรุงรักษาโครงการของคุณ) คุณสามารถพิจารณาสร้างคลาสแบบคงที่แทน ระดับบางส่วนจ้างชื่อระดับคงคล้ายกับวิธีที่คุณจะ namespace using staticที่แล้วบริโภคผ่านทาง ตัวอย่างเช่น:

โครงสร้างของโครงการจำลองโดยใช้รูปแบบนี้

Program.cs

namespace ConsoleApp1
{
    using static Foo;
    using static Flob;

    class Program
    {
        static void Main(string[] args)
        {
            Bar();
            Baz();
            Wibble();
            Wobble();
        }
    }
}

Foo / Bar.cs

namespace ConsoleApp1
{
    static partial class Foo
    {
        public static void Bar() { }
    }
}

Foo / Baz.cs

namespace ConsoleApp1
{
    static partial class Foo
    {
        public static void Baz() { }
    }
}

Flob / Wibble.cs

namespace ConsoleApp1
{
    static partial class Flob
    {
        public static void Wibble() { }
    }
}

Flob / Wobble.cs

namespace ConsoleApp1
{
    static partial class Flob
    {
        public static void Wobble() { }
    }
}

-6

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

หากคุณกำลังเขียนห้องสมุดคณิตศาสตร์หรือบางสิ่งบางอย่างฉันก็จะแนะนำแม้ว่าปกติฉันจะเกลียดพวกเขาวิธีการขยาย

คุณสามารถใช้ส่วนขยายวิธีการเพื่อเพิ่มฟังก์ชั่นลงในประเภทข้อมูลมาตรฐาน

ตัวอย่างเช่นสมมติว่าฉันมีฟังก์ชั่นทั่วไป MySort () แทน Helpers.MySort หรือเพิ่ม ListOfMyThings.MySort ใหม่ฉันสามารถเพิ่มลงใน IEnumerable ได้


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

1
หากคุณไม่มี "วิธีอรรถประโยชน์ทั่วไป" คุณอาจไม่แยกส่วนของตรรกะออกจากส่วนที่เหลือของตรรกะของคุณเพียงพอ เช่นเท่าที่ฉันรู้ไม่มีจุดประสงค์ในการเข้าร่วมฟังก์ชั่น URL ของเส้นทางใน. NET ดังนั้นฉันมักจะจบลงด้วยการเขียน cruft แบบนั้นน่าจะดีกว่าในฐานะเป็นส่วนหนึ่งของไลบรารี่มาตรฐาน แต่ lib มาตรฐานทุกตัวจะหายไปบางอย่าง ทางเลือกโดยปล่อยให้ตรรกะซ้ำไปซ้ำมาโดยตรงในโค้ดอื่นของคุณทำให้สามารถอ่านได้น้อยลงอย่างมากมาย
jpmc26


1
@Ewan ที่ไม่ใช่ฟังก์ชั่นมันเป็นลายเซ็นผู้รับมอบอำนาจทั่วไป ...
TheCatWhisperer

1
@Ewan นั่นคือจุดทั้งหมดของ TheCatWhisperer: คลาสยูทิลิตี้เป็นสิ่งที่น่าสนใจมากในการใส่เมธอดในคลาส ดีใจที่คุณเห็นด้วย
jpmc26
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.