ตารางการตั้งค่าส่วนกลาง (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) หรือบั๊กในโค้ดของตนเอง

แนวทางปฏิบัติที่ปลอดภัยกว่าสำหรับนักพัฒนาโมดูล

  1. ทำ Namespacing ให้กับทุก key – เติมชื่อทางเทคนิคของโมดูลไว้หน้า key การตั้งค่าทุกตัว (เช่น my_module_width) วิธีนี้จะช่วยสร้างตัวระบุที่เป็นเอกลักษณ์โดยไม่ต้องพึ่งพาคอลัมน์แสดงความเป็นเจ้าของแยกต่างหาก
  2. หลีกเลี่ยงการลบ key เก่าที่ไม่มี prefix – การปล่อยให้มีแถวข้อมูลที่ล้าสมัยเหลืออยู่ในตารางเพียงไม่กี่แถวแทบจะไม่สิ้นเปลืองพื้นที่จัดเก็บเลย และยังช่วยขจัดความเสี่ยงที่จะเกิดความเสียหายต่อส่วนอื่น (collateral damage)
  3. ตรวจสอบความเป็นเจ้าของก่อนการลบ – หากจำเป็นต้องลบจริงๆ ให้ตรวจสอบก่อนว่าค่าของ key นั้นถูกตั้งค่าโดยโค้ดของคุณเอง (ตัวอย่างเช่น การเก็บค่า marker ที่มีเพียงโมดูลของคุณเท่านั้นที่ทราบ)
  4. ตรวจสอบวิธีการถอนการติดตั้ง – ค้นหาการเรียกใช้ deleteByName ใน codebase โดยควรตรวจสอบทุกจุดเพื่อให้แน่ใจว่า key นั้นมีการทำ namespacing ที่เป็นเอกลักษณ์แล้ว
  5. จัดทำเอกสารรูปแบบการตั้งชื่อ – ระบุแนวทางปฏิบัติสั้นๆ ไว้ในไฟล์ 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) และชื่อเสียงที่เสียไป—อาจมีมูลค่าสูงกว่านั้นมาก