為什麼要分離圖片處理邏輯?Django 最佳實踐指南

更新日期: 2024 年 12 月 26 日

在 Django 項目中,將代碼合理分層是保持代碼可讀性、可維護性和可測試性的關鍵之一。

對於圖片處理這類業務邏輯,是否應該單獨抽離成工具函數?

本文將帶你理解分離邏輯的原因和優勢,以及在不同情況下該如何做出選擇。


分離關注點(Separation of Concerns)

關注點分離的原則強調,每個模組或類應該只專注於完成單一的責任。

  • 原因: 模型的核心職責是定義數據結構,而圖片轉換和上傳則屬於業務邏輯,與模型的主要功能不同。

    將兩者混合在一起會使代碼臃腫,降低可讀性。
  • 優勢:
    • 代碼更清晰:模型專注於數據結構,圖片處理則由工具模組負責。
    • 便於修改:當圖片處理邏輯需要變更時,直接修改工具函數即可,無需進一步修改模型代碼。

提升代碼的可重用性

在多個地方可能需要執行類似的圖片處理邏輯(如 Profile 模型和 ServiceImage 模型)時,將這些邏輯抽取成工具函數尤為重要。

  • 原因: 抽取通用邏輯可以避免代碼重複,方便多處調用。
  • 優勢:
    • 減少重複代碼:一旦工具函數完成,任何需要圖片處理的地方都可以直接使用。
    • 方便擴展:如果未來需要支持其他圖片格式(如 PNG 或 GIF),只需修改工具函數即可,不必在每個模型中逐一更新。

提高代碼的可測試性

工具函數的分離不僅讓邏輯更清晰,還能讓測試更加簡單和高效。

  • 原因: 單獨的工具函數可以被獨立測試,避免因與模型的耦合而導致測試覆蓋率下降。
  • 優勢:
    • 易於測試:工具函數可以模擬圖片轉換和上傳邏輯,並獨立驗證其結果。
    • 錯誤定位更清晰:圖片處理出現問題時,可以單獨檢查工具函數,而無需調試整個模型的保存流程。

什麼情況下可以不分離?

如果圖片處理邏輯只在單一模型中使用,並且該邏輯相對簡單,那麼可以直接將邏輯寫在模型的 save 方法中。

示例:直接在模型中處理圖片

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile")
    photo = models.ImageField(upload_to="profile_photos/", blank=True, null=True)

    def save(self, *args, **kwargs):
        # 圖片轉 WebP 並上傳邏輯
        if self.photo:
            from PIL import Image
            from io import BytesIO
            import boto3
            import uuid
            from django.conf import settings

            image = Image.open(self.photo)
            webp_image_io = BytesIO()
            image.save(webp_image_io, format="WEBP", quality=85)
            webp_image_io.seek(0)

            unique_filename = f"profile_photos/{uuid.uuid4()}.webp"

            s3 = boto3.client("s3", region_name=settings.AWS_S3_REGION_NAME)
            s3.upload_fileobj(
                webp_image_io,
                settings.AWS_STORAGE_BUCKET_NAME,
                unique_filename,
                ExtraArgs={"ContentType": "image/webp"},
            )

            self.photo.name = unique_filename

        super().save(*args, **kwargs)

這種方式在邏輯簡單、只需在單一模型中實現時是可行的,但隨著功能擴展,這種方式容易導致代碼重複,增加維護成本。


如何實現邏輯分離?

將圖片處理邏輯提取到工具模組是一種更符合 Django 最佳實踐的做法。

步驟:創建工具函數

在項目中新增一個工具模組,例如 utils/image_processing.py

from PIL import Image
from io import BytesIO
import boto3
import uuid
from django.conf import settings

def process_and_upload_image(photo, folder_name):
    # 圖片轉換為 WebP
    image = Image.open(photo)
    webp_image_io = BytesIO()
    image.save(webp_image_io, format="WEBP", quality=85)
    webp_image_io.seek(0)

    # 生成唯一文件名
    unique_filename = f"{folder_name}/{uuid.uuid4()}.webp"

    # 上傳至 S3
    s3 = boto3.client("s3", region_name=settings.AWS_S3_REGION_NAME)
    s3.upload_fileobj(
        webp_image_io,
        settings.AWS_STORAGE_BUCKET_NAME,
        unique_filename,
        ExtraArgs={"ContentType": "image/webp"},
    )

    return unique_filename

步驟:在模型中調用工具函數

將工具函數集成到模型中,讓模型邏輯更加簡潔:

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="profile")
    photo = models.ImageField(upload_to="profile_photos/", blank=True, null=True)

    def save(self, *args, **kwargs):
        if self.photo:
            from utils.image_processing import process_and_upload_image
            unique_filename = process_and_upload_image(self.photo, "profile_photos")
            self.photo.name = unique_filename

        super().save(*args, **kwargs)

綜合建議

何時直接在模型中處理邏輯?

  • 當圖片處理邏輯僅限於單個模型使用。
  • 預計未來不會在多個地方復用。

何時將邏輯抽離為工具函數?

  • 當圖片處理邏輯需要在多個模型中使用。
  • 需要提高代碼的可重用性和可測試性。
  • 預計未來會進行功能擴展或修改。

總結

將圖片處理邏輯單獨抽離成工具函數是一種符合 Django 最佳實踐的設計方式,可以提升代碼的可讀性、重用性和測試效率。

然而,根據項目的複雜程度和需求,可以靈活選擇是否分離。

合理的結構設計能讓你的代碼更加清晰高效,未來維護起來也更加輕鬆! 😊

Similar Posts

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *