Source Code ของ Doom 3

นี่คือเรื่องราวเกี่ยวกับซอร์สโค้ดของDoom 3และมันสวยงามแค่ไหน ใช่ที่สวยงาม ให้ฉันอธิบาย

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

บทความนี้ปรากฏครั้งแรกเมื่อวันที่ 14 มกราคม 2013

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

ในช่วงหกสัปดาห์สุดท้ายของการพัฒนาDyadฉันได้เพิ่มโค้ดมากกว่า 13k บรรทัด MainMenu.cc บอลลูนเป็น 24,501 เส้น ซอร์สโค้ดที่สวยงามครั้งหนึ่งคือความยุ่งเหยิงที่เต็มไปด้วย #ifdefs, ตัวชี้ฟังก์ชันที่ไม่จำเป็น, SIMD แบบอินไลน์และโค้ด asm ที่น่าเกลียด - ฉันได้เรียนรู้คำศัพท์ใหม่: "โค้ดเอนโทรปี" ฉันค้นหาอินเทอร์เน็ตเพื่อหาโปรเจ็กต์อื่น ๆ ที่สามารถใช้เพื่อเรียนรู้วิธีจัดระเบียบโค้ดหลายแสนบรรทัด หลังจากดูเอ็นจิ้นเกมขนาดใหญ่หลายตัวฉันรู้สึกท้อแท้มาก คู่รหัสที่มาก็ไม่จริงที่ไม่ดีเมื่อเทียบกับทุกอย่างอื่นออกมี!

ไม่พอใจฉันมองต่อไปและพบการวิเคราะห์ซอร์สโค้ดDoom 3ของ id Softwareโดยผู้เชี่ยวชาญด้านคอมพิวเตอร์ Fabien Sanglard

ฉันใช้เวลาสองสามวันในการอ่านซอร์สโค้ดDoom 3และอ่านบทความที่ยอดเยี่ยมของ Fabien เมื่อฉันทวีต:

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

เพื่อนำสิ่งต่างๆมาสู่มุมมอง: Dyadมีโค้ด 193k บรรทัด C ++ ทั้งหมด Doom 3มี 601k, Quake IIIมี 229k และQuake IIมี 136k ที่ทำให้คู่หนึ่งในระหว่างQuake IIและQuake III เหล่านี้เป็นโครงการขนาดใหญ่

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

มีมาตรฐานการเข้ารหัส idTech 4 ( .doc ) ที่ฉันคิดว่าควรค่าแก่การอ่าน ฉันทำตามมาตรฐานเหล่านี้เกือบทั้งหมดและฉันจะพยายามอธิบายว่าทำไมถึงดีและทำไมพวกเขาถึงทำให้โค้ดDoom 3สวยงามมากโดยเฉพาะ

หนึ่งในสิ่งที่ฉลาดที่สุดที่ผมเคยเห็นจากการลงโทษคือการใช้ทั่วไปของการวิเคราะห์ของพวกเขาศัพท์[ 1 ]และ parser [ 2 ] ไฟล์รีซอร์สทั้งหมดเป็นไฟล์ ascii ที่มีไวยากรณ์แบบรวมซึ่งรวมถึงสคริปต์ไฟล์แอนิเมชั่นไฟล์กำหนดค่า ฯลฯ ทุกอย่างเหมือนกัน สิ่งนี้ช่วยให้สามารถอ่านและประมวลผลไฟล์ทั้งหมดโดยใช้โค้ดกลุ่มเดียว ตัวแยกวิเคราะห์มีประสิทธิภาพโดยเฉพาะอย่างยิ่งโดยรองรับชุดย่อยหลักของ C ++ ด้วยการยึดติดกับตัวแยกวิเคราะห์แบบรวมและตัวอักษรส่วนประกอบอื่น ๆ ทั้งหมดของเครื่องยนต์ไม่จำเป็นต้องกังวลเกี่ยวกับการทำให้เป็นอนุกรมข้อมูลเนื่องจากมีรหัสสำหรับสิ่งนั้นอยู่แล้ว สิ่งนี้ทำให้ส่วนอื่น ๆ ของโค้ดสะอาดขึ้น

Doomรหัส ‘s ค่อนข้างแข็งแม้จะพอที่จะไม่แข็งในความคิดของฉันด้วยความเคารพ const [ 3 ] Const ตอบสนองวัตถุประสงค์หลายประการซึ่งฉันเชื่อว่าโปรแกรมเมอร์หลายคนเพิกเฉย กฎของฉันคือ "ทุกอย่างควรเป็น const เสมอเว้นแต่จะเป็นไม่ได้" ฉันหวังว่าตัวแปรทั้งหมดใน C ++ เป็นค่าเริ่มต้น Doomมักจะยึดติดกับนโยบายพารามิเตอร์ "no in-out"; หมายความว่าพารามิเตอร์ทั้งหมดของฟังก์ชันอาจเป็นอินพุตหรือเอาต์พุตไม่ได้ทั้งสองอย่าง สิ่งนี้ช่วยให้เข้าใจสิ่งที่เกิดขึ้นกับตัวแปรได้ง่ายขึ้นมากเมื่อคุณส่งผ่านไปยังฟังก์ชัน ตัวอย่างเช่น:

นิยามฟังก์ชันนี้ทำให้ฉันมีความสุข!

ฉันรู้หลายสิ่งหลายอย่างจาก consts เพียงเล็กน้อย:

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

นี่เป็นปัญหาเกี่ยวกับโวหาร แต่สิ่งที่สวยงามอย่างหนึ่งที่Doomมักจะทำคือไม่แสดงความคิดเห็นมากเกินไป ฉันเคยเห็นโค้ดมากเกินไปที่ดูเหมือน:

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

// แยกพื้นผิวออกเป็นพื้นผิวด้านหน้าและด้านหลังพื้นผิวจะไม่เปลี่ยนแปลง
// frontOnPlaneEdges และ backOnPlaneEdges เป็นทางเลือกที่จะจัดเก็บดัชนีไว้ที่ขอบที่วางบนระนาบแยก
// ส่งคืน SIDE_?

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

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

Doomไม่เสียพื้นที่แนวตั้ง:

นี่คือตัวอย่างจาก t_stencilShadow :: R_ChopWinding ():

ฉันสามารถอ่านอัลกอริทึมทั้งหมดนั้นได้ใน 1/4 ของหน้าจอโดยปล่อยให้อีก 3 / 4s เพื่อทำความเข้าใจว่าบล็อกของโค้ดนั้นพอดีกับโค้ดโดยรอบที่ใด ฉันเห็นโค้ดแบบนี้มากเกินไป:

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

หลังใช้เวลา 18 บรรทัดเทียบกับ 11 ในครั้งแรก นั่นคือเกือบสองเท่าของจำนวนบรรทัดของโค้ดสำหรับฟังก์ชันเดียวกัน * EXACT * หมายความว่าโค้ดส่วนถัดไปไม่พอดีกับหน้าจอสำหรับฉัน ชิ้นต่อไปคืออะไร?

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

อีกสิ่งหนึ่งที่ id บอกว่าฉันเชื่อว่า "ถูกต้อง" และไม่ใช่ปัญหาเรื่องสไตล์คือพวกเขา * ใช้เสมอ * {} แม้ว่าจะเลือกได้ ฉันคิดว่ามันเป็นความผิดที่จะข้ามวงเล็บปีกกา ฉันเคยเห็นรหัสมากมายเช่น:

นั่นเป็นรหัสที่น่าเกลียดแย่กว่าการใส่ {} บรรทัดของตัวเองเสียอีก ฉันไม่พบตัวอย่างเดียวในรหัสของรหัสที่พวกเขาข้าม {} การละเว้น {} ทางเลือกทำให้การแยกวิเคราะห์นี้ while () บล็อกใช้เวลานานกว่าที่จำเป็น นอกจากนี้ยังทำให้การแก้ไขเป็นเรื่องที่น่าปวดหัวถ้าฉันต้องการแทรกสาขา if-statement ภายในเส้นทางอื่น if (c> d)

id ทำสิ่งที่ไม่สำคัญในโลก C ++ พวกเขาเขียนฟังก์ชันSTL [ 4 ] ที่จำเป็นทั้งหมดอีกครั้ง ฉันเองมีความสัมพันธ์แบบรัก - เกลียดชังกับ STL ในDyadฉันใช้มันในการสร้างดีบักเพื่อจัดการทรัพยากรแบบไดนามิก ในการเปิดตัวฉันได้อบทรัพยากรทั้งหมดเพื่อให้สามารถโหลดได้เร็วที่สุดและไม่ใช้ฟังก์ชัน STL ใด ๆ STL เป็นสิ่งที่ดีเพราะมีโครงสร้างข้อมูลทั่วไปที่รวดเร็ว มันไม่ดีเพราะการใช้มันมักจะน่าเกลียดและเกิดข้อผิดพลาดได้ง่าย ตัวอย่างเช่นลองดูที่คลาส std :: vector <T> สมมติว่าฉันต้องการทำซ้ำในแต่ละองค์ประกอบ:

ที่ทำให้ง่ายขึ้นด้วย C ++ 11:

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

การลบค่าออกจาก std :: vector ก็โง่เช่นกัน:

Gee โปรแกรมเมอร์ทุกคนจะพิมพ์ถูกทุกครั้ง!

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

รหัส C ++ สามารถทำให้เกิดความดื้อด้านและน่าเกลียดได้อย่างรวดเร็วโดยไม่ต้องใช้ความขยันในส่วนของโปรแกรมเมอร์ หากต้องการดูว่ามีสิ่งเลวร้ายเกิดขึ้นได้อย่างไรให้ดูซอร์สโค้ด STL ไมโครซอฟท์และ GCC ของ[ 5 ]การใช้งาน STL อาจจะเป็นรหัสที่มาที่น่าเกลียดที่สุดที่ฉันเคยเห็น แม้ว่าโปรแกรมเมอร์จะใช้ความระมัดระวังอย่างมากในการทำให้โค้ดเทมเพลตของพวกเขาสามารถอ่านได้มากที่สุด แต่ก็ยังคงเป็นระเบียบ ลองดูห้องสมุด Loki ของ Andrei Alexandrescuหรือไลบรารีเพิ่มประสิทธิภาพซึ่งเขียนโดยโปรแกรมเมอร์ C ++ ที่ดีที่สุดในโลกและได้รับการดูแลเป็นอย่างดีเพื่อให้สวยงามที่สุดเท่าที่จะเป็นไปได้และพวกมันก็ยังน่าเกลียดและอ่านไม่ออก

id แก้ปัญหานี้ได้โดยการไม่ทำสิ่งต่างๆให้กว้างเกินไป พวกเขามี HashTable <V> และคลาส HashIndex HashTable บังคับให้พิมพ์คีย์เป็น const char * และ HashIndex เป็นคู่ int-> int นี่ถือเป็นการฝึก C ++ ที่ไม่ดี พวกเขา "ควร" มีคลาส HashTable เดียวและเขียนความเชี่ยวชาญเฉพาะบางส่วนสำหรับ KeyType = const char * และเฉพาะ <int, int> รหัสใดถูกต้องสมบูรณ์และทำให้รหัสสวยงามขึ้นมาก

สิ่งนี้สามารถตรวจสอบเพิ่มเติมได้โดยการเปรียบเทียบ 'แนวปฏิบัติ C ++ ที่ดี' สำหรับการสร้างแฮชและ id ทำอย่างไร

หลายคนถือว่าเป็นแนวทางปฏิบัติที่ดีในการสร้างคลาสการคำนวณเฉพาะเป็นพารามิเตอร์สำหรับ HashTable ดังนี้:

สิ่งนี้อาจเป็นความเชี่ยวชาญสำหรับบางประเภท:

จากนั้นคุณสามารถส่ง ComputeHashForType เป็น HashComputer สำหรับ HashTable:

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

นิยามฟังก์ชันคงโหด!

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

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

และ

และคุณใช้ทั้งสองอย่างและทำสิ่งที่ชอบ:

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

Doomทำสิ่งที่“ ผิด” โดยสมบูรณ์ตามตรรกะของ C ++ ทั่วไป: มันเขียนสิ่งที่ไม่ธรรมดาที่สุดเท่าที่จะทำได้โดยใช้ generics เฉพาะเมื่อมันสมเหตุสมผล สิ่งที่ไม่ลงโทษ ‘s HashTable ทำอย่างไรเมื่อมันต้องการที่จะสร้างกัญชาของบางสิ่งบางอย่าง? มันเรียก idStr :: GetHash () เนื่องจากคีย์ประเภทเดียวที่ยอมรับคือ const char * จะเกิดอะไรขึ้นหากต้องการคีย์อื่น ฉันเดาว่าพวกเขาต้องการแม่แบบคีย์และบังคับให้เรียกใช้ key.getHash () และให้คอมไพเลอร์บังคับว่าคีย์ทุกประเภทมีเมธอด int getHash ()

ฉันไม่รู้ว่าทีมเขียนโปรแกรมดั้งเดิมของ id อยู่กับ บริษัท มากแค่ไหน แต่อย่างน้อย John Carmack ก็มาจากพื้นหลัง C เกม id ทั้งหมดก่อนQuake IIIถูกเขียนด้วยภาษา C ฉันพบโปรแกรมเมอร์ C ++ จำนวนมากที่ไม่มีพื้นหลัง C ที่แข็งแกร่งเกิน C ++ ize รหัสของพวกเขา ตัวอย่างเทมเพลตก่อนหน้านี้เป็นเพียงกรณีเดียว อีกสามตัวอย่างที่ฉันพบบ่อยๆ ได้แก่ :

id ถูกตัดสินอย่างมากในทุกกรณีเหล่านี้

บ่อยครั้งอาจมีคนสร้างคลาส:

เป็นการเสียเวลาในการอ่านโค้ดและบรรทัด ใช้เวลาเขียนนานกว่าและอ่านเมื่อเทียบกับ:

จะเกิดอะไรขึ้นถ้าคุณมักจะเพิ่ม var ด้วยจำนวน n?

เทียบกับ

ตัวอย่างแรกอ่านและเขียนได้ง่ายกว่ามาก

id ไม่ใช้ stringstreams สตรีมสตริงอาจเป็นการทำลายล้างที่รุนแรงที่สุดของโอเปอเรเตอร์โอเวอร์โหลดที่ฉันเคยเห็น: <<

ตัวอย่างเช่น:

น่าเกลียดจัง มันมีข้อดีอย่างมาก: คุณสามารถกำหนดวิธีการที่เทียบเท่ากับ toString () ของ Java ต่อคลาสโดยไม่ต้องสัมผัสกับ vtables ของคลาส แต่ไวยากรณ์ไม่เหมาะสมและ id เลือกที่จะไม่ใช้ การเลือกใช้ printf () แทน stringstreams ทำให้อ่านโค้ดได้ง่ายขึ้นและฉันคิดว่ามันเป็นการตัดสินใจที่ถูกต้อง

ดีกว่ามาก!

ไวยากรณ์สำหรับตัวดำเนินการ SomeClass << ก็ไร้สาระเช่นกัน:

[ หมายเหตุด้านข้าง: John Carmack ได้ระบุว่าเครื่องมือวิเคราะห์แบบคงที่พบว่าจุดบกพร่องที่พบบ่อยคือการจับคู่พารามิเตอร์ที่ไม่ถูกต้องใน printf () ฉันสงสัยว่าพวกเขาเปลี่ยนเป็น stringstreams ในRageเพราะเหตุนี้ GCC และ clang พบข้อผิดพลาดในการจับคู่พารามิเตอร์ printf () กับ -Wall ดังนั้นคุณจึงไม่จำเป็นต้องใช้เครื่องมือวิเคราะห์แบบคงที่ราคาแพงเพื่อค้นหาข้อผิดพลาดเหล่านี้]

อีกสิ่งหนึ่งที่ทำให้โค้ดDoomสวยงามคือการใช้ตัวดำเนินการน้อยเกินไป Operator overloading เป็นคุณสมบัติที่ดีมากของ C ++ ช่วยให้คุณทำสิ่งต่างๆเช่น:

หากไม่มีการดำเนินการเหล่านี้มากเกินไปจะทำให้เสียเวลาในการเขียนและแยกวิเคราะห์มากขึ้น Doomหยุดที่นี่ ฉันเคยเห็นรหัสที่ไม่มี ฉันเคยเห็นโค้ดที่จะโอเวอร์โหลดตัวดำเนินการ '%' เพื่อหมายถึงผลิตภัณฑ์จุดหรือตัวดำเนินการ Vector * Vector เพื่อทำการคูณเวกเตอร์ที่ชาญฉลาด มันไม่สมเหตุสมผลที่จะทำให้ตัวดำเนินการ * สำหรับผลิตภัณฑ์ข้ามชิ้นเพราะมีอยู่ใน 3 มิติเท่านั้นถ้าคุณต้องการทำ:
some_2d_vec * some_2d_vec ควรทำอย่างไร แล้ว 4d หรือสูงกว่าล่ะ? การโอเวอร์โหลดของตัวดำเนินการขั้นต่ำของ id ทำให้ผู้อ่านโค้ดไม่คลุมเครือ

สิ่งที่ยิ่งใหญ่ที่สุดอย่างหนึ่งที่ฉันได้เรียนรู้จากรหัสDoomคือการเปลี่ยนแปลงรูปแบบง่ายๆ ฉันเคยมีชั้นเรียนที่ดูเหมือน:

ตามมาตรฐานการเข้ารหัสDoom 3ของ id พวกเขาใช้แท็บจริงที่มีช่องว่าง 4 ช่อง การมีการตั้งค่าแท็บที่สอดคล้องกันสำหรับโปรแกรมเมอร์ทุกคนทำให้พวกเขาสามารถจัดแนวคำจำกัดความของคลาสได้ในแนวนอน:

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

ฉันต่อต้านการพิมพ์พิเศษทั้งหมด ฉันต้องทำสิ่งต่างๆให้เสร็จเร็วที่สุด แต่นี่เป็นสถานการณ์หนึ่งที่ฉันคิดว่าการพิมพ์เพิ่มขึ้นเล็กน้อยเมื่อกำหนดคลาสมากกว่าที่จะจ่ายเองทุกครั้งที่โปรแกรมเมอร์ต้องแยกวิเคราะห์คำจำกัดความของคลาส มีตัวอย่างโวหารอื่น ๆ อีกมากมายที่ระบุไว้ในDoom 3 Coding Standards ( .doc ) ที่นำไปสู่ความสวยงามของซอร์สโค้ดของDoom

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

ตัวอย่างเช่น:

ดีกว่ามาก:

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

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

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

Shawn McGrath เป็นผู้พัฒนาเกมในโตรอนโตและเป็นผู้สร้างเกมแข่งรถปริศนาประสาทหลอน PlayStation 3 ที่ได้รับรางวัล Dyad ค้นหาข้อมูลเพิ่มเติมเกี่ยวกับเกมของเขา ปฏิบัติตามเขาบนทวิตเตอร์

เชิงอรรถ

[ 1 ]เครื่องมือวิเคราะห์คำศัพท์จะแปลงอักขระของซอร์สโค้ด (ในบริบทที่เกี่ยวข้อง) เป็นชุดของโทเค็นที่มีนัยสำคัญทางความหมาย ซอร์สโค้ดอาจมีลักษณะดังนี้:

x = y + 5;

วิเคราะห์คำศัพท์ (หรือ“lexer” สำหรับระยะสั้น) อาจ tokenize แหล่งที่มาเป็นเช่น:
x => ตัวแปร
= => การกำหนดผู้ประกอบ
การ y => ตัวแปร
+ => ผู้ประกอบการเพิ่มเติม
5 => จำนวนเต็มอักษร
; => คำสั่งสิ้นสุด

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

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

ในตัวอย่าง: x = y + 5 ต้นไม้แยกวิเคราะห์จะมีลักษณะดังนี้:

[ 3 ] “ const” คือคีย์เวิร์ด C ++ ที่ทำให้แน่ใจว่าตัวแปรไม่สามารถเปลี่ยนแปลงได้หรือวิธีการจะไม่เปลี่ยนแปลงเนื้อหาของคลาส "const" เป็นตัวย่อของ "ค่าคงที่" เป็นที่น่าสังเกตว่า C ++ มีวิธีแก้ปัญหาไม่ว่าจะผ่าน const_cast [T] หรือ C-style cast: (T *) การใช้การแบ่ง const อย่างสมบูรณ์เหล่านี้และเพื่อประโยชน์ในการโต้แย้งฉันชอบที่จะเพิกเฉยต่อการมีอยู่ของพวกมันและไม่เคยใช้มันในทางปฏิบัติ

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

[ 5 ] GCC - GNU Compiler Collection: ชุดของคอมไพเลอร์ที่รองรับภาษาการเขียนโปรแกรมหลายภาษา สำหรับกรณีของบทความนี้อ้างถึงคอมไพเลอร์ GNU C / C ++ GCC เป็นคอมไพเลอร์ฟรีที่มีซอร์สโค้ดเต็มรูปแบบให้ใช้งานได้ฟรีและทำงานบนคอมพิวเตอร์และระบบปฏิบัติการที่หลากหลาย คอมไพเลอร์อื่น ๆ ที่ใช้กันทั่วไป ได้แก่ : clang, Microsoft Visual C ++, IBM XL C / C ++, Intel C ++ Compiler

Suggested posts

เวอร์ชันอะนิเมะอย่างเป็นทางการของตัวละคร Harry Potter

เวอร์ชันอะนิเมะอย่างเป็นทางการของตัวละคร Harry Potter

สิ่งเหล่านี้ไม่ใช่แฟนอาร์ต คุณกำลังดูภาพวาดสไตล์อะนิเมะอย่างเป็นทางการของตัวละครสำคัญ ๆ ของ Harry Potter

ทุกอย่างแสดงให้เห็นในวันนี้ที่สถานะการเล่นของ Sony ในเดือนตุลาคมสำหรับ PS4, PS5

ทุกอย่างแสดงให้เห็นในวันนี้ที่สถานะการเล่นของ Sony ในเดือนตุลาคมสำหรับ PS4, PS5

วันนี้ค่อนข้างสั้น—แม้จะเป็นไปตามมาตรฐานการถ่ายทอดสด—กิจกรรม State of Play ไม่ใช่การอัปเดตที่สำคัญที่สุดเท่าที่เราเคยเห็นมา แต่นี่คือบทสรุปของทุกสิ่งที่แสดงขึ้น Deathverse: Let It Die ประกาศเปิดตัวนักสู้ในสังเวียนที่กำลังจะมีขึ้นในวันนี้ และดูจะไม่เป็นไร! ตัวอย่างแสดงให้เห็นการต่อสู้ระยะประชิดและบุคลิกภาพบางส่วน

Related posts

ผู้ลี้ภัยกลับมาเยือนบ้านเกิดของเธออีกครั้งผ่าน The Oculus Rift

ผู้ลี้ภัยกลับมาเยือนบ้านเกิดของเธออีกครั้งผ่าน The Oculus Rift

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

Pokémon x Sumo Collab ที่เรารอคอย

Pokémon x Sumo Collab ที่เรารอคอย

นักมวยปล้ำซูโม่ป่าปรากฏตัว! ในช่วงหลายปีที่ผ่านมา โปเกมอนได้ทำงานร่วมกันมากมาย เช่น New Era, Oreo, Levi's และ Casio บางคนมีความน่าสนใจมากกว่าคนอื่น

ผู้พิทักษ์จักรวาลของ Marvel: The Kotaku Review

ผู้พิทักษ์จักรวาลของ Marvel: The Kotaku Review

เมื่อ Square Enix เปิดเผย Marvel's Guardians of the Galaxy ที่ E3 เมื่อต้นปีนี้ ฉันไม่ตื่นเต้นเลย ความคิดที่จะเล่นเป็นปีเตอร์ "Star-Lord" Quill ซึ่งเป็นสมาชิกที่น่าสนใจน้อยที่สุดของกลุ่มซุปเปอร์กาแล็กซี่ปกป้องกาแล็กซี่ไม่ได้ทำอะไรให้ฉัน

บน Pornhub ครูคณิตศาสตร์สอนแคลคูลัสของเขา

บน Pornhub ครูคณิตศาสตร์สอนแคลคูลัสของเขา

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

MORE COOL STUFF

'The Bachelorette': Ryan Fox อธิบายว่าทำไมเขาถึงนำสมุดบันทึกที่น่าอับอายมา

'The Bachelorette': Ryan Fox อธิบายว่าทำไมเขาถึงนำสมุดบันทึกที่น่าอับอายมา

Michelle Young ส่ง Ryan Fox กลับบ้านหลังจากอ่านสมุดบันทึกของเขาในคืนที่ 1 ของ 'The Bachelorette' ไรอันอธิบายว่าทำไมเขาถึงเอาโน้ตมา

3 จาก Ree Drummond's Must-Try 'Pioneer Woman' สูตรขนมแอปเปิ้ล

3 จาก Ree Drummond's Must-Try 'Pioneer Woman' สูตรขนมแอปเปิ้ล

อบกับแอปเปิ้ลด้วยสูตรอาหาร 'Pioneer Woman' ของ Ree Drummond ของ Food Network ที่นำแสดงโดยผลไม้ตามฤดูกาล

'ผู้กล้าและคนสวย' แอบดู: Deacon & Sheila Team Up Against Hope — ใครจะได้พันธมิตรใน Steffy

'ผู้กล้าและคนสวย' แอบดู: Deacon &amp; Sheila Team Up Against Hope — ใครจะได้พันธมิตรใน Steffy

'The Bold and the Beautiful' ประจำสัปดาห์นี้เป็นการรวมตัวกันของมหากาพย์ — หนึ่งที่แฟน ๆ ส่วนใหญ่ไม่เคยเห็นมาในความฝันที่ดุร้ายที่สุดของพวกเขา

'This Is Us' Season 6 ใช้บ้านของ Alyson Hannigan เพื่อถ่ายทำฉากสำคัญ

'This Is Us' Season 6 ใช้บ้านของ Alyson Hannigan เพื่อถ่ายทำฉากสำคัญ

เมื่อเร็วๆ นี้ Alyson Hannigan เปิดเผยว่าสถานที่อันโดดเด่นแห่งหนึ่งใน 'This Is Us' ซีซั่น 6 ถ่ายทำที่บ้านของเธอในลอสแองเจลิส

วิธีเปลี่ยนชื่อของคุณบน Facebook

วิธีเปลี่ยนชื่อของคุณบน Facebook

ต้องการเปลี่ยนชื่อของคุณใน Facebook? ทำได้ง่ายๆ เพียงไม่กี่ขั้นตอน

7,000 ก้าวคือ 10,000 ก้าวใหม่

7,000 ก้าวคือ 10,000 ก้าวใหม่

หากคุณไม่บรรลุเป้าหมาย 10,000 ก้าวในแต่ละวัน เรามีข่าวดี สุขภาพของคุณสามารถได้รับประโยชน์มากพอ ๆ กับคุณหากคุณทำตามขั้นตอนน้อยลงเช่นกัน

ทำไมคุณไม่สามารถปั๊มแก๊สของคุณเองในรัฐนิวเจอร์ซีย์?

ทำไมคุณไม่สามารถปั๊มแก๊สของคุณเองในรัฐนิวเจอร์ซีย์?

Garden State เป็นรัฐเดียวในสหรัฐอเมริกาที่สูบก๊าซของคุณเองผิดกฎหมาย สิ่งที่ช่วยให้?

โอกาสในการตีกวางเพิ่มขึ้นในฤดูใบไม้ร่วง

โอกาสในการตีกวางเพิ่มขึ้นในฤดูใบไม้ร่วง

และอีกอย่าง การขับรถตอนพลบค่ำและช่วงพระจันทร์เต็มดวงไม่ได้ช่วยอะไรคุณเลย

Maggie Gyllenhaal ร่วมงานกับ Dakota Johnson ในลอนดอน รวมทั้ง Vanessa Hudgens, Christian Siriano และอีกมากมาย

Maggie Gyllenhaal ร่วมงานกับ Dakota Johnson ในลอนดอน รวมทั้ง Vanessa Hudgens, Christian Siriano และอีกมากมาย

Maggie Gyllenhaal และ Dakota Johnson ถ่ายภาพรอบปฐมทัศน์ในลอนดอน Vanessa Hudgens ออกจากโรงยิมใน LA, Christian Siriano อยู่ในนิวยอร์คเพื่อเฉลิมฉลอง 'Project Runway' ซีซั่นที่ 19 และอีกมากมาย จากฮอลลีวูดถึงนิวยอร์กและทุกที่ในระหว่างนั้น ดูว่าดาราคนโปรดของคุณมีอะไรบ้าง

ไร้ยางอาย ' Emma Kenney อ้างว่ากลายเป็น 'สถานที่ที่เป็นบวกมากขึ้น' หลังจากออกจาก Emmy Rossum

ไร้ยางอาย ' Emma Kenney อ้างว่ากลายเป็น 'สถานที่ที่เป็นบวกมากขึ้น' หลังจากออกจาก Emmy Rossum

สารส้มไร้ยางอาย Emma Kenney พูดถึงประสบการณ์การทำงานกับ Emmy Rossum ในซีรีส์ Showtime

Hamilton Star Javier Muñozเมื่อถูกภูมิคุ้มกันบกพร่องในโรคระบาด: 'ฉันอยู่ในความหวาดกลัวอย่างแท้จริง'

Hamilton Star Javier Muñozเมื่อถูกภูมิคุ้มกันบกพร่องในโรคระบาด: 'ฉันอยู่ในความหวาดกลัวอย่างแท้จริง'

'ไม่มีโอกาสที่จะถูกจับ' Javier Muñoz ซึ่งเป็นผู้ติดเชื้อเอชไอวีและผู้รอดชีวิตจากมะเร็งกล่าวกับผู้คน

Rachael Ray กล่าวว่าเธอรู้สึกขอบคุณสำหรับการ 'มีชีวิตอยู่' หลังจากไฟไหม้บ้านและน้ำท่วมอพาร์ตเมนต์

Rachael Ray กล่าวว่าเธอรู้สึกขอบคุณสำหรับการ 'มีชีวิตอยู่' หลังจากไฟไหม้บ้านและน้ำท่วมอพาร์ตเมนต์

'หลายคนเขียนถึงฉันและเอื้อมมือออกไปและบอกว่าเราสูญเสียมากเกินไป' Rachael Ray กล่าวในรายการ Extra

เรื่องราวของอุปกรณ์ไมโครขั้นสูง

เซมิคอนดักเตอร์สำหรับโลก

เรื่องราวของอุปกรณ์ไมโครขั้นสูง

จากการสำรวจสิ่งที่ทำให้ปัญญาประดิษฐ์เป็นไปได้ - ฮาร์ดแวร์ที่เป็นรากฐานของซอฟต์แวร์ฉันคิดว่ามันน่าสนใจที่จะอ่านเพิ่มเติมเกี่ยวกับ บริษัท เซมิคอนดักเตอร์ที่ใหญ่ที่สุดแห่งหนึ่งของโลก ดังนั้นฉันจึงสนใจเรื่องราวของ Advanced Micro Devices (AMD)

เหตุการณ์ที่เซิร์ฟเวอร์ส่ง

เหตุการณ์ที่เซิร์ฟเวอร์ส่ง

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

9 แนวทางการเข้ารหัสที่คุณควรปฏิบัติตามใน JavaScript | JavaScript ที่ดีกว่า

9 แนวทางการเข้ารหัสที่คุณควรปฏิบัติตามใน JavaScript | JavaScript ที่ดีกว่า

มีเครื่องมือและปลั๊กอินมากมายในโลกของ JavaScript คุณอาจเคยได้ยินแนวทางการเข้ารหัสหรือกฎการทับซ้อนกันหลายข้อมาก่อน

ได้เวลา

ได้เวลา

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

Language