软件工程第一次结对编程
10/11-10/16日短短五天,我和隊(duì)友通過結(jié)對(duì)編程的方式完成了一個(gè)用來做“黃金點(diǎn)游戲”的小程序,項(xiàng)目地址:
https://github.com/ycWang9725/golden_point.git
黃金點(diǎn)游戲的基本規(guī)則
假設(shè)有M個(gè)玩家,P1,P2,…Pm
在 (0-100) 開區(qū)間內(nèi),所有玩家自由選擇兩個(gè)正有理數(shù)數(shù)字提交(可以相同或者不同)給服務(wù)器,
假設(shè)提交N11,N12,N21,N22,Nm1,Nm2等M2個(gè)數(shù)字后,服務(wù)器計(jì)算:(N11+N12+N21+N22+…+Nm1+Nm2)/(M2)*0.618 = Gnum,得到黃金點(diǎn)數(shù)字Gnum
查看所有玩家提交的數(shù)字與Gnum的算術(shù)差的絕對(duì)值,值最小者得M分,值最大者扣2分。其它玩家不得分
此回合結(jié)束,進(jìn)行下一回合,多回合后,累計(jì)得分高者獲勝。
我們的bot收到的輸入是游戲當(dāng)前輪和歷史所有輪次的Gnum和所有玩家的預(yù)測(cè)值,輸出兩個(gè)對(duì)下一輪黃金點(diǎn)的預(yù)測(cè)值。
PSP表
在任務(wù)要求下達(dá)的當(dāng)天晚上,我與隊(duì)友兩人便開始了討論、設(shè)計(jì)與編碼。
| 計(jì)劃: 明確需求和其他因素, 估計(jì)以下的各個(gè)任務(wù)需要多少時(shí)間 | 30 | 20 |
| 開發(fā)(包括下面 8 項(xiàng)子任務(wù)) | 885 | 875 |
| ·需求分析 | 120 | 60 |
| ·生成設(shè)計(jì)文檔 | 10 | 20 |
| ·設(shè)計(jì)復(fù)審 | 10 | 10 |
| ·代碼規(guī)范 | 5 | 5 |
| ·具體設(shè)計(jì) | 20 | 20 |
| ·具體編碼 | 600 | 600 |
| ·代碼復(fù)審 | - | - |
| ·測(cè)試 | 120 | 180 |
| 報(bào)告 | 150 | 150 |
| ·測(cè)試報(bào)告 | - | - |
| ·計(jì)算工作量 | 30 | 30 |
| ·事后總結(jié) | 120 | 120 |
| 總共花費(fèi)時(shí)間 | 1065 | 1045 |
由于采用結(jié)對(duì)編程的工作方式,我們沒有計(jì)劃也沒有經(jīng)歷代碼復(fù)審階段。需求分析階段的預(yù)估時(shí)間和實(shí)際時(shí)間較長(zhǎng),是因?yàn)槲遗c隊(duì)友兩人對(duì)強(qiáng)化學(xué)習(xí)(Q-learning方法)并不熟悉,因此將查找參考資料和學(xué)習(xí)的時(shí)間算了進(jìn)去。我們的具體編碼階段并非完全用于編碼,事實(shí)上,其中有大約2/3的時(shí)間在修改和驗(yàn)證算法的策略,我們將這些時(shí)間均算作此項(xiàng)。由于程序中bug大多在編碼和驗(yàn)證過程中發(fā)現(xiàn)并修改,在測(cè)試階段檢測(cè)并修改的bug非常少,我們沒有撰寫測(cè)試報(bào)告。
我們的bot
get_numbers.py內(nèi)部未定義類。
包含的函數(shù)及功能:
| LineToNums(line, type=float) | 處理輸入的歷史數(shù)據(jù)行,得到可以被get_result()使用的數(shù)據(jù)結(jié)構(gòu) |
| get_result(history) | 將history數(shù)據(jù)輸入到兩個(gè)model之中,分別接收兩個(gè)model的輸出, 用隨機(jī)數(shù)的方式選擇一個(gè)交給main()函數(shù),并根據(jù)兩個(gè)模型預(yù)測(cè)值的排名更新選擇模型的閾值 |
| main() | main()函數(shù)調(diào)用前兩個(gè)函數(shù),作為與外界的輸入輸出接口 |
QLearning主要含一個(gè)class QLearn,包含的函數(shù)及功能(較為重要的用紅色標(biāo)出):
| init(self, load, load_path, test, bot_num, n_steps, epsilon_th, mode, rwd_fun, state_map, coding, multi_rwd) | 初始化一個(gè)QLearing對(duì)象,根據(jù)輸入的參數(shù)在npy文件中l(wèi)oad或初始化16個(gè)變量,它們被用來保存q-table, model預(yù)測(cè)的隨機(jī)程度, model計(jì)算reward和rank的模式等 |
| update_epsilon(self) | 每次調(diào)用將會(huì)更新epsilon,若在預(yù)測(cè)中使用epsilon,則減小model輸出隨機(jī)數(shù)的可能性 |
| next_action(self, all_last_actions, curr_state) | 輸入上一輪的Gnum和所有玩家的預(yù)測(cè),調(diào)用uptate_q_table, update_epsilon, prob2action等函數(shù),更新model的參數(shù)并輸出model的預(yù)測(cè)值 |
| prob2action(self, prob) | 根據(jù)model的不同模式,輸出由q_table和上一輪Gnum決定,加或不加softmax噪聲的Q-table的action |
| action2outputs(self, last_action, last_gnum) | 將Q-table的action譯碼成輸出值 |
| update_q_table(self, all_last_actions, curr_state) | 在不同模式下調(diào)用calculate_reward或calculate_multi_reward計(jì)算模型的reward,并借助Q-learning的更新公式更新q-table |
| calculate_reward(self, all_last_actions, curr_state) | 根據(jù)上一輪所有玩家的輸出和Gnum,計(jì)算自己的排名,調(diào)用my_rank2score產(chǎn)生得分作為reward |
| rank2score(self, rank, n_bots) | 輸入一個(gè)rank,根據(jù)模式的不同生成soft/norm-soft/soft-hard-avg類型的score |
| my_rank2score(self, my_rank, n_bots) | 調(diào)用rank2score,返回輸入的rank list產(chǎn)生的score list |
| gnum2state(self, gnum) | 根據(jù)模式的不同,將輸入的Gnum用不同的方式編碼成Q-table的state |
| calculate_multi_reward(self, all_last_actions, curr_state) | 根據(jù)上一輪所有玩家的輸出和Gnum,計(jì)算Q-table一個(gè)state的所有action的rank, 由于action較多,采用了與calculate_reward中不同的計(jì)算排名的方法,調(diào)用my_rank2score產(chǎn)生得分作為reward |
| random_softmax(self, vector) | 用softmax的方法對(duì)輸入的Q-table行進(jìn)行加噪聲的取Max,返回Q-table的action編號(hào) |
IIR主要含一個(gè)class IIR,包含的函數(shù)及功能:
| init(self, alpha, noise_rate) | 初始化一個(gè)QLearing對(duì)象,在npy文件中l(wèi)oad或初始化self.mem變量,它被用來保存Gnum的歷史平滑值 |
| get_next(self, last_gnum) | 根據(jù)輸入的上一輪Gnum和self.mem,計(jì)算出Gnum的滑動(dòng)平均值并加均勻噪聲輸出 |
每次調(diào)用get_numbers.get_result()都將創(chuàng)建一個(gè)QLearing對(duì)象和一個(gè)IIR對(duì)象。然后分別調(diào)用QLearn.next_action()和IIR.get_next()得到兩個(gè)輸出,再根據(jù)歷史表現(xiàn)二選一輸出。
除此之外,我們還編寫了幾個(gè)用于測(cè)試的模塊:test.py, plot_all.py, view_npy.py
我們的算法主要依賴于Q-table的學(xué)習(xí),因此我們?yōu)樗恿藥讉€(gè)trick。
將Q-table的action改為0.3-3的對(duì)數(shù)差分編碼,使得Q-table在同一個(gè)state下獲得更大的橫向精度,也使model的輸出保持在上一輪黃金點(diǎn)的0.3-3倍范圍內(nèi),保證了輸出的穩(wěn)定性。
事實(shí)上,除了上面的setting,我們還嘗試了不同的更新策略、編碼策略、reward策略,從中選擇了表現(xiàn)最好的作為QLearning model的最終版本。
UML
Design by Contract
契約式設(shè)計(jì)的好處在于:能夠?qū)⑶疤釛l件和后繼條件以及不變量分開處理,明確了調(diào)用方和被調(diào)用方的權(quán)利與義務(wù),避免了雙方的權(quán)利或義務(wù)的重疊,有助于使得整體的代碼條理更清晰、功能劃分更明確、避免冗余的判斷。在結(jié)對(duì)編程中,假設(shè)需要我和隊(duì)友各自寫具有相互調(diào)用關(guān)系的類時(shí),雙方都會(huì)在函數(shù)最開始用assert指出必須滿足的前置條件,并對(duì)后繼條件不符的情況進(jìn)行異常處理,同時(shí)對(duì)于不變量進(jìn)行檢查。
規(guī)范與異常處理
程序的代碼規(guī)范主要是依靠Pycharm的內(nèi)置代碼規(guī)范,達(dá)成共識(shí)的方式非常簡(jiǎn)單,我倆都有強(qiáng)迫癥,看著編譯器里面各種黃線白線就特別不爽,所以就不惜一切代價(jià)消除這些線,因此我們兩個(gè)都使用同一套代碼規(guī)范。
設(shè)計(jì)規(guī)范的話,根據(jù)《構(gòu)建之法》書中提到的,一個(gè)函數(shù)只干一件事情,功能劃分到最小單元。這一點(diǎn),我們嚴(yán)格執(zhí)行,甚至其中一個(gè)要把兩個(gè)數(shù)構(gòu)成的列表轉(zhuǎn)化成q-learning的獎(jiǎng)勵(lì)的函數(shù)都要拆成兩個(gè)來寫,其中一個(gè)rank2score負(fù)責(zé)專門將一個(gè)輸入值轉(zhuǎn)化為一個(gè)獎(jiǎng)勵(lì)輸出、另外一個(gè)my_rank2score在此上封裝,調(diào)用rank2score兩次,得到一個(gè)獎(jiǎng)勵(lì)向量。這樣寫確實(shí)有好處,代碼想要重用時(shí)在任何功能層面上都能即拿即用,改的時(shí)候也能一改全改,非常方便。
本次編程中只對(duì)測(cè)試過程中出現(xiàn)的異常進(jìn)行了處理,處理方式也特別簡(jiǎn)單,就是pass,如果是需要輸出的地方就指定一個(gè)確定的值。從這個(gè)角度上來說,我們確實(shí)很欠缺。以后應(yīng)該在編碼的過程中就想到異常處理這一步,形成習(xí)慣。
結(jié)對(duì)編程的過程
結(jié)對(duì)編程最開始接到任務(wù)時(shí)是很緊張的,因?yàn)椴粌H是要參加比賽,而且還要用到自己設(shè)計(jì)的AI,更重要的是給的時(shí)間非常短,還不到一周。聽到建議使用q-learning,更是比較懵逼,我們結(jié)對(duì)的兩個(gè)人從來沒有接觸過任何強(qiáng)化學(xué)習(xí)。因此,從任務(wù)剛剛布置下來就開始了工作。
2018/10/11 THU 晚上
兩個(gè)人找了一個(gè)休息室,架上電腦,先看了一下Q-learning 的論文,又看了一些大佬的博客,大概了解到了Q-learning是怎樣一回事,然后開始寫Q-learning的Python類,同時(shí)也看了一下老師給的Python接口,在公用的模擬復(fù)盤程序中試了一下能不能跑通。Q-learning類寫到一半,由于時(shí)間原因,回寢室休息,第一天工作結(jié)束。
放一張圖表明代碼進(jìn)度(真的,只寫了這么幾行,太慢了。。。)
2018/10/12 FRI 晚上
既然已經(jīng)熟悉了基礎(chǔ)知識(shí),也熟悉了接口,接下來就是悶頭寫實(shí)現(xiàn)了。這次實(shí)現(xiàn)的任務(wù)量也不是很大,Q-learning類的代碼量擴(kuò)充到了100行,同時(shí)又在這天晚上新寫了一個(gè)60多行的python下的模擬復(fù)盤環(huán)境,用以實(shí)時(shí)debug。至此,一個(gè)最基礎(chǔ)的q-table已經(jīng)寫好了。目標(biāo)不高,能跑就行。。
再上兩張圖表明代碼進(jìn)度(把之前寫的函數(shù)折疊掉了,用于增量表現(xiàn))
2018/10/13 SAT
和一起實(shí)習(xí)的同學(xué)去爬香山,自己長(zhǎng)期不運(yùn)動(dòng)導(dǎo)致突然爬一次山就渾身酸痛。。。沒有任何理由在這一天寫任何代碼。
2018/10/14 SUN
11點(diǎn)多才起床。。但是該碼代碼還得碼代碼。下午兩點(diǎn)開始,兩個(gè)人會(huì)面,開始試驗(yàn)q-learning。沒的說,先讓10個(gè)q-learning bots干一架再說!結(jié)果是這樣的:
就這樣對(duì)著屏幕分析了一個(gè)小時(shí)之后,覺得頭昏眼花。算了,寫個(gè)腳本畫個(gè)圖吧,于是就有了這個(gè)100多行的plot腳本(折疊了,要不看不到代碼總行數(shù))
然后每個(gè)bot都得到了一張屬于他自己的計(jì)分圖,如下圖,是第0個(gè)bot的Hard reward走勢(shì)
對(duì)比不同bot總體得分圖:(這里橫坐標(biāo)代表不同threshold參數(shù)的bot。顯然,最后一個(gè)bot被干掉了)
有了這些工具,媽媽再也不用擔(dān)心我們分析數(shù)據(jù)了。
就這樣,不停換和添加bot的參數(shù),試驗(yàn)到底具有怎樣個(gè)性的bot 容易贏,一直到10點(diǎn),然后開始拿在前面的bot戰(zhàn)中獲勝的bot放到模擬復(fù)盤中跑,模擬它在參加那次夏令營的比賽。然而,情況不容樂觀。。。成功地拿到-300多分,穩(wěn)居最后一位。
怎么辦呢?分析了一下bot提交的數(shù)發(fā)現(xiàn),好像bot 根本沒學(xué)到大盤走勢(shì)啊!!以至于到盤末大盤都已經(jīng)降到4.8左右了,我們的bot還在提交80.5這樣的數(shù)。。怎么辦?看看q-table吧
原來如此。。訓(xùn)練集太小,根本更新不全q-table嘛。。另外,每個(gè)action的精度太低了,只有1的精確度,那不就是說假設(shè)黃金點(diǎn)是4,結(jié)果我們就算預(yù)測(cè)到在這附近,也只能提交量階中最接近的數(shù)4.5嘛?別人如果是基于統(tǒng)計(jì)的模型的話很輕松就能算出類似4.11這樣的數(shù)啊,所以我們輕松被超過啊。好,現(xiàn)在知道問題了,那就簡(jiǎn)單了,解決問題不就得了!
第一:訓(xùn)練集太小,那我就增大數(shù)據(jù)量啊!怎么在確定有限的回合中增大數(shù)據(jù)量呢?原先的q-learning是在不斷試錯(cuò)中總結(jié)經(jīng)驗(yàn),每次只更新上一次對(duì)應(yīng)狀態(tài)和上一次對(duì)應(yīng)動(dòng)作那一個(gè)q-table 表項(xiàng)的元素。那為什么一定要等錯(cuò)誤發(fā)生之后再總結(jié)經(jīng)驗(yàn)?zāi)?#xff1f;為什么不能學(xué)習(xí)歷史,避免出錯(cuò)呢?于是就有了我們的第一個(gè)trick:模擬歷史學(xué)習(xí)法,即根據(jù)歷史的狀態(tài)和歷史的黃金點(diǎn)結(jié)果,模擬自己采取每一個(gè)可能的action,得到reward,一次對(duì)q-table中的一整行進(jìn)行更新。這樣數(shù)據(jù)量一下提升了100倍(100是我們每一個(gè)state的action數(shù))
第二:action精度不夠,討論過后,我們的思路漸漸被引向了學(xué)過的差分編碼,即每個(gè)action是到上一次黃金點(diǎn)的差量,這樣基于黃金點(diǎn)不可能有特別大變動(dòng)的假設(shè),可以把a(bǔ)ction對(duì)應(yīng)的值域縮小,在action總數(shù)不變的情況下,action的精度提高了。然而,我們不止于此,又想到:能不能讓精度對(duì)黃金點(diǎn)的位置有自適應(yīng)呢?于是想到了“對(duì)數(shù)差分編碼”(這個(gè)名字我不知道有沒有,是我隨口說的)就是,每一次的action都對(duì)應(yīng)于在上一次的黃金點(diǎn)基礎(chǔ)上乘以多少,這個(gè)乘的數(shù)就對(duì)應(yīng)action,并且action的下標(biāo)與乘的數(shù)的對(duì)數(shù)成正比。這樣,假設(shè)action對(duì)應(yīng)3-0.3的數(shù),則精度大概能達(dá)到(3/0.3)^(1/100)*gnum,其中,gnum是上一輪的黃金點(diǎn)。假設(shè)上一輪黃金點(diǎn)是4,那么精度可達(dá)0.093。如果黃金點(diǎn)更小,則精度更高。同理,state也可以使用對(duì)數(shù)編碼達(dá)到自適應(yīng)精度。這就是我們的第二個(gè)trick:對(duì)數(shù)(差分)編碼
好,有了這兩個(gè)點(diǎn)子,那就沒問題了,安心睡覺了,明天再碼代碼
2018/10/15 MON 晚上
瘋狂碼代碼趕ddl,把昨天的所有想法都實(shí)現(xiàn)進(jìn)去,為此,給bot添加了許多參數(shù)輸入,用于決定工作模式,到最后,q-learning bot的init函數(shù)變成了下面這樣,此時(shí)一個(gè)屏已經(jīng)裝不下了,全是參數(shù)。。。總體代碼量600-700行吧
這一天工作到凌晨?jī)牲c(diǎn),得到了不錯(cuò)的成績(jī):
但是發(fā)現(xiàn)在黃金點(diǎn)收斂到4.8左右的那次比賽的模擬復(fù)盤中,由于黃金點(diǎn)始終較高,所以導(dǎo)致我們的自適應(yīng)精度較低,導(dǎo)致我們沒能拿第一。仔細(xì)一想,一共有400次提交,黃金點(diǎn)如果不單調(diào)遞減的話,那就很有可多收斂到一個(gè)較大的數(shù)上,或者在其上下波動(dòng)。如果上下波動(dòng)的話,一般的統(tǒng)計(jì)模型都難以處理,反而對(duì)于我們的q-learning比較有利。然而是如果黃金點(diǎn)精密地收斂到一個(gè)較大的數(shù)上對(duì)我們非常不利,因?yàn)槲覀兊木扔邢?#xff0c;而一般的統(tǒng)計(jì)模型可以輕松擬合這種收斂的序列,精度要多少有多少。于是,迫不得已,再加一個(gè)與q-learning互補(bǔ)的統(tǒng)計(jì)模型吧,沒剩多少時(shí)間了,那就寫一個(gè)最簡(jiǎn)單的一階IIR濾波+噪聲吧。這就是我們的第三個(gè)trick。只好先睡覺了,明天就要比賽了,要保持精力。
2018/10/16 TUE 上午+下午
加了一個(gè)IIR,寫了互補(bǔ)控制策略,中途出了幾個(gè)bug,緊張死了。好在最后把bug都消除了,在兩次模擬復(fù)盤中都能拿第一了。5:00,提交最終版。
2018/10/16 TUE 晚上5:30-6:00
第一輪比賽。才拿了個(gè)第六名,感覺很失落,趕緊不吃飯了,分析問題原因。
2018/10/16 TUE 晚上6:00-7:00
復(fù)盤看了一下q-table,發(fā)現(xiàn)本次比賽的統(tǒng)計(jì)特性決定了q-table的值特別聚堆,沒區(qū)分度,于是增大了softmax的指數(shù)倍數(shù)(下圖中函數(shù)第三行的“vector * 150”),相當(dāng)于normalize了。提交上去,聽天由命了。
2018/10/16 TUE 晚上7:00-7:30
第二輪比賽,穩(wěn)拿第一。兩方面原因,一方面是我的改動(dòng),另外一方面有很多提交搗亂數(shù)字的隊(duì)伍都更規(guī)矩了,當(dāng)然這也是大趨勢(shì),如果很多人擾亂的話,會(huì)導(dǎo)致擾亂的效果下降,第二輪這種改變也符合我們的預(yù)期。
最后總評(píng)我們也是第一。很開心,沒白浪費(fèi)這么多時(shí)間。
總結(jié)成功的原因
從計(jì)劃安排與實(shí)施過程方面,有三點(diǎn)原因:
1.很快確定核心算法,中途雖然遇到波折,但是始終都在以同一個(gè)算法為中心進(jìn)行改動(dòng),因此在有限的時(shí)間內(nèi)能夠把這一個(gè)算法做到極致
2.起步過程不貪心,沒有想要一口吃個(gè)胖子。
3.信心比較堅(jiān)定,測(cè)試修改過程中都有一個(gè)明確的目標(biāo):至少要在模擬復(fù)盤中拿到第一。
在技術(shù)層面上,有三點(diǎn)原因:
1.在宏觀上,我們使用的策略是“唯快不破”的想法,即:以最快速度適應(yīng)大盤的變化規(guī)律,這樣,不論對(duì)手采取什么手段,我們都能很快地學(xué)出來這種手段的適應(yīng)辦法,從而超過使用這種手段的人的得分。
2.在微觀上,我們使用的模型參數(shù)量遠(yuǎn)多于其他組的模型,因此有很強(qiáng)的適應(yīng)能力,不僅如此,我們還設(shè)計(jì)了多種不同的tricks來提高模型性能,大幅提高學(xué)習(xí)效率。
3.搭建了比較完善的調(diào)試平臺(tái),能夠?qū)崟r(shí)地跟蹤各項(xiàng)參數(shù)的變化以及得分的變化,針對(duì)性地采取解決方案。這一點(diǎn)在比賽中場(chǎng)修改程序中起到了至關(guān)重要的作用。
結(jié)對(duì)編程的心得體會(huì)
在結(jié)對(duì)編程中,我對(duì)于python比較熟悉,隊(duì)友對(duì)于C#比較熟悉。因此,在算法上的python代碼主要由我來碼,隊(duì)友做領(lǐng)航員的角色。在復(fù)盤程序的調(diào)試中,主要由隊(duì)友操作C#程序,我也從中學(xué)到了一些C#的知識(shí)。結(jié)對(duì)編程的過程中,我和隊(duì)友互相實(shí)時(shí)復(fù)審代碼,十分高效,提早解決了很多可能出現(xiàn)的問題,算是學(xué)習(xí)到了。而且我們還從零開始學(xué)習(xí)實(shí)踐了不太好搞的強(qiáng)化學(xué)習(xí),很有收獲!
列舉一下我認(rèn)為本次結(jié)對(duì)編程中雙方的優(yōu)缺點(diǎn):
隊(duì)友:
優(yōu)點(diǎn):
1.看代碼非常仔細(xì),不論我寫到那里,她都能在我的代碼中提出我可能忽略的地方。
2.在我專心實(shí)現(xiàn)算法時(shí),她也會(huì)查找一些資料和庫的使用方法,輔助我快速實(shí)現(xiàn)。
3.理解代碼的能力以及與我溝通的能力很強(qiáng),有幾次我寫代碼過程中忘記告訴她我的想法到底是什么了,但是她仍然能很快通過看代碼意會(huì)出我到底在干什么,甚至進(jìn)一步提出改進(jìn)方案。
4.學(xué)習(xí)能力比較突出,在結(jié)對(duì)最開始,她好像不太熟悉numpy等庫,但到結(jié)對(duì)后期,她已經(jīng)能夠指出我使用這些庫的問題。
缺點(diǎn):
可能是我很快就把解決方案都說到了吧,隊(duì)友對(duì)于算法改進(jìn)方案的貢獻(xiàn)不是很多。
我:
優(yōu)點(diǎn):
1.對(duì)于python的常用庫都比較熟悉,因此搭建python的調(diào)試、測(cè)試環(huán)境等都很快。
2.綜合運(yùn)用學(xué)過的知識(shí),想到很多trick,提高了模型能力。
3.對(duì)于代碼架構(gòu)有著自己的一套設(shè)計(jì),功能劃分都很明確,因此編程到后期代碼量較大、功能較多時(shí)仍然能夠保持頭腦比較清醒,把更大的精力放在算法設(shè)計(jì)和實(shí)現(xiàn)功能上,而非重構(gòu)和管理代碼上。
缺點(diǎn):
比較粗心,經(jīng)常被挑錯(cuò),而且有的時(shí)候?qū)懘a走神,寫著寫著就忘記了最開始提到的某些重點(diǎn)。
轉(zhuǎn)載于:https://www.cnblogs.com/RubikCube/p/9817804.html
總結(jié)
以上是生活随笔為你收集整理的软件工程第一次结对编程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: TensorFlow 官方文档中文版发布
- 下一篇: elasticsearch 性能调优(转