Thuật ngữ codebase là gì có lẽ không còn xa lạ với các nhà phát triển phần mềm. Tuy nhiên khái niệm này vẫn còn mới đối với ai không hiểu rõ về code hoặc mới bắt đầu code. Vậy codebase là gì và tầm quan trọng của nó ra sao? Bài viết này của Aptech sẽ giúp bạn giải đáp những thắc mắc trên, đồng thời giới thiệu các nguyên tắc thiết kế codebase hiệu quả để bạn quản lý và phát triển dự án một cách khoa học.
Codebase là gì?
Codebase, hay còn gọi là cơ sở mã, là tập hợp toàn bộ mã nguồn của một chương trình, thành phần hoặc hệ thống phần mềm. Codebase bao gồm mọi tệp cần thiết để biên soạn phần mềm thành mã máy và các tệp cấu hình. Mã nguồn thường được viết bằng các ngôn ngữ lập trình mà con người có thể đọc được như Java, C#, Python, JavaScript, hay văn bản thuần túy và ngôn ngữ đánh dấu mở rộng. Ngoài ra, codebase còn chứa các tệp tin hỗ trợ trong việc hiểu, triển khai hoặc sử dụng ứng dụng, chẳng hạn như các tệp readme, tập lệnh mẫu, thông tin về giấy phép hoặc các tài liệu hướng dẫn khác.
![Codebase là gì? Codebase là gì?](https://aptechbmt.edu.vn/wp-content/uploads/2024/07/codebase-la-gi.jpg)
Nguyên tắc thiết kế Codebase
Sau khi bạn đã hiểu được Codebase là gì, để xây dựng một Codebase hiệu quả, tuân theo các nguyên tắc thiết kế là rất quan trọng. Dưới đây là những nguyên tắc then chốt giúp bạn tạo dựng một Codebase dễ hiểu, dễ bảo trì, dễ mở rộng cho dự án phần mềm của mình
Composition
Nguyên tắc thiết kế codebase của React chủ yếu dựa trên sự kết hợp của các component. Mỗi component được phát triển bởi các lập trình viên khác nhau, thì vẫn có thể hoạt động hài hòa trong hệ thống. Mục tiêu của nguyên tắc là cho phép bổ sung tính năng vào một component mà không làm ảnh hưởng đến toàn bộ codebase.
Ví dụ, bạn có thể thêm state vào một component mà không cần thay đổi các component khác đang sử dụng nó. Tương tự, bạn cũng có thể thêm code khởi tạo hoặc hủy bỏ vào bất kỳ component nào khi cần thiết. Việc sử dụng state và lifecycle method trong các component không sai, nhưng bạn cần sử dụng chúng một cách hợp lý. Ngoài ra, chúng đóng vai trò quan trọng làm cho React trở nên hữu ích.
Mặc dù các component thường được mô tả như các function đơn giản, nhưng chúng cần nhiều hơn thế để trở nên hữu ích. Trong React, các component mô tả bất kỳ hành vi có thể kết hợp nào, bao gồm rendering, lifecycle, và state. Một số thư viện bên ngoài như Relay còn bổ sung các trách nhiệm khác như quản lý phụ thuộc dữ liệu.
![Composition Composition](https://aptechbmt.edu.vn/wp-content/uploads/2024/07/composition.jpg)
Common Abstraction
Thông thường, chúng tôi không ủng hộ việc thêm các tính năng có thể được triển khai trong môi trường của người dùng vào React. Chúng tôi không muốn làm ứng dụng của bạn trở nên cồng kềnh với những đoạn mã thư viện không cần thiết. Tuy nhiên, có những ngoại lệ đối với nguyên tắc này. Chẳng hạn, nếu React không cung cấp các phương thức vòng đời hoặc các state cục bộ, người dùng sẽ tự phát triển các abstraction riêng cho chúng. Khi có nhiều abstraction cạnh tranh, React không thể tối ưu hóa hoặc áp dụng các thuộc tính của một trong số chúng mà phải hoạt động theo “mẫu chung thấp nhất”.
Đó là lý do đôi khi chúng tôi thêm các tính năng vào chính React. Nếu chúng tôi nhận thấy nhiều component đang triển khai một tính năng nhất định theo những cách không tương thích hoặc không hiệu quả, chúng tôi có thể chọn tích hợp nó vào React. Chúng tôi không đưa ra quyết định này một cách tùy tiện. Khi chúng tôi thực hiện, đó là vì chúng tôi tin rằng việc nâng cao mức độ abstraction sẽ mang lại lợi ích cho toàn bộ hệ sinh thái.
Escape Hatches
React là một công cụ thực dụng, được phát triển để đáp ứng nhu cầu của các sản phẩm tại Facebook. Mặc dù nó chịu ảnh hưởng từ một số mô hình không hoàn toàn chính thống như lập trình chức năng, mục tiêu chính của dự án là duy trì khả năng tiếp cận cho các nhà phát triển với đa dạng kỹ năng và kinh nghiệm.
Khi chúng tôi quyết định ngừng sử dụng một mẫu không ưa thích, chúng tôi có trách nhiệm xem xét mọi trường hợp sử dụng hiện tại và cung cấp hướng dẫn cho cộng đồng về các lựa chọn thay thế trước khi loại bỏ nó. Nếu một mẫu cụ thể hữu ích cho việc xây dựng ứng dụng nhưng khó thể hiện theo cách khai báo, chúng tôi sẽ tạo ra một API bắt buộc cho nó.
Trong trường hợp chúng tôi không thể tìm ra một API hoàn hảo cho một tính năng cần thiết trong nhiều ứng dụng, chúng tôi sẽ cung cấp một API tạm thời để giải quyết vấn đề, với điều kiện rằng nó có thể bị loại bỏ sau này và mở ra cơ hội cho những cải tiến trong tương lai.
![](https://aptechbmt.edu.vn/wp-content/uploads/2024/07/escape-hatches.jpg)
Stability
React là một công cụ thực dụng, được phát triển để đáp ứng nhu cầu của các sản phẩm tại Facebook. Mặc dù nó chịu ảnh hưởng từ một số mô hình không hoàn toàn chính thống như lập trình chức năng, mục tiêu chính của dự án là duy trì khả năng tiếp cận cho các nhà phát triển với đa dạng kỹ năng và kinh nghiệm.
Khi React quyết định ngừng sử dụng một mẫu không ưa thích, React phải xem xét mọi trường hợp sử dụng hiện tại và cung cấp hướng dẫn cho cộng đồng về các lựa chọn thay thế trước khi loại bỏ nó. Nếu một mẫu cụ thể hữu ích cho việc xây dựng ứng dụng nhưng khó thể hiện theo cách khai báo, React sẽ tạo ra một API bắt buộc cho nó. Trong trường hợp React không thể tìm ra một API hoàn hảo cho một tính năng cần thiết trong nhiều ứng dụng, React sẽ cung cấp một API tạm thời để giải quyết vấn đề, với điều kiện rằng nó có thể bị loại bỏ sau này và mở ra cơ hội cho những cải tiến trong tương lai.
Interoperability
Khả năng tương tác với các hệ thống hiện có và áp dụng dần dần là rất quan trọng. Tại Facebook, React có một kho mã lớn không phải là React, với trang web sử dụng XHP cho hệ thống thành phần phía máy chủ, các thư viện giao diện người dùng nội bộ trước khi có React, và chính React. Điều quan trọng là mọi nhóm sản phẩm có thể bắt đầu sử dụng React cho một tính năng nhỏ thay vì viết lại mã hiện tại.
Đây là lý do tại sao React cung cấp các cửa sổ thoát (escape hatches) để làm việc với các mô hình có thể thay đổi và đảm bảo tích hợp tốt với các thư viện giao diện người dùng khác. Người dùng có thể bao bọc giao diện người dùng hiện có thành một thành phần khai báo (declarative component) và ngược lại. Điều này rất quan trọng để triển khai dần dần mà không làm gián đoạn hệ thống.
![](https://aptechbmt.edu.vn/wp-content/uploads/2024/07/interoperability.jpg)
Scheduling
React không phải là một thư viện xử lý dữ liệu tổng quát, mà là một công cụ để xây dựng giao diện người dùng. Khi sử dụng React, các component của bạn không được gọi trực tiếp như một function bình thường. Thay vào đó, mỗi component trả về một mô tả về cách hiển thị và mô tả này có thể bao gồm các component như <LikeButton> do người dùng viết và các phần tử HTML như <div>. React sẽ xử lý việc “giải nén” các component này vào cây UI vào một thời điểm sau và áp dụng các thay đổi theo cách đệ quy.
React có thể hoãn việc gọi chúng nếu cần vì bạn không gọi trực tiếp các function component này. Trong cách triển khai hiện tại, React duyệt cây UI và gọi các hàm render của toàn bộ cây được cập nhật trong một lần đánh dấu. Trong tương lai, nó có thể trì hoãn một số bản cập nhật để tránh giảm FPS.
Điều này phân biệt rõ ràng giữa phương pháp “push” và phương pháp “pull” trong thiết kế React. Phương pháp “pull” cho phép React quản lý việc lên lịch và thực thi các bản cập nhật theo cách có thể điều chỉnh và tối ưu hóa.
Một trong những lý do React muốn giữ quyền kiểm soát lập lịch là để đảm bảo rằng các tính toán được thực hiện đúng lúc và đúng cách, nhằm tránh giảm hiệu suất và tối đa hóa trải nghiệm người dùng. Bằng cách này, React có thể ưu tiên các hoạt động quan trọng hơn như tương tác người dùng so với các công việc nền.
Điều này cũng giải thích vì sao setState() trong React là không đồng bộ. Nó là cách để “lên lịch một cập nhật” thay vì thực hiện ngay lập tức, giúp giữ cho ứng dụng luôn mượt mà và có hiệu suất cao. Với quy mô và tính phức tạp của các ứng dụng tại Facebook, việc đảm bảo tính nhất quán và hiệu quả của lập lịch là rất quan trọng.
Developer Experience
Việc duy trì công cụ React DevTools để giúp bạn kiểm tra component tree React trong Chrome và Firefox. Công cụ này đã được chứng minh là giúp tăng năng suất đáng kể cho cả các kỹ sư tại Facebook và cộng đồng sử dụng React.
Ngoài ra, React cũng nỗ lực đưa ra các cảnh báo hữu ích cho các developer. Ví dụ, React sẽ cảnh báo bạn trong quá trình phát triển nếu bạn sử dụng các thẻ theo cách mà trình duyệt không hiểu hoặc nếu bạn mắc các lỗi đánh máy phổ biến trong API. Những cảnh báo này và các kiểm tra liên quan là lý do chính khiến phiên bản phát triển của React chậm hơn so với phiên bản sản xuất.
React học hỏi từ các mô hình sử dụng trong nội bộ Facebook để hiểu rõ các lỗi thường gặp và cách phòng tránh chúng từ sớm. Khi phát triển các tính năng mới, React luôn cố gắng dự đoán các lỗi tiềm ẩn và đưa ra các cảnh báo thích hợp.
![Developer Experience Developer Experience](https://aptechbmt.edu.vn/wp-content/uploads/2024/07/developer-experience.jpg)
Debugging
Trong React, khi xảy ra sự cố, việc có breadcrumbs để theo dõi lỗi đến nguồn gốc của nó trong codebase là rất quan trọng. Props và state trong React đóng vai trò như những breadcrumbs này.
Nếu bạn phát hiện điều gì đó không đúng trên giao diện người dùng, bạn có thể mở React DevTools, xác định component chịu trách nhiệm hiển thị và kiểm tra props và state để xem liệu chúng có chính xác hay không. Nếu mọi thứ đều đúng, vấn đề có thể nằm ở trong hàm render() của component hoặc một số hàm được gọi từ render().
Nếu state không chính xác, bạn biết rằng sự cố có thể do lệnh setState() trong file đó gây ra. Việc xác định và sửa lỗi thường đơn giản vì thường chỉ có một vài lệnh setState() trong một file duy nhất. Nếu props không chính xác, bạn có thể duyệt qua component tree trong inspector để tìm ra component đầu tiên “nhiễm độc” props không tốt.
Khả năng theo dõi từ giao diện người dùng đến dữ liệu đã tạo ra nó thông qua props và state là vô cùng quan trọng đối với React. Mục tiêu thiết kế rõ ràng là không để state bị “mắc kẹt” trong closures và combinators, mà là có sẵn cho React trực tiếp.
Mặc dù giao diện người dùng là động, React coi việc render() đồng bộ theo props và state là quan trọng để làm cho quá trình gỡ lỗi từ dự đoán thành một quy trình chặt chẽ và có hạn.
Configuration
React nhận thấy rằng việc sử dụng các tùy chọn cấu hình thời gian chạy toàn cầu có thể gặp phải nhiều vấn đề. Ví dụ, đôi khi React nhận được yêu cầu triển khai các hàm như React.configure(options) hoặc React.register(component). Tuy nhiên, điều này có thể gây ra nhiều vấn đề, đặc biệt là khi các hàm này được gọi từ thư viện component của bên thứ ba. Nếu một ứng dụng React nhúng một ứng dụng React khác và cấu hình mong muốn của chúng không tương thích, sẽ có nguy cơ xảy ra xung đột và lỗi không dự đoán được. Làm sao để một component của bên thứ ba có thể chỉ định rằng nó yêu cầu một cấu hình cụ thể cũng là một vấn đề phức tạp.
Việc cung cấp cấu hình toàn cầu không phải là một giải pháp tốt cho môi trường React. Vì component là trung tâm của React và muốn giữ sự rõ ràng và tính độc lập của từng component. Ngoài ra, React không khuyến khích sử dụng cấu hình toàn cầu trong mã.
Thay vào đó, React cung cấp các tùy chọn cấu hình ở cấp độ xây dựng, ví dụ như bản dựng development và production riêng biệt. Điều này giúp chúng tôi quản lý và kiểm soát cách mà từng phiên bản ứng dụng React được cấu hình mà không ảnh hưởng đến các thành phần khác. React cũng có thể xem xét thêm các cấu hình bản dựng khác như bản dựng profiling trong tương lai nếu có nhu cầu.
![Configuration Configuration](https://aptechbmt.edu.vn/wp-content/uploads/2024/07/configuration.jpg)
Optimized for Tooling
Một số API thường có tên dài dòng khi được sử dụng trong mã của React. Ví dụ, React sử dụng componentDidMount() thay vì didMount() hoặc onMount(). Mục đích của việc này là để làm cho các điểm tương tác với thư viện dễ nhận thấy.
Trong một codebase lớn tại Facebook, khả năng tìm kiếm và hiểu các API cụ thể là vô cùng quan trọng. React đánh giá cao việc sử dụng các tên dài dòng để đặc biệt là các tính năng nên được sử dụng một cách tiết kiệm. Ví dụ, dangerouslySetInnerHTML là một ví dụ điển hình, mà nó rất khó để bỏ qua trong quá trình review mã.
Việc tối ưu hóa cho việc tìm kiếm cũng rất quan trọng, vì React dựa vào codemods để thực hiện các thay đổi quy mô lớn. React muốn việc áp dụng các thay đổi này tự động trở nên dễ dàng và an toàn, và việc sử dụng các tên dài dòng duy nhất giúp chúng tôi đạt được mục tiêu này. Ngoài ra, các tên riêng biệt cũng giúp chúng tôi dễ dàng viết các quy tắc lint rules tùy chỉnh về cách sử dụng React mà không cần lo lắng về khả năng phạm lỗi.
JSX cũng đóng vai trò quan trọng tương tự trong mã của React. Mặc dù nó không bắt buộc khi sử dụng với React, nhưng nó được sử dụng rộng rãi trên Facebook vì lý do thẩm mỹ và thực tiễn.
Trong codebase của React, JSX cung cấp một gợi ý rõ ràng cho các công cụ xử lý React element tree. Điều này giúp React có thể tối ưu hóa thời gian xây dựng bằng cách nâng cấp các phần tử hằng số, sử dụng codemod để tái sử dụng thành phần nội bộ và áp dụng lint rules một cách an toàn, đồng thời cung cấp vị trí nguồn của JSX trong các cảnh báo.
Kết luận
Tóm lại, thiết kế Codebase hiệu quả là chìa khóa cho sự thành công của dự án phần mềm. Với những thông tin về Codebase là gì và các nguyên tắc thiết kế được trình bày trong bài viết này, bạn có thể xây dựng một Codebase dễ hiểu, dễ bảo trì, dễ mở rộng và linh hoạt, đảm bảo phát triển phần mềm một cách hiệu quả và lâu dài.