1. 程式人生 > >黑客工坊揭密:原來他們是這樣製作開源軟體的

黑客工坊揭密:原來他們是這樣製作開源軟體的

近幾年來,市面上流行的開源軟體越來越多,有的觸目驚心,有的改動了世界,有的震驚了國家。在這些黑客工坊裡,人們究竟是怎麼製作出來的?

在這些黑客工坊裡,有的做出來的軟體很好很流行;有的做出來則很一般但是卻也很流行;有的則做出來的很好但是不流行。這究竟是為什麼呢?

在這篇文章裡,我將揭密開源軟體的製作流程,中間會插入豐富的示例。考慮到這個過程的複雜性,同時為了讓讀者能對這個過程一目瞭然。筆者花了半天,畫了一張通俗易懂地開發流程圖:

?wxfrom=5&wx_lazy=1

開源軟體開發流程

即下面的 11 個步驟:

  1. 問題。

  2. 目標。制定出一個 SMART 的目標

  3. 想法。確認出需要釋出的功能

  4. 名字。是的,你需要一個吊炸天的名字

  5. 開始構建。尋找模板建立你的 Hello, World

  6. 編寫功能。

  7. 釋出。儘早地發現 0.1.0 版本

  8. 迭代。有計劃的釋出功能,直到它完成你想要的功能 -> 1.0.0

  9. 自動化。測試,持續整合,持續釋出。

  10. 接受反饋。評估使用者的反饋,決定是否新增為功能

  11. marketing。編寫文件、文章、部落格,在社交媒體進行宣傳

等等,真實的情況並沒有這麼複雜,在這裡只是為了方便敘述。

Then,讓我們一點點地脫下開源的面紗。

0. 問題:為什麼這個軟體存在著

有用的軟體都是為了解決某些特定需求、問題而存在的。在開源軟體世界裡,從小至左填充一個字串的 left-pad,大到改變世界的 Linux,他們都解決了一些特定的問題。儘管有一些可能是學習而建立的,但是它解決了開發者的學習問題。

總而言之,就是軟體存在的意義。

如在先前的文章《2017 年節點——我寫的那些開源軟體》總結的那樣,我們想造一個輪子的來源可以是:

  • 日常工作中遇到的一些問題,提出對應的解決方案。

  • 使用某個開源軟體的過程中,發現它不能滿足我們的需求

  • 提取出工作上的一些好的技術實踐

  • 我想開發一個工具,來幫助其它人

  • 我有一個想法,我要用它來改變世界

  • 無聊,我就是想造個輪子

接下來,讓我舉幾個例子。

現在軟體不能滿足需求

當我在自己的專案上實施架構決策記錄的時候,我找到了一個相關的庫:adr-tools,但是發現這個庫有一些小的缺點:

  • 使用 shell 編寫,不易讀懂、只支援類 Unix;

  • 模板裡使用的是英語,不支援中文及其他語言

這種時候,一般是在專案上提個 issue,但是發現這個專案使用 shell 擴充套件起來不容易。因此,自己來寫一個類似的軟體是一種更好的選擇,於是我使用 TypeScript 寫了 Node.js 版的 ADR。

日常工作的結晶

在我們的日常、業餘的開發工作中,我們往往能實施一些好的技術實踐。只是受限於勞動合同的約束,我們不能直接使用這些程式碼來開源。但是可以獨立地重新開始來造一個輪子。在一些有開源文化的公司裡,便可以走開源的流程開源出來。

如我建立的 Dore,就是一個總結工作結晶的實踐。它總結了我在專案上使用 React Native 實現的 WebView 容器的經驗。不過,這依賴於我們對程式碼的抽象能力。即使,我們在專案上用了好的思想,但是並不一定能提取出來。

我需要一個新的工具

上面的兩個例子,都需要一定的執行和抽象能力。除了自己樂意造輪子,我們還可以隨意地建立一個工具,作為自己製作的工具。

如我最近寫的 Solla,就是為了解決我寫作的時候找圖封面圖的問題。

1. 下定你的決心

我的意思是,你要下定決心去做這一件事情。因為,你很有可能半途而廢,比如說我可能在上一步裡,習慣了使用英語來寫架構決策記錄。

這是一件很難的事情,特別是當你和我一樣,在 GitHub 上挖了兩百多個坑位之後,你可能不會想去做這樣的事件。

權衡這方面的利益不是一件容易的事情,所以不妨直接進入下一步。

2. 完善關於這個軟體的想法、需求

當我們決定建立這個開源軟體的時候,我們就需要細緻地想想它到底需要什麼功能。它的核心功能又是什麼?

在擁有對比軟體的情況下,核心功能與其它軟體都是差不多的。以文字編輯器為例,如果你不能提供插入圖片的功能,那麼有需求的使用者可能就跑了。而為了吸引不同的使用者,就需要一些額外的吸引人的特性。同樣是文字編輯器,如果你能提供 Markdown 支援,那麼你就能吸引這些使用者。因此,如果是對比其它框架,那麼就要完成相同的核心功能,並提供一些額外的功能。

所以,在計劃的初期就要思考能提供怎樣的特性。

再以架構決策記錄框架 ADR 為例,那便是:

  • 採用一種通用的語言環境,如 Node.js,以支援主流的作業系統

  • 多語言支援,我的意思是它至少可以支援 English 和 中文

  • 支援狀態日誌查詢,即我應該可以看到一個決策在生命週期裡的變化

  • 一個更好的列表展示,我應該可以檢視到某條決策,以及對應的最後狀態、修改時間等等

  • 使用 markdown 展示,以便在 GitHub 上顯示

  • 擁有一個 ToC 頁面,方便使用者檢視

補充一點,上面有幾個功能都是在實現的過程中想的,有一兩個是在使用的過程中想到的。這些功能能在設計初期的時候考慮到的話,那麼後期架構就不會需要需求的新增而帶來開發風險。

3. 取一個合適的名字

好了,在需求確認得差不多之後,就可以開始動手了。然後,我們就會面臨一個很嚴峻的問題,取一個合適的名字

取個名字

取名,對於多數人(包含程式設計師)來說,是一件痛苦的事。先設想一下,我們寫一個 Web 框架,我們可能需要一個能朗朗上口的名字,以 Vue、React、Angular 來看,最好是單音節、雙音節、三音節。

並且,它沒有被包管理工具註冊過的:

  • Ruby 就檢視有沒有對應的 gem 名可以用。

  • Node.js 則是 npm

  • Python 便是 pip

想想,還是 Java 程式設計師好,這個依賴的包名是 com.phodal.xxoo,代表了這是 phodal.com 的 xxoo,而不是 hug8217.com 的 xxoo。那麼,其對應的域名有可能就是 xxoo.phodal.com。

如果我們想到的名字已經被註冊過了,那麼這就很尷尬了。在這個時候,可以採取自己的命名規則:

我之前的命名規則:moImages、moLogs、moForms、moLe,都是以 mo 開頭加一個對應用途的單詞。

比如我最近的系列:dore、mifa、solla、sido,則是以唱名結合來取名字的。

選擇合適的開源協議

取完名字之後,就需要選擇一個合適的開源協議。不同的許可(協議)會賦予使用者不同的權利,如 GPL 協議強制要求開源修改過原始碼的程式碼,而寬鬆一點的 MIT 則不會有這種要求。

更細緻的協議選型,可以見我之前畫的選型圖:

aG1vFUMgRBDtN30bDJ9vaQJ9kic1ePyLor8tZd0Yic8YKmZnt4fITb5hqTfkOePGZvtCRpicNEBOWP33iaqlIkYnicA

Licenses

按我的習慣,都是以 MIT 協議來發布開源軟體,CC-NC 協議來發布電子書。

釋出 0.0.1

是的,當我們取好名字,選好了協議,我們就要釋出 0.0.1 版本了。

我的意思是,我們要先搶下這個名字,即佔坑。在佔坑前,請深思,你真的會寫好這個工具嗎?

比如說,我們使用 Node.js 來寫一個 MQTT 的包,那麼直接用 MQTT 顯然更容易被搜尋到:Node.js MQTT。

不行的話,只能用 Mosquitto、Mosca 這種名字了。又或者 moMQTT、iMQTT 這種加上字首的名字,對應於 Java 平臺,就可以是 jMQTT。

4. 開始構建

如我在《全棧應用開發:精益實踐》一書中所說,在我們編寫軟體之前,我們要先做技術選型與搭建開發環境。這裡的技術選型就沒有那麼複雜,就只需要選擇一個合適的語言。

選擇合適的語言

選擇合適的語言,只針對於某些有各種子集的語言,或者小版本間差異比較大的語言,如 Ruby。

不過,還有一個是語言版本的問題。在面向伺服器的作業系統,都安裝有 Python 環境,但是可能版本不一樣。有的是 2.7 的,有的版本可能是 3.4。所以,對於使用 Python 語言的使用者來說,還存在選擇版本的問題。

如果是一個非 JavaScript 的軟體,可能就不存在需要選語言了。如果是一個 JavaScript CLI(命令列)工具,也會有這樣的問題,到底是使用編寫 CoffeScript、TypeScript 還是 ES6 編寫都是一個問題。

在編寫 CLI 工具時,就需要了解是要針對某些使用者而開發。如對於 Java 程式設計師來說,他們的電腦上可能沒有 Node.js 環境,如果他們使用的是 macOS 電話,那麼應該都是有 Ruby、Python環境。同樣的,對於前端程式設計師來說,如果是 Windows 系統,那麼也沒有 Java、Python 和 Ruby、Go 環境,直接使用 JavaScript 語言就更容易了。

搭建構建 || 尋找模板

自己搭建一個完整的專案架子,是一個相當浪費時間的事情——特別是,我們第一次造輪子的時候。對於一些框架的外掛、CLI 工具來說,可能官方直接提供了一個 Hello, World 模板。但是,有一些時候往往沒有這麼簡單。要麼找一個差不多的模板,再有針對於的修改;要麼複製現有的架子,以我們的名稱替換其中的名字。

當我造輪子的時候,我習慣在網上搜索相應的模板,比如說: javascript lib boilerplate、 typescript lib boilerplate 或者 typescript cli starter。在沒有合適的情況下,就是找一些使用 TypeScript 寫的庫,在那之上進行修改。

5. 編寫原型:核心功能

核心功能,意味著,不做過多的錯誤處理——假設使用者是按我們的預期行為進行的。依微軟公司(Steve Ballmer)的經驗,20% 的程式碼是在核心的邏輯上,而有超過 80% 的程式碼是在處理錯誤邏輯上。

所以,在那之前,請先完成核心的功能。它可以讓你更快地釋出早期的預覽版,以早點接受市場的反饋。

構建開源與構建產品是類似的,能越早推出早期版本,就越有機會贏得市場;能不斷地繼續接受反饋並進行改進,就越能吸引忠實使用者;能做好一些市場工作,也就越吸引更多使用者。

6. 釋出

如果這是我們第一次開發開源軟體,那麼我們應該先發布幾個版本,來測試預期的過程是不是正常的。這個時候,就應該釋出另外一個早期版本 0.1.0。從之前佔坑的 0.0.1 到 0.1.0 之間,我們會做一些簡單的開發,比如,驗證一些基本的核心功能是不是好的、編寫一個容易讀懂的 README和一句話文案。

0.1.0 版本

0.1.0 意味著,我們擁有了一些基本的功能,並且框架本身是可以工作的。

比如說,你要做一個 Ruby 的 Gem 包,那麼你的第一個版本是讓包可以被呼叫。哪怕只是一個 Hello, World,它都能驗證我們的模式是可以工作的。

如果你要實現的是一個 CLI 工具,那麼你需要的就是安裝之後,可以直接使用。比如說,最近我在寫 Solla 的時候,在引入了 async 之後,就報錯了:

  1. ERROR in [default]

  2. Cannot find module'tslib'.

這只是其中遇到的一個問題,如果問題過多的話,那麼到時候除錯起來就有些麻煩。

簡化安裝、使用

在設計釋出流程的時候,我們也要注意框架本身的易用性。如我們開發一個庫,那麼使用者只新增一個依賴就可以使用:

  1. dependencies {

  2.    compile 'pl.droidsonroids.gif:android-gif-drawable:1.2.+'

  3. }

或者:

  1. npm install -g adr

早先,為了支援不同的語言,還需要 adr init,後來把這個步驟變成了可選,即配置了一些預設的配置。於是,使用者可以直接使用 adr new 建立。

麻煩一些的情況下,可能要多一兩步:

  1. yarn add react-native-check-app-install

  2. react-native link

如果整個安裝的過程很複雜,那麼使用者在使用的過程中就會放棄。

7. 迭代

好了,我們的早期幾個版本都可以工作了,我們正常地走向迭代開發的過程:

aG1vFUMgRBDtN30bDJ9vaQJ9kic1ePyLoAmYzDmckXUTWwQG3VeNCbmakm7umFpTnSeknX9AhGwNc9r0j3uWoDQ

Agile 迭代

更新文件

首先在 README 上,寫好專案的簡介,並根據專案 API 的變化,不斷地更新 README。如果專案的使用者需要依賴而文件,那麼請及時更新文件。

不過,指望程式設計師及時更新文件可能是一種不可靠的事情。這時候,應該使用 jsDOC、tsDoc、JavaDoc 這一類的工具,以從程式碼中生成對應的文件。

編寫測試

測試,是一個專案的質量保證。在可能地時候,應該花時間去寫測試,它可以讓專案看上去很可靠。然後,在醒目地地方告訴使用者,“你看這個軟體的測試覆蓋率有 92%,質量有相當高的保證。”

aG1vFUMgRBDtN30bDJ9vaQJ9kic1ePyLoIq4O68ibQJvapPOdLK9c1II6DXDuNy1FgZsfqe5yeFXfEM7snZhnMSQ

測試覆蓋率

特別是使用者使用的是你的庫,它能表明這個庫相當地可靠。

1.0.0 版本

在完成了我們計劃的主要功能之後,就可以釋出我們的 1.0.0 版本。與版本號 0.5.5 相比來說,版本號 1.0.0 會給人更可靠的感覺。如果你喜歡的話,可以像 Oracle 一樣直接釋出 2.0 的版本,它與 1.0.0 版本相比,看上去更加可靠。

8. 自動化

接下來,就是對上面做的內容進行一系列的自動化。如自動化文件、自動化 CHANGELOG、持續整合 等等。

自動化 CHANGELOG

可以按 GitHub 上的 Conventional Changelog 來編寫自己的提交資訊:

  1. <type>[optional scope]: <description>

  2. [optional body]

  3. [optional footer]

就可以優雅地生成 CHANGELOG 了:

aG1vFUMgRBDtN30bDJ9vaQJ9kic1ePyLoeOUqlguGaLA3GdpxibMehu37hk5GHuPicxT5ABa1OnCpWOPUUs5n5icfQ

提交資訊示例

自動化 RELEASE

當我們添加了一個新功能,或者修復一個 BUG 的時候。考慮到使用者的需要,我們就會發布這個新版本,一般來說,我們可能要這麼做:

  • 使用 git tag 提交一個新的版本

  • 修改 CHANGELOG 來增加更新的功能

  • 更新文件來通知使用者

  • 等等

這些都應該可以自動化,如我使用的 TypeScript 模板,就可以執行 release 來發佈下一個版本。

  1. "release": "yarn reset && yarn test && yarn docs:publish && yarn changelog",

9. 接受反饋

開源是一種社群行為,當用戶看到我們的原始碼寫得不好、出現 BUG、新增需求等等。就會在 GitHub 提交一些反饋,又或者是 PULL Request。

作為我們的使用者,我們應該對他們做出即時地響應。但是不是一味著同意使用者的需求,應該有一個清晰地 Roadmap,根據自己的時間來安排是否要開發。

如果使用者提了一個可怕的需求,那麼

  • 要做,這個需求在考慮中

  • 做,這個需求在待辦列表中

  • 不做,這個需求還需要驗證

  • blablabla

不管結果如何,在看到反饋的時候,儘可能早地去回覆使用者。

10. marketing

開源需要一些營銷的技巧,這些技巧可以幫你吸引關注。舉個簡單的例子,司徒正美的 avalon 框架出身得很早,也 MV* 方面也做得很不錯,但是在 marketing 上就……。以至於國內的很多前端,都不瞭解這個框架,要不今天在國內可能就是 AVRR 四大框架了。

在那篇《如何運營一個開源專案並取得較大影響力?》中,我詳細介紹瞭如何進行開源推廣。

  • 編寫一個好的 README

  • 寫好專案的一句話文案

  • README 裡寫明解決了什麼問題

  • README 裡寫明下專案的特性

同時,在國內的一些技術資訊類網站上,也可以釋出一些簡單的文章來介紹框架。這些平臺有:

  • 極客頭條

  • 掘金

  • 開發者頭條

  • v2ex

  • 知乎

  • 不成器的微博

  • 等等

一般來說,如果已經有了對比的框架,會寫一個對比的報告。考慮到,每個框架都會各自有優勢,只是優勢的大和小,決定了使用者量。如框架 A 容易上手,框架 B 設計思想好,那麼顯然框架 A 的優勢更大。這樣的對比可以放在 README 中,但是不提倡放在文件中。

結論

So,這就是開源軟體的開發流程。

aG1vFUMgRBDtN30bDJ9vaQJ9kic1ePyLoOvgu8gibCYkvltHoxmWqfZGdX66SLvia1qQZnqSC9IgqcUWaGyr7ibQRw

開源軟體開發流程