ถ้ายังยิงเท้าตัวเองซ้ำๆ อยู่ ก็ไปซ่อมปืนซะ
- ในทีมมักมีจุดที่พลาดเกี่ยวกับระบบกันบ่อย แต่หลายครั้งกลับไม่ค่อยคิดว่าจะลดความผิดพลาดนั้นอย่างไร
- ในกรณีแบบนี้ สิ่งสำคัญคือการปรับปรุงระบบเพื่อลดความผิดพลาด
- ประสบการณ์:
- ตอนพัฒนา iOS แล้วใช้ CoreData การอัปเดต UI ทำได้เฉพาะบน main thread เท่านั้น
- callback ของ subscription เกิดขึ้นได้ทั้งบน main thread และ background thread จึงทำให้เกิดปัญหาบ่อยครั้ง
- สมาชิกทีมเดิมรู้เรื่องนี้และรับมือได้ดี แต่ในรีวิวของสมาชิกใหม่มักถูกพูดถึงอยู่เรื่อยๆ
- เวลาพลาดขึ้นมาก็จะดู crash report แล้วเติม
DispatchQueue.main.async เข้าไป
- เพื่อแก้ปัญหานี้ จึงอัปเดตชั้น subscription ให้เรียก subscriber บน main thread เสมอ ใช้เวลาแค่ 10 นาที
- ลบทั้งคลาสของ crash ออกไปได้ และลดภาระทางความคิดลงเล็กน้อย
- ใครก็ตามถ้าได้คิดสักไม่กี่นาทีก็คงเห็นว่าเป็นปัญหาที่ชัดเจน
- แต่เพราะไม่มีจังหวะตามธรรมชาติที่จะเข้าไปแก้ ปัญหาแบบนี้จึงอยู่มานานอย่างน่าแปลก
- กล่าวคือ ถ้าอยู่กับทีมมานาน ปัญหาแบบนี้มักกลืนหายไปเป็นฉากหลัง
- ต้องเปลี่ยนวิธีคิด
- ควรเตือนตัวเองบ้างเป็นครั้งคราวว่า เมื่อมีปัญหาแบบนี้ เราสามารถทำให้ชีวิตของตัวเองและทีมง่ายขึ้นได้
หาสมดุลระหว่างคุณภาพกับความเร็ว
- ระหว่างความเร็วในการลงมือทำกับความมั่นใจในความถูกต้อง มี trade-off อยู่เสมอ
- ควรถามตัวเองว่า ในสถานการณ์ตอนนี้ การปล่อยบั๊กออกไปพอรับได้แค่ไหน
- ถ้าคำตอบของคำถามนี้ไม่ส่งผลต่อวิธีทำงาน แปลว่าเรายึดติดแข็งเกินไป
- งานแรกที่ทำเป็นโปรเจกต์ประมวลผลข้อมูล ซึ่งมีระบบที่ดีสำหรับ reprocess ข้อมูลย้อนหลัง
- ผลกระทบจากการปล่อยบั๊กจึงน้อยมาก ในสภาพแวดล้อมแบบนี้สามารถพึ่งพา guardrail ได้ระดับหนึ่งและเดินให้เร็วขึ้นได้
- การทำ test coverage 100% หรือกระบวนการ QA ขนาดใหญ่มีแต่ทำให้พัฒนาช้าลง
- งานที่สองเป็นผลิตภัณฑ์ที่มีคนใช้หลายสิบล้านคน จัดการทั้งข้อมูลการเงินมูลค่าสูงและข้อมูลระบุตัวบุคคล บั๊กจึงร้ายแรงมาก
- แม้แต่บั๊กเล็กๆ ก็ต้องมี postmortem
- ฟีเจอร์ออกช้ามาก แต่คิดว่าปีนั้นปล่อยบั๊กออกไป 0 ตัว
- ในกรณีส่วนใหญ่ เราไม่ได้อยู่ในสถานการณ์แบบบริษัทที่สอง
- ถ้าบั๊กไม่ถึงขั้นร้ายแรงมาก (เช่น 99% ของเว็บแอป) ปล่อยให้เร็วแล้วแก้ให้เร็วจะดีกว่า
- ไปได้ไกลกว่าการเสียเวลาทำให้ฟีเจอร์สมบูรณ์แบบตั้งแต่แรก
เวลาที่ใช้ลับคมเลื่อย แทบจะคุ้มค่าเสมอ
- การใช้เครื่องมือให้คล่องเป็นเรื่องสำคัญ
- ควรเขียนโค้ดให้เร็ว รู้คีย์ลัดหลักๆ และชำนาญทั้งระบบปฏิบัติการกับ shell
- คุณจะได้ใช้การ rename, ไปยัง type definition, หา references และอื่นๆ บ่อยมาก
- ควรรู้คีย์ลัดสำคัญของ editor ทั้งหมด และพิมพ์ได้อย่างมั่นใจและรวดเร็ว
- การใช้ browser dev tools อย่างมีประสิทธิภาพก็สำคัญเช่นกัน
- การเลือกเครื่องมือให้ดีและใช้มันอย่างชำนาญเป็นข้อได้เปรียบอย่างมาก
- หนึ่งในสัญญาณเชิงบวกที่ใหญ่ที่สุดที่เห็นในวิศวกรใหม่ คือความสนใจในการเลือกเครื่องมือและการใช้งานอย่างคล่องแคล่ว
ถ้าอธิบายได้ไม่ชัดว่ามันยากตรงไหน นั่นก็น่าจะเป็นความซับซ้อนที่เกิดขึ้นโดยบังเอิญ และปัญหานี้ก็คุ้มค่าที่จะจัดการ
- ผู้จัดการที่ชอบที่สุดคนหนึ่งมีนิสัยกดดันต่อเนื่องทุกครั้งที่ผมบอกว่างาน implement ยาก
- บ่อยครั้งคำตอบของเขาจะประมาณว่า "มันก็แค่ส่ง X ตอนทำ Y ไม่ใช่เหรอ" หรือ "มันก็เหมือน Z ที่เราทำเมื่อไม่กี่เดือนก่อนหรือเปล่า"
- เป็นการแย้งในระดับที่สูงมาก ไม่ใช่ระดับฟังก์ชันและคลาสจริงๆ ที่ผมกำลังอธิบาย
- มุมมองทั่วไปมักคิดว่าการที่ผู้จัดการมาทำให้ง่ายแบบนี้เป็นเรื่องน่ารำคาญเฉยๆ
- แต่ที่น่าแปลกคือ ในสัดส่วนที่สูงมาก พอเขาถามต่อไปเรื่อยๆ ผมกลับตระหนักได้ว่าความซับซ้อนส่วนใหญ่ที่กำลังอธิบายนั้นเป็น accidental complexity
- และพอแก้มันก่อน ปัญหาก็กลายเป็นเรื่องเล็กอย่างที่ผู้จัดการพูดจริงๆ
- วิธีคิดแบบนี้ยังมีแนวโน้มจะทำให้การเปลี่ยนแปลงในอนาคตง่ายขึ้นด้วย
พยายามแก้บั๊กให้ลึกลงไปอีกชั้น
- แทนที่จะแก้บั๊กแค่ผิวหน้า สิ่งสำคัญคือการหาต้นตอและแก้ที่สาเหตุจริง
- เมื่อมี React component ใน dashboard ที่ใช้วัตถุ
User จากสถานะของผู้ใช้ที่ล็อกอินอยู่ในปัจจุบัน
- มี bug report จาก Sentry ว่าระหว่าง render นั้น
user เป็น null
- เราอาจเติม
if (!user) return null แบบเร็วๆ หรือ
- ถ้าสืบเพิ่มอีกหน่อย จะพบว่าฟังก์ชัน logout ทำ state update สองอย่างที่แยกจากกัน
- อย่างแรกคือตั้งผู้ใช้เป็น
null และอย่างที่สองคือ redirect ไปหน้าแรก
- ถ้าสลับลำดับของสองอย่างนี้ ก็จะไม่มี component ไหนเจอบั๊กนี้อีกเลย
- เพราะภายใน dashboard วัตถุผู้ใช้จะไม่เป็น
null เด็ดขาด
- ถ้ายังแก้บั๊กแบบประเภทแรกไปเรื่อยๆ ระบบก็จะเละ
แต่ถ้าแก้แบบประเภทที่สองไปเรื่อยๆ คุณจะได้ทั้งระบบที่สะอาดและความเข้าใจเชิงลึกเรื่อง invariant
อย่าประเมินค่าต่ำเกินไปของการขุดดู history เพื่อสืบสวนบั๊ก
- ผมค่อนข้างเก่งในการ debug ปัญหาแปลกๆ ด้วยเครื่องมือทั่วไปอย่าง
println และ debugger
- เลยไม่ค่อยเปิดดู git มากนักเพื่อไล่ประวัติของบั๊ก แต่สำหรับบั๊กบางประเภท เรื่องนี้สำคัญมาก
- เมื่อไม่นานมานี้ ดูเหมือน server จะมี memory leak ต่อเนื่อง ถูก OOM kill แล้วรีสตาร์ตซ้ำๆ
- ตัดสาเหตุที่น่าจะเป็นไปได้ออกไปหมดแล้ว และก็ reproduce บนเครื่อง local ไม่ได้
- มันให้ความรู้สึกเหมือนปิดตาปา dart
- พอไปดู commit history ก็พบว่ามันเริ่มเกิดหลังจากเพิ่มการรองรับ Play Store billing
- ทั้งที่มันดูเป็นแค่ HTTP request ไม่กี่ตัว เป็นจุดที่คงไม่คิดจะไปดูแม้อีกเป็นล้านปี
- สุดท้ายพบว่า หลัง access token ตัวแรกหมดอายุ มันเข้า infinite loop ในการดึง access token
- แต่ละ request อาจเพิ่มหน่วยความจำแค่ราว 1kB แต่เมื่อ retry ทุก 10ms บนหลาย thread มันก็สะสมเร็วมาก
- ปกติเรื่องแบบนี้คงทำให้ stack overflow แต่ในที่นี้ใช้ async recursion ของ Rust จึงไม่เกิด stack overflow
- เรื่องนี้คงไม่มีวันนึกออกเอง แต่พอถูกพาไปดูโค้ดเฉพาะจุดที่ชัดเจนว่าทำให้เกิดปัญหา ทฤษฎีก็ผุดขึ้นมาทันที
- ไม่มีหลักตายตัวว่าควรใช้วิธีนี้เมื่อไร
- มันอาศัยสัญชาตญาณ คือความรู้สึก "เอ๊ะ?" อีกแบบหนึ่งจาก bug report ที่กระตุ้นให้เริ่มการสืบสวนแนวนี้
- เมื่อเวลาผ่านไป สัญชาตญาณนี้พัฒนาได้ แต่แค่รู้ว่าบางครั้งมันมีค่ามากก็เพียงพอแล้ว
- ถ้าปัญหาเหมาะสม ลองใช้
git bisect
- เมื่อคุณมี commit หนึ่งที่รู้ว่าเสีย และอีก commit หนึ่งที่รู้ว่าดี
โค้ดที่แย่ให้ feedback แต่โค้ดที่สมบูรณ์แบบไม่ให้ ดังนั้นควรพลาดไปในทางที่เขียนโค้ดแย่
- การเขียนโค้ดที่แย่มากๆ นั้นง่ายสุดๆ
- แต่การเขียนโค้ดที่ทำตาม best practice ทุกข้อแบบสมบูรณ์เป๊ะก็ง่ายเหมือนกัน
- ผ่านทั้ง unit, integration, fuzz, mutation test ครบหมด แต่ startup ก็คงเงินหมดก่อน
- ส่วนใหญ่ของการเขียนโปรแกรมคือการหาสมดุล
- ถ้าพลาดไปในทางเขียนโค้ดให้เร็ว...
- บางครั้งคุณก็จะเดือดร้อนเพราะ technical debt ที่ไม่ดี
- คุณจะได้เรียนรู้ว่า "ต้องเพิ่ม test ที่ดีให้ data processing"
- เพราะหลายครั้งการมาแก้ทีหลังแทบเป็นไปไม่ได้
- และจะได้เรียนรู้ด้วยว่า "ต้องคิดเรื่องการออกแบบตารางให้ดีจริงๆ"
- เพราะการเปลี่ยนโดยไม่ให้เกิด downtime อาจยากมาก
- ถ้าพลาดไปในทางเขียนโค้ดให้สมบูรณ์แบบ...
- คุณจะไม่ได้ feedback อะไรเลย
- ทุกอย่างจะช้าไปหมดโดยทั่วไป
- คุณจะไม่รู้ว่าใช้เวลากับตรงไหนได้คุ้ม และตรงไหนกำลังเสียเวลา
- กลไก feedback เป็นสิ่งจำเป็นต่อการเรียนรู้ แต่คุณกลับไม่ได้รับมัน
- ขอให้ชัดก่อนว่าหมายถึงโค้ด "แย่" แบบไหน
- ไม่ได้หมายถึง "จำ syntax สร้าง hashmap ไม่ได้ เลยใช้ inner loop สองรอบ"
- แต่หมายถึงแบบนี้:
- แทนที่จะ rewrite การเก็บข้อมูลเพื่อทำให้ไม่สามารถแสดงสถานะบางแบบได้ ก็เพิ่ม assertion ของ invariant ไว้ตาม checkpoint สำคัญไม่กี่จุด
- server model นี้ตรงกับ DTO ที่จะเขียนทุกอย่างอยู่แล้ว ก็ serialize ตรงๆ ไปก่อน แทนที่จะเขียน boilerplate ทั้งหมด ค่อยทำ DTO ภายหลังเมื่อจำเป็น
- component พวกนี้เล็กน้อยและถึงมีบั๊กก็ไม่ร้ายแรง จึงข้ามการเขียน test ไป
ทำให้การ debug ง่ายขึ้น
- ตลอดหลายปีที่ผ่านมา ผมได้เก็บเทคนิคเล็กๆ มากมายที่ทำให้ซอฟต์แวร์ debug ได้ง่ายขึ้น
- ถ้าไม่ลงทุนทำให้ debug ง่ายขึ้น เมื่อซอฟต์แวร์ซับซ้อนขึ้นเรื่อยๆ คุณจะต้องเสียเวลามหาศาลในการ debug แต่ละ issue
- คุณจะเริ่มกลัวการเปลี่ยนแปลง เพราะแค่ไล่บั๊กใหม่ไม่กี่ตัวก็อาจกินเวลาเป็นสัปดาห์
- คอยสังเกตว่าเวลาที่ใช้ debug นั้นหมดไปกับการ setup, การ reproduce, และการเก็บกวาดหลังทำงานมากแค่ไหน
- ถ้าเกิน 50% ก็ควรมองหาวิธีทำให้ง่ายขึ้น แม้ครั้งนี้จะใช้เวลานานขึ้นอีกนิดก็ตาม
- ถ้าอย่างอื่นเท่ากัน การแก้บั๊กควรง่ายขึ้นเรื่อยๆ ตามเวลา
เวลาทำงานเป็นทีม ให้ถามคำถามเสมอ
- มันมีสเปกตรัมตั้งแต่ "พยายามหาคำตอบทุกอย่างด้วยตัวเอง" ไปจนถึง "คอยกวนเพื่อนร่วมงานด้วยคำถามเล็กๆ น้อยๆ"
- ผมคิดว่าคนช่วงต้นอาชีพส่วนใหญ่มักเอนเอียงไปทางแบบแรกมากเกินไป
- รอบตัวคุณจะมีคนที่อยู่กับ codebase นานกว่า รู้เทคโนโลยี X ดีกว่า รู้จักผลิตภัณฑ์ดีกว่า หรือแค่เป็นวิศวกรที่มีประสบการณ์มากกว่าเสมอ
- ช่วง 6 เดือนแรกในที่ไหนสักแห่ง คุณมักเสียเวลาเป็นชั่วโมงเพื่อหาคำตอบบางอย่าง ทั้งที่จริงอาจได้คำตอบในไม่กี่นาที
- จงถามคำถาม การถามจะน่ารำคาญสำหรับใครสักคนก็ต่อเมื่อชัดเจนว่าคุณหาคำตอบเองได้ภายในไม่กี่นาทีเท่านั้น
วงจรการ deploy สำคัญมาก ควรคิดอย่างรอบคอบว่าจะ deploy ให้เร็วและบ่อยได้อย่างไร
- startup มี runway จำกัด และโปรเจกต์ก็มี deadline
- เมื่อคุณลาออกมาเป็นอิสระ เงินเก็บก็อยู่ได้เพียงไม่กี่เดือน
- ในอุดมคติ ความเร็วของโปรเจกต์ควรเพิ่มแบบทบต้นตามเวลา จน deploy ฟีเจอร์ได้เร็วกว่าที่คิดไว้มาก
- การ deploy ให้เร็วต้องอาศัยหลายอย่าง
- ระบบที่ไม่เปราะบางต่อบั๊ก
- turnaround time ที่รวดเร็วระหว่างทีม
- ความกล้าตัด 10% ของฟีเจอร์ใหม่ออก (ส่วนที่กินเวลา engineering 50%) และวิจารณญาณที่จะรู้ว่าส่วนนั้นคืออะไร
- pattern ที่สม่ำเสมอและนำกลับมาใช้ซ้ำได้ สำหรับประกอบเป็นหน้าจอ/ฟีเจอร์/endpoint ใหม่
- การ deploy ที่เร็วและง่าย
- กระบวนการที่ไม่ทำให้ช้าลง (test ที่ไม่เสถียร, CI ที่ช้า, linter ที่จุกจิก, PR review ที่ช้า, JIRA ที่ถูกปฏิบัติราวกับศาสนา ฯลฯ)
- และอีกเป็นล้านอย่าง
- การ deploy ช้าควรต้องมี postmortem พอๆ กับการทำ production พัง
- แม้อุตสาหกรรมของเราจะไม่ได้เดินแบบนั้น แต่นั่นก็ไม่ได้แปลว่าในระดับบุคคลเราจะยึดดาวเหนือเรื่องการ deploy ให้เร็วไม่ได้
6 ความคิดเห็น
"ยิงตัวเองที่เท้า" = หมายถึงการผูกมัดตัวเองจนเข้าตาจน
ใช่ไหม
ถ้าโค้ดที่มีปัญหา (ปืนที่พัง) ทำให้เกิดปัญหา (ยิงเท้าตัวเอง) ก็หมายความว่าควรซ่อมปืนนั้นเสีย
ช็อกเหมือนมีคนยกสิ่งที่อยู่ในหัวผมออกมาวางตรงหน้าเลย ตกใจมาก..
อ่านได้ดีมากครับ!!
อ่านอย่างเพลิดเพลินครับ
แม้จะไม่ใช่นักพัฒนา แต่ก็มีหลายส่วนที่รู้สึกร่วมได้เหมือนกัน