- เมื่อต้องดูแลรักษาโค้ดเบสที่ไม่คุ้นเคย เรามักใช้เวลาไปมากกับการค้นหาสตริง
- แม้แต่ในโปรเจ็กต์ที่เขียนคนเดียว ก็ยังต้องค้นหาหลายอย่าง เช่น ชื่อฟังก์ชัน ข้อความ 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 ความคิดเห็น
มีเครื่องมืออีกตัวที่ช่วยแปลงเป็น full path ของ json เพื่อให้ค้นหาด้วย grep ได้ง่าย เลยขอฝากไว้ด้วยครับ!
https://th.news.hada.io/topic?id=3159
ดีนะ... greppability นี่แหละ...
ข้อมูลที่มีประโยชน์ถ้าเขียนให้อยู่ในบรรทัดเดียวเท่าที่ทำได้ก็ดูจะมีประโยชน์เหมือนกัน
ดีครับ
ความคิดเห็นจาก Hacker News
การค้นหาสัญลักษณ์อย่างชื่อฟังก์ชันและชื่อคลาสนั้นด้อยกว่าการใช้เครื่องมือที่เข้าใจไวยากรณ์ของโค้ด
คิดว่าน่าจะมีประโยชน์ถ้าเครื่องมือ grep มีโหมด "ไม่แยกตัวพิมพ์เล็กใหญ่แบบซูเปอร์"
เห็นด้วยกับเรื่องความสามารถในการ grep
ตอนออกแบบ Hamilton เป้าหมายคือทำให้สามารถ grep นิยามของฟังก์ชันและการใช้งานส่วนลูกของมันได้ง่าย
'greppable' ไม่ใช่คำ/แนวคิดที่นิยมใช้กันอยู่แล้วในตัวมันเอง
เคยเห็นตัวอย่างที่ซับซ้อนซึ่งใช้การแทรกสตริงแบบมีเงื่อนไข
สไตล์การเขียนโค้ดและเครื่องมือจำนวนมากจะคงค่าคงที่สตริงไว้ในบรรทัดเดียวโดยไม่สนความยาวบรรทัด
Rust, Javascript, Lisp ใส่คีย์เวิร์ดไว้หน้าการนิยามฟังก์ชัน ทำให้ค้นหาได้ง่าย
เห็นด้วยกับ greppability แต่ไม่เห็นด้วยกับการคงชื่อเดียวกันไว้ข้ามขอบเขต
การค้นหาโค้ดได้ง่ายเป็นเรื่องดี แต่ตัวอย่างนี้จงใจเพิ่มโอกาสเกิดข้อผิดพลาด