《軟件方法(上)》讀書筆記

1、建模

1.1、業務建模之願景

重點1:通俗一點講,一個東西的願景就是:東西最應該賣個誰,對他有什麼好處?

重點2:願景是需求排序的主要依據。

重點3:老大、願景、需求都是基於現狀尋找最值得的改進。改進過後,又是新的現狀了,還是基於現狀尋找最值得的改進。進一步說也可以說,需求只有真假對錯,沒有變化。說需求有變化,那是從一個靜止時間點來看的。

1.1.1、願景




建模之願景

1.2、業務建模之業務用例圖

有了願景,我們知道老大對他所代表的組織的現狀的某些指標不滿意。接下來就可以研究組織,弄清楚到底是組織的哪些環節造成了這些指標比較差,這就是業務建模(Business modeling)的主要內容。

重點1:軟件系統只是組織的一個零件。組織裏面還有很多系統,其中最值錢的是千百年來一直在使用,現在依然是最複雜的系統——人腦系統。

重點2:開發團隊發現需求“容易變化”。根源之一是需求的來路不正,沒有把系統當作一個零件放在組織中來看,靠拍腦袋得出需求,導致得到的系統需求是錯的。

1.2.1、業務角色

① 業務執行者

以某組織為研究對象,在組織之外和組織交互的其他組織(人群或機構)就是該組織的執行者。以一家商業銀行為研究對象,觀察在它邊界之外和它打交道的人群或機構,可以看到儲戶來存錢,企業來貸款,人民銀行要它作監督等等,這些就是該商業銀行的執行者,如下圖所示:




業務執行者(一)

這裏要注意的是,作為觀察者的建模人員本身是一個人腦系統,所以在觀察組織邊界時,直覺上觀察到的不是組織之間的交互,而是組織派出的系統之間的交互,但是一定要把它理解成組織間的交互,因為談論業務執行者時,研究對象是組織,所以外部對應物——業務執行者也應該是組織。例如:以某國稅局為研究對象,可以觀察到企業財務人員到國稅局報稅,但業務執行者不是企業財務人員,而是企業。也許到後來,企業財務人員和國稅系統交互,又或許再後來是企業系統與國稅系統交互,從組織的抽象級別來看,都應該理解為企業和國稅局這兩個機構之間的交互,如下圖所示:




業務執行者(二)

② 業務工人

組織內的人稱為業務工人,例如某商業銀行裏面的營業員。業務工人是可以被替換的人腦零件,它可能被其他業務工人替換,但更有可能被業務實體替換。

③ 業務實體

業務實體是組織中的非人智能系統,例如銀行的ATM、點鈔機、營業系統。

1.2.2、識別業務用例

重點1:業務用例指業務執行者希望通過和所研究組織交互獲得的價值。業務用例是組織的價值,不會因為某個人腦系統或電腦系統的存在或消失而改變,好比如300年前的商業銀行和當前的銀行的業務用例是不變的,因為銀行提供價值的本質沒有改變。所以“這個系統的業務用例是什麼”這樣的說法是錯誤的。

重點1:用好用例,關鍵在於理解“價值”。價值是期望和承諾的平衡點、買賣的平衡點。例如以“醫院”為研究對象,真正的用例是“患者→看病”,而不是“患者→挂號”,患者→挂號”可以是以“挂號室”為研究對象的業務用例(如下圖)。所以做任何事情之前,要搞清楚“邊界”,沒有邊界會很容易盲目“拍腦袋”做一些努力但沒效果的事情。




業務用例識別

1.2.2.1、識別業務用例思路

識別業務用例的思路有兩條:

【從外到內】從業務執行者開始考慮,思考業務執行者和組織交互的目的(主要);

【從內到外】通過觀察組織的內部活動,一直問為什麼,向外推導出組織外部的某個業務執行者(補漏)。




業務用例識別思路

1.2.2.1.1、識別業務用例常犯錯誤

錯誤1 :把業務工人的行為當做業務用例。




錯誤用例識別(1)

錯誤2:業務用例隨待引入系統伸縮。




錯誤用例識別(2)

錯誤3:把害怕遺漏掉的擴展路徑片段提升為業務用例。




錯誤用例識別(3)

錯誤4:管理型業務用例。




錯誤用例識別(4)

總結:錯誤的根源主要源於:建模人員分不清問題和問題的解決方案。

1.3、業務建模之業務序列圖

1.3.1、描述業務流程的手段

本章節主要討論的是業務建模中最繁重的工作——描述業務用例的實現,即業務流程,然後改進它,推導出待引入系統的用例。目前描述業務流程的可選擇手段有文本、活動圖和序列圖,它們的主要區別如下(以財務部“員工→報銷”用例的實現為樣例):

● 文本




文本樣例

文本的缺點是不夠生動,所以在描述業務流程時很少使用文本方式。不過,描述系統用例(即系統需求)的流程時,文本是常用的,因為此時更注重精確,而且還要表達業務規則、性能等目前尚未被UML標準覆蓋的內容。

● 活動圖




活動圖樣例

序列圖是UML圖形描述業務流程的兩種選擇之一。活動圖的前身是流程圖,應該是在建模人員中使用頻率最高的圖形,是隨机械工程領域慢慢引入到計算機領域。不過,隨着編程語言表達能力越來越強,針對簡單的分支或循環邏輯畫圖在很多情況下已經變得沒有必要。

● 序列圖




序列圖樣例

序列圖與活動圖的比較如下:

1)活動圖只關注人,序列圖把人當作系統;

在上一章節中已經提到,現在的業務流程中已經有很多領域邏輯是封裝在業務實體而不是業務工人中,如果忽略非人智能系統,很多重要信息就丟掉了。

2)活動圖表示動作,序列圖強迫思考動作背後的目的;

序列圖可以更加清晰地表述業務工人或業務實體對外的責任,也就是用例的期望值。期望和承諾是用例和對象技術的關鍵思想,使用序列圖來做業務建模,“對象協作以完成用例”的思想就可以統一地慣竊業務建模和系統建模的始終。

3)活動圖“靈活”,序列圖“不靈活”;

不少人認為活動圖勝過序列圖的地方是它靈活,但這靈活是一把雙刃劍。活動圖很靈活,他的控制箭頭可以指向任何地方,就像編碼原始時代的“goto”語句,所以活動圖很容易畫。不過,“很容易畫”的活動圖也比較容易掩蓋建模人員對業務流程認識不足或者業務流程本身存在缺陷的事實。序列圖可通過alt、loop等結構化控製片段來描述業務流程,強迫建模人員用這種方式思考。

1.3.2、業務序列圖要點

1.3.2.1、消息代表責任分配而不是數據流動

序列圖中最重要的要點是消息的含義。A指向B的消息,代表“A請求B做某事”,或者“A調用B做某事的服務”,而“做某事”是B的一個責任。




序列圖消息含義

1.3.2.2、抽象級別是系統之間的協作

業務建模的研究對象是組織,出現在業務序列圖生命線上的對象,其最小顆粒是系統,包括人和非人系統,而系統之間最主要突出的是協作。所以“系統粒度”和“協作”是業務序列圖的關鍵要點,如果記住了這兩個關鍵點,就可以避免了對組織對象抽象的錯誤以及對協作理解的錯誤。




系統內部的組件暴露



表達了過細的交互步驟

以上說的兩種錯誤是把需求和分析的工作流的工作帶入了業務建模。第一樣例圖提到的系統內部的組件,應該在分析和設計工作流中描述;第二樣例圖提到了交互步驟,應該在需求工作流中描述。除了以上兩種抽象級別的錯誤,還有一種是:業務序列圖的內容和業務用例圖差不多,如下所示:




目標組織作為整體出現在業務序列圖中  

1.3.2.3、把時間看作特殊的業務實體

業務序列圖中,我們把時間看作特殊的業務實體。把時間看作上帝造好掛在天上的一個大鐘,向全世界各種系統發送時間消息,這樣,就和後面需要工作流中映射系統用例的時間執行者一致了,同時也幫助理清什麼情況下使用時間執行者的問題。




把時間當作一個系統

值得注意的一點是,時間和定時器不是一個概念,時間是“外系統”,定時器是其他系統用來和時間打交道的“邊界類”。世界上只有一個時間系統,但有無數的定時器。如果建模人員在識別系統用例時說“執行者是定時器”,那就錯了,執行者是時間。




時間和定時器的區別

1.3.2.4、為業務對象分配合適的責任

分配給業務對象的責任必須是該對象有能力承擔的,這需要我們自身必須對理解要十分清晰,畢竟我們自己說話有時候的會含糊。例如“工作人員用Word寫標書”這樣的說法好像可以接受,但如果按照說話的文字不假思索地隨便畫,會很容易導致對象責任分配不準確。




不恰當與不恰當的責任分配對比

1.3.3、現狀業務序列圖

業務序列圖描述的是業務流程,建模需要通過在現狀的業務序列圖基礎上找出改進的要點。如果要把現狀序列圖畫出來,就必須讓自己站在客觀的角度“親臨現場”,“如實”地把所看到的記錄下來,儘力描繪出真實的現狀。但說起來非常簡單,做到卻極其困難。總結到這裏,忽然讓我想起了彼得·德魯克。下面列出一些描述現狀時經常犯的錯誤。

1)錯誤:把想象中的改進當成現狀

很多時候造成這種錯誤,背後的原因很可能是根本沒有深入到組織流程中去做觀察和訪談,對現狀沒有認識,只好想像一個改進后的場景來應付。

2)錯誤:把“現狀”誤解為“純手工”

有的建模人員以為人做的事情才是本質,所以他畫的業務流程中,只有人,沒有非人系統,完全忽略了在技術進步下慢慢可以替代人的這些“業務實體”。

3)錯誤:把“現狀”誤解為“本開發團隊未參与之前”

開發團隊很容易會誤以為當他們開始參与組織流程完善而開發系統的時候當作“現狀”,這就是典型的“技術思維”,很多時候在開發團隊在參与組織流程完善前,組織已經經過了許多次“非系統級”的流程改進,這是站在組織角度去看問題的。

4)錯誤:把“現狀”誤解為“規範”

建模人員在建模業務流程時,照搬組織制定的規範,沒有去觀察實際工作中人們是如何做的,或者即使觀察到了人們實際沒有按照規範做,卻依然按照規範建模。這樣做,得到的業務流程是不真實的,畢竟上有政策,下有對策。

5)錯誤:“我是創新,沒有現狀”

互聯網創業公司的建模人員很容易犯的這個錯誤,動不動就說“我做的是互聯網創新,沒有現狀”,但他們已經忘記了歷史上所有的創新都是站在前輩這些巨人的肩膀之上這個事實。

6)錯誤:“我做產品,沒有現狀”

非定製系統的開發團隊進程拿這句話做接口。A公司的流程和B公司的流程有差異,中國的流程和外國的流程有差異,畫誰的現狀好的?問這個問題的時候,我想是開始開發團隊忘記了“做需求時把產品當項目做”的道理,在第2章節中也提到過“誰比誰更像”的重點。

1.3.4、改進業務序列圖

上面提到的現狀業務序列圖是對組織現狀的客觀描述,而改進業務序列圖是通過信息化手段去思考對業務現狀序列圖的一些改進。通常,信息化給人類的工作和生活帶來的改進有三種模式。

1.3.4.1、改進模式一:物流變成信息流

和信息的光電運輸比起來,用其他手段運輸的物的流轉速度就顯得太慢了,而且運輸成本會隨着距離的增加而明顯增加。如果同類物的不同實例之間可以相互取代,那麼可以提煉物中包含的部分或全部有價值的信息,在需要發生物流的地方,改為通過軟件系統交互信息,需要物的時候再將信息變成物,這樣就可以大大增加流轉速度和降低流轉成本。




物流變成信息流改進

1.3.4.2、改進模式二:改善信息流轉

軟件系統越來越多,而各個軟件系統之間溝通不暢,導致一個人為了達到某個目的可能需要和多個軟件系統打交道,如果把各軟件系統之間的協調工作改為一個軟件系統來完成,人只需要和單個軟件系統打交道,信息的流轉就改進了。




改善信息流轉

1.3.4.3、改進模式三:封裝領域邏輯

在業務流程中,有很多步驟是由人腦來判斷和計算的,領域邏輯封裝在人腦中。相對於計算機,人腦(業務人才)存在成本高,狀態不穩定、會徇私舞弊等問題。如果能夠提煉人腦中封裝的領域邏輯,改為封裝到軟件系統中,用軟件系統代替人腦,業務流程就得到了改進。換句話說,領域邏輯的封裝是對系統“內在”價值的提升,相對於前兩個改進模式有更高的要求和更大的困難。




封裝領域邏輯改進

1.3.4.4、改進思考方式:阿布爾思考法

在軟件開發團隊中,當有人提出新的想法時,經常會被馬上否定“太難了,做不了”,最終得到一個平庸的、毫無競爭力的系統。學會像阿布(俄羅斯大富豪羅曼·阿布拉莫維奇)一樣思考,有助於克服普通人因資源受限而不敢展開想象的思維障礙。阿布思考法分兩步:

1)假設有充足的資源去解決問題,得到一個完美的方案;

2)用手上現有的資源去山寨這個完美的方案。

其實,阿布思考法的核心思想就是,不要閉門造車,要“接地氣”的行動起來,主動去觀察,或主動尋找有用的信息去分析和調研,不要被各種局限被迫讓步。

2、需求

2.1、需求之系統用例圖

在“建模”階段我們研究和思考的對象是組織,從組織的整體性客觀地去發現組織如何可以通過信息化手段去優化流程。有了客觀的整體性分析和改進認知,接下來的“需求”階段需要深入到系統層面去思考了。按正常邏輯,每一步都有“承上啟下”的作用,本章節所研究的系統用例就是通過上一步業務序列圖中所映射出來的。

執行者和用例的概念在業務建模的學習中已經出現過,現在要研究的執行者和用例與業務建模時研究的執行者和用例相比,不同之處是研究對象,之前研究的是組織,現在研究的是系統。

2.1.1、系統執行者要點

系統執行者的定義:在所研究系統外,與該系統發生功能性交互的其他系統。

2.1.1.1、系統是能獨立對外提供服務的整體

封裝了自身的數據和行為,能獨立對外提供服務的東西才能稱為系統。不了解這點,建模人員很容易把“添加一些功能”當作“研發新系統”。




劃分系統用例

2.1.1.2、系統邊界是責任的邊界

系統執行者不是所研究系統的一部分,是該系統邊界外的另一個系統。這裏的系統邊界不是物理的邊界,而是責任的邊界。




錯誤的遙控軟件用例圖    



正確的遙控軟件用例圖

 

2.1.1.3、系統執行者和系統有交互

外系統必須和系統有交互,否則不能算是系統的執行者。如一名旅客來到火車站售票窗口,告訴售票員目的地和車次,售票員使用售票系統幫助旅客購買火車票,這個場景中,和火車票系統交互的是售票員,他是售票系統的執行者。




旅客不是售票系統的執行者

如果火車售票系統現在已經提供了旅客自行購票的接口,例如互聯網購票、售票機等,這種情況下,旅客也是售票系統的執行者。不過“售票員→售票”和“旅客→購票”是兩個不同的用例。

2.1.1.4、交互是功能性交互

上面說的交互還引出一個問題:假設售票員使用鼠標和售票系統交互,按道理,比起銷售員,鼠標里售票系統更近,為什麼不把鼠標作為售票系統的執行者呢?還有,假設售票系統運行在Windows操作系統之上,那麼Windows是不是售票系統的執行者?其實吧,辨別這些問題的要點就是:執行者和系統發生的交互是系統的功能需求。鼠標和操作系統跟售票系統的交互都不是售票系統的核心域概念。售票員和售票系統之間的交互才是,所以售票員才是售票系統的執行者。




售票系統的功能需求

2.1.1.5、系統執行者可以是人或非人系統

系統執行者可以是一個人腦系統,也可以是一個非人智能系統,甚至是一個特別的系統——時間。在軟件業的早期,一個系統的執行者往往全部都是人。隨着時間的推移,系統的執行者中非人執行者所佔的比例越來越多。用例的優勢在於“執行者”和“涉眾”的概念,把演員和觀眾分開。演員(執行者)在台上表演,觀眾(涉眾)在台下看,演員表演什麼是由觀眾的口味決定的,演員可以不是人,但觀眾肯定是人。演員如果是人類,那麼在觀眾席上也會有一個位置,不過在第幾排就不知道了(權重等級)。用例使用“執行者”和“涉眾”代替了原來的“用戶”是一個非常大的突破,建模人員如果過多地關注“用戶”,混淆了真正真正重要的前排“涉眾”的需求,把操作人員當前重要的調研對象(非關鍵人員),那麼花在重要的前排涉眾(關鍵人)身上的時間可能就不夠了。越來越多的系統執行者不是人類,也就是說沒有“用戶”。




從“執行者都是人”到“執行者有一些是人”

 

2.1.1、識別系統執行者

2.1.2.1、從業務序列圖映射系統執行者

如果沒有做業務建模,識別系統執行者只能靠頭腦風暴。例如:什麼人會使用系統來工作?什麼人負責維護系統?系統需要和哪些其他智能系統交互?有沒有定時引發的事件?等等問題。有了業務建模,可以直接從業務序列圖映射即可。業務序列圖上,和所研究系統有實線相連的對象映射為所研究系統的執行者。




業務序列圖:需找租客線索  



從業務序列圖映射得到系統執行者

 

2.1.3、系統用例要點

2.1.3.1、價值的買賣的平衡點

系統用例的定義:系統能夠為執行者提供的、涉眾可以接受的價值。和業務用例相比,研究對象從組織變成了系統,要理解好系統用例,重點依然是之前所強調的買賣平衡點、期望和承諾平衡點。

用例之前的許多需求方法學,把需求定義為思考系統“做什麼”,用例把需求提升到思考系統“賣什麼”的高度。這種思考是非常艱難的,因為它沒有標準答案,只有最佳答案。要得到這個答案,不能靠拍腦袋,必須揣摩涉眾。要得到合適的用例,需要有一顆善於提擦他人的心。




ATM和程序員人腦系統的用例

2.1.3.2、價值不等於可以這樣做

有些人會較真,還是以ATM為例子,有些人會因為“難道ATM放在那裡我就不能登錄一下就離開嗎?我今晚下班就去ATM那裡登錄一下給你看,然後走人。”ATM確實能登錄,但登錄功能並非ATM的賣點,如果以一個“門禁系統”為研究對象,登錄就可以作為它的用例。

還有一種情況,例如科員可以有A和B用例,科長因為比科員的權力大,所以就能擁有科員的用例。用例的執行者只是表明這個用例是為這一類執行者而做的。但不代表系統一定要有權限控制以防止其他的人或電腦系統使用該用例。即時系統確實需要有權限控制,而且角色的劃分和執行者相近,也要把這兩者分開,更不可以因為系統不設權限控制,所以把執行者的名字合併為“用戶”。




用例劃分案例

有些書中會給出“最佳粒度原則”。例如:一個系統的用例最好控制在XXX個之內,一個用例的基本路徑最好控制在X步到X步之間……這些是沒有根據的。市場需要各種各樣的系統,有功能眾多的,也有功能單一的,也有交付複雜的,應該把屁股坐到涉眾那邊去,揣摩涉眾的心裏,實事求是地寫下來。如果建模人員在粒度問題上激烈爭吵以及糾纏不清,有可能已經犯了錯誤,最常犯的錯誤是把步驟當作用例。




錯誤:把步驟當作用例

2.1.3.3、增刪改查用例的根源是從設計映射需求

有一些用例圖,映入眼帘的用例是四個四個一組的,仔細一看,剛好對應看數據庫的四種操作。相當於把數據庫的各個表名加上新增、刪除、修改、查詢,就得到了用例的名字。有些建模人員確實也知道這個錯誤,但他們學乖了,乾脆把每四個用例合併,改名叫“管理XX”或(“XX管理”),然後新增、刪除、修改、查詢等用例再擴展它,可惜依然是換湯不換藥。




從數據庫視角得到的用例

 

2.1.3.4、從設計映射需求錯誤二:“復用”用例

增刪改查用例實際上就是從設計映射需求,導致“復用”用例的一種情況。在看看以下例子:




“復用”用例錯誤示例——缺陷管理系統

從不同的業務序列圖分別映射得到系統有右邊四個用例,但有的建模人員會動起心思:這些實現起來不都是針對“缺陷”表來“select X X X from缺陷表where X X X”嗎,合併成一個用例“查詢缺陷”多好!於是得到左邊的結果。實際上,右邊這四個用例面對的執行者不同,背後的涉眾利益也有差別。

當然,如果真的像這位建模人員講的,把“數據庫”,買回去就好,想怎麼折騰這信息都可以那不是更加簡單。其實,用例是涉眾願意“購買”的、對系統的一種“用法”,只要涉眾願意“購買”,當然越多越好。講到這裏,就要來說一個需求的基本要點:需求不考慮“復用”,如果考慮“復用”,要警惕自己是不是已經轉換到了設計視角來思考問題。

2.1.3.5、系統用例不存在層次問題

系統用例的研究對象就是某特定系統,不是組織,也不是系統內部的組件。如果存在“層次”上的疑惑,背後的原因是研究對象不知不覺改變了。

像醫院信息系統的用例,有人會畫成如下圖所示,原因可能是前面沒有畫業務用例圖和業務序列圖,所以建模人員頭腦里不知不覺把醫院信息系統的價值和醫院的價值混在一起了。




錯誤的“高層”用例:混淆組織的價值和系統的價值

還有以下的防汛系統用例圖,把系統的願景當成了“高層”用例:




錯誤的“高層”用例:把願景當作用例

以下更為常見的錯誤,為系統的“模塊”或“子系統”畫用例圖:




錯誤:模塊的用例  



用例仍然是系統的用例    



錯誤:子系統的用例

2.1.3.6、用例的命名是動賓結構

用例的命名是動賓結構,例如“取現金”。動詞前面可以加狀語,賓語前面可以加定語,把一句話的主語砍掉,剩下的可以用作用例的名字。

給用例起名時不要使用弱動詞。用例之前的需求技術,可能是以“名詞+動詞”的形式命名系統的功能,例如“發票作廢”,後來要改成用例的動賓結構了,有的建模人員就在前面加一個弱動詞“進行”,就變成了“進行發票作廢”,這個也是不合適的。

如果“名詞+動詞”已經成為行業中的一個術語,也未必要嚴格的動賓結構,例如“成果分析”是某行業的一個術語,也就不必硬要倒過來變成“分析成果”了。

2.1.4、識別系統用例

2.1.4.1、從業務序列圖映射系統用例

其實,只要認真做好業務建模,從業務序列圖上映射系統用例,得到的結果自然就會符合上面說的這些要點。

從業務序列圖中,從外部指向所研究系統的消息,可以映射為該系統的用例。現在我們繼續從“識別系統執行者”的用例中結合執行者和系統用例一起識別。




從業務序列圖上找到從外部指向所研究系統的信息    



從業務序列圖映射得到系統用例

 

在以上業務序列圖中,有一處消息是“外呼人員”指向“線索管理系統”的消息為“提供本人當天名單”,但在以上系統用例圖中,用例名改為了“查看本人當天名單”。因為序列圖上的消息代表“請求某系統做某事”,用例代表“用某系統來做某事”,一定要理解兩種圖的要點,所以有的地方需要調整。

在以上系統用例圖中,有的箭頭是從執行者指向用例,這樣的執行者稱為用例的主執行者,有的箭頭是從用例指向執行者,這樣的執行者稱為用例的輔執行者。主執行者主動發起用例的交互,輔執行者在交互的過程中被動參与進來。

值得注意一下,輔執行者這個概念是被誤用的比較多。最常見的錯誤是把信息的接收者或者將來可能使用信息的人當成輔執行者。另一種輔執行者的誤用剛好相反,把信息的來源當作輔執行者。




錯誤:把可能會用到所生產信息的人當作輔執行者  



錯誤:把提供用例所需要信息的人當作輔執行者

以上錯誤的原因很多是因為前面沒有畫業務序列圖,導致建模人員在畫系統用例圖的時候產生焦慮,總是希望在圖上多放一些信息,以免自己忘記了。一般來說,輔執行者是非人智能系統的情況較多,人腦系統作為輔執行者的情況比較少,所以碰到輔執行者是人的時候,要多留心。




正確:合適的輔執行者(因為辦卡需要用戶輸入密碼)

 

2.2、需求之系統用例規約

用例圖表達了用例的目標,但是對於完整的需求來說,這是遠遠不夠的。用例的背後封裝了不同級別的相關需求,我們需要通過書寫用例規約把這些需求表達出來。用例規約就是以用例為核心來組織需求內容的需求規約。用例規約的各項內容可以通過以下類圖來展示:




用例規約的內容

2.2.1、前置條件和後置條件

用例通過前置條件(precondition)、後置條件(postcondition)以契約的形式表達需求。用例相當於系統的一個承諾:在滿足前置條件時開始,按照裏面的路徑步驟走,系統就能達到後置條件。為了避免掉入“從實現角度看這樣可以那樣也可以”的陷阱,後置條件只需要寫出最想要的那個狀態即可。

● 前置條件:用例開始前,系統需要滿足的約束。

● 後置條件:用例成功結束后,系統需要滿足的約束。

2.2.1.1、前置條件、後置條件必須是系統能檢測的




系統必須能檢測前置、後置條件

以上圖為例,“錄入保單”用例的前置條件是錯誤的。業務代表是否已經把保單交給內勤,系統無法檢測,不能作為前置條件;同樣,“收銀”用例的後置條件也是不對的。顧客是否已經帶着貨物離開商店,系統也無法檢測,不能作為後置條件。

2.2.1.2、前置條件必須是用例開始前系統能檢測的




前置條件必須是用例開始前系統能檢測的

 以上圖所示,儲戶開始取現金的交互前,系統不知道儲戶是誰,要去多少錢,所以無法檢測“儲戶賬戶里有足夠的金額”這個條件。如果把前置條件設置為類似於“存在大於最低限額的現金”這樣的背景條件作為前提條件是可以的。就算很長時間沒人來ATM取現金,這個條件是否成立就擺在那裡。

2.2.1.3、前置後置條件是狀態,不是動作

例如,“經理→批假”的前置條件不能寫“員工提交請假單”,因為是一個動作不是狀態,應改為“存在待審批的請假單”。特別要注意的是,寫成“員工已經提交請假單”很可能也是不對的,因為狀態和導致達到某個狀態的行為不是一一對應的,請假單未必是員工自己提交的,也可以組長負責幫本組人員請假,也可能是從另外的系統批量導入。

如果分不清狀態和行為的區別,建模就會遇到很大的麻煩。後面的建模工作中,還會不斷討論狀態和行為的問題。

2.2.1.4、前置後置條件要用核心域詞彙描述

“系統正常運行”、“網絡連接正常”等放之四海而皆準的約束,和所研究系統沒有特定關係,不需要在前置條件中寫出來,否則會得到一堆沒有任何增值作用的廢話。

後置條件也不能簡單地把用例的名字加上“成功”二字變成“XXX成功”。例如“顧客→下單”的後置條件寫成“顧客已經成功下單”,這不是廢話嗎?更合適的後置條件是“訂單信息已保存”。

2.2.1.5、“已登錄”不應作為前置條件

“已登錄”是一個比較有爭議的情況,以購物網站為研究對象,登錄不是用例。這一點已經在前面的已經學習過,那如何處理登錄?

1)畫法一:把其他用例作為“登錄”的擴展




畫法一:把其他用例作為“登錄”的擴展

 

會員登錄后可以下單,也可以查看以往訂單,還可以退貨……所以上圖這個方法把下單、查看以往訂單畫出登錄的擴展。這是錯的。並不是先做A然後做B或C,B和C就成了A的擴展。

2)畫法二:把“登錄”作為被包含用例




畫法二:把“登錄”作為被包含用例

把“登錄”變成被其他用例包含(Include)的被包含用例(Include Use Case)。這樣做是正確的。登錄用例本來不存在,後來在寫用例規約的時候,發現“下單”、“查看以往訂單”等用例都有以下步驟:




“查看以往訂單”步驟

為了節省書寫用例規約的工作量,考慮把這些形成一個小目標的步驟集合(不是單個步驟)分離出來,作為一個被包含用例單獨編寫規約。這個用例只被其他用例包含,不由主執行者指向。所以,如果按照這個做法的話,“下單”用例規約的步驟里,應該有表示包含“登錄”用例的步驟集合:會員【登錄】。這裏的“登錄”二字加了粗括號表示這是一個被包含用例。它的步驟和約束在另外的地方描述。當然,不喜歡用粗括號可以用下劃線等其他方法以示區分。

3)畫法三:其他用例以“已登錄”作為前置條件




畫法三:其他用例以“已登錄”作為前置條件

有些人覺得畫法二會讓好些用例會出現會員【登錄】,看起來有些礙眼,就想能不能把它提到前置條件里,那就得到了畫法三。把“登錄”作為一個用例,“會員已經登錄”作為其他用例的前置條件。這樣用例的步驟看起來更清爽,但是嚴格來說這也是不對的,“登錄”不能作為購物網站的用例。

以上章節學習過,如果在做需求時考慮復用,可能已經陷入了設計的思維。能夠在多個用例中復用登錄的狀態,這是設計人員的本事,他甚至可以做到10個用例的界面都從一個模板生成,但不能因此就把這10個用例合併成一個。

2.2.2、涉眾利益

前提條件是起點,後置條件是終點,中間的路該怎麼走?這就要由涉眾決定了。也就是我們需要對關鍵人按重要程度排序(從前排到後排)去考慮他們的利益,根據這些利益去梳理正確的需求。以銀行ATM為例子,儲戶在ATM取現金的時,涉及的涉眾利益如如下:

● 儲戶:希望方便;擔心權益受損。

● 銀行負責人:希望安全;希望節約運營成本。

正是這些涉眾利益的交鋒之下,目前我們日常生活中所看到的ATM的用例片段如下:




ATM用例片段

從步驟1有設計約束“通過磁條卡或芯片卡提交賬戶號碼”看,這是為了照顧儲戶“方便”的利益。在銀行角度,雖然儲戶是上帝,為了儲戶更加方便,不用密碼更方便的。但從銀行角度要考慮安全問題,不可能不設置密碼,但為什麼只設置6位而不是8位或者更多呢?這又是“安全”和“方便”交鋒后的妥協……

2.2.2.1、涉眾的來源

1)涉眾來源一:人類執行者

用例的執行者如果是人類,當然是用例的涉眾。執行者如果不是人類,就不是涉眾,因為它沒有利益主張。




考慮人類執行者之後的涉眾

上圖保險系統的“內勤→錄入保單”用例中,內勤是人類,是涉眾,而OA系統不是人類,不是涉眾。

2)涉眾來源二:上游

執行者要使用系統做某個用例,可能會需要一些資源,這些資源的提供者可能就是用例的涉眾。還是以“內勤→錄入保單”為例,保單由業務人員代表提供給內勤。如果內勤喝醉了酒亂錄,信息錯得一塌糊塗,業務代表的利益就被損害了。所以,考慮到上游之後,“內勤→錄入保單”用例的涉眾有內勤和業務代表了。




考慮上游之後的涉眾

3)涉眾來源三:下游

執行者使用系統做某個用例,產生的後果會影響到其他人。這些受影響的人也是涉眾。還是以“內勤→錄入保單”為例,如果系統做得不好,沒有檢測內勤錄保單時是否填了必填項就放了過去,後面負責審核的經理工作量增加了。還有,OA系統雖然不是該用例的涉眾,但假如保險系統不停地向OA系統發送垃圾數據包,導致OA癱瘓,那麼OA系統維護人員的工作量就增加了。所以,OA系統維護人員也是下游的涉眾。考慮下游之後,“內勤→錄入保單”用例的涉眾如下圖所示:




考慮下游之後的涉眾

 

4)涉眾來源四:信息的主人

用例會用到一些信息,這些信息會涉及某些人,雖然這些人也許並不知道這個系統的存在,但他們是用例的涉眾。還是以“內勤→錄入保單”為例,保單的信息涉及被保人,投保人和受益人,如果信息出錯或泄露,這些人就會遭殃,所以他們是涉眾。因為這類涉眾可能和系統沒什麼接口,比較容易被忽略,所以要特別需要注意。




考慮信息的主人之後的涉眾

其實,前面的業務建模對識別涉眾起到了非常大的幫助,如果做需求前做了業務建模,會更加了解一件事情的前因後果,大多數涉眾都能夠從業務序列圖中看出來。如下圖所示:




業務建模可以幫助尋找涉眾

 

2.2.2.2、尋找涉眾利益

查理·芒格說過:說服別人要訴諸利益,而非訴諸理性。所以要學會思考涉眾的利益點是一門非常大的學問。尋找涉眾利益時,要“親兄弟,明算賬”,把不同涉眾各自關注的利益體現出來,而不是寫成一模一樣的。家裡兩夫妻對同一件事情都還有不同立場,更不用說一個組織裏面形形色色的涉眾了。例如,司機開車進廠裝化肥,工作人員通過地磅系統操縱地磅給車稱重。針對這件事,不同的涉眾可謂是“各懷鬼胎”:

● 化肥公司老闆——擔心公司內部人員貪污;

● 地磅操作員——希望操作簡便;擔心承擔責任;擔心繫統壞掉影響工作量;

● 倉管人員——擔心稱不準導致無謂的返工裝包;

● 買主——擔心進去時稱得輕了,出來時稱得重了,導致給少了化肥;

● 司機——擔心等候時間太長導致每天拉貨次數減少;

即使有些利益有時不方便白紙黑字寫出來共享,但至少建模人員要心知肚明,不能一團和氣了事。建模人員要仔細觀察和揣摩涉眾的痛苦,才能找到真正的涉眾利益,否則寫出來的“涉眾利益”往往很蒼白。例如以下例子:

● 護士——擔心出錯

這都是正確的無用廢話,誰都擔心出錯,但為什麼還是出錯?仔細調研過之後寫出來就生動多了:

● 護士——擔心自己的藥理學知識記錯,對藥物名稱相近的藥物計算錯劑量,導致給葯錯誤;

2.2.2.3、善於積累涉眾利益

需求是不斷變化的,我想這都是共識了,新系統肯定在功能或性能上和舊系統有所不同,否則還做什麼新系統呢。但是,背後的涉眾利益要穩定得多。看看之前ATM的例子出現的涉眾利益:

儲戶——希望方便;擔心權益受損;

銀行負責人——希望安全;希望節約運營成本;

其實這些涉眾利益不止適用於ATM,也適用於清朝的錢莊櫃檯、現在的銀行櫃檯、網上銀行和手機銀行。換句話說,越本質越穩定。

2.2.3、基本路徑

一個用例會有多個場景,其中有一個場景描述了最成功的情況,執行者和系統的交互非常順利,一路綠燈直抵用例的後置條件。這個場景稱為基本路徑。用例把基本路徑分離出來,目的是凸顯用例的核心價值。還是以ATM為例,發生在ATM上的場景有很多:

1)張三來了,插卡,輸入密碼,輸入金額,順順利利取到錢,高興地走了;

2李四來了,插卡,輸密碼,密碼錯,在輸,再錯,再輸,卡被吞掉了;

3王五來了,插卡,輸密碼,輸金額,今天取得太多不能取了……

以上三個場景,只有場景(1)是銀行在大街上擺放一台ATM的初衷。雖然場景(2)和(3)是難以避免,但場景(1)出現得越多越好,這是涉眾對ATM的期望。

書寫路徑步驟的時候需要注意以下一些要點。這些要點有重疊的地方,如果違反了其中一個要點,很可能會違反另外的要點。

2.2.3.1、按照交互四步曲書寫

執行者和系統按回合交互,直到達成目的。需要的回合數是不定的,可能一個回合足夠,也可能需要多個回合。一個回合中的步驟分為四類:請求、驗證、改變、回應。如下圖所示:




交互四步曲

在一個回合中,請求是必須的,同時還需要其他三類步驟中的至少一類。看看以下例子,可以看到,第一個回合只需要請求和響應,第二個回合則四類步驟都有。




回合制的交互示例

當時間作為主執行者而且不需要和其他輔執行者交互的用例中,可能會出現不需要回應的情況,而且只有一個回合,如下所示:




回合制的交互示例

在書寫步驟時要注意以下一些形式上的問題:

1)對於時間為主執行者的用例,回合中的請求步驟不寫“時間告知時間周期到了”,而是寫“當到達時間周期時”。

2)驗證步驟不寫“是否”。例如以上例子中,第4步寫“系統驗證註冊信息充分”,不寫“系統驗證註冊信息是否充分”,目的是要表達“充分”是基本路徑期望的驗證結果。

3)系統和輔執行者之間的交互可以看作是一種回應步驟,寫成“系統請求輔助執行者做某事”,例如“系統請求郵件列表系統群發郵件”。

2.2.3.2、只寫系統能感知和承諾的內容

看看以下例子:

……

4、系統反饋應收總金額

5、顧客付款

6、收銀員提交付款方式和金額

7、系統計算找零金額

8、系統反饋找零金額,打印收據

9、收銀員找零

……

顧客付款和收銀員找零是系統無法感知和承諾的。如果寫在步驟里,會讓人產生誤解:只要用了本系統,顧客就會乖乖付款,收銀員會乖乖找零——也許顧客忘記付款和收銀員忘記找零正式商場要解決的一個頭痛問題。

2.2.3.3、使用主動語句理清責任

把動作的責任人放在主語的位置,看看以下兩句話:

1)伊布從瓦倫西亞處得到傳球,舒梅切爾撲救伊布的射門。

2瓦倫西亞傳球,伊布射門,舒梅切爾撲救。

雖然上面一句比較文藝,但下面一句把責任理得更清晰。用例步驟也是如此:

系統從會員處獲得用戶名和密碼(錯)

會員提交用戶名和密碼(對)

用戶名和密碼被驗證(錯)

系統驗證用戶名和密碼(對)

會員要是不提交,就不要怪系統沒有動靜;會員要是提交了,系統不動彈,那就要怪系統了。做到規規矩矩說話,把責任理清楚,其實不容易。再列舉一些常見的“胡說八道”,如下所示:




規矩用詞

2.2.3.4、主語只能是主執行者或系統

寫需求,就是要把系統當作一個黑箱,描述它對外提供的功能以及功能附帶的質量需求。系統如何構造,不屬於需求描述的範圍,除非是涉眾強加的設計約束。所以步驟里不能出現“執行者請求前端系統做某事,前端系統請求後端系統做某事”“執行者請求客戶端做某事,客戶端請求服務端做某事”“執行者請求A子系統做某事,A子系統請求B子系統做某事”,就算這個系統最終的組成是分解成很多個部分,分佈在一百多個國家運行,需求里也只有兩個字:系統。前面已經學習過了,系統邊界是責任邊界,而非物理邊界,如下所示:




需求把系統看作是黑箱

 

2.2.3.5、使用核心域術語描述

路徑步驟應該使用核心域的術語來描述,也就是說,要說“人話”。以下以一個零件採購系統為研究對象,比較以下兩句話,哪一句是“人話”,哪一句是“鳥語”?

1)系統建立連接,打開連接,執行SQL語句,從“零件”表查詢……

2系統根據查詢條件搜索零件

其實,一眼就能看出,第一句是“鳥語”,第二句是“人話”了。不同職能多少會有主觀意識,做Java開發的覺得自己做的才是技術,需求屬於業務。但需求人員卻覺得自己做的也是技術,他們所研究的客戶才是業務。但客戶覺得自己做的才是技術……,這咋看起來有點像一個鄙視鏈。其實,大家做的都是“技術”,這是領域不同而已。應該用“核心域”和“非核心域”來代替“業務”和“技術”。

如果所研究系統是一個關係數據庫的腳本工具,核心域是關係數據庫領域,上面提到的“系統建立連接,打開連接,執行SQL語句”就成了合適的需求。

2.2.3.6、不要涉及界面細節

很多人寫需求的時候,會把界面的細節帶進來,例如:

● 會員從下拉框中選擇類別

● 會員從文本框中輸入查詢條件

● 會員單擊“確定”按鈕

這些界面細節很可能不是需求,更多是設計方案,背後可能隱藏更多的真正需求,也許是可用性需求“操作次數不超過5次”。但畢竟我們面對的客戶各種各樣的喜好都有,可能有些前排涉眾明確要求“一定要用下拉框”,那麼“下拉框”也是需求,但依然不能寫“會員從下拉框框中選擇類別”。因為這裏涉及兩類需求,她們的穩定性和變化趨勢不同,應該分開描述:

● 會員選擇類別(這是步驟)

● 通過下拉框來實現(這是設計約束)

用例的需求組織方式是分層的,從用例的路徑、步驟、約束,穩定性會越來越低,如下所示:




用例規約的需求層級

把需求分層級了,穩定和不穩定的需求就分開了,更有利於了解事情的核心本質。平時遇到的大部分“需求變更”發生在補充約束級別,例如輸入會員信息時加個微信字段(字段列表變了),調整結賬時的打折規則(業務規則變了)。級別越高的需求,內容越穩定。

很多時候做需求會把看得見和需求混為一談,真正的需求不見得是顯而易見的。需求判斷的標準不是涉眾是否看得見,而是涉眾是否在意。第一章已經學習過,需求和設計不是一一對應的,而是多對多的。

2.2.3.7、不要涉及交互細節

在步驟中,除了避免描述頁面細節,還要避免描述交互細節。例如有人會這樣寫:

● 會員每輸入賬戶名稱的一個字符

● 系統在界面中驗證當前輸入信息合法

寫的人有他的道理:系統不是等待提交后才驗證輸入信息是否合法,而是隨時驗證立即反饋,這樣使用戶體驗更好。其實,這隻是交互設計的一些技能。忍不住要在需求規約里描述界面和交互的細節,背後的原因和忍不住要思考內部代碼如何實現的原因是一樣的,都是對自己的設計技能沒有信心,害怕“現在想到了如果不忘記記下來以後就忘記了”。這些都是人性的弱點。

用例的步驟應該把焦點放在系統必須接收什麼輸入、系統必須輸出什麼信息以及系統必須做什麼處理這三個重點上,加上字段列表、業務規則、可用性需求等約束,足以表達各種需求。

關於用例的交互怎麼寫,是一個比較頭疼的問題。即使不涉及交互設計細節的問題,也免不了混進交互設計的成分,例如,為什麼要分兩個回合而不是一個回合?實際上涉眾更希望一個回合就能達到用例的目標。所以,建議觀點是在用例規約中把路徑步驟刪掉,只保留輸入輸出、涉眾利益和補充約束,交互的路徑步驟由交互設計人員決定。

2.2.3.8、需求是“不這樣不行”

許多需求人員之所以在需求崗位上,並不是因為他掌握了該掌握的需求技能,可能只是因為他工作年限足夠長該換到需求崗位了——和許多年齡到了就上崗的夫妻和父母相似。這樣的需求人員硬着頭皮做需求時,最常用的一招就是托着腦袋想“這個東西是什麼樣子”,然後畫一個界面原型拿去和涉眾確認。一旦涉眾說“差不多就這樣吧”,就把這個界面原型作為需求交給分析設計人員。在這一點上,互聯網公司的產品經理表現得尤為明顯。如果僥倖成功,就拚命鼓吹“原型大法好”,因為他只會這個。

當需求人員問問題時都是“這樣可以嗎”,相當於:

● 需求人員:界面這樣布局可以嗎?

● 涉    眾:(好用就行,我又不會做界面,問我可不可以我當然說可以了)可以。

● 需求人員:代碼這樣寫可以嗎?

● 涉    眾:(好用就行,我又不會寫代碼,問我可不可以我當然說可以了)可以。

如果問的問題改為“不這樣可以嗎?”,像下面這樣:

● 需求人員:界面不這樣布局可以嗎?

● 涉    眾:不可以,這是政府的規定,你們不要自己亂髮揮啊!

● 需求人員:代碼不這樣寫可以嗎?

● 涉    眾:不可以,這段代碼是我小舅子寫的,一定要這樣,否則不給錢。

這時,界面和代碼就成為了需求,當然,只是補充約束級別的需求。說到這裏,我們歸納出需求的判斷標準:需求是“不這樣不行”,而不是“這樣也行”。

2.2.4、擴展路徑

基本路徑上的每個步驟都有可能發生很多意外,其實某些意外是系統要負責處理的,處理意外的路徑就是擴展路徑。因為一個步驟上出現的意外及其處理可能有多種,所以同一步驟上的擴展路徑可能有多條。




擴展路徑和步驟

對於擴展路徑及其步驟的標號,本書採用的是Cockburn推薦的方法。擴展路徑的標號方法是在擴展步驟的数字序號後面加上字符序號,例如2a表示步驟2的第a條擴展路徑,2b表示步驟2的第b條擴展路徑。擴展路徑條件的最後加上冒號,接下來是該擴展路徑的步驟,標號方法是在擴展路徑編號後面加上数字序號,例如2a1。也就是說,步驟的編號以数字結尾,擴展路徑編號以字母結尾。如果多重擴展,那就繼續按此形式標註。還是以之前ATM“儲戶→取現金”用例為例。該用例規約加上擴展路徑之後如下所示:

基本路徑:

1. 儲戶提交賬戶號碼

2. 系統驗證賬戶號碼合法

3. 系統提示輸入密碼

4. 儲戶輸入密碼

5. 系統驗證密碼合法、正確

6. 系統提示輸入取現金額

7. 儲戶輸入取現金額

8. 系統驗證取現金額合法

9. 系統記錄取現信息,更新賬戶餘額

……

2a. 賬戶號碼不合法:

         2a1.系統反饋賬戶號碼不合法

         2a2.返回1

5a. 密碼不合法:

         5a1.返回3

5b. 密碼合法但不正確:

         5b1.系統驗證當日取款累計輸錯密碼次數不超過3

                   5b1a.當日取款累計輸錯密碼次數超過3次:

                            5b1a1.系統關閉賬戶

                            5b1a2.用例結束

         5b2.系統反饋密碼不正確

         5b3.返回3

8a. 取現金額不合法:

         8a1.返回6

有一點需要注意,和輔執行者交互的步驟很有可能會出現擴展。例如:

4. 系統請求短信平台發布信息

……

擴展

……

4a. 短信平台無響應:

         4a1.系統反饋短信平台無響應

……

但,如果系統不需要從外部系統那裡得到任何結果,這個外系統就不是輔執行者,所以它出現故障會不會導致擴展的討論是沒有意義的。例如:

5. 系統向經理的电子郵箱通知有新的待審批申請

除了以上步驟之外,從其他步驟產生擴展路徑一定要非常謹慎,否則容易讓不屬於需求的內容混進用例規約中。特別要注意下面幾點。

1)能感知和要處理的意外才是擴展

2)設計技能不足導致的錯誤不是擴展

3)不引起交互行為變化的選擇不是擴展

4)界面跳轉不是擴展

2.2.5、補充約束

路徑步驟里描述的需求是不完整的。例如:

用例名:發布講座消息

……

1. 工作人員輸入講座信息,請求發布

2. 系統驗證講座信息充分

3. 系統生成發布內容

4. 系統請求短信平台發布消息

5. 系統保存講座信息和發布情況

6. 系統反饋信息已經保存併發布

……

步驟1中的“講座信息”包括哪些內容?需要添加字段列表。步驟2中“充分”指什麼,需要添加業務規則。從步驟1到步驟6有沒有速度上的要求?需要質量需求。

如果補充約束的內容只和單個用例相關,可以直接放在該用例的規約中;如果補充約束適用於多個用例,可以單獨集中到另外的地方,從用例規約引用。

補充約束前面的編號不代表順序,而是表示該約束綁定的步驟編號,如果某條補充約束不是針對某一步驟,而是針對多個步驟甚至整個用例,前面的編號可以是“*”,如下所示:

5. 發布情況=發布時間+工作人員

補充約束的類型可用類圖表示:




用例的補充約束

2.2.5.1、字段列表

字段列表用來描述步驟里某個領域概念的細節。例如上面“發布講座消息”用例的步驟中,步驟1、3、5都需要分別添加字段列表。

字段列表可以用自然語言表達,例如:

字段列表

1. 講座信息包括:舉辦時間、地點、專家信息、主題、簡介。專家信息包括:姓名、單位、頭銜。

也可以用符號表達,例如:

字段列表

1. 講座信息=舉辦時間+地點+專家+主題+簡介

1. 專家=姓名+單位+頭銜

表示的符號可以採用過去數據字典常用的符號。例如:“+”表示數據序列,“()”表示可選項,“{}”表示多個,“[|||]”表示可能的取值。例如:

註冊信息=公司名+聯繫人+電話+{聯繫地址}

聯繫地=州+城市+街道+(郵編)

保存信息=註冊信息+註冊時間

客房狀態=[空閑|已預定|佔用|維修中]

很多時候,做需求會把設計的細節帶上,例如“電話號碼varchar(255)”或把數據模型圖附上,這是不準確的。數據模型是設計,設計應該來源於需求,而非空想一個設計,然後把他當成需求。

2.2.5.2、業務規則

業務規則描述步驟中系統運算的一些規則,例如上面“發布講座消息”用例的步驟2中的“充分”沒有說清楚,需要添加業務規則,例如:

業務規則:

2. 必須有的項包括:時間、地點、專家、主題

其實,主要涉眾能理解,行業上適用的任何方式(例如數學、物理公式)都可以用來表達業務規則。描述業務規則時要注意:業務規則不等於實現算法。因為業務規則也是一種需求,也是從涉眾的角度看“不這樣不行”的。例如,研究一款為盲人或殘疾人而做的語音輸入軟件,用例規約有如下片段:

3. 系統將語音輸入翻譯為文字

業務規則

3. 採用XX識別算法

這樣的業務規則可能是有問題的,如果前排是盲人或殘疾人,他們是不懂什麼叫“XX算法”,也不在意你是否用了這個算法,他們在意的需求是:“背景噪音強度為XX的情況下,識別率應該在XX以上”。當然也不排除前排涉眾是有意推廣某廠家的識別技術,那麼“採用XX識別算法”也可以成為需求。

2.2.5.3、質量需求

系統滿足功能需求,說明系統能把事情做正確。在做正確的基礎上,系統還需要在做的過程中滿足一些指標,這些指標就是質量需求,也被稱為“非功能需求”。

1)可用性

可用性需求是對人類執行者和系統之間交互質量的度量。如果系統僅能正確達到用例目標,但交互太頻繁,人類執行者是不喜歡用的。在表達可用性需求時,僅僅說“系統容易使用”是不行的。這是無法量化的目標,合適的可用性需求應該是可以度量的,例如:




可用性度量

2)性能

性能包括速度、容量、能力等,例如:

① 系統應在0.5秒之內拍攝超速車的照片(速度)

② 應允許1000個執行者同時使用此用例(容量)

③ 在標準工作負荷下,系統的CPU佔用率應少於50%(能力)

在尋找質量需求時,性能類型的質量需求往往是最多的。

3)可靠性

可靠性表示系統的安全性和完整性,通常用平均無故障時間(MTBF,Mean Time Between Failures)和平均修復時間(MTTR, Mean Time To Repair)表示。可靠性需求往往不是針對單個用例,而是針對整個系統,可以在所有用例規約的最後,單獨用小篇幅描述。

4)可支持性

可支持性表示系統升級和修復的能力。例如:

① 95%的緊急錯誤應能在30個工作時內修復

② 在修復故障時,未修復的相關缺陷平均數應小於0.5

③ 升級新版本時,應保存所有系統設置和個人設置

和可靠性一樣,可支持性需求往往不是針對單個用例,而是針對整個系統,可以在所有用例規約的最後,單獨用小篇幅描述。

以上介紹了質量需求的種類,很多時候質量需求不用可以去尋找,按照前面說過的“不這樣行嗎”的標準,把混入需求的設計刪掉,然後問為什麼,背後往往隱含的就是質量需求,如下所示:




質量需求

2.2.5.4、設計約束

設計約束是在實現系統時必須要遵守的一些約束,包括界面樣式、報表格式、平台、語言等。

設計約束既不是功能需求,也不是質量需求。例如,“用Oracle數據庫保存數據”,其實用DB2也可以滿足同樣的功能需求和質量需求,但必須要用Oracle,因為客戶已經採購了許多Oracle數據庫軟件,如果不用,成本就會增加。

設計約束是需求的一種,也一樣要從涉眾的視角描述。在很多需求規約中,不少來自開發人員視角的設計偽裝成設計約束,混進了需求的隊伍。例如“系統應採用三層架構方式搭建”,涉眾並不了解“三層”好在哪裡,為什麼不是四層、五層等,然後問“為什麼”,背後的真正需求可能還是性能需求。

需求是問題,設計是解決方案,二者的穩定性不同。

 

2.3、需求啟發

2.3.1、需求啟發要點

需求人員要能夠像獵人一樣,用銳利的眼睛發現隱藏在叢林中的獵物,像偵探一樣,用縝密的思維判斷出偽裝成好人的兇手。但需求人員會有許多的障礙:如

1)需求的一個啟發障礙是知識的詛咒,意思是:一旦知道某個東西,就很難相像不知道它會是什麼樣子;

2)知識的詛咒在需求啟發中體現為溝通的困難;

3)需求啟發的另一個障礙是做和定義的不同;

但理解了以下兩個要點,有助於克服需求啟發中的障礙:

1)和涉眾交流的形式應該採用視圖,而不是模型;

2)和涉眾交流的內容應該聚焦涉眾利益,而不是需求;

2.3.2、需求啟發手段




需求啟發手段

2.3.3、需求人員的素質培養




需求人員的素質

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

從零開始搭建前後端分離的NetCore2.2(EF Core CodeFirst+Autofac)+Vue的項目框架之十二Swagger(參數)使用二

  引言

  在 中提到了 Swagger 的基本使用,僅限於沒有參數,沒有驗證的那種api文檔生成,那麼這篇就連接上篇繼續,在一般具有安全性、權限等驗證的接口上,

  都會在header/url中加上請求者的秘鑰、簽名等,當然也有可能添加到body等其它地方, Swashbuckle.AspNetCore 都支持這些寫法。

  如何使用 — 下面將介紹兩種使用方式

兩種方式參數設置到何處都是在  In屬性上,屬性對於值如下:    參考

  • query: 參数字段值對應放在url中
  • header: 參數值對應放在header param中
  • body: 參數對應放到請求體中
  • path: 參數應該對應放到請求路徑  // 具體貌似沒用
  • formData: 參數對應放到請求表單中

  第一種:將一個或多個參數保護API的“securityDefinitions”添加到生成的Swagger中。

這種是直接在文檔的右上方添加一個 Authorize 按鈕,設置了值后,每一個請求都會在設置的位置上加上相應的值,在 上一篇隨筆中的 ConfigureServices 方法中,

對應位置 services.AddSwaggerGen(options =>{}) 中的  XmlComments  下 添加代碼如下:

                options.AddSecurityDefinition("token", new ApiKeyScheme
                {
                    Description = "token format : {token}",//參數描述
                    Name = "token",//名字
                    In = "header",//對應位置
                    Type = "apiKey"//類型描述
                });
                options.AddSecurityDefinition("sid", new ApiKeyScheme
                {
                    Description = "sid format : {sid}",//參數描述
                    Name = "sid",//名字
                    In = "header",//對應位置
                    Type = "apiKey"//類型描述
                });
                //添加Jwt驗證設置 設置為全局的,不然在代碼中取不到
                options.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>> {
                    { "token", Enumerable.Empty<string>() },
                    { "sid", Enumerable.Empty<string>() },
                });

  添加完成后,運行起來看下效果,效果圖: 

 設置上對應值,調用測試方法,可以在header中取到剛設置到的值,

 這裡能看到,可以取到設置的參數了。這樣一來,在需要驗證的接口上,我們就可以通過接口文檔來測試了。基本不用再藉助postman等接口測試工具了。

但是,但是,這裡有一個問題,就是只要設置了參數值,每一次訪問都會在請求中帶上參數。

下面將介紹第二種方式,只給需要驗證用戶的接口上添加驗證參數。

  第二種:使用“filters”擴展Swagger生成器,來實現只在需要添加參數的方法上添加參數。複雜的可以根據自己的需求來添加對應參數

實現方式就是先新建一個類,名: SwaggerParameter ,實現 IOperationFilter 接口。SwaggerParameter 類代碼如下: 

    /// <summary>
    /// 自定義添加參數
    /// </summary>
    public class SwaggerParameter : IOperationFilter
    {
        /// <summary>
        /// 實現 Apply 方法
        /// </summary>
        /// <param name="operation"></param>
        /// <param name="context"></param>
        public void Apply(Operation operation, OperationFilterContext context)
        {
            if (operation.Parameters == null) operation.Parameters = new List<IParameter>();
            var attrs = context.ApiDescription.ActionDescriptor.AttributeRouteInfo;
            var t = typeof(BaseUserController);
            //先判斷是否是繼承用戶驗證類
            if (context.ApiDescription.ActionDescriptor is ControllerActionDescriptor descriptor && context.MethodInfo.DeclaringType?.IsSubclassOf(t) == true)
            {
                //再驗證是否允許匿名訪問
                var actionAttributes = descriptor.MethodInfo.GetCustomAttributes(inherit: true);
                bool isAnonymous = actionAttributes.Any(a => a is AllowAnonymousAttribute);
                // 需要驗證的方法添加
                if (!isAnonymous)
                {
                    operation.Parameters.Add(new NonBodyParameter()
                    {
                        Name = "sid",
                        In = "header", //query header body path formData
                        Type = "string",
                        Required = true,//是否必選
                        Description = "登錄返回的sid"
                    });
                    operation.Parameters.Add(new NonBodyParameter()
                    {
                        Name = "token",
                        In = "header", //query header body path formData
                        Type = "string",
                        Required = true,//是否必選
                        Description = "登錄返回的token"
                    });
                }
            }
        }
    }

 運行起來后,進入到  文檔頁面,可以看到右上角的 Authorize 按鈕已經不見了,在不需要驗證的方法上,也找不到相應需要設置參數的輸入框。就只有在需要驗證的接口上才有。

參考Swagger文檔圖如下: 

參考代碼圖如下:

 

效果圖: 

  這樣一來設置也就完成了。從上面就能看出,就只有需要用戶驗證的接口才會有相應參數。 

 

我的設置方式是先定義了用戶驗證控制器類,讓需要用戶驗證的控制器繼承該控制器,然後在該控制器中不需要用戶驗證的接口上加上 AllowAnonymous 屬性 

設置fitter時就可以根據上面提到的兩個點來進行判斷是否需要加上參數,如果不是這樣實現的,可以根據自己的需求變更fitter類,來控制文檔的生成。 

 

以上若有什麼不對或可以改進的地方,望各位指出或提出意見,一起探討學習~ 

有需要源碼的可通過此 鏈接拉取 覺得還可以的給個 start 和點個 下方的推薦哦~~謝謝!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

(三十五)golang–面向對象之多態

多態:變量具有多種形態,可以用統一的接口來調用不同的實現。

接口體現多態特徵:

(1)多態參數:之前所講的Usb接口案例,既可以接受手機變量,也可以接受相機變量,就體現了usb接口的多態;

(2)多態數組:

package main

import (
    "fmt"
)

type usb interface {
    start()
    stop()
}

type phone struct {
    name string
}

func (p phone) start() {
    fmt.Println(p.name, "手機開始工作")
}

func (p phone) stop() {
    fmt.Println(p.name, "手機停止工作")
}

type camera struct {
    name string
}

func (c camera) start() {
    fmt.Println(c.name, "相機開始工作")
}

func (c camera) stop() {
    fmt.Println(c.name, "相機停止工作")
}

type computer struct {
}

func (co computer) working(usb usb) {
    usb.start()
    usb.stop()
}

func main() {
    var usbArr [3]usb
    usbArr[0] = phone{"小米"}
    usbArr[1] = phone{"vivo"}
    usbArr[2] = camera{"尼康"}
    fmt.Println(usbArr)
    for i := 0; i < len(usbArr); i++ {
        usbArr[i].start()
        usbArr[i].stop()
    }
}

我們以前講到,數組是只能存儲同一種類型的數據,利用多態數組,就可以存儲不同的類型了;

如何將一個接口變量賦值給一個自定義類型的變量?使用類型斷言

類型斷言:由於接口是一般類型,不知道具體類型,如果要轉成具體類型,就需要使用類型斷言;要保持原來空接口指向的數據類型和斷言的數據類型一致;

為了避免輸出panic報錯,可以進行斷言判斷;

類型斷言實踐一:

我們給phone中加入一個方法call(),在調用usb變量時,usb.call(),肯定是不對的,因為usb可能是phone,也可能是camera,而camera是沒有這個函數的,因此,在調用的時候用類型斷言。

package main

import (
    "fmt"
)

type usb interface {
    start()
    stop()
}

type phone struct {
    name string
}

func (p phone) start() {
    fmt.Println(p.name, "手機開始工作")
}

func (p phone) call() {
    fmt.Println(p.name,"手機在打電話")
}

func (p phone) stop() {
    fmt.Println(p.name, "手機停止工作")
}

type camera struct {
    name string
}

func (c camera) start() {
    fmt.Println(c.name, "相機開始工作")
}

func (c camera) stop() {
    fmt.Println(c.name, "相機停止工作")
}

type computer struct {
}

func (co computer) working(usb usb) {
    usb.start()
    //如果usb還指向phone的結構體變量,則還需要調用call方法
    if phone, ok := usb.(phone); ok { phone.call() }
    usb.stop()
}

func main() {
    var usbArr [3]usb
    usbArr[0] = phone{"小米"}
    usbArr[1] = phone{"vivo"}
    usbArr[2] = camera{"尼康"}
    var com computer
    fmt.Println(usbArr)
    for i := 0; i < len(usbArr); i++ {
        com.working(usbArr[i])
    }
}

類型斷言實踐2:循環判斷輸入參數的類型

package main

import (
    "fmt"
)

type student struct {
    name string
}

func typeJudge(items ...interface{}) {
    for index, x := range items {
        switch x.(type) {
        case bool:
            fmt.Printf("第%v個參數是bool類型,值是%v\n", index, x)
        case int, int32, int64:
            fmt.Printf("第%v個參數是整數類型,值是%v\n", index, x)
        case float32:
            fmt.Printf("第%v個參數是float32類型,值是%v\n", index, x)
        case float64:
            fmt.Printf("第%v個參數是float64類型,值是%v\n", index, x)
        case string:
            fmt.Printf("第%v個參數是string類型,值是%v\n", index, x)
        case student:
            fmt.Printf("第%v個參數是student類型,值是%v\n", index, x)
        case *student:
            fmt.Printf("第%v個參數是*student類型,值是%v\n", index, x)
        default:
            fmt.Printf("第%v個參數類型不確定,值是%v\n", index, x)
        }
    }
}

func main() {
    var n1 float32 = 1.1
    var n2 float64 = 1.2
    var n3 int32 = 1
    var name string = "tom"
    var n5 bool = true

    stu1 := student{"jack"}
    stu2 := &student{"bob"}

    typeJudge(n1, n2, n3, name, n5, stu1, stu2)
}

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

I/O多路復用模型

背景

在文章中提到了五種I/O模型,其中前四種:阻塞模型、非阻塞模型、信號驅動模型、I/O復用模型都是同步模型;還有一種是異步模型。

想寫一個系列的文章,介紹從I/O多路復用到異步編程和RPC框架,整個演進過程,這一系列可能包括:

  1. Reactor和Proactor模型
  2. 為什麼需要異步編程
  3. enable_shared_from_this用法分析
  4. 網絡通信庫和RPC

為什麼有多路復用?

多路復用技術要解決的是“通信”問題,解決核心在於“同步事件分離器”(de-multiplexer),linux系統帶有的分離器select、poll、epoll網上介紹的比較多,大家可以看看這篇介紹的不錯的文章:。通信的一方想要知道另一方的狀態(以決定自己做什麼),有兩種方法: 一是輪詢,二是消息通知。

輪詢

輪詢的一種典型的實現可能是這樣的:當然這裏的epoll_wait()也可以使用poll()或者select()替換。

whiletrue) {
    active_stream[] = epoll_wait(epollfd)
    for i in active_stream[] {
        read or write till
    }
}

輪詢方式主要存在以下不足:

  • 增加系統開銷。無論是任務輪詢還是定時器輪詢都需要消耗對應的系統資源。
  • 無法及時感知設備狀態變化。在輪詢間隔內的設備狀態變化只有在下次輪詢時才能被發現,這將無法滿足對實時性敏感的應用場合。
  • 浪費CPU資源。無論設備是否發生狀態改變,輪詢總在進行。在實際情況中,大多數設備的狀態改變通常不會那麼頻繁,輪詢空轉將白白浪費CPU時間片。

消息通知

其實現方式通常是: “阻塞-通知”機制。阻塞會導致一個任務(task_struct,進程或者線程)只能處理一個”I/O流”或者類似的操作,要處理多個,就要多個任務(需要多個進程或線程),因此靈活性上又不如輪詢(一個任務足夠),很矛盾。

 

select、poll、epoll對比

矛盾的根源就是”一”和”多”的矛盾: 希望一個任務處理多個對象,同時避免處理阻塞-通知機制的內部細節。解決方案是多路復用(muliplex)。多路復用有3種基本方案,select()/poll()/epoll(),都是來解決這一矛盾的。

  • 通知代理: 用戶把需要關心的對象註冊給select()/poll()/epoll()函數。
  • 一對多: 所有的被關心的對象,只要有一個對象有了通知事件,select()/poll()/epoll()就會結束阻塞狀態。
  • 方便性: 用戶(程序員)不用再關心如何阻塞和被通知,以及哪些情況下會有通知產生。這件事情已經由上述幾個系統調用做了,用戶只需要實現”通知來了我該做什麼”。

 

那麼上面3個系統調用的區別是什麼呢?
第一個select(),結合了輪詢和阻塞兩種方式,沒有問題,每次有一個對象事件發生的時候,select()只是知道有事件發生了,具體是哪個對象發生的,不知道,需要從頭到尾輪詢一遍,複雜度是O(n)。poll函數相對select函數變化不大,只是提升了最大的可輪詢的對象個數。epoll函數把時間複雜度降到O(1)。

 

為什麼select慢而epoll效率高?
select()之所以慢,有幾個原因: select()的參數是一個FD數組,意味着每次select調用,都是一次新的註冊-阻塞-回調,每次select都要把一個數組從用戶空間拷貝到內核空間,內核檢測到某個對象狀態變化並寫入后,再從內核空間拷貝回用戶空間,select再把這個數組讀取一遍,並返回。這個過程非常低效。

epoll的解決方案相當於是一種對select()的算法優化: 它把select()一個函數做的事情分解成了3步,首先epoll_create()創建一個epollfd對象(相當於一個池子),然後所有被監聽的fd通過epoll_ctrl()註冊到這個池子,也就是為每個fd指定了一個內部的回調函數(這樣,就沒有了每次調用時的來回拷貝,用戶空間的數組到內核空間只有這一次拷貝)。epoll_wait阻塞等待。在內核態有一個和epoll_wait對應的函數調用,把就緒的fd,填入到一個就緒列表中,而epoll_wait讀取這個就緒列表,做到了快速返回(O(1))。

詳細的對比可以參考select、poll、epoll之間的區別總結:

 

有了上面的原理介紹,這裏舉例來說明下epoll到底是怎麼使用的,加深理解。舉兩個例子:

一個是比較簡單的父子進程通信的例子,單個小程序,不需要跑多個應用實例,不需要用戶輸入。
一個是比較實戰的socket+epoll,畢竟現實案例中哪有兩個父子進程間通訊這麼簡單的應用場景。

有了多路復用,難道還不夠?

有了I/O復用,有了epoll已經可以使服務器併發幾十萬連接的同時,維持高TPS了,難道這還不夠嗎?答案是,技術層面足夠了,但在軟件工程層面卻是不夠的。例如,總要有個for循環去調用epoll,總來處理epoll的返回,這是每次都要重複的工作。for循環體裏面寫什麼—-通知返回之後,做事情的程序最好能以一種回調的機制,提供一個編程框架,讓程序更有結構一些。另一方面,如果希望每個事件通知之後,做的事情能有機會被代理到某個線程裏面去單獨運行,而線程完成的狀態又能通知回主任務,那麼”異步”的進制就必須被引入。

所以,還有兩個問題要解決,一是”編程框架”,一是”異步”。我們先看幾個目前流行的框架,大部分框架已經包含了某種異步的機制。我們接下來的篇章將介紹“編程框架”和“異步I/O模型”。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

pod刪除主要流程源碼解析

本文以v1.12版本進行分析

當一個pod刪除時,client端向apiserver發送請求,apiserver將pod的deletionTimestamp打上時間。kubelet watch到該事件,開始處理。

syncLoop

kubelet對pod的處理主要都是在syncLoop中處理的。

func (kl *Kubelet) syncLoop(updates <-chan kubetypes.PodUpdate, handler SyncHandler) {
for {
...
        if !kl.syncLoopIteration(updates, handler, syncTicker.C, housekeepingTicker.C, plegCh) {
            break
        }
...

與pod刪除主要在syncLoopIteration中需要關注的是以下這兩個。

func (kl *Kubelet) syncLoopIteration(configCh <-chan kubetypes.PodUpdate, handler SyncHandler,
    syncCh <-chan time.Time, housekeepingCh <-chan time.Time, plegCh <-chan *pleg.PodLifecycleEvent) bool {
    select {
    case u, open := <-configCh:
...
        switch u.Op {
...
        case kubetypes.UPDATE:
            handler.HandlePodUpdates(u.Pods)
...
    case <-housekeepingCh:
        if !kl.sourcesReady.AllReady() {
        } else {
            if err := handler.HandlePodCleanups(); err != nil {
                glog.Errorf("Failed cleaning pods: %v", err)
            }
        }
    }

第一個是由於發送給apiserver的DELETE請求觸發的,增加了deletionTimestamp的事件。這裏對應於kubetypes.UPDATE。也就是會走到HandlePodUpdates函數。

另外一個與delete相關的是每2s執行一次的來自於housekeepingCh的定時事件,用於清理pod,執行的是handler.HandlePodCleanups函數。這兩個作用不同,下面分別進行介紹。

HandlePodUpdates

先看HandlePodUpdates這個流程。只要打上了deletionTimestamp,就必然走到這個流程里去。

func (kl *Kubelet) HandlePodUpdates(pods []*v1.Pod) {
    for _, pod := range pods {
...
        kl.dispatchWork(pod, kubetypes.SyncPodUpdate, mirrorPod, start)
    }
}

在HandlePodUpdates中,進而將pod的信息傳遞到dispatchWork中處理。

func (kl *Kubelet) dispatchWork(pod *v1.Pod, syncType kubetypes.SyncPodType, mirrorPod *v1.Pod, start time.Time) {
    if kl.podIsTerminated(pod) {
        if pod.DeletionTimestamp != nil {
            kl.statusManager.TerminatePod(pod)
        }
        return
    }
    // Run the sync in an async worker.
    kl.podWorkers.UpdatePod(&UpdatePodOptions{
        Pod:        pod,
        MirrorPod:  mirrorPod,
        UpdateType: syncType,
        OnCompleteFunc: func(err error) {
...

這裏首先通過判斷了kl.podIsTerminated(pod)判斷pod是不是已經處於了Terminated狀態。如果是的話,則不進行下面的kl.podWorkers.UpdatePod。

func (kl *Kubelet) podIsTerminated(pod *v1.Pod) bool {
    status, ok := kl.statusManager.GetPodStatus(pod.UID)
    if !ok {
        status = pod.Status
    }
    return status.Phase == v1.PodFailed || status.Phase == v1.PodSucceeded || (pod.DeletionTimestamp != nil && notRunning(status.ContainerStatuses))
}

這個地方特別值得注意的是,並不是由了DeletionTimestamp就會認為是Terminated狀態,而是有DeletionTimestamp且所有的容器不在運行了。也就是說如果是一個正在正常運行的pod,是也會走到kl.podWorkers.UpdatePod中的。UpdatePod通過一系列函數調用,最終會通過異步的方式執行syncPod函數中進入到syncPod函數中。

func (kl *Kubelet) syncPod(o syncPodOptions) error {
...
    if !runnable.Admit || pod.DeletionTimestamp != nil || apiPodStatus.Phase == v1.PodFailed {
        var syncErr error
        if err := kl.killPod(pod, nil, podStatus, nil); err != nil {
...

在syncPod中,調用killPod從而對pod進行停止操作。

killPod

killPod是停止pod的主體。在很多地方都會使用。這裏主要介紹下起主要的工作流程。停止pod的過程主要發生在killPodWithSyncResult函數中。

func (m *kubeGenericRuntimeManager) killPodWithSyncResult(pod *v1.Pod, runningPod kubecontainer.Pod, gracePeriodOverride *int64) (result kubecontainer.PodSyncResult) {
    killContainerResults := m.killContainersWithSyncResult(pod, runningPod, gracePeriodOverride)
...
    for _, podSandbox := range runningPod.Sandboxes {
            if err := m.runtimeService.StopPodSandbox(podSandbox.ID.ID); err != nil {
...

killPodWithSyncResult的主要工作分為兩個部分。killContainersWithSyncResult負責將pod中的container停止掉,在停止后再執行StopPodSandbox。

func (m *kubeGenericRuntimeManager) killContainer(pod *v1.Pod, containerID kubecontainer.ContainerID, containerName string, reason string, gracePeriodOverride *int64) error {
    if err := m.internalLifecycle.PreStopContainer(containerID.ID); err != nil {
        return err
    }
...
    err := m.runtimeService.StopContainer(containerID.ID, gracePeriod)

killContainersWithSyncResult的主要工作是在killContainer中完成的,這裏可以看到,其中的主要兩個步驟是在容器中進行prestop的操作。待其成功后,進行container的stop工作。至此所有的應用容器都已經停止了。下一步是停止pause容器。而StopPodSandbox就是執行這一過程的。將sandbox,也就是pause容器停止掉。StopPodSandbox是在dockershim中執行的。

func (ds *dockerService) StopPodSandbox(ctx context.Context, r *runtimeapi.StopPodSandboxRequest) (*runtimeapi.StopPodSandboxResponse, error) {
...
if !hostNetwork && (ready || !ok) {
...
        err := ds.network.TearDownPod(namespace, name, cID, annotations)
...
    }
    if err := ds.client.StopContainer(podSandboxID, defaultSandboxGracePeriod); err != nil {

StopPodSandbox中主要的部分是先進行網絡卸載,再停止相應的容器。在完成StopPodSandbox后,至此pod的所有容器都已經停止完成。至於volume的卸載,是在volumeManager中進行的。本文不做單獨介紹了。停止后的容器在pod徹底清理后,會被gc回收。這裏也不展開講了。

HandlePodCleanups

上面這個流程並不是刪除流程的全部。一個典型的情況就是,如果container都不是running,那麼在UpdatePod的時候都return了,那麼又由誰來處理呢?這裏我們回到最開始,就是那個每2s執行一次的HandlePodCleanups的流程。也就是說比如container處於crash,container正好不是running等情況,其實是在這個流程里進行處理的。當然HandlePodCleanups的作用不僅僅是清理not running的pod,再比如數據已經在apiserver中強制清理掉了,或者由於其他原因這個節點上還有一些沒有完成清理的pod,都是在這個流程中進行處理。

func (kl *Kubelet) HandlePodCleanups() error {
... 
    for _, pod := range runningPods {
        if _, found := desiredPods[pod.ID]; !found {
            kl.podKillingCh <- &kubecontainer.PodPair{APIPod: nil, RunningPod: pod}
        }
    }

runningPods是從cache中獲取節點現有的pod,而desiredPods則是節點上應該存在未被停止的pod。如果存在runningPods中有而desiredPods中沒有的pod,那麼它應該被停止,所以發送到podKillingCh中。

func (kl *Kubelet) podKiller() {
...
    for podPair := range kl.podKillingCh {
...

        if !exists {
            go func(apiPod *v1.Pod, runningPod *kubecontainer.Pod) {
                glog.V(2).Infof("Killing unwanted pod %q", runningPod.Name)
                err := kl.killPod(apiPod, runningPod, nil, nil)
...
            }(apiPod, runningPod)
        }
    }
}

在podKiller流程中,會去接收來自podKillingCh的消息,從而執行killPod,上文已經做了該函數的介紹了。

statusManager

在最後,statusManager中的syncPod流程,將會進行檢測,通過canBeDeleted確認是否所有的容器關閉了,volume卸載了,cgroup清理了等等。如果這些全部完成了,則從apiserver中將pod信息徹底刪除。

func (m *manager) syncPod(uid types.UID, status versionedPodStatus) {
...
    if m.canBeDeleted(pod, status.status) {
        deleteOptions := metav1.NewDeleteOptions(0)
        deleteOptions.Preconditions = metav1.NewUIDPreconditions(string(pod.UID))
        err = m.kubeClient.CoreV1().Pods(pod.Namespace).Delete(pod.Name, deleteOptions)
...

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

MySQL 5.7 – 通過 BINLOG 恢複數據

日常開發,運維中,經常會出現誤刪數據的情況。誤刪數據的類型大致可分為以下幾類:

  • 使用 delete 誤刪行
  • 使用 drop table 或 truncate table 誤刪表
  • 使用 drop database 語句誤刪數據庫
  • 使用 rm 命令誤刪整個 MySQL 實例。

不同的情況,都會有其優先的解決方案:

  • 針對誤刪行,可以通過 Flashback 工具將數據恢復
  • 針對誤刪表或庫,一般採用通過 BINLOG 將數據恢復。
  • 而對於誤刪 MySQL 實例,則需要我們搭建 HA 的 MySQL 集群,並保證我們的數據跨機房,跨城市保存。

本篇主要討論的內容是誤刪表或者庫,會先介紹有關 BINLOG 的操作命令,然後會對誤刪表的這種情況進行實際的模擬。

BINLOG 常見操作命令

BINLOG 的查詢方式一般分為兩種,一種是進入 MySQL 控制台進行查詢,另一種是通過 MySQL 提供的工具 mysqlbinlog 進行查詢,兩者的不同會在下面介紹。

通過 MySQL Cli 查詢 BINLOG 信息

在 cli 中,常見的命令如下:

# 查詢 BINLOG 格式
show VARIABLES like 'binlog_format';

# 查詢 BINLOG 位置
show VARIABLES like 'datadir';

# 查詢當前數據庫中 BINLOG 名稱及大小
show binary logs;

# 查看 master 正在寫入的 BINLOG 信息
show master status\G;

# 通過 offset 查看 BINLOG 信息
show BINLOG events in 'mysql-bin.000034' limit 9000,  10;

# 通過 position 查看 binlog 信息
show BINLOG events in 'mysql-bin.000034' from 1742635 limit 10;

使用 show BINLOG events 的問題:

  • 使用該命令時,如果當前 binlog 文件很大,而且沒有指定 limit,會引發對資源的過度消耗。因為 MySQL 客戶端需要將 binlog 的全部內容處理,返回並显示出來。為了防止這種情況,mysqlbinlog 工具是一個很好的選擇。

通過 mysqlbinlog 查詢 BINLOG 信息

在介紹 mysqlbinlog 工具使用前,先來看下 BINLOG 文件的內容:

# 查詢 BINLOG 的信息
mysqlbinlog  --no-defaults mysql-bin.000034 | less
# at 141
#100309  9:28:36 server id 123  end_log_pos 245
  Query thread_id=3350  exec_time=11  error_code=0
  • at 表示 offset 或者說事件開始的起始位置
  • 100309 9:28:36 server id 123 表示 server 123 開始執行事件的日期
  • end_log_pos 245 表示事件的結束位置 + 1,或者說是下一個事件的起始位置。
  • exec_time 表示在 master 上花費的時間,在 salve 上,記錄的時間是從 Master 記錄開始,一直到 Slave 結束完成所花費的時間。
  • rror_code=0 表示沒有錯誤發生。

在大致了解 binlog 的內容后,mysqlbinlog 的用途有哪些?:

  • mysqlbinlog 可以作為代替 cli 讀取 binlog 的工具。
  • mysqlbinlog 可以將執行過的 SQL 語句輸出,用於數據的恢復或備份。

查詢 BINLOG 日誌:

# 查詢規定時候后發生的 BINLOG 日誌
mysqlbinlog --no-defaults --base64-output=decode-rows -v  --start-datetime  "2019-11-22 14:00:00" --database sync_test  mysql-bin.000034 | less

導出 BINLOG 日誌,用於分析和排查 sql 語句:

mysqlbinlog --no-defaults --base64-output=decode-rows -v  --start-datetime  "2019-11-22 14:00:00" --database sync_test  mysql-bin.000034 > /home/mysql_backup/binlog_raw.sql

導入 BINLOG 日誌

# 通過 BINLOG 進行恢復。
mysqlbinlog --start-position=1038 --stop-position=1164 --database=db_name  mysql-bin.000034 | mysql  -u cisco -p db_name

# 通過 BINLOG 導出的 sql 進行恢復。
mysql -u cisco -p db_name < binlog_raw.sql.sql

mysqlbinlog 的常用參數:

  • --database 僅僅列出配置的數據庫信息
  • --no-defaults 讀取沒有選項的文件, 指定的原因是由於 mysqlbinlog 無法識別 BINLOG 中的 default-character-set=utf8 指令
  • --offset 跳過 log 中 N 個條目
  • --verbose 將日誌信息重建為原始的 SQL 陳述。
    • -v 僅僅解釋行信息
    • -vv 不但解釋行信息,還將 SQL 列類型的註釋信息也解析出來
  • --start-datetime 显示從指定的時間或之後的時間的事件。
    • 接收 DATETIME 或者 TIMESTRAMP 格式。
  • --base64-output=decode-rows 將 BINLOG 語句中事件以 base-64 的編碼显示,對一些二進制的內容進行屏蔽。
    • AUTO 默認參數,自動显示 BINLOG 中的必要的語句
    • NEVER 不會显示任何的 BINLOG 語句,如果遇到必須显示的 BINLOG 語言,則會報錯退出。
    • DECODE-ROWS 显示通過 -v 显示出來的 SQL 信息,過濾到一些 BINLOG 二進制數據。

MySQL Cli 和 mysqlbinlog 工具之間的比較

如果想知道當前 MySQL 中正在寫入的 BINLOG 的名稱,大小等基本信息時,可以通過 Cli 相關的命令來查詢。

但想查詢,定位,恢復 BINLOG 中具體的數據時,要通過 mysqlbinlog 工具,因為相較於 Cli 來說,mysqlbinlog 提供了 --start-datetime--stop-position 等這樣更為豐富的參數供我們選擇。這時 Cli 中 SHOW BINLOG EVENTS 的簡要語法就變得相形見絀了。

使用 BINLOG 恢複數據

恢復的大致流程如下:

  1. 會創建數據庫和表,並插入數據。
  2. 誤刪一條數據。
  3. 繼續插入數據。
  4. 誤刪表。
  5. 最後將原來以及之後插入的數據進行恢復。

準備數據

準備數據庫,表及數據:

# 創建臨時數據庫
CREATE DATABASE IF NOT EXISTS test_binlog default charset utf8 COLLATE utf8_general_ci; 


# 創建臨時表
CREATE TABLE `sync_test` (`id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;


# 添加數據
insert into sync_test (id, name) values (null, 'xiaoa');
insert into sync_test (id, name) values (null, 'xiaob');
insert into sync_test (id, name) values (null, 'xiaoc');

# 查看添加的數據
select * from sync_test;

刪除表或者數據

誤刪操作:

# 刪除 name=xiaob 的數據
delete from sync_test where id=3

# 插入幾條數據
insert into sync_test (id, name) values (null, 'xiaod');
insert into sync_test (id, name) values (null, 'xiaoe');
insert into sync_test (id, name) values (null, 'xiaof');

# 刪除表
DROP TABLE sync_test;

數據的恢復

在執行數據恢復前,如果操作的是生產環境,會有如下的建議:

  • 使用 flush logs 命令,替換當前主庫中正在使用的 binlog 文件,好處如下:
    • 可將誤刪操作,定位在一個 BINLOG 文件中,便於之后的數據分析和恢復。
    • 避免操作正在被使用的 BINLOG 文件,防止發生意外情況。
  • 數據的恢復不要在生產庫中執行,先在臨時庫恢復,確認無誤后,再倒回生產庫。防止對數據的二次傷害。

通常來說,恢復主要有兩個步驟:

  1. 在臨時庫中,恢復定期執行的全量備份數據。
  2. 然後基於全量備份的數據點,通過 BINLOG 來恢復誤操作和正常的數據。

使用 BINLOG 做數據恢復前:

# 查看正在使用的 Binlog 文件
show master status\G;
# 显示結果是: mysql-bin.000034

# 執行 flush logs 操作,生成新的 BINLOG
flush logs;

# 查看正在使用的 Binlog 文件
show master status\G;
# 結果是:mysql-bin.000035

確定恢複數據的步驟:

這裏主要是有兩條誤刪的操作,數據行的誤刪和表的誤刪。有兩種方式進行恢復。

  • 方式一:首先恢復到刪除表操作之前的位置,然後再單獨恢復誤刪的數據行。
  • 方式二:首先恢復到誤刪數據行的之前的位置,然後跳過誤刪事件再恢複數據表操作之前的位置。

這裏採用方式一的方案進行演示,由於是演示,就不額外找一個臨時庫進行全量恢復了,直接進行操作。

查詢創建表的事件位置和刪除表的事件位置

#  根據時間確定位置信息
mysqlbinlog --no-defaults --base64-output=decode-rows -v  --start-datetime  "2019-11-22 14:00:00" --database test_binlog  mysql-bin.000034 | less

創建表的開始位置:

刪除表的結束位置:

插入 name=’xiaob’ 的位置:

# 根據位置導出 SQL 文件
mysqlbinlog --no-defaults --base64-output=decode-rows -v --start-position "2508132" --stop-position "2511004" --database test_binlog  mysql-bin.000034 > /home/mysql_backup/test_binlog_step1.sql
 
 
mysqlbinlog --no-defaults --base64-output=decode-rows -v --start-position "2508813" --stop-position "2509187" --database test_binlog  mysql-bin.000034 > /home/mysql_backup/test_binlog_step2.sql
 

# 使用 mysql 進行恢復
mysql -u cisco -p < /home/mysql_backup/test_binlog_step1.sql
mysql -u cisco -p < /home/mysql_backup/test_binlog_step2.sql

MySQL 5.7 中無論是否打開 GTID 的配置,在每次事務開啟時,都首先會出 GTID 的一個事務,用於并行複製。所以在確定導出開始事務位置時,要算上這個事件。

在使用 –stop-position 導出時,會導出在指定位置的前一個事件,所以這裏要推后一個事務。

對於 DML 的語句,主要結束位置要算上 COMMIT 的位置。

總結

在文章開始時,我們熟悉了操作 BINLOG 的兩種方式 CLI 和 mysqlbinlog 工具,接着介紹了其間的區別和使用場景,對於一些大型的 BINLOG 文件,使用 mysqlbinlog 會更加的方便和效率。並對 mysqlbinlog 的一些常見參數進行了介紹。

接着通過使用 mysqlbinlog 實際模擬了數據恢復的過程,並在恢複數據時,提出了一些需要注意的事項,比如 flush logs 等。

最後在恢複數據時,要注意 start-positionend-position 的一些小細節,來保證找到合適的位置。

參考

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

高性能網絡通訊原理

目錄

高性能網絡通訊原理

前言

本來想對netty的源碼進行學習和探究,但是在寫netty之前許多底層的知識和原理性的東西理解清楚,那麼對學習網絡通訊框架的效果則會事半功倍。

本篇主要探討高性能網絡通訊框架的一些必要知識和底層操作系統相關的原理。在探討如何做之前,我們先討論下為什麼要做。

隨着互聯網的高速發展,用戶量呈指數形式遞增,從原來的PC普及到現在的移動設備普及。用戶量都是千萬甚至億為單位計算,尤其是實時通訊軟件,在線實時互動的應用出現,在線用戶數從原來的幾十上百到後來的上萬甚至上千萬。單台服務的性能瓶頸和網絡通訊瓶頸慢慢呈現。應用架構從單應用到應用數據分離,再到分佈式集群高可用架構。單台服務的性能不足可以通過構建服務集群的方式水平擴展,應用性能瓶頸被很好的解決。但是橫向擴展帶來了直接的經濟成本。

一個高性能的網絡通訊框架從硬件設備到操作系統內核以及用戶模式都需要精心設計。只要有任何地方有疏漏都會出現短板效應。

I/O訪問

當我們在讀取socket數據時,雖然我們在代碼僅僅是調用了一個Read操作,但是實際操作系統層面做了許多事情。首先操作系統需要從用戶模式轉換為內核模式,處理器會通過網卡驅動對網卡控制器進行操作,網卡控制器則控制網卡。

處理器不會直接操控硬件。

為了提高CPU利用率,I/O訪問方式也發生了很大變化。

  1. 早期的CPU直接控制外圍設備,後來增加了控制器或I/O模塊。處理器開始將I/O操作從外部設備接口分離出來。處理器通過向I/O模塊發送命令執行I/O指令。然而當I/O操作完成時並不會通知處理器I/O,因此處理器需要定時檢查I/O模塊的狀態,它會進行忙等待,因此效率並不高。
  2. 後來CPU支持了中斷方式,處理器無需等待執行I/O操作,通過中斷控制器產生中斷信號通知I/O操作完成,大大的提高了處理器利用效率。這時的I/O操作使用特定的in/out(I/O端口)指令或直接讀寫內存的方式(內存映射I/O)。但是這些方式都需要處理器使用I/O寄存器逐個內存單元進行訪問,效率並不高,在I/O操作時需要消耗的CPU時鐘周期。
  3. 為了提高效率,後來增加了DMA控制器,它可以模擬處理起獲得內存總線控制權,進行I/O的讀寫。當處理器將控制權交給DMA控制器之後,DMA處理器會先讓I/O硬件設備將數據放到I/O硬件的緩衝區中,然後DMA控制器就可以開始傳輸數據了。在此過程中處理器無需消耗時鐘周期。當DMA操作完成時,會通過中斷操作通知處理器。

I/O訪問的發展趨勢是盡可能減少處理器干涉I/O操作,讓CPU從I/O任務中解脫出來,讓處理器可以去做其他事情,從而提高性能。

對於I/O訪問感興趣的同學可以看《操作系統精髓與設計原理(第5版)》第十一章I/O管理相關內容和《WINDOWS內核原理與實現》第六章I/O論述相關內容

I/O模型

在討論I/O模型之前,首先引出一個叫做C10K的問題。在早期的I/O模型使用的是同步阻塞模型,當接收到一個新的TCP連接時,就需要分配一個線程。因此隨着連接增加線程增多,頻繁的內存複製,上下文切換帶來的性能損耗導致性能不佳。因此如何使得單機網絡併發連接數達到10K成為通訊開發者熱門的討論話題。

同步阻塞

前面提到,在最原始的I/O模型中,對文件設備數據的讀寫需要同步等待操作系統內核,即使文件設備並沒有數據可讀,線程也會被阻塞住,雖然阻塞時不佔用CPU始終周期,但是若需要支持併發連接,則必須啟用大量的線程,即每個連接一個線程。這樣必不可少的會造成線程大量的上下文切換,隨着併發量的增高,性能越來越差。

select模型/poll模型

為了解決同步阻塞帶來線程過多導致的性能問題,同步非阻塞方案產生。通過一個線程不斷的判斷文件句柄數組是否有準備就緒的文件設備,這樣就不需要每個線程同步等待,減少了大量線程,降低了線程上下文切換帶來的性能損失,提高了線程利用率。這種方式也稱為I/O多路復用技術。但是由於數組是有數組長度上限的(linux默認是1024),而且select模型需要對數組進行遍歷,因此時間複雜度是\(O_{(n)}\)因此當高併發量的時候,select模型性能會越來越差。

poll模型和select模型類似,但是它使用鏈表存儲而非數組存儲,解決了併發上限的限制,但是並沒有解決select模型的高併發性能底下的根本問題。

epoll模型

在linux2.6支持了epoll模型,epoll模型解決了select模型的性能瓶頸問題。它通過註冊回調事件的方式,當數據可讀寫時,將其加入到通過回調方式,將其加入到一個可讀寫事件的隊列中。這樣每次用戶獲取時不需要遍歷所有句柄,時間複雜度降低為\(O_{(1)}\)。因此epoll不會隨着併發量的增加而性能降低。隨着epoll模型的出現C10K的問題已經完美解決。

異步I/O模型

前面講的幾種模型都是同步I/O模型,異步I/O模型指的是發生數據讀寫時完全不同步阻塞等待,換句話來說就是數據從網卡傳輸到用戶空間的過程時完全異步的,不用阻塞CPU。為了更詳細的說明同步I/O與異步I/O的區別,接下來舉一個實際例子。

當應用程序需要從網卡讀取數據時,首先需要分配一個用戶內存空間用來保存需要讀取的數據。操作系統內核會調用網卡緩衝區讀取數據到內核空間的緩衝區,然後再複製到用戶空間。在這個過程中,同步阻塞I/O在數據讀取到用戶空間之前都會被阻塞,同步非阻塞I/O只知道數據已就緒,但是從內核空間緩衝區拷貝到用戶空間時,線程依然會被阻塞。而異步I/O模型在接收到I/O完成通知時,數據已經傳輸到用戶空間。因此整個I/O操作都是完全異步的,因此異步I/O模型的性能是最佳的。

在我的另一篇文章對windows操作系統I/O原理做了簡要的敘述,感興趣的同學可以看下。

I/O線程模型

從線程模型上常見的線程模型有Reactor模型和Proactor模型,無論是哪種線程模型都使用I/O多路復用技術,使用一個線程將I/O讀寫操作轉變為讀寫事件,我們將這個線程稱之為多路分離器。

對應上I/O模型,Reacor模型屬於同步I/O模型,Proactor模型屬於異步I/O模型。

Reactor模型

在Reactor中,需要先註冊事件就緒事件,網卡接收到數據時,DMA將數據從網卡緩衝區傳輸到內核緩衝區時,就會通知多路分離器讀事件就緒,此時我們需要從內核空間讀取到用戶空間。

同步I/O採用緩衝I/O的方式,首先內核會從申請一個內存空間用於存放輸入或輸出緩衝區,數據都會先緩存在該緩衝區。

Proactor模型

Proactor模型,需要先註冊I/O完成事件,同時申請一片用戶空間用於存儲待接收的數據。調用讀操作,當網卡接收到數據時,DMA將數據從網卡緩衝區直接傳輸到用戶緩衝區,然後產生完成通知,讀操作即完成。

異步I/O採用直接輸入I/O或直接輸出I/O,用戶緩存地址會傳遞給設備驅動程序,數據會直接從用戶緩衝區讀取或直接寫入用戶緩衝區,相比緩衝I/O減少內存複製。

總結

本文通過I/O訪問方式,I/O模型,線程模型三個方面解釋了操作系統為實現高性能I/O做了哪些事情,通過提高CPU使用效率,減少內存複製是提高性能的關鍵點。

參考文檔

  1. 《操作系統精髓與設計原理(第5版)》
  2. 《WINDOWS內核原理與實現》

微信掃一掃二維碼關注訂閱號傑哥技術分享
出處:
作者:傑哥很忙
本文使用「CC BY 4.0」創作共享協議。歡迎轉載,請在明顯位置給出出處及鏈接。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

氫燃料沒未來?Honda改拚電動車,明年搶攻歐陸

本田汽車(Honda)計畫於2018年一次推出兩款全電動車,且宣告未來將以電動車作為發展主軸。

根據《BusinessInsider》報導表示,本田六月就已表示將打造中國專屬電動車,8月29日發佈的聲明稿則指明另一款電動車是為歐洲客戶所設計。

本田說,新城市電動概念車(Urban EV Concept)將在九月法蘭克福車展上亮相,這也是本田在歐洲第一款電動車,肩負打開歐洲電動車市場的任務。

目前本田僅有一款純電動車在美國上市,不過本田表示,2030年旗下三分之二的車型都要電動化,達成目標的方法將在法蘭克福車展上對外說明。

本田與日本同業豐田(Toyota)此前主要開發氫燃料車與混和動力車,但由上述可知本田策略已經轉向發展電動車,可能引領其它日本車廠跟進。

(本文內容由授權使用。圖片出處:)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

馬斯克遇程咬金,第一台電動卡車不姓特斯拉

馬斯克近日才剛在推特上表示,近期將推出全電動的半掛式卡車,令市場相當的看好,然而如今第一個達成創舉的廠商,卻不是特斯拉。

特斯拉將在9 月發表載貨用的全電動半掛卡車(Tesla Semi)原型,雖然業界一直在質疑其負載及續航力,不過若成功的話也等於是對市場投下一個震撼彈。畢竟相較汽柴油車的引擎,電動車的維護成本較低,假如電池成本也持續降低,在配合環保法規的情況下,相當的有市場。

不過如今一家原本專做柴油及天然氣發動機的龍頭廠商康明斯(Cummins)推出了相當於第7 級重型卡車的電動車,其配備了先進的140 kWh 的電池組,並預計將於2019 年開始交付給客戶。這台卡車被命名為AEOS,應該是取意於希臘神話中替太陽神阿波羅拉車的火焰飛馬。

 

AEOS in motion: Raw footage of our first fully electric heavy duty demonstration Urban Hauler Tractor.

— Cummins Inc. (@Cummins)

 

且Cummins 也能僅提供動力系統總成,將可應用於電動巴士等其他車型。電動巴士製造商 Proterra 的總裁Ryan Popple 指出,這是相當令人震驚的消息,是邁向完全電氣化交通的一大步,隨著各國正積極修訂法規鼓勵電動車,目前這方面的市場相當大,巴士及校車等訂單幾乎滿載。

據Cummins 表示,這輛電動卡車動力總成與一般柴油車相當,可牽引4,4000 磅的重量,近20 噸,單次充電可行駛約100 英里,並可加掛額外的電池組將行程擴展到300 英里,還可安裝太陽能電池板,還有再生制軔(Regenerative brake)及低阻力輪胎的研發將可望進一步提昇其行駛距離。目前的充電站設計,將可在1 小時內充滿,而Cummins 的目標是希望,能夠在2020 年以前,將充電時間縮短到20 分鐘。

不過Cummins 的執行長Thomas Linebarger 也坦言,依目前電池科技的進展,第7 級重型卡車的應用已是極限,第8 級的重型拖車目前仍遙遙無期。所以也將推出同型的高效率柴油引擎及混合動力車款。儘管有所突破,但目前電動系統仍較適用於商用卡車,Cummins 一舉超越特斯拉也同樣宣示了將進軍此市場的意圖。

當然特斯拉就算被搶得頭籌,也不代表無法反擊,據之前透露的消息,其產品可能將定位於行程達200-300 英里的運輸範圍,性能可能比AEOS 還強。但Cummins 此舉或許更大意義在於強調,他們這些傳統龍頭,儘管受到挑戰,仍有相當強的底蘊,在不同動力系統的研發上,有超乎市場想像的潛力。

(合作媒體:。圖片出處:)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

孫宇晨回顧區塊鏈歷程:不走熱點走心

  10 月 26 日上午,波場創始人孫宇晨發布微博回顧其投身區塊鏈過程,在經歷了王小川口水仗、巴菲特天價午餐之後,孫宇晨稱不走熱點說些走心的話,“2013 年回國推廣區塊鏈,那時慘到連區塊鏈這個詞都不存在,六年了,我們沒成先烈,還活着。對於區塊鏈造福每个中國人的未來,即便在公司即將倒閉的夜晚,我都從未懷疑。”

  在孫宇晨看來,區塊鏈是互聯網的 2.0 形態,互聯網的 1.0 形態是信息網絡,而 2.0 形態是價值網絡。互聯網第一步傳播信息,就如同今天的互聯網,第二步傳播價值,價值包括通證:貨幣、股票、債券等所有人類的價值所有物。

  在長文最後,孫宇晨表示:“互聯網已經到了下半場,區塊鏈卻還沒開始預備賽。對於即將打響的預備賽,我想說,我準備好了!”

  以下為孫宇晨微博全文:

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!