ยอดที่รอดมาถึงปลายทาง
เจ็ดบทที่ผ่านมาวัดค่าทีละจุด บทนี้เอาจุดเหล่านั้นมาต่อกันเป็นท่อเดียว แล้วส่งออเดอร์ใบหนึ่ง เข้าไปจริง
ออเดอร์ใบนั้นมียอดรวม 10 มียอดคืนเงินที่ตามมาทีหลังเป็น 25 และมีรหัสลูกค้าติดลบ ส่วน
orderId ของมันยาวเกินช่วงที่ทศนิยม 64 bit เก็บได้ตรงตัว
{"orderId": 9007199254740993, "total": 10, "refunded": 25, "customerId": -7}คำถามของบทนี้ไม่ใช่ว่าท่อเส้นไหนเขียนสวยกว่า แต่เป็นว่า ค่าที่ออกจากปลายทางของสองเส้นนี้ ต่างกันตรงไหนบ้าง เมื่ออินพุตเป็นใบเดียวกัน คำตอบวัดออกมาได้ครบทั้งแปดจุดที่วัด
orderctl คือเครื่องมือประมวลผลออเดอร์ที่คอร์สภาษาในเสานี้สร้างซ้ำทีละภาษา repo ตัวอย่าง
กำลังจัดทำ ระหว่างนี้ทุกตัวเลขในบทนี้รันเองได้ด้วย npm run verify:integer-semantics
ซึ่งเรียก python3 scripts/integer-semantics/probe.py · ไม่ต้องมี Docker และไม่ต้องต่อเน็ต
อินพุตใบเดียว ที่พิสูจน์ได้ว่าใบเดียว
หัวข้อที่มีชื่อว่า “อินพุตใบเดียว ที่พิสูจน์ได้ว่าใบเดียว”ท่อทั้งสองเส้นอยู่ในโปรแกรมเดียวกัน และอ่าน body จากตัวแปรตัวเดียวกัน
การเทียบท่อสองเส้นจะไม่มีความหมายเลย ถ้าใครสักคนเถียงได้ว่าสองเส้นนั้นรับอินพุตคนละใบ เงื่อนไขนี้จึงถูกบังคับไว้ที่ตัวโปรแกรม ไม่ได้ฝากไว้กับวินัยของคนรัน
body := []byte(os.Args[1])out := map[string]string{"shardCount": fmt.Sprintf("%d", shardCount)}naive(body, out)guarded(body, out)body มาจากอาร์กิวเมนต์เดียวที่ส่งเข้ามาทาง argv แล้วฟังก์ชันทั้งสองตัวรับตัวแปรตัวเดียวกันนั้น
ไปใช้ ไม่มีการอ่านอินพุตซ้ำ และไม่มีบรรทัดไหนประกอบอินพุตขึ้นใหม่ก่อนถึงสองฟังก์ชันนั้น
runner ไม่พิมพ์อินพุตของตัวเองออกมาสักใบ body ที่ยกไว้ข้างบนจึงมาจากช่อง orderBody ของ
measured.json ซึ่งเป็นค่าที่ probe ส่งเข้าไป ส่วน body สองใบที่บทนี้ยกมาทีหลัง คือผลของขั้นที่สอง
ของแต่ละเส้น ไม่ใช่อินพุต
ปลายทางของทั้งสองเส้นก็เป็นชุดเดียวกัน คือ shard จำนวน 8 ใบ ชื่อ shard-0 ถึง shard-7
ซึ่งจำนวนใบนี้ก็ออกมาเป็นค่าที่วัดได้ด้วย ไม่ได้อยู่แต่ใน source
สี่ขั้นที่ทั้งสองเส้นเดินเหมือนกันคือ decode body · marshal ใหม่แล้วส่งต่อ · ลบส่วนคืนเงิน
ออกจากยอด · แล้วเลือก shard จาก customerId ต่างกันที่วิธีเขียนแต่ละขั้นเท่านั้น
แปดจุดที่วัด บนปลายทางของสองเส้น
หัวข้อที่มีชื่อว่า “แปดจุดที่วัด บนปลายทางของสองเส้น”สี่ขั้นให้แปดแถว เพราะบางขั้นรายงานทั้งค่าที่ได้ และความล้มเหลวที่มันพบ · แต่ละแถวให้สองค่า
| จุดที่วัด | ท่อเปล่า | ท่อที่มีด่าน |
|---|---|---|
ชนิดของ orderId ที่ decode ได้ | float64 | json.Number |
orderId ที่อ่านออกมา | 9.007199254740992e+15 | 9007199254740993 |
body ที่ส่งต่อ | {"customerId":-7,"orderId":9007199254740992,"refunded":25,"total":10} | {"customerId":-7,"orderId":"9007199254740993","refunded":25,"total":10} |
ผลของ total - refunded | 4294967281 | ไม่มีค่าคืนออกมา |
| ความล้มเหลวของการลบ | ไม่รายงาน | refunded 25 exceeds total 10 |
| index ของ shard | -7 | 1 |
| shard ที่ลงจริง | ไม่ได้ลงใบไหนเลย | shard-1 |
| ความล้มเหลวตอนอ่าน array | runtime error: index out of range [-7] | ไม่รายงาน |
สองแถวแรกมาจากขั้นที่หนึ่ง แถวที่สามมาจากขั้นที่สอง แถวที่สี่กับห้ามาจากขั้นที่สาม และสาม แถวสุดท้ายมาจากขั้นที่สี่
ทั้งสองเส้นตอบไม่ตรงกันครบทั้งสี่ขั้น ไม่ใช่ขั้นใดขั้นหนึ่ง และไม่มีขั้นไหนที่ค่าของสองเส้น บังเอิญมาเจอกันกลางทาง
ท่อเปล่า · สี่ขั้น กับเสียงที่ดังครั้งเดียว
หัวข้อที่มีชื่อว่า “ท่อเปล่า · สี่ขั้น กับเสียงที่ดังครั้งเดียว”ขั้นที่หนึ่งถึงสามคืนค่าที่หน้าตาใช้งานได้ แล้วขั้นที่สี่เป็นขั้นเดียวที่รายงานความล้มเหลว
ขั้นที่หนึ่ง · id ที่มาถึงเป็น float64
หัวข้อที่มีชื่อว่า “ขั้นที่หนึ่ง · id ที่มาถึงเป็น float64”json.Unmarshal ที่ปลายทางเป็น map[string]any ไม่คืน error ออกมาเลย และชนิดที่ orderId
ได้คือ float64 ตามที่เอกสารของ encoding/json ระบุไว้สำหรับตัวเลขของ JSON
ค่าที่อ่านออกมาคือ 9.007199254740992e+15 ซึ่งเขียนเป็นหลักล้วนได้เป็น 9007199254740992
น้อยกว่า id ที่ส่งมาอยู่หนึ่ง นี่คือจุดที่บทที่ 5 วัดไว้ ย้ายมาอยู่บนพรมแดนของการ parse
ขั้นที่สอง · body ที่ส่งต่อได้โดยไม่มีใครปฏิเสธ
หัวข้อที่มีชื่อว่า “ขั้นที่สอง · body ที่ส่งต่อได้โดยไม่มีใครปฏิเสธ”gateway อ่าน body ลง map แล้ว Marshal กลับออกไปข้างหน้า สิ่งที่ออกมายังเป็น JSON ที่ถูกต้อง
ทุกประการ ทุกช่องยังอยู่ครบ และ service ข้างหลังรับมันได้ตามปกติ
{"customerId":-7,"orderId":9007199254740992,"refunded":25,"total":10}total refunded และ customerId เดินผ่านมาครบทุกหลัก เพราะทั้งสามค่าอยู่ในช่วงที่ทศนิยม
64 bit เก็บได้ตรงตัว ช่องเดียวที่เปลี่ยนค่าคือ orderId และมันเปลี่ยนไปแล้วตั้งแต่ขั้นที่หนึ่ง
ลำดับของช่องในบรรทัดนี้เรียงตามตัวอักษร ไม่ได้เรียงตามอินพุต เพราะเอกสารของ encoding/json
ระบุว่า Marshal เรียงคีย์ของ map ก่อนเขียนออกไป การเรียงใหม่นั้นไม่ใช่ค่าที่เปลี่ยน และ body
ของท่อที่มีด่านก็เรียงแบบเดียวกันด้วยเหตุผลเดียวกัน
ขั้นที่สาม · การลบที่คืนตัวเลขออกมา
หัวข้อที่มีชื่อว่า “ขั้นที่สาม · การลบที่คืนตัวเลขออกมา”ยอดกับส่วนคืนเงินถูกอ่านออกมาเป็น float64 แล้วแปลงเป็น uint32 ก่อนลบกัน ผลที่ได้คือ
4294967281 และขั้นนี้ไม่รายงานความล้มเหลวอะไรเลย ช่อง error ของมันเป็นสตริงว่าง
สเปกของ Go เขียนกฎนี้ไว้ตรงๆ ที่หัวข้อ Integer overflow ว่าการบวกลบคูณและเลื่อน bit ของ ค่า unsigned คิดแบบ modulo ของสองยกกำลังความกว้าง ผลลัพธ์จึงเป็นค่าที่ถูกต้องตามชนิด
นี่คือจุดที่บทที่ 1 กับบทที่ 2 วัดไว้ เดินทางมาถึงยอดเงินจริง ยอดที่ยังต้องเก็บจากออเดอร์ ใบนี้กลายเป็นสี่พันล้าน โดยไม่มีบรรทัดไหนในท่อบอกว่ามีอะไรผิด
ขั้นที่สี่ · index ที่ติดลบ และบรรทัดเดียวที่ดัง
หัวข้อที่มีชื่อว่า “ขั้นที่สี่ · index ที่ติดลบ และบรรทัดเดียวที่ดัง”customerId % 8 ใน Go ให้เศษที่ตามเครื่องหมายของตัวตั้ง ค่าที่ได้จึงเป็น -7 ไม่ใช่ค่าที่
อยู่ในช่วง 0 ถึง 7 ของ shard แปดใบ
สเปกของ Go ที่หัวข้อ Index expressions ระบุว่า index อยู่ในช่วงเมื่อ 0 <= x < len(a)
นอกจากนั้นคือนอกช่วง และการอ่าน array ด้วย index ที่นอกช่วงตอนรัน ทำให้เกิด panic
runtime error: index out of range [-7]ค่าที่ขั้นนี้คืนจึงเป็นสตริงว่าง ไม่มี shard ใบไหนรับออเดอร์ใบนี้ และนี่คือจุดที่บทที่ 4 วัดไว้ มาจบที่ปลายทางจริงของมัน
ใน service จริง panic ตัวนี้ฆ่าคำขอที่กำลังทำอยู่ ส่วนใน runner ของบทนี้มันถูกดักไว้ด้วย
recover เพื่อให้ความล้มเหลวกลายเป็นค่าที่วัดได้ ไม่ใช่โปรแกรมที่ตายก่อนบันทึกค่า
ท่อที่มีด่าน · body ใบเดิม สี่ขั้นที่เขียนใหม่
หัวข้อที่มีชื่อว่า “ท่อที่มีด่าน · body ใบเดิม สี่ขั้นที่เขียนใหม่”ด่านทั้งสี่ไม่ได้แก้ค่าให้ถูก มันเปลี่ยนสิ่งที่ขั้นนั้นยอมส่งต่อไปข้างหน้า
ขั้นที่หนึ่ง · Decoder ที่ตั้ง UseNumber
หัวข้อที่มีชื่อว่า “ขั้นที่หนึ่ง · Decoder ที่ตั้ง UseNumber”Decoder.UseNumber() สั่งให้ตัวเลขของ JSON ถูก decode เป็น json.Number แทน float64
ชนิดที่ orderId ได้จึงเป็น json.Number และค่าที่อ่านออกมาคือ 9007199254740993
json.Number เก็บตัวเลขไว้เป็นข้อความของ literal เดิม การอ่านค่ากลับออกมาเป็นจำนวนเต็มจึง
เป็นการกระทำที่ผู้เรียกสั่งเอง ไม่ใช่การกระทำที่เกิดไปแล้วตั้งแต่ตอน decode
ขั้นที่สอง · id ที่ส่งต่อเป็นสตริง
หัวข้อที่มีชื่อว่า “ขั้นที่สอง · id ที่ส่งต่อเป็นสตริง”ก่อน Marshal ท่อเส้นนี้คัดทุกช่องของ map ที่ decode มาลงใน map ใบใหม่ก่อน แล้วจึงเขียนทับ
ช่อง orderId ของใบใหม่ด้วยข้อความของ json.Number ตัวเดิม map ที่ decode มาไม่ถูกแตะ
และขั้นที่สามกับขั้นที่สี่ก็อ่านค่าจากใบเดิมนั้น ไม่ใช่จากใบที่ส่งออกไป
สิ่งที่ออกไปข้างหน้าจึงเป็นสตริง ไม่ใช่ตัวเลขของ JSON
{"customerId":-7,"orderId":"9007199254740993","refunded":25,"total":10}ทุกหลักของ id ต้นทางยังอยู่ครบใน body ใบนี้ ราคาของมันคือรูปของช่องนั้นเปลี่ยนไป ทุก service
ข้างหลังที่ประกาศ orderId เป็นตัวเลขจึงต้องแก้ตาม
ขั้นที่สาม · การลบที่ไม่ยอมคืนตัวเลข
หัวข้อที่มีชื่อว่า “ขั้นที่สาม · การลบที่ไม่ยอมคืนตัวเลข”ยอดกับส่วนคืนเงินในท่อเส้นนี้เดินทางมาเป็น json.Number แล้วถูกแปลงลง uint32 ก่อนลบ
ทั้งสองค่าจึงเป็นจำนวนเต็มตลอดทาง ไม่ได้ผ่านทศนิยมสักขั้น จากนั้นการลบถูกห่อไว้ด้วยการตรวจก่อนลบ
runner ไม่ได้ประกาศสกุลเงินหรือหน่วยของยอดไว้ที่ไหนเลย การเก็บยอดเป็น หน่วยย่อยที่สุดของสกุลเงินminor unitsการเก็บเงินเป็นจำนวนเต็มของหน่วยย่อยที่สุด แทนการเก็บเป็นทศนิยม · คอร์สนี้เป็นเจ้าของการคำนวณบนค่านั้น ส่วนชนิดที่ห่อมันไว้เป็นของคอร์ส payments-ledger-ddd คือเหตุผลที่ระบบจริงเลือกรูปจำนวนเต็มแบบนี้ ไม่ใช่ค่าที่ probe วัดได้
การตรวจนั้นให้ผลแบบเดียวกับ checked arithmetic ที่บทที่ 2 วัดไว้ ต่างกันที่ด่านนี้เขียนขึ้นเอง ในท่อ ไม่ใช่การกระทำที่ภาษามีมาให้
refunded 25 exceeds total 10ช่องผลลัพธ์ของขั้นนี้ไม่มีค่าคืนออกมาเลย สิ่งที่ผู้เรียกได้ไปคือความล้มเหลวที่ระบุทั้งสองค่า ในข้อความเดียว และไม่มีตัวเลขให้เผลอส่งต่อ
ขั้นที่สี่ · index ที่อยู่ในช่วงเสมอ
หัวข้อที่มีชื่อว่า “ขั้นที่สี่ · index ที่อยู่ในช่วงเสมอ”นิพจน์ที่ท่อเส้นนี้ใช้คือการครอบเศษให้เป็นบวกก่อนใช้งาน ซึ่งเป็นรูปที่บทที่ 4 ยกไว้เป็นทางแก้
ค่าที่ได้จาก customerId ที่เป็น -7 กับ shard แปดใบ จึงเป็น 1
ขั้นนี้คืน shard-1 และไม่รายงานความล้มเหลว ความล้มเหลวของออเดอร์ใบนี้จึงมาจากขั้นที่สาม
ขั้นเดียว ไม่ใช่จากการเลือกปลายทาง
flowchart TD B["body ใบเดียว · orderId 9007199254740993 · total 10 · refunded 25 · customerId -7"] B --> N["ท่อเปล่า"] B --> G["ท่อที่มีด่าน"] N --> N1["ขั้นที่ 1 · float64 · อ่านได้ 9.007199254740992e+15"] N1 --> N2["ขั้นที่ 2 · ส่งต่อ orderId เป็นตัวเลข 9007199254740992"] N2 --> N3["ขั้นที่ 3 · ลบแล้วได้ 4294967281 · ไม่รายงานความล้มเหลว"] N3 --> N4["ขั้นที่ 4 · index -7 · ได้ runtime error แทนที่จะได้ shard"] G --> G1["ขั้นที่ 1 · json.Number · อ่านได้ 9007199254740993"] G1 --> G2["ขั้นที่ 2 · ส่งต่อ orderId เป็นสตริง 9007199254740993"] G2 --> G3["ขั้นที่ 3 · ไม่คืนค่า · รายงานว่า refunded 25 exceeds total 10"] G3 --> G4["ขั้นที่ 4 · index 1 · ลงที่ shard-1"]
คำบรรยายภาพ: สองเส้นเริ่มจากกล่องเดียวกัน และแยกกันตั้งแต่ขั้นแรก · กล่องสีเดียวกันทั้งภาพ เพราะทุกกล่องคือค่าที่วัดได้ ไม่ใช่ความเห็นว่าเส้นไหนดีกว่า
สามในสี่ค่าของท่อเปล่า หน้าตาเหมือนข้อมูลปกติ
หัวข้อที่มีชื่อว่า “สามในสี่ค่าของท่อเปล่า หน้าตาเหมือนข้อมูลปกติ”เส้นที่มีด่านไม่ได้ชนะเพราะตอบถูก มันชนะเพราะไม่ยอมส่งคำตอบผิดออกไปเงียบๆ
ไล่ค่าที่ท่อเปล่าคืนออกมาทีละขั้น แล้วถามว่าขั้นไหนบ้างที่ระบบเฝ้าระวังจะเห็น
- ขั้นที่หนึ่ง คืนตัวเลขหนึ่งค่า
Unmarshalไม่คืน error และชนิดที่ได้ก็เป็นชนิดปกติ - ขั้นที่สอง คืน body ที่เป็น JSON ถูกต้อง ทุกช่องอยู่ครบ ส่งต่อได้ทันที
- ขั้นที่สาม คืนจำนวนเต็มบวกหนึ่งค่า และช่องความล้มเหลวเป็นสตริงว่าง
- ขั้นที่สี่ ไม่คืนค่า และคืนข้อความที่มีคำว่า
runtime errorอยู่ในนั้น
สามขั้นแรกไม่มีอะไรให้ alert ไปจับเลยสักอย่าง ค่าที่มันคืนคือตัวเลข body และตัวเลข ซึ่งเป็น รูปเดียวกับที่ระบบรับไว้เป็นปกติอยู่แล้ว
ขั้นที่สี่เป็นขั้นเดียวที่ใครก็ตามจะรู้ตัว และมันเป็นขั้นที่ความเสียหายน้อยที่สุดในสี่ขั้น เพราะ ไม่มี shard ใบไหนรับออเดอร์ใบนี้ไว้
ความเสียหายจริงอยู่ที่สามขั้นแรก คือ id ที่เปลี่ยนไปหนึ่ง body ที่พา id ใหม่นั้นต่อไปทั้งระบบ และยอดสี่พันล้านที่ถูกส่งไปเป็นยอดที่ยังต้องเก็บ
ด่านทั้งสี่ไม่ได้ทำให้ยอดของออเดอร์ใบนี้คำนวณออกมาได้ ขั้นที่สามไม่มีค่าคืนออกมาเลย เพราะ ส่วนคืนเงินมากกว่ายอดจริงๆ สิ่งที่ด่านเปลี่ยนคือความล้มเหลวนั้นกลายเป็นข้อความที่อ่านได้ แทนที่จะเป็นตัวเลข
ราคาที่วัดได้ในบทนี้มีสองข้อ ทั้งสองข้ออยู่ในตารางแปดแถวข้างบน
- รูปของ
orderIdใน body ที่ส่งต่อเปลี่ยนจากตัวเลขเป็นสตริง ทุก service ที่รับ body ใบนี้ต้องแก้ตาม และการแก้นั้นต้องลงพร้อมกันทั้งระบบ - ขั้นที่สามไม่มีตัวเลขให้ถือต่อ ผู้เรียกต้องเขียนทางเดินของความล้มเหลวเอง ไม่ใช่รับค่า มาแล้วไปต่อเหมือนเดิม
probe ของคอร์สนี้ไม่ได้วัดเวลา ไม่ได้วัดปริมาณงานต่อวินาที และไม่ได้วัดต้นทุนของด่านเหล่านี้ สักข้อ บทนี้จึงไม่บอกว่าด่านถูกหรือแพง มันบอกเฉพาะค่าที่ออกจากปลายทางของสองเส้น
ห้าจุด กับบทที่วัดแต่ละจุด
หัวข้อที่มีชื่อว่า “ห้าจุด กับบทที่วัดแต่ละจุด”บทนี้ไม่ได้เป็นเจ้าของจุดไหน มันคือที่ที่จุดทั้งห้าถูกวางต่อกันเป็นท่อเดียว
ธีสิสของคอร์สนี้เป็นประโยคเดียว คือ ค่าจำนวนเต็มค่าหนึ่งเปลี่ยนได้ห้าจุดระหว่างทางผ่านระบบ และทุกจุดในห้าจุดนั้นเงียบ เจ็ดบทก่อนหน้าวัดครบทั้งห้าจุดแล้ว
| จุด | เรื่อง | บท |
|---|---|---|
| 1 | ความกว้างหมด | บทที่ 1 และบทที่ 2 |
| 2 | ค่าสัมบูรณ์กับความไม่สมมาตรของ two’s complement | บทที่ 3 |
| 3 | การหารกับเศษของจำนวนติดลบ | บทที่ 4 |
| 4 | ทศนิยมกลืนจำนวนเต็ม | บทที่ 5 |
| 5 | พรมแดนของการ parse | บทที่ 6 และบทที่ 7 |
ท่อในบทนี้เดินผ่านสี่จุดในห้าจุดนั้น และแต่ละจุดโผล่ที่ขั้นของมันเอง
- ขั้นที่หนึ่ง คือจุดที่ 5 กับจุดที่ 4 มาเจอกันบรรทัดเดียว ชนิดปลายทางที่ parse ได้เป็น
float64คือสิ่งที่ทำให้ทศนิยมกลืน id ไปหนึ่ง - ขั้นที่สาม คือจุดที่ 1 ความกว้างของ
uint32หมดลง แล้วค่าวนกลับเป็น4294967281 - ขั้นที่สี่ คือจุดที่ 3 เศษของจำนวนติดลบตามเครื่องหมายของตัวตั้ง แล้ว index ออกนอกช่วง
จุดที่ 2 ไม่มีที่นั่งในท่อนี้ เพราะท่อนี้ไม่ได้หาค่าสัมบูรณ์ของอะไรเลยสักขั้น การเขียนว่ามันโผล่ ที่นี่ด้วยจะเป็นการอ้างค่าที่ probe ไม่ได้วัด
สิ่งที่ทั้งแปดบทชี้ไปที่เดียวกันคือ ตัวตัดสินว่าค่าจะเปลี่ยนหรือไม่ ไม่ใช่ชื่อภาษา แต่เป็นชนิด ปลายทางที่คุณประกาศ กับด่านที่คุณเขียนหรือไม่ได้เขียนไว้ตรงนั้น
-
Go package documentation —
encoding/json(เข้าถึง 2026-09-13) —Unmarshalระบุว่าเมื่อปลายทางเป็น interface value มันเก็บ float64, for JSON numbers ลงไป ·Decoder.UseNumberระบุว่ามันทำให้ decoder unmarshal a number into an interface value as a Number instead of as a float64 ·Numberระบุไว้ว่า A Number represents a JSON number literal · และหน้าเดียวกันระบุเรื่อง การเขียน map กลับออกไปว่า The map keys are sorted and used as JSON object keys -
The Go Programming Language Specification — Index expressions (เข้าถึง 2026-09-13) — ระบุว่า index
xอยู่ในช่วงเมื่อ0 <= x < len(a)นอกจากนั้น คือนอกช่วง และสำหรับ array ระบุว่า if x is out of range at run time, a run-time panic occurs ซึ่งเป็นที่มาของข้อความที่ขั้นที่สี่ของท่อเปล่าคืนออกมา -
The Go Programming Language Specification — Integer overflow (เข้าถึง 2026-09-13) — ระบุว่าสำหรับค่า unsigned การกระทำ
+-*และ<<are computed modulo 2n, where n is the bit width of the unsigned integer’s type ซึ่งเป็นเหตุผลที่10 - 25แบบuint32ให้4294967281โดยไม่มีความล้มเหลวใดออกมา -
ตัวเลขทุกตัวในบทนี้มาจาก
scripts/integer-semantics/measured.jsonช่องpipelineซึ่งscripts/integer-semantics/probe.pyเขียนขึ้นหลังยิงทั้งชุดสามรอบต่อการเรียกหนึ่งครั้ง · source ของท่อทั้งสองเส้นอยู่ที่scripts/integer-semantics/runners/pipeline/main.go· ช่องpipelineกับ runner ของมันลง repo ในคอมมิตเดียวกัน วัดบนgo1.27.0บน darwin/arm64 เมื่อ 2026-09-13
เช็กความเข้าใจ — บทที่ 8
ข้อ 1 / 3ท่อเปล่าเดินครบทั้งสี่ขั้นบน body ใบนี้ แล้วมีกี่ขั้นที่รายงานความล้มเหลวออกมา