Bẫy bảo trì: Tại sao việc sao chép-dán code giết chết năng suất
Hầu hết các nhà phát triển cuối cùng đều gặp phải một rào cản khi họ cảm thấy mệt mỏi với việc sao chép các hàm tiện ích giống nhau giữa các dự án. Bạn có thể viết một hàm hoàn hảo để định dạng ngày tháng hoặc một wrapper API tùy chỉnh. Hai tuần sau, bạn bắt đầu một dự án mới và thấy mình đang lục lọi trong các thư mục cũ để tìm tệp cụ thể đó. Bạn sao chép nó qua, nhưng sau đó bạn phát hiện ra một lỗi. Bây giờ bạn phải sửa nó ở cả hai nơi. Lặp lại điều này trên mười dự án, và bạn không còn là một nhà phát triển nữa—bạn là một cỗ máy đồng bộ hóa thủ công.
Gần đây tôi đã làm việc với một nhóm gồm 12 người, nơi mọi microservice đều có phiên bản tiện ích logger riêng. Khi chúng tôi cần cập nhật định dạng nhật ký (log) để đáp ứng yêu cầu bảo mật mới, chúng tôi đã phải mở 15 kho lưu trữ khác nhau. Chúng tôi đã mất 40 giờ làm việc thủ công lặp đi lặp lại chỉ để áp dụng một thay đổi code dài 10 dòng. Đó là một sự lãng phí lớn đối với tài năng kỹ thuật.
Nguyên nhân gốc rễ: Sự rào cản trong phân phối gói
Tại sao chúng ta cứ tiếp tục sao chép-dán thay vì xây dựng một thư viện? Thông thường, đó là vì việc thiết lập ban đầu mang lại cảm giác nặng nề. Nhiều nhà phát triển phải vật lộn với ba trở ngại cụ thể:
- Bối rối về công cụ: Việc quyết định giữa
setup.py,Poetry, hoặcHatchcho Python, hoặc quản lý cácexportsphức tạp trong tệppackage.jsoncủa JavaScript. - Rủi ro về độ ổn định: Nỗi sợ rằng một thay đổi nhỏ có thể làm hỏng các dự án phụ thuộc phía sau nếu không có một bộ kiểm thử (test) mạnh mẽ.
- Áp lực phát hành thủ công: Sự lo lắng về việc vô tình xuất bản một phiên bản bị lỗi hoặc rò rỉ các khóa API nhạy cảm trong quá trình tải lên từ terminal cá nhân.
Nếu không có một quy trình làm việc chuẩn hóa, “chi phí” để tạo ra một thư viện có vẻ cao hơn “chi phí” của việc sao chép-dán. Tuy nhiên, nợ kỹ thuật về lâu dài luôn đắt đỏ hơn.
Đánh giá các lựa chọn chia sẻ của bạn
Trước khi bắt đầu tự động hóa, hãy xem cách hầu hết các nhóm cố gắng giải quyết vấn đề chia sẻ:
- Git Submodules: Bạn liên kết một kho lưu trữ này bên trong một kho lưu trữ khác. Điều này nghe có vẻ hiệu quả nhưng việc quản lý các phiên bản thường là một cơn ác mộng. Nó thường xuyên làm hỏng các pipeline CI/CD khi các quyền truy cập không được đồng bộ hoàn hảo.
- Thư mục chia sẻ nội bộ: Cách này chỉ hoạt động nếu mọi người ở trong cùng một mạng nội bộ. Nó thất bại ngay khi bạn cần mở rộng quy mô hoặc chia sẻ mã với cộng đồng rộng lớn hơn.
- Các Registry công khai (npm và PyPI): Đây là tiêu chuẩn công nghiệp. Nó cung cấp Quản lý phiên bản theo ngữ nghĩa (SemVer), cài đặt dễ dàng qua
piphoặcnpm, và quản lý phụ thuộc tự động.
Lợi ích thực sự về hiệu quả đến từ việc tự động hóa quá trình phát hành. Bạn sẽ không bao giờ phải chạy npm publish hoặc twine upload từ máy tính xách tay cá nhân của mình nữa.
Bước 1: Cấu trúc dự án để thành công
Một thư viện cần một bố cục cụ thể để được các trình quản lý gói nhận diện. Dưới đây là một cấu trúc sạch sẽ cho các dự án đa nền tảng.
Đối với thư viện Python (PyPI)
Phát triển Python hiện đại sử dụng pyproject.toml. Tệp này tuân theo các tiêu chuẩn PEP 517 và PEP 518, thay thế cho cách tiếp cận setup.py và requirements.txt vốn bị phân mảnh.
my-python-lib/
├── src/
│ └── my_library/
│ ├── __init__.py
│ └── core.py
├── tests/
│ └── test_core.py
├── pyproject.toml
├── README.md
└── LICENSE
Tệp pyproject.toml của bạn xác định hệ thống build và siêu dữ liệu (metadata):
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "my-awesome-lib"
version = "0.1.0"
description = "Một tiện ích hiệu suất cao để xử lý dữ liệu"
requires-python = ">=3.8"
authors = [{ name = "Tên của bạn", email = "[email protected]" }]
Đối với thư viện JavaScript/TypeScript (npm)
Đối với npm, package.json là cốt lõi. Tôi khuyên bạn nên sử dụng TypeScript cho các thư viện để cung cấp các định nghĩa kiểu (type definitions) tự động cho người dùng.
my-js-lib/
├── src/
│ └── index.ts
├── dist/
├── tests/
│ └── index.test.ts
├── package.json
├── tsconfig.json
└── README.md
Trong package.json, hãy chỉ định mảng files để giữ cho gói của bạn gọn nhẹ. Chỉ bao gồm thư mục dist đã được biên dịch, không bao gồm mã nguồn của bạn:
{
"name": "@your-username/my-js-lib",
"version": "1.0.0",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": ["dist"],
"scripts": {
"build": "tsc",
"test": "vitest run"
}
}
Bước 2: Viết các bài kiểm thử đáng tin cậy
Bạn không thể xuất bản một thư viện mà không có kiểm thử. Nếu thư viện của bạn bị lỗi, bạn sẽ làm hỏng mọi ứng dụng phụ thuộc vào nó. Tôi đã sử dụng phương pháp này trong các môi trường sản xuất nơi tính ổn định là yếu tố không thể thương lượng. Đối với Python, hãy sử dụng pytest. Đối với JavaScript, vitest hiện là lựa chọn nhanh nhất và hiện đại nhất. Đừng chạy theo độ bao phủ (coverage) 100%. Thay vào đó, hãy tập trung vào việc kiểm thử API công khai: nếu người dùng nhập X, liệu họ có luôn nhận được Y không?
# Ví dụ kiểm thử Python đơn giản
def test_addition():
from my_library.core import add
assert add(2, 3) == 5
Bước 3: Tự động hóa phát hành với GitHub Actions
Quá trình tự động hóa sẽ bắt đầu bất cứ khi nào bạn tạo một thẻ “Release” mới trên GitHub. Điều này đảm bảo môi trường build sạch sẽ và loại bỏ cái cớ “chạy tốt trên máy của tôi”.
Workflow cho PyPI (Sử dụng Trusted Publishers)
Tạo tệp .github/workflows/pypi-publish.yml. PyPI hiện hỗ trợ “Trusted Publishing” thông qua OIDC. Điều này có nghĩa là bạn không cần lưu trữ mật khẩu hoặc token trong GitHub Secrets.
name: Phát hành lên PyPI
on:
release:
types: [published]
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- uses: actions/checkout@v4
- name: Thiết lập Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Build và Phát hành
run: |
pip install build
python -m build
- uses: pypa/gh-action-pypi-publish@release/v1
Workflow cho npm
Tạo tệp .github/workflows/npm-publish.yml. Bạn sẽ cần thêm một NPM_TOKEN vào GitHub Repository Secrets để quy trình này hoạt động.
name: Phát hành lên npm
on:
release:
types: [published]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20.x'
registry-url: 'https://registry.npmjs.org'
- run: npm ci
- run: npm run build
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} # Sử dụng token từ bí mật của GitHub
Các thực hành tốt nhất để bảo trì
Xuất bản code chỉ là một nửa chặng đường. Để làm cho thư viện của bạn hữu ích với người khác, hãy ghi nhớ bốn nguyên tắc sau:
- Quản lý phiên bản theo ngữ nghĩa (SemVer): Sử dụng định dạng
MAJOR.MINOR.PATCH. Nếu bạn thay đổi tên hàm làm hỏng mã hiện có, hãy tăng phiên bản Major. - README là giao diện của bạn: Một thư viện không có README coi như không tồn tại. Hãy bao gồm một đoạn code 5 dòng mà người dùng có thể sao chép để thấy kết quả ngay lập tức.
- Chọn một Giấy phép (License): Sử dụng MIT hoặc Apache 2.0 để được chấp nhận tối đa. Nhiều công ty bị cấm về mặt pháp lý khi sử dụng mã thiếu tệp LICENSE rõ ràng.
- Giữ các phụ thuộc gọn nhẹ: Mỗi thư viện bạn thêm vào sẽ trở thành gánh nặng cho người dùng của bạn. Nếu bạn có thể tự viết một hàm 10 dòng, hãy tránh thêm một thư viện phụ thuộc nặng 200KB.
Chuyển mã của bạn sang một registry toàn cầu sẽ thay đổi quan điểm của bạn về phát triển. Nó buộc bạn phải thiết kế các giao diện sạch sẽ hơn và viết tài liệu tốt hơn. Khi pipeline GitHub Actions của bạn đã hoạt động, việc phát hành một phiên bản mới chỉ đơn giản là nhấp vào một nút. Quy trình làm việc này loại bỏ rào cản của việc chia sẻ và cho phép bạn tập trung vào việc xây dựng các tính năng thay vì quản lý các tệp tin.

