วัตถุการกำหนดค่าเดียวเป็นความคิดที่ไม่ดีหรือไม่?


43

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

ปัญหาหนึ่งคือคุณไม่ต้องการทดสอบกับการกำหนดค่าแบบเดียวกับที่คุณใช้ มีสองวิธีในการแก้ไขปัญหานี้:

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

แต่อย่างใดคุณยังคงมีปัญหาที่สอง: เกือบทุกชั้นสามารถใช้วัตถุกำหนดค่า ดังนั้นในการทดสอบคุณจำเป็นต้องตั้งค่าการกำหนดค่าสำหรับคลาสที่ทดสอบ แต่ยังเป็นการอ้างอิงทั้งหมดอีกด้วย สิ่งนี้สามารถทำให้รหัสทดสอบของคุณน่าเกลียด

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


การกำหนดค่าได้รับการตั้งค่าโดยการอ่านไฟล์บนดิสก์ใช่ไหม เหตุใดจึงไม่เพียงแค่มีไฟล์ "test.config" ที่สามารถอ่านได้
อานนท์

ที่แก้ปัญหาแรก แต่ไม่ใช่ที่สอง
JW01

@ JW01: การทดสอบทั้งหมดของคุณจำเป็นต้องมีการกำหนดค่าที่แตกต่างกันอย่างชัดเจนหรือไม่? คุณกำลังจะมีการตั้งค่าการกำหนดค่าที่อยู่ที่ไหนสักแห่งในระหว่างการทดสอบของคุณไม่ได้คุณ?
อานนท์

จริง แต่ถ้าฉันใช้การตั้งค่ากลุ่มเดียวต่อไป (ด้วยกลุ่มการทดสอบที่แตกต่างกัน) การทดสอบทั้งหมดของฉันจะสิ้นสุดลงโดยใช้การตั้งค่าเดียวกัน และเนื่องจากฉันอาจต้องการทดสอบพฤติกรรมด้วยการตั้งค่าที่แตกต่างกันจึงไม่เหมาะ
JW01

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

คำตอบ:


35

ฉันไม่ได้มีปัญหากับวัตถุการกำหนดค่าเดียวและสามารถเห็นข้อดีในการรักษาการตั้งค่าทั้งหมดไว้ในที่เดียว

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

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

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


7

ฉันแยกกลุ่มของการตั้งค่าที่เกี่ยวข้องกับส่วนต่อประสาน

สิ่งที่ต้องการ:

public interface INotificationEmailSettings {
   public string To { get; set; }
}

public interface IMediaFileSettings {
    public string BasePath { get; set; }
}

เป็นต้น

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

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

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

ดังนั้นฉันจะใช้คลาสสแตติกเป็นส่วนหน้าสร้างรายการที่ง่ายจากที่ใดก็ได้ไปยังการตั้งค่าและภายในคลาสสแตติกที่ฉันจะให้บริการค้นหาการใช้อินเทอร์เฟซและรับการตั้งค่า

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

public static class AllSettings {
    public INotificationEmailSettings NotificationEmailSettings {
        get {
            return ServiceLocator.Get<INotificationEmailSettings>();
        }
    }
}

ฉันพบว่าส่วนผสมนี้เป็นสิ่งที่ดีที่สุดในโลก


3

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

(การอ้างอิงข้อผูกมัด: การทำงานอย่างมีประสิทธิภาพด้วยรหัสดั้งเดิมซึ่งประกอบด้วยเทคนิคนี้และเทคนิคล้ำค่าอื่น ๆ อีกมากมายสำหรับการทดสอบรหัสดั้งเดิม)

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

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


ฉันชอบแนวทางของคุณมากและฉันจะไม่แนะนำว่าความคิดของผู้ตั้งค่าควรเก็บเป็น "ความลับ" หรือถือว่าเป็น "แฮ็คด่วน" สำหรับเรื่องนั้น หากคุณยอมรับจุดยืนที่การทดสอบหน่วยเป็นผู้ใช้ / ผู้บริโภค / ส่วนหนึ่งที่สำคัญของแอปพลิเคชันของคุณการมีtest_คุณสมบัติและวิธีการไม่ใช่สิ่งที่เลวร้ายอีกต่อไปใช่ไหม? ฉันยอมรับว่าวิธีการแก้ปัญหาที่แท้จริงของปัญหาคือการใช้กรอบงาน DI แต่ในตัวอย่างเช่นคุณเมื่อทำงานกับรหัสดั้งเดิมหรือกรณีง่าย ๆ อื่น ๆ รหัสการทดสอบไม่ได้ใช้อย่างหมดจด
Filip Dupanović

1
@kron นี่เป็นเทคนิคที่มีประโยชน์อย่างมากและเป็นประโยชน์สำหรับการสร้างรหัสทดสอบที่เป็นมรดกและฉันใช้มันอย่างกว้างขวางเช่นกัน อย่างไรก็ตามรหัสการผลิตไม่ควรมีคุณสมบัติใด ๆ ที่นำมาใช้เพียงเพื่อวัตถุประสงค์ในการทดสอบ ในระยะยาวนี่คือจุดที่ฉันต้องปรับโครงสร้าง
PéterTörök

2

จากประสบการณ์ของฉันในโลกของ Java นี่คือสิ่งที่ Spring มีไว้สำหรับ มันสร้างและจัดการออบเจ็กต์ (beans) ตามคุณสมบัติบางอย่างที่ตั้งค่าไว้ที่รันไทม์และแอปพลิเคชันของคุณจะโปร่งใส คุณสามารถไปกับไฟล์ test.config ที่ Anon กล่าวถึงหรือคุณสามารถรวมตรรกะบางอย่างในไฟล์กำหนดค่าที่ Spring จะจัดการเพื่อตั้งค่าคุณสมบัติตามคีย์อื่น ๆ (เช่นชื่อโฮสต์หรือสภาพแวดล้อม)

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


2

คำถามนี้เกี่ยวกับปัญหาทั่วไปมากกว่าการกำหนดค่า ทันทีที่ฉันอ่านคำว่า "ซิงเกิล" ฉันก็นึกถึงปัญหาทั้งหมดที่เกี่ยวข้องกับรูปแบบนั้นในทันทีไม่น้อยที่สามารถทดสอบได้

รูปแบบซิงเกิลคือ "ถือว่าเป็นอันตราย" ความหมายมันไม่ใช่สิ่งที่ผิดเสมอไป แต่มักจะเป็น หากคุณกำลังพิจารณาที่จะใช้รูปแบบซิงเกิลสำหรับสิ่งใด ๆหยุดพิจารณา:

  • คุณจะต้องซับคลาสหรือไม่?
  • คุณจะต้องตั้งโปรแกรมไปยังส่วนต่อประสานหรือไม่?
  • คุณต้องการใช้การทดสอบหน่วยกับมันหรือไม่?
  • คุณจะต้องแก้ไขบ่อยๆหรือไม่?
  • แอปพลิเคชันของคุณมีวัตถุประสงค์เพื่อสนับสนุนแพลตฟอร์ม / สภาพแวดล้อมการผลิตที่หลากหลายหรือไม่
  • คุณยังกังวลเล็กน้อยเกี่ยวกับการใช้หน่วยความจำหรือไม่?

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

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


0

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

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

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


0

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

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

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


0

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

ฉันได้รับแรงบันดาลใจในเรื่องนี้โดย Joshua Flanagan แห่งลอ Techies; เขาเขียนบทความเกี่ยวกับเรื่องนี้เมื่อไม่นานมานี้

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