66 คะแนน โดย GN⁺ 2025-08-18 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • สรุปกับดักที่ไม่เป็นไปตามสัญชาตญาณที่นักพัฒนามักตกหลุมพราง พร้อมอธิบายสาเหตุของบั๊กที่เกิดขึ้นได้บ่อย
  • ครอบคลุมปัญหาที่พบได้บ่อยในเทคโนโลยีหลากหลายด้าน เช่น 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 จะถูกมองเป็นช่องว่าง และช่องว่างที่ต่อเนื่องกันจะถูกรวมเหลือหนึ่งช่อง
    • `` ป้องกันการยุบช่องว่าง แต่มีพฤติกรรมเฉพาะที่จุดเริ่มต้น/สิ้นสุด
    • โดยทั่วไปช่องว่างต้น/ท้ายคอนเทนต์จะถูกละเลย แต่ `` เป็นข้อยกเว้น
    • ช่องว่าง/การขึ้นบรรทัดใหม่ระหว่าง 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 ให้ `` อาจเกิดอาการเลย์เอาต์สั่นจากการโหลดภาพล่าช้า
    • แนะนำ: กำหนดแอตทริบิวต์เพื่อป้องกัน CLS
  • คำขอเครือข่ายสำหรับการดาวน์โหลดไฟล์ใน Chrome

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

    • ในกรณีอย่าง console.log('') จะมอง `` ตัวแรกเป็นแท็กปิด
    • อ้างอิง: 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 อาจคืน mutable ArrayList หรือ immutable Collections.emptyList() แล้วแต่กรณี และหากแก้ไขอย่างหลังจะเกิด UnsupportedOperationException
  • มีกรณีที่เมธอดซึ่งคืนค่า Optional กลับคืน 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 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 ถูกเปลี่ยนผิดพลาด เพราะปัญหานี้

1 ความคิดเห็น

 
GN⁺ 2025-08-18
ความเห็นจาก Hacker News
  • เราเตอร์และไฟร์วอลล์บางตัวอาจตัดการเชื่อมต่อ TCP ที่ว่างอยู่แบบเงียบ ๆ โดยไม่ส่งสัญญาณใด ๆ ให้แอปพลิเคชันเลย ทำให้โค้ดที่เก็บ TCP connection pool ไว้ เช่น HTTP client library หรือ database client เจอปัญหาว่าการเชื่อมต่อถูกทำให้ใช้ไม่ได้โดยไม่มีคำเตือน วิธีแก้คือกำหนดค่า system TCP keepalive หรือถ้าเป็น HTTP ก็ใช้เฮดเดอร์ Connection: keep-alive, Keep-Alive: timeout=30, max=1000 ได้ เมื่อ TCP connection เชื่อมต่อแล้ว เราเตอร์ระหว่างทางจะไม่เก็บสถานะไว้ ปัญหาจริงคือ firewall หรือ NAT session timeout และในกรณีนี้ก็จะไม่มีแพ็กเก็ต RST ส่งมาด้วย ในสภาพแวดล้อม K8s เคยเจอปัญหาว่าค่าตั้งต้นของโมดูล conntrack ต่ำเกินไป ลองใช้ HTTP Keep-Alive แล้วก็ช่วยได้แค่การนำการเชื่อมต่อกลับมาใช้ซ้ำ ไม่ได้ช่วยคงการเชื่อมต่อไว้ในเครือข่าย(ลิงก์อธิบาย) เพราะ HTTP Keep-Alive ไม่ได้สร้างแพ็กเก็ตจริง ๆ แค่เลื่อนเวลาปิดออกไป ในขณะที่ TCP Keep-Alive จะส่งแพ็กเก็ตเป็นระยะเพื่อรีเซ็ตตัวจับเวลา

    • TCP Keep-Alive อาจทำงานได้ไม่ดีกับอุปกรณ์มือถือ ระบบปฏิบัติการมือถืออาจติดตามหรือจัดการได้เฉพาะ keep-alive packet ที่เกิดในระดับแอปพลิเคชันเท่านั้น แต่ TCP Keep-Alive ทำงานต่ำกว่าระดับแอป จึงอาจถูกปิดใช้งานได้ในบางกรณี ทั้งที่แอปยังคงเข้าถึงได้อยู่
  • เมธอดที่คืนค่า Optional<T> ก็ยังอาจคืน null ได้ แนวปฏิบัติแบบนี้ชวนสับสนมาก ถ้ามีแรงทางอารมณ์พอคงยื่น JEP เพื่อเพิ่มแอนโนเทชันอย่าง @java.lang.NonNullReference ไปแล้ว อยากให้เวลาใช้แอนโนเทชันนี้ประกาศชนิดข้อมูล คอมไพเลอร์จะถือว่าการกำหนด null เป็นข้อผิดพลาด เช่น Alpha อาจกำหนด null ได้ แต่ Beta จะ error และคงต้องไปดูสเปกด้วยว่า dead code elimination ใน javac ทำงานอย่างไรจริง ๆ เช่นส่วนที่ b = null ใน if (true) อาจถูก elide ออกไป และในทางข้อกำหนดอาจยังเป็นโค้ดที่ยอมรับได้

    • ใน Kotlin กรณีแบบนี้เป็น compile error อยู่แล้ว ไม่จำเป็นต้องมีแอนโนเทชันเพิ่ม

    • ก็สงสัยว่าถ้าเป็นภาษาที่มี null อยู่แล้ว จำเป็นต้องใช้ Optional<T> จริงหรือไม่ ถ้าอย่าง Python ที่ค่าคืนของฟังก์ชันเขียนเป็น T | None ตรง ๆ ไม่ใช่ Optional object ถ้ายังต้องตรวจเช็กเหมือนกัน ความแตกต่างของเฟรมเวิร์กก็ดูไม่ชัด ถ้าไม่ได้ใช้สไตล์ monad แบบพิเศษ สุดท้ายการเช็กก็เหมือนเดิม

  • มีคนบอกว่า Java, C#, JS เข้ารหัสสตริงในหน่วยความจำเป็นตระกูล UTF-16 แต่สำหรับ Java นั้นไม่ถูกต้อง และ C#, JS ก็อาจไม่แน่เสมอไป ถ้า string type ของภาษาใดทึบพอ วิธีแทนค่าในหน่วยความจำก็เป็นรายละเอียดการติดตั้งใช้งาน และใน Java เรื่องนี้ชัดเจนขึ้นตั้งแต่เวอร์ชัน 9(JEP ที่เกี่ยวข้อง) การมี FFI ก็เป็นอีกเหตุผลที่ทำให้เปลี่ยนรายละเอียดการติดตั้งใช้งานได้ยาก อีกอย่าง มีคนบอกว่าค่าสูงสุดของจำนวนเต็มที่แสดงได้อย่างแม่นยำใน JS คือ 2^53−1 แต่จริง ๆ จำนวนเต็มที่ใหญ่กว่านั้นอย่าง 2^100 ก็แสดงได้อย่างแม่นยำ เพียงแต่ 2^53−1 หมายถึงค่าที่ n-1, n, n+1 ทั้งหมดแสดงได้อย่างแม่นยำใน IEEE double ดังนั้น n == n-1 และ n == n+1 จะเป็น false ทั้งคู่

    • C# มีการแทนค่าในหน่วยความจำที่ค่อนข้างตายตัวมาก เพราะมีกรณีเข้าถึงบัฟเฟอร์โดยตรงผ่าน ReadOnlySpan<char> หรือ raw char* อยู่บ่อย จึงถือว่า char เป็นชนิด code point แบบ UTF-16 ส่วน JS อาจยังพอหลบเลี่ยงเรื่องนี้ได้บ้าง

    • อยากใช้คำว่า max safe integer มากกว่า max accurate integer

    • (เรื่องการเข้ารหัส base64) สำหรับประเด็นที่ว่าสตริงในหน่วยความจำของ Java, C#, JS ไม่ใช่ตระกูล UTF-16 แม้เชิงเทคนิคอาจถูก แต่ในทางปฏิบัติถ้าเอาสตริงจากภาษาที่ใช้ UTF-8 เป็นฐานไปเข้ารหัส base64 แล้วมาถอดรหัสใน Java ก็มีเคสที่การแทนค่าแบบ UTF-16 ของ Java ทำให้เกิดปัญหาได้

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

    • โดยเนื้อแท้แล้วคู่มือมีไว้เพื่อบันทึกไว้ ไม่ใช่ให้พึ่งความจำล้วน ๆ คู่มือยูนิกซ์ส่วนใหญ่ก็เป็นแบบนั้น เรามักเปิดคู่มือเมื่อรู้อยู่แล้วว่าซอฟต์แวร์ตัวหนึ่งทำอะไร แต่ลืมวิธีใช้อย่างละเอียด ในทางกลับกัน ถ้าเป็นมือใหม่จริง ๆ ที่ต้องทำความเข้าใจแนวคิด ควรใช้ tutorial หรือ guide มากกว่า คู่มือมีบทบาทคล้ายของเตรียมไว้เพื่อให้ตั้งคำถามได้ดีขึ้น
  • อ่านบทความ “Traceroute Isn’t Real” แล้วรู้สึกน่าสนใจมาก ตลอดมารับรู้ได้ว่า traceroute data มักคลาดเคลื่อนมากหรือดูไม่มีความหมาย ตอนนี้ก็เข้าใจเหตุผลแล้วจึงมีประโยชน์มาก(ลิงก์ต้นฉบับ) ถ้ามีข้อมูลอัปเดตใหม่ ๆ ก็อยากรู้เหมือนกัน

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

  • เรื่องค่าอาร์กิวเมนต์เริ่มต้นใน Python ที่ไม่ใช่ค่าซึ่งถูกสร้างใหม่ทุกครั้งที่เรียก แต่เป็นค่าที่ถูกเก็บไว้ เป็นจุดที่ต้องรู้ให้ดีเวลาใช้ตัวแปร datetime

    • ไม่ใช่นักพัฒนาที่ใช้ Python เป็นประจำ แต่สัปดาห์นี้เจ็บตัวหนักเพราะค่าอาร์กิวเมนต์เริ่มต้นถูกเก็บไว้ พอไม่ได้ส่ง set เป็นอาร์กิวเมนต์ ก็พยายามจะกำหนด empty set ให้ แต่ set เดิมกลับถูกนำกลับมาใช้ซ้ำจนเกิดบั๊ก ใช้เวลานานมากกว่าจะหาสาเหตุเจอ
  • ใน “กับดัก” ข้อแรกของหน้า มีการบอกว่า min-width: auto ทำให้ความกว้างขั้นต่ำถูกกำหนดตามเนื้อหา แต่ถ้าไม่ใช่ flex/grid ก็ไม่จริง ตาม MDN ใน block, inline, table ฯลฯ ค่า auto จะถูกปรับเป็น 0(ลิงก์เอกสารทางการ)

    • กับดักข้อแรกจริง ๆ คือ “ไม่มีทางอ่านคุณสมบัติ CSS ใด ๆ แบบแยกขาดได้” เพราะอย่างที่ชื่อบอกว่าเป็น cascading ค่าปริยายและผลของหลายกฎจะมารวมกันที่ไหนสักแห่ง ดังนั้นบริบทของทั้งเอกสารจึงสำคัญ

    • ส่วน cascade ของคุณสมบัติข้อความใน CSS ยังพอเข้าใจได้ แต่ CSS layout นั้นเข้าใจยากเกินไป ไม่ว่าจะมองจากมุมของผู้ออกแบบหน้า คนลงมือทำ หรือผู้ใช้ ก็ยังรู้สึกยากจะเข้าใจว่าออกแบบมาเพื่อใครกันแน่

  • โดยรวมเป็นโพสต์รวมรายการที่โอเคพอสมควร มีความเห็นบางอย่าง

    • เรื่อง Han unification ใน Unicode การที่อักขระที่มีความหมายเดียวกันใช้ code point เดียวกันในหลายภาษา แล้วจะแสดงผลต่างกันตามฟอนต์นั้นไม่ใช่กับดัก ตัวอย่างคันจิที่ยกมาถูกใช้เกือบเหมือนกันทั้งในจีนและญี่ปุ่น และผู้ใช้ทั้งสองภาษาก็รับรู้ว่ารูปแปรอื่น ๆ ก็ยังเป็นอักขระแนวคิดเดียวกัน ผู้เขียนกำลังพูดเหมือนกับว่าเราควรนิยามตัวอักษร ‘A’ แยกระหว่างภาษาอังกฤษกับภาษาฝรั่งเศส ซึ่งในความเป็นจริงไม่ใช่แบบนั้น ดูหัวข้อ Han unification
    • -0.0 และ +0.0 (ศูนย์ลบและศูนย์บวก) แม้จะถูกมองว่าเท่ากันในการเปรียบเทียบ floating point แต่ก็มีวิธีแยกได้ เช่นดูจาก bit pattern หรือผลของ 1.0/-0.0 = -∞, 1.0/0.0 = +∞
    • เห็นด้วยมากกับคำแนะนำให้ตั้ง timezone ของเซิร์ฟเวอร์เป็น UTC ควรใช้ UTC กับทุกอย่างที่ต้องเก็บรักษา มีคุณค่าต่อการอ้างอิงในอนาคต หรือต้องการ timestamp ที่แม่นยำ เช่น เซิร์ฟเวอร์, log, การเก็บรูปภาพ ส่วนเวลา local ควรใช้แค่ในการสื่อสารเท่านั้น
    • มีคำแนะนำว่าในจำนวนเต็ม (low + high) / 2 เสี่ยง overflow จึงควรใช้ low + (high - low) / 2 แต่ถ้า low หรือ high เป็นค่าติดลบ ก็อาจแค่ย้ายช่วงที่ overflow ไปเท่านั้น เป็นประเด็นสำคัญใน binary search ทั่วไป
    • การใช้ชนิดจำนวนเต็มและการคำนวณใน C/C++ ให้ถูกต้องก็เป็นกับดักใหญ่เหมือนกัน(ลิงก์ไกด์ที่เกี่ยวข้อง)
    • มีคนบอกว่า rebase อาจเปลี่ยน history ได้ แต่จริง ๆ แล้ว rebase ก็คือคำสั่งที่ rewrite history โดยตรงอยู่แล้ว
    • แม้ผู้ใช้สองภาษาจะรับรู้อักขระเดียวกันในเชิงแนวคิด แต่ก็ไม่ควรลดทอนเหลือแค่ว่าเป็น “รูปแบบฟอนต์” เพราะการที่ Unicode code point เหมือนกัน ไม่ได้หมายความว่าจะสลับแทนกันได้อย่างปลอดภัยเสมอไป สำหรับผู้ใช้ชาวญี่ปุ่น การแทนแบบนี้อาจถึงขั้นเป็นเหตุผลให้ปฏิเสธการใช้ผลิตภัณฑ์ได้

    • จริงอยู่ที่ A ภาษาอังกฤษกับ A ภาษาฝรั่งเศสไม่แยกกัน แต่ก็มีกรณีอย่าง А (อักษรซีริลลิก) กับ A (อักษรละติน) ที่หน้าตาเหมือนกันแต่เป็นคนละ code point และใน Han unification ก็มีหลายกรณีที่จับอักขระรูปร่างค่อนข้างต่างกันมาอยู่รวมกัน คนเรียนภาษาญี่ปุ่นหรือภาษาจีนอาจสับสนได้จริง เช่นอักขระ '喝' (drink) ที่อาจแสดงต่างกันมากตาม ลิงก์ แค่คัดลอกก็อาจเห็นรูปเปลี่ยนทันทีเพราะวิธีจัดการซับซ้อนมาก Han unification เป็นหัวข้อที่ปวดหัวมากในโลกจริง

  • มีการพูดถึงความต่างเล็ก ๆ ระหว่าง numpy กับ pytorch แต่ถ้าไม่มีตัวอย่างที่ชัดเจนก็ไม่ค่อยมีประโยชน์ และข้อมูลก็น้อยเกินกว่าจะรู้สึกว่าเป็นกับดักที่ใช้งานจริง