0

Ác mộng Cannot redeclare class: Đừng biến source code của bạn thành bãi rác tiền tố (prefix) nữa!

Chào anh em thiện lành! Đi code dạo nhiều năm, chắc hẳn anh em nào cũng từng trải qua cái cảnh "vò đầu bứt tai" khi nghĩ tên cho biến, cho class. Người ta hay đùa rằng: "Có 2 thứ khó nhất trong ngành khoa học máy tính: Xóa cache và Đặt tên".

Hôm nay, mình sẽ kể cho anh em nghe về một "cú vấp" liên quan đến việc đặt tên, và cách mà khái niệm Language Namespaces (Không gian tên) đã lôi mình ra khỏi vũng lầy của những đoạn code dài lê thê, xấu xí.

1. Mở bài: Đêm sập server vì một class mang tên Transaction

Nhớ lại hồi mình mới đi làm 1-2 năm, lúc đó đang maintain một hệ thống e-commerce bằng PHP thuần (thời đó chưa xài Framework xịn xò như bây giờ). Trong hệ thống, mình có tự viết một class tên là Transaction để lưu lịch sử nạp tiền của user. Mọi thứ chạy ngon ơ.

Rồi một ngày, sếp yêu cầu tích hợp thêm một cổng thanh toán mới. Mình hăm hở lên Github tải bộ SDK của bên cổng thanh toán đó về, require vào project, và... BÙM! Trắng trang. Mở file log lên thì đập vào mắt là dòng chữ đỏ lòm: Fatal error: Cannot redeclare class Transaction in /vendor/payment/...

Hóa ra, trong cái SDK của bên thứ 3, họ cũng có một class tên là Transaction. Hai class cùng tên "dẫm chân" lên nhau, và trình biên dịch không biết phải dùng cái nào.

Để fix nhanh, đêm đó mình hì hục đi đổi tên class của mình thành MyProject_Transaction. Thế là phải lùng sục khắp source code, chỗ nào gọi Transaction là phải sửa lại hết. Sửa xong lại nơm nớp lo sợ: "Nhỡ ngày mai cài thêm thư viện xuất file PDF, nó lại có class tên là Pdf_Transaction thì sao?". Mình nhận ra, cứ đà này, tên class của mình sẽ dài ngoằng và source code trông không khác gì một bãi rác tiền tố (prefix).

2. Hành trình giác ngộ: Namespace là cái quái gì?

Giữa lúc tuyệt vọng, mình bắt đầu nghiên cứu xem các Framework hiện đại họ giải quyết bài toán này thế nào. Và mình va phải khái niệm Namespace.

Hiểu một cách dân dã nhất, Namespace giống hệt như Hệ thống thư mục (Folders) trên máy tính của bạn. Bạn không thể tạo 2 file tên là baocao.docx trên cùng một màn hình Desktop. Nhưng bạn hoàn toàn có thể tạo 1 file baocao.docx trong thư mục KeToan, và 1 file baocao.docx khác nằm trong thư mục NhanSu. Hai file này chung sống hòa bình, không ai đụng ai.

Namespace trong lập trình (như C++, C#, Java, PHP...) cũng y chang như vậy. Nó tạo ra một "ranh giới ảo" để gom nhóm các class/hàm/biến lại với nhau.

Nhìn lại thảm họa đặt tên ngày xưa: Trước khi có Namespace, để tránh trùng lặp, các bậc tiền bối (đặc biệt là dân code WordPress hoặc Zend Framework) phải gộp cả cấu trúc thư mục vào tên class. Nó sinh ra những con quái vật như thế này:

// Code kiểu cổ đại, không có Namespace
$table = new Zend_Db_Table_Abstract();
$user = new MyCompany_Auth_User_Model();

Viết xong cái tên class là muốn gãy luôn ngón tay!

Và đây là lúc Namespace tỏa sáng: Thay vì dùng tiền tố dài dòng, mình khai báo không gian tên cho class của mình ở ngay đầu file.

// File: src/Models/Transaction.php
namespace App\Models;

class Transaction {
    // Logic giao dịch nội bộ
}

Bên SDK của cổng thanh toán, họ cũng tự bọc code của họ lại:

// File: vendor/payment/Transaction.php
namespace Vendor\PaymentGateway;

class Transaction {
    // Logic của cổng thanh toán
}

Bây giờ, khi cần dùng cả 2 class trong cùng một controller, mình chỉ việc use (import) và gán bí danh (alias) cho chúng. Code trở nên "sạch như Ngọc Trinh":

namespace App\Controllers;

// Import vào và gọi đúng tên "cúng cơm" của nó
use App\Models\Transaction as LocalTransaction;
use Vendor\PaymentGateway\Transaction as PaymentTransaction;

class CheckoutController {
    public function process() {
        // Khởi tạo class của mình
        $localTx = new LocalTransaction(); 
        
        // Khởi tạo class của thư viện
        $gatewayTx = new PaymentTransaction(); 
    }
}

3. Bài học rút ra (The Takeaways)

Từ khi giác ngộ Namespace, tư duy tổ chức code của mình thăng hạng rõ rệt:

  1. Chấm dứt nạn "đụng hàng": Bạn có thể thoải mái đặt những cái tên ngắn gọn, có ý nghĩa (như User, Order, Request) mà không sợ bị trùng với thư viện của người khác hay các module khác trong team.
  2. Tổ chức logic rõ ràng: Nhìn vào namespace App\Http\Controllers, bạn biết ngay class này đang làm nhiệm vụ gì, thuộc tầng nào trong kiến trúc (Architecture).
  3. Trái tim của Autoloading: Ở các ngôn ngữ như PHP (với chuẩn PSR-4) hay C#, Namespace chính là bản đồ để hệ thống tự động tìm và load đúng file vật lý chứa class đó vào bộ nhớ mà không cần bạn phải require hay include thủ công từng file một.

Lời kết

Namespace có thể là một khái niệm cơ bản mà bạn được học ngay trong những ngày đầu tiếp cận với C++, C# hay PHP 5.3+. Nhưng để thực sự hiểu giá trị của nó, đôi khi chúng ta phải trải qua những đêm sấp mặt đi fix lỗi Cannot redeclare class. Nó không chỉ là cú pháp ngôn ngữ, mà là một công cụ để bạn kiến trúc (architect) lại source code của mình một cách chuyên nghiệp.

Anh em thì sao? Trong các project hiện tại, team anh em có quy chuẩn nào (Convention) cho việc chia nhỏ Namespace không? Ví dụ chia theo Feature (Domain Driven Design) hay chia theo Layer (MVC)? Cùng comment chia sẻ bên dưới để chúng ta cùng đàm đạo nhé!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí