การจัดการและจัดการจำนวนคลาสที่เพิ่มขึ้นอย่างมากหลังจากเปลี่ยนมาใช้ SOLID?


49

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

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

Solution
- Business
-- AccountLogic
-- DocumentLogic
-- UsersLogic
- Entities (Database entities)
- Models (Domain Models)
- Repositories
-- AccountRepo
-- DocumentRepo
-- UserRepo
- ViewModels
-- AccountViewModel
-- DocumentViewModel
-- UserViewModel
- UI

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

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

  • ทดสอบหน่วย
  • การนับชั้นเรียน
  • ความซับซ้อนของเพื่อน

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

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

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

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


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

ในการบันทึกเอกสารลงในไดรฟ์เครือข่ายฉันต้องการคลาสเฉพาะสองสามคลาส:

- IBasePathProvider 
-- string GetBasePath() // returns the network path to store files
-- string GetPatientFolderName() // gets the name of the folder where patient files are stored
- BasePathProvider // provides an implementation of IBasePathProvider
- BasePathProviderTests // ensures we're getting what we expect

- IUniqueFilenameProvider
-- string GetFilename(string path, string fileType);
- UniqueFilenameProvider // performs some filesystem lookups to get a unique filename
- UniqueFilenameProviderTests

- INewGuidProvider // allows me to inject guids to simulate collisions during unit tests
-- Guid NewGuid()
- NewGuidProvider 
- NewGuidProviderTests

- IFileExtensionCombiner // requests may come in a variety of ways, need to ensure extensions are properly appended.
- FileExtensionCombiner
- FileExtensionCombinerTests

- IPatientFileWriter
-- Task SaveFileAsync(string path, byte[] file, string fileType)
-- Task SaveFileAsync(FilePushRequest request) 
- PatientFileWriter
- PatientFileWriterTests

นั่นคือทั้งหมด 15 ชั้นเรียน (ไม่รวม POCOs และนั่งร้าน) เพื่อทำการบันทึกอย่างตรงไปตรงมา จำนวนนี้เพิ่มขึ้นอย่างมีนัยสำคัญเมื่อฉันต้องการสร้าง POCO เพื่อเป็นตัวแทนนิติบุคคลในระบบไม่กี่ระบบสร้าง repos สองสามตัวเพื่อสื่อสารกับระบบของบุคคลที่สามที่ไม่เข้ากันกับ ORM อื่น ๆ ของเราและวิธีการทางตรรกะที่สร้างขึ้นเพื่อจัดการความซับซ้อน


52
"งานที่เคยใช้ไฟล์ 5-10 ไฟล์ตอนนี้สามารถใช้ 70-100!" ในนรก? สิ่งนี้ไม่ปกติ คุณกำลังทำการเปลี่ยนแปลงประเภทใดที่ต้องเปลี่ยนไฟล์จำนวนมาก?
ร่าเริง

43
ความจริงที่ว่าคุณต้องเปลี่ยนไฟล์เพิ่มเติมต่องาน (มากขึ้นอย่างมาก!) หมายความว่าคุณทำผิดพลาด จุดทั้งหมดคือการจัดระเบียบโค้ดของคุณ (เมื่อเวลาผ่านไป) ในรูปแบบที่สะท้อนถึงรูปแบบการเปลี่ยนแปลงที่สังเกตได้ทำให้การเปลี่ยนแปลงง่ายขึ้น หลักการทั้งหมดใน SOLID มาพร้อมกับเหตุผลบางอย่างที่อยู่เบื้องหลังมัน (เมื่อใดและทำไมจึงควรใช้) ดูเหมือนว่าคุณจะได้รับตัวเองในสถานการณ์นี้โดยใช้สิ่งเหล่านี้อย่างสุ่มสี่สุ่มห้า สิ่งเดียวกันกับการทดสอบหน่วย (TDD); หากคุณกำลังทำมันโดยไม่เข้าใจวิธีการออกแบบ / สถาปัตยกรรมคุณจะต้องขุดลงไปในหลุม
Filip Milovanović

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

25
@Euphoric: ปัญหาสามารถเกิดขึ้นได้ทั้งสองวิธี ฉันสงสัยว่าคุณตอบสนองต่อความเป็นไปได้ที่คลาส 70-100 นั้น overkill แต่มันเป็นไปไม่ได้เลยที่สิ่งนี้จะเป็นโครงการขนาดใหญ่ที่อัดแน่นไปด้วยไฟล์ 5-10 ไฟล์ (ฉันเคยทำงานในไฟล์ 20KLOC มาก่อน ... ) และ 70-100 เป็นปริมาณที่เหมาะสมจริง ๆ
Flater

18
มีความผิดปกติของความคิดที่ฉันเรียกว่า "โรคความสุขในวัตถุ" ซึ่งเป็นความเชื่อที่ว่าเทคนิค OO เป็นจุดจบในตัวเองมากกว่าหนึ่งในเทคนิคที่เป็นไปได้หลายอย่างเพื่อลดค่าใช้จ่ายในการทำงานใน codebase ขนาดใหญ่ คุณมีรูปแบบขั้นสูงโดยเฉพาะ "โรคความสุขที่มั่นคง" โซลิดไม่ใช่เป้าหมาย การลดต้นทุนในการบำรุงรักษา codebase นั้นเป็นเป้าหมาย ประเมินข้อเสนอของคุณในบริบทนั้นไม่ใช่ว่าเป็นหลักคำสอนหรือไม่ (ข้อเสนอของคุณอาจจะไม่ใช่หลักคำสอนจริง ๆ แล้ว SOLID ก็เป็นจุดที่น่าพิจารณาเช่นกัน)
Eric Lippert

คำตอบ:


104

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

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

ชั้นเรียนที่เป็นนามธรรมออกไปDateTime.Nowฟังดูดี แต่คุณต้องการเพียงหนึ่งในนั้นและมันสามารถห่อด้วยคุณสมบัติสภาพแวดล้อมอื่น ๆ ในชั้นเดียวที่มีความรับผิดชอบในการสรุปคุณสมบัติด้านสิ่งแวดล้อม เป็นความรับผิดชอบเดียวอีกครั้งที่มีความรับผิดชอบย่อยหลายอย่าง

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

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

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

ดังนั้นหากส่วนต่าง ๆ ของรหัสของคุณสามารถทดสอบได้โดยรวมโดยไม่มีผลกระทบต่อส่วนอื่น ๆ ให้ทำเช่นนั้น

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


27
ฉันต้องการเพิ่มว่าอาการปวดหัวที่คล้ายกันเกิดขึ้นเมื่อผู้คนพยายามที่จะยึดมั่นกับมนต์ที่ซ้ำ ๆ กันหลายครั้งของ "วิธีการควรทำสิ่งเดียว" และจบลงด้วยตันของวิธีการหนึ่งบรรทัดเพียงเพราะมันสามารถทำให้เป็นเทคนิคได้ .
Logarr

8
Re "จงปฏิบัติตนให้เป็นอยู่เสมอและจดจำทุกสิ่งไว้ซึ่งการประนีประนอม" : สาวกของลุงบ๊อบไม่เป็นที่รู้จักในเรื่องนี้
Peter Mortensen

13
ในการสรุปส่วนแรกคุณมักจะมีพนักงานฝึกหัดทำกาแฟโดยไม่ต้องมีปลั๊กอินครบชุดสวิตช์เปิดฝาตรวจสอบว่าเติมน้ำตาลต้องการเติมตู้เย็นเปิดรับนมออกหรือไม่ - ช้อน, ลง - ถ้วย, เทกาแฟ, ใส่น้ำตาล, ใส่นม, กวน, และฝึกหัดส่งมอบถ้วย ; P
Justin เวลา

12
สาเหตุของปัญหาของ OP ดูเหมือนจะเข้าใจผิดความแตกต่างระหว่างการทำงานซึ่งควรดำเนินการเดียวงานและการเรียนที่ควรจะมีเพียงหนึ่งเดียวความรับผิดชอบ
alephzero

6
"กฎสำหรับการแนะนำของคนฉลาดและการเชื่อฟังคนโง่" - Douglas Bader
Calanus

29

ตอนนี้งานที่เคยถ่าย 5-10 ไฟล์สามารถใช้ 70-100!

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

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

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

รวบรวมสิ่งที่เปลี่ยนด้วยเหตุผลเดียวกัน แยกสิ่งต่าง ๆ เหล่านั้นที่เปลี่ยนแปลงด้วยเหตุผลต่าง ๆ

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

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

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


1
ฉันเดาว่าฉันแค่พยายามคิดให้ออกว่าเส้นอยู่ตรงไหน ในงานที่ผ่านมาฉันต้องทำการดำเนินการที่ค่อนข้างง่าย แต่มันอยู่ใน codebase โดยไม่มีการนั่งร้านหรือฟังก์ชั่นที่มีอยู่มาก ทุกอย่างที่ฉันต้องทำนั้นเรียบง่ายมาก แต่ทั้งหมดนั้นมีเอกลักษณ์และไม่เหมาะกับชั้นเรียนที่ใช้ร่วมกัน ในกรณีของฉันฉันจำเป็นต้องบันทึกเอกสารลงในไดรฟ์เครือข่ายและบันทึกลงในตารางฐานข้อมูลสองตารางแยกกัน กฎที่ล้อมรอบแต่ละขั้นตอนนั้นค่อนข้างพิเศษ แม้แต่การสร้างชื่อไฟล์ (guid ธรรมดา) ก็มีชั้นเรียนไม่กี่ชั้นเพื่อให้การทดสอบสะดวกยิ่งขึ้น
JD Davis

3
อีกครั้ง @JDDavis การเลือกเรียนหลายคลาสในชั้นเรียนเดียวเพื่อจุดประสงค์ในการทดสอบคือการวางเกวียนก่อนม้าและตรงข้ามกับ SRP ซึ่งเรียกร้องให้จัดกลุ่มฟังก์ชันการทำงานร่วมกันเป็นกลุ่ม ฉันไม่สามารถแนะนำคุณในรายละเอียดได้ แต่ปัญหาที่การเปลี่ยนแปลงการทำงานแต่ละอย่างจำเป็นต้องแก้ไขไฟล์จำนวนมากเป็นปัญหาที่คุณควรจัดการกับ (และพยายามหลีกเลี่ยง) ไม่ใช่สิ่งที่คุณควรพยายามปรับ
John Bollinger

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

12

ตอนนี้งานที่เคยถ่าย 5-10 ไฟล์สามารถใช้ 70-100!

นี่เป็นเรื่องโกหก งานไม่เคยใช้ไฟล์เพียง 5-10 ไฟล์

คุณไม่ได้แก้ปัญหาใด ๆ ที่มีไฟล์น้อยกว่า 10 ไฟล์ ทำไม? เพราะคุณกำลังใช้ C # C # เป็นภาษาระดับสูง คุณใช้ไฟล์มากกว่า 10 ไฟล์เพื่อสร้างโลกสวัสดี

โอ้แน่ใจว่าคุณไม่สังเกตเห็นพวกเขาเพราะคุณไม่ได้เขียน ดังนั้นคุณไม่ต้องดูพวกเขา คุณเชื่อใจพวกเขา

ปัญหาไม่ได้เป็นจำนวนไฟล์ มันคือตอนนี้คุณมีเรื่องมากมายที่คุณไม่เชื่อใจ

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

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

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

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

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

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

สิ่งนี้เกี่ยวข้องกับอะไร:

การจัดการและจัดการจำนวนคลาสที่เพิ่มขึ้นอย่างมากหลังจากเปลี่ยนมาใช้ SOLID?

สิ่งที่เป็นนามธรรม

คุณสามารถทำให้ฉันเกลียดโค้ดฐานใด ๆ ที่มี abstractions ไม่ดี สิ่งที่เป็นนามธรรมที่ไม่ดีคือสิ่งที่ทำให้ฉันมองเข้าไปข้างใน อย่าแปลกใจเมื่อฉันมองเข้าไปข้างใน ทำสิ่งที่ฉันคาดหวังไว้

ให้ชื่อที่ดีการทดสอบที่อ่านได้ (ตัวอย่าง) ที่แสดงวิธีใช้อินเทอร์เฟซและจัดระเบียบเพื่อที่ฉันจะได้พบกับสิ่งต่าง ๆ และฉันไม่สนใจว่าเราจะใช้ไฟล์ 10, 100 หรือ 1,000

คุณช่วยฉันค้นหาสิ่งต่าง ๆ ด้วยชื่ออธิบายที่ดี ใส่สิ่งที่มีชื่อที่ดีในสิ่งที่มีชื่อที่ดี

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

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

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

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

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

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


3
อาจเป็นเรื่องที่ควรกล่าวถึงว่าความเจ็บปวดของคำถามบางอย่างอาจเพิ่มขึ้นเช่นกัน - ในขณะที่ใช่พวกเขาอาจต้องทำ 15 ไฟล์สำหรับสิ่งนี้ ... ตอนนี้พวกเขาไม่ต้องเขียน GUIDProvider อีกครั้งหรือ BasePathProvider หรือ ExtensionProvider ฯลฯ มันเป็นอุปสรรค์แบบเดียวกับที่คุณได้รับเมื่อคุณเริ่มโครงการกรีนฟิลด์ใหม่ - กลุ่มของคุณสมบัติการสนับสนุนที่ส่วนใหญ่เป็นเรื่องเล็กน้อยโง่เขียน แต่ยังต้องเขียน สร้างมันขึ้นมา แต่เมื่อพวกเขาอยู่ที่นั่นคุณไม่จำเป็นต้องคิดเกี่ยวกับพวกเขา
Delioth

@Deoth ฉันเชื่อว่าเป็นกรณีนี้ ก่อนหน้านี้หากเราต้องการฟังก์ชั่นบางส่วน (สมมติว่าเราต้องการ URL ที่ตั้งอยู่ใน AppSettings) เราก็มีคลาสที่มีขนาดใหญ่หนึ่งคลาสที่ผ่านการใช้งานไปแล้ว ด้วยวิธีการใหม่ไม่มีเหตุผลที่จะต้องผ่านไปทั้งหมดAppSettingsเพื่อรับ URL หรือเส้นทางไฟล์
JD Davis

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

10

ดูเหมือนว่ารหัสของคุณจะไม่แยกกันอย่างชัดเจนและ / หรือขนาดงานของคุณใหญ่เกินไป

การเปลี่ยนแปลงรหัสควรเป็น 5-10 ไฟล์เว้นแต่ว่าคุณกำลังทำการแปลงรหัสหรือปรับขนาดใหญ่ หากการเปลี่ยนแปลงครั้งเดียวมีการสัมผัสไฟล์จำนวนมากแสดงว่าการเปลี่ยนแปลงของคุณอาจล้มเหลว abstractions ที่ปรับปรุงแล้ว (ความรับผิดชอบเดี่ยวมากกว่า, การแบ่งแยกอินเตอร์เฟส, การพึ่งพาการกลับกัน) ควรช่วย อาจเป็นไปได้ว่าคุณอาจมีความรับผิดชอบเพียงอย่างเดียวและอาจใช้ลัทธิปฏิบัตินิยมมากขึ้น - ลำดับชั้นของประเภทที่สั้นกว่าและบางกว่า นั่นควรทำให้โค้ดนั้นง่ายต่อการเข้าใจเช่นกันเนื่องจากคุณไม่ต้องทำความเข้าใจกับไฟล์หลายสิบไฟล์เพื่อให้รู้ว่าโค้ดกำลังทำอะไรอยู่

อาจเป็นสัญญาณว่างานของคุณใหญ่เกินไป แทนที่จะ "เฮ้เพิ่มคุณสมบัตินี้" (ซึ่งต้องมีการเปลี่ยนแปลง UI และ API การเปลี่ยนแปลงและการเปลี่ยนแปลงการเข้าถึงข้อมูลและการเปลี่ยนแปลงความปลอดภัยและการทดสอบการเปลี่ยนแปลงและ ... ) ทำลายมันลงเป็นชิ้นที่ให้บริการได้มากขึ้น ที่จะง่ายต่อการตรวจสอบและเข้าใจง่ายขึ้นเพราะคุณต้องตั้งสัญญาที่เหมาะสมระหว่างบิต

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


2
ไฟล์ 5-10 ถึง 70-100 ไฟล์เป็นมากกว่าสมมติฐาน งานสุดท้ายของฉันคือการสร้างฟังก์ชั่นบางอย่างในหนึ่งใน microservices รุ่นใหม่ของเรา บริการใหม่ควรได้รับการร้องขอและบันทึกเอกสาร ในการทำเช่นนั้นฉันต้องการคลาสเพื่อเป็นตัวแทนเอนทิตีผู้ใช้ใน 2 ฐานข้อมูลแยกกันและ repos สำหรับแต่ละ Repos เพื่อแสดงตารางอื่น ๆ ที่ฉันต้องการเขียนถึง คลาสเฉพาะสำหรับจัดการการตรวจสอบข้อมูลไฟล์และการสร้างชื่อ และรายการจะดำเนินต่อไป ไม่ต้องพูดถึงทุก ๆ คลาสที่มีลอจิกจะถูกแสดงโดยอินเตอร์เฟสดังนั้นมันจึงสามารถจำลองขึ้นสำหรับการทดสอบหน่วย
JD Davis

1
เท่าที่โค้ดเบสเก่าของเรานั้นมีการรวมกันอย่างแน่นหนาและเสาหินอย่างไม่น่าเชื่อ ด้วยวิธี SOLID การมีเพศสัมพันธ์เพียงอย่างเดียวระหว่างชั้นเรียนได้ในกรณีของ POCOs ทุกอย่างถูกส่งผ่าน DI และอินเทอร์เฟซ
JD Davis

3
@JDDavis - รอทำไม microservice หนึ่งจึงทำงานโดยตรงกับฐานข้อมูลหลาย ๆ
Telastyn

1
มันเป็นการประนีประนอมกับผู้จัดการ dev ของเรา เขาชอบซอฟต์แวร์แบบเสาหินและขั้นตอนอย่างหนาแน่น ด้วยเหตุนี้ microservices ของเราจึงเป็นมาโครที่มากกว่าที่ควรจะเป็น เมื่อโครงสร้างพื้นฐานของเราดีขึ้นเรื่อย ๆ สิ่งต่าง ๆ ก็จะค่อยๆเคลื่อนเข้าสู่ไมโครไซต์ของตนเอง สำหรับตอนนี้เราค่อนข้างทำตามวิธี strangler เพื่อย้ายการทำงานบางอย่างออกเป็น microservices เนื่องจากบริการหลายอย่างต้องการการเข้าถึงทรัพยากรที่เฉพาะเจาะจงเราจึงย้ายบริการเหล่านั้นไปสู่บริการไมโครสโคปของตนเองเช่นกัน
JD Davis

4

ฉันต้องการอธิบายเกี่ยวกับบางสิ่งที่ได้กล่าวมาแล้วที่นี่ แต่เพิ่มเติมจากมุมมองของการวาดขอบเขตของวัตถุ หากคุณกำลังติดตามสิ่งที่คล้ายกับการออกแบบที่ขับเคลื่อนด้วยโดเมนวัตถุของคุณอาจเป็นตัวแทนของธุรกิจของคุณ CustomerและOrderตัวอย่างเช่นจะเป็นวัตถุ ตอนนี้ถ้าฉันต้องเดาตามชื่อคลาสที่คุณมีเป็นจุดเริ่มต้นAccountLogicคลาสของคุณมีรหัสที่จะทำงานสำหรับบัญชีใด ๆ อย่างไรก็ตามใน OO แต่ละชั้นจะมีบริบทและตัวตน คุณไม่ควรรับAccountวัตถุและส่งต่อไปยังAccountLogicคลาสและให้คลาสนั้นทำการเปลี่ยนแปลงกับAccountวัตถุ นั่นคือสิ่งที่เรียกว่าแบบจำลองโลหิตจางและไม่ได้เป็นตัวแทนของ OO เป็นอย่างดี แต่คุณAccountระดับควรมีพฤติกรรมเช่นAccount.Close()หรือAccount.UpdateEmail()และพฤติกรรมเหล่านั้นจะส่งผลกระทบต่ออินสแตนซ์ของบัญชีนั้นเท่านั้น

ตอนนี้วิธีการจัดการพฤติกรรมเหล่านี้สามารถ (และในหลายกรณีควร) ถูกโหลดไปยังการอ้างอิงที่แสดงโดย abstractions (เช่นอินเทอร์เฟซ) Account.UpdateEmailตัวอย่างเช่นอาจต้องการอัปเดตฐานข้อมูลหรือไฟล์หรือส่งข้อความไปยังบัสบริการ ฯลฯ และอาจมีการเปลี่ยนแปลงในอนาคต ดังนั้นAccountคลาสของคุณอาจมีการพึ่งพาตัวอย่างเช่น an IEmailUpdateซึ่งอาจเป็นหนึ่งในหลาย ๆ อินเตอร์เฟสที่มีการใช้งานโดยAccountRepositoryวัตถุ คุณไม่ต้องการที่จะผ่านIAccountRepositoryอินเตอร์เฟซทั้งหมดไปยังAccountวัตถุเพราะมันอาจจะทำมากเกินไปเช่นการค้นหาและค้นหาบัญชี (ใด ๆ ) อื่น ๆ ซึ่งคุณอาจไม่ต้องการให้Accountวัตถุเข้าถึงได้ แต่แม้ว่าAccountRepositoryอาจจะใช้ทั้งสองอย่างIAccountRepositoryและIEmailUpdateอินเตอร์เฟสAccountวัตถุจะสามารถเข้าถึงส่วนเล็ก ๆ ที่ต้องการ นี้จะช่วยให้คุณรักษาอินเตอร์เฟสแยกหลักการ

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

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


2

นั่นคือทั้งหมด 15 ชั้นเรียน (ไม่รวม POCOs และนั่งร้าน) เพื่อทำการบันทึกอย่างตรงไปตรงมา

มันบ้ามาก .... แต่ชั้นเรียนเหล่านี้ฟังดูเหมือนฉันจะเขียนเอง ลองดูที่พวกเขากัน ลองเพิกเฉยต่ออินเตอร์เฟสและการทดสอบก่อน

  • BasePathProvider- IMHO โครงการที่ไม่สำคัญกับการทำงานกับไฟล์จำเป็นต้องใช้ ดังนั้นฉันคิดว่ามันมีสิ่งนั้นอยู่แล้วและคุณสามารถใช้มันได้
  • UniqueFilenameProvider - แน่นอนคุณมีอยู่แล้วใช่ไหม
  • NewGuidProvider - กรณีเดียวกันยกเว้นว่าคุณเพิ่งจะใช้ GUID
  • FileExtensionCombiner - กรณีเดียวกัน
  • PatientFileWriter - ฉันเดาว่านี่เป็นคลาสหลักสำหรับงานปัจจุบัน

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


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

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

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


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

@JDDavis ฉันพยายามเขียนสิ่งที่แตกต่างจากคำตอบอื่น ๆ (ซึ่งฉันเห็นด้วยกับ) เมื่อใดก็ตามที่คุณคัดลอกและวางบางสิ่งคุณจะป้องกันการนำมาใช้ซ้ำแทนการทำสิ่งที่คุณสร้างในโค้ดที่ไม่สามารถใช้ซ้ำได้ซึ่งจะทำให้คุณสามารถคัดลอกและวางได้มากกว่าหนึ่งวัน IMHO เป็นบาปที่ใหญ่เป็นอันดับสองหลังจากทำตามกฎสุ่มสี่สุ่มห้า คุณต้องหาจุดที่น่าสนใจของคุณโดยที่กฎต่อไปนี้ทำให้คุณมีประสิทธิผลมากขึ้น (โดยเฉพาะการเปลี่ยนแปลงในอนาคต) และบางครั้งก็ช่วยเล็กน้อยในกรณีที่ความพยายามไม่เหมาะสม มันคือทั้งหมดที่เกี่ยวข้อง
maaartinus

@JDDavis และทุกอย่างขึ้นอยู่กับคุณภาพของเครื่องมือของคุณ ตัวอย่าง: มีคนอ้างว่าเป็น DI enterprisey และมีความซับซ้อนในขณะที่ผมอ้างว่าเป็นส่วนใหญ่ฟรี +++เกี่ยวกับการทำผิดกฎ: มีสี่คลาสฉันต้องการในสถานที่ซึ่งฉันสามารถฉีดได้หลังจากการปรับโครงสร้างครั้งใหญ่ทำให้รหัสน่าเกลียดมากขึ้น (อย่างน้อยสำหรับตาของฉัน) ดังนั้นฉันตัดสินใจที่จะทำให้พวกเขาเป็นโสด อาจหาวิธีที่ดีกว่านี้ แต่ฉันมีความสุขกับมันจำนวนของซิงเกิลตันเหล่านี้ไม่ได้เปลี่ยนไปตั้งแต่อายุ)
maaartinus

คำตอบนี้เป็นการแสดงออกถึงสิ่งที่ฉันคิดเมื่อ OP เพิ่มตัวอย่างให้กับคำถาม @JDDavis ให้ฉันเพิ่มว่าคุณอาจบันทึกรหัสสำเร็จรูป / คลาสโดยใช้เครื่องมือการทำงานสำหรับกรณีง่าย ๆ ยกตัวอย่างเช่นผู้ให้บริการ GUI - แทนการแนะนำอินเทอร์เฟซใหม่ให้กับคลาสใหม่สำหรับสิ่งนี้ทำไมไม่เพียงแค่ใช้Func<Guid>สิ่งนี้และฉีดเมธอดแบบไม่ระบุชื่อ()=>Guid.NewGuid()เข้าในตัวสร้าง และไม่จำเป็นต้องทดสอบฟังก์ชันเฟรมเวิร์ก. Net นี่คือสิ่งที่ Microsoft ทำเพื่อคุณ ทั้งหมดนี้จะช่วยให้คุณประหยัด 4 คลาส
Doc Brown

... และคุณควรตรวจสอบว่ากรณีอื่น ๆ ที่คุณนำเสนอสามารถทำให้ง่ายขึ้นด้วยวิธีเดียวกัน (อาจไม่ใช่ทั้งหมด)
Doc Brown

2

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

นี่คือสิ่งที่ฉันสงสัยว่านี่กำลังจะปิดราง:

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

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

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

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

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

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

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

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

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

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

นับตั้งแต่ทำการสลับหนึ่งในข้อร้องเรียนที่ใหญ่ที่สุดจากนักพัฒนาคือพวกเขาไม่สามารถยืนทบทวนและตรวจสอบไฟล์หลายสิบไฟล์ซึ่งก่อนหน้านี้ทุกงานต้องการเพียงผู้พัฒนาที่สัมผัส 5-10 ไฟล์

การตรวจสอบแบบเพื่อนนั้นง่ายกว่ามากเมื่อการทดสอบหน่วยผ่านทั้งหมดและส่วนใหญ่ของการตรวจสอบนั้นเป็นเพียงการทำให้แน่ใจว่าการทดสอบนั้นมีความหมาย

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