วิธีการควบคุมการเข้าถึง class ใน package ของเรา

วิธีการควบคุมการเข้าถึง class ใน package ของเรา

TLDR จาก docs.flutter.dev/packages-and-plugins/devel...

By convention, implementation code is placed under lib/src. Code under lib/src is considered private; other packages should never need to import src/.... To make APIs under lib/src public, you can export lib/src files from a file that’s directly under lib.

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

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

ปัญหาหลักเกิดจากการนำเข้าคลาสส่วนใหญ่จะเป็น widget โดยตรงจากไฟล์เลย ทำให้มีบรรทัด import หลายบรรทัด หากเราไม่มีการเซ็ตอัพที่ถูกต้อง ผู้ใช้จะสามารถ importโดยตรงเนื่องจากคลาสใน Dart ถูกเปิดเผยเป็นสาธารณะโดยอัตโนมัติ ซึ่งปัญหาเหล่านี้จริงๆแล้วสามารถป้องกันได้ง่ายมาก เดี๋ยวเราลองดูตัวอย่างจากโครงสร้างโปรเจคด้านล่างนี้นะครับ

ui_package_example_1/
├── lib/
│   ├── widgets/
│   │   ├── custom_button.dart
│   │   └── custom_text_field.dart
│   │   └── custom_icon.dart
│   ├── custom_widgets.dart
├── pubspec.yaml
└── README.md
// custom_widgets.dart
export '../lib/widgets/custom_button.dart';
export '../lib/widgets/custom_text_field.dart';
export '../lib/widgets/custom_icon.dart';

พอมาถึงตรงนี้เราอาจจะคิดว่าการสร้างไฟล์สำหรับ export ซึ่งก็คือ custom_widgets.dart ในโปรเจคนี้จะแก้ปัญหาได้ คำตอบคือใช่แต่เป็นเพียงบางส่วนเท่านั้น custom_widgets.dart จะช่วยให้ผู้ใช้ import ไฟล์เดียวเพื่อใช้วิดเจ็ตจากแพ็คเกจทั้งหมดได้ทีเดียว. แต่ต้นตอของปัญหาไม่ได้อยู่ที่การเพิ่มวิธีการนำเข้าเพิ่มเติม แม้มีไฟล์ export นี้ ผู้ใช้งานแพ็คเกจยังสามารถนำเข้าจากไฟล์วิดเจ็ตโดยตรงได้อยู่ดี ปัญหาจริงๆก็คือไฟล์เหล่านี้ยังถูกเปิดเผยต่อผู้ใช้และ IntelliSense ใน IDE ของเราก็ยังแสดงเส้นทางการนำเข้าโดยตรงอยู่ดี และถ้ายังมีเส้นทางให้มีการ import ตรงได้ผู้ใช้งานก็จะทำ ส่วนใหญ่น่าจะไม่ได้ตั้งใจด้วยซ้ำแต่เพราะ IntelliSense มันแนะนำให้เอง

การแก้ปัญหาที่จริงแล้วง่ายมาก เพียงแค่วางไฟล์ที่เราไม่ต้องการเปิดเผยอัตโนมัติในโฟลเดอร์ src ตามโครงสร้างโปรเจคด้านล่างนี้

ui_package_example_2/
├── src/
│   ├── widgets/
│   │   ├── custom_button.dart
│   │   └── custom_text_field.dart
│   │   └── custom_icon.dart
│   ├── custom_widgets.dart
├── pubspec.yaml
└── README.md

อัพเดต custom_widgets.dart ด้วย

// custom_widgets.dart
export '../lib/src/custom_button.dart';
export '../lib/src/custom_text_field.dart';
export '../lib/src/custom_icon.dart';

เพื่อความเรียบง่ายเราแค่เปลี่ยนโฟลเดอร์ widgets เป็นโฟลเดอร์ src ซึ่งในโปรเจ็กต์จริงเราก็ควรสร้างโครงสร้างโฟลเดอร์ที่มีความหมายสำหรับโปรเจ็กต์ในโฟลเดอร์ src อีกที

เพียงเท่านี้คนใช้งานแพ็คเกจก็จะสามารถ import เฉพาะสิ่งที่เราต้องการจะ export ใน custom_widgets.dart ได้เท่านั้น ซึ่งจริงๆก็คือง่ายมากอย่างที่เห็น แต่ถ้าเราไม่รู้เรื่องนี้แล้วมาแก้ทีหลัง ก็งานเข้ากันไปจ้า

Good Practices เพิ่มเติม

ใช้ show และไม่ใช้ part

นอกจากการใช้โครงสร้างโฟลเดอร์ src แล้วหากไฟล์ของคุณมีคลาสหลายตัวและคุณต้องการส่งออกเพียงบางส่วนคุณสามารถใช้คำสั่ง show เหมือนในตัวอย่างด้านล่างนี้

// custom_widgets.dart
export '../lib/src/custom_button.dart' show CustomButton, buttonStyleEnum;
export '../lib/src/custom_text_field.dart' show CustomTextFied;
export '../lib/src/custom_icon.dart' show CustomIcon;

คุณอาจเคยได้ยินเกี่ยวกับการใช้ part ในการจัดการไลบรารีเพิ่มเติมจากการใช้ show ซึ่งแนวทางปฏิบัติของ Dart แนะนำให้หลีกเลี่ยงการใช้ part

Note: You may have heard of the part directive, which allows you to split a library into multiple Dart files. We recommend that you avoid using part and create mini libraries instead.

การ Import ภายในแพ๊คเกจกันเอง

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

When importing a library file from your own package, use a relative path when both files are inside of lib, or when both files are outside of lib. Use package: when the imported file is in lib and the importer is outside.

สรุป

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

อ้างอิง