มุมมองที่เข้าใจง่ายเกี่ยวกับแนวคิดของ Rich Hickey เกี่ยวกับ Clojure: ความเรียบง่าย ความไม่เปลี่ยนแปลง และค่าเริ่มต้นที่ดีกว่า—บทเรียนปฏิบัติในการสร้างระบบซับซ้อนที่สงบและปลอดภัยขึ้น

\nคุณสามารถคิดว่ามันเป็นภาษาที่ส่งเสริมให้คุณใช้บล็อกที่ชัดเจนและหลีกเลี่ยงผลข้างเคียงที่ซ่อนอยู่\n\n### ปัญหาที่มันต้องการลดลง\n\nClojure ไม่ได้ถูกสร้างมาเพื่อทำให้สคริปต์เล็ก ๆ สั้นลง แต่มุ่งที่ความเจ็บปวดซ้ำ ๆ ในโปรเจกต์:\n\n- : เมื่อหลายส่วนของระบบสามารถแก้ไขข้อมูลเดียวกันได้ บั๊กจะขึ้นกับจังหวะเวลาและยากต่อการทำซ้ำ\n- : เมื่อข้อมูลถูกล็อกไว้ภายในอ็อบเจ็กต์หรือคลาส การนำกลับมาใช้ใหม่ยากขึ้นและการเปลี่ยนแปลงส่งผลกระทบทั่วโค้ดเบส\n- : เมื่อระบบเพิ่มงานแบ็กกราวด์ คิว และงานขนาน คำถามว่า "ใครเปลี่ยนอะไร เมื่อไร" จะเป็นปัญหารายวัน\n\nค่าเริ่มต้นของ Clojure ผลักดันไปทางชิ้นส่วนเคลื่อนที่น้อยลง: โครงสร้างข้อมูลที่มั่นคง, การอัปเดตที่ชัดเจน, และเครื่องมือที่ทำให้การประสานงานปลอดภัยขึ้น\n\n### มีประโยชน์ถึงแม้คุณจะไม่ย้ายไปใช้ Clojure\n\nคุณค่าไม่ได้จำกัดอยู่ที่การเปลี่ยนภาษา แนวคิดสำคัญของ Hickey—ทำให้เรียบง่ายโดยการลดการพึ่งพาที่ไม่จำเป็น, ปฏิบัติต่อข้อมูลเป็นข้อเท็จจริงที่คงทน, และลดสถานะที่เปลี่ยนแปลงได้—สามารถปรับปรุงระบบใน Java, Python, JavaScript และภาษาอื่น ๆ ได้เช่นกัน\n\n## ความเรียบง่าย: ไม่ใช่ "ง่าย" แต่ชิ้นส่วนเคลื่อนที่น้อยลง\n\nRich Hickey แยกเส้นที่ชัดเจนระหว่าง และ — และนี่เป็นเส้นที่หลายโปรเจกต์ข้ามโดยไม่รู้ตัว\n\n### Simple vs. easy (ด้วยตัวอย่างในชีวิตประจำวัน)\n\n คือวิธีที่บางสิ่งรู้สึกตอนนี้ คือจำนวนชิ้นส่วนและการพันกันของมัน\n\n- บะหมี่กึ่งสำเร็จรูปมันง่าย ในขณะที่สตูว์พื้นฐานอาจเรียบง่าย: ส่วนผสมน้อยหนึ่งหม้อ ไม่มีอะไรซ่อนอยู่\n- รีโมทที่มีปุ่ม 60 ปุ่มอาจทำให้ฟีเจอร์หนึ่งเป็น "ง่าย" แต่ไม่เรียบง่าย รีโมทที่มีปุ่ม 6 ปุ่มชัดเจนจะเรียบง่ายกว่า แม้ว่าจะต้องใช้เวลาเรียนรู้บ้าง\n\nในซอฟต์แวร์ "easy" มักหมายถึง "พิมพ์เร็ววันนี้" ในขณะที่ "simple" หมายถึง "ยากที่จะทำให้เสียในเดือนหน้า"\n\n### วิธีที่ "ง่ายตอนนี้" สร้างความซับซ้อนในอนาคต\n\nทีมมักเลือกทางลัดที่ลดแรงเสียดทานทันทีแต่เพิ่มโครงสร้างที่มองไม่เห็นซึ่งต้องบำรุงรักษา:\n\n- "แค่เพิ่มแฟลก" ตอนนี้ทุกฟีเจอร์ต้องพิจารณาแฟลกนั้น\n- "เราจะเก็บค่าที่คำนวณแล้วเพื่อประหยัดเวลา" ตอนนี้คุณต้องทำให้มันสอดคล้องกันข้ามเส้นทางโค้ด\n- "เราจะแก้ที่ UI" ตอนนี้กฎธุรกิจเดียวกันอยู่ในหลายที่\n\nแต่ละตัวเลือกอาจรู้สึกว่าเร็ว แต่เพิ่มจำนวนชิ้นส่วนเคลื่อนที่ กรณีพิเศษ และการพึ่งพาข้ามกัน นั่นคือวิธีที่ระบบกลายเป็นเปราะบางโดยไม่มีความผิดพลาดใหญ่เพียงครั้งเดียว\n\n### ความเร็วไม่เท่ากับความเรียบง่าย\n\nการส่งงานเร็วอาจดี—แต่ มักหมายความว่าคุณกำลังกู้ยืมจากอนาคต ดอกเบี้ยจะปรากฏเป็นบั๊กที่ยากจะทำซ้ำ การเตรียมทีมที่ลากช้า และการเปลี่ยนแปลงที่ต้องการ "การประสานงานอย่างระมัดระวัง"\n\n### เช็คลิสต์ไว้วัดความซับซ้อนที่เกิดโดยไม่ตั้งใจ\n\nถามคำถามเหล่านี้เมื่อรีวิวการออกแบบหรือ PR:\n\n- เราแนะนำโหมดใหม่ แฟลก หรือสาขาการกำหนดค่าใหม่หรือไม่?\n- เรากำลังแคชหรือทำสำเนาข้อมูลที่ต้องรักษาความสอดคล้องหรือไม่?\n- โมดูลหลายตัวต้องเปลี่ยนพร้อมกันสำหรับพฤติกรรมเดียวหรือไม่?\n- กฎถูกนำไปใช้มากกว่าหนึ่งที่หรือไม่?\n- เพื่อนร่วมทีมใหม่จะคาดเดาวิธีการทำงานได้หรือไม่โดยไม่ต้องคำอธิบายเพิ่ม?
ความซับซ้อนสะสมมาจากการตัดสินใจเล็ก ๆ ที่ดูสมเหตุสมผลในช่วงเวลานั้น (การเพิ่มแฟลก, แคช, ข้อยกเว้น, ตัวช่วยร่วม) ซึ่งเพิ่ม โหมด และ การผูกมัด ให้กับระบบ
สัญญาณที่ดีคือเมื่อ "การเปลี่ยนแปลงเล็กน้อย" ต้องการการแก้ไขที่ประสานกันข้ามโมดูลหรือบริการหลายส่วน หรือเมื่อผู้ตรวจสอบโค้ดต้องพึ่งพาความรู้แบบปากต่อปากเพื่อประเมินความปลอดภัยของการเปลี่ยนแปลง
การลัดขั้นตอนจะเพิ่มแรงเสียดทานวันนี้ (เวลาในการส่งงาน) แต่ผลที่ตามมาจะถูกผลักไปยังอนาคต: เวลาที่ใช้ดีบัก, ค่าใช้จ่ายในการประสานงาน, และความเสี่ยงเมื่อเปลี่ยนแปลง
นิสัยที่เป็นประโยชน์คือถามในการออกแบบหรือรีวิว PR: “สิ่งนี้เพิ่มชิ้นส่วนเคลื่อนที่หรือกรณีพิเศษอะไรบ้าง และใครจะเป็นคนดูแลมัน?”
ค่าเริ่มต้นกำหนดสิ่งที่วิศวกรมักทำเมื่ออยู่ภายใต้ความกดดัน หากการแก้ไขเป็นค่าเริ่มต้น รัฐที่แชร์จะกระจาย หากค่าเริ่มต้นคือ “เก็บในหน่วยความจำได้” การติดตามต้นตอจะหายไป
ปรับปรุงค่าเริ่มต้นโดยทำให้เส้นทางที่ปลอดภัยเป็นเส้นทางที่สะดวกที่สุด: ข้อมูลไม่เปลี่ยนแปลงที่ขอบเขต, การกำหนด timezone/null/การ retry อย่างชัดเจน, และความเป็นเจ้าของสถานะที่ชัดเจน
สถานะคือทุกสิ่งที่เปลี่ยนแปลงตามเวลา ปัญหาคือการเปลี่ยนแปลงสร้างโอกาสให้เกิดความขัดแย้ง: ส่วนประกอบสองตัวอาจมีค่าปัจจุบันต่างกัน
บั๊กมักปรากฏเป็นพฤติกรรมที่ขึ้นกับการจับเวลา ("ทำงานบนเครื่องฉัน" แต่ไม่เสถียรในโปรดักชัน) เพราะคำถามคือ: เราใช้เวอร์ชันข้อมูลใดเมื่อทำงานนั้น?
ความไม่เปลี่ยนแปลงหมายถึงคุณไม่แก้ไขค่าเดิมในที่เดิม แต่สร้างค่าใหม่ที่แสดงการอัพเดต
ในทางปฏิบัติช่วยได้เพราะ:
ไม่เสมอไปว่าไม่ต้องใช้ การเปลี่ยนแปลงได้มีประโยชน์เมื่อถูก กักขัง:
กฎสำคัญคือ: อย่าให้โครงสร้างที่เปลี่ยนแปลงได้รั่วไหลข้ามขอบเขตที่หลายส่วนสามารถอ่าน/เขียนได้
เงื่อนไขการแข่งขันมักมาจากข้อมูลที่แชร์แล้วเปลี่ยนแปลงได้ซึ่งถูกอ่านแล้วเขียนโดยผู้ทำงานหลายตัว
ความไม่เปลี่ยนแปลงลดพื้นที่ที่ต้องประสานงานเพราะผู้เขียนจะสร้างเวอร์ชันใหม่แทนการแก้ไขวัตถุร่วม คุณยังคงต้องมีกฎในการ เผยแพร่ เวอร์ชันปัจจุบัน แต่ข้อมูลเองจะไม่เป็นเป้าหมายที่เคลื่อนที่
มองข้อเท็จจริงเป็นบันทึกแบบเพิ่มต่อ (append-only) ของสิ่งที่เกิดขึ้น และมอง "มุมมอง" เป็นสถานะปัจจุบันที่คำนวณจากข้อเท็จจริงเหล่านั้น
เริ่มจากเล็ก ๆ โดยไม่ต้องย้ายไปสถาปัตยกรรม event sourcing เต็มรูปแบบ:
เก็บข้อมูลเป็นข้อมูลชัดเจนและเรียบง่าย (ค่า) แล้วรันพฤติกรรม ต่อมัน หลีกเลี่ยงการฝังกฎที่รันได้ลงในระเบียนที่เก็บ
สิ่งนี้ทำให้ระบบพัฒนาได้ง่ายขึ้นเพราะ:
เลือก workflow ที่เปลี่ยนบ่อยหนึ่งอย่างแล้วทำสามขั้นตอนต่อไปนี้:
วัดผลโดยบั๊กที่ไม่เสถียรลดลง, ขอบเขตความเสียหายน้อยลงต่อการเปลี่ยน, และความจำเป็นในการประสานงานอย่างระมัดระวังลดลง