• สรุปกับดักที่ไม่เป็นไปตามสัญชาตญาณที่นักพัฒนามักตกหลุมพราง พร้อมอธิบายสาเหตุของบั๊กที่เกิดขึ้นได้บ่อย
  • ครอบคลุมปัญหาที่พบได้บ่อยในเทคโนโลยีหลากหลายด้าน เช่น HTML, CSS, Unicode/การเข้ารหัสข้อความ, เลขทศนิยมลอยตัว, เวลา
  • เน้นว่าความเข้าใจผิดหรือข้อผิดพลาดอาจเกิดขึ้นได้จาก ความแตกต่างเล็กน้อยของไวยากรณ์และพฤติกรรมการทำงาน ในแต่ละภาษาและเฟรมเวิร์ก
  • อธิบายด้วยตัวอย่างถึงกับดักที่อาจเกิดขึ้นในสภาพแวดล้อมใช้งานจริงในโดเมนหลักของฝั่งแบ็กเอนด์ เช่น concurrency, networking, database
  • แนะนำสถานการณ์ปัญหา วิธีแก้ไข และแนวทางปรับปรุงพฤติกรรมที่ไม่คาดคิดผ่านตัวอย่างและลิงก์อ้างอิงที่หลากหลาย

HTML และ CSS

  • ค่าเริ่มต้นของ min-width ใน Flexbox/Grid

    • min-width มีค่าเริ่มต้นเป็น auto
    • min-width: auto จะถูกกำหนดตามขนาดของคอนเทนต์ และมีลำดับความสำคัญเหนือ flex-shrink, overflow: hidden, width: 0, max-width: 100%
    • แนะนำให้ระบุ min-width: 0 อย่างชัดเจน
  • ความแตกต่างระหว่างแนวนอนและแนวตั้งใน CSS

    • width: auto จะพยายามเติมพื้นที่ของพาเรนต์ ส่วน height: auto จะพอดีกับคอนเทนต์
    • width: auto ขององค์ประกอบแบบ inline, inline-block, float จะไม่ขยายออก
    • margin: 0 auto ใช้จัดกึ่งกลางแนวนอน ส่วน margin: auto 0 ไม่สามารถจัดกึ่งกลางแนวตั้งได้ (แต่ใน flex-direction: column สามารถจัดกึ่งกลางแนวตั้งได้)
    • การรวม margin จะเกิดขึ้นเฉพาะในแนวตั้งเท่านั้น
    • หากเปลี่ยนทิศทางเลย์เอาต์ เช่น writing-mode: vertical-rl พฤติกรรมก็จะกลับด้านตามไปด้วย
  • Block Formatting Context (BFC)

    • สร้าง BFC ได้ด้วย display: flow-root (นอกจากนี้ overflow: hidden/auto/scroll, display: table ฯลฯ ก็ทำได้ แต่มีผลข้างเคียง)
    • ป้องกันปัญหา margin ของ sibling ที่ติดกันในแนวตั้งซ้อนทับกัน หรือ margin ของลูกทะลุออกนอกพาเรนต์ได้ด้วย BFC
    • หากพาเรนต์มีเพียงลูกที่เป็น float ความสูงอาจยุบเหลือ 0 → แก้ได้ด้วย BFC
    • หากมี border หรือ padding จะไม่เกิดการรวม margin
  • Stacking Context

    • เงื่อนไขที่ทำให้เกิด stacking context ใหม่
      • คุณสมบัติการเรนเดอร์ เช่น transform, filter, perspective, mask, opacity
      • position: fixed หรือ sticky
      • กำหนด z-index + กำหนดตำแหน่ง absolute/relative
      • กำหนด z-index + เป็นองค์ประกอบภายใน flexbox/grid
      • isolation: isolate
    • คุณลักษณะ
      • z-index มีผลเฉพาะภายใน stacking context นั้นเท่านั้น
      • พิกัดของ position: absolute/fixed อ้างอิงจาก ancestor ที่ถูกกำหนดตำแหน่งไว้ซึ่งอยู่ใกล้ที่สุด
      • sticky จะไม่ทำงานข้าม stacking context
      • แม้เป็น overflow: visible ก็ยังอาจถูกตัดโดย stacking context
      • background-attachment: fixed จะถูกจัดวางโดยอิงตาม stacking context
  • หน่วย viewport

    • ในเบราว์เซอร์มือถือ เมื่อแถบที่อยู่/แถบนำทางหายไปจากหน้าจอขณะเลื่อน ค่า 100vh จะเปลี่ยนไป
    • วิธีแก้สมัยใหม่: ใช้ 100dvh
  • เกณฑ์อ้างอิงของ Absolute Position

    • position: absolute ไม่ได้อ้างอิงพาเรนต์เสมอไป แต่จะอ้างอิง relative/absolute หรือ ancestor ที่เป็น stacking context ที่อยู่ใกล้ที่สุด
  • พฤติกรรมของ Blur

    • backdrop-filter: blur จะไม่พิจารณาองค์ประกอบรอบข้าง
  • การทำให้ Float ไม่มีผล

    • หากพาเรนต์เป็น flex หรือ grid ค่า float ของลูกจะไม่มีผล
  • หน่วยเปอร์เซ็นต์ของ width/height

    • หากขนาดของพาเรนต์ยังไม่ได้ถูกกำหนดล่วงหน้า จะไม่ทำงาน (เพื่อหลีกเลี่ยงการอ้างอิงแบบวนซ้ำ)
  • คุณสมบัติขององค์ประกอบ Inline

    • display: inline จะไม่สนใจ width, height, margin-top, margin-bottom
  • การจัดการ Whitespace

    • โดยปกติการขึ้นบรรทัดใหม่ใน HTML จะถูกมองเป็นช่องว่าง และช่องว่างที่ต่อเนื่องกันจะถูกรวมเหลือหนึ่งช่อง
    • <pre> ป้องกันการยุบช่องว่าง แต่มีพฤติกรรมเฉพาะที่จุดเริ่มต้น/สิ้นสุด
    • โดยทั่วไปช่องว่างต้น/ท้ายคอนเทนต์จะถูกละเลย แต่ <a> เป็นข้อยกเว้น
    • ช่องว่าง/การขึ้นบรรทัดใหม่ระหว่าง inline-block จะแสดงเป็นช่องว่างจริง (แต่จะไม่เกิดใน flex/grid)
  • text-align

    • ใช้ได้กับการจัดข้อความและองค์ประกอบ inline แต่ใช้จัดองค์ประกอบ block ไม่ได้
  • box-sizing

    • ค่าเริ่มต้นคือ content-box → ไม่รวม padding/border
    • เมื่อกำหนด width: 100% + padding อาจล้นพื้นที่พาเรนต์ได้
    • วิธีแก้: box-sizing: border-box
  • Cumulative Layout Shift

    • หากไม่กำหนดแอตทริบิวต์ width และ height ให้ <img> อาจเกิดอาการเลย์เอาต์สั่นจากการโหลดภาพล่าช้า
    • แนะนำ: กำหนดแอตทริบิวต์เพื่อป้องกัน CLS
  • คำขอเครือข่ายสำหรับการดาวน์โหลดไฟล์ใน Chrome

    • จะไม่แสดงในแผง Network ของ DevTools (เพราะถูกจัดการในแท็บอื่น)
    • หากต้องการวิเคราะห์ ให้ใช้ chrome://net-export/
  • ปัญหาการพาร์ส JavaScript ใน HTML

    • ในกรณีอย่าง <script>console.log('</script>')</script> จะมอง </script> ตัวแรกเป็นแท็กปิด
    • อ้างอิง: Safe JSON in script tags

Unicode และการเข้ารหัสข้อความ

  • Code point และ grapheme cluster

    • grapheme cluster คือ “หน่วยอักขระ” ใน GUI
    • อักขระ ASCII ที่มองเห็นได้โดยทั่วไปมี 1 code point = 1 grapheme cluster
    • อีโมจิอาจเป็น grapheme cluster หนึ่งตัวที่ประกอบด้วยหลาย code point
    • ใน UTF-8 code point หนึ่งตัวใช้ 1~4 ไบต์ ดังนั้นจำนวนไบต์กับจำนวน code point จึงไม่เท่ากันเสมอ
    • ใน UTF-16 code point หนึ่งตัวใช้ 2 ไบต์หรือ 4 ไบต์ (surrogate pair)
    • มาตรฐานไม่ได้จำกัดจำนวน code point ภายใน cluster แต่ในการใช้งานจริงมักมีการจำกัดเพื่อเหตุผลด้านประสิทธิภาพ
  • ความแตกต่างของการทำงานกับสตริงในแต่ละภาษา

    • Rust: ใช้ UTF-8 สำหรับสตริงภายใน, len() คือจำนวนไบต์, ไม่สามารถทำ indexing โดยตรงได้, chars().count() คือจำนวน code point, ตรวจสอบความถูกต้องของ UTF-8 อย่างเข้มงวด
    • Golang: สตริงเป็นอาร์เรย์ของไบต์ในทางปฏิบัติ, ความยาวและ indexing เป็นระดับไบต์, โดยทั่วไปใช้ UTF-8
    • Java, C#, JS: อิงกับ UTF-16, วัดความยาวเป็นหน่วยละ 2 ไบต์, indexing ก็เป็นหน่วย 2 ไบต์เช่นกัน, มี surrogate pair
    • Python: len() คืนค่าจำนวน code point, การ indexing จะคืนสตริงที่มี code point เดียว
    • C++: std::string ไม่มีข้อจำกัดเรื่อง encoding, ทำงานเหมือนเวกเตอร์ของไบต์, ความยาว/การ indexing เป็นระดับไบต์
    • ในบรรดาภาษาที่กล่าวถึง ไม่มีภาษาใดวัดความยาวหรือ indexing ในระดับ grapheme cluster
  • BOM (Byte Order Mark)

    • ไฟล์ข้อความบางชนิดมี BOM เช่น EF BB BF → ระบุว่าเป็นการเข้ารหัสแบบ UTF-8
    • มักใช้ใน Windows และซอฟต์แวร์ที่ไม่ใช่ Windows อาจจัดการ BOM ไม่ได้
  • ข้อควรระวังอื่น ๆ

    • เมื่อแปลงข้อมูลไบนารีเป็นสตริง ส่วนที่ไม่ถูกต้องจะถูกแทนที่ด้วย � (U+FFFD)
    • มี Confusable characters อยู่ (อักขระที่ดูคล้ายกัน)
    • Normalization: ตัวอย่างเช่น é สามารถแทนได้ด้วย U+00E9 (โค้ดพอยต์เดียว) หรือ U+0065+U+0301 (สองโค้ดพอยต์)
    • มี Zero-width characters และ Invisible characters
    • ความแตกต่างของการขึ้นบรรทัดใหม่: Windows ใช้ CRLF \r\n, Linux/MacOS ใช้ LF \n
    • Han unification: อักขระที่มีรูปร่างต่างกันเล็กน้อยในแต่ละภาษาจะใช้โค้ดพอยต์เดียวกัน
      • ฟอนต์จะเรนเดอร์ให้เหมาะสมโดยรวมรูปแบบเฉพาะของแต่ละภาษาไว้
      • เมื่อต้องทำ internationalization ต้องเลือกฟอนต์รูปแบบที่ถูกต้อง

จำนวนทศนิยมลอยตัว (Floating point)

  • คุณสมบัติของ NaN

    • NaN จะไม่เท่ากับค่าใด ๆ เลยแม้แต่ตัวมันเอง (NaN == NaN จะเป็น false เสมอ)
    • NaN != NaN จะเป็น true เสมอ
    • ผลลัพธ์ของการคำนวณที่มี NaN มักจะแพร่ต่อเป็น NaN
  • ค่าพิเศษ

    • มีทั้ง +Inf และ -Inf ซึ่งต่างจาก NaN
    • -0.0 เป็นค่าที่แยกจาก +0.0
      • ในการเปรียบเทียบจะถือว่าเท่ากัน แต่ในการคำนวณบางอย่างจะทำงานต่างกัน
      • ตัวอย่าง: 1.0 / +0.0 == +Inf, 1.0 / -0.0 == -Inf
  • ความเข้ากันได้กับ JSON

    • มาตรฐาน JSON ไม่อนุญาต NaN และ Inf
      • JS JSON.stringify จะแปลง NaN, Inf เป็น null
      • Python json.dumps(...) จะพิมพ์ NaN, Infinity ออกมาตามเดิม (ผิดมาตรฐาน)
        • หากใช้ตัวเลือก allow_nan=False แล้วมี NaN/Inf จะเกิด ValueError
      • Golang json.Marshal จะคืนค่า error หากมี NaN/Inf อยู่
  • ปัญหาด้านความแม่นยำ

    • การเปรียบเทียบจำนวนทศนิยมลอยตัวโดยตรงอาจล้มเหลว → แนะนำรูปแบบ abs(a - b) < ε
    • JS จัดการตัวเลขทั้งหมดเป็นจำนวนทศนิยมลอยตัว
      • ช่วงจำนวนเต็มที่ปลอดภัยคือ -(2^53 - 1) ~ 2^53 - 1
      • หากเกินช่วงนี้ การแทนค่าจำนวนเต็มจะไม่แม่นยำ
      • แนะนำให้ใช้ BigInt กับจำนวนเต็มขนาดใหญ่
      • หาก JSON มีจำนวนเต็มที่เกินช่วงปลอดภัย ค่าผลลัพธ์จาก JSON.parse อาจไม่แม่นยำ
      • timestamp ระดับมิลลิวินาทีปลอดภัยถึงปี 287,396 แต่ระดับนาโนวินาทีจะมีปัญหา
  • กฎของการคำนวณที่ใช้ไม่ได้เสมอไป

    • เนื่องจากการสูญเสียความแม่นยำตามลำดับการคำนวณ กฎการเปลี่ยนหมู่และกฎการแจกแจงจึงไม่เป็นจริงอย่างเคร่งครัด
    • การคำนวณแบบขนาน (เช่น การคูณเมทริกซ์, การหาผลรวม) อาจสร้างผลลัพธ์ที่ไม่กำหนดแน่นอน
  • ประสิทธิภาพ

    • การหารช้ากว่าการคูณมาก
    • หากต้องหารด้วยจำนวนเดียวกันหลายครั้ง สามารถ optimize ได้ด้วยการหาค่าส่วนกลับก่อนแล้วคูณแทน
  • ความแตกต่างตามฮาร์ดแวร์

    • การรองรับ FMA (Fused Multiply-Add): ฮาร์ดแวร์บางตัวคำนวณค่ากลางด้วยความแม่นยำสูงกว่า
    • การจัดการช่วง Subnormal: ฮาร์ดแวร์รุ่นใหม่รองรับ แต่บางรุ่นเก่าอาจปัดเป็น 0
    • ความแตกต่างของโหมดการปัดเศษ
      • มีทั้ง RNTE (ปัดไปยังเลขคู่ที่ใกล้ที่สุด), RTZ (ตัดเข้าหา 0) เป็นต้น
      • x86/ARM สามารถตั้งค่าได้เป็นสถานะแบบ thread-local ที่เปลี่ยนแปลงได้
      • GPU มีโหมดการปัดเศษต่างกันตามระดับคำสั่ง
    • ความแตกต่างของการทำงานของฟังก์ชันคณิตศาสตร์ เช่น ฟังก์ชันตรีโกณมิติ, ลอการิทึม
    • x86 มี legacy 80-bit FPU และ per-core rounding mode → ไม่แนะนำให้ใช้
    • นอกจากนี้ยังมีปัจจัยอีกหลายอย่างที่ทำให้ผลลัพธ์ floating point ต่างกันตามฮาร์ดแวร์
  • วิธีเพิ่มความแม่นยำ

    • ออกแบบกราฟการคำนวณให้ตื้นลง (ลดโครงสร้างที่เป็นการคูณต่อเนื่อง)
    • หลีกเลี่ยงกรณีที่ค่ากลางมีขนาดใหญ่มากหรือเล็กมาก
    • ใช้การคำนวณระดับฮาร์ดแวร์อย่าง FMA

เวลา (Time)

  • Leap second

    • Unix timestamp จะไม่สนใจ leap second
    • เมื่อเกิด leap second เวลาบริเวณใกล้เคียงอาจยืดหรือหดลง (Leap smear)
  • เขตเวลา (Time zone)

    • UTC และ Unix timestamp ใช้ร่วมกันทั่วโลก
    • เวลาที่มนุษย์อ่านได้ขึ้นอยู่กับเขตเวลาของแต่ละพื้นที่
    • แนะนำให้เก็บ timestamp ใน DB แล้วแปลงใน UI
  • เวลาออมแสง (DST)

    • บางพื้นที่มีการปรับนาฬิกา 1 ชั่วโมงในช่วงฤดูร้อน
  • การซิงก์ NTP

    • ระหว่างการซิงก์อาจเกิดสถานการณ์ที่เวลา “ย้อนกลับ” ได้
  • การตั้งค่าเขตเวลาของเซิร์ฟเวอร์

    • แนะนำให้ตั้งค่าเซิร์ฟเวอร์เป็น UTC
    • ในระบบแบบกระจาย หากแต่ละโหนดใช้เขตเวลาต่างกันจะเกิดปัญหา
    • หลังเปลี่ยนเขตเวลาของระบบอาจต้องตั้งค่า DB ใหม่หรือรีสตาร์ต
  • นาฬิกาฮาร์ดแวร์ vs นาฬิการะบบ

    • นาฬิกาฮาร์ดแวร์ไม่มีแนวคิดเรื่องเขตเวลา
    • Linux: จัดการนาฬิกาฮาร์ดแวร์เป็น UTC
    • Windows: จัดการนาฬิกาฮาร์ดแวร์เป็นเวลาท้องถิ่น

Java

  • == ใช้เปรียบเทียบ reference ของอ็อบเจ็กต์ ส่วนการเปรียบเทียบเนื้อหาของอ็อบเจ็กต์ต้องใช้ .equals
  • หากไม่ override equals และ hashcode การตัดสินว่าอ็อบเจ็กต์เหมือนกันใน map/set จะอิงตาม reference
  • หากแก้ไขเนื้อหาของอ็อบเจ็กต์ที่เป็น key ของ map หรือสมาชิกของ set การทำงานของคอนเทนเนอร์จะพัง
  • เมธอดที่คืนค่า List<T> อาจคืน mutable ArrayList หรือ immutable Collections.emptyList() แล้วแต่กรณี และหากแก้ไขอย่างหลังจะเกิด UnsupportedOperationException
  • มีกรณีที่เมธอดซึ่งคืนค่า Optional<T> กลับคืน null (ไม่แนะนำ)
  • หากมี return ในบล็อก finally exception ที่เกิดใน try หรือ catch จะถูกเพิกเฉยและใช้ค่าที่ finally คืนแทน
  • มีไลบรารีที่เพิกเฉยต่อ interrupt และกระบวนการ initialize class ที่รวม IO อาจพังเพราะ interrupt ได้
  • exception ของ task ที่ส่งเข้า thread pool ด้วย .submit() จะไม่ถูกพิมพ์ลง log โดยปริยาย และตรวจได้ผ่าน future เท่านั้น; ถ้ามองข้าม future ก็จะตรวจ exception ไม่ได้
    • งาน scheduleAtFixedRate จะหยุดเงียบ ๆ เมื่อเกิด exception
  • หาก numeric literal ขึ้นต้นด้วย 0 จะถูกมองเป็นเลขฐานแปด (0123 → 83)
  • debugger จะเรียก .toString() ของตัวแปรภายในฟังก์ชัน และ toString() ของบางคลาสมีผลข้างเคียง ทำให้พฤติกรรมของโค้ดระหว่างดีบักอาจต่างออกไปได้ (ปิดได้ใน IDE)

Golang

  • append() จะนำหน่วยความจำเดิมกลับมาใช้เมื่อยังมี capacity เหลือ และการ append กับ subslice อาจเขียนทับหน่วยความจำของ parent ได้
  • defer จะทำงานตอนฟังก์ชันคืนค่า ไม่ใช่ตอนจบบล็อกสโคป
  • defer จับ mutable variable ไว้
  • เกี่ยวกับ nil
    • nil slice กับ empty slice ไม่เหมือนกัน
    • string เป็น nil ไม่ได้ มีได้แค่สตริงว่าง
    • nil map อ่านได้แต่เขียนไม่ได้
    • พฤติกรรมพิเศษของ interface nil: ถ้า data pointer เป็น null แต่ type info ไม่เป็น null จะไม่เท่ากับ nil
  • Dead wait: มีกรณีบั๊ก concurrency จริงใน Go
  • มี Timeout หลายประเภท ซึ่งอธิบายรายละเอียดไว้ใน net/http

C/C++

  • หากเก็บพอยน์เตอร์ไปยังสมาชิกของ std::vector แล้ว vector มีการ grow จะเกิดการจัดสรรหน่วยความจำใหม่ ทำให้พอยน์เตอร์ใช้ไม่ได้
  • std::string ที่สร้างจาก literal string อาจเป็นอ็อบเจ็กต์ชั่วคราว ทำให้การเรียก c_str() มีความเสี่ยง
  • หากแก้ไขคอนเทนเนอร์ระหว่างการวนซ้ำ จะทำให้ iterator ใช้ไม่ได้
  • std::remove ไม่ได้ลบจริง แต่เป็นการจัดเรียงสมาชิกใหม่ การลบต้องใช้ erase
  • หาก numeric literal เริ่มต้นด้วย 0 จะถูกตีความเป็นเลขฐาน 8 (0123 → 83)
  • Undefined behavior (UB): ระหว่างกระบวนการ optimize นั้น UB สามารถถูกเปลี่ยนแปลงได้อย่างอิสระ จึงอันตรายหากเขียนโค้ดโดยพึ่งพามัน
    • การเข้าถึงหน่วยความจำที่ยังไม่ได้ initialize เป็น UB
    • การแปลง char* เป็น struct pointer แล้วเข้าถึงก่อนที่อายุการใช้งานของอ็อบเจ็กต์จะเริ่มต้นเป็น UB แนะนำให้ initialize ด้วย memcpy
    • การเข้าถึงหน่วยความจำที่ไม่ถูกต้อง (เช่น null pointer) เป็น UB
    • integer overflow/underflow เป็น UB (ส่วน unsigned สามารถ underflow ต่ำกว่า 0 ได้)
    • Aliasing: หากพอยน์เตอร์คนละชนิดอ้างถึงหน่วยความจำเดียวกัน จะเกิด UB ตามกฎ strict aliasing
      • ข้อยกเว้น: 1) ชนิดที่มีความสัมพันธ์แบบสืบทอด 2) การแปลงเป็น char*, unsigned char*, std::byte* (แต่ไม่รวมการแปลงกลับ)
      • แนะนำให้ใช้ memcpy หรือ std::bit_cast สำหรับการแปลงแบบบังคับ
    • การเข้าถึง unaligned memory เป็น UB
  • การจัดแนวหน่วยความจำ (Memory Alignment)
    • จำนวนเต็ม 64 บิต ต้องอยู่ที่แอดเดรสที่หารด้วย 8 ลงตัว
    • บน ARM การเข้าถึงแบบ unaligned อาจทำให้โปรแกรม crash ได้
    • หากตีความ byte buffer เป็น struct โดยตรง อาจเกิดปัญหา alignment
    • alignment อาจทำให้เกิด struct padding และสิ้นเปลืองหน่วยความจำ
    • คำสั่ง SIMD บางชนิด (เช่น AVX) รองรับเฉพาะข้อมูลที่จัดแนวแล้ว โดยทั่วไปต้องการ alignment 32 ไบต์

Python

  • อาร์กิวเมนต์เริ่มต้นของฟังก์ชันจะไม่ถูกสร้างใหม่ทุกครั้งที่เรียก แต่จะเก็บค่าเริ่มต้นเดิมไว้

SQL Databases

  • การจัดการ Null

    • x = null ใช้งานไม่ได้ ต้องใช้ x is null
    • Null ไม่เท่ากับตัวมันเอง (คล้าย NaN)
    • Unique index อนุญาตให้มี Null ซ้ำได้ (ยกเว้น Microsoft SQL Server)
    • วิธีจัดการ Null ใน select distinct แตกต่างกันไปในแต่ละ DB
    • count(x) และ count(distinct x) จะไม่สนใจแถวที่มีค่า Null
  • พฤติกรรมทั่วไป

    • การแปลงวันที่แบบ implicit อาจขึ้นอยู่กับ timezone
    • join ที่ซับซ้อนร่วมกับ distinct อาจช้ากว่าการใช้ nested query
    • ใน MySQL(InnoDB) หากฟิลด์สตริงไม่ใช่ utf8mb4 จะเกิดข้อผิดพลาดเมื่อแทรกอักขระ UTF-8 แบบ 4 ไบต์
    • MySQL(InnoDB) โดยปกติ ไม่แยกตัวพิมพ์เล็ก-ใหญ่
    • MySQL(InnoDB) อนุญาตการแปลงแบบ implicit: select '123abc' + 1; → 124
    • gap lock ของ MySQL(InnoDB) อาจทำให้เกิด deadlock ได้
    • ใน MySQL(InnoDB) หาก group by ไม่ตรงกับคอลัมน์ใน select จะคืนผลลัพธ์ที่ไม่เป็น deterministic
    • ใน SQLite หากไม่ใช้ strict ประเภทของฟิลด์แทบไม่มีความหมายมากนัก
    • Foreign key อาจก่อให้เกิด lock โดยปริยายและนำไปสู่ deadlock ได้
    • กลไก locking อาจทำให้ repeatable read isolation ของแต่ละ DB ถูกทำลายได้
    • Distributed SQL DB อาจไม่รองรับ locking หรือมีพฤติกรรมเฉพาะตัว (แตกต่างกันไปตาม DB)
  • ประสิทธิภาพ/การปฏิบัติการ

    • ปัญหา N+1 query จะไม่ปรากฏใน slow query log เพราะแต่ละ query รันเร็ว
    • ทรานแซกชันที่รันยาวนานอาจทำให้เกิดปัญหา lock เป็นต้น → แนะนำให้จบทรานแซกชันให้เร็ว
    • กรณีของการ lock ทั้งตาราง
      • ใน MySQL 8.0+ การเพิ่ม unique index/foreign key ส่วนใหญ่ทำพร้อมกันได้
      • MySQL เวอร์ชันเก่าอาจเกิดการ lock ทั้งตาราง
      • หาก mysqldump ไม่มีตัวเลือก --single-transaction จะเกิด read lock ทั้งตาราง
      • ใน PostgreSQL การใช้ create unique index หรือ alter table ... add foreign key จะทำให้เกิด read lock ทั้งตาราง
        • วิธีหลีกเลี่ยง: ใช้ create unique index concurrently
        • สำหรับ foreign key ให้ใช้รูปแบบ ... not valid แล้วตามด้วย validate constraint
  • Range query

    • ช่วงที่ไม่ทับซ้อนกัน:
      • เงื่อนไขแบบตรงไปตรงมา p >= start and p <= end ไม่มีประสิทธิภาพ (แม้จะมี composite index)
      • วิธีที่มีประสิทธิภาพ:
        select *   
        from (select ... from ranges where start <= p order by start desc limit 1)   
        where end >= p  
        
        (ต้องมีเพียง index ของคอลัมน์ start)
    • ช่วงที่อาจทับซ้อนกันได้:
      • ไม่มีประสิทธิภาพเมื่อใช้ B-tree index ทั่วไป
      • แนะนำให้ใช้ spatial index ใน MySQL และ GiST ใน PostgreSQL

Concurrency and Parallelism

  • volatile

    • volatile ไม่สามารถใช้แทน lock ได้ และไม่ได้ให้ atomicity
    • ข้อมูลที่ lock ปกป้องอยู่แล้วไม่จำเป็นต้องใช้ volatile (เพราะ lock รับประกัน memory order)
    • C/C++: volatile ป้องกันได้เพียง optimization บางส่วน และไม่ได้เพิ่ม memory barrier
    • Java: การเข้าถึง volatile ให้ sequentially-consistent ordering (JVM จะใส่ memory barrier หากจำเป็น)
    • C#: การเข้าถึง volatile ให้ release-acquire ordering (CLR จะใส่ memory barrier หากจำเป็น)
    • ช่วยป้องกัน optimization ที่ผิดพลาดซึ่งเกี่ยวข้องกับการ reorder การอ่าน/เขียนหน่วยความจำได้
  • ปัญหา TOCTOU (Time-of-check to time-of-use)

  • การจัดการเงื่อนไขบังคับในชั้นแอปพลิเคชันสำหรับ SQL DB

    • ในกรณีที่บังคับใช้ข้อจำกัดที่ไม่สามารถแสดงด้วย unique index แบบง่ายได้ในแอปพลิเคชัน (เช่น unique ข้ามสองตาราง, unique แบบมีเงื่อนไข, unique ภายในช่วงเวลา):
      • MySQL(InnoDB): ที่ระดับ repeatable read หากทำ select ... for update แล้วค่อย insert และมี index บนคอลัมน์ unique ก็ถือว่าใช้ได้เพราะอาศัย gap lock (แต่ gap lock อาจทำให้เกิด deadlock ภายใต้โหลดสูง → ต้องมี deadlock detection และ retry)
      • PostgreSQL: logic เดียวกันในระดับ repeatable read ยังไม่เพียงพอเมื่อมี concurrency (ปัญหา write skew)
        • วิธีแก้:
          • ใช้ serializable isolation level
          • ใช้ข้อจำกัดของ DB แทนแอปพลิเคชัน
            • unique แบบมีเงื่อนไข → partial unique index
            • unique ข้ามสองตาราง → แทรกข้อมูลที่ซ้ำกันลงในตารางแยก แล้วใส่ unique index
            • การกีดกันภายในช่วงเวลา → range type + exclude constraint
  • Atomic reference counting

    • หากมีหลายเธรดเปลี่ยนตัวนับเดียวกันบ่อย ๆ เช่น Arc, shared_ptr จะทำให้ประสิทธิภาพลดลง
  • Read-write lock

    • บาง implementation ไม่รองรับการอัปเกรดจาก read lock เป็น write lock
    • หากพยายามขอ write lock ขณะยังถือ read lock อยู่ อาจเกิด deadlock ได้

Common in many languages

  • การตรวจสอบ Null/None/nil ที่ตกหล่น เป็นสาเหตุของข้อผิดพลาดที่พบบ่อย
  • หากแก้ไขคอนเทนเนอร์ระหว่างวนลูป อาจเกิด data race ในเธรดเดียว ได้
  • ความผิดพลาดจากการแชร์ข้อมูลแบบ mutable: เช่น ใน Python [[0] * 10] * 10 ไม่ใช่การสร้างอาร์เรย์ 2D ที่ถูกต้อง
  • (low + high) / 2 อาจเกิด overflow → วิธีที่ปลอดภัยคือ low + (high - low) / 2
  • การประเมินแบบ short circuit: a() || b() ถ้า a เป็น true จะไม่รัน b, a() && b() ถ้า a เป็น false จะไม่รัน b
  • ค่าเริ่มต้นของ profiler จะรวมแค่ CPU time เท่านั้น → การรอ DB เป็นต้นจะไม่แสดงใน flamegraph ทำให้ตีความผิดได้
  • dialect ของ regular expression ต่างกันในแต่ละภาษา → regex ที่ทำงานใน JS อาจใช้ใน Java ไม่ได้

Linux and bash

  • หลังย้ายไดเรกทอรีแล้ว pwd จะแสดงพาธเดิม ส่วนพาธจริงคือ pwd -P
  • cmd > file 2>&1 → ทั้ง stdout+stderr ลงไฟล์, cmd 2>&1 > file → มีแค่ stdout ลงไฟล์ ส่วน stderr ยังออกตามเดิม
  • ชื่อไฟล์ แยกตัวพิมพ์เล็ก-ใหญ่ (ต่างจาก Windows)
  • ไฟล์ executable มี ระบบ capability อยู่ด้วย (ตรวจสอบได้ด้วย getcap)
  • ความเสี่ยงจากตัวแปรที่ unset: ถ้า DIR unset แล้วรัน rm -rf $DIR/ อาจกลายเป็น rm -rf / → ป้องกันได้ด้วย set -u
  • การนำค่ามาใช้กับ environment: ถ้าต้องการให้สคริปต์มีผลกับ shell ปัจจุบัน ให้ใช้ source script.sh → ถ้าต้องการให้มีผลถาวรให้เพิ่มใน ~/.bashrc
  • Bash มี การแคชคำสั่ง: ถ้าย้ายไฟล์ใน $PATH อาจเกิด ENOENT → รีเฟรชแคชด้วย hash -r
  • หากใช้ตัวแปรโดยไม่ใส่เครื่องหมายอัญประกาศ บรรทัดใหม่จะถูกมองเป็นช่องว่าง
  • set -e: จะออกจากสคริปต์ทันทีเมื่อเกิดข้อผิดพลาด แต่จะไม่ทำงานภายใน conditional (||, &&, if)
  • K8s livenessProbe ชนกับ debugger: debugger แบบ breakpoint จะหยุดทั้งแอป ทำให้ health check ไม่ตอบสนอง → Pod อาจถูกปิดได้

React

  • แก้ไข state โดยตรง ในโค้ด render
  • ใช้ Hook ภายใน if/loop → ผิดกฎ
  • ลืมใส่ค่าที่จำเป็นใน dependency array ของ useEffect
  • ลืมโค้ด clean up ใน useEffect
  • กับดักของ closure: เก็บ state เก่าไว้จนเกิดบั๊ก
  • เปลี่ยนข้อมูลในตำแหน่งที่ไม่ถูกต้อง → กลายเป็น component ที่ไม่บริสุทธิ์
  • ไม่ใช้ useCallback → ทำให้เกิดการ re-render ที่ไม่จำเป็น
  • หากส่ง ค่าที่ไม่ได้ memoize เข้าไปใน component ที่ memoize ไว้ จะทำให้การ optimize ของ memo ใช้ไม่ได้

Git

  • Rebase คือการเขียนประวัติใหม่

    • หลัง rebase แล้ว push ปกติจะชนกัน → ต้อง force push เท่านั้น
    • ถ้าประวัติของ remote branch เปลี่ยน เวลาทำ pull ก็ควรใช้ --rebase
    • --force-with-lease ในบางกรณีช่วยป้องกันการทับ commit ของนักพัฒนาคนอื่นได้ แต่ถ้า fetch อย่างเดียวแล้วไม่ pull ก็ยังป้องกันไม่ได้
  • ปัญหาเวลา revert merge

    • การ revert merge ให้ผลไม่สมบูรณ์ → ถ้า merge branch เดิมอีกครั้งจะไม่มีการเปลี่ยนแปลง
    • วิธีแก้: revert ตัว revert อีกที หรือใช้วิธีที่สะอาดกว่า (backup → reset → cherry-pick → force push)
  • ข้อควรระวังเกี่ยวกับ GitHub

    • ถ้า commit secret อย่าง API key ไปแล้ว ต่อให้ force push ทับ GitHub ก็ยังมีประวัติเหลืออยู่
    • ถ้า B เป็น fork ของ private repo A แม้ B จะเป็น private แต่ถ้า A กลายเป็น public เนื้อหาใน B ก็จะถูกเปิดเผยด้วย (ลบแล้วก็ยังเข้าถึงได้)
  • git stash pop: ถ้าเกิด conflict stash จะไม่ถูก drop

  • .DS_Store ถูกสร้างอัตโนมัติโดย macOS → แนะนำให้เพิ่ม **/.DS_Store ใน .gitignore

Networking

  • เราเตอร์และไฟร์วอลล์บางตัวจะตัด TCP connection ที่ idle แบบเงียบ ๆ → อาจทำให้ connection pool ของ HTTP client หรือ DB client ใช้งานไม่ได้ → วิธีแก้คือ ตั้งค่า TCP keepalive
  • ผลลัพธ์ของ traceroute เชื่อถือได้ไม่มาก → บางกรณี tcptraceroute อาจมีประโยชน์กว่า
  • TCP slow start เป็นสาเหตุให้ latency เพิ่มขึ้นได้ → แก้ได้ด้วยการปิด tcp_slow_start_after_idle
  • ปัญหา TCP sticky packet: อัลกอริทึม Nagle ทำให้การส่งแพ็กเก็ตล่าช้า → แก้ได้ด้วยการเปิด TCP_NODELAY
  • เมื่อวาง backend ไว้หลัง Nginx ต้องตั้งค่าการ reuse connection → ถ้าไม่ตั้งค่า อาจเชื่อมต่อไม่สำเร็จเพราะพอร์ตภายในไม่พอในสภาวะโหลดสูง
  • โดยปกติ Nginx จะ buffer packet → ทำให้ SSE(EventSource) เกิดความหน่วง
  • มาตรฐาน HTTP ไม่ได้ห้าม body ในคำขอ GET หรือ DELETE → บางระบบใช้งาน body แต่ไลบรารีและเซิร์ฟเวอร์จำนวนมากไม่รองรับ
  • สามารถโฮสต์หลายเว็บไซต์บน IP เดียวได้ → ใช้ HTTP Host header และ SNI ของ TLS ในการแยก → จึงมีเว็บไซต์ที่เข้าแบบ IP ตรง ๆ ไม่ได้
  • CORS: เมื่อขอข้อมูลข้าม origin เบราว์เซอร์จะบล็อกการเข้าถึง response → ฝั่งเซิร์ฟเวอร์ต้องตั้ง header Access-Control-Allow-Origin
    • ถ้ารวมการส่ง cookie ด้วย ต้องมีการตั้งค่าเพิ่มเติม
    • ถ้า frontend และ backend ใช้ โดเมนและพอร์ตเดียวกัน ก็จะไม่มีปัญหา CORS

Other

  • ข้อควรระวังเกี่ยวกับ YAML

    • YAML ไวต่อช่องว่างkey:value ผิด, key: value จึงถูกต้อง
    • รหัสประเทศ NO ถ้าเขียนโดยไม่ใส่เครื่องหมายอัญประกาศ อาจถูกตีความเป็น false
    • ถ้าเขียน Git commit hash โดยไม่ใส่เครื่องหมายอัญประกาศ อาจถูกแปลงเป็นตัวเลขได้
  • ปัญหา CSV ใน Excel

    • เมื่อเปิด CSV ใน Excel จะมีการ แปลงค่าอัตโนมัติ
      • แปลงเป็นวันที่: 1/2, 1-22-Jan
      • แปลงเลขขนาดใหญ่แบบไม่แม่นยำ: 1234567890123456789012345678901234500000
    • สาเหตุคือ Excel จัดการตัวเลขภายในแบบ floating point
    • เคยมีกรณีที่ชื่อยีน SEPT1 ถูกเปลี่ยนผิดพลาด เพราะปัญหานี้

ยังไม่มีความคิดเห็น

ยังไม่มีความคิดเห็น