ใช้ประโยชน์จากการเขียนโปรแกรม Declarative เพื่อสร้างเว็บแอปที่บำรุงรักษาได้
เผยแพร่แล้ว: 2022-03-11ในบทความนี้ ผมจะแสดงวิธีที่การนำเทคนิคการเขียนโปรแกรมรูปแบบการประกาศมาใช้อย่างรอบคอบสามารถช่วยให้ทีมสร้างเว็บแอปพลิเคชันที่ขยายและบำรุงรักษาได้ง่ายขึ้น
“…การเขียนโปรแกรมเชิงประกาศเป็นกระบวนทัศน์การเขียนโปรแกรมที่แสดงตรรกะของการคำนวณโดยไม่อธิบายโฟลว์การควบคุมของมัน” —Remo H. Jansen การเขียนโปรแกรมเชิงปฏิบัติด้วย TypeScript
เช่นเดียวกับปัญหาอื่นๆ ในซอฟต์แวร์ การตัดสินใจใช้เทคนิคการเขียนโปรแกรมที่เปิดเผยในแอปพลิเคชันของคุณจำเป็นต้องมีการประเมินข้อดีข้อเสียอย่างรอบคอบ ตรวจสอบหนึ่งในบทความก่อนหน้าของเราสำหรับการอภิปรายเชิงลึกเกี่ยวกับสิ่งเหล่านี้
ในที่นี้ จุดเน้นอยู่ที่วิธีที่รูปแบบการเขียนโปรแกรมที่เปิดเผยออกมาค่อยๆ นำไปใช้สำหรับแอปพลิเคชันทั้งใหม่และที่มีอยู่ซึ่งเขียนด้วย JavaScript ซึ่งเป็นภาษาที่รองรับกระบวนทัศน์หลายแบบ
ขั้นแรก เราจะพูดถึงวิธีใช้ TypeScript ทั้งในส่วนหลังและส่วนหน้า เพื่อทำให้โค้ดของคุณมีความชัดเจนและยืดหยุ่นในการเปลี่ยนแปลงมากขึ้น จากนั้นเราจะสำรวจเครื่องจักรที่มีสถานะจำกัด (FSM) เพื่อปรับปรุงการพัฒนาส่วนหน้าและเพิ่มการมีส่วนร่วมของผู้มีส่วนได้ส่วนเสียในกระบวนการพัฒนา
FSM ไม่ใช่เทคโนโลยีใหม่ พวกเขาถูกค้นพบเมื่อเกือบ 50 ปีที่แล้วและเป็นที่นิยมในอุตสาหกรรมต่างๆ เช่น การประมวลผลสัญญาณ วิชาการบิน และการเงิน ซึ่งความถูกต้องของซอฟต์แวร์เป็นสิ่งสำคัญ นอกจากนี้ยังเหมาะสมอย่างยิ่งกับปัญหาการสร้างแบบจำลองที่มักเกิดขึ้นในการพัฒนาเว็บสมัยใหม่ เช่น การประสานงานการอัปเดตสถานะอะซิงโครนัสที่ซับซ้อนและแอนิเมชั่น
ผลประโยชน์นี้เกิดขึ้นเนื่องจากข้อจำกัดเกี่ยวกับวิธีการจัดการของรัฐ เครื่องแสดงสถานะสามารถอยู่ในสถานะเดียวเท่านั้นพร้อมกันและมีสถานะใกล้เคียงที่จำกัด ซึ่งสามารถเปลี่ยนเป็นการตอบสนองต่อเหตุการณ์ภายนอกได้ (เช่น การคลิกเมาส์หรือการตอบสนองการดึงข้อมูล) ผลที่ได้คืออัตราข้อบกพร่องที่ลดลงอย่างมาก อย่างไรก็ตาม วิธีการของ FSM อาจเป็นเรื่องยากที่จะขยายขนาดเพื่อให้ทำงานได้ดีในแอปพลิเคชันขนาดใหญ่ ส่วนขยายล่าสุดของ FSM ที่เรียกว่า statechart ช่วยให้ FSM ที่ซับซ้อนสามารถแสดงภาพและปรับขนาดเป็นแอปพลิเคชันขนาดใหญ่กว่าได้ ซึ่งเป็นรสชาติของเครื่องจักรที่มีสถานะจำกัดที่บทความนี้เน้น สำหรับการสาธิตของเรา เราจะใช้ไลบรารี XState ซึ่งเป็นหนึ่งในโซลูชันที่ดีที่สุดสำหรับ FSM และ statechart ใน JavaScript
ประกาศที่ส่วนหลังด้วย Node.js
การเขียนโปรแกรมเว็บเซิร์ฟเวอร์แบ็กเอนด์โดยใช้วิธีการเปิดเผยเป็นหัวข้อขนาดใหญ่ และโดยทั่วไปอาจเริ่มต้นด้วยการประเมินภาษาโปรแกรมที่ใช้งานได้ฝั่งเซิร์ฟเวอร์ที่เหมาะสม สมมติว่าคุณกำลังอ่านข้อความนี้ในเวลาที่คุณเลือก (หรือกำลังพิจารณา) Node.js สำหรับส่วนหลังของคุณ
ส่วนนี้ให้รายละเอียดวิธีการในการสร้างแบบจำลองเอนทิตีที่ส่วนหลังที่มีประโยชน์ดังต่อไปนี้:
- ปรับปรุงความสามารถในการอ่านโค้ด
- การปรับโครงสร้างใหม่อย่างปลอดภัย
- ศักยภาพในการปรับปรุงประสิทธิภาพเนื่องจากการให้แบบจำลองประเภทการรับประกัน
การรับประกันพฤติกรรมผ่านการสร้างแบบจำลองประเภท
JavaScript
พิจารณางานในการค้นหาผู้ใช้ที่ระบุผ่านที่อยู่อีเมลใน JavaScript:
function validateEmail(email) { if (typeof email !== "string") return false; return isWellFormedEmailAddress(email); } function lookupUser(validatedEmail) { // Assume a valid email is passed in. // Safe to pass this down to the database for a user lookup.. }
ฟังก์ชันนี้ยอมรับที่อยู่อีเมลเป็นสตริงและส่งคืนผู้ใช้ที่เกี่ยวข้องจากฐานข้อมูลเมื่อมีข้อมูลที่ตรงกัน
สมมติฐานคือ lookupUser()
จะถูกเรียกเมื่อทำการตรวจสอบความถูกต้องพื้นฐานแล้วเท่านั้น นี่เป็นข้อสันนิษฐานที่สำคัญ จะเกิดอะไรขึ้นหากผ่านไปหลายสัปดาห์ มีการจัดโครงสร้างใหม่บางส่วนและข้อสันนิษฐานนี้ไม่มีอยู่อีกต่อไป เห็นด้วยอย่างยิ่งว่าการทดสอบหน่วยตรวจพบจุดบกพร่อง หรือเราอาจส่งข้อความที่ไม่มีการกรองไปยังฐานข้อมูล!
TypeScript (ความพยายามครั้งแรก)
ลองพิจารณาว่า TypeScript เทียบเท่ากับฟังก์ชันการตรวจสอบความถูกต้อง:
function validateEmail(email: string) { // No longer needed the type check (typeof email === "string"). return isWellFormedEmailAddress(email); }
นี่เป็นการปรับปรุงเล็กน้อย โดยคอมไพเลอร์ TypeScript ช่วยเราจากการเพิ่มขั้นตอนการตรวจสอบรันไทม์เพิ่มเติม
ความปลอดภัยรับประกันว่าการพิมพ์ที่รัดกุมนั้นยังไม่ได้ถูกเอาเปรียบจริงๆ ลองดูว่า
TypeScript (พยายามครั้งที่สอง)
มาปรับปรุงความปลอดภัยของประเภทและไม่อนุญาตให้ส่งสตริงที่ยังไม่ได้ประมวลผลเป็นอินพุตไปยัง looukupUser
:
type ValidEmail = { value: string }; function validateEmail(input: string): Email | null { if (!isWellFormedEmailAddress(input)) return null; return { value: email }; } function lookupUser(email: ValidEmail): User { // No need to perform validation. Compiler has already ensured only valid emails have been passed in. return lookupUserInDatabase(email.value); }
จะดีกว่านี้ แต่ยุ่งยาก การใช้ ValidEmail
ทั้งหมดจะเข้าถึงที่อยู่จริงผ่าน email.value
TypeScript ใช้การพิมพ์ แบบโครงสร้าง มากกว่าการพิมพ์แบบ ระบุชื่อที่ ใช้โดยภาษาต่างๆ เช่น Java และ C#
แม้ว่าจะมีประสิทธิภาพ แต่ก็หมายความว่าประเภทอื่นใดที่ยึดตามลายเซ็นนี้ถือว่าเทียบเท่า ตัวอย่างเช่น รหัสผ่านประเภทต่อไปนี้สามารถส่งต่อไปยัง lookupUser()
โดยไม่มีข้อร้องเรียนจากคอมไพเลอร์:
type ValidPassword = { value: string }; const password = { value: "password" }; lookupUser(password); // No error.
TypeScript (พยายามครั้งที่สาม)
เราสามารถพิมพ์เล็กน้อยใน TypeScript โดยใช้จุดตัด:
type ValidEmail = string & { _: "ValidEmail" }; function validateEmail(input: string): ValidEmail { // Perform email validation checks.. return input as ValidEmail; } type ValidPassword = string & { _: "ValidPassword" }; function validatePassword(input: string): ValidPassword { ... } lookupUser("[email protected]"); // Error: expected type ValidEmail. lookupUser(validatePassword("MyPassword"); // Error: expected type ValidEmail. lookupUser(validateEmail("[email protected]")); // Ok.
ตอนนี้เราบรรลุเป้าหมายที่ส่งผ่านเฉพาะสตริงอีเมลที่ตรวจสอบแล้วไปยัง lookupUser()
ได้
เคล็ดลับแบบมือโปร: ใช้รูปแบบนี้อย่างง่ายดายโดยใช้ประเภทตัวช่วยต่อไปนี้:
type Opaque<K, T> = T & { __TYPE__: K }; type Email = Opaque<"Email", string>; type Password = Opaque<"Password", string>; type UserId = Opaque<"UserId", number>;
ข้อดี
ด้วยการพิมพ์เอนทิตีในโดเมนของคุณอย่างจริงจัง เราสามารถ:
- ลดจำนวนการตรวจสอบที่ต้องทำขณะรันไทม์ ซึ่งใช้รอบ CPU ของเซิร์ฟเวอร์อันมีค่า (แม้ว่าจะมีจำนวนเล็กน้อย แต่สิ่งเหล่านี้จะเพิ่มขึ้นเมื่อให้บริการคำขอนับพันต่อนาที)
- รักษาการทดสอบพื้นฐานให้น้อยลงเนื่องจากการค้ำประกันที่คอมไพเลอร์ TypeScript มีให้
- ใช้ประโยชน์จากการปรับโครงสร้างใหม่โดยใช้เครื่องมือแก้ไขและคอมไพเลอร์
- ปรับปรุงความสามารถในการอ่านโค้ดผ่านอัตราส่วนสัญญาณต่อสัญญาณรบกวนที่ได้รับการปรับปรุง
ข้อเสีย
การสร้างแบบจำลองประเภทมาพร้อมกับข้อเสียบางประการที่ต้องพิจารณา:
- โดยปกติแล้ว การแนะนำ TypeScript จะทำให้ toolchain ซับซ้อน ส่งผลให้เวลาในการสร้างและทดสอบชุดทำงานยาวนานขึ้น
- หากเป้าหมายของคุณคือการสร้างต้นแบบคุณลักษณะและเข้าถึงผู้ใช้โดยเร็วที่สุด ความพยายามพิเศษที่จำเป็นในการสร้างแบบจำลองประเภทอย่างชัดเจนและเผยแพร่ผ่าน codebase อาจไม่คุ้มค่า
เราได้แสดงให้เห็นว่าโค้ด JavaScript ที่มีอยู่บนเซิร์ฟเวอร์หรือแบ็กเอนด์/ส่วนตรวจสอบส่วนหน้าที่ใช้ร่วมกันสามารถขยายได้ด้วยประเภทต่างๆ ได้อย่างไร เพื่อปรับปรุงความสามารถในการอ่านโค้ด และอนุญาตให้มีการจัดโครงสร้างใหม่ได้อย่างปลอดภัย ซึ่งเป็นข้อกำหนดที่สำคัญสำหรับทีม
ส่วนต่อประสานผู้ใช้ที่ประกาศ
ส่วนต่อประสานผู้ใช้ที่พัฒนาโดยใช้เทคนิคการเขียนโปรแกรมแบบเปิดเผยเน้นความพยายามในการอธิบาย "อะไร" มากกว่า "อย่างไร" ส่วนประกอบพื้นฐานหลักสองในสามของเว็บ ได้แก่ CSS และ HTML เป็นภาษาการเขียนโปรแกรมเชิงประกาศที่ผ่านการทดสอบของเวลาและเว็บไซต์มากกว่า 1 พันล้านแห่ง
React เป็นโอเพ่นซอร์สโดย Facebook ในปี 2013 และได้เปลี่ยนแปลงแนวทางการพัฒนาส่วนหน้าอย่างมีนัยสำคัญ เมื่อใช้งานครั้งแรก ฉันชอบที่ฉันสามารถ ประกาศ GUI เป็นฟังก์ชันของสถานะของแอปพลิเคชันได้ ตอนนี้ฉันสามารถเขียน UI ขนาดใหญ่และซับซ้อนจากหน่วยการสร้างขนาดเล็กโดยไม่ต้องจัดการกับรายละเอียดยุ่ง ๆ ของการจัดการ DOM และการติดตามว่าส่วนใดของแอปจำเป็นต้องอัปเดตเพื่อตอบสนองต่อการกระทำของผู้ใช้ ฉันสามารถเพิกเฉยต่อด้าน เวลา เป็นส่วนใหญ่เมื่อกำหนด UI และมุ่งเน้นไปที่การตรวจสอบให้แน่ใจว่าแอปพลิเคชันของฉันเปลี่ยนจากสถานะหนึ่งไปอีกสถานะหนึ่งอย่างถูกต้อง
เพื่อให้บรรลุวิธีที่ง่ายกว่าในการพัฒนา UIs React ได้แทรกเลเยอร์ที่เป็นนามธรรมระหว่างผู้พัฒนากับเครื่อง/เบราว์เซอร์: DOM เสมือน
เฟรมเวิร์กเว็บ UI สมัยใหม่อื่น ๆ ได้เชื่อมช่องว่างนี้ด้วย แม้ว่าจะมีวิธีที่ต่างกัน ตัวอย่างเช่น Vue ใช้การตอบสนองต่อการทำงานผ่านตัวรับ/เซ็ตเตอร์ JavaScript (Vue 2) หรือพร็อกซี่ (Vue 3) Svelte นำการเกิดปฏิกิริยาผ่านขั้นตอนการคอมไพล์ซอร์สโค้ดพิเศษ (Svelte)
ตัวอย่างเหล่านี้ดูเหมือนจะแสดงให้เห็นถึงความต้องการอย่างมากในอุตสาหกรรมของเราในการจัดหาเครื่องมือที่ดีกว่าและง่ายกว่าสำหรับนักพัฒนาในการแสดงพฤติกรรมของแอปพลิเคชันผ่านแนวทางการประกาศ
สถานะแอปพลิเคชันประกาศและลอจิก
ในขณะที่ชั้นการนำเสนอยังคงหมุนรอบรูปแบบของ HTML บางรูปแบบ (เช่น JSX ใน React, แม่แบบที่ใช้ HTML ที่พบใน Vue, Angular และ Svelte) ฉันสันนิษฐานว่าปัญหาของการสร้างแบบจำลองสถานะของแอปพลิเคชันในลักษณะที่เป็น นักพัฒนารายอื่นเข้าใจได้ง่ายและสามารถบำรุงรักษาได้เนื่องจากแอปพลิเคชันเติบโตขึ้นยังไม่ได้รับการแก้ไข เราเห็นหลักฐานของสิ่งนี้ผ่านการขยายตัวของห้องสมุดและแนวทางการจัดการของรัฐที่ดำเนินต่อไปจนถึงทุกวันนี้
สถานการณ์มีความซับซ้อนโดยความคาดหวังที่เพิ่มขึ้นของเว็บแอปสมัยใหม่ ความท้าทายที่เกิดขึ้นใหม่ซึ่งแนวทางการจัดการของรัฐสมัยใหม่ต้องสนับสนุน:
- แอปพลิเคชั่นออฟไลน์ครั้งแรกโดยใช้เทคนิคการสมัครสมาชิกขั้นสูงและแคช
- โค้ดที่กระชับและการใช้โค้ดซ้ำสำหรับข้อกำหนดขนาดบันเดิลที่ลดขนาดลงเรื่อยๆ
- ความต้องการประสบการณ์ผู้ใช้ที่ซับซ้อนมากขึ้นผ่านแอนิเมชั่นที่มีความเที่ยงตรงสูงและการอัปเดตตามเวลาจริง
(Re) การเกิดขึ้นของ Finite-state Machines และ Statecharts
มีการใช้เครื่องจักรแบบไฟไนต์สเตทอย่างกว้างขวางสำหรับการพัฒนาซอฟต์แวร์ในอุตสาหกรรมบางประเภทที่ความทนทานของแอปพลิเคชันเป็นสิ่งสำคัญ เช่น การบินและการเงิน นอกจากนี้ยังได้รับความนิยมอย่างต่อเนื่องสำหรับการพัฒนาเว็บแอปส่วนหน้าผ่าน ตัวอย่างเช่น ไลบรารี XState ที่ยอดเยี่ยม
Wikipedia กำหนดเครื่องสถานะ จำกัด เป็น:
เครื่องนามธรรมที่สามารถอยู่ในสถานะใดสถานะหนึ่งในช่วงเวลาหนึ่ง FSM สามารถเปลี่ยนจากสถานะหนึ่งไปอีกสถานะหนึ่งเพื่อตอบสนองต่ออินพุตภายนอกบางอย่าง การเปลี่ยนแปลงจากสถานะหนึ่งไปอีกสถานะหนึ่งเรียกว่าการเปลี่ยนแปลง FSM ถูกกำหนดโดยรายการสถานะ สถานะเริ่มต้น และเงื่อนไขสำหรับการเปลี่ยนแต่ละครั้ง
และต่อไป:
สถานะคือคำอธิบายสถานะของระบบที่กำลังรอดำเนินการเปลี่ยน
FSM ในรูปแบบพื้นฐานไม่สามารถปรับขนาดได้ดีกับระบบขนาดใหญ่เนื่องจากปัญหาสถานะการระเบิด เมื่อเร็ว ๆ นี้ UML statechart ถูกสร้างขึ้นเพื่อขยาย FSM ด้วยลำดับชั้นและการทำงานพร้อมกัน ซึ่งเป็นตัวเปิดใช้งานสำหรับการใช้ FSM ในวงกว้างในแอปพลิเคชันเชิงพาณิชย์

ประกาศลอจิกการสมัครของคุณ
ประการแรก FSM มีลักษณะเป็นโค้ดอย่างไร มีหลายวิธีในการปรับใช้เครื่องที่มีสถานะจำกัดใน JavaScript
- เครื่องไฟไนต์สเตทเป็นคำสั่งสวิตช์
นี่คือเครื่องที่อธิบายสถานะที่เป็นไปได้ที่ JavaScript สามารถนำไปใช้ได้ โดยใช้คำสั่ง switch:
const initialState = { type: 'idle', error: undefined, result: undefined }; function transition(state = initialState, action) { switch (action) { case 'invoke': return { type: 'pending' }; case 'resolve': return { type: 'completed', result: action.value }; case 'error': return { type: 'completed', error: action.error ; default: return state; } }
โค้ดรูปแบบนี้จะคุ้นเคยสำหรับนักพัฒนาที่เคยใช้ไลบรารีการจัดการสถานะ Redux ยอดนิยม
- เครื่องจำกัดสถานะเป็นวัตถุ JavaScript
นี่คือเครื่องเดียวกับที่ใช้งานเป็นวัตถุ JavaScript โดยใช้ไลบรารี JavaScript XSstate:
const promiseMachine = Machine({ id: "promise", initial: "idle", context: { result: undefined, error: undefined, }, states: { idle: { on: { INVOKE: "pending", }, }, pending: { on: { RESOLVE: "success", REJECT: "failure", }, }, success: { type: "final", actions: assign({ result: (context, event) => event.data, }), }, failure: { type: "final", actions: assign({ error: (context, event) => event.data, }), }, }, });
แม้ว่าเวอร์ชัน XSstate จะมีขนาดกะทัดรัดน้อยกว่า การแสดงวัตถุก็มีข้อดีหลายประการ:
- เครื่องของรัฐนั้นเป็น JSON แบบง่าย ซึ่งสามารถคงอยู่ได้อย่างง่ายดาย
- เนื่องจากเป็นการประกาศเครื่องจึงสามารถเห็นภาพได้
- หากใช้ TypeScript คอมไพเลอร์จะตรวจสอบว่ามีการดำเนินการเฉพาะการเปลี่ยนสถานะที่ถูกต้องเท่านั้น
XState รองรับ statecharts และใช้ข้อกำหนด SCXML ซึ่งทำให้เหมาะสำหรับการใช้งานในแอปพลิเคชันขนาดใหญ่มาก
การแสดงภาพ Statecharts ของสัญญา:
แนวทางปฏิบัติที่ดีที่สุด XSstate
ต่อไปนี้คือแนวทางปฏิบัติที่ดีที่สุดบางประการที่ควรใช้เมื่อใช้ XSstate เพื่อช่วยให้โครงการสามารถบำรุงรักษาได้
แยกผลข้างเคียงจาก Logic
XState อนุญาตให้ระบุผลข้างเคียง (ซึ่งรวมถึงกิจกรรม เช่น การบันทึกหรือคำขอ API) แยกจากตรรกะของเครื่องสถานะ
สิ่งนี้มีประโยชน์ดังต่อไปนี้:
- ช่วยตรวจจับข้อผิดพลาดทางตรรกะโดยรักษาสถานะรหัสเครื่องให้สะอาดและเรียบง่ายที่สุด
- แสดงภาพสถานะเครื่องได้อย่างง่ายดายโดยไม่จำเป็นต้องถอดต้นแบบเพิ่มเติมก่อน
- ทดสอบเครื่องของรัฐได้ง่ายขึ้นโดยการฉีดบริการจำลอง
const fetchUsersMachine = Machine({ id: "fetchUsers", initial: "idle", context: { users: undefined, error: undefined, nextPage: 0, }, states: { idle: { on: { FETCH: "fetching", }, }, fetching: { invoke: { src: (context) => fetch(`url/to/users?page=${context.nextPage}`).then((response) => response.json() ), onDone: { target: "success", actions: assign({ users: (context, event) => [...context.users, ...event.data], // Data holds the newly fetched users nextPage: (context) => context.nextPage + 1, }), }, onError: { target: "failure", error: (_, event) => event.data, // Data holds the error }, }, }, // success state.. // failure state.. }, });
แม้ว่าจะเป็นเรื่องน่าดึงดูดใจที่จะเขียนเครื่องของรัฐในลักษณะนี้ในขณะที่คุณยังคงทำงานต่างๆ ได้ การแยกข้อกังวลที่ดีกว่าทำได้โดยการส่งต่อผลข้างเคียงเป็นตัวเลือก:
const services = { getUsers: (context) => fetch( `url/to/users?page=${context.nextPage}` ).then((response) => response.json()) } const fetchUsersMachine = Machine({ ... states: { ... fetching: { invoke: { // Invoke the side effect at key: 'getUsers' in the supplied services object. src: 'getUsers', } on: { RESOLVE: "success", REJECT: "failure", }, }, ... }, // Supply the side effects to be executed on state transitions. { services } });
นอกจากนี้ยังช่วยให้ทำการทดสอบหน่วยของเครื่องสถานะได้ง่าย ทำให้สามารถจำลองการดึงข้อมูลของผู้ใช้ได้อย่างชัดเจน:
async function testFetchUsers() { return [{ name: "Peter", location: "New Zealand" }]; } const machine = fetchUsersMachine.withConfig({ services: { getUsers: (context) => testFetchUsers(), }, });
แยกเครื่องจักรขนาดใหญ่
ไม่ชัดเจนในทันทีว่าจะจัดโครงสร้างโดเมนของปัญหาให้อยู่ในลำดับชั้นของเครื่องจักรที่มีสถานะจำกัดที่ดีได้อย่างไรเมื่อเริ่มต้น
เคล็ดลับ: ใช้ลำดับชั้นของส่วนประกอบ UI ของคุณเพื่อช่วยแนะนำกระบวนการนี้ ดูส่วนถัดไปเกี่ยวกับวิธีการแมปเครื่องสถานะกับส่วนประกอบ UI
ประโยชน์หลักของการใช้เครื่องของรัฐคือการสร้างแบบจำลองสถานะและช่วงการเปลี่ยนภาพทั้งหมดอย่างชัดเจนระหว่างสถานะในแอปพลิเคชันของคุณ เพื่อให้เข้าใจพฤติกรรมที่เป็นผลลัพธ์ได้อย่างชัดเจน ทำให้มองเห็นข้อผิดพลาดหรือช่องว่างทางตรรกะได้ง่าย
เพื่อให้มันทำงานได้ดี เครื่องจักรจะต้องมีขนาดเล็กและรัดกุม โชคดีที่การเขียนเครื่องของรัฐตามลำดับชั้นนั้นง่าย ในตัวอย่างผังบัญญัติของระบบสัญญาณไฟจราจร สถานะ "สีแดง" จะกลายเป็นเครื่องแสดงสถานะย่อย เครื่องหลัก "ไฟ" ไม่ทราบสถานะภายในของ "สีแดง" แต่ตัดสินใจว่าเมื่อใดจึงจะเข้าสู่ "สีแดง" และลักษณะการทำงานที่ตั้งใจไว้เมื่อออกจากระบบ:
1-1 การแมปเครื่องของรัฐกับส่วนประกอบ UI แบบเก็บสถานะ
ตัวอย่างเช่น ไซต์อีคอมเมิร์ซสมมติที่เรียบง่ายซึ่งมีมุมมอง React ต่อไปนี้:
<App> <SigninForm /> <RegistrationForm /> <Products /> <Cart /> <Admin> <Users /> <Products /> </Admin> </App>
ขั้นตอนการสร้างเครื่องของรัฐที่สอดคล้องกับมุมมองข้างต้นอาจคุ้นเคยสำหรับผู้ที่ใช้ไลบรารีการจัดการสถานะ Redux:
- คอมโพเนนต์มีสถานะที่ต้องสร้างโมเดลหรือไม่ ตัวอย่างเช่น ผู้ดูแลระบบ/ผลิตภัณฑ์ต้องไม่ การดึงเพจไปยังเซิร์ฟเวอร์บวกกับโซลูชันแคช (เช่น SWR) อาจเพียงพอ ในทางกลับกัน ส่วนประกอบ เช่น SignInForm หรือ Cart มักจะมีสถานะที่จำเป็นต้องได้รับการจัดการ เช่น ข้อมูลที่ป้อนลงในฟิลด์หรือเนื้อหาในรถเข็นปัจจุบัน
- เทคนิคท้องถิ่น (เช่น
setState() / useState()
ของ React เพียงพอที่จะจับปัญหาหรือไม่ การติดตามว่าโมดอลแบบป๊อปอัปของรถเข็นเปิดอยู่ในปัจจุบันแทบไม่ต้องใช้เครื่องที่มีสถานะจำกัด - เครื่องสถานะที่เป็นผลลัพธ์มีแนวโน้มที่จะซับซ้อนเกินไปหรือไม่? ถ้าใช่ ให้แยกเครื่องออกเป็นชิ้นเล็กๆ หลายๆ ชิ้น เพื่อระบุโอกาสในการสร้างเครื่องย่อยที่สามารถนำกลับมาใช้ใหม่ได้ในที่อื่น ตัวอย่างเช่น เครื่อง SignInForm และ RegistrationForm อาจเรียกใช้อินสแตนซ์ของ textFieldMachine ลูกเพื่อตรวจสอบความถูกต้องของแบบจำลองและสถานะสำหรับฟิลด์อีเมล ชื่อ และรหัสผ่านของผู้ใช้
เมื่อใดจึงควรใช้แบบจำลองเครื่องจักรที่มีสถานะจำกัด
ในขณะที่ statechart และ FSM สามารถแก้ปัญหาที่ท้าทายบางอย่างได้อย่างสวยงาม การตัดสินใจเลือกเครื่องมือและแนวทางที่ดีที่สุดสำหรับแอปพลิเคชันนั้น ๆ มักจะขึ้นอยู่กับปัจจัยหลายประการ
บางสถานการณ์ที่ใช้เครื่องจักรที่มีสถานะ จำกัด ส่องแสง:
- แอปพลิเคชันของคุณมีองค์ประกอบการป้อนข้อมูลจำนวนมาก ที่การเข้าถึงหรือการมองเห็นฟิลด์ถูกควบคุมโดยกฎที่ซับซ้อน: ตัวอย่างเช่น การป้อนแบบฟอร์มในแอปเคลมประกัน ที่นี่ FSM ช่วยให้แน่ใจว่ากฎเกณฑ์ทางธุรกิจได้รับการปฏิบัติอย่างแข็งแกร่ง นอกจากนี้ สามารถใช้คุณลักษณะการแสดงภาพของ statechart เพื่อช่วยเพิ่มความร่วมมือกับผู้มีส่วนได้ส่วนเสียที่ไม่ใช่ด้านเทคนิค และระบุข้อกำหนดทางธุรกิจโดยละเอียดในช่วงต้นของการพัฒนา
- เพื่อให้ทำงานได้ดีขึ้นในการเชื่อมต่อที่ช้ากว่าและมอบประสบการณ์ที่มีความเที่ยงตรงสูงแก่ผู้ใช้ เว็บแอปต้องจัดการโฟลว์ข้อมูล async ที่ซับซ้อนมากขึ้น FSM จำลองสถานะทั้งหมดที่แอปพลิเคชันสามารถอยู่ได้อย่างชัดเจน และแผนภูมิสถานะสามารถมองเห็นได้เพื่อช่วยในการวินิจฉัยและแก้ปัญหาข้อมูลแบบอะซิงโครนัส
- แอปพลิเคชันที่ต้องใช้แอนิเมชั่นแบบ state-based ที่ซับซ้อนมาก สำหรับแอนิเมชั่นที่ซับซ้อน เทคนิคสำหรับการสร้างโมเดลแอนิเมชั่นเป็นกระแสเหตุการณ์ตลอดเวลาด้วย RxJS นั้นเป็นที่นิยม สำหรับหลาย ๆ สถานการณ์ วิธีนี้ใช้ได้ผลดี แต่เมื่อแอนิเมชั่นที่มีเนื้อหาสมบูรณ์ถูกรวมเข้ากับชุดสถานะที่รู้จักที่ซับซ้อน FSM จะให้ "จุดพัก" ที่กำหนดไว้อย่างดีซึ่งแอนิเมชันจะไหลไปมา FSM ที่รวมกับ RxJS ดูเหมือนจะเป็นการผสมผสานที่ลงตัวเพื่อช่วยส่งมอบประสบการณ์ผู้ใช้ที่มีความเที่ยงตรงสูงและแสดงออกถึงความเที่ยงตรงสูง
- แอปพลิเคชันไคลเอนต์ ที่หลากหลาย เช่น การตัดต่อรูปภาพหรือวิดีโอ เครื่องมือสร้างไดอะแกรม หรือเกมที่ตรรกะทางธุรกิจส่วนใหญ่อยู่ในฝั่งไคลเอ็นต์ FSM นั้นแยกจากเฟรมเวิร์ก UI หรือไลบรารีโดยเนื้อแท้และง่ายต่อการเขียนการทดสอบเพื่อให้สามารถทำซ้ำแอปพลิเคชันคุณภาพสูงได้อย่างรวดเร็วและจัดส่งด้วยความมั่นใจ
คำเตือนเครื่องไฟไนต์สเตท
- แนวทางทั่วไป แนวทางปฏิบัติที่ดีที่สุด และ API สำหรับไลบรารี statechart เช่น XState นั้นแปลกใหม่สำหรับนักพัฒนาฟรอนต์เอนด์ส่วนใหญ่ ซึ่งต้องใช้เวลาและทรัพยากรในการทุ่มเทเวลาเพื่อให้เกิดประสิทธิผล โดยเฉพาะสำหรับทีมที่มีประสบการณ์น้อย
- คล้ายกับข้อแม้ก่อนหน้านี้ ในขณะที่ความนิยมของ XState ยังคงเติบโตและมีการบันทึกเป็นอย่างดี ไลบรารีการจัดการสถานะที่มีอยู่ เช่น Redux, MobX หรือ React Context มีการติดตามจำนวนมากที่ให้ข้อมูลออนไลน์มากมายที่ XState ยังไม่ตรงกัน
- สำหรับแอปพลิเคชันที่ทำตามโมเดล CRUD ที่ง่ายกว่า เทคนิคการจัดการสถานะที่มีอยู่รวมกับไลบรารีแคชทรัพยากรที่ดี เช่น SWR หรือ React Query ก็เพียงพอแล้ว ในที่นี้ ข้อจำกัดเพิ่มเติมที่ FSM มีให้ แม้ว่าแอปที่ซับซ้อนจะมีประโยชน์อย่างเหลือเชื่อ แต่ก็อาจทำให้การพัฒนาช้าลงได้
- เครื่องมือนี้มีความสมบูรณ์น้อยกว่าไลบรารีการจัดการสถานะอื่น ๆ โดยที่งานยังคงดำเนินการเกี่ยวกับการสนับสนุน TypeScript ที่ได้รับการปรับปรุงและส่วนขยาย devtools ของเบราว์เซอร์
ห่อ
ความนิยมและการยอมรับของโปรแกรมการประกาศในชุมชนการพัฒนาเว็บยังคงเพิ่มขึ้นอย่างต่อเนื่อง
ในขณะที่การพัฒนาเว็บสมัยใหม่ยังคงมีความซับซ้อนมากขึ้น ไลบรารีและเฟรมเวิร์กที่นำแนวทางการเขียนโปรแกรมแบบเปิดเผยมาใช้นั้นมีความถี่เพิ่มขึ้น เหตุผลดูเหมือนชัดเจน—ต้องสร้างแนวทางที่ง่ายกว่าและอธิบายได้มากกว่าในการเขียนซอฟต์แวร์
การใช้ภาษาที่พิมพ์อย่างเข้มงวด เช่น TypeScript ช่วยให้สามารถกำหนดโมเดลเอนทิตีในโดเมนแอปพลิเคชันได้กระชับและชัดเจน ซึ่งช่วยลดโอกาสของข้อผิดพลาดและจำนวนโค้ดตรวจสอบที่มีแนวโน้มผิดพลาดที่ต้องจัดการ การนำเครื่อง finite-state และ statecharts มาใช้ในส่วนหน้าช่วยให้นักพัฒนาสามารถประกาศตรรกะทางธุรกิจของแอปพลิเคชันผ่านการเปลี่ยนสถานะ ทำให้สามารถพัฒนาเครื่องมือสร้างภาพข้อมูลที่สมบูรณ์ และเพิ่มโอกาสในการทำงานร่วมกันอย่างใกล้ชิดกับผู้ที่ไม่ใช่นักพัฒนา
เมื่อเราทำเช่นนี้ เราจะเปลี่ยนโฟกัสจากวิธีการทำงานของแอปพลิเคชันไปเป็นมุมมองในระดับที่สูงขึ้น ซึ่งช่วยให้เรามุ่งความสนใจไปที่ความต้องการของลูกค้าได้มากยิ่งขึ้นและสร้างมูลค่าที่ยั่งยืน