1 นาที

ริช ฮิคกีย์และ Clojure: ความเรียบง่าย ความไม่เปลี่ยนแปลง และค่าเริ่มต้นที่ดีกว่า

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

ริช ฮิคกีย์และ Clojure: ความเรียบง่าย ความไม่เปลี่ยนแปลง และค่าเริ่มต้นที่ดีกว่า

ทำไมความซับซ้อนจึงชนะในโปรเจกต์จริง

ซอฟต์แวร์ไม่ค่อยซับซ้อนทันทีในครั้งเดียว มันกลายเป็นเช่นนั้นจากการตัดสินใจ "สมเหตุสมผล" ทีละเรื่อง: แคชเล็ก ๆ เพื่อให้ทันกำหนดเวลา, วัตถุที่เปลี่ยนแปลงได้ร่วมกันเพื่อหลีกเลี่ยงการคัดลอก, ข้อยกเว้นกฎเพราะ "อันนี้พิเศษ" แต่ละตัวเลือกดูเหมือนเล็กน้อย แต่รวมกันแล้วจะสร้างระบบที่การเปลี่ยนแปลงรู้สึกเสี่ยง บั๊กยากต่อการทำซ้ำ และการเพิ่มฟีเจอร์เริ่มใช้เวลานานกว่าการพัฒนา

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

บทความนี้คืออะไร (และไม่ใช่อะไร)

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

ทำไมค่าเริ่มต้นจึงสำคัญกว่าที่คุณคิด

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

เราจะมุ่งไปที่สามธีม:

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

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

Rich Hickey และสิ่งที่ Clojure มาตั้งใจแก้

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

Clojure คืออะไร (ในภาพรวม แบบไม่ใช้ศัพท์ลึก)

Clojure เป็นภาษาการเขียนโปรแกรมสมัยใหม่ที่รันบนแพลตฟอร์มที่เป็นที่รู้จักเช่น JVM (runtime ของ Java) และ JavaScript ถูกออกแบบให้ทำงานร่วมกับระบบนิเวศที่มีอยู่ในขณะเดียวกันก็ส่งเสริมสไตล์เฉพาะ: แทนข้อมูลเป็นข้อมูลธรรมดา, ให้ความสำคัญกับค่าที่ไม่เปลี่ยนแปลง และแยก "สิ่งที่เกิดขึ้น" ออกจาก "สิ่งที่แสดงบนหน้าจอ"

คุณสามารถคิดว่ามันเป็นภาษาที่ส่งเสริมให้คุณใช้บล็อกที่ชัดเจนและหลีกเลี่ยงผลข้างเคียงที่ซ่อนอยู่

ปัญหาที่มันต้องการลดลง

Clojure ไม่ได้ถูกสร้างมาเพื่อทำให้สคริปต์เล็ก ๆ สั้นลง แต่มุ่งที่ความเจ็บปวดซ้ำ ๆ ในโปรเจกต์:

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

ค่าเริ่มต้นของ Clojure ผลักดันไปทางชิ้นส่วนเคลื่อนที่น้อยลง: โครงสร้างข้อมูลที่มั่นคง, การอัปเดตที่ชัดเจน, และเครื่องมือที่ทำให้การประสานงานปลอดภัยขึ้น

มีประโยชน์ถึงแม้คุณจะไม่ย้ายไปใช้ Clojure

คุณค่าไม่ได้จำกัดอยู่ที่การเปลี่ยนภาษา แนวคิดสำคัญของ Hickey—ทำให้เรียบง่ายโดยการลดการพึ่งพาที่ไม่จำเป็น, ปฏิบัติต่อข้อมูลเป็นข้อเท็จจริงที่คงทน, และลดสถานะที่เปลี่ยนแปลงได้—สามารถปรับปรุงระบบใน Java, Python, JavaScript และภาษาอื่น ๆ ได้เช่นกัน

ความเรียบง่าย: ไม่ใช่ "ง่าย" แต่ชิ้นส่วนเคลื่อนที่น้อยลง

Keep mobile logic clean
Spin up a Flutter mobile app from chat and keep the business logic clear and testable.

Rich Hickey แยกเส้นที่ชัดเจนระหว่าง simple และ easy — และนี่เป็นเส้นที่หลายโปรเจกต์ข้ามโดยไม่รู้ตัว

Simple vs. easy (ด้วยตัวอย่างในชีวิตประจำวัน)

Easy คือวิธีที่บางสิ่งรู้สึกตอนนี้ Simple คือจำนวนชิ้นส่วนและการพันกันของมัน

  • บะหมี่กึ่งสำเร็จรูปมันง่าย ในขณะที่สตูว์พื้นฐานอาจเรียบง่าย: ส่วนผสมน้อยหนึ่งหม้อ ไม่มีอะไรซ่อนอยู่
  • รีโมทที่มีปุ่ม 60 ปุ่มอาจทำให้ฟีเจอร์หนึ่งเป็น "ง่าย" แต่ไม่เรียบง่าย รีโมทที่มีปุ่ม 6 ปุ่มชัดเจนจะเรียบง่ายกว่า แม้ว่าจะต้องใช้เวลาเรียนรู้บ้าง

ในซอฟต์แวร์ "easy" มักหมายถึง "พิมพ์เร็ววันนี้" ในขณะที่ "simple" หมายถึง "ยากที่จะทำให้เสียในเดือนหน้า"

วิธีที่ "ง่ายตอนนี้" สร้างความซับซ้อนในอนาคต

ทีมมักเลือกทางลัดที่ลดแรงเสียดทานทันทีแต่เพิ่มโครงสร้างที่มองไม่เห็นซึ่งต้องบำรุงรักษา:

  • "แค่เพิ่มแฟลก" ตอนนี้ทุกฟีเจอร์ต้องพิจารณาแฟลกนั้น
  • "เราจะเก็บค่าที่คำนวณแล้วเพื่อประหยัดเวลา" ตอนนี้คุณต้องทำให้มันสอดคล้องกันข้ามเส้นทางโค้ด
  • "เราจะแก้ที่ UI" ตอนนี้กฎธุรกิจเดียวกันอยู่ในหลายที่

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

ความเร็วไม่เท่ากับความเรียบง่าย

การส่งงานเร็วอาจดี—แต่ ความเร็วโดยไม่มีการทำให้เรียบง่าย มักหมายความว่าคุณกำลังกู้ยืมจากอนาคต ดอกเบี้ยจะปรากฏเป็นบั๊กที่ยากจะทำซ้ำ การเตรียมทีมที่ลากช้า และการเปลี่ยนแปลงที่ต้องการ "การประสานงานอย่างระมัดระวัง"

เช็คลิสต์ไว้วัดความซับซ้อนที่เกิดโดยไม่ตั้งใจ

ถามคำถามเหล่านี้เมื่อรีวิวการออกแบบหรือ PR:

  • เราแนะนำโหมดใหม่ แฟลก หรือสาขาการกำหนดค่าใหม่หรือไม่?
  • เรากำลังแคชหรือทำสำเนาข้อมูลที่ต้องรักษาความสอดคล้องหรือไม่?
  • โมดูลหลายตัวต้องเปลี่ยนพร้อมกันสำหรับพฤติกรรมเดียวหรือไม่?
  • กฎถูกนำไปใช้มากกว่าหนึ่งที่หรือไม่?
  • เพื่อนร่วมทีมใหม่จะคาดเดาวิธีการทำงานได้หรือไม่โดยไม่ต้องคำอธิบายเพิ่ม?

คำถามที่พบบ่อย

Why does complexity keep “winning” in real projects?

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

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

What’s the difference between “simple” and “easy” in software?

การลัดขั้นตอนจะเพิ่มแรงเสียดทานวันนี้ (เวลาในการส่งงาน) แต่ผลที่ตามมาจะถูกผลักไปยังอนาคต: เวลาที่ใช้ดีบัก, ค่าใช้จ่ายในการประสานงาน, และความเสี่ยงเมื่อเปลี่ยนแปลง

นิสัยที่เป็นประโยชน์คือถามในการออกแบบหรือรีวิว PR: “สิ่งนี้เพิ่มชิ้นส่วนเคลื่อนที่หรือกรณีพิเศษอะไรบ้าง และใครจะเป็นคนดูแลมัน?”

How do programming language and framework defaults create accidental complexity?

ค่าเริ่มต้นกำหนดสิ่งที่วิศวกรมักทำเมื่ออยู่ภายใต้ความกดดัน หากการแก้ไขเป็นค่าเริ่มต้น รัฐที่แชร์จะกระจาย หากค่าเริ่มต้นคือ “เก็บในหน่วยความจำได้” การติดตามต้นตอจะหายไป

ปรับปรุงค่าเริ่มต้นโดยทำให้เส้นทางที่ปลอดภัยเป็นเส้นทางที่สะดวกที่สุด: ข้อมูลไม่เปลี่ยนแปลงที่ขอบเขต, การกำหนด timezone/null/การ retry อย่างชัดเจน, และความเป็นเจ้าของสถานะที่ชัดเจน

Why is state described as a “complexity multiplier”?

สถานะคือทุกสิ่งที่เปลี่ยนแปลงตามเวลา ปัญหาคือการเปลี่ยนแปลงสร้างโอกาสให้เกิดความขัดแย้ง: ส่วนประกอบสองตัวอาจมีค่าปัจจุบันต่างกัน

บั๊กมักปรากฏเป็นพฤติกรรมที่ขึ้นกับการจับเวลา ("ทำงานบนเครื่องฉัน" แต่ไม่เสถียรในโปรดักชัน) เพราะคำถามคือ: เราใช้เวอร์ชันข้อมูลใดเมื่อทำงานนั้น?

What does immutability mean in practical, non-academic terms?

ความไม่เปลี่ยนแปลงหมายถึงคุณไม่แก้ไขค่าเดิมในที่เดิม แต่สร้างค่าใหม่ที่แสดงการอัพเดต

ในทางปฏิบัติช่วยได้เพราะ:

  • ผู้ที่อ่านค่าจะเชื่อมั่นได้ว่าค่านั้นจะไม่เปลี่ยนกลางทาง
  • การทำซ้ำบั๊กง่ายขึ้น (อินพุตคงที่)
  • การแชร์ข้อมูลข้ามเธรด/โมดูลปลอดภัยขึ้น
When is mutability acceptable (or even preferable)?

ไม่เสมอไปว่าไม่ต้องใช้ การเปลี่ยนแปลงได้มีประโยชน์เมื่อถูก กักขัง:

  • ตัวแปรภายในฟังก์ชัน
  • จุดร้อนด้านประสิทธิภาพ (ลูปแน่น, การพาร์ส, งานตัวเลข)
  • แคชส่วนตัวที่ซ่อนอยู่หลังอินเตอร์เฟซแคบ ๆ

กฎสำคัญคือ: อย่าให้โครงสร้างที่เปลี่ยนแปลงได้รั่วไหลข้ามขอบเขตที่หลายส่วนสามารถอ่าน/เขียนได้

How does immutability help with concurrency under load?

เงื่อนไขการแข่งขันมักมาจากข้อมูลที่แชร์แล้วเปลี่ยนแปลงได้ซึ่งถูกอ่านแล้วเขียนโดยผู้ทำงานหลายตัว

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

What does it mean to separate “facts” from “views,” and how can I apply it incrementally?

มองข้อเท็จจริงเป็นบันทึกแบบเพิ่มต่อ (append-only) ของสิ่งที่เกิดขึ้น และมอง "มุมมอง" เป็นสถานะปัจจุบันที่คำนวณจากข้อเท็จจริงเหล่านั้น

เริ่มจากเล็ก ๆ โดยไม่ต้องย้ายไปสถาปัตยกรรม event sourcing เต็มรูปแบบ:

  • เพิ่มตาราง audit สำหรับการเปลี่ยนแปลงสำคัญ
  • บันทึกเหตุการณ์การเปลี่ยนแปลงสำหรับงานที่มีความเสี่ยงสูงหนึ่งงาน
  • เก็บ snapshot พร้อมหน้าต่างประวัติสั้น ๆ เพื่อการ replay/ดีบัก
What is “data-first” design and why does it reduce coupling?

เก็บข้อมูลเป็นข้อมูลชัดเจนและเรียบง่าย (ค่า) แล้วรันพฤติกรรม ต่อมัน หลีกเลี่ยงการฝังกฎที่รันได้ลงในระเบียนที่เก็บ

สิ่งนี้ทำให้ระบบพัฒนาได้ง่ายขึ้นเพราะ:

  • ระเบียนเก่ายังคงอ่านได้เมื่อโค้ดเปลี่ยน
  • ผู้บริโภคใหม่สามารถนำข้อมูลเดิมไปใช้ซ้ำได้
  • คุณเปลี่ยนตรรกะโดยไม่ต้องแก้ไขประวัติ
What are the first 3 concrete changes to try next sprint to reduce complexity?

เลือก workflow ที่เปลี่ยนบ่อยหนึ่งอย่างแล้วทำสามขั้นตอนต่อไปนี้:

  1. ทำให้ข้อมูลที่ผ่านขอบเขตไม่เปลี่ยนแปลงโดยค่าเริ่มต้น: พิจารณา input/output ของ API, ข้อความ, เหตุการณ์เป็นค่าสร้างครั้งเดียวแล้วไม่แก้ไข
  2. รีแฟกเตอร์แกนหลักให้เป็นการแปลงแบบ pure: ข้อมูลเข้า → ข้อมูลออก; ดัน I/O และผลข้างเคียงไปยังขอบ
  3. ลดโครงสร้างที่แชร์แล้วเปลี่ยนแปลงได้: ให้เจ้าของเดียวต่อสถานะแต่ละชิ้นและเปิดผ่านอินเตอร์เฟซเล็ก ๆ

วัดผลโดยบั๊กที่ไม่เสถียรลดลง, ขอบเขตความเสียหายน้อยลงต่อการเปลี่ยน, และความจำเป็นในการประสานงานอย่างระมัดระวังลดลง

Related posts