69 คะแนน โดย xguru 2024-07-15 | 6 ความคิดเห็น | แชร์ทาง WhatsApp

ถ้ายังยิงเท้าตัวเองซ้ำๆ อยู่ ก็ไปซ่อมปืนซะ

  • ในทีมมักมีจุดที่พลาดเกี่ยวกับระบบกันบ่อย แต่หลายครั้งกลับไม่ค่อยคิดว่าจะลดความผิดพลาดนั้นอย่างไร
  • ในกรณีแบบนี้ สิ่งสำคัญคือการปรับปรุงระบบเพื่อลดความผิดพลาด
  • ประสบการณ์:
    • ตอนพัฒนา 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 ความคิดเห็น

 
carnoxen 2024-07-19

"ยิงตัวเองที่เท้า" = หมายถึงการผูกมัดตัวเองจนเข้าตาจน
ใช่ไหม

 
yunghn 2024-07-25

ถ้าโค้ดที่มีปัญหา (ปืนที่พัง) ทำให้เกิดปัญหา (ยิงเท้าตัวเอง) ก็หมายความว่าควรซ่อมปืนนั้นเสีย

 
gargoyle92 2024-07-16

ช็อกเหมือนมีคนยกสิ่งที่อยู่ในหัวผมออกมาวางตรงหน้าเลย ตกใจมาก..

 
cbbatte 2024-07-16

อ่านได้ดีมากครับ!!

 
hannah0su 2024-07-15

อ่านอย่างเพลิดเพลินครับ

 
arfwene 2024-07-15

แม้จะไม่ใช่นักพัฒนา แต่ก็มีหลายส่วนที่รู้สึกร่วมได้เหมือนกัน