เราควรกำหนดประเภทสำหรับทุกสิ่งหรือไม่


141

เมื่อเร็ว ๆ นี้ฉันมีปัญหากับการอ่านรหัสของฉัน
ฉันมีฟังก์ชั่นที่ทำการดำเนินการและส่งคืนสตริงที่แสดง ID ของการดำเนินการนี้สำหรับการอ้างอิงในอนาคต (บิตเช่น OpenFile ใน Windows ที่ส่งคืนหมายเลขอ้างอิง) ผู้ใช้จะใช้ ID นี้ในภายหลังเพื่อเริ่มการทำงานและเพื่อตรวจสอบความสมบูรณ์

ID ต้องเป็นสตริงแบบสุ่มเนื่องจากความกังวลเกี่ยวกับการทำงานร่วมกัน สิ่งนี้สร้างวิธีที่มีลายเซ็นที่ไม่ชัดเจนเช่น:

public string CreateNewThing()

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

public OperationIdentifier CreateNewThing()

ประเภทจะมีเฉพาะสตริงและใช้เมื่อใดก็ตามที่มีการใช้สตริงนี้

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

คุณคิดว่าเป็นวิธีปฏิบัติที่ดีในการห่อประเภทที่เรียบง่ายในชั้นเรียนด้วยเหตุผลด้านความปลอดภัยหรือไม่?


4
คุณอาจสนใจอ่านหนังสือเกี่ยวกับประเภทจิ๋ว ฉันเล่นกับมันซักหน่อยและจะไม่แนะนำ แต่มันเป็นวิธีที่สนุกที่จะคิด darrenhobbs.com/2007/04/11/tiny-types
Jeroen Vannevel

40
เมื่อคุณใช้บางอย่างเช่น Haskell คุณจะเห็นว่าการใช้ประเภทช่วยได้มากน้อยเพียงใด ประเภทช่วยให้แน่ใจว่าโปรแกรมถูกต้อง
Nate Symer

3
แม้แต่ภาษาแบบไดนามิกเช่น Erlang ก็ได้รับประโยชน์จากระบบการพิมพ์ซึ่งเป็นเหตุผลว่าทำไมมันจึงมีระบบพิมพ์ดีดและเครื่องมือพิมพ์ดีดที่คุ้นเคยกับ Haskellers แม้จะเป็นภาษาแบบไดนามิก เช่นภาษา C / C ++ ที่จริงประเภทของสิ่งที่เป็นจำนวนเต็มว่าเราเห็นด้วยกับคอมไพเลอร์ในการรักษาวิธีการบางอย่างหรือ Java ที่คุณเกือบ sorta มีชนิดที่ถ้าคุณตัดสินใจที่จะแสร้งทำเป็นชั้นเรียนหลายประเภทในปัจจุบันไม่ว่าจะเป็นปัญหามากไปความหมายใน ภาษา (นี่คือ "คลาส" หรือ "ประเภท"?) หรือความกำกวมประเภท (นี่คือ "URL", "สตริง" หรือ "UPC"?) ในที่สุดใช้เครื่องมืออะไรก็ได้ที่คุณสามารถแก้ความกำกวม
zxq9

7
ฉันไม่แน่ใจว่าแนวคิดมาจาก C ++ นั้นมีค่าใช้จ่ายในประเภทใด ด้วย C ++ 11 ผู้สร้างคอมไพเลอร์ไม่เห็นปัญหาในการให้แลมบ์ดาแต่ละชนิดมีเอกลักษณ์เฉพาะตัว แต่ TBH คุณไม่สามารถคาดหวังได้มากนักในการแสดงความคิดเห็นเกี่ยวกับ "ระบบพิมพ์ C / C ++" ราวกับว่าทั้งสองแบ่งปันระบบประเภท
MSalters

2
@JDB - ไม่ไม่ ไม่เหมือนกัน
Davor Ždralo

คำตอบ:


152

ดั้งเดิมเช่นstringหรือintไม่มีความหมายในโดเมนธุรกิจ เป็นผลโดยตรงจากนี้คือการที่คุณอาจเข้าใจผิดใช้ URL เมื่อรหัสสินค้าที่คาดว่าหรือใช้ปริมาณเมื่อคาดหวังว่าราคา

นี่คือเหตุผลที่ความท้าทาย Object Calisthenicsมีการห่อแบบดั้งเดิมเป็นหนึ่งในกฎ:

กฎข้อที่ 3: ห่อดั้งเดิมและสตริงทั้งหมด

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

เอกสารเดียวกันอธิบายว่ามีประโยชน์เพิ่มเติม:

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

อันที่จริงเมื่อพื้นฐานมีการใช้ก็มักจะเป็นเรื่องยากมากในการติดตามตำแหน่งที่แน่นอนของรหัสที่เกี่ยวข้องกับประเภทที่มักจะนำไปสู่ความรุนแรงซ้ำซ้อนรหัส หากมีPrice: Moneyคลาสเป็นธรรมชาติที่จะหาช่วงตรวจสอบภายใน หากมีการใช้ a int(แย่กว่านั้นdouble) เพื่อจัดเก็บราคาผลิตภัณฑ์ใครควรตรวจสอบช่วง ผลิตภัณฑ์? การคืนเงินหรือไม่ รถเข็น?

ในที่สุดประโยชน์ที่สามที่ไม่ได้กล่าวถึงในเอกสารคือความสามารถในการเปลี่ยนประเภทพื้นฐานที่ค่อนข้างง่าย ถ้าวันนี้ฉันProductIdมีshortเป็นชนิดพื้นฐานของและต่อมาฉันต้องใช้intแทนโอกาสที่รหัสในการเปลี่ยนแปลงจะไม่ขยายฐานรหัสทั้งหมด

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

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

  • ทะเยอทะยานทรัพย์สิน
  • เขตข้อมูลสำรองของมัน
  • คอนสตรัคเตอร์ที่กำหนดค่าให้กับเขตข้อมูลสำรอง
  • กำหนดเองToString(),
  • ความคิดเห็นเกี่ยวกับเอกสาร XML (ซึ่งมีหลายบรรทัด)
  • A EqualsและการGetHashCodeแทนที่ (เช่น LOC จำนวนมาก)

และในที่สุดเมื่อเกี่ยวข้อง:

  • DebuggerDisplayแอตทริบิวต์
  • การแทนที่==และ!=โอเปอเรเตอร์
  • ในที่สุดโอเวอร์โหลดของโอเปอเรเตอร์การแปลงทางอ้อมเพื่อแปลงไปและกลับจากประเภทที่ห่อหุ้ม
  • รหัสสัญญา (รวมถึงค่าคงที่ซึ่งเป็นวิธีที่ค่อนข้างยาวโดยมีคุณสมบัติสามอย่าง)
  • ตัวแปลงหลายตัวที่จะใช้ระหว่างการทำให้เป็นอันดับ XML, การทำให้เป็นอันดับ JSON หรือการจัดเก็บ / การโหลดค่าไปยัง / จากฐานข้อมูล

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

สรุป:

  • ทำการห่อข้อมูลพื้นฐานในคลาสที่มีความหมายในโดเมนธุรกิจของแอปพลิเคชันเมื่อ (1) ช่วยลดข้อผิดพลาด (2) ลดความเสี่ยงในการทำซ้ำรหัสหรือ (3) ช่วยเปลี่ยนประเภทต้นแบบในภายหลัง

  • อย่าห่อแบบดั้งเดิมทุกครั้งที่คุณพบในรหัสของคุณโดยอัตโนมัติ: มีหลายกรณีที่การใช้งานstringหรือใช้งานintได้ดีอย่างสมบูรณ์

ในทางปฏิบัติในการpublic string CreateNewThing()ส่งคืนอินสแตนซ์ของThingIdคลาสแทนstringอาจช่วยได้ แต่คุณอาจ:

  • ส่งคืนอินสแตนซ์ของId<string>คลาสนั่นคือวัตถุประเภททั่วไปที่ระบุว่าประเภทพื้นฐานเป็นสตริง คุณมีประโยชน์ในการอ่านได้โดยไม่ต้องมีข้อ จำกัด ในการดูแลรักษามากมาย

  • ส่งคืนอินสแตนซ์ของThingคลาส หากผู้ใช้ต้องการเพียง ID ก็สามารถทำได้ด้วย:

    var thing = this.CreateNewThing();
    var id = thing.Id;
    

33
ฉันคิดว่าพลังในการเปลี่ยนประเภทพื้นฐานเป็นสิ่งสำคัญที่นี่ มันเป็นสตริงวันนี้ แต่อาจเป็น GUID หรือพรุ่งนี้ยาว การสร้าง wrapper ประเภทจะซ่อนข้อมูลนั้นไว้ซึ่งนำไปสู่การเปลี่ยนแปลงน้อยลงตามท้องถนน ++ คำตอบที่ดีที่นี่
RubberDuck

4
ในกรณีของเงินตรวจสอบให้แน่ใจว่าสกุลเงินนั้นรวมอยู่ในวัตถุเงินหรือโปรแกรมทั้งหมดของคุณใช้สกุลเงินเดียวกัน (ซึ่งควรจะบันทึกไว้)
Paŭlo Ebermann

5
@Blrfl: ในขณะที่มีกรณีที่ถูกต้องที่จำเป็นต้องมีการสืบทอดลึกฉันมักจะเห็นการสืบทอดดังกล่าวในรหัสการออกแบบที่ไม่ได้ตั้งใจอ่านง่ายซึ่งเป็นผลมาจากทัศนคติแบบจ่ายเงินตาม LOC ดังนั้นตัวละครในเชิงลบของส่วนนี้ของคำตอบของฉัน
Arseni Mourzenko

3
@ArTs: นั่นคือ "ขอประณามเครื่องมือเพราะบางคนใช้วิธีที่ผิด" อาร์กิวเมนต์
Blrfl

6
@MainMa ตัวอย่างสำหรับความหมายที่อาจมีการหลีกเลี่ยงข้อผิดพลาดค่าใช้จ่าย: en.wikipedia.org/wiki/Mars_Climate_Orbiter#Cause_of_failure
cbojar

60

ฉันจะใช้ขอบเขตเป็นกฎง่ายๆ: ยิ่งขอบเขตของการสร้างและบริโภคนั้นแคบลงvaluesเท่าไหร่โอกาสที่คุณจะสร้างวัตถุที่มีค่าน้อยลงก็จะยิ่งน้อยลงเท่านั้น

สมมติว่าคุณมีรหัสเทียมต่อไปนี้

id = generateProcessId();
doFancyOtherstuff();
job.do(id);

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


14
เป็นจุดที่ดีมาก ประเภทที่ชัดเจนเป็นเครื่องมือสำคัญในการสื่อสารความตั้งใจซึ่งมีความสำคัญอย่างยิ่งต่อวัตถุและขอบเขตการให้บริการ
Avner Shahar-Kashtan

+1 แม้ว่าคุณจะยังไม่ได้รับการปรับเปลี่ยนรหัสทั้งหมดdoFancyOtherstuff()ในรูทีนย่อยใหม่คุณอาจคิดว่าการjob.do(id)อ้างอิงนั้นไม่ได้อยู่ในท้องถิ่นเพียงพอที่จะเก็บไว้เป็นสตริงธรรมดา
Mark Hurd

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

34

ระบบประเภทคงที่ทั้งหมดเกี่ยวกับการป้องกันการใช้ข้อมูลที่ไม่ถูกต้อง

มีตัวอย่างที่ชัดเจนของประเภทการทำเช่นนี้:

  • คุณไม่สามารถรับเดือนของ UUID ได้
  • คุณไม่สามารถคูณสองสตริงได้

มีตัวอย่างที่ละเอียดยิ่งขึ้น

  • คุณไม่สามารถจ่ายอะไรบางอย่างโดยใช้ความยาวของโต๊ะ
  • คุณไม่สามารถสร้างคำขอ HTTP โดยใช้ชื่อของใครบางคนเป็น URL ได้

เราอาจถูกล่อลวงให้ใช้doubleทั้งราคาและความยาวหรือใช้stringทั้งชื่อและ URL แต่การทำเช่นนั้นจะทำลายระบบการพิมพ์ที่ยอดเยี่ยมของเราและทำให้การใช้งานผิดพลาดเหล่านี้ผ่านการตรวจสอบแบบคงที่ของภาษา

การเดินทางปอนด์วินาทีสับสนกับนิวตันวินาทีสามารถมีผลลัพธ์ที่น่าสงสารที่รันไทม์


นี่เป็นปัญหากับสตริงโดยเฉพาะ พวกเขามักจะกลายเป็น "ประเภทข้อมูลสากล"

เราคุ้นเคยกับอินเตอร์เฟสข้อความเป็นหลักกับคอมพิวเตอร์และเรามักจะขยายส่วนติดต่อผู้ใช้ (UIs) เหล่านี้เป็นอินเตอร์เฟสการเขียนโปรแกรม (API) เราคิดว่า 34.25 เป็นตัวอักษรที่34.25 เราคิดว่าวันที่เป็นตัวละคร2015/05/03 เราคิดว่า UUID เป็นตัวละครที่75e945ee-f1e9-11e4-b9b2-1697f925ec7b

แต่โมเดลจิตนี้เป็นอันตรายต่อสิ่งที่เป็นนามธรรม

คำหรือภาษาตามที่เขียนหรือพูดดูเหมือนจะไม่มีบทบาทใด ๆ ในกลไกการคิดของฉัน

Albert Einstein

ในทำนองเดียวกันการเป็นตัวแทนข้อความไม่ควรมีบทบาทใด ๆ ในการออกแบบประเภทและ API ระวังstring! (และประเภท "ดั้งเดิม" ทั่วไปอื่น ๆ ที่มากเกินไป)


ประเภทสื่อสาร "การทำงานที่สมเหตุสมผล"

ตัวอย่างเช่นฉันเคยทำงานกับลูกค้าเพื่อ HTTP REST API REST ดำเนินการอย่างถูกต้องใช้เอนทิตีสื่อบันทึกหลายมิติซึ่งมีไฮเปอร์ลิงก์ชี้ไปยังเอนทิตีที่เกี่ยวข้อง ในไคลเอนต์นี้ไม่เพียง แต่พิมพ์เอนทิตี (เช่นผู้ใช้บัญชีการสมัครสมาชิก) การเชื่อมโยงไปยังเอนทิตีเหล่านั้นก็ถูกพิมพ์ด้วยเช่นกัน (UserLink, AccountLink, SubscriptionLink) ลิงก์นั้นเล็กไปกว่าการพันรอบ ๆUriแต่การแยกประเภทต่าง ๆ ทำให้มันเป็นไปไม่ได้ที่จะลองใช้ AccountLink เพื่อดึงข้อมูลผู้ใช้ หากทุกอย่างเป็นธรรมดาUri- หรือแย่กว่านั้นstring- ความผิดพลาดเหล่านี้จะพบได้ที่รันไทม์เท่านั้น

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


แน่นอนสิ่งดีทั้งหมดสามารถใช้เกิน พิจารณา

  1. เพิ่มความคมชัดให้กับโค้ดของคุณมากเพียงใด

  2. ใช้บ่อยแค่ไหน

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


21
"สตริงมักจะกลายเป็นชนิดข้อมูล 'สากล'" ซึ่งเป็นที่มาของชื่อเล่น "สตริงที่พิมพ์ในภาษาที่มีแก้ม"
MSalters

@MSters ฮ่าฮ่าดีมาก
พอลเดรเปอร์

4
และแน่นอน05-03-2015 ว่า การตีความหมายถึงวันที่หมายความว่าอะไร?
CVn

10

คุณคิดว่าเป็นวิธีปฏิบัติที่ดีในการห่อประเภทที่เรียบง่ายในชั้นเรียนด้วยเหตุผลด้านความปลอดภัยหรือไม่?

บางครั้ง

นี้เป็นหนึ่งในกรณีที่คุณต้องชั่งน้ำหนักปัญหาที่อาจเกิดขึ้นจากการใช้แทนที่จะเป็นรูปธรรมมากขึ้นstring OperationIdentifierความรุนแรงของพวกเขาคืออะไร? ความชอบของพวกเขาคืออะไร?

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

ในบางกรณีคุณจะประหยัดเวลาและความพยายามด้วยการมีรูปแบบที่ดีในการทำงาน ในคนอื่น ๆ มันจะไม่คุ้มค่ากับปัญหา

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


10

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

  • ประสิทธิภาพ. ฉันต้องอ้างอิงภาษาจริงที่นี่ ใน C # ถ้า ID ลูกค้าสั้นและคุณห่อไว้ในคลาส - คุณเพิ่งสร้างโอเวอร์เฮดจำนวนมาก: หน่วยความจำเนื่องจากตอนนี้ 8 ไบต์บนระบบ 64 บิตและความเร็วนับตั้งแต่ตอนนี้มันถูกจัดสรรบนฮีป
  • เมื่อคุณตั้งสมมติฐานกับประเภท หากรหัสลูกค้าสั้นและคุณมีตรรกะบางอย่างที่บรรจุไว้ในบางลักษณะ - โดยปกติคุณจะทำการตั้งสมมติฐานกับประเภทนั้น ในสถานที่เหล่านั้นตอนนี้คุณต้องทำลายสิ่งที่เป็นนามธรรม หากเป็นจุดเดียวก็ไม่ใช่เรื่องใหญ่หากทั่วทุกมุมคุณอาจพบว่าครึ่งหนึ่งของเวลาที่คุณใช้ดั้งเดิม
  • เพราะไม่ใช่ทุกภาษามี typedef และสำหรับภาษาที่ไม่มีและโค้ดที่เขียนไปแล้วการเปลี่ยนแปลงเช่นนี้อาจเป็นงานใหญ่ที่อาจทำให้เกิดข้อบกพร่อง (แต่ทุกคนมีชุดทดสอบที่มีการครอบคลุมที่ดีใช่ไหม)
  • ในบางกรณีจะลดความสามารถในการอ่าน ฉันจะพิมพ์สิ่งนี้ได้อย่างไร ฉันจะตรวจสอบสิ่งนี้ได้อย่างไร ฉันควรตรวจสอบโมฆะหรือไม่ มีคำถามทั้งหมดที่คุณต้องเจาะลึกลงไปในความหมายของประเภท

3
แน่นอนถ้าชนิด wrapper เป็นแบบโครงสร้างแทนที่จะเป็นคลาสดังนั้น a short(ตัวอย่าง) จะมีค่าใช้จ่ายแบบเดียวกันหรือไม่ได้ห่อ (?)
MathematicalOrchid

1
มันจะไม่เป็นการออกแบบที่ดีสำหรับประเภทที่จะแค็ปซูลตรวจสอบความถูกต้องของตัวเองหรือไม่? หรือเพื่อสร้างผู้ช่วยประเภทเพื่อตรวจสอบมันได้หรือไม่
Nick Udell

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

7

ไม่คุณไม่ควรกำหนดประเภท (คลาส) สำหรับ "ทุกอย่าง"

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

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

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


5

ดูเหมือนว่าสิ่งที่คุณต้องทำก็คือระบุการผ่าตัด

นอกจากนี้คุณพูดในสิ่งที่การดำเนินการควรจะทำ:

เพื่อเริ่มการทำงาน [และ] เพื่อตรวจสอบความเรียบร้อย

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

มันบอกว่า

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

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

เกี่ยวกับความคิดเห็น

มันจะเป็น wtf-y ในการเรียกคำสั่งคลาสนี้แล้วไม่มีตรรกะอยู่ภายใน

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

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

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

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


เนื่องจากรูปแบบเป็นนามธรรมมันอาจเป็นเรื่องยากที่จะเกิดขึ้นกับคำอุปมาอุปมัยโลกแห่งความจริง นี่คือความพยายาม:

"เฮ้ย่าคุณช่วยกรุณากดปุ่มบันทึกบนทีวีที่ 12 ดังนั้นฉันไม่ควรพลาดซิมป์สันที่ช่อง 1?"

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


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

มันสมเหตุสมผลมาก พิจารณาการส่งคืนการดำเนินการไม่ใช่ OperationIdentifier สิ่งนี้คล้ายกับ C # TPL ที่คุณส่งคืนภารกิจหรืองาน <T> @Ziv: คุณพูดว่า "ตรรกะของการดำเนินการถูกห่อหุ้มในคลาสอื่น" แต่นั่นหมายความว่าคุณไม่สามารถจัดหาได้จากที่นี่เช่นกัน?
Moby Disk

@Ziv ฉันขอแตกต่าง ดูเหตุผลที่ฉันเพิ่มในคำตอบของฉันและดูว่าพวกเขามีเหตุผลกับคุณหรือไม่ =)
null

5

แนวคิดเบื้องหลังการห่อแบบดั้งเดิม

  • เพื่อสร้างภาษาเฉพาะโดเมน
  • จำกัด ข้อผิดพลาดของผู้ใช้โดยส่งค่าที่ไม่ถูกต้อง

เห็นได้ชัดว่ามันเป็นเรื่องยากมากและไม่สามารถนำไปใช้ได้ทุกที่

ตัวอย่างเช่นถ้าคุณมีคำสั่งเรียน

Order
{
   long OrderId { get; set; }
   string InvoiceNumber { get; set; }
   string Description { get; set; }
   double Amount { get; set; }
   string CurrencyCode { get; set; }
}

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

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

    Order
    {
       OrderIdType OrderId { get; set; }
       InvoiceNumberType InvoiceNumber { get; set; }
       string Description { get; set; }
       Currency Amount { get; set; }
    }

ดังนั้นจึงไม่มีเหตุผลที่จะห่อทุกอย่าง แต่สิ่งที่สำคัญจริงๆ


4

ความคิดเห็นไม่เป็นที่นิยม:

โดยปกติแล้วคุณไม่ควรกำหนดประเภทใหม่!

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

ประเภทหลอกทำให้ฟังก์ชั่นห้องสมุดทั่วไปไร้ประโยชน์

ฟังก์ชั่นคณิตศาสตร์ของ Java สามารถทำงานได้กับตัวเลขพื้นฐานทั้งหมด แต่ถ้าคุณกำหนดเปอร์เซ็นต์ของคลาสใหม่ (การตัด double ที่สามารถอยู่ในช่วง 0 ~ 1) ฟังก์ชันเหล่านี้ทั้งหมดจะไร้ประโยชน์และจำเป็นต้องถูกห่อด้วยคลาสที่ (ยิ่งแย่กว่า) จำเป็นต้องรู้เกี่ยวกับการเป็นตัวแทนภายในของ Percentage class .

ประเภทหลอกไปไวรัส

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

นำข้อความออกไป

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

ตัวอย่าง:

A uintสามารถเป็นตัวแทนของ UserId ได้อย่างสมบูรณ์เราสามารถใช้ตัวดำเนินการของ Java ในตัวuintของ s (เช่น ==) และเราไม่ต้องการตรรกะทางธุรกิจเพื่อปกป้อง 'สถานะภายใน' ของรหัสผู้ใช้


ตกลง. ปรัชญาการเขียนโปรแกรมทั้งหมดเป็นสิ่งที่ดี แต่ในทางปฏิบัติคุณต้องทำให้มันใช้งานได้ดี
Ewan

หากคุณเห็นโฆษณาใด ๆ สำหรับการกู้เงินให้กับคนที่ยากจนที่สุดในสหราชอาณาจักรคุณจะพบว่าเปอร์เซ็นต์ที่จะรวมเป็นสองเท่าในช่วง
0..1

1
ใน C / C ++ / Objective C คุณสามารถใช้ typedef ซึ่งให้ชื่อใหม่สำหรับประเภทดั้งเดิมที่มีอยู่ ในทางกลับกันรหัสของคุณควรทำงานไม่ว่าประเภทพื้นฐานเช่นถ้าฉันเปลี่ยน OrderId จาก uint32_t เป็น uint64_t (เพราะฉันต้องการประมวลผลคำสั่งมากกว่า 4 พันล้านรายการ)
gnasher729

ฉันจะไม่ลงคะแนนให้คุณเพราะความกล้าหาญของคุณ แต่ pseudotypes และประเภทที่กำหนดไว้ในคำตอบที่แตกต่างกันไป เป็นประเภทที่เฉพาะเจาะจงทางธุรกิจ Psuedotypes เป็นประเภทที่ยังคงทั่วไป
0fnt

@ gnasher729: C ++ 11 ยังมีตัวอักษรที่ผู้ใช้กำหนดเองซึ่งทำให้กระบวนการห่อหุ้มนั้นหวานมากสำหรับผู้ใช้: ระยะทาง d = 36.0_mi + 42.0_km; . msdn.microsoft.com/en-us/library/dn919277.aspx
damix911

3

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

NamingConventionเป็นกุญแจสำคัญในการอ่านได้ ทั้งสองรวมกันสามารถถ่ายทอดความตั้งใจอย่างชัดเจน
แต่ /// ยังมีประโยชน์

ใช่ฉันจะบอกว่าสร้างประเภทของตัวเองเมื่ออายุการใช้งานของพวกเขาเกินขอบเขตของคลาส พิจารณาการใช้ทั้งสองอย่างStructและClassไม่ใช่คลาสเสมอ


2
อะไร///หมายความว่าอย่างไร
Gabe

1
/// เป็นความคิดเห็นเอกสารประกอบใน C # ที่ช่วยให้ Intellisense แสดงเอกสารของลายเซ็นที่จุดวิธีการโทร พิจารณาวิธีที่ดีที่สุดในการรหัสเอกสาร สามารถทำไร่ไถนาด้วยเครื่องมือและเสนอการเข้าถึงการทำงานของระบบ Intellisence msdn.microsoft.com/en-us/library/tkxs89c5%28v=vs.71%29.aspx
phil soady
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.