不知不覺又作了三次做業,容我在本文胡言亂語幾句2333。javascript
第五次做業是前面的電梯做業的多線程版本,難度也有了一些提高。(點擊就送指導書)php
程序的類圖結構以下:
css
程序的邏輯時序圖結構以下:
html
能夠看出,此次的程序依然存在部分類或方法代碼較爲集中的狀況,這樣的狀況在類Lift
、LiftController
,甚至筆者本身的第三方庫DebugLogger
中較爲明顯。甚至在Lift
和LiftController
類中能夠發現其實業務邏輯已經很是的密集。前端
不出意料,我方公測不存在問題。java
此次對方的公測也不存在問題,能夠說是賞心悅目了。git
不出意料,我方互測不存在問題(然而出了點別的情況23333,爲了保證筆者博客的乾淨,筆者將在文末講述,此處不作討論)github
此次,筆者給對方找出了兩個bug。正則表達式
這兩個bug分別是:算法
Invalid格式錯誤 這個,其實就是字面意思,這位同窗的程序Invalid請求輸出信息的時候格式不正確(準確的說,是不符合指導書需求)。
ER
這樣的請求進入調度器以後將直接轉進對應電梯的子消息隊列。FR
這樣的請求將被暫存在調度器的消息隊列,等能夠接受這一請求的電梯出現後,將其發送至該電梯的請求隊列中。因而,在通常的架構中,在請求隊列裏,通常都會有這樣的一個輪詢邏輯:不斷輪詢各個電梯的狀態,等能夠合法接受請求的電梯出現後,將其進行分配。然而就是在這個地方,若是,在輪詢的一半時,某個電梯的狀態忽然改變了,該怎麼辦。
假設,有這樣的三個請求:
某電梯即將到達一層,目前還在運行中。其餘電梯均正忙。
因而,假設這個時候開始了一個輪詢:
然而,最不巧的是,偏在這個時候,這個電梯已經到達了一層且處於空閒狀態,並且請求3還沒判斷,因而就會出現這樣的尷尬局面:
這還不算完,因爲該電梯當前在一層,因而:
災難性的後果發生了,這三個原本多半能夠有效負載均衡的請求,由於一個線程同步問題,就這麼被一波帶走了。
事實上,這個bug在筆者本身進行測試的時候,就已經發現。筆者思考後,以爲有兩種思路解決這一問題:
此次做業能夠說是筆者在多線程上一次工程化的嘗試。
筆者以前主要寫的是C++
(用於算法競賽)、C#
(GUI桌面應用編程)、Python
(用於各類腳本)、Ruby
(用於同袍和Questionor後端的維護)、php
(偶爾也會用到)以及前端的html
+css
+javascript
。雖然之前接觸過多線程編程,不過也大都用於腳本編程(實際上,多線程的這種特性在網絡請求等待的時候能夠極大提升腳本效率),並且也大都是簡單的併發+阻塞。在此次做業中,筆者真正在強類型OOP語言中進行系統化工程化的多線程編程仍是頭一次。
此次筆者認真研究了線程相關的鎖(lock)、監視器(monitor)等機制,而且仔細思考了在這樣的一個工程中如何經過這些機制來避免由於同步問題致使的錯誤,且兼顧併發效率。能夠說收穫不小。
此外,筆者的程序結構依然存在高內聚的問題,再加上這是第一次設計真正的多線程工程代碼,太多的時間花在瞭如何讓程序沒有bug上,因而代碼風格仍是較差。
第六次做業叫作IFTTT,大體意思就是基於IF
... THEN
... 邏輯的文件系統監視器(點擊就送指導書)
此次代碼的類圖結構以下:
此次的UML時序圖結構以下:
此次的代碼質量分析報告:
能夠看見,整體的代碼質量有較大的改觀,不過仍是存在少數類的行數過大。
不出意料,公測沒有出現錯誤。
不過這裏有一個有趣的小插曲,筆者一開始公測被報了一個bug,理由是監視器沒有作出響應。筆者打開了這位同窗提供的測試輸入,用的是D:\
這樣的根目錄,因而筆者想起來指導書上貌似規定過不要用規模過大的路徑進行測試,並對於這一狀況向這位測試人員進行了解釋,因而呢,改爲了經過。(至於文件系統監視器相關的問題,筆者將在這一節的總結中稍微講些本身的見解)
對方的程序,在公測環節彷佛出現了不少的bug。並且彷佛徹底不具有對於整個目錄樹的監視反應能力。(也許是功能就沒設計全?)
不出意料,我方在互測環節沒有出現任何bug。
此次互測中,對方被筆者查出了兩個bug:
此次做業是筆者所寫的第二次多線程工程代碼,從代碼分析數據來看,總體代碼風格有了較大的改觀,再也不有很明顯高度集中的類設計,主要方法的代碼密集程度也有所降低。
然而,筆者本身內心清楚,不少地方的代碼仍舊不夠成熟。同時,筆者已經開始探索着開發一套能夠提供各種快速搭建和管理功能的java工程代碼框架,而且已經從第七次做業開始使用這樣的框架。
以及,此次貌似是指導書被吐槽最嚴重的一次。此次的issue也能夠說是史無前例的龐雜,其中筆者以爲一部分緣由是指導書沒有去匹配大部分人對文件系統以及IFTTT的認知水平。
說到文件系統監視器,通常來講有兩種主要的解決方案:
顯然,此次做業通常採用的策略是前者。這是一種很廉價且在數據規模不大的狀況下很靠譜的策略。
可是,各位應該也已經發現這一方法的一些明顯弱點:
關於第三點,筆者舉例說明下。
例如,在兩次快照拍攝之間,a.txt
重命名爲a1.txt
,b.txt
目錄更改到了dir\a.txt
,這麼一來,快照增量檢測到的應該是這樣的四條信息:
a.txt
b.txt
a1.txt
dir\a.txt
假如咱們的a.txt
和b.txt
文件大小和修改時間再徹底一致的話(本次做業中判斷文件等價的惟二依據是文件大小和修改時間,並沒採用各種文件指紋算法),問題就來了——a.txt
和b.txt
到底去了哪呢?
a.txt
和b.txt
等價,因此解釋爲a.txt
--> dir\a.txt
, b.txt
--> a1.txt
,這樣是說得通的。a.txt
和b.txt
等價,因此解釋爲a.txt
--> a1.txt
,b.txt
消失,dir\a.txt
出現,其實也說得通。實際上,快照+增量機制所帶來的一個無解的難題就是增量事件變得再也不惟一可斷定。
然而,這還不算完。咱們此次採用的是多線程機制檢測文件系統變化。因而呢,這樣又不得不引入了線程同步問題。由於,按照原來課程組的要求,彷佛還得保證在出現同質文件的狀況下事件不能夠發生衝突(例如,對於上述的例子,不能夠同時檢測到a.txt
--> a1.txt
和b.txt
--> a1.txt
)。實際上這樣的要求自己徹底合理,這件事在單線程內的斷定也很好辦,HashMap判重一下便可。然而要是這樣的斷定分散在多個線程內呢?因而又有了一連串的問題:
因而這個問題又成了一個無底洞(因而,後來課程組決定不對這種極端狀況進行測試)。
根據我的瞭解,在實際應用中,這樣的問題經常是基於另一種思路——根據文件系統底層事件來檢測文件系統變化。
Java
和C#
中均有現成的FileSystemWatcher
類可供直接使用。此次做業中筆者所檢查的程序存在程序不能正常結束的狀況。筆者打開了這份程序進行了查看,這份程序在頂層,打開了各個線程後,就再也不對各個線程進行控制。
筆者以爲,多線程程序設計的一個基本原則是——任何線程在任什麼時候候均不該該處於脫離控制的狀態。不管是消息隊列,仍是各個業務邏輯線程,甚至是GUI,在任何階段都應該在上層線程的控制之下。即上層須要結束線程的時候能夠隨時正常下達指令,且下達指令後須要用join等命令進行阻塞等待,直到各個線程安全關閉,再結束程序。
第七次做業是出租車系統模擬。不得不說,事情終於開始變得有趣了(點擊就送指導書)
本次做業的類圖結構:
本次做業的UML時序圖:
本次做業的代碼質量分析報告:
能夠看見,排除GUI模塊以外(GUI模塊並不是出自筆者之手),代碼局部複雜度已經獲得了必定程度上的控制(三個紅色的那個函數點開看了下,是因爲代碼重複性較高致使的)
不出意料,公測我方未被測出bug。
對方的公測存在一個bug,即沒有對於起點終點爲同一個節點的狀況進行斷定。這樣的bug添加一處斷定便可。
最終,不出意料,我方未被測出bug。
不過中途也仍是出現了一些有趣的小插曲。這位測試者試了下在缺乏map.txt
的狀況下運行程序,而後看到筆者的程序輸出了紅色字,因而認爲筆者的程序crash
了。
然而實際上,筆者的程序外部包裹了try catch,只是在catch外面使用了printStackTrace
。而且程序的實際返回值也是0
,也就是說是正常且平穩的結束的。因而筆者擺事實講道理,進行了申訴以後,對方撤回了這個bug。
然而程序也的確顯示了紅色字,這又是爲啥呢?筆者經過研究java源碼,找到了問題所在。
咱們知道,通常高級語言程序通常會帶三種自帶的Stream:
Java
中的System.in
Java
中的System.out
Java
中的System.err
接下來咱們來看看一切異常類的祖先類——Throwable
類的部分源碼:
public void printStackTrace() { printStackTrace(System.err); } /** * Prints this throwable and its backtrace to the specified print stream. * * @param s <code>PrintStream</code> to use for output */ public void printStackTrace(PrintStream s) { synchronized (s) { s.println(this); StackTraceElement[] trace = getOurStackTrace(); for (int i=0; i < trace.length; i++) s.println("\tat " + trace[i]); Throwable ourCause = getCause(); if (ourCause != null) ourCause.printStackTraceAsCause(s, trace); } }
該源碼片斷截取自Throwable
類,能夠看到,默認不帶參數的printStackTrace
類,實際上是在調用System.err
進行輸出。因此難怪輸出的會是紅色字,由於的確輸出到了異常流內。
說到這裏問題就解決了,之後若是須要避免相似的誤解,調用printStackTrace(System.out)
而非printStackTrace()
便可。
這位同窗的代碼整體而言寫的仍是挺不錯的,不過在測試的過程當中發現有一個很坑爹的設定。
這份程序只有在按照指定的方式結束程序後,纔會有detail.txt
細節信息輸出(也就是說用其餘的方式,即使平穩結束程序尚未文件輸出)。
這樣一來,雖然實際上輸出了,但也等於徹底不具有實時交互的特性。雖然指導書上並無明令禁止,但實際上已經違背了這個設計的初衷。
因而筆者向多名助教求證過以後,報了一個imcomplete
。
在此次做業中,筆者在開始動工以前,準備了一個簡單的程序框架模板。使得程序搭建效率有了略微的提升(關於程序模板,筆者將在下文繼續講述)。
同時,筆者自我感受,從此次開始,筆者的多線程程序設計框架開始變得日趨成熟。
筆者從這三次做業開始,真正接觸了系統化工程化的多線程OOP程序設計,開始從零開始一步步思考,如何充分利用多線程的併發機制,協同各個進程,同時充分兼顧多線程併發效率。
從中,筆者仍是看到了自身的一些不足:
筆者將會從接下來進一步的實踐當中,進一步改善代碼風格,設計更完善更符合規範,人機性能更好的程序。
據筆者觀察,貌似不少的同窗至今仍熱衷於使用靜態數組來進行數據的存儲。
起初,筆者十分不解,在Java
這樣的OOP語言中,相關的數據結構封裝類可謂至關完備,爲啥還要使用數組呢?
通過一些觀察,不少人仍離不開靜態數組的緣由大抵以下:
0
、1
、2
之類的數字表示(然而,靜態數組實際上有很致命的缺陷:
grep
命令將對方程序裏頭的靜態數組一口氣揪出來,只要能找到,基本上很快就能開開心心的拿下此次的一血。這招一抓一個準,屢試不爽,並且基本是抓住的都是crash,通常人我不告訴他23333靜態數組這樣的東西只在極少數特定場合下稍微方便些,然而帶來的倒是不少性能和工程性上的不可控,可謂得不償失。
建議使用Java
內置的數據結構,諸如List
、Vector
、ArrayList
類,這些類均進行過有效的代碼封裝和性能優化,各方面性能均有保證,且不會很容易的出現錯誤。
從第七次做業開始,引入了一些代碼規範相關的考察。我的以爲,其實這是一件很好的事情,畢竟真正的工程永遠離不開維護,也很難離開teamwork。
然而,據筆者觀察,彷佛不少人對這件事頗爲反感與不解,諸如如下的論調:
是的,上述的想法能夠說很是廣泛,筆者在第七次做業正式發佈後的客服羣裏,基本天天都能看到這樣的論調。感受至關多的人以爲這個要求很不合理。
首先,關於代碼規範的重要性,筆者在上次博客做業已經有說過,不想再重複嘮叨一遍(或者說,嘮叨了估計也沒人愛聽。。。)。
不過這樣估計說服不了任何人,那容筆者來舉幾個親自遇到的案例吧。
這個圖,來自於第六次做業筆者測試的這位同窗的summary.txt
恩,沒錯,這就是summary.txt
。爲了防止各位看了一臉懵逼,我還能夠告訴大家,冒號前的0
、1
、2
、3
表示的是四種不一樣的事件。
那麼,請你如今告訴我,這個summary.txt
是在表達什麼?
是的,沒錯,看不懂的不止你一個,由於筆者當時看到這個的時候也仍是一臉懵逼(即使猜到了前面的數字表示的是各個事件類型)。
因而,筆者只好開始研究他的源代碼。而後很驚喜的發現,這位老哥的全部代碼全都寫在了一個文件裏頭,並且右邊滾動條上還密密麻麻的都是各類warning。
終於,功夫不負有心人,筆者終於找到了一絲線索:
恩,就是這裏
public void addSummary(String trigger) { lock.lock(); summary[trigger.equals("renamed") ? 0 : trigger.equals("Modified") ? 1 : trigger.equals("path-changed") ? 2 : 3]++; PrintWriter output; try { output = new PrintWriter("summary.txt"); for (int i = 0; i < 4; i++) output.println(i + ":" + summary[i]); output.close(); } catch (FileNotFoundException e) { System.out.println("Fail to output to summary.txt!"); } lock.unlock(); }
就是這句
summary[trigger.equals("renamed") ? 0 : trigger.equals("Modified") ? 1 : trigger.equals("path-changed") ? 2 : 3]++;
咱們來研究下這個超長的三元表達式在表達什麼(emmm,筆者做爲多年的老碼農,表示愣是一眼沒看懂):
renamed
,返回0
,不然繼續Modified
,返回1
,不然繼續path-changed
,返回2
,不然繼續3
到這裏,筆者費盡千辛萬苦,終於看明白這個文件了。
想到這裏,假如,我不是一個測試人員,而是這位老哥的teammate,想要一塊兒開發一個項目。
若是,須要對接的時候要是遇到了這樣的狀況,得費多大的勁才能搞清楚?
試想一想,若是你每次開發項目,都要花一堆的時間在這種無謂的事情上,你以爲值得麼?有何效率可言?
若是,輸出的不是
0:1 1:0 2:0 3:0
而是
renamed: 1 Modified: 0 path-changed: 0 size-changed: 0
若是,這個程序可以知足咱們所規定的:
這麼一來能夠節省多少的時間。
在第七次做業中,筆者分配到的測試程序,依然存在和上面相似的狀況。
當筆者一次性輸入多個請求的時候,等待3秒後,出現了一片的報錯:
筆者當時瞬間就懵了,到底哪些指令執行了,哪些指令分配失敗了?
而後對detail.txt
仔細研究了好半天后,才終於確認程序運行的是對的。
其實吧,在這種時候順帶輸出下錯誤的詳細狀況(甚至不用太詳細,輸出一下哪條指令出錯也是好的),對於編程者而言真的就是幾秒鐘的事情。
然而若是不加,不只給別人會帶來很大的困擾,大家本身debug的時候,也會處於徹底摸不着頭腦的狀態——由於不管哪裏錯了,輸出的都同樣。
說了以上這些,其實我只想說明一點:
固然,可能不少人仍是沒法理解,這也正常。等有一天大家真正參與項目維護(尤爲是多人團隊項目)的時候,大家就會明白這些事情的重要性了。
筆者在以前的屢次程序做業中,發現每次都要花上好半天的時間搭建程序框架,並且作得基本都是重複工做。
做爲一個聰明的懶人,筆者因而本身寫了一個簡單的的工程代碼模板。Git倉庫,歡迎star
這樣的一個框架能較好的符合筆者本人每次寫代碼的工程結構須要。同時對於一些常見需求也都進行了以行爲邏輯爲主的封裝,能夠經過類繼承等方式快速構建功能模塊(尤爲是多線程功能模塊)。
這個庫將會不斷地維護和更新,但願能夠幫助到你們。
筆者做爲一個寫了不少年代碼(至今已有10年有餘),且實際維護過不止一個工程項目的程序猿,每當想在羣裏分享一些我的對於程序、對於代碼規範、對於工程的理解和見解時,永遠會有一些人站出來針鋒相對。
他們的主要邏輯以下:
其實,筆者對於這樣的想法仍是表示能夠理解的。畢竟每一個人站的角度不一樣,格局天然也有天壤之別(俗稱:屁股決定腦殼)。
不過,筆者仍是但願各位能好好想一想大家是爲什麼而學習的。僅僅就是爲了趕快畢業拿文憑?
固然了,若是這就是你的所有想法,並且之後不想從事這方面的工做的話,那的確隨意,只能說你和我們技術發燒友(或者最起碼是打算靠這個吃飯的人)不是一路的人。
若是不是這樣,那麼你就該站在更高的格局上想問題:
結論很簡單,能有所收穫的,就是對的;能收穫更大的,就是更好的。
接下來我來逐個迴應下這三個常見邏輯:
咱們就是想完成做業啊
請想想僅僅就是完成一個做業的話,你能學到的東西有多少。。。個人程序是不能運行了仍是怎麼着了
請想一想,程序能運行可是毫無維護性和可合做性,這種代碼除了糊弄一下做業有任何價值麼。。。你說的再多代碼規範啥的同樣會被鑽牛角尖啊
請相信咱們的助教團,他們會給你公道的。你只管作好本身就是了。另外,可能有些人(甚至包括一些著名大佬)受到知乎上一些所謂的6系人的影響,認爲北航的OO課程就是一無可取毫無收穫的。對於這樣的無腦黑,我只能對您表示深深的同情和憐憫,由於您在用您的將來前途消費,而目的,僅僅只是爲了證實一個帖子,和一些前人片面的話語的正確性。
前方高能。接下來的章節可能會引發部分人的不適,請非戰鬥人員迅速撤離。
時間過得真快,一不當心又過去了三次做業。不過這其中天然也有些不爽的事情發生。
雖然呢,筆者很清楚,這樣的東西寫進本身的技術博客實在是很是不雅,簡直能夠說弄髒了筆者的博客(事實上這也是筆者沒在上面說這件事的緣由)。可是有些事情嘛。。。仍是不吐不快。
因此接下來講件事,請你們自行評判。
筆者在第五次做業截止後的一個晚上,忽然被告知,本身被無效了???
而後,到羣裏問了下助教緣由,緣由是,我在readme.md
的指導書url連接中包含了"我的信息"
連接地址全文以下:
https://files.cnblogs.com/files/HansBug/OO%E7%AC%AC05%E6%AC%A1%E4%BD%9C%E4%B8%9A%E6%8C%87%E5%AF%BC%E4%B9%A62018v1.2.pdf
當時,懵逼了。然而再仔細一想,發現事情並不簡單。
越想越以爲不對勁。因而筆者過後仔細一琢磨,能夠推測出這我的的邏輯流程圖:
個人天啊,老哥誒,您老人家爲了避免測試別人的程序爲了讓本身睡大覺,可真是煞費苦心啊。走了這麼多步終於找到了我,我佩服你那爲了偷懶不怕艱難險阻過五關斬六將的精神,是在下輸了。
或者,咱們甚至還能夠在這個的基礎上進一步擴展下:
也許可能各位還有點疑惑,甚至以爲我是在用惡意揣測別人。很明顯,有如此耐心歷經那麼多個環節,只是爲了找別人的無效做業痕跡的人,我並不相信他有可能一上來就是想好好測試的。
然而這樣的人物,一個對學術毫無敬畏之心只想着偷懶萬歲的人,竟然還能和筆者分到了相近的段位,筆者只能哀嘆——「知人知面不知心」啊。
綜上,我只想對這人說一句:你有種給我站出來,別玩陰的!!!
不過,再一想一想,人家如何如何,關我何事。筆者仍是想認真的對學術和工程負責到底的,以及,沒法帶來任何實質性改變的怒火是毫無心義的。
正如上文所言,這類人挖地三尺的通常性動機,就是在於一旦把對方無效了,本身就能夠不用測試了。
事實上此次事情的後續進展也代表筆者的猜想徹底正確——截至互測時間結束,筆者這份被無效的做業依然一個點都沒有進行測試(包括公測)。最後仍是在筆者的一再要求下,課程組的助教們幫筆者完成了測試(在此感謝默默支持的老師和助教們,真的很是感謝)。
至此,這人的動機能夠說是很是明顯了。若是他只是一個按照規則辦事且對學術和工程質量負責的人,那如何解釋到最後都一個點都沒測試的狀況?
說到這裏,筆者對於課程有個改進的思路,以遏制這種表面矯枉過正實則惡意滿滿的行爲:
理由其實也很是簡單:
以上是個人見解。