โปรแกรมเมอร์ที่ดีใส่ใจโครงสร้างข้อมูลและความสัมพันธ์ของมัน
(read.engineerscodex.com)- จากคำกล่าวของ Linus Torvalds การออกแบบที่ดีเริ่มจากการกำหนด โครงสร้างข้อมูลและความสัมพันธ์ ให้มั่นคงก่อนการเขียนโค้ด
- โมเดลข้อมูลที่ออกแบบดีจะทำให้ลอจิกของแอปพลิเคชันเรียบง่ายขึ้นอย่างเป็นธรรมชาติ และทำให้ซอฟต์แวร์ เชื่อถือได้และเข้าใจง่ายขึ้น
- หากเลื่อนการคิดเรื่องโมเดลข้อมูลไปไว้ทีหลัง ปริมาณงานในภายหลังจะเพิ่มขึ้น แต่ถ้าจัดโครงสร้างให้ดีตั้งแต่ต้น migration และการขยายระบบที่ซับซ้อนจะง่ายขึ้น
- ในโปรเจกต์หนึ่ง แทนที่จะปรับแต่งอัลกอริทึมที่ซับซ้อน ได้ใช้ การจัดโครงสร้างข้อมูลใหม่ เพื่อตัดหมวดปัญหาทั้งหมดออกไป และแทนที่ฟังก์ชัน 500 บรรทัดด้วยฟังก์ชัน 50 บรรทัดพร้อมโครงสร้างข้อมูล
- ในงานจริง ควรใช้ type ที่เข้มงวดขึ้นกับอินเทอร์เฟซและฐานข้อมูล และควรออกแบบการไหลของข้อมูลกับการโต้ตอบระหว่างคอมโพเนนต์ก่อนรายละเอียดของโค้ด
โครงสร้างข้อมูลเป็นตัวกำหนดการออกแบบโค้ด
- Linus Torvalds มองว่า Git เป็นการออกแบบที่เรียบง่ายซึ่งมี โครงสร้างข้อมูลที่มั่นคงและมีเอกสารกำกับ และเน้นวิธีวางโค้ดไว้รอบ ๆ ข้อมูล
- ประโยคสำคัญคือคำกล่าวที่ว่า “โปรแกรมเมอร์ที่แย่กังวลเรื่องโค้ด ส่วนโปรแกรมเมอร์ที่ดีกังวลเรื่องโครงสร้างข้อมูลและความสัมพันธ์ของมัน”
- หนึ่งในเหตุผลที่ Git ประสบความสำเร็จคือการออกแบบโค้ดโดยมีข้อมูลเป็นศูนย์กลาง
- โครงสร้างข้อมูลที่ดีทำให้การออกแบบและบำรุงรักษาโค้ดง่ายขึ้น และเพิ่ม ความน่าเชื่อถือ ของซอฟต์แวร์ ความเข้าใจระบบ และความอ่านง่ายของโค้ด
- ลอจิกของแอปพลิเคชันมักจะเดินตามโมเดลข้อมูล
- หากคิดเรื่องโมเดลข้อมูลทีหลัง ปริมาณงานในภายหลังจะเพิ่มขึ้น
- โมเดลข้อมูลที่ออกแบบดีทำให้ migration และการขยายระบบที่ซับซ้อนในภายหลังง่ายขึ้น
- ในกรณีโปรเจกต์จริง การจัดโครงสร้างข้อมูลใหม่ ให้ผลมากกว่าการขัดเกลาอัลกอริทึมที่ซับซ้อนเพิ่มเติม
- เปลี่ยนโครงสร้างข้อมูลเพื่อตัดหมวดปัญหาทั้งหมดออกไป
- ฟังก์ชัน 500 บรรทัดถูกแทนที่ด้วยฟังก์ชัน 50 บรรทัดและโครงสร้างข้อมูลที่ออกแบบดี
- โค้ดใหม่เร็วขึ้น และเข้าใจรวมถึงบำรุงรักษาได้ง่ายขึ้น
- อย่างไรก็ตาม เนื่องจากต้องจัดโครงสร้างข้อมูลเดิมใหม่ ภาระงานจึงย้ายลงไปยังชั้นล่าง
ย้ายความซับซ้อนไปไว้ฝั่งข้อมูลจะดีกว่า
- “Rule of Representation” ใน The Art of Unix Programming อธิบายว่าให้บรรจุความรู้ไว้ในข้อมูล เพื่อทำให้ลอจิกของโปรแกรม เรียบง่ายและแข็งแรง
- ลอจิกเชิงขั้นตอนตรวจสอบโดยมนุษย์ได้ยาก แต่โครงสร้างข้อมูลที่ซับซ้อนสร้างโมเดลและให้เหตุผลได้ง่ายกว่า
- ไดอะแกรม pointer tree ที่มี 50 โหนดอาจมีพลังในการสื่อและอธิบายสูงกว่า flowchart ของโปรแกรม 50 บรรทัด
- การแสดงตารางแปลงเป็นการกำหนดค่าเริ่มต้นของอาร์เรย์ ทำให้เนื้อหาเดียวกันโปร่งใสและชัดเจนกว่าการเขียนเป็นคำสั่ง
switch - หากต้องเลือกระหว่างการวางความซับซ้อนไว้ในโค้ดหรือโครงสร้างข้อมูล การย้ายความซับซ้อนไปไว้ที่ โครงสร้างข้อมูล จะดีกว่า
ในงานจริงให้ออกแบบการไหลของข้อมูลก่อน
- วิธีปฏิบัติที่ตรงที่สุดคือ เริ่มจากข้อมูล
- การใช้ type ที่เข้มงวดขึ้นกับอินเทอร์เฟซหรือฐานข้อมูลช่วยลดความซับซ้อนของโค้ดได้
- จำเป็นต้องใช้เวลาคิดเรื่องโครงสร้างข้อมูลล่วงหน้านานขึ้น
- ไม่ได้หมายความว่าโค้ดไม่สำคัญ ทุกองค์ประกอบล้วนสำคัญร่วมกัน
- ก่อนลงรายละเอียดของโค้ด แนวทางระดับสูงที่มองก่อนว่าข้อมูลไหลอย่างไรและคอมโพเนนต์โต้ตอบกันอย่างไรนั้นมีประโยชน์
- ในตัวอย่างข้อกำหนดของ Senior Engineer(L5) ที่ FAANG โดยทั่วไปจะรวมถึงการเขียน เอกสารออกแบบระดับสูง สำหรับระบบที่ซับซ้อนกว่า
- การนำแผนของทีมและการสร้างโรดแมปที่ดีสำหรับฟีเจอร์ขนาดกลางถึงใหญ่ก็รวมอยู่ในนี้ด้วย
- ความสามารถในการออกแบบการไหลของข้อมูลและการโต้ตอบระหว่างคอมโพเนนต์ก่อน เชื่อมโยงกับอิทธิพลทางวิศวกรรมในระดับที่สูงขึ้น
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
บทความ Substack นั้นดูเหมือนคัดลอกคำอ้างหลายส่วนมาจากโพสต์ Stack Exchange นี้ตรง ๆ: https://softwareengineering.stackexchange.com/questions/1631...
“ถ้าคุณให้ผมดู flowchart[โค้ด] แล้วซ่อนตาราง[สคีมา] ไว้ ผมก็จะยังงงต่อไป แต่ถ้าคุณให้ผมดูตาราง[สคีมา] โดยปกติผมก็ไม่จำเป็นต้องดู flowchart[โค้ด] เพราะมันจะชัดเจนอยู่แล้ว” — Fred Brooks, “The Mythical Man Month”, บทที่ 9
“จงอ่าน The Mythical Man Month ผมจำได้ว่าเคยคิดว่าหนังสือที่เก่าแบบนั้นคงไม่มีอะไรที่เกี่ยวข้องกับการพัฒนาซอฟต์แวร์ในปัจจุบัน แต่ผมคิดผิด”
จากนั้นนักพัฒนาแอปพลิเคชันก็จะเริ่มใช้ฐานข้อมูลในทางที่ผิด เพราะทำได้เร็วกว่าและมีงานต้องทำเยอะกว่า
โครงสร้างข้อมูล กับ ไทป์ ไม่ใช่สิ่งเดียวกัน โครงสร้างข้อมูลคือรูปแบบของบิตและการอ้างอิงไปยังรูปแบบบิตอื่น ๆ นั่นคือพอยน์เตอร์หรือความสัมพันธ์
ไทป์ในความหมายที่ใช้ในภาษาโปรแกรมจะวางข้อจำกัดให้กับรูปแบบบิตเหล่านั้น แต่ยังสามารถแสดงฟีเจอร์ของภาษาอื่น ๆ ได้อีกมาก การสร้างลำดับชั้นของไทป์ที่ซับซ้อนด้วย abstraction ที่ไม่จำเป็น ไม่ได้แปลว่า “ใส่ใจโครงสร้างข้อมูล” และเป็นรูปแบบความล้มเหลวที่แม้แต่วิศวกรเก่ง ๆ ก็พลาดบ่อย
ไทป์คือบิตบนดิสก์
สิ่งที่ผู้เขียนตั้งใจจะสื่อจริง ๆ ตรงนี้น่าจะใกล้เคียงกับการคิดปัญหาให้ลึกขึ้น และอย่าเลือกโครงสร้างที่จะกลับมาขัดขาในภายหลัง เช่น ลองดูว่า Unix pipe แพร่กระจายไปไกลแค่ไหน และขยายไปสู่โดเมนกับ use case มากมายเพียงใด มันเป็นวิธีที่ยอดเยี่ยมในการมองภาพว่าจะสร้างระบบอย่างไรโดยเคารพข้อจำกัดของทั้งมนุษย์และเครื่องจักร
Ken Thompson และคนอื่น ๆ ก็ใช้เวลาพอสมควรกว่าจะตระหนักว่าสิ่งอย่าง pipe นั้นสมเหตุสมผลใน Unix นี่ไม่ใช่ insight ที่ได้มาง่าย ๆ แต่ต้องอาศัยความพากเพียรในการค้นหา building block ที่ถูกต้องของระบบ และงานต่อเนื่องหลังจากนั้น
Linus มักสรุปสิ่งที่คนอื่นคิดกันแบบคลุมเครือได้ดีเสมอ สิ่งที่บทความพูดถึงก็คล้ายกับ DDD ซึ่งกลายเป็นทักษะที่สูญหายไปแล้ว
ที่ว่า “สูญหาย” หมายถึงนักพัฒนาส่วนใหญ่ที่เจอในช่วงนี้ สนใจการย้ายอัลกอริทึมกับ JSON ไปมามากกว่าการทำความเข้าใจโดเมนที่ตนจัดการ และการจำลองเอนทิตีกับปฏิสัมพันธ์ของมัน ในการออกแบบสมัยใหม่บน AWS มันปรากฏออกมาเป็นชุด DynamoDB GSI ที่มีเหตุผลรองรับอ่อน ๆ, อ็อบเจ็กต์ที่ผอมแห้ง และเลเยอร์ “service” ที่เหมือนสคริปต์ซึ่งเอาแฮ็กมาซ้อนทับแฮ็ก บางทีอาจเคยมีสมมติฐานโดยนัยว่า ภายในขอบเขตของบริการ บริบทของโดเมนจะถูกนิยามได้ดีเพียงพอ แต่ผมไม่คิดว่าเป็นสมมติฐานที่ดี
ไม่รู้เหมือนกันว่าอุตสาหกรรมของเราเสียความเข้มงวดในการออกแบบไปตรงไหน ที่โรงเรียนหรือเปล่า, ใน pipeline การสัมภาษณ์หรือเปล่า, เพราะลดมาตรฐานลงหรือเปล่า, หรือทั้งหมดรวมกัน
ที่แย่กว่านั้นคือ การออกแบบทำบาปใหญ่ตรงที่มันทำให้อัตโนมัติได้ไม่ง่าย ผู้คนจึงทำตามการออกแบบที่เครื่องมือบังคับมาอย่างไม่วิจารณ์ และรู้สึกอึดอัดกับแนวคิดที่ว่าต้องคิดให้ลึกขึ้นเกี่ยวกับสิ่งที่ตนทำ ทุกคนอยาก outsource การคิดนี้ให้ “ผู้เชี่ยวชาญ”
อีกปัญหาคือมันไม่ได้ถูกสอนอย่างถูกต้อง ต้องเรียนรู้ด้วยตัวเองเป็นเวลาหลายปี และถูกมองว่าไม่จริงเท่าโค้ด จึงถูกถือว่าสำคัญน้อยกว่า แต่ความเชื่อแบบนี้สุดท้ายจะล็อกระดับของสิ่งที่เราสร้างได้ไว้แค่ขั้นมือใหม่ระดับสูง โปรแกรมเมอร์เลือกกันแบบรวมหมู่ที่จะคงมาตรฐานให้ต่ำที่สุดเท่าที่เป็นไปได้ และในหัวข้อนี้ดูเหมือนแทบจะมีสภาพจิตวิทยาแบบปูในถังที่คอยดึงกันลงมา
ยังเกิดโค้ดซ้ำจำนวนมากที่ดูไม่เหมือนการซ้ำในเชิง syntax แต่ในเชิงฟังก์ชันแล้วทำงานเดียวกัน
1 https://martinfowler.com/bliki/AnemicDomainModel.html
2 https://wiki.c2.com/?PrimitiveObsession
ผมคิดว่าเพราะผลลัพธ์ของโค้ดแย่ ๆ มองเห็นได้น้อยกว่า สะพานแย่ ๆ จะถล่ม แต่โค้ดแย่ ๆ ก็แค่ถูก refactor หรือถูกแทนที่ด้วยโค้ดที่มากขึ้นเท่านั้น มันก็แค่ไฟล์ข้อความหนึ่งที่ผู้บริหารไม่เข้าใจ ถูกเปลี่ยนเป็นไฟล์ข้อความอีกไฟล์หนึ่งที่ผู้บริหารก็ไม่เข้าใจเหมือนกัน
และเมื่ออะไรสักอย่างทำงานได้แล้ว ทุกอย่างก็เงียบกริบ ไม่มีอะไรถาวรไปกว่าแฮ็กชั่วคราวที่ดันทำงานได้สมบูรณ์แบบ แต่แฮ็กชั่วคราว 1000 อันไม่ได้สร้างระบบที่ถูกวิศวกรรมมาอย่างดี การเติบโตเป็นผู้ใหญ่ในการพัฒนาซอฟต์แวร์คือการโฟกัสที่ข้อมูลและความสัมพันธ์มากกว่าการเขียนโค้ด เราต้องสามารถแปลงสิ่งนั้นเป็นโค้ดได้ แต่ไม่ใช่แปลงโค้ดที่ทำงานได้ให้เป็น data model หากต้องแปลงข้อมูลและความสัมพันธ์ให้เป็นโค้ด
การที่ domain entity สามารถบรรจุ logic ทั้งหมดได้ ไม่ได้แปลว่ามันต้องทำแบบนั้นเสมอไป ตัวอย่างเช่น ถ้าต้องตรวจสอบว่าชื่อผู้ใช้มีอยู่แล้วหรือไม่ จะให้ทำสิ่งนั้นใน domain entity ที่ “พึ่งพา” data access layer ไม่ได้อย่างไร? มักมีคนแนะนำสิ่งอย่าง “domain service” แต่แบบนั้น business logic ก็จะกระจัดกระจายไปหลายที่ ซึ่งรู้สึกขัดกับเจตนาของ DDD
ผมค่อนข้างชอบ DDD ในฐานะปรัชญา แต่เกลียด pattern แบบ “tactical DDD” อย่างยิ่ง ผมคิดว่ามีคนจำนวนมากเกินไปที่มอง Domain-Driven Design เท่ากับ Domain-Driven Implementation ผมพยายามสร้างโดเมนที่ rich ในที่ที่เหมาะสม แต่มันไม่เหมาะกับทุกโปรเจกต์ และพยายามไม่จมกับศัพท์เฉพาะ ผมไม่สนใจว่า type “Name” เป็น value object หรือ aggregate root สิ่งที่สำคัญกว่ามากคือ bounded context และผมก็ยอมรับด้วยว่า บางครั้ง DDD อาจเพิ่มความซับซ้อนให้แอปพลิเคชันโดยแทบไม่ได้อะไรกลับมา ผมจะไม่พูดเด็ดขาดว่ามันเป็นยาครอบจักรวาล
ต่อไปผมก็ยังจะใช้ DDD แต่สลัดความรู้สึกไม่ได้ว่า DDD เหมือนความพยายามจะสื่อว่า “เห็นไหม การเขียนโปรแกรมเชิงวัตถุก็ไม่ได้แย่ขนาดนั้นนี่?” และผมก็ไม่แน่ใจด้วยว่ามันบรรลุเป้าหมายนั้นหรือไม่
ผมรู้สึกว่าน่าสนใจ เพราะก่อนเริ่มทำงานวิศวกรรมแบบมืออาชีพ ผมใช้ระบบสถิติอย่าง Matlab, R และ Python ยุคแรก ๆ ทำงานกับข้อมูลและการวิเคราะห์สถิติทุกวัน
ดังนั้นมุมมองด้านวิศวกรรมของผมจึงตั้งอยู่บนสองอย่างเสมอ คือการจัดการสถานะเชิงฟังก์ชันและ data workflow
หลังทำงานเป็นวิศวกรซอฟต์แวร์มา 10 ปี ผมพบว่าวิศวกร “สายวิทยาศาสตร์” ส่วนใหญ่ เช่น Minsky หรือ Shannon อธิบายโลกคอมพิวติงด้วยการจัดการสถานะ, การแปลงข้อมูล และการจัดการ overhead ของการคำนวณ บุคคลสำคัญและผู้บุกเบิกของซอฟต์แวร์ล้วนให้ความสำคัญกับข้อมูลและสถานะอย่างมาก คอมพิวติงยุคแรกก็แทบจะมีแค่นั้น และคาดกันว่ารูปแบบเช่นนั้นจะดำเนินต่อไป
ในทางกลับกัน สมมติฐานพื้นฐานที่ควรเป็นจริงเสมอในการออกแบบระบบวิศวกรรมและทุกคนควรทำตาม กลับไม่มีความสอดคล้องกันเลย และถึงมีก็มักใกล้เคียงกับกระแสมากกว่า ในซอฟต์แวร์ปฏิบัติการส่วนใหญ่ กำหนดการทางธุรกิจ เป็นตัวกำหนดลำดับความสำคัญและโครงสร้างทางวิศวกรรมมากกว่าความแข็งแกร่ง, antifragility และการจัดการสถานะอย่างมาก
องค์กรวิชาชีพอย่าง guild หรือสหภาพแรงงานถูกวิศวกรซอฟต์แวร์ปฏิเสธแทบจะเป็นสากล เพราะไม่มีผลเสียใด ๆ จากการไม่ถือ IEEE เป็นเรื่องจริงจัง ในทางปฏิบัติจึงแทบไม่มีใครถือมันเป็นเรื่องจริงจัง ผลลัพธ์คือไม่มีเครื่องมือบังคับใช้หรือกำกับตนเองในทางปฏิบัติเหมือนวิศวกรรมโยธาหรือชีวการแพทย์ และแม้แต่ฝั่งนั้นเองก็ยังถูกใช้งานได้เพียงระดับเฉียด ๆ เท่านั้น
โดยรวมแล้ว สถานะปัจจุบันของการพัฒนาซอฟต์แวร์ถูกตัดขาดจากรากเหง้าที่เคยสูงส่งและเป็นปรัชญาอย่างสิ้นเชิง และถูกนำโดยบริษัทที่ให้ความสำคัญกับระบบที่ทำเงินให้คนมีเงินเป็นหลัก ดังนั้น “สิ่งที่ดี” จึงแทบไม่เกี่ยวข้องกับสิ่งที่มีแรงจูงใจให้ทำเลย
“ถ้าให้ผมดูผังงาน[โค้ด]แต่ซ่อนตาราง[โครงสร้างข้อมูล]ไว้ ผมก็จะยังงงอยู่อย่างนั้น แต่ถ้าให้ผมดูตาราง โดยปกติแล้วผมไม่จำเป็นต้องดูผังงาน เพราะมันจะชัดเจนอยู่แล้ว” — Fred Brooks
การจับคู่กับตารางพื้นฐานแบบ 1:1 นั้นเป็นข้อจำกัดอย่างมาก และผมคิดว่ามันนำไปสู่โมเดลที่พลาดพลังในการแสดงออกที่ภาษา现代มอบให้
นี่โดยพื้นฐานแล้วคือมุมมองของ functional programming และทฤษฎีหมวดหมู่
มีอ็อบเจกต์ข้อมูลบางอย่างอยู่ และมันให้ข้อจำกัดว่าโครงสร้างนั้นสามารถถูกแปลงได้อย่างไร จากนั้นลอจิกของโปรแกรมทั้งหมดก็กลายเป็นเรื่องของการแปลงที่รักษาโครงสร้างนั้นไว้
การแปลงจะเรียบง่ายขึ้นและให้เหตุผลได้ง่ายขึ้น สุดท้ายก็เหลือกราฟที่การแปลงเป็นเส้นเชื่อม และโครงสร้างเป็นโหนด โดยทั่วไปให้เหตุผลได้ง่ายกว่าโปรแกรมเชิงคำสั่งตามอำเภอใจ
มุมมองของ functional programming นั้นใกล้เคียงกับแนวคิดว่าไม่ควรแปลงอ็อบเจกต์และควรหลีกเลี่ยงการเปลี่ยนแปลง ซึ่งเป็นคนละเรื่องกับการอภิปรายนี้ แก่นของทฤษฎีหมวดหมู่คือการจัดการรูปแบบความสัมพันธ์ที่ปรากฏร่วมกันในหลายสาขาของคณิตศาสตร์ และไม่เกี่ยวข้องกับสิ่งที่กำลังพูดถึงตรงนี้เลย บางทีอาจตั้งใจจะพูดถึงทฤษฎีชนิดข้อมูลก็ได้ แต่นั่นก็ไม่เกี่ยวข้องเช่นกัน
ข้อสรุปที่ผมเคยได้คือแบบนี้ ทุกสิ่งที่เราทำในโค้ดมีแนวโน้มจะอยู่รอดสั้นกว่า การตัดสินใจที่ดีเพียงครั้งเดียว เกี่ยวกับข้อมูลมาก
https://www.swyx.io/data-outlasts-code-but
หลักการนี้ใช้ได้ใน ระดับธุรกิจ ด้วย ผมยังต้องรับมือกับนักวิเคราะห์ธุรกิจที่ยึดติดกับกระบวนการ(โค้ด) แต่ไม่ใช้เวลาเพื่อทำความเข้าใจเอนทิตีและความสัมพันธ์ของมัน(ข้อมูล)ก่อนอยู่เรื่อย ๆ
ผลก็คือเมื่อถึงเวลาต้องสร้างอะไรบางอย่าง พวกเขาสื่อสารกับนักพัฒนาไม่ได้ว่าโมเดลข้อมูลควรมีหน้าตาอย่างไร กระบวนการถูกนำไปใช้งานจริง ส่วนโมเดลข้อมูลไม่ได้ถูกออกแบบอย่างรอบคอบ แต่ถูกปะติดปะต่อแบบเฉพาะหน้าไปเรื่อย ๆ