第6章:可維護性軟體構建方法 6.3可維護性構建技術

啥也博士發表於2019-01-19

大綱

基於狀態的構建

  • 基於自動機的程式設計
  • 設計模式:Memento提供了將物件恢復到之前狀態的功能(撤消)。
  • 設計模式:狀態允許物件在其內部狀態改變時改變其行為。

表驅動結構*

基於語法的構建

  • 語法和解析器
  • 正規表示式(regexp)
  • 設計模式:直譯器實現一種專門的語言。

基於狀態的構建

基於狀態的程式設計是一種程式設計技術,它使用有限狀態機(FSM)來描述程式行為,即使用“狀態”來控制程式的流程。

使用有限狀態機來定義程式的行為,使用狀態來控制程式的執行

  • 例如,在電梯的情況下,可能會停止,向上移動,向下移動,停止,關閉門並開啟門。

這些都被認為是一個狀態,接下來發生的事情是由電梯的當前狀態決定的。
根據當前狀態,決定下一步要執行什麼操作,執行操作之後要轉移到什麼新的狀態

  • 如果電梯剛剛關好,接下來會發生什麼情況? 它可以停止,向上移動或向下移動。
  • 當電梯停下時,你預計下一個動作是門開啟,向上移動或向下移動。

(1) 基於自動機的程式設計

基於自動機的程式設計是一種程式設計模式,其中程式或其一部分被認為是有限狀態機(FSM)或任何其他形式自動機的模型。

  • 將程式視為有限自動機。
  • 每臺自動機可以一次接受一個“步驟”,程式的執行分解為單獨的步驟。
  • 這些步驟通過改變代表“狀態”的變數的值來相互溝通。
  • 程式的控制流程由該變數的值決定。

應用程式設計方法應與控制系統(Automata System)的設計類似。
核心思想:將程式看作是一個有限狀態自動機,側重於對“狀態”和“狀態轉換”的抽象和程式設計

程式的執行被分解為一組自動執行的步驟

  • 每個步驟實際上是一個程式碼段的執行(所有步驟都相同),它有一個入口點。 這樣的部分可以是功能或其他例程,或者只是一個迴圈體。

各步驟之間的通訊通過“狀態變數”進行

  • 在任何兩個步驟之間,程式不能有其狀態的隱式分量,例如本地(堆疊)變數值,返回地址,當前指令指標等。
  • 在進入自動機步驟的任何兩個時刻取得的整個程式的狀態只能在被認為是自動機狀態的變數值中有所不同。

如何實施?

基於自動機的程式碼的整個執行過程都是自動機步驟的一個(可能是顯式的)迴圈。

“狀態”變數可以是簡單的列舉資料型別,但可以使用更復雜的資料結構。

一種常見的技術是建立一個狀態轉換表,一個包含表示每種可能狀態的行的二維陣列,以及表示輸入引數的列。

  • 行和列滿足的表格的值是在符合兩個條件的情況下機器應轉換到的下一個狀態。

應用領域

高可靠性系統

  • 軍事應用
  • 航空航天工業
  • 汽車行業

嵌入式系統
移動系統
視覺化系統
Web應用程式
客戶端伺服器應用程式

(2) State Pattern

狀態模式 (behavioral pattern)

假設一個物件總是處於幾個已知狀態之一
物件所處的狀態決定了幾種方法的行為
可以在每種方法中使用if / case語句
更好的解決方案:狀態模式
有一個狀態物件的引用

  • 通常,狀態物件不包含任何欄位
  • 更改狀態:更改狀態物件
  • 方法委託給狀態物件

狀態模式註釋
可以為每個狀態類的例項使用單例

  • 狀態物件不封裝狀態,所以可以共享 – 不可變

輕鬆新增新的狀態

  • 新狀態可以擴充套件其他狀態
  • 僅覆蓋選定的功能

(3) Memento Pattern

備忘錄模式 (behavioral)

意圖

  • 在不違反封裝的情況下,捕獲並外部化物件的內部狀態,以便稍後可以將物件返回到此狀態。
  • 封裝“檢查點”功能的魔術餅乾(cookie)。
  • 促進撤消或回滾到完整的物件狀態。

問題:需要將物件恢復到以前的狀態(例如“撤銷”或“回滾”操作)。
記住物件的歷史狀態,以便於“回滾”

備忘錄設計模式定義了三種不同的角色:

  • 發起者 – 知道如何儲存自己的物件。需要“備忘”的類
  • 看守者 – 知道發起者需要儲存和恢復的原因和時間的物件。新增發起者的備忘記錄和恢復
  • 備忘錄 – 由發起人撰寫和閱讀的鎖盒,由看守人管理。備忘錄,記錄發起者物件的歷史狀態

*表驅動的構造

什麼是“表驅動”?

表驅動方法是一種使用表來查詢資訊而不是使用邏輯語句(例如if-else和switch-case)的模式。
在簡單情況下,使用邏輯語句更快更容易,但隨著邏輯鏈變得更復雜,表驅動的程式碼:

  • 比複雜的邏輯簡單
  • 更容易修改
  • 更高效

表驅動程式設計的核心思想:將程式碼中複雜的if-else和switch-case語句從程式碼中分離出來,通過“查表”的方式完成,從而提高可維護性

查詢東西的方法

• 直接訪問
• 索引訪問
• 階梯訪問

選擇其中之一取決於資料的性質以及資料域的大小。

(1) 直接訪問表

簡單

  • 您只是通過一個或多個索引“查詢事物”。
  • 與所有查詢表一樣,直接訪問表取代了更復雜的邏輯控制結構。
  • 他們是“直接進入”的,因為你不必跳過任何複雜的迴圈來找到你想要的資訊。

(2) 索引訪問表

有時直接索引是一個問題,特別是如果可能的值域很大。
例如,如果您想使用產品ID(8位數字),並製作一張對映200個產品的表格。

查詢索引與直接索引

索引元素很小(整數),值可以有效地大(只有你需要的那麼多),比如字串(名字,描述,錯誤資訊等)。
多個索引可以訪問相同的資料(員工資訊可以按名稱,聘用日期,出售等進行對映)
可維護 – 從應用程式介面隔離查詢方法。

(3) 階梯訪問表

表格中的條目對資料範圍有效,而不適用於不同的資料點

關鍵點

表格提供了複雜邏輯和繼承結構的替代方案。 如果您發現程式的邏輯或繼承樹讓您感到困惑,那麼問問自己是否可以通過查詢表進行簡化。

使用表格的一個關鍵考慮因素是決定如何訪問表格。 您可以通過直接訪問,索引訪問或階梯訪問來訪問表。

使用表格的另一個關鍵考慮因素是決定放入表格的具體內容。

語法驅動的構造(Grammar-based construction)

基於語法的構建目標

理解語法生成和正規表示式操作符的思想
能夠讀取語法或正規表示式,並確定它是否匹配一系列字元
能夠編寫語法或正規表示式來匹配一組字元序列並將其解析為資料結構

基於字串/流的I / O

某些程式模組以位元組序列或字元序列的形式輸入或輸出輸出,當它儲存在記憶體中時稱為字串,或者在流入或流出模組時稱為字串。 有一類應用,從外部讀取文字資料,在應用中做進一步處理。

具體來說,一個位元組或字元序列可能是:

  • 磁碟上的檔案,這種情況下,規範稱為檔案格式,程式需讀取檔案並從中抽取正確的內容
  • 通過網路傳送的訊息,在這種情況下,規範是有線協議從網路上傳輸過來的訊息,遵循特定的協議
  • 使用者在控制檯上鍵入的命令,在這種情況下,規範是命令列介面,使用者在命令列輸入的指令,遵循特定的格式
  • 儲存在記憶體中的字串,也有格式需要

語法的概念

對於這些型別的序列,語法的概念是設計的一個好選擇:

  • 它不僅可以幫助區分合法序列和非法序列,還可以將序列解析為程式可以使用的資料結構。 使用語法判斷字串是否合法,並解析成程式裡使用的資料結構
  • 從語法產生的資料結構通常是遞迴資料型別。通常是遞迴的資料結構

正規表示式

  • 這是一個廣泛使用的工具,用於許多字串處理任務,需要反彙編字串,從中提取資訊或進行轉換。

解析器生成器是一種將語法自動轉換為該語法的解析器的工具。 根據語法生成它的解析器,用於後續的解析

(1) 語法的組成部分

終結:語法中的文字串

為了描述一串符號,無論它們是位元組,字元還是其他型別的從固定集合中抽取的符號,我們都使用稱為語法的緊湊表示法。

語法定義了一組字串。用語法定義一個“字串”

  • 例如,URL的語法將指定HTTP協議中合法URL的一組字串。

文法中的文字被稱為終結節點,葉節點

  • 它們被稱為終結,因為它們是代表字串結構的解析樹的葉子。語法解析樹的葉子節點
  • 他們沒有孩子,不能再進一步擴大。 無法再往下擴充套件
  • 我們通常用引號將終結寫入,如`http`或`:`。 通常表示為字串

語法中的非終結者與生產者

一個語法由一組產品描述,每個產品定義一個非終結非終止節點

  • 非終結符就像一個變數,它表示一組字串,而生成則表示該變數根據其他變數(非終結符),運算子和常量(終結)的定義。 遵循特定規則,利用操作符,終止節點和其他非終止節點,構造新的字串
  • 非終結符是表示字串的樹的內部節點。

語法中的生產具有這種形式

  • 非終結符:: =終結,非終結符和運算子的表示式

語法的非終結點之一被指定為根。

  • 語法識別的字串集合是匹配根非終結符的字串。
  • 這個非終結者通常被稱為root或start。根節點

(2) 語法中的操作符

三個基本的語法運算子

生產表達中最重要的三個操作是:

  • 連線,不是由一個符號表示,而是一個空格:x :: = y z an x是一個y,後跟一個z
  • 重複,用表示:x :: = y x是零或更多y
  • 聯合,也稱為選擇,由|:x :: = y |表示 z an x是y或z

(5) 正則語法和正規表示式

正則語法

正則語法有一個特殊的性質:通過用右端代替每個非終結符(除了根結尾之外),可以將它縮減為單根生成,只有終結和操作符在右側。
正則語法:簡化之後可以表達為一個產生式而不包含任何非終止節點

正規表示式(正規表示式)

終結和操作符的簡化表示式可以用更緊湊的形式寫成,稱為正規表示式。
正規表示式避免了終結周圍的引號以及終結和運算子之間的空格,因此它只包含終結字元,用於分組的括號和運算子字元。去除引號和空格,從而表達更簡潔(更難懂)

  • 正規表示式比原始語法的可讀性要低得多,因為它缺少記錄每個子表示式意義的非終結符名稱。
  • 但是一個正規表示式的實現很快,並且有很多支援正規表示式的程式語言的庫。

正規表示式中的一些特殊運算子

. 任何單個字元
d任意數字,與[0-9]相同
是任何空格字元,包括空格,製表符,換行符
w任何單詞字元,包括字母和數字
,(,), *, +,…轉義一個操作符或特殊字元,以便它按字面順序匹配

上下文無關文法

通常,可以用我們的語法系統表達的語言稱為上下文無關的。

  • 並非所有的上下文無關語言也是正則的; 也就是說,有些語法不能簡化為單一的非遞迴生成。
  • HTML語法是上下文無關的,但不是正則的。

大多數程式語言的語法也是無上下文的。
一般來說,任何具有巢狀結構的語言(如巢狀括號或大括號)都是上下文無關的,但不是正則的。

(6) *解析器

語法,解析器和解析器生成器
目標:

  • 能夠將語法與解析器生成器結合使用,將字元序列解析為解析樹
  • 能夠將分析樹轉換為有用的資料型別

解析器將輸入文字轉為解析樹

解析器需要一系列字元並嘗試將該序列與語法進行匹配。 解析器:輸入一段文字,與特定的語法規則建立匹配,輸出結果
解析器通常會生成一個解析樹,該解析樹顯示如何將語法生成擴充套件為與字元序列匹配的句子。 解析器:將文字轉化為解析樹

  • 解析樹的根是語法的起始非終結符。
  • 解析樹的每個節點都擴充套件為語法的一個生成。

解析的最後一步是對這個分析樹做一些有用的工作。 利用產生的分析樹,進行下一步的處理
表示語言表示式的遞迴抽象資料型別稱為抽象語法樹(AST)。

解析器生成器根據語法定義生成解析器

解析器生成器是一種讀取語法規範並將其轉換為可識別語法匹配的Java程式的工具。
更廣泛地:

  • 解析器生成器是一種程式設計工具,它根據某種形式的語言形式描述建立解析器,直譯器或編譯器。
  • 輸入可能是一個文字檔案,其中包含用BNF或EBNF編寫的定義程式語言語法的語法。 – 輸出是語法分析器的一些原始碼。

Backus Normal Form(BNF)巴克斯正規化

1959年6月,Backus Normal Form(BNF)首次提出,以遞迴形式描述語言的各種成分,凡遵守其規則的程式就可保證語法上的正確性。

  • 經過Peter Naur的改進與完善以及Niklaus Wirth的擴充,形成了EBNF(擴充套件BNF),也就是目前使用的BNF。
  • 經Donald Knuth的建議,BNF中的N變成了Naur(Backus-Naur Form)。

Grammar定義語法規則(BNF格式的文字),Parser generator根據 語法規則產生一個parser,使用者利用parser來解析文字,看其是否符 合語法定義並對其做各種處理(例如轉成parse tree)

(7) 在Java中使用正規表示式

用於正規表示式處理的java.util.regex

java.util.regex包主要由三個類組成:

  • 一個Pattern物件是一個正規表示式的編譯後的表示。 Pattern類不提供公共建構函式。 要建立一個模式,你必須首先呼叫其公共靜態編譯方法之一,然後返回一個Pattern物件。 這些方法接受一個正規表示式作為第一個引數。模式是對正規表示式的正規表示式進行編譯之後得到的結果
  • 匹配物件是解釋模式並對輸入字串執行匹配操作的引擎。 像Pattern類一樣,Matcher沒有定義公共建構函式。 您通過呼叫Pattern物件上的匹配器方法來獲得Matcher物件。 Matcher:利用Pattern對輸入字串進行解析
  • PatternSyntaxException物件是指示正規表示式模式中的語法錯誤的未經檢查的異常。

正規表示式在程式語言中非常有用。

  • 在Java中,可以使用正規表示式來處理字串(例如String.split,String.matches,java.util.regex.Pattern)。
  • 它們作為現代指令碼語言(如Python,Ruby和Javascript)的一流功能而內建,您可以在許多文字編輯器中將它們用於查詢和替換。

(8)* Interpreter

直譯器模式

直譯器模式提供了評估語言語法或表達的方法。
意圖

  • 給定一種語言,為其語法定義一個表示法,以及一個使用表示法來解釋語言句子的直譯器。 給定一種語法,定義該語法的程式內部表示,形成該語法的直譯器,將遵循語法規則的文字解釋成程式內部的表示(例如一組物件)
  • 將一個領域對映到一種語言,將語言對映到一種語法,將語法對映為一種等級物件導向設計。解釋語法的“引擎”:遵循語法的文字⇒OO表示
  • 用於定義語法,標記輸入並儲存它。

實施

  • 它使用複合模式來表示語法。

因為語法通常形成樹結構,故使用複合模式來表達遵循語法的內容。

  • 它定義了行為,而複合只定義了結構。

針對該層次化樹形結構,定義了一組行為來處理結構中的不同型別節點

直譯器與語法+解析器
語法+解析器

  • 語法由BNF等形式定義
  • 解析器讀取使用者輸入的待解析的文字,判定其是否與語法匹配,並轉為符合語法的解析樹,交給其他功能做後續處理 – 可用於高度複雜的語法規則

直譯器模式

  • 語法由程式設計師手工定義為一組介面/類及其之間的關係,對語法的解釋(即解析器)由此組的類的內部操作(Interpret())負責
  • 每條語法規則(生產)都要定義相應的類 – 相當於開發一個簡單的解析器 – 使用者使用的時候,呼叫這個類類完成對輸入文字的解釋(這裡的“解釋”,其實相當於 “翻譯”) – 只適用於簡單的語法規則,過於複雜的語法就需要引入大量的類

總結

機器處理的文字語言在電腦科學中無處不在。
語法是描述這種語言的最流行的形式
正規表示式是語法的一個重要子類,可以在不遞迴的情況下表達。

減少錯誤保證安全

  • 語法和正規表示式是字串和流的宣告性規範,可以由庫和工具直接使用。
  • 這些規範通常比手工分析程式碼更簡單,更直接,更不容易出錯。

容易明白

  • 語法以比手寫解析程式碼更易於理解的形式捕獲序列的形狀。
  • 正規表示式,唉,往往不易理解,因為它們是可能是一個更容易理解的正則語法的簡化形式。

準備好改變

  • 語法可以很容易地編輯,但不幸的是,正規表示式很難改變,因為複雜的正規表示式是神祕而難以理解的。

相關文章