寫論文必不可少的,就是創建代碼并進行實驗。好的項目管理可以讓實驗進行得更加順利。本篇博客以一次項目實踐為例,介紹項目管理的方法,以及可能遇到的問題,并提供一些可行的解決方案。
目錄
- 項目管理工具
- 開始第一步
- 版本管理十分關鍵
- 如何進行版本管理
- 創建分支
- 進行改動
- 添加改動與上傳
- 分支帶來的新問題
- 常用的git使用命令
- 一些技巧讓項目管理更簡單
- 項目結構構建
- Commit規范
- 文檔與注釋
- 總結
項目管理工具
項目管理困難的主要原因在于,項目并非一個人構建的。許多人同時寫代碼,就容易出現:A改了代碼的同時,B也改了代碼,二人改了同一塊代碼,導致項目版本出現分歧的情況。這個時候,采用一個好用的項目管理工具,則可以很好地解決版本沖突的情況。Github則是一款以解決版本沖突為目標的項目管理工具,該工具跨平臺皆可使用,并且簡單易用,因而成為廣大程序員經常使用的項目管理工具。本篇博客也就以此為工具展開對項目管理方法的經驗介紹。
開始第一步
在注冊完github賬號之后,你就可以開始項目管理了。一般而言,項目開始之初,我們并沒有一個程序項目,因而我們需要先構建一個程序項目。進入個人主頁后可以在左側個人擁有的項目庫(Repositories)中找到新建(New)的按鈕,點擊進去后會看到這樣的畫面。
新建一個項目之前,需要先給項目起一個名字并填在Repository name那里,在Description那里可以填寫對項目的介紹(ps: 對項目的理解越深刻,就可以用越簡短的話介紹清楚這個項目到底要做什么。寫Description有助于后續進行比對,并檢查是否按照計劃完成項目),同時還可以選擇該項目是否公眾可見(Public or Private),在初始化的時候可以選擇添加一個README文件(ps: 建議初始化的時候就帶上README文件,可以在里面寫一些需要項目參與者提前知道的先驗知識)。
除此之外,還可以添加.gitignore
文件來指定每次git add
以及git push
的時候是否需要忽略一些文件(ps:每次進行實驗都會產生許多中間文件,如果都把它們git push
到repo里,顯然會浪費很多網絡資源,不利于項目開發);license這個功能限制了他人對你開源代碼庫的程度,不同license有不同的規則,具體請見官方文檔(ps: 這個不需要在最開始就決定下來,初始化一個項目的時候并不一定要確認此項,不過為了后期開源代碼能得到更多的關注,可以隨著項目進展再思考license如何選擇)。
建好一個代碼項目,應該會有一個這樣的結果。可以發現,初始化README里面的內容就是我們最開始在Description欄里寫的東西。個人認為這個界面十分簡潔美觀,github為項目開發提供的功能其實都已經集成在這一個界面上了。菜單欄里可以看到有以下功能:
- Code: 項目代碼在這個界面里,做出的改動也會都體現在這里面
- Issue: 這里類似于一個論壇,項目參與者可以在這里提出問題,其他參與者可以在這里進行討論與解答問題(ps: 項目進行的過程中總會遇到許多奇奇怪怪的問題,這些問題大家共同商量出一個結果會比一個人自己悶頭解決更靠譜一些,這里可以幫助項目參與者達成共識,可以有效提升項目的質量)。
- Pull Request: 把其他分支上的代碼拉到master分支上,并進行版本的融合。每個項目參與者會提供一部分功能代碼,這些代碼規范的寫法是在不同的分支上,在各自分支上測試無誤后,再由管理者將其merge到master主分支上。這一過程被稱為pull request,其功能實現則可以在這個界面里實現。
- Action: 創建工作流以自動化測試以及其他關鍵操作(比如pull request)。
- Projects: 將其他相關的github repo列舉在這里,方便后來的項目參與者了解情況。
- Wiki:對與項目更加詳細的介紹,好多東西無法在項目的注釋里、README文檔里講清楚,這部分信息可以都放在Wiki里進行詳細介紹。(ps: 文檔管理做的越好,項目質量越高,但是這部分在我看了眾多項目后發現,得到的關注并不是很充足)。
- Security: 安全設置。
- Insights: 項目流量監督。
- Settings: 項目其他關鍵設置。
以上就是在項目創建之初,我們要在遠端(也就是網站)上進行的操作,下面我們要將遠端的repo與本地進行聯系。
本地打開終端,并輸入指令git clone [url]
就可以從遠端拉取剛剛初始化的項目代碼,從而使得本地與遠端形成一個聯系。
??這里可能會遇到一些坑:
- 網絡失敗:是指loss connection這種錯,一般錯因在于本地機器的網絡太過拉胯,重啟網絡服務、重啟機器、更改網絡設置等一些操作往往可以解決(ps:不過有時候就算真的這么處理了,網絡該連不上還是連不上,我實驗室機器的網絡太過拉胯,一把辛酸淚)
- 沒有權限:這要在本地設置ssh key 然后上傳到遠端,具體操作詳見官方文檔。
例如,對于How2Code這個例子,我需要輸入指令git clone https://github.com/Doris404/How2Code.git
然后就可以看到本地機器就有一個名為How2Code的文件夾,里面含有一個README文檔,和遠端的設定一樣。
接下來我們就可以進行項目開發了 ??
版本管理十分關鍵
Github解決的最核心的問題就是版本管理。我們先來了解一下這個問題究竟有多復雜。我將用一個例子來解釋此問題的復雜程度。
一個三人團隊要構建項目實現網上訂餐功能,三人在項目初始階段約定,一人實現一個功能,并且初步商定的功能包括:查詢可點餐館及可點菜品、下單與付款、評價與點評、糾紛處理等。
A作為項目主要負責人實現了下單與付款這個最復雜的功能,B實現查詢可點餐館及可點菜品,C實現評價與點評、糾紛處理這兩個功能。
劃分功能時大家一致認為,這些代碼互相獨立性比較強,所以三人分別在不同的機器上各自實現各自代碼即可以做到版本管理。然而在項目進行過程中,出現了意想不到的情況。A下單與付款的功能實現依賴于B顯示出來的界面。因此B最初實現的界面一旦發生改動,則A的代碼實現也要跟著變更。在項目進行過程中則出現了以下情況:
- B 實現了一個初始界面
- A 基于初始界面進行功能實現,但于此同時B改動了一些初始界面,并形成了一個新版界面
- A 和 B都上傳代碼,此時A的代碼不能在新版界面上測試通過,只能在初始界面上通過,但由于缺乏版本管理工具,此時的代碼無法通過,并且我們遺失了初始界面。
Github提供了分支用于解決這個問題,同樣是上述情況,如果我們使用branch,則是另一番景象:
- B實現了一個初始界面,并給它放在branch 1上
- A實現了基于初始界面的功能,傳到branch 1上
- B改動初始界面,并形成新版界面,傳到branch 2上
這樣branch1的代碼就是可以正常跑通的,而branch 2上則缺少A的代碼,A則可以依據branch 1上自己實現的代碼進行改動,并且提交到branch 2上。
相信從上述例子中,你已經明白了版本管理有多么關鍵。
如何進行版本管理
創建分支
Github提供了branch功能可以用于版本管理,這一部分我將介紹如何使用branch進行版本管理。在本地終端進入項目文件夾,輸入git branch
即查看當前分支名稱。默認分支是master分支。
使用branch進行版本管理的第一步就是創建新的分支,運行git branch checkout -b [your branch name]
即可以創建一個新的分支,并切換到這個新的分支上。
進行改動
此時你已經轉到這個新的分支上了,就可以在這個新的分支上進行項目開發。例如,我改動了README文檔,添加了一些文本。
則可以通過git status
查看到我們哪些更改,然后決定是否要接受這些改動。
git add [your file name]
則可以將對指定文檔的改動添加到改動之中,如果在添加之后后悔了,可以使用git restore --staged [your file name]
來撤銷git add
操作。執行結束后,你會發現剛才提交到改動中的改動被移除改動。
另一個git restore
指令與剛才使用的git restore --staged [your file name]
不一樣,對沒在改動中的改動git restore
將撤銷你在文件中做出的改動,執行結束后,在文檔中的改動將消失。
添加改動與上傳
git add .
可以將所有的改動都添加到緩存之中,接著調用git commit -m "[your commit info]"
來添加改動的介紹,最后調用git push —-force-with-lease origin [your branch name]
來將改動push到遠端的指定分支。
遠端可以看到,新分支創建成功,并且也有我們添加的改動
可以發現,new_branch比master領先1個commit,我們可以對new_branch的改動進行比較,并決定是否merge到master分支。點擊Compare & pull request則可以申請一個pull request對new_branch的改動進行融合。
填寫new_branch具體做了什么改動,然后點擊Create pull request即可以創建一個pull request。
創建成功后,其他參與者可以提交comment來發表對改動的建議。
值得注意的是該分支上還可以push 更多的改動。只要該分支上的改動沒merge到master,在此之前該分支上的所有改動,都會集成到之前提交到pull request之中。
分支融合有3種可選方案,新手不建議選擇Rebase and merge方案,建議從前2個方案中進行選擇。前兩個方案本質都是將改動以merge的形式加到master中,區別在于選Create a merge commit則將這兩次改動分別merge進去,選Squash and merge則可以將2此commit 合并成一次commit。考慮到簡潔性,建議選擇Squash進行融合。
Rebase這個方法是三個方法中最為簡潔的一種,它將以線性來融合改動,這同時也帶來了風險。例如,如果new_branch改動和master改動出現沖突,則可能出現改動覆蓋的情況,這意味著有一支的改動因為代碼融合消失了。因而不建議初學者使用這個融合方式。
更多解釋可以查看博客的介紹。
merge得到通過后,可以在master分支上看到之前的2次commit融合成1次commit提交到master分支上,并且commit info是第2次commit info。
至此,我們完成了一次基于分支的改動提交。
分支帶來的新問題
分支提供了一個版本融合的方案,但是總會有一些菜鳥在初次使用的時候不太熟練,然后誤把改動添加到master分支上,我們如何保護master分支呢?
Github提供一些規則來實現對主要分支的保護,點擊Protect this branch,則可以添加規則來約束對master分支的改動(ps:這些改動也可以在setting-Branches里進行)。添加的規則種類繁多,包括每個分支的起名規則,分支提交的約束等等。
這里我選了在分支融合之前,必須要請求pull request至少1次(ps:其他的選項可以根據個人需求進行勾選)以及分支要符合標準的命名規則:name_file
分支所用者以及改動主文件名。
常用的git使用命令
git clone [url]
git branch # see which branch we are in
git checkout -b [branch name] # create a new branch and turn to this branch
git checkout [branch name] # turn to a exist branh
git add [your file name] # add your update into cache
git commit -m "[update info]" # add commit info
git push --force-with-lease origin [branch name] # push your update into [branch name]
?? master分支代碼就是最權威的,改動的時候一定要基于master分支,另開一個分支,然后再在這個新開的分支上改動代碼,將改動push到這個新分支上,然后將此分支merge到master分支上。
一些技巧讓項目管理更簡單
除了上述基于Github功能的項目管理,我們也可以發動主觀能動性來使得項目管理更加順滑。
項目結構構建
在項目創建之初就應該對項目有一個明確的定位,這個項目到底實現什么功能,具體如何構建項目代碼結構,才能有利于后續的代碼書寫,都是在項目之初有明確的定位的。這件事情非常重要!!!
至于如何獲得明確的定位,是超出本篇博客的內容(ps:以我淺顯的經驗,明確的定位并不是一次討論就能決定的,需要需求方和實現方多交流溝通確定)。
Commit規范
首先,就是commit info的書寫規范,一般來說commit info是要做到提示參與者這次提交具體進行了哪些改動的功能,對于一個不了解情況的人而言,其能閱讀的文本數量實在有限,因此需要提交者用最短的話描述出代碼改動的核心。建議項目之初就總結一下commit info的書寫規范,并強制要求所有參與者遵守。一個可選的例子是:
<type> (scope): describe
- type:commit的類型說明,允許7種標識
feat: 添加新功能
fix: 修補bug
docs: 增加文檔
style: 修改格式
refactor: 重構
test: 添加測試
chore: 構建過程或輔助工具的變動
- scope:改動范圍,比如某個包,某個文件
- describe: 改動的描述,以動詞開頭,例如:
update README.md
一個可能的例子就是:feat(train.py): add train.py
表示這次改動添加了train.py文件實現了train的功能。
文檔與注釋
文檔和注釋對項目實現十分重要是所有程序員的共識,然而現實卻是大家寫文檔和注釋的意愿并不強烈。一方面是因為代碼書寫占據了大部分程序員的大部分時間,結束了一天辛勞工作的程序員實在沒用動力去完成文檔注釋的書寫;另一方面,文檔與注釋的確讓項目實現變得更加簡單易行,但是寫文檔的好處主要是體現在除去文檔書寫者的所有人身上,除非所有人都寫文檔和注釋,否則文檔和注釋的堅持者將遺憾地發現自己做了最多的工作,而受益卻是別人的。基于上述原因,文檔與注釋的書寫情況總是不容樂觀。
這篇博客不想對不寫文檔與注釋的人進行道德上的譴責,畢竟偷懶是人之常情。本篇博客希望提供一個寫文檔的思路,來減少善良的程序員在寫文檔與注釋時所承擔的時間開銷 ??
如何寫文檔才能提升寫文檔的效率呢?我的經驗是,先確定文檔的結構,通過子標題的形式來將文檔結構簡明地總結出來,然后填充每個子標題的具體內容。這樣做有一個好處,可以極大地調動其他參與者的文檔書寫積極性。
例如,對于網上訂餐項目,三人實現4個功能包括:查詢可點餐館及可點菜品、下單與付款、評價與點評、糾紛處理。A作為項目管理者,對于這個項目的結構有個非常明確的認識(至少要比B和C這兩個參與者明確),因而它非常清楚本項目的代碼包含從數據庫獲取信息模塊,顯示信息模塊,交易模塊,評價和售后模塊。因而項目的文檔結構也應符合項目代碼結構,對這些模塊進行各自的介紹。
對代碼介紹的文檔中則應包含這4個子標題,并且對這些模塊的具體實現進行展開解釋。代碼實現上A對顯示信息模塊等非主寫模塊并不了解,因此他會將這部分文檔書寫交給給自負責的人。
上述介紹只是寫文檔的最基本介紹,更深入的技巧,建議讀者閱讀一些提升寫作能力的工具書。
好消息是,文檔的書寫不止有手工書寫這一種方法(ps:這好像是廢話),一些項目文檔生成工具可以幫助我們從注釋生成文檔。對于python代碼而言,可以使用Sphinx包來輕松實現項目注釋文檔生成。具體使用參考博客。
注釋對于我們理解函數功能有極大的作用,在寫注釋時可以參考一些優秀項目的注釋書寫規范,這里給出一個實例供讀者參考。
class People(object):""" __init__Args:name(str): name of a personfather_name(str): father name of this personmother_name(str): mother name of this persongender(str): can only be male or femaleconfig(json): config file that may contain name, father_name, mother_name, genderReturns:a person from PeopleNotes:Some description of PeopleExamples:person = People(name = 'Doris404',father_name = 'Xiaoming',mother_name = 'Xiaohong',gender = 'female')"""def __init__(self, name, father_name, mother_name,gender,config):try:self.name = config['name']except:self.name = nametry:self.father_name = config['father_name']except:self.father_name = father_nametry:self.mother_name = config['mother_name']except:self.mother_name = mother_nametry:self.gender = config['gender']except:self.gender = gendertry:self.config = configexcept:pass
總結
以上是我在項目管理方面的經驗,更多內容可以參考我的github repo,上述教程中的例子可以在How2Code repo中得到體現。
如果本篇博客可以幫到你的話,請給我一個贊吧,感謝 ??