摘要:應(yīng)用軟件重構(gòu)技術(shù),需要解決修改代碼卻不影響程序原有功能的問題。文章利用契約式程序設(shè)計(jì)思想解決重構(gòu)的行為保持問題,給出了契約式重構(gòu)(Refactoring by Contract)方法,并應(yīng)用Java語言給出了這種方法的具體實(shí)現(xiàn)過程。
關(guān)鍵詞:重構(gòu);契約;行為保持;屬性;工具
引言
在保證質(zhì)量的前提下,軟件開發(fā)需要更高的速度和效率。隨著代碼量的增長(zhǎng),對(duì)代碼的理解和修改,會(huì)變得越來越困難。在軟件開發(fā)過程中使用設(shè)計(jì)模式可以規(guī)范開發(fā)人員的開發(fā)習(xí)慣,繼承優(yōu)秀的開發(fā)經(jīng)驗(yàn),高效地開發(fā)新系統(tǒng)。重構(gòu)是一種對(duì)代碼的修改技術(shù)。敏捷軟件開發(fā)實(shí)踐提倡“Refactoring to Patterns”,這是目前普遍公認(rèn)的最好的使用設(shè)計(jì)模式的方法。在重構(gòu)過程中最重要的問題是如何保證程序在重構(gòu)前后的功能不變,因?yàn)橐坏┏绦虻墓δ馨l(fā)生了改變,而這種改變又是不可預(yù)期的,那么程序的修改就沒有任何意義了。契約原是經(jīng)濟(jì)領(lǐng)域的一個(gè)概念應(yīng)用在軟件開發(fā)中是指,在軟件修改前,“簽定”一份協(xié)議(契約),規(guī)定程序或函數(shù)、方法所需要的條件,在程序修改后檢查程序的運(yùn)行結(jié)果以確保在修改后函數(shù)或方法的功能仍然未改變。契約式軟件開發(fā)技術(shù)提出至今,已經(jīng)有了一些開發(fā)軟件,可以利用現(xiàn)有的契約式程序開發(fā)軟件作為輔助工具,重構(gòu)技術(shù)的契約化過程就成為了一種可行的、有效的方法。
1 重構(gòu)的概念和作用
1.1 重構(gòu)的定義
效率和可維護(hù)性可能是進(jìn)行重構(gòu)的最重要理由。
重構(gòu)的一般定義:
重構(gòu)(Refactoring,名詞):是對(duì)軟件的內(nèi)部結(jié)構(gòu)所作的一種改變,這種改變?cè)诳捎^察行為(Observable behaviour)不變的條件下使軟件更容易理解,而且修改更廉價(jià)。
重構(gòu)(Refactor,動(dòng)詞):應(yīng)用一系列不改變軟件可觀察行為的操作對(duì)軟件進(jìn)行重新組織(restructure)。
定義中最重要的方面是,不改變軟件系統(tǒng)的可觀察行為,并且軟件結(jié)構(gòu)是朝著更好的設(shè)計(jì)、更容易理解和可重用的方向改變。重構(gòu)提供了一種以更有效和受控方式清理代碼的技術(shù)。Martin Fowler名詞形式的重構(gòu)定義是說重構(gòu)是對(duì)軟件內(nèi)部結(jié)構(gòu)的改變,這種改變的前提是不能改變程序的可觀察的行為,這種改變的目的就是為了讓它更容易理解,更容易被修改。動(dòng)詞形式的定義則突出重構(gòu)是一種對(duì)軟件重構(gòu)的行為,其方法就是應(yīng)用一系列的重構(gòu)操作。
“不能改變程序的可觀察的行為”就是重構(gòu)之前軟件實(shí)現(xiàn)什么功能,之后照樣實(shí)現(xiàn)什么功能。任何用戶,不管是終端用戶還是其他的程序員,都不知道也不必知道某些內(nèi)部的事情所發(fā)生的變化。
1.2 目前重構(gòu)技術(shù)的應(yīng)用的不足
在Opdyke的一這篇經(jīng)典文獻(xiàn)中,提出了重構(gòu)的概念,并分析了程序的特征,探討了使用數(shù)學(xué)方法保證重構(gòu)技術(shù)中行為保持的可行性。但是文章并未給出軟件開發(fā)過程中可以實(shí)際使用的方法或技術(shù),這不能不說是一個(gè)遺憾。在Martin Fowler的著作中,他給出了一份重構(gòu)方法的目錄,總結(jié)了實(shí)際開發(fā)中存在的重構(gòu)技術(shù),但是也未給出重構(gòu)的行為保持方法。在目前的重構(gòu)工具中,還沒有一款軟件能夠保證重構(gòu)的行為不變。為了避開這個(gè)問題,這些軟件大都實(shí)現(xiàn)了比較簡(jiǎn)單的重構(gòu)方法,如rename等。一些比較大型的如Tease Apart Inheritance工具,也不能保證重構(gòu)后軟件行為不變。因此在軟件開發(fā)過程中,當(dāng)需要使用重構(gòu)技術(shù)的時(shí)候,可以僅僅使用某種軟件的方法,而不是復(fù)雜的數(shù)學(xué)上的公式來方便的監(jiān)測(cè)或保證重構(gòu)的行為不影響程序的功能。
2 契約式程序設(shè)計(jì)
Design by Contract(DbC,契約式設(shè)計(jì))是面向?qū)ο筌浖髱烞ertrand Meyer對(duì)軟件構(gòu)造方法的一個(gè)重大貢獻(xiàn),無論是在形式化的數(shù)學(xué)證明中,還是在實(shí)踐運(yùn)用中,它都被證明是大幅改善軟件工程質(zhì)量的有效手段。
每個(gè)軟件都是由若干不同的模塊組成的,軟件的錯(cuò)誤,是指某些模塊沒有正確履行自己的職責(zé)。要徹底杜絕軟件錯(cuò)誤,只有分清各自模塊的責(zé)任,并且建立機(jī)制,保證各模塊正確履行自己的責(zé)任。
如何保證各方恪守職責(zé)呢?Eiffel語言引入了契約(Contract)的概念。契約所核查的,是“為保證正確性所必須滿足的條件”,也就是核查模塊是否正確履行自己的職責(zé)。
契約式設(shè)計(jì)就是在設(shè)計(jì)和編碼階段向面向?qū)ο蟪绦蛑屑尤霐嘌?assertion)。所謂斷言,就是必須為真的假設(shè),只有這些假設(shè)為真,程序才能正確執(zhí)行并可能做到正確無誤。契約式設(shè)計(jì)的主要斷言包括先驗(yàn)條件(precondition)、后驗(yàn)條件(postcondition)以及不變式(invaIiant):
(1)先驗(yàn)條件針對(duì)方法(method),它規(guī)定了在調(diào)用該方法之前必須為真的條件。
(2)后驗(yàn)條件也是針對(duì)方法,它規(guī)定了方法順利執(zhí)行完畢之后必須為真的條件。
(3)不變式針對(duì)整個(gè)類,它規(guī)定了該類任何實(shí)例調(diào)用任何方法時(shí)都必須為真的條件。
目前,直接在語言層面上應(yīng)用了契約式設(shè)計(jì)思想的程序設(shè)計(jì)語言只有Eiffel,但對(duì)于Java和c++,則可以通過添加工具的支持,滿足契約式設(shè)計(jì)的要求。Java語言的契約式設(shè)計(jì)的支持工具主要有iContract、Jass、Jcontract、JMSAssert等。
3 契約式重構(gòu)
3.1 契約對(duì)重構(gòu)中行為保持的作用
契約式設(shè)計(jì)強(qiáng)制程序模塊在修改和使用之前必須提供保證正確性所必須滿足的條件。利用這一思想可以在重構(gòu)過程中保持程序的“行為”不發(fā)生變化,從而實(shí)現(xiàn)安全性重構(gòu)。
在對(duì)一段程序進(jìn)行重構(gòu)之前,要先明確這段代碼的作用及其與程序其他部分的聯(lián)系,確定重構(gòu)的目標(biāo)。在要重構(gòu)的代碼前加上契約,規(guī)定類、方法、變量的行為要求,保證程序所接收的是正確的值,并在代碼后加上契約,保證重構(gòu)后程序模塊的功能是正確無誤的。這樣做的目的是:一方面建立重構(gòu)前后良好的程序文擋,利于檢查和修改。另一方面,確保重構(gòu)之后得到合法的程序,使重構(gòu)之后的程序的行為方式與重構(gòu)之前等價(jià)。
在理解上,契約可以從某種程度上被視為一種“程序代碼”,在代碼本身被修改時(shí),契約也應(yīng)當(dāng)做出相應(yīng)的修改。在重構(gòu)一段代碼時(shí),首先可以抽象出變化的模型,這個(gè)模型除了可以用UML表示外,也可以是以契約的形式來反映我們所要的改變和結(jié)果,把契約寫成代碼進(jìn)行安全性重構(gòu)。另外注意:在類內(nèi)引入—個(gè)新的方法或變量時(shí),也應(yīng)當(dāng)使它們滿足相應(yīng)的類的契約。
3.2 契約式重構(gòu)的應(yīng)用方法
每一個(gè)重構(gòu)方法包括以下的幾個(gè)部分:
(1)名稱。
(2)簡(jiǎn)要。簡(jiǎn)單介紹此重構(gòu)方法的適用情景及它所做的事情。
(3)動(dòng)機(jī)。介紹為什么需要這個(gè)重構(gòu)和什么情況下不該使用這個(gè)重構(gòu)。
(4)作法。簡(jiǎn)明介紹如何進(jìn)行此重構(gòu)。
(5)范例。
為了保證重構(gòu)過程中“不改變代碼的外在的行為”,在重構(gòu)方法中,添加契約式重構(gòu)的部分:
(1)回答問題以確定重構(gòu)的契約:
a.這段代碼的作用是什么?
b.這段代碼中需要被重構(gòu)的屬性有哪些?改變這些屬性預(yù)期會(huì)出現(xiàn)哪些結(jié)果?
①正常的改變對(duì)這些屬性的影響,如添加訪問限制、更改名稱、修改引用等。
②非正常的改變對(duì)這些屬性的影響,如對(duì)不存在的情況或事件的訪問執(zhí)行等。
c.源代碼中要重構(gòu)的代碼段與其它部分之間的關(guān)系,及影響結(jié)果。
d.查找并確定要重構(gòu)代碼中基本查詢和派生查詢。
e.根據(jù)代碼的功能及可能產(chǎn)生的結(jié)果確定契約。
(2)執(zhí)行重構(gòu)方法。
(3)重構(gòu)結(jié)束,運(yùn)行測(cè)試并使用契約支持工具檢測(cè)重構(gòu)的行為保持。
3.3 契約式重構(gòu)實(shí)例
根據(jù)以上的研究,結(jié)合一個(gè)Java程序重構(gòu)的例子來說明契約式重構(gòu)的具體實(shí)現(xiàn)方法:
這是—個(gè)利用順序表實(shí)現(xiàn)的城市數(shù)據(jù)庫,每條數(shù)據(jù)庫記錄包括城市名和城市坐標(biāo),數(shù)據(jù)庫允許插入記錄、按照名字或者坐標(biāo)刪除或檢索記錄,還支持打印在指定結(jié)點(diǎn)y給定距離內(nèi)的所有記錄。
首先需要區(qū)分開命令和查詢。查詢返回一個(gè)結(jié)果,但不改變對(duì)象的外在性質(zhì);命令改變對(duì)象的狀態(tài),但不返回結(jié)果。在這里size( )、valueof( )是查詢,remove( )、insert( )是命令。接著將基本查詢同派生查詢區(qū)分開,派生查詢可以用基本查詢來定義。對(duì)于每一個(gè)命令都寫一個(gè)后驗(yàn)條件,規(guī)定每個(gè)基本查詢的值。
在重構(gòu)的過程中,注意到我們可以利用Extract Class提取—個(gè)順序表類ABlist,而保留源代碼不變,因此不必添加契約。接下來注意到變量DefaultSize、msize、numlnlist都屬于類ABlist,先用Move Field將他們復(fù)制過去,不更改源代碼中的相應(yīng)變量。
下一步將ABlist中變量的作用域更改為public,接著更改源代碼中相應(yīng)的引用,使它們引用ABlist中的變量。這些變量不必添加契約,但它們必須滿足新類中的契約。
然后用Move Method將size( )、valueof( )、insert( )、remove( )逐個(gè)的搬移到Ablist中,添加契約。在這里為了節(jié)省篇幅僅詳細(xì)解釋insert()的契約:首先,添加的結(jié)果是要在順序表里加入一個(gè)新的記錄,需要被修改的屬性有size( )、numlnlist,改變的結(jié)果會(huì)使numlnlist和size()都增加了一個(gè)相應(yīng)量。源代碼中相應(yīng)引用部分更改為新的類方法。確定契約為:前置條件(@pre)為numlnlist
4 結(jié)束語
軟件從往往帶有某種缺陷:或者有bug,或者需要進(jìn)行擴(kuò)展。運(yùn)用重構(gòu)技術(shù)能夠很好地修改代碼,它具有良好的面向?qū)ο蟮脑O(shè)計(jì)優(yōu)點(diǎn),滿足設(shè)計(jì)原則。契約式重構(gòu)通過在模塊中加入契約,確保了重構(gòu)過程中軟件行為保持不變。