First things first

Xin chào các bạn, mình là Thanh Tùng, một lập trình viên với N năm kinh nghiệm.

Trong quá trình làm việc nhóm, mỗi người có một phong cách viết code khác nhau gây khó khăn cho việc đọc và bảo trì code dẫn đến tăng thời gian phát triển, giảm hiệu suất của dự án.

Điều đó thôi thúc mình viết bài viết này để chia sẻ kinh nghiệm của mình về việc sử dụng các công cụ để nâng cao chất lượng code, cụ thể là đối với Python.

Một vài lý do để chuẩn hóa code:

  • Code dễ đọc và bảo trì hơn
  • Giảm thiểu bugs và lỗi tiềm ẩn
  • Tăng hiệu quả code review
  • Đảm bảo mọi người trong team tuân theo cùng một chuẩn
  • Vân vân và mây mây

Đến đây, nếu bạn đang tìm kiếm cách để nâng cao chất lượng code Python của bạn, thì đây chính là bài viết dành cho bạn.

Mình cùng bắt đầu nhé!

My example repo

Các công cụ chính mà mình sử dụng

1. Black - The Uncompromising Code Formatter

Black là một code formatter tự động cho Python. Điểm đặc biệt của Black là nó “không thỏa hiệp” - nghĩa là bạn không cần phải cấu hình nhiều, Black sẽ format code theo một chuẩn nhất định.

Cài đặt Black:

pip install black

Sử dụng Black:

Format một file

black your_file.py

Format tất cả file trong thư mục

black .

Ví dụ

Trước khi sử dụng Black:

def foo(

):
    print ("Hello world!")
    print   ("Hello world!")
    print   (
        "Hello world!"
        )

Sau khi sử dụng Black:

def foo():
    print("Hello world!")
    print("Hello world!")
    print("Hello world!")

Trông chuẩn hơn rồi đúng không?

2. Flake8 - Style Guide Enforcement

Flake8 là một công cụ linting phổ biến, kết hợp các tính năng của PyFlakes, pycodestyle và Ned Batchelder’s McCabe script.

Cài đặt Flake8:

pip install flake8

Sử dụng Flake8:

flake8 your_file.py

Ví dụ

Code không hợp lệ:

def my_function():
    # This is a really long comment that exceeds the 79-character limit and causes a line length error in Flake8.
    return 42

Flake8 sẽ báo lỗi:

examples/use_flake8.py:3:80: E501 line too long (113 > 79 characters)

Code hợp lệ:

def my_function():
    # This is a really long comment that exceeds the 79-character limit 
    # and causes a line length error in Flake8.
    return 42

3. Pylint - A Tool for Style Checks

Pylint là một công cụ phân tích và đánh giá code Python. Nó kiểm tra code theo nhiều tiêu chuẩn khác nhau, từ độ dài dòng, độ sâu lồng ghép, đến việc sử dụng biến, hàm, lớp, …

Cài đặt Pylint:

pip install pylint

Sử dụng Pylint:

pylint your_file.py

Ví dụ

Code không hợp lệ:

def my_function():
    return 42

Pylint sẽ báo lỗi:

# ************* Module try_pylint
# your_file.py:2:0: C0304: Final newline missing (missing-final-newline)
# your_file.py:1:0: C0114: Missing module docstring (missing-module-docstring)

# -----------------------------------
# Your code has been rated at 0.00/10

Code hợp lệ:

"""This is a docstring for the module."""
def my_function():
    """This is a docstring for the function."""
    return 42

Đến đây liệu bạn có tự hỏi flake8 và pylint đều là linter nhưng có gì khác nhau không?

Câu trả lời là có, và đó là:

  • Flake8 là linter đơn giản hơn, chỉ kiểm tra một số vấn đề cơ bản như độ dài dòng, định dạng, …
  • Pylint là linter phức tạp hơn, kiểm tra nhiều vấn đề hơn, như là độ dài dòng, định dạng, …

Cụ thể: xem thêm

ProblemPylintFlake8
Unnecessary semicolon
Missing newlines
Missing whitespaces
Missing docstring
Unused arguments
Incorrect naming style
Selecting element from the list with incorrect index
Dividing by zero expression
Missing return statement
Calling method that doesn’t exist
Unnecessary pass statement

4. Commitlint - Linting Git Commit Messages

Commitlint là một công cụ linting commit message, giúp đảm bảo các commit message tuân thủ theo quy chuẩn nhất định.

Đến đây, mình lại nhớ đến ông anh, thằng bạn, thằng em mình thường viết commit message rất ối dồi ôi :))
Bad Commit Message

Hay là như thế này:

Bad Commit Message

Tất nhiên là viết như thế thì chẳng ai có thể hiểu được, kể cả tác giá (sau vài ngày). Và để giải quyết vấn đề này, mình đã sử dụng Commitlint.

Commitlint quy định (mặc định):

  • Commit message header từ 10 đến 50 ký tự

    • => Không được viết test, fix bug hay update ui nữa rồi ^^
  • Commit message body từ 0 đến 72 ký tự

  • Commit message footer từ 0 đến 72 ký tự

  • Cấu trúc commit message theo chuẩn Conventional Commits:

    • Phải bắt đầu bằng type (feat, fix, docs, style, refactor, test, chore)
    • Sau type là dấu : và description
    • Ví dụ: feat: add new login feature
    • Type có thể là:
      • build: Những thay đổi ảnh hưởng build hoặc dependecies (ví dụ: thay đổi version của package, sửa docker file, …)
      • ci: Những thay đổi đối với các cấu hình và kịch bản CI (ví dụ: Gitlab CI, Circle, BrowserStack, SauceLabs)
      • chore: Những thay đổi lặt vặt (ví dụ: .gitignore, .pre-commit-config.yaml, …)
      • docs: Chỉ thay đổi về tài liệu (ví dụ: README.md, CHANGELOG.md, …)
      • feat: Thêm/bớt tính năng
      • fix: Sửa lỗi
      • perf: Những thay đổi nhằm cải thiện hiệu suất
      • refactor: Những thay đổi về tên, cấu trúc code mà không làm thay đổi logic (ví dụ: đổi tên biến, hàm, lớp; tách nhỏ hàm, lớp, tệp…)
      • revert: Hoàn tác một commit trước đó
      • style: Những thay đổi không ảnh hưởng đến ý nghĩa của code (ví dụ: thêm khoảng trắng, định dạng, thiếu dấu chấm phẩy, v.v.)
      • test: Thêm/sửa test

Xem thêm: Conventional Commits

Vậy làm sao để sử dụng được Commitlint? Mời các bạn đến phần tiếp theo.

Pre-commit - Git Hooks Made Easy

Pre-commit giúp tự động hóa việc kiểm tra code trước khi commit, đảm bảo code luôn đạt chuẩn trước khi được push lên repository.

Cài đặt pre-commit (1 lần):

pip install pre-commit

Cấu hình pre-commit hooks trong file .pre-commit-config.yaml:

  • Cấu hình Black
repos:
...
-   repo: https://github.com/psf/black
    rev: 25.1.0
    hooks:
    -   id: black
        language_version: python3.10
  • Cấu hình Flake8
repos:
...
-   repo: https://github.com/PyCQA/flake8
    rev: 7.1.2
    hooks:
    -   id: flake8
        language_version: python3.10
  • Cấu hình Pylint
repos:
...
-   repo: https://github.com/pylint-dev/pylint
    rev: v3.3.4
    hooks:
    -   id: pylint
        language_version: python3.10
  • Cấu hình Commitlint
repos:
...
-   repo: https://github.com/opensource-nepal/commitlint
    rev: v1.3.0
    hooks:
    - id: commitlint

Cài đặt commit hooks

pre-commit install
pre-commit install --hook-type commit-msg

Sau khi cài đặt, mỗi khi commit, pre-commit sẽ kiểm tra code của bạn và báo lỗi nếu không đạt chuẩn.

Cấu hình cho IDE

Ngoài việc sử dụng pre-commit để kiểm tra code, bạn có thể cấu hình cho IDE để kiểm tra code trước khi commit.

VSCode

  • Cài đặt extension: Black Formatter
  • Cấu hình:
{
  "python.formatting.provider": "black"
}

flake8

  • Cài đặt extension: flake8
  • Cấu hình:
{
  "python.linting.flake8Enabled": true
}

pylint

  • Cài đặt extension: pylint
  • Cấu hình:
{
  "python.linting.pylintEnabled": true
}

Tích hợp CI/CD

Việc tích hợp các công cụ kiểm tra vào CI/CD sẽ giúp bạn thêm một lần nữa kiểm tra code trước khi merge vào main branch. Đặc biệt là với những tricker:

  • git commit –no-verify
  • add file trên site gitlab

Gitlab CI

Ở đây, mình gộp chung format với lint vào 1 job để tối ưu thời gian build.

lint stage sẽ chạy lần lượt các công cụ: black, flake8, pylint và commitlint và chỉ được chạy khi pipeline là merge request và target branch là master hoặc release-*

stages:
  - lint
  - test
  - build
  - deploy

lint:
  image: python:3.10-slim
  stage: lint
  rules:
    - if: $CI_PIPELINE_SOURCE == 'merge_request_event' && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME =~ /^(master|release\-.*)$/ # merge request
  environment: $ENVIRONMENT
  script:
    - pip install -r requirements-dev.txt
    - echo "Running linting with black..."
    - black --check .
    - echo "Running linting with flake8..."
    - flake8 .
    - echo "Running linting with pylint..."
    - pylint .
    - echo "Running linting with commitlint..."
    - commitlint "$CI_COMMIT_MESSAGE"

  cache:
    paths:
      - .cache/pip
  allow_failure: false

Lời kết

Việc thiết lập các công cụ kiểm tra chất lượng code có thể mất một chút thời gian ban đầu, nhưng những lợi ích mà nó mang lại là rất đáng giá. Không chỉ giúp code của bạn sạch đẹp hơn, mà còn giúp cả team làm việc hiệu quả hơn.

Hy vọng bài viết này giúp ích cho các bạn trong việc setup môi trường phát triển Python chuyên nghiệp. Hẹn gặp lại các bạn trong những bài viết tiếp theo! 😊

My example repo

Tham khảo