ข้อผิดพลาดที่น่าอับอายที่สุดในอาชีพการเขียนโปรแกรมของฉัน (จนถึงตอนนี้)

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

อันดับที่สาม - คอมไพเลอร์ Microsoft C

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

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

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

มันเป็นฝันร้าย หลายปีต่อมา ฉันได้รับแจ้งว่าโปรแกรมเมอร์ที่สืบทอดโค้ดของฉันเกลียดฉัน

ข้อผิดพลาดที่น่าอับอายที่สุดในอาชีพการเขียนโปรแกรมของฉัน (จนถึงตอนนี้)

บทเรียน

ดังที่ David Patterson และ John Hennessy เขียนไว้ใน Computer Architecture and Computer Systems Design หนึ่งในหลักการสำคัญของสถาปัตยกรรมและการออกแบบคือการทำให้สิ่งต่าง ๆ ทำงานได้เร็วที่สุดเท่าที่จะเป็นไปได้

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

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

ฉันต้องโทรหานักพัฒนาที่มีประสบการณ์ และร่วมกับเขาเพื่อคิดถึงกรณีทั่วไปและจัดการกับพวกเขาโดยเฉพาะ ฉันจะเขียนโค้ดน้อยลง แต่นั่นเป็นสิ่งที่ดี ดังที่ Jeff Atwood ผู้ก่อตั้ง Stack Overflow เขียนไว้ ศัตรูที่เลวร้ายที่สุดของโปรแกรมเมอร์คือตัวโปรแกรมเมอร์เอง:

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

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

ข้อผิดพลาดที่น่าอับอายที่สุดในอาชีพการเขียนโปรแกรมของฉัน (จนถึงตอนนี้)

อันดับที่สอง: การโฆษณาบนโซเชียลเน็ตเวิร์ก

ตอนที่ฉันทำงานที่ Google เกี่ยวกับการโฆษณาบนโซเชียลมีเดีย (จำ Myspace ได้ไหม) ฉันเขียนข้อความแบบนี้ในภาษา C++:

for (int i = 0; i < user->interests->length(); i++) {
  for (int j = 0; j < user->interests(i)->keywords.length(); j++) {
      keywords->add(user->interests(i)->keywords(i)) {
  }
}

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

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

ข้อผิดพลาดที่น่าอับอายที่สุดในอาชีพการเขียนโปรแกรมของฉัน (จนถึงตอนนี้)

บทเรียน

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

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

นี่คือสิ่งที่ บอก เกี่ยวกับ โทมัส วัตสัน หัวหน้าระดับตำนานของ IBM:

มีการประกาศคำสั่งของรัฐบาลมูลค่าประมาณหนึ่งล้านดอลลาร์ IBM Corporation - หรือเรียกอีกอย่างว่า Thomas Watson Sr. เป็นการส่วนตัว - ต้องการได้รับมันจริงๆ น่าเสียดายที่ตัวแทนฝ่ายขายไม่สามารถดำเนินการได้ และ IBM แพ้การประมูล วันรุ่งขึ้น พนักงานคนนี้เข้าไปในห้องทำงานของมิสเตอร์วัตสันและวางซองจดหมายไว้บนโต๊ะ มิสเตอร์วัตสันไม่สนใจเลย - เขากำลังรอพนักงานคนหนึ่งและรู้ว่านั่นคือจดหมายลาออก

วัตสันถามว่าเกิดอะไรขึ้น

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

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

ฉันมีเสื้อยืดที่เขียนว่า “ถ้าคุณเรียนรู้จากความผิดพลาดจริงๆ ฉันก็ก็เป็นผู้เชี่ยวชาญแล้ว” ที่จริงแล้ว เมื่อพูดถึงข้อผิดพลาด ฉันเป็นแพทย์สาขาวิทยาศาสตร์

อันดับที่ 1: App Inventor API

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

ยิ่งเลวร้ายยิ่งดี

ฉันอ่าน เรียงความโดย Richard Gabriel เกี่ยวกับแนวทางนี้ในยุค 90 ในฐานะนักศึกษาระดับบัณฑิตศึกษา และฉันชอบมันมากจนต้องถามนักศึกษาของฉัน ถ้าจำไม่ดีก็รีเฟรชความจำนะมันเล็ก บทความนี้เปรียบเทียบความปรารถนาที่จะ "ทำให้ถูกต้อง" และแนวทาง "แย่กว่าดีกว่า" ในหลาย ๆ ด้าน รวมถึงความเรียบง่ายด้วย

วิธีที่ควรจะเป็น: การออกแบบควรเรียบง่ายทั้งในการใช้งานและส่วนต่อประสาน ความเรียบง่ายของอินเทอร์เฟซมีความสำคัญมากกว่าความเรียบง่ายในการใช้งาน

ยิ่งแย่ยิ่งดี: การออกแบบควรเรียบง่ายในการใช้งานและอินเทอร์เฟซ ความเรียบง่ายของการนำไปใช้งานมีความสำคัญมากกว่าความเรียบง่ายของอินเทอร์เฟซ

เรามาลืมเรื่องนั้นกันสักครู่ น่าเสียดายที่ฉันลืมเรื่องนี้ไปหลายปีแล้ว

นักประดิษฐ์แอป

ขณะที่ทำงานที่ Google ฉันเป็นส่วนหนึ่งของทีม นักประดิษฐ์แอปซึ่งเป็นสภาพแวดล้อมการพัฒนาออนไลน์แบบลากและวางสำหรับนักพัฒนา Android ที่ต้องการ มันคือปี 2009 และเราต้องรีบเปิดตัวเวอร์ชันอัลฟ่าให้ทันเวลา เพื่อที่ว่าในช่วงฤดูร้อนเราจะได้จัดคลาสมาสเตอร์สำหรับครูที่สามารถใช้สภาพแวดล้อมในการสอนในช่วงฤดูใบไม้ร่วงได้ ฉันอาสาติดตั้งสไปรท์ เพราะฉันเคยเขียนเกมบน TI-99/4 มาก่อน สำหรับผู้ที่ไม่ทราบ สไปรท์เป็นวัตถุกราฟิกสองมิติที่สามารถเคลื่อนที่และโต้ตอบกับองค์ประกอบซอฟต์แวร์อื่นๆ ได้ ตัวอย่างของสไปรท์ ได้แก่ ยานอวกาศ ดาวเคราะห์น้อย ลูกหิน และแร็กเก็ต

เราใช้ App Inventor เชิงวัตถุใน Java ดังนั้นจึงมีเพียงวัตถุจำนวนมากในนั้น เนื่องจากลูกบอลและสไปรท์มีพฤติกรรมคล้ายกันมาก ฉันจึงสร้างคลาสสไปรท์แบบนามธรรมที่มีคุณสมบัติ (ฟิลด์) X, Y, ความเร็ว (ความเร็ว) และส่วนหัว (ทิศทาง) พวกเขามีวิธีการเดียวกันในการตรวจจับการชน การกระเด้งออกจากขอบหน้าจอ ฯลฯ

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

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

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

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

ในที่สุดฉันก็ได้แก้ไขข้อบกพร่องนี้เมื่อไม่นานมานี้ สิบปีต่อมา “แพตช์” ไม่ใช่ “แก้ไขแล้ว” เพราะอย่างที่ Joshua Bloch กล่าวไว้ว่า API นั้นคงอยู่ชั่วนิรันดร์ ไม่สามารถทำการเปลี่ยนแปลงที่จะส่งผลต่อโปรแกรมที่มีอยู่ได้ เราได้เพิ่มคุณสมบัติ OriginAtCenter ด้วยค่า false ในโปรแกรมเก่าและจริงในโปรแกรมในอนาคตทั้งหมด ผู้ใช้อาจถามคำถามเชิงตรรกะ: ใครคิดที่จะวางจุดเริ่มต้นไว้ที่อื่นที่ไม่ใช่ศูนย์กลางด้วยซ้ำ ถึงผู้ซึ่ง? ถึงโปรแกรมเมอร์คนหนึ่งที่ขี้เกียจเกินไปที่จะสร้าง API ปกติเมื่อสิบปีก่อน

บทเรียนที่ได้รับ

เมื่อทำงานกับ API (ซึ่งโปรแกรมเมอร์เกือบทุกคนต้องทำในบางครั้ง) คุณควรปฏิบัติตามคำแนะนำที่ดีที่สุดที่ระบุไว้ในวิดีโอของ Joshua Bloch "วิธีสร้าง API ที่ดีและเหตุใดจึงสำคัญ" หรือ ในรายการสั้นๆ นี้:

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

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

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

ข้อสรุป

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

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

ที่มา: will.com

เพิ่มความคิดเห็น