22 คะแนน โดย GN⁺ 2024-09-04 | 5 ความคิดเห็น | แชร์ทาง WhatsApp
  • เมื่อต้องดูแลรักษาโค้ดเบสที่ไม่คุ้นเคย เรามักใช้เวลาไปมากกับการค้นหาสตริง
  • แม้แต่ในโปรเจ็กต์ที่เขียนคนเดียว ก็ยังต้องค้นหาหลายอย่าง เช่น ชื่อฟังก์ชัน ข้อความ error ชื่อคลาส เป็นต้น
  • ถ้าค้นหาได้ไม่ดี ก็เสี่ยงที่จะหา reference ในโค้ดเบสไม่เจอ และอาจถูกมองว่าเป็นสิ่งที่ไม่จำเป็น
  • จากสถานการณ์แบบนี้ จึงสรุปออกมาเป็นกฎบางข้อที่ใช้รักษา Greppability ของโค้ดเบสได้

อย่าแยก identifier ออกเป็นส่วน ๆ

  • การแยก identifier หรือประกอบขึ้นแบบไดนามิกเป็นความคิดที่ไม่ค่อยดี
  • สมมติว่ามีตารางฐานข้อมูลสองตารางชื่อ shipping_addresses และ billing_addresses การประกอบชื่อตารางแบบไดนามิกตามประเภทของคำสั่งซื้ออาจดูดีในตอนแรก
const getTableName = (addressType: 'shipping' | 'billing') => {  
    return `${addressType}_addresses`  
}  
  • มันอาจดูเป็น DRY แต่ไม่ดีต่อการดูแลรักษา เพราะถ้ามีใครค้นหาชื่อตาราง shipping_addresses ในโค้ดเบส ก็อาจพลาดส่วนนี้ไป
  • การ hardcode identifier กลับเป็นวิธีที่ดีกว่า
  • โค้ดที่รีแฟกเตอร์เพื่อให้ค้นหาได้ง่ายขึ้น:
const getTableName = (addressType: 'shipping' | 'billing') => {  
    if (addressType === 'shipping') {  
        return 'shipping_addresses'  
    }  
    if (addressType === 'billing') {  
        return 'billing_addresses'  
    }  
    throw new TypeError('addressType must be billing or shipping')  
}  
  • หลักการเดียวกันนี้ใช้ได้กับชื่อคอลัมน์ ฟิลด์ของอ็อบเจ็กต์ และชื่อเมธอด/ฟังก์ชันด้วย (ใน JavaScript การประกอบชื่อเมธอดแบบไดนามิกทำได้ง่ายมาก)

ใช้ชื่อเดียวกันตลอดทั้งสแตก

  • อย่าเปลี่ยนชื่อฟิลด์ตรงขอบเขตของแอปพลิเคชันเพียงเพื่อให้เข้ากับรูปแบบการตั้งชื่อ
  • ตัวอย่างคลาสสิกคือการนำ identifier แบบ snake_case ของ PostgreSQL เข้ามาใน JavaScript แล้วแปลงเป็น camelCase ซึ่งไม่ใช่แนวทางที่ดี
  • เพราะมันทำให้ค้นหาได้ยากขึ้น ถ้าจะหาทั้งหมดให้เจอ ก็ต้องค้นสองสตริงแทนที่จะเป็นสตริงเดียว
const getAddress = async (id: string) => {  
    const address = await getAddressById(id)  
    return {  
        streetName: address.street_name,  
        zipCode: address.zip_code,  
    }  
}  
  • ทางที่ดีกว่าคือคืนค่าอ็อบเจ็กต์กลับไปตรง ๆ
const getAddress = async (id: string) => {  
    return await getAddressById(id)  
}  

แบบแบนดีกว่าแบบซ้อนลึก

  • ได้แรงบันดาลใจจาก Zen ของ Python เมื่อต้องจัดการกับ namespace โดยมากแล้วการทำให้โครงสร้างโฟลเดอร์/อ็อบเจ็กต์แบนจะดีกว่าการซ้อนลึก
  • หากมีตัวเลือกสองแบบสำหรับการตั้งค่าไฟล์แปลภาษา:
{  
    "auth": {  
        "login": {  
            "title": "Login",  
            "emailLabel": "Email",  
            "passwordLabel": "Password",  
        },  
        "register": {  
            "title": "Register",  
            "emailLabel": "Email",  
            "passwordLabel": "Password",  
        }  
    }  
}  
{  
    "auth.login.title": "Login",  
    "auth.login.emailLabel": "Email",   
    "auth.login.passwordLabel": "Password",  
    "auth.register.title": "Login",  
    "auth.register.emailLabel": "Email",  
    "auth.register.passwordLabel": "Password",  
}  
  • ควรเลือกตัวเลือกที่สอง เพราะค้นหาคีย์ได้ง่าย และอ้างอิงได้แบบ t('auth.login.title')
  • เมื่อลองดูโครงสร้างของ React component:
./components/AttributeFilterCombobox.tsx  
./components/AttributeFilterDialog.tsx  
./components/AttributeFilterRating.tsx  
./components/AttributeFilterSelect.tsx  
  • แบบนี้จะดีกว่าโครงสร้างต่อไปนี้
./components/attribute/filter/Combobox.tsx  
./components/attribute/filter/Dialog.tsx  
./components/attribute/filter/Rating.tsx  
./components/attribute/filter/Select.tsx  
  • ในมุมมองของการค้นหา เพราะเราสามารถค้นหาชื่อคอมโพเนนต์แบบเต็มที่มี namespace รวมอยู่ด้วย เช่น AttributeFilterCombobox แทนชื่อทั่วไปอย่าง Dialog ได้

ความเห็นของ GN⁺

  • บล็อกโพสต์นี้อธิบายความสำคัญของการค้นหา identifier เมื่อต้องดูแลรักษาโค้ดเบสได้เป็นอย่างดี
  • การประกอบ identifier แบบไดนามิก หรือเปลี่ยนชื่อที่ขอบเขตของแอปพลิเคชัน ทำให้การดูแลโค้ดยากขึ้น identifier ควรสม่ำเสมอและชัดเจน
  • ในทางกลับกัน การ hardcode identifier และคง namespace ให้แบน จะดีกว่าในมุมมองของการค้นหา
  • เป็นแนวคิดที่ควรนำไปใช้ในโปรเจ็กต์เพื่อเพิ่มความอ่านง่ายและความสามารถในการดูแลรักษาของโค้ด
  • นอกเหนือจากกฎที่ผู้เขียนเสนอ ยังมีวิธีอื่นอีกมากในการยกระดับคุณภาพโค้ด เช่น การเขียน Self-Documenting Code และการใช้คอมเมนต์ที่มีความหมาย

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

 
nowpark 2024-09-06

มีเครื่องมืออีกตัวที่ช่วยแปลงเป็น full path ของ json เพื่อให้ค้นหาด้วย grep ได้ง่าย เลยขอฝากไว้ด้วยครับ!

https://th.news.hada.io/topic?id=3159

 
botplaysdice 2024-09-05

ดีนะ... greppability นี่แหละ...

 
ahwjdekf 2024-09-04

ข้อมูลที่มีประโยชน์ถ้าเขียนให้อยู่ในบรรทัดเดียวเท่าที่ทำได้ก็ดูจะมีประโยชน์เหมือนกัน

 
roxie 2024-09-09

ดีครับ

 
GN⁺ 2024-09-04
ความคิดเห็นจาก Hacker News
  • การค้นหาสัญลักษณ์อย่างชื่อฟังก์ชันและชื่อคลาสนั้นด้อยกว่าการใช้เครื่องมือที่เข้าใจไวยากรณ์ของโค้ด

    • แค่ฟีเจอร์ "Go to Definition" และ "Find Usages" ก็ช่วยลดความจำเป็นในการค้นหาด้วยข้อความได้มาก
    • ตลอด 10 ปีที่ผ่านมา ส่วนใหญ่ค้นหาเฉพาะสตริงที่ผู้ใช้มองเห็น
    • โพสต์แบบนี้หมายความว่าผู้เขียนควรลงทุนเวลาเรียนรู้เครื่องมือที่ดีกว่าและเหมาะกับภาษาของตัวเอง
    • แค่มี IDE ที่ดีก็ประหยัดเวลาได้มาก
  • คิดว่าน่าจะมีประโยชน์ถ้าเครื่องมือ grep มีโหมด "ไม่แยกตัวพิมพ์เล็กใหญ่แบบซูเปอร์"

    • เช่น ขยายการค้นหา "FooBar|first_name" ให้จับคู่ได้กับทุกรูปแบบของเคส
    • นึกแทบไม่ออกว่ามีสถานการณ์ไหนที่ฟีเจอร์นี้ไม่ควรเป็นค่าเริ่มต้น
  • เห็นด้วยกับเรื่องความสามารถในการ grep

    • ในภาษาสวีเดนมีคำอย่าง "grep-bar" หรือ "grep-barhet" ใช้อยู่จริง
    • "greppbar" หมายถึง "ที่เข้าใจได้" และ "greppbarhet" หมายถึง "ความสามารถในการเข้าใจได้"
  • ตอนออกแบบ Hamilton เป้าหมายคือทำให้สามารถ grep นิยามของฟังก์ชันและการใช้งานส่วนลูกของมันได้ง่าย

    • ในโลกของการแปลงข้อมูลด้วย Python สร้างโค้ดเบสที่ grep แทบไม่ช่วยอะไรได้ง่ายมาก
  • 'greppable' ไม่ใช่คำ/แนวคิดที่นิยมใช้กันอยู่แล้วในตัวมันเอง

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

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

    • เพื่อให้เมื่อเห็นสตริงในผลลัพธ์ของโปรแกรมแล้ว จะสามารถค้นหาสตริงเดียวกันนั้นในโค้ดได้
  • Rust, Javascript, Lisp ใส่คีย์เวิร์ดไว้หน้าการนิยามฟังก์ชัน ทำให้ค้นหาได้ง่าย

    • C ไม่มีคีย์เวิร์ดแบบนี้ จึงค้นหาได้แค่ชื่อฟังก์ชัน
    • กฎการเขียนโค้ดของ C บางแบบจะแยกนิยามออกเป็นสองบรรทัดเพื่อให้ค้นหาได้ง่ายขึ้น
  • เห็นด้วยกับ greppability แต่ไม่เห็นด้วยกับการคงชื่อเดียวกันไว้ข้ามขอบเขต

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

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