กับดักที่นักพัฒนาต้องระวัง
(qouteall.fun)- สรุปกับดักที่ไม่เป็นไปตามสัญชาตญาณที่นักพัฒนามักตกหลุมพราง พร้อมอธิบายสาเหตุของบั๊กที่เกิดขึ้นได้บ่อย
- ครอบคลุมปัญหาที่พบได้บ่อยในเทคโนโลยีหลากหลายด้าน เช่น HTML, CSS, Unicode/การเข้ารหัสข้อความ, เลขทศนิยมลอยตัว, เวลา
- เน้นว่าความเข้าใจผิดหรือข้อผิดพลาดอาจเกิดขึ้นได้จาก ความแตกต่างเล็กน้อยของไวยากรณ์และพฤติกรรมการทำงาน ในแต่ละภาษาและเฟรมเวิร์ก
- อธิบายด้วยตัวอย่างถึงกับดักที่อาจเกิดขึ้นในสภาพแวดล้อมใช้งานจริงในโดเมนหลักของฝั่งแบ็กเอนด์ เช่น concurrency, networking, database
- แนะนำสถานการณ์ปัญหา วิธีแก้ไข และแนวทางปรับปรุงพฤติกรรมที่ไม่คาดคิดผ่านตัวอย่างและลิงก์อ้างอิงที่หลากหลาย
HTML และ CSS
-
ค่าเริ่มต้นของ min-width ใน Flexbox/Grid
min-widthมีค่าเริ่มต้นเป็นautomin-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
- สร้าง BFC ได้ด้วย
-
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
- เงื่อนไขที่ทำให้เกิด 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
- Rust: ใช้ UTF-8 สำหรับสตริงภายใน,
-
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
- 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 อยู่
- JS
- มาตรฐาน JSON ไม่อนุญาต 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อาจคืน mutableArrayListหรือ immutableCollections.emptyList()แล้วแต่กรณี และหากแก้ไขอย่างหลังจะเกิดUnsupportedOperationException - มีกรณีที่เมธอดซึ่งคืนค่า
Optionalกลับคืนnull(ไม่แนะนำ) - หากมี return ในบล็อก
finallyexception ที่เกิดใน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สำหรับการแปลงแบบบังคับ
- ข้อยกเว้น: 1) ชนิดที่มีความสัมพันธ์แบบสืบทอด 2) การแปลงเป็น
- การเข้าถึง 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: ถ้า
DIRunset แล้วรัน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
Hostheader และ 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 โดยไม่ใส่เครื่องหมายอัญประกาศ อาจถูกแปลงเป็นตัวเลขได้
- YAML ไวต่อช่องว่าง →
-
ปัญหา CSV ใน Excel
- เมื่อเปิด CSV ใน Excel จะมีการ แปลงค่าอัตโนมัติ
- แปลงเป็นวันที่:
1/2,1-2→2-Jan - แปลงเลขขนาดใหญ่แบบไม่แม่นยำ:
12345678901234567890→12345678901234500000
- แปลงเป็นวันที่:
- สาเหตุคือ Excel จัดการตัวเลขภายในแบบ floating point
- เคยมีกรณีที่ชื่อยีน SEPT1 ถูกเปลี่ยนผิดพลาด เพราะปัญหานี้
- เมื่อเปิด CSV ใน Excel จะมีการ แปลงค่าอัตโนมัติ
1 ความคิดเห็น
ความเห็นจาก 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 จะส่งแพ็กเก็ตเป็นระยะเพื่อรีเซ็ตตัวจับเวลาเมธอดที่คืนค่า
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 ทำให้เกิดปัญหาได้
ทิปหรือข้อมูลในรูปแบบคู่มือดูเหมือนจะช่วยให้เข้าใจได้เร็วขึ้นเฉพาะเรื่องที่รู้อยู่แล้วหรือเกือบรู้อยู่แล้วเป็นส่วนใหญ่ คู่มือจำนวนมากมีประโยชน์กับการจัดระเบียบความรู้และการทบทวนมากกว่าการเรียนรู้ตั้งแต่ต้น จึงไม่ค่อยมีประสิทธิภาพสำหรับการสอนคนที่ไม่รู้อะไรเลย
อ่านบทความ “Traceroute Isn’t Real” แล้วรู้สึกน่าสนใจมาก ตลอดมารับรู้ได้ว่า traceroute data มักคลาดเคลื่อนมากหรือดูไม่มีความหมาย ตอนนี้ก็เข้าใจเหตุผลแล้วจึงมีประโยชน์มาก(ลิงก์ต้นฉบับ) ถ้ามีข้อมูลอัปเดตใหม่ ๆ ก็อยากรู้เหมือนกัน
บทความนี้ดูไม่ค่อยใช่รายการของกับดักหรือ trap จริง ๆ เท่าไร แต่เป็นรายการทิปเล็ก ๆ ที่ผู้เขียนเรียนรู้จากประสบการณ์มากกว่า หลายข้อใช้ได้แค่ในบริบทแคบ ๆ ซึ่งไม่ได้อธิบายบริบทให้ชัด และบางข้อก็ดูเหมือนข้อมูลผิดด้วย เพราะฉะนั้นไม่จำเป็นต้องรับทั้งบทความแบบตรงตัวเกินไป มองเป็นเหมือนกระแสความคิดหรือโน้ตก็พอ
เรื่องค่าอาร์กิวเมนต์เริ่มต้นใน Python ที่ไม่ใช่ค่าซึ่งถูกสร้างใหม่ทุกครั้งที่เรียก แต่เป็นค่าที่ถูกเก็บไว้ เป็นจุดที่ต้องรู้ให้ดีเวลาใช้ตัวแปร
datetimesetเป็นอาร์กิวเมนต์ ก็พยายามจะกำหนด empty set ให้ แต่setเดิมกลับถูกนำกลับมาใช้ซ้ำจนเกิดบั๊ก ใช้เวลานานมากกว่าจะหาสาเหตุเจอใน “กับดัก” ข้อแรกของหน้า มีการบอกว่า
min-width: autoทำให้ความกว้างขั้นต่ำถูกกำหนดตามเนื้อหา แต่ถ้าไม่ใช่ flex/grid ก็ไม่จริง ตาม MDN ในblock,inline,tableฯลฯ ค่าautoจะถูกปรับเป็น 0(ลิงก์เอกสารทางการ)กับดักข้อแรกจริง ๆ คือ “ไม่มีทางอ่านคุณสมบัติ CSS ใด ๆ แบบแยกขาดได้” เพราะอย่างที่ชื่อบอกว่าเป็น cascading ค่าปริยายและผลของหลายกฎจะมารวมกันที่ไหนสักแห่ง ดังนั้นบริบทของทั้งเอกสารจึงสำคัญ
ส่วน cascade ของคุณสมบัติข้อความใน CSS ยังพอเข้าใจได้ แต่ CSS layout นั้นเข้าใจยากเกินไป ไม่ว่าจะมองจากมุมของผู้ออกแบบหน้า คนลงมือทำ หรือผู้ใช้ ก็ยังรู้สึกยากจะเข้าใจว่าออกแบบมาเพื่อใครกันแน่
โดยรวมเป็นโพสต์รวมรายการที่โอเคพอสมควร มีความเห็นบางอย่าง
-0.0และ+0.0(ศูนย์ลบและศูนย์บวก) แม้จะถูกมองว่าเท่ากันในการเปรียบเทียบ floating point แต่ก็มีวิธีแยกได้ เช่นดูจาก bit pattern หรือผลของ1.0/-0.0 = -∞,1.0/0.0 = +∞(low + high) / 2เสี่ยง overflow จึงควรใช้low + (high - low) / 2แต่ถ้าlowหรือhighเป็นค่าติดลบ ก็อาจแค่ย้ายช่วงที่ overflow ไปเท่านั้น เป็นประเด็นสำคัญใน binary search ทั่วไปแม้ผู้ใช้สองภาษาจะรับรู้อักขระเดียวกันในเชิงแนวคิด แต่ก็ไม่ควรลดทอนเหลือแค่ว่าเป็น “รูปแบบฟอนต์” เพราะการที่ Unicode code point เหมือนกัน ไม่ได้หมายความว่าจะสลับแทนกันได้อย่างปลอดภัยเสมอไป สำหรับผู้ใช้ชาวญี่ปุ่น การแทนแบบนี้อาจถึงขั้นเป็นเหตุผลให้ปฏิเสธการใช้ผลิตภัณฑ์ได้
จริงอยู่ที่ A ภาษาอังกฤษกับ A ภาษาฝรั่งเศสไม่แยกกัน แต่ก็มีกรณีอย่าง А (อักษรซีริลลิก) กับ A (อักษรละติน) ที่หน้าตาเหมือนกันแต่เป็นคนละ code point และใน Han unification ก็มีหลายกรณีที่จับอักขระรูปร่างค่อนข้างต่างกันมาอยู่รวมกัน คนเรียนภาษาญี่ปุ่นหรือภาษาจีนอาจสับสนได้จริง เช่นอักขระ '喝' (drink) ที่อาจแสดงต่างกันมากตาม ลิงก์ แค่คัดลอกก็อาจเห็นรูปเปลี่ยนทันทีเพราะวิธีจัดการซับซ้อนมาก Han unification เป็นหัวข้อที่ปวดหัวมากในโลกจริง
มีการพูดถึงความต่างเล็ก ๆ ระหว่าง numpy กับ pytorch แต่ถ้าไม่มีตัวอย่างที่ชัดเจนก็ไม่ค่อยมีประโยชน์ และข้อมูลก็น้อยเกินกว่าจะรู้สึกว่าเป็นกับดักที่ใช้งานจริง