กระรอกดินสีฟ้าเขียวต้นฉบับใช้กรรไกรตัดสายลูกปัดตรงปมสุดท้ายบนโต๊ะสีครีม

Go 1.27 bytes.CutLast: แยกส่วนท้ายโดยไม่ Index เอง

พี่โชว์Engineering

ชื่อ object, cache key และ log field มักมี separator ซ้ำ เช่น tenant/order/retry แล้วเราต้องแยกตรงเครื่องหมายสุดท้ายครับ เดิมต้องหา LastIndex เช็ก -1 แล้วคำนวณตำแหน่งเอง จุดเสี่ยงไม่ใช่อัลกอริทึมยาก แต่เป็น branch เล็ก ๆ ที่คนลืมกรณีไม่มี separator

Go 1.27 เพิ่ม bytes.CutLast ให้คืน before, after และ found ในรูปเดียวกับ bytes.Cut แต่เลือก occurrence สุดท้าย API จึงบอกเจตนาและบังคับให้ผู้เรียกเห็นกรณีไม่พบ

WHY: ทำ Edge Case ให้มองเห็นจากรูปทรง API

ถ้าข้อมูลเป็น []byte อยู่แล้ว การแปลงเป็น string เพื่อใช้ strings.LastIndex สร้างงานและทำให้ ownership คลุมเครือ CutLast คืน subslice ที่ชี้เข้า backing array เดิม จึงไม่ต้องคัดลอก แต่ข้อดีนี้มาพร้อมกติกา: แก้ input แล้วผลลัพธ์เปลี่ยนตาม และการเก็บชิ้นเล็กไว้นานอาจรั้ง buffer ใหญ่ทั้งก้อน

ตัวอย่างนี้แยก suffix หลัง slash ตัวสุดท้าย และคัดลอกผลลัพธ์เมื่อจะเก็บข้ามอายุ request

package cutlast

import (
    "bytes"
    "testing"
)

func splitSuffix(input []byte) (prefix, suffix []byte, ok bool) {
    prefix, suffix, ok = bytes.CutLast(input, []byte("/"))
    if !ok {
        return bytes.Clone(input), nil, false
    }
    return bytes.Clone(prefix), bytes.Clone(suffix), true
}

func TestSplitSuffix(t *testing.T) {
    prefix, suffix, ok := splitSuffix([]byte("tenant/order/retry"))
    if !ok || string(prefix) != "tenant/order" || string(suffix) != "retry" {
        t.Fatalf("unexpected split: %q %q %v", prefix, suffix, ok)
    }

    original, suffix, ok := splitSuffix([]byte("single"))
    if ok || string(original) != "single" || suffix != nil {
        t.Fatalf("missing delimiter contract changed")
    }
}

HOW: เลือก Cut, CutLast หรือ Split

ใช้ bytes.Cut เมื่อ separator แรกแบ่ง header กับ payload ใช้ CutLast เมื่อ suffix สุดท้ายเป็นชนิดไฟล์ revision หรือ action และใช้ Split เมื่อจำเป็นต้องเดินทุกส่วนจริง ๆ การ Split เพื่อหยิบเพียงชิ้นท้ายสร้าง slice header หลายชิ้นโดยไม่จำเป็น

กรณี separator อยู่ต้นข้อมูล before จะว่าง ถ้าอยู่ท้าย after จะว่าง ทั้งสองกรณี found ยังเป็น true ต้องตัดสินใจทางธุรกิจเองว่าค่าว่างยอมรับได้หรือไม่ หาก input มาจาก protocol ที่ห้าม empty segment ให้ validate ต่ออย่างชัดเจน

ถ้าเพียงอ่านและคืนผลทันที subslice แบบ zero-copy เหมาะกว่า bytes.Clone แต่ถ้าผลจะเข้า cache หรือ queue ระยะยาว การ clone ปล่อย buffer ต้นฉบับได้เร็วกว่า วัด memory profile ก่อนเปลี่ยนทั้งระบบ

Recap

  • CutLast แยกที่ separator ตัวสุดท้ายและคืน found
  • empty before/after ไม่เท่ากับไม่พบ separator
  • ผลลัพธ์ zero-copy อ้าง backing array เดิม
  • clone เมื่อเก็บชิ้นเล็กนานกว่าบัฟเฟอร์ใหญ่
  • เลือก Cut, CutLast หรือ Split ตามจำนวนส่วนที่ต้องใช้

ลองแทน LastIndex ที่มีการบวกหนึ่งเองสักจุด แล้วเพิ่ม test สำหรับไม่มี separator กับ separator ท้ายข้อมูลครับ