คำถามนี้เป็นเรื่องของบล็อกของฉันกันยายน 20 2010 คำตอบของ Josh และ Chad ("พวกเขาไม่เพิ่มคุณค่าแล้วทำไมต้องใช้" และ "เพื่อขจัดความซ้ำซ้อน") นั้นถูกต้องโดยพื้นฐาน เพื่อให้เนื้อออกมามากขึ้น:
คุณลักษณะที่ช่วยให้คุณสามารถกำหนดรายการอาร์กิวเมนต์ซึ่งเป็นส่วนหนึ่งของ "คุณลักษณะที่ใหญ่กว่า" ของวัตถุเริ่มต้นได้พบกับคุณลักษณะ "น้ำตาล" ของเรา บางประเด็นที่เราพิจารณา:
- ต้นทุนการออกแบบและข้อกำหนดต่ำ
- เรากำลังจะเปลี่ยนรหัสตัวแยกวิเคราะห์ที่จัดการกับการสร้างออบเจ็กต์อย่างกว้างขวาง ต้นทุนการพัฒนาเพิ่มเติมในการสร้างรายการพารามิเตอร์เป็นทางเลือกนั้นไม่มากเมื่อเทียบกับต้นทุนของคุณสมบัติที่ใหญ่กว่า
- ภาระการทดสอบค่อนข้างน้อยเมื่อเทียบกับต้นทุนของคุณสมบัติที่ใหญ่กว่า
- ภาระงานเอกสารค่อนข้างน้อยเมื่อเทียบ ...
- ภาระการบำรุงรักษาคาดว่าจะน้อย ฉันจำไม่ได้ว่ามีข้อบกพร่องใด ๆ ที่รายงานในคุณลักษณะนี้ในช่วงหลายปีนับตั้งแต่มีการจัดส่ง
- คุณลักษณะนี้ไม่ก่อให้เกิดความเสี่ยงที่ชัดเจนในทันทีต่อคุณลักษณะในอนาคตในด้านนี้ (สิ่งสุดท้ายที่เราต้องการทำคือการสร้างคุณลักษณะที่ง่ายและราคาถูกในตอนนี้ซึ่งจะทำให้การใช้คุณลักษณะที่น่าสนใจยิ่งขึ้นในอนาคตทำได้ยากขึ้นมาก)
- คุณลักษณะนี้ไม่เพิ่มความคลุมเครือใหม่ ๆ ให้กับการวิเคราะห์ศัพท์ไวยากรณ์หรือความหมายของภาษา ไม่มีปัญหาสำหรับการวิเคราะห์ประเภท "โปรแกรมบางส่วน" ที่ดำเนินการโดยเอ็นจิ้น "IntelliSense" ของ IDE ในขณะที่คุณกำลังพิมพ์ และอื่น ๆ
- คุณลักษณะนี้กระทบ "จุดหวาน" ทั่วไปสำหรับคุณลักษณะการเริ่มต้นของวัตถุที่มีขนาดใหญ่ขึ้น โดยทั่วไปถ้าคุณใช้ object initializer มันเป็นเพราะคอนสตรัคเตอร์ของอ็อบเจกต์ไม่อนุญาตให้คุณตั้งค่าคุณสมบัติที่คุณต้องการ เป็นเรื่องปกติมากที่วัตถุดังกล่าวจะเป็นเพียง "ถุงคุณสมบัติ" ที่ไม่มีพารามิเตอร์ใน ctor ตั้งแต่แรก
แล้วทำไมคุณไม่ได้ยังทำให้วงเล็บที่ว่างเปล่าเป็นตัวเลือกในการโทรสร้างเริ่มต้นของการแสดงออกสร้างวัตถุที่ไม่ได้มีการเริ่มต้นวัตถุ?
ลองดูรายการเกณฑ์ด้านบนอีกครั้ง หนึ่งในนั้นคือการเปลี่ยนแปลงไม่ได้ทำให้เกิดความคลุมเครือใหม่ ๆ ในการวิเคราะห์คำศัพท์ไวยากรณ์หรือความหมายของโปรแกรม การเปลี่ยนแปลงที่เสนอของคุณจะแนะนำความคลุมเครือการวิเคราะห์ความหมาย:
class P
{
class B
{
public class M { }
}
class C : B
{
new public void M(){}
}
static void Main()
{
new C().M(); // 1
new C.M(); // 2
}
}
บรรทัดที่ 1 สร้าง C ใหม่เรียกใช้ตัวสร้างเริ่มต้นจากนั้นเรียกวิธีอินสแตนซ์ M บนอ็อบเจ็กต์ใหม่ บรรทัดที่ 2 สร้างอินสแตนซ์ใหม่ของ BM และเรียกใช้ตัวสร้างเริ่มต้น หากวงเล็บในบรรทัดที่ 1 เป็นทางเลือกบรรทัดที่ 2 จะไม่ชัดเจน จากนั้นเราจะต้องสร้างกฎที่แก้ไขความคลุมเครือ เราไม่สามารถทำให้เป็นข้อผิดพลาดได้เพราะนั่นจะเป็นการเปลี่ยนแปลงขั้นสุดท้ายที่เปลี่ยนโปรแกรม C # ทางกฎหมายที่มีอยู่ให้เป็นโปรแกรมที่ใช้งานไม่ได้
ดังนั้นกฎจะต้องมีความซับซ้อนมากโดยพื้นฐานแล้ววงเล็บเป็นเพียงทางเลือกในกรณีที่ไม่ได้ทำให้เกิดความคลุมเครือ เราต้องวิเคราะห์กรณีที่เป็นไปได้ทั้งหมดที่แนะนำความไม่ชัดเจนจากนั้นเขียนโค้ดในคอมไพเลอร์เพื่อตรวจจับ
ในแง่นั้นให้ย้อนกลับไปดูค่าใช้จ่ายทั้งหมดที่ฉันพูดถึง ตอนนี้มีกี่คนที่มีขนาดใหญ่? กฎที่ซับซ้อนมีค่าใช้จ่ายในการออกแบบข้อกำหนดการพัฒนาการทดสอบและการจัดทำเอกสารขนาดใหญ่ กฎที่ซับซ้อนมีแนวโน้มที่จะทำให้เกิดปัญหากับการโต้ตอบที่ไม่คาดคิดกับคุณลักษณะต่างๆในอนาคต
ทั้งหมดนี้เพื่ออะไร? ผลประโยชน์ของลูกค้าเพียงเล็กน้อยที่ไม่ได้เพิ่มพลังในการเป็นตัวแทนใหม่ให้กับภาษา แต่เพิ่มกรณีมุมที่บ้าคลั่งเพียงแค่รอตะโกน "gotcha" ให้กับจิตวิญญาณที่ไม่สงสัยที่น่าสงสาร คุณลักษณะเช่นนี้จะถูกตัดทันทีและใส่ไว้ในรายการ "ไม่เคยทำ"
คุณพิจารณาความคลุมเครือนั้นได้อย่างไร?
สิ่งนั้นชัดเจนทันที ฉันค่อนข้างคุ้นเคยกับกฎใน C # ในการพิจารณาว่าเมื่อใดที่คาดว่าจะมีชื่อประ
เมื่อพิจารณาคุณสมบัติใหม่คุณจะพิจารณาได้อย่างไรว่ามันทำให้เกิดความคลุมเครือหรือไม่? ด้วยมือโดยการพิสูจน์อย่างเป็นทางการโดยการวิเคราะห์ด้วยเครื่องจักรอะไร?
ทั้งสาม. ส่วนใหญ่เราแค่ดูสเป็คและเส้นก๋วยเตี๋ยวตามที่ได้กล่าวไว้ข้างต้น ตัวอย่างเช่นสมมติว่าเราต้องการเพิ่มตัวดำเนินการคำนำหน้าใหม่ใน C # ที่เรียกว่า "frob":
x = frob 123 + 456;
(UPDATE: frob
แน่นอนว่าawait
การวิเคราะห์ในที่นี้เป็นการวิเคราะห์ที่ทีมออกแบบดำเนินการเมื่อเพิ่มawait
)
"frob" ในที่นี้จะเหมือนกับ "new" หรือ "++" - มาก่อนนิพจน์บางประเภท เราจะหาลำดับความสำคัญและการเชื่อมโยงที่ต้องการและอื่น ๆ จากนั้นเริ่มถามคำถามเช่น "จะเกิดอะไรขึ้นถ้าโปรแกรมมีประเภทฟิลด์คุณสมบัติเหตุการณ์วิธีการค่าคงที่หรือที่เรียกว่า frob อยู่แล้ว นั่นจะนำไปสู่กรณีต่างๆทันทีเช่น:
frob x = 10;
หมายความว่า "ทำการดำเนินการ frob กับผลลัพธ์ของ x = 10 หรือสร้างตัวแปรประเภท frob ที่เรียกว่า x และกำหนด 10 ให้? (หรือถ้า frobbing สร้างตัวแปรก็อาจเป็นการกำหนด 10 ถึงfrob x
หลังจากนั้นให้*x = 10;
แยกวิเคราะห์และถูกต้องตามกฎหมายถ้าx
เป็นint*
)
G(frob + x)
หมายความว่า "frob ผลลัพธ์ของตัวดำเนินการยูนารีบวกบน x" หรือ "เพิ่มนิพจน์ frob เป็น x"?
และอื่น ๆ เพื่อแก้ไขความไม่ชัดเจนเหล่านี้เราอาจแนะนำฮิวริสติกส์ เมื่อคุณพูดว่า "var x = 10;" มันคลุมเครือ อาจหมายถึง "อนุมานประเภทของ x" หรืออาจหมายถึง "x เป็นประเภท var" ดังนั้นเราจึงมีฮิวริสติก: เราพยายามค้นหาประเภทที่ชื่อ var ก่อนและถ้าไม่มีอยู่เราจะอนุมานประเภทของ x
หรือเราอาจเปลี่ยนรูปแบบเพื่อไม่ให้มีความคลุมเครือ เมื่อพวกเขาออกแบบ C # 2.0 พวกเขามีปัญหานี้:
yield(x);
หมายความว่า "ให้ x ในตัววนซ้ำ" หรือ "เรียกวิธีการให้ผลตอบแทนด้วยอาร์กิวเมนต์ x" โดยเปลี่ยนเป็น
yield return(x);
ตอนนี้มันไม่ชัดเจน
ในกรณีของการ parens ตัวเลือกในวัตถุ initializer มันจะตรงไปตรงมาเกี่ยวกับเหตุผลที่ว่ามีความงงงวยแนะนำหรือไม่เพราะจำนวนของสถานการณ์ในการที่จะได้รับอนุญาตที่จะแนะนำสิ่งที่เริ่มต้นด้วย {มีขนาดเล็กมาก โดยพื้นฐานแล้วมีเพียงบริบทคำสั่งต่างๆคำสั่ง lambdas ตัวเริ่มต้นอาร์เรย์และนั่นคือเกี่ยวกับมัน เป็นเรื่องง่ายที่จะให้เหตุผลในทุกกรณีและแสดงให้เห็นว่าไม่มีความคลุมเครือ การตรวจสอบให้แน่ใจว่า IDE มีประสิทธิภาพค่อนข้างยาก แต่สามารถทำได้โดยไม่มีปัญหามากเกินไป
การเล่นซอแบบนี้มักจะเพียงพอ หากเป็นคุณสมบัติที่ยุ่งยากเป็นพิเศษเราจะดึงเครื่องมือที่หนักกว่าออกมา ตัวอย่างเช่นเมื่อออกแบบ LINQ หนึ่งในกลุ่มคอมไพเลอร์และหนึ่งใน IDE ที่มีพื้นฐานในทฤษฎีตัวแยกวิเคราะห์ได้สร้างตัวสร้างตัวแยกวิเคราะห์ที่สามารถวิเคราะห์ไวยากรณ์เพื่อค้นหาความคลุมเครือจากนั้นจึงป้อนไวยากรณ์ C # ที่เสนอเพื่อความเข้าใจในการสืบค้น ; การทำเช่นนี้พบหลายกรณีที่ข้อความค้นหาไม่ชัดเจน
หรือเมื่อเราทำการอนุมานประเภทขั้นสูงเกี่ยวกับ lambdas ใน C # 3.0 เราได้เขียนข้อเสนอของเราแล้วส่งไปยัง Microsoft Research ในเคมบริดจ์ซึ่งทีมภาษามีดีพอที่จะพิสูจน์หลักฐานอย่างเป็นทางการว่าข้อเสนอการอนุมานประเภทคือ ในทางทฤษฎีเสียง
มีความคลุมเครือใน C # วันนี้หรือไม่?
แน่ใจ
G(F<A, B>(0))
ใน C # 1 มีความชัดเจนว่าหมายถึงอะไร มันเหมือนกับ:
G( (F<A), (B>0) )
นั่นคือมันเรียก G ด้วยอาร์กิวเมนต์สองตัวที่เป็นบูล ใน C # 2 นั่นอาจหมายถึงความหมายใน C # 1 แต่อาจหมายถึง "ส่ง 0 ไปยังวิธีการทั่วไป F ที่รับพารามิเตอร์ประเภท A และ B แล้วส่งผลลัพธ์ของ F ไปยัง G" เราได้เพิ่มฮิวริสติกที่ซับซ้อนให้กับตัวแยกวิเคราะห์ซึ่งกำหนดว่าคุณอาจหมายถึงกรณีใดในสองกรณี
ในทำนองเดียวกันการร่ายมีความคลุมเครือแม้ใน C # 1.0:
G((T)-x)
"เหวี่ยง -x เป็น T" หรือ "ลบ x ออกจาก T" หรือไม่ อีกครั้งเรามีฮิวริสติกที่ทำให้เดาได้ดี