นากแม่น้ำต้นฉบับกำลังสอดบัตรรหัสผ่านกรอบตรวจที่ต้องพอดีทั้งใบ ไม่ใช่เพียงมีบางส่วนตรงกัน

Java Pattern.asMatchPredicate: ใช้ regex แบบตรงทั้งข้อความใน Stream

พี่โชว์Engineering

เวลา filter Stream ด้วย regex คำว่า “match” มีสองความหมายครับ: ทั้งข้อความต้องตรง หรือแค่ค้นเจอบางช่วง Pattern.asMatchPredicate() ใช้ Matcher.matches() จึงตรงทั้ง input ส่วน asPredicate() ใช้ find() และยอมให้มีข้อความก่อนหรือหลัง

WHY: ชื่อคล้าย แต่ขอบเขต validation ต่างกัน

ถ้ารหัสงานต้องเป็น JOB- ตามด้วยตัวเลข การค้นเจอบางช่วงจะยอมรับ note JOB-42 done ทั้งที่ input ไม่ใช่รหัสล้วน เหมือน รปภ. ตรวจว่ามีบัตรอยู่ในกระเป๋า แทนที่จะตรวจว่าบัตรที่ยื่นมาทั้งใบถูกต้อง เลือก predicate ผิดตัวทำให้ validation หลวมโดยไม่มี compile error

HOW: กรองเฉพาะค่าที่ตรงทั้งข้อความ

import java.util.List;
import java.util.function.Predicate;
import java.util.regex.Pattern;

public class Main {
  public static void main(String[] args) {
    Pattern jobId = Pattern.compile("JOB-[0-9]+");
    Predicate<String> fullMatch = jobId.asMatchPredicate();

    List<String> values = List.of(
        "JOB-7",
        "note JOB-42 done",
        "JOB-x",
        "JOB-108");

    List<String> accepted = values.stream().filter(fullMatch).toList();
    assert accepted.equals(List.of("JOB-7", "JOB-108")) : accepted;

    assert jobId.asPredicate().test("note JOB-42 done");
    assert !fullMatch.test("note JOB-42 done");
  }
}

การ compile Pattern ครั้งเดียวแล้ว reuse ลดพิธีและเลี่ยง compile regex ซ้ำในทุก element แต่ regex ยังอาจกิน CPU มากเมื่อ pattern มี backtracking รุนแรง โดยเฉพาะ input ที่ผู้โจมตีควบคุม จำกัดความยาว input และออกแบบ pattern ให้ง่ายก่อนนำไปใช้ที่ขอบ API

Decision table

เจตนาAPI
validation ทั้งค่าasMatchPredicate()
ค้นหาบางช่วงในข้อความasPredicate()
ต้องใช้กลุ่ม capturematcher() แล้วอ่าน group
input ปริมาณมาก/ไม่เชื่อถือจำกัดขนาดและ benchmark pattern

อย่าเติม ^...$ แล้วสลับ API ไปมาโดยไม่จำเป็น เพราะ anchor กับ line mode มีรายละเอียดของตัวเอง การเลือกเมธอดที่บอก semantics ตรงทำให้ review ง่ายกว่า

Recap

  • ใช้ asMatchPredicate สำหรับ full-string validation
  • ใช้ asPredicate เมื่อเจตนาคือค้นเจอบางส่วน
  • compile Pattern ครั้งเดียวแล้ว reuse
  • จำกัด input และตรวจ regex ที่เสี่ยง backtracking