返回文章列表

技術文章README撰寫與版本控制最佳實踐

本文探討軟體開發中 README 檔案的撰寫技巧,比較 Markdown 與 reStructuredText 格式的優劣,並詳細說明 Python 套件版本控制的最佳實踐,包含語義化版本控制、依賴項版本規範、CHANGES.md 檔案的撰寫,以及使用 Twine 上傳套件至 PyPI 的完整流程。

軟體開發 檔案撰寫

README 檔案是開源專案的門面,清晰的檔案結構和規範的版本控制能有效提升專案的專業度。選擇易讀易寫的 Markdown 格式撰寫 README,搭配語義化版本控制,能讓使用者快速理解專案的功能和使用方法。妥善管理套件版本,特別是依賴項的版本範圍設定,有助於避免版本衝突,確保套件在不同環境下的穩定性。使用 Twine 上傳套件至 PyPI,則能方便其他開發者安裝和使用你的專案。此外,CHANGES.md 檔案的維護,能讓使用者追蹤版本更新的內容,方便掌握新功能和錯誤修復。從簡單的指令碼到可擴充套件的框架,外掛架構的設計能提升專案的彈性,讓使用者根據自身需求新增自定義功能,例如整合特定硬體或軟體的監控指標。

專案README檔案的撰寫與格式選擇

在軟體開發與開源專案中,README檔案扮演著至關重要的角色。它不僅是專案的門面,也為潛在的使用者提供了快速瞭解專案功能與使用方法的途徑。選擇適當的README檔案格式對於提升專案的可讀性和專業度至關重要。

README檔案的格式選擇

目前,主流的README檔案格式包括純文字(Plain Text)、reStructuredText(rst)以及Markdown(md)。其中,PyPI(Python Package Index)支援這三種格式,使得開發者可以根據專案需求和個人偏好進行選擇。

  • 純文字(Plain Text):最簡單的格式,但功能有限,無法提供豐富的格式控制。
  • reStructuredText(rst):常用於Sphinx檔案系統,適合用於撰寫較為複雜的技術檔案。
  • Markdown(md):因其易讀易寫且被廣泛使用的特點,成為許多開源專案的首選。

Markdown格式詳解

由於Markdown的易用性和廣泛支援,許多GitHub專案選擇使用Markdown格式撰寫README檔案。以下是一個基本的Markdown語法範例:

基本語法

# 標題1

## 標題2

### 標題3

#### 標題4

_斜體_ **粗體** **_粗斜體_**

1. 有序列表1
2. 有序列表2
   1. 子列表需要縮排
   2. 編號不必連續

* 無序列表1
* 無序列表2
  - 子列表可以使用連字號

`反引號`用於標註程式碼或檔名。

```python
def example():
    return True

參照文字可以使用右尖括號。


#### 連結與圖片
```markdown
[連結文字](https://example.com)
![圖片說明](https://example.com/image.png)

表格

| 欄位1 | 欄位2 | 欄位3 |
| --- | --- | --- |
| 資料1 | 資料2 | 資料3 |
| 資料4 | 資料5 | 資料6 |

內容解密:

  • Markdown的標題使用#符號來表示,數量越多代表標題層級越深。
  • 使用*_來表示斜體,**來表示粗體。
  • 清單可以使用數字或符號(如*+-)來建立有序或無序列表。
  • 連結和圖片的語法相似,但圖片需要在前面加上!
  • 表格使用管道符號|來分隔欄位,使用-來分隔表頭和內容。

reStructuredText格式簡介

對於需要使用reStructuredText格式的專案,以下是一些基本的語法:

基本語法

標題1
========
標題2
---
-
---
-
標題3
++++++++

*斜體* **粗體**

1. 有序列表1
2. 有序列表2
#. 子列表使用#號自動編號

- 無序列表1
- 無序列表2

``反引號``用於標註程式碼或檔名。

.. code:: python
   def example():
       return True

> 參照文字可以使用空行分隔。

連結與圖片

`連結文字 <https://example.com>`_
.. image:: https://example.com/image.png
   :alt: 圖片說明

表格

=============== === ===
欄位1          欄位2 欄位3
=============== === ===
資料1          資料2 資料3
資料4          資料5 資料6
=============== === ===

內容解密:

  • reStructuredText使用不同的符號來表示標題層級,如=-+等。
  • 使用*來表示斜體,**來表示粗體。
  • 清單可以使用數字或符號來建立有序或無序列表。
  • 連結需要使用特定的語法,包括連結文字和目標URL。
  • 表格需要使用=來分隔欄位和表頭、內容。

選擇適合的README格式

對於大多數Python專案來說,Markdown是撰寫README檔案的理想選擇。其易讀性和廣泛的支援使得它非常適合用於描述專案功能和使用方法。reStructuredText則更適合於需要撰寫複雜技術檔案的專案。

軟體版本管理與包裝指令碼的最佳實踐

在開發與維護 Python 套件時,正確的版本管理與包裝指令碼是至關重要的。本篇文章將探討如何有效地管理軟體版本,特別是在 apd.sensors 這個範例專案中。

版本號的選擇與語義化版本控制

版本號的管理對於軟體開發者與使用者來說都是非常重要的。語義化版本控制(Semantic Versioning, SemVer)提供了一套清晰的版本號管理規則,特別適用於函式庫的開發。

語義化版本控制的核心原則

  1. 主版本號(Major Version):當發生向後不相容的變更時,主版本號需要遞增。例如,新增必要引數、重新命名函式或改變函式回傳值型別等。
  2. 次版本號(Minor Version):當新增功能或特性,但保持向後相容時,次版本號需要遞增。例如,新增函式或為現有函式新增可選引數。
  3. 修補版本號(Patch Version):當進行錯誤修復且不影響公開 API 時,修補版本號需要遞增。這些變更應該對使用者不可見,除非他們觸發了特定的錯誤情況。

日曆版本控制

日曆版本控制(Calendar Versioning, CalVer)是另一種流行的版本號管理方案,它直接使用發布日期作為版本號。這種方法簡化了版本號的決定過程,但可能會使不同版本之間的差異變得不明確。

日曆版本控制的適用場景

  • 當專案的發布總是代表著重大變更或非常小的調整時。
  • 不需要考慮變更對使用者的影響。

依賴項版本規範

在使用 apd.sensors 這樣的函式庫時,使用者需要能夠控制所接受的函式庫版本範圍。通常,這涉及到指定最低所需版本和最高可接受版本(通常是下一個主版本的門檻)。

正確設定依賴項版本的最佳實踐

  1. 寬鬆的版本規範:在 install_requires 中應避免過於嚴格的版本限制,以便使用者能夠根據需要升級。
  2. 排除已知不相容的版本:明確排除任何已知不相容或有嚴重錯誤的版本。

CHANGES.md 的重要性與格式規範

CHANGES.md 檔案記錄了 apd.sensors 套件在不同版本之間的變更。這對於使用者瞭解何時需要升級以獲得新功能或錯誤修復至關重要。

CHANGES.md 的標準格式

  1. 標題層級的一致性:保持 Markdown 標題層級的一致性。
  2. 變更記錄的格式:每個版本應有一個適當層級的標題,後面跟著版本號和發布日期(括號內)。然後,使用無序列表列出變更內容,可選地包含變更作者。

## 變更記錄

### 1.0.0 (2019-06-20)
* 新增初始感測器功能 (Matthew Wilkes)

README.md 與 PyPI 的整合

setup.cfg 中組態 README.mdCHANGES.md 的內容會被合併用於形成 PyPI 上的 long_description。因此,這些檔案應遵循 Markdown 格式,以確保正確顯示。

軟體套件版本控制策略

在開發 Python 套件時,如何指定依賴套件的版本是一個重要的議題。正確的版本控制可以避免相依性衝突,並確保套件的穩定性。

嚴格版本控制的陷阱

嚴格版本控制(Strict pins)是指指定依賴套件的確切版本,例如 psutil==5.6.3。這種做法看似安全,但實際上可能會造成更多問題。當其他開發者需要使用該套件和其他依賴不同版本的套件時,就會產生版本衝突,需要手動解決。

例子

假設我們將 psutil 固定在 5.6.3 版本,後來有人想要使用我們的套件和另一個依賴 psutil 更新版本的套件,就會出現版本衝突。

鬆散版本控制

鬆散版本控制(Loose pins)是指只排除已知不相容的版本,例如 psutil>=5.5。這種做法允許相依性解析系統自動選擇合適的版本,避免手動干預。

實作方法

可以透過執行 pipenv install psutil==4.0.0 等指令來測試不同版本,找出最低可接受版本。如果沒有已知不相容的版本,可以考慮不指定版本,並記錄已知可用的版本組合,例如提交 Pipfile.lock

推薦設定

install_requires =
    psutil
    click
    adafruit-circuitpython-dht ; 'arm' in platform_machine

嚴格版本控制的替代方案

如果瞭解所使用的版本控制方案(例如 Semantic Versioning),可以設定相對寬鬆的版本範圍。例如,對於遵循 semver 的套件,可以使用 ~=7.0 來允許小版本的更新。

例子

對於 clickpsutil,可以根據其變更記錄,設定合理的版本範圍:

install_requires =
    psutil ~= 5.6
    click ~= 7.0
    adafruit-circuitpython-dht ~= 3.2.0 ; 'arm' in platform_machine

如何選擇版本控制策略

每種策略都有其優缺點。鬆散版本控制目前較為流行,因為它允許終端使用者自行管理相依性版本。如果開發大型應用程式,可能需要更嚴格的版本控制,但這需要更頻繁的測試和更新。

上傳套件版本

完成 apd.sensors 套件的開發後,需要將其上傳到自訂的索引伺服器和 PyPI,以便於後續範例的使用和真實世界的應用。

注意事項

  • 請勿將個人學習用的套件上傳到 PyPI,除非是對他人套件的改進或修復。
  • 可以使用 https://test.pypi.org/ 進行測試和學習。

使用 Twine 上傳套件至 PyPI 的完整

在 Python 開發中,將專案上傳至 PyPI(Python Package Index)是一個重要的步驟,以便其他開發者能夠輕鬆安裝和使用你的套件。Twine 是目前最推薦的工具,用於安全地上傳 Python 套件至 PyPI。本文將詳細介紹如何使用 Twine 來完成這項任務。

安裝 Twine

首先,你需要安裝 Twine。可以使用 pipenv 來安裝 Twine 作為開發依賴:

pipenv install --dev twine

或者,你也可以使用 pip 進行全域安裝:

pip install --user twine

建立套件分發檔案

在上傳套件之前,你需要建立套件的分發檔案。這可以透過執行以下命令來完成:

pipenv run python setup.py sdist bdist_wheel

此命令將產生兩個檔案:一個原始碼分發檔案(.tar.gz)和一個 wheel 分發檔案(.whl)。這兩種格式對於確保你的套件能夠在不同環境中正確安裝都是非常重要的。

內容解密:

  • sdist 引數用於建立原始碼分發檔案,這對於支援多種 Python 版本和環境至關重要。
  • bdist_wheel 引數則用於建立 wheel 檔案,這是一種預先編譯的格式,可以簡化安裝過程。

檢查套件分發檔案

Twine 提供了一個基本的檢查工具,用於驗證你的套件分發檔案是否正確無誤。你可以執行以下命令來進行檢查:

pipenv run twine check dist/*

如果檢查透過,你將看到類別似以下的輸出:

Checking distribution dist/apd.sensors-1.0.0-py3-none-any.whl: Passed
Checking distribution dist/apd.sensors-1.0.0.tar.gz: Passed

內容解密:

  • twine check 命令用於檢查套件分發檔案是否存在任何渲染錯誤或問題。
  • 透過檢查後,你可以確認你的套件分發檔案是有效的,可以安全地上傳至 PyPI。

上傳至 PyPI

一旦你的套件分發檔案準備就緒並透過檢查,你就可以使用 Twine 將它們上傳至 PyPI:

pipenv run twine upload dist/*

在上傳過程中,你將被提示輸入 PyPI 的認證資訊。確保你擁有一個有效的 PyPI 帳戶。

內容解密:

  • twine upload 命令負責將你的套件分發檔案上傳至指定的倉函式庫(預設為 PyPI)。
  • 一旦上傳完成,相關的版本就不能被覆寫;任何變更都需要透過增加版本號來實作。

組態 Twine

Twine 提供了一些組態選項,可以提高使用體驗。例如,如果你安裝了 keyring 函式庫,Twine 可以利用作業系統的憑證管理器來儲存你的 PyPI 登入資訊。

你也可以直接在 ~/.pypirc 檔案中設定相關資訊,以便於管理多個倉函式庫的認證資訊。

[distutils]
index-servers =
    pypi
    rpi4

[pypi]
username:MatthewWilkes

[rpi4]
repository: http://rpi4:8080
username: MatthewWilkes
password: hunter2

這樣組態後,你可以輕鬆地上傳套件至不同的倉函式庫,包括本地的私有倉函式庫。

從指令碼到框架

到目前為止,我們所建立的套件具有相對基本的指令碼介面且缺乏擴充套件性。大多數應用程式並不需要被擴充套件;通常將所有可選程式碼封裝在一起比維護分散在主程式碼函式庫之外的外掛更為簡單。然而,使用外掛架構來管理(例如)應用程式的可選功能可能非常有吸引力。

如果您的直接使用者是其他程式設計師,那麼提供外掛架構可能會使他們的工作變得更容易。這通常適用於開源框架,外部開發人員可能會為自己的使用或透過諮詢協定為其客戶建立額外功能。如果您正在從事開源專案並且不確定是否應該使用外掛架構,我傾向於包含它。人們總會擴充套件您的程式碼;相比於您的軟體的分支版本新增額外功能,包含定義良好的外掛的錯誤報告更容易理解。

我們的感測器工具的使用者不一定是程式設計師;他們是想要取得特定系統資訊的人。然而,他們可能會希望為自己的特定使用案例取得自定義資訊,在這種情況下,他們可能會聘請程式設計師新增新功能。

我們已經在實作外掛架構的道路上邁出了很大一步;我們有一個定義良好的類別,描述了我們的感測器的行為,形式為 Sensor[type] 泛型基礎類別。除了定義良好的介面之外,我們還需要一種方法來列舉我們可用的感測器。我們在 show_sensors 函式中做到了這一點,該函式將所有感測器硬編碼在檔案中。這對於不需要外掛架構的應用程式來說效果很好,其中所有感測器都由相同的開發人員編寫並作為單一組分發。然而,一旦我們期望第三方編寫自定義感測器,這種方法就會失敗。

編寫感測器外掛

讓我們暫時思考一下作為使用者對這個工具有什麼期望。除了許多人可能會使用的溫度和濕度感測器之外,我還想監控一些很少有人會覺得有用的東西。其中之一是我屋頂安裝的太陽能板的輸出。我有一個指令碼,可以透過藍牙從我的逆變器中提取讀數,它使用現有的開源命令列工具來完成收集和解釋資料的艱苦工作。我希望能夠將其納入我的資料收集。

由於與特定品牌和型號的太陽能板逆變器的整合對大多數人來說不是一個有用的元件,因此我不會將其整合到核心的 apd.sensors 套件中。相反,我將建立一個獨立的外掛,就像使用者可能為其自定義邏輯所做的那樣。

開發外掛

開發這個感測器是另一個使用 Jupyter 筆記本進行原型設計的絕佳機會。我們需要在 Raspberry Pi 伺服器上設定遠端環境,如第 1 章所述,並將 apd.sensors 套件安裝到其中。這使我們能夠透過本地的 Jupyter 例項連線並能夠從伺服器上安裝的 apd.sensors 版本匯入 Sensor 基礎類別。

import subprocess

class SolarCumulativeOutput:
    def __init__(self):
        self.value = None

    def get_data(self):
        try:
            output = subprocess.check_output(['solar-inverter-cli', '--cumulative-output'])
            self.value = float(output.strip())
        except Exception as e:
            # 處理例外
            pass

    def __str__(self):
        if self.value is None:
            return "Unknown"
        return f"{self.value} kWh"

內容解密:

  1. subprocess.check_output 用於呼叫外部命令 solar-inverter-cli 以取得累積輸出資料。
  2. 透過 float(output.strip()) 將輸出轉換為浮點數,以表示太陽能板的累積輸出。
  3. 使用 try-except 區塊來捕捉可能發生的例外,例如命令執行失敗或輸出格式不正確。
  4. __str__ 方法用於傳回物件的字串表示,如果資料尚未取得,則傳回 “Unknown”。

未來發展

對於伺服器監控設定,您可能需要幾套不同的外掛。雖然您可能在所有機器上都有 CPU 和 RAM 使用率資料,但某些伺服器角色有特定的應用程式指標,例如處理非同步任務的機器的任務佇列長度、Web 應用程式防火牆伺服器的被阻止主機數量或資料函式庫伺服器的連線統計資訊。

處理外部工具依賴

有兩種廣泛的方法來處理需要外部工具的事實。首先,我可以建立一個包含所需工具的 C 程式的 Python 發行版。然後,我必須安排在安裝我的 Python 套件時編譯和連結該工具。我需要包含錯誤處理以解決此工具不可安裝的問題,並記錄其需求。一旦安裝,我就可以使用其現有的指令碼介面或直接使用 Python 的支援來呼叫本地程式碼。

或者,我可以記錄我的感測器僅在該工具安裝時才有效,並使程式碼假設它存在。這大大簡化了我的開發過程,但使安裝對終端使用者來說更加困難。由於我不認為這是一個普遍有用的功能,這是迄今為止最具吸引力的選擇。在使用者很少的情況下,沒有必要為了完美而投入過多的精力。