ตารางการตั้งค่าส่วนกลาง (global configuration table) ของ PrestaShop ทำให้ขั้นตอนการถอนการติดตั้ง (uninstall routine) ของโมดูลหนึ่งสามารถลบการตั้งค่าที่เป็นของส่วนขยาย (extensions) อื่นที่ไม่เกี่ยวข้องกันเลยได้อย่างง่ายดาย เพียงแค่การเรียกใช้ Configuration::deleteByName('width') เพียงครั้งเดียว ก็สามารถลบค่าความกว้าง (width) ที่กำหนดเองของร้านค้าอื่นทิ้งไปได้ ทำให้เจ้าของร้านเกิดความสับสน ในขณะที่โมดูลที่เป็นต้นเหตุกลับดูเหมือนไม่มีอันตรายใดๆ
ทำไมตารางการตั้งค่าที่ใช้ร่วมกันจึงมีความสำคัญ
PrestaShop เก็บการตั้งค่าของทุกโมดูลไว้ในตารางเดียวซึ่งประกอบด้วยเพียง key และ value เท่านั้น ตารางนี้ไม่มีคอลัมน์ที่บันทึกว่าโมดูลใดเป็นผู้สร้างแถวนั้นๆ และไม่มีการบังคับใช้รูปแบบการตั้งชื่อ (naming convention) ใดๆ ส่งผลให้โมดูลสองตัวที่บังเอิญใช้ key เดียวกัน เช่น “width” หรือ “API_DATE_FROM” จะอ่านและเขียนข้อมูลลงในแถวเดียวกันในฐานข้อมูล โดยการเขียนครั้งล่าสุดจะเป็นผู้ชนะ และการลบใดๆ ที่เกิดขึ้นหลังจากนั้นจะลบแถวข้อมูลนั้นออกไปสำหรับทั้งสองโมดูล
เมื่อโค้ดถอนการติดตั้งกลายเป็นเครื่องมือลบข้อมูล
วิธีการถอนการติดตั้งทั่วไปจะมีลักษณะดังนี้:
public function uninstall()
{
return Configuration::deleteByName('width');
}
ความตั้งใจคือเพื่อล้างการตั้งค่าของตัวโมดูลเอง แต่เนื่องจาก key ไม่ได้มีการทำ namespacing คำสั่งนี้จึงลบแถวข้อมูล ใดๆ ก็ตาม ที่ชื่อว่า “width” โดยไม่มีการบันทึกคำเตือน (warning) หรือการโยน exception ใดๆ แถวข้อมูลนั้นจะหายไปเฉยๆ ส่งผลให้โมดูลอื่นๆ ของเจ้าของร้านสูญเสียการตั้งค่าไปอย่างเงียบๆ และอาจเริ่มทำงานผิดปกติ
ปัญหานี้จะยิ่งน่ากังวลเป็นพิเศษในช่วงการอัปเกรดเวอร์ชัน นักพัฒนาที่เปลี่ยนจากเวอร์ชัน 1.0 เป็น 2.0 อาจเริ่มบันทึกค่าภายใต้ key ที่มี prefix เช่น MY_MODULE_WIDTH และเพื่อ "ล้าง" ข้อมูลเก่าที่ไม่มี prefix พวกเขาจึงเพิ่มคำสั่งลบลงในขั้นตอนการถอนการติดตั้ง โดยเชื่อว่าพวกเขากำลังลบเพียงแค่ข้อมูลเก่า (legacy data) เท่านั้น แต่ในความเป็นจริง พวกเขากำลังลบข้อมูลของส่วนขยายอื่นๆ ที่เก็บไว้ภายใต้ key ทั่วไป (generic key) เดียวกันด้วย
สิ่งที่การตรวจสอบ (audit) ค้นพบ
การตรวจสอบคลังเก็บโมดูลสาธารณะ (public module repositories) จำนวน 57 แห่ง เผยให้เห็นรูปแบบที่เกิดขึ้นซ้ำๆ ดังนี้:
- โมดูลจำนวนมากใช้ key ทั่วไป เช่น “width”, “height” หรือ “API_DATE_FROM” โดยไม่มี prefix ที่มาจากชื่อโมดูล
- วิธีการถอนการติดตั้งหลายวิธีมีการเรียกใช้
Configuration::deleteByNameที่พุ่งเป้าไปที่ key ทั่วไปเหล่านี้ - ปัญหานี้ไม่ได้จำกัดอยู่แค่เพียงนักพัฒนาคนใดคนหนึ่งหรือโมดูลประเภทใดประเภทหนึ่งเท่านั้น แต่การออกแบบตารางที่ใช้ร่วมกันทำให้มันกลายเป็นความเสี่ยงเชิงระบบ (systemic risk)
การตรวจสอบไม่พบ log หรือข้อความแสดงข้อผิดพลาดใดๆ ที่จะแจ้งเตือนเจ้าของร้านว่าการตั้งค่าของโมดูลอื่นถูกลบออกไป อาการเพียงอย่างเดียวที่พบคือการสูญเสียการตั้งค่าอย่างกะทันหัน ซึ่งเจ้าของร้านอาจเข้าใจผิดว่าเป็นปัญหาจากแคช (cache) หรือบั๊กในโค้ดของตนเอง
แนวทางปฏิบัติที่ปลอดภัยกว่าสำหรับนักพัฒนาโมดูล
- ทำ Namespacing ให้กับทุก key – เติมชื่อทางเทคนิคของโมดูลไว้หน้า key การตั้งค่าทุกตัว (เช่น
my_module_width) วิธีนี้จะช่วยสร้างตัวระบุที่เป็นเอกลักษณ์โดยไม่ต้องพึ่งพาคอลัมน์แสดงความเป็นเจ้าของแยกต่างหาก - หลีกเลี่ยงการลบ key เก่าที่ไม่มี prefix – การปล่อยให้มีแถวข้อมูลที่ล้าสมัยเหลืออยู่ในตารางเพียงไม่กี่แถวแทบจะไม่สิ้นเปลืองพื้นที่จัดเก็บเลย และยังช่วยขจัดความเสี่ยงที่จะเกิดความเสียหายต่อส่วนอื่น (collateral damage)
- ตรวจสอบความเป็นเจ้าของก่อนการลบ – หากจำเป็นต้องลบจริงๆ ให้ตรวจสอบก่อนว่าค่าของ key นั้นถูกตั้งค่าโดยโค้ดของคุณเอง (ตัวอย่างเช่น การเก็บค่า marker ที่มีเพียงโมดูลของคุณเท่านั้นที่ทราบ)
- ตรวจสอบวิธีการถอนการติดตั้ง – ค้นหาการเรียกใช้
deleteByNameใน codebase โดยควรตรวจสอบทุกจุดเพื่อให้แน่ใจว่า key นั้นมีการทำ namespacing ที่เป็นเอกลักษณ์แล้ว - จัดทำเอกสารรูปแบบการตั้งชื่อ – ระบุแนวทางปฏิบัติสั้นๆ ไว้ในไฟล์ README ของโมดูล เพื่อให้ผู้ร่วมพัฒนาในอนาคตเข้าใจถึงความสำคัญของการใช้ key ที่มี prefix
การทดสอบเพื่อป้องกันการลบข้อมูลข้ามโมดูลโดยไม่ตั้งใจ
วิธีการที่นำไปใช้ได้จริงในการตรวจจับบั๊กก่อนที่จะส่งผลกระทบต่อร้านค้าที่ใช้งานจริง:
- ใส่ข้อมูลเริ่มต้น (Seed) ในตารางการตั้งค่า ด้วย key ที่เป็นของโมดูลอื่น (เช่น
other_module_setting=>test) - รันขั้นตอนการถอนการติดตั้งของโมดูลในสภาพแวดล้อมที่ควบคุมได้
- ตรวจสอบ (Assert) ว่า key ที่ใส่ไว้ตอนแรกยังคงอยู่หลังจากกระบวนการถอนการติดตั้งเสร็จสิ้น
การทำให้การตรวจสอบนี้เป็นอัตโนมัติในชุดการทดสอบหน่วย (unit-test suite) ของโมดูล จะช่วยให้มั่นใจได้ว่าการเปลี่ยนแปลงใดๆ ในอนาคตที่ทำให้เกิดการเรียกใช้ deleteByName ที่ผิดพลาดจะทำให้การทดสอบล้มเหลว และนำไปสู่การตรวจสอบอีกครั้ง
สิ่งที่เจ้าของร้านควรสังเกต
เจ้าของร้านแทบจะไม่มีโอกาสได้เห็นแถวข้อมูลภายในฐานข้อมูล แต่พวกเขาสามารถสังเกตเห็นอาการได้ นั่นคือ หลังจากปิดใช้งานหรือถอนการติดตั้งโมดูลหนึ่งแล้ว ส่วนขยายอื่นๆ กลับคืนสู่การตั้งค่าเริ่มต้นอย่างกะทันหัน หากเกิดเหตุการณ์เช่นนี้ ให้ขอให้ผู้พัฒนายืนยันว่าโค้ดการถอนการติดตั้งของโมดูลนั้นเคารพกฎของตารางการตั้งค่าส่วนกลาง และขอรายการ key การตั้งค่าทั้งหมดที่โมดูลนั้นใช้ หากพบ key ใดที่ไม่มี prefix ที่ชัดเจน นั่นคือสัญญาณอันตราย (red flag)
บทสรุป
การออกแบบของ PrestaShop ทำให้ตารางการตั้งค่า (configuration table) กลายเป็นทรัพยากรที่ใช้ร่วมกัน และขั้นตอนการถอนการติดตั้งที่ขาดความระมัดระวังอาจลบการตั้งค่าของโมดูลอื่นทิ้งไปโดยไม่ทิ้งร่องรอยใดๆ ด้วยการทำ namespacing ให้กับคีย์, หลีกเลี่ยงการล้างข้อมูลที่รุนแรงเกินไป และการเพิ่มการทดสอบง่ายๆ เพื่อปกป้องข้อมูลของโมดูลอื่น นักพัฒนาจะสามารถป้องกันการสูญหายของข้อมูลโดยไม่รู้ตัว และช่วยให้ร้านค้าของผู้ประกอบการมีความเสถียร ความพยายามที่ต้องใช้นั้นน้อยมาก แต่ความเสียหายจากการสูญเสียการตั้งค่า—ไม่ว่าจะเป็นการร้องเรียนจากลูกค้า, ตั๋วแจ้งปัญหา (support tickets) และชื่อเสียงที่เสียไป—อาจมีมูลค่าสูงกว่านั้นมาก
