引言
IBM Rational Team Concert(RTC)是 IBM Rational 面向軟件交付技術的下一代協作平台-Jazz 平台上的軟件開發環境,它通過集成工作項追蹤、源代碼控制和可配置的流程管理來實現敏捷開發。其中流程管理是其區別於一般版本管理工具的一個重要功能,它更注重於將對代碼的管理融入到整個代碼的開發周期和團隊協作當中去。
本文基於 RTC 定制了一套代碼評審流程。該流程能夠幫助 Moderator 管理評審任務,分配評審任務給多個 Reviewer,以及追蹤代碼評審中發現的問題。這套評審流程適合在項目中管理評審過程,提高代碼評審質量。
另外,Rational Team Concert(RTC) 為開發人員提供了便捷且易擴展的 Java API, 本文結合定制的代碼評審流程對 RTC 進行擴展,開發了基於多種代碼庫(CVS 和 RTC)的代碼評審工具,用來實現自動檢測並生成代碼變更集,將其與對應的工作項關聯,然後提交評審的任務,從而大大簡化用戶操作,同時提高了開發人員的協同工作效率。這個工具基於 Eclipse 平台和 RTC 的 Plain Java API 進行開發。
場景介紹
代碼評審是項目開發過程中十分重要的步驟,優秀的代碼評審過程可以極大的提高項目代碼的質量,在代碼提交之前檢查出代碼的缺陷,從而減少開發測試的代價。然而對現有的復雜項目進行代碼評審面臨著很多挑戰。
對於一些已存在的項目,大多將項目代碼存放在 CVS、SVN 等版本管理工具中,無法在 RTC 中形成代碼對比,也無法與代碼評審項相關聯;
由於大多數項目本身十分復雜,各個模塊的開發團隊都有自己的 Leader,對於一個功能點或算法的修改往往需要各個 team leader 的參與,或指定某些技術專家參與,RTC 本身自帶的單選下拉框無法滿足多個評審人的場景;
由於很多團隊是跨地區或時區的,每次都專門在代碼評審會上進行代碼評審是不現實的,可以將不同地方的團隊成員加入到代碼評審項中,並借助於 RTC 自身的郵件提醒功能使團隊成員可以實時得到代碼評審的通知,另一方面可以根據代碼評審項中評審類型來決定是否進行代碼評審會議;
現有的代碼評審流程大多缺乏對評審出的問題進行記錄、狀態追蹤和關閉的功能,無法生成評審進度及代碼質量度量報告給項目經理和質量管理人員,以供其對項目質量進行全面管理。
RTC 有強大、靈活的過程定制能力,開發團隊可以根據團隊自身的特性定制自己的全新開發流程,也可以對已有開發流程進行必要的修改。本文作者根據團隊業務開發需要的特殊性自定義了普適的代碼評審流程,與 RTC 默認的流程管理流程不同,定制的代碼評審流程更加適用於團隊,尤其是跨國團隊的代碼評審流程,同時借助於 RTC 的實時協作功能大大的降低團隊出錯機率,使不同角色有在一個實時工作環境裡相互協作,從而保證交付高質量的代碼。該評審流程如圖 1 所示。
從流程圖可以看出,整個評審的過程需要三個角色的參與,包括(TA)開發人員、(Moderator)中介者、(Reviewer)評審人。具體的代碼評審過程如下:
開發人員針對某 PTR 或者 PCR 進行代碼修改之後,借助於代碼評審工具將該 PTR 或 PCR 相關的代碼抽取為一個代碼變更集(ChangeSet), 同時創建一個代碼評審任務項(review task),將該代碼變更集作為任務項的子域與其綁定在一起遞交給中介者(Moderator),與此同時,該中介者將會收到一封提醒郵件來處理這個代碼評審任務項;
Moderator 根據 PTR 或 PCR 的性質、所屬模塊等的不同將該任務項分配給不同的代碼評審人,這些評審人同樣會收到郵件提醒去處理這個任務項;
代碼評審人打開任務項,並對其附帶的代碼變更集進行評審,在評審過程中,如果評審人對代碼變更集需要提出相關建議,可以創建問題任務項並將其作為子域附加在代碼評審任務項上;
開發人員收到問題任務項發出的提醒郵件,並根據評審人提出的建議進行代碼修改,然後借助於代碼評審工具將再次修改的代碼抽取為一個代碼變更集將其與問題相關的代碼評審任務項關聯,從而實現代碼質量的不斷提升;
如果與當前代碼任務項關聯的問題都已經解決,並且所有的評審人都同意該代碼變更,中介者會負責將該代碼評審任務項關閉;
由上面的描述可以看出,評審過程共涉及到兩個自定義任務項:代碼評審任務項(Review Task)和問題任務項(Issue Task)。可以通過接下來的步驟,在 RTC 中創建評審過程中需要的任務項:
定義一個帶有屬性的新工作項目類型
在 Project Area 目錄 中,選擇 Process Configuration > Project Configuration> Configuration Data> WorkItems> Types and Attributes;
為了創建代碼評審流程需要的新工作項類型,點擊 Add 以打開如圖 3 所示的 Add Type 對話框窗口,這樣您就可以為新工作項類型提供名字和 ID;
選擇工作項需要的編輯器或自定義編輯器,並在選擇的結果界面為該工作項提供可見的圖標;
添加自定義屬性,包括 Moderator、Review Type、PTR\PCR No.等,這裡以 Moderator 為例;
為了添加自定義屬性,您可以在 Attributes 部分點擊 Add 打開如圖 5 所示的對話框窗口;
與其它普通屬性不同,由於中介者屬性的選項由當前項目的組成成員構成,RTC 為此類屬性提供了 Contributor 類型來展示所有的項目成員,所以這裡 Contributor 屬性的 Type 必須選成 Contributor。
自定義屬性創建完成後,將其添加到代碼評審任務項的編輯器中如圖 6;
在工作項編輯器頁面,選擇 Overview > Details,並點擊 Add Presentation 按鈕添加自定義屬性;
在 Edit Presentation 彈出窗口中,選擇基於屬性的演示並將 Moderator 屬性添加進來;
當 Moderator 屬性出現在評審工作項的展示區域時,您就完成了評審工作項自定義屬性的創建過程;
測試定制的評審工作項;
在 Team Artifacts 視圖中,為了測試定制的新工作項類型,您可以打開當前工作項目,並選擇 Create Work Item,圖 10 顯示了彈出的 Create Work Item 界面;
選擇類型名為 Review 的工作項目,選中後點擊 Finish。這將會創建一個新的 Review 工作項目,注意第六行為自定義的 Moderator 屬性
另一個自定義工作項 Issue 的創建過程與 Review 類似,讀者可以通過其包含的屬性來類比創建。
代碼評審工具的實現
RTC 作為項目管理、團隊協作的工具,其流程管理功能大大提高了開發人員的工作效率。但是,目前的與流程管理相關聯的代碼僅支持存放在 RTC 自身的代碼庫中,而很多已有的項目大多將項目代碼存放在 CVS、SVN 等版本管理工具的代碼庫中,而這些項目如果需要結合 RTC 代碼評審流程就需要開發代碼評審工具,本文通過擴展 RTC 實現支持多代碼源的代碼評審流程。
另一方面,通過前面代碼評審流程的描述可以看出,在整個評審過程中開發人員需要很多的手工操作,比如 WorkItem 的創建、代碼變更集的追加等工作。代碼評審工具進一步的節省開發人員在這方面的時間開銷,它在 RTC 的基礎使得代碼評審流程在 Eclipse 中的實現更加自動化,簡化用戶操作,更加方便了開發人員的使用,從而進一步提升工作效率。
通過 Plain Java API 來整合不同代碼庫並自動化創建代碼變更集;
結合 Eclipse 的插件編程技術和 CVS、RTC 等提供的 Plain Java API,可以從不同的代碼庫獲取代碼並生成 RTC 中可以使用的代碼變更集。此處以 CVS 為例。
通過研究 CVS 的實現原理可以發現基於 CVS 的項目根據 CVS 文件夾中的 Entry 文件來判斷當前文件是否被修改過,其格式如下所示:
/name/revision/timestamp[+conflict]/options/tagdate
其中 name 是目錄中文件的名字,revision 是正在編輯的文件派生的修訂版本號,“0”表示新添加的文件,“-”表示刪除的文件。最關鍵的是 timestamp 時間戳字段,它表示 CVS 創建文件的時間;如果時間戳和文件的最後修改時間不一致,意味著文件已經被修改過。通過遍歷解析當前項目文件夾中所有的 Entry 文件可以得到當前項目中的修改文件集合。然後代碼評審工具依據修改文件集合中文件的層次結構,借助 SWT 的 ContainerCheckedTreeViewer 類將已修改文件集合以樹型結構展示給開發人員,相關代碼如下所示:
清單 1.展示代碼變更集的關鍵代碼
public class TreePage extends WizardPage { @Override public void createControl(Composite parent) { Composite container = new Composite(parent, SWT.NONE); //存放樹形結構的容器對象 ContainerCheckedTreeViewer tv = new ContainerCheckedTreeViewer(container,SWT.BORDER|SWT.H_SCROLL); tv.getTree().setLayoutData(new GridData(GridData.FILL_BOTH)); tv.setContentProvider(new FileTreeContentProvider()); tv.setLabelProvider(new FileTreeLabelProvider()); tv.addDoubleClickListener(new IDoubleClickListener() { @Override public void doubleClick(DoubleClickEvent arg0) { IStructuredSelection is = (IStructuredSelection) arg0 .getSelection(); Object obj = is.getFirstElement(); TreeItem[] items = tv.getTree().getSelection(); if (null == items || items.length == 0) { return; } if (items[0].getExpanded()) { items[0].setExpanded(false); } else { tv.expandToLevel(obj, 1); } } }); tv.setAutoExpandLevel(2); GridLayout layout = new GridLayout(); container.setLayout(layout); layout.numColumns = 1; setControl(container); setPageComplete(false); } @Override public void setVisible(boolean visible) { super.setVisible(visible); if (visible) { //在顯示樹形結構時,首先要將構建好的樹形結點數據賦給 TreeViewer 對象,這些樹形結點 //通過結點類型不同(包括 PackageFolder、RootFolder 和 FileObj 等)建立結點之間 //的父子關系 tv.setInput(buildFileTree()); } } }
下圖展示了代碼評審工具中已修改代碼文件集合呈現出的樹狀結構。
通過 CVS Java API 登陸 CVS 服務器,建立同 CVS 代碼庫的連接。然後根據開發人員選擇的需要提交的代碼文件,代碼評審工具從 CVS 中獲取原始代碼信息用於 Java 的應用程序中。本文通過封裝 CVSLoginHandler 類來實現登陸 CVS、執行 CVS 命令等操作;
查看本欄目
public class CVSLoginHandler { //用於與 CVS 服務器建立連接的 CVS Clinet 實例 private Client cvsclient; //用於保存 CVS 連接信息的 CVSRoot 實例 private CVSRoot cvsroot; //用於保存 CVS 連接的 Connection 實例 private Connection connection; //本地項目路徑 private String localPath; //通過 CVS 連接信息構建 CVSRoot 實例 public CVSLoginHandler(String connectionString) { cvsroot = CVSRoot.parse(connectionString); } //建立與 CVS 服務器的連接 public Connection openConnection() throws AuthenticationException, CommandAbortedException { connection = ConnectionFactory.getConnection(cvsroot); connection.open(); return connection; } //關閉與 CVS 服務器的連接 public void closeConnection() throws IOException { connection.close(); } //執行 CVS 命令 public void excute(Command command) throws AuthenticationException, CommandAbortedException, IOException, CommandException { cvsclient = new Client(connection, new StandardAdminHandler()); cvsclient.setLocalPath(localPath); globalOptions = new GlobalOptions(); cvsclient.getEventManager().addCVSListener(new BasicListener()); cvsclient.executeCommand(command, globalOptions); } //生成檢出某模塊的 CVS 指令 public Command checkout(String modulename) { CheckoutCommand command = new CheckoutCommand(); command.setModule(modulename); command.setRecursive(true); return command; } }
通過 RTC 提供的 Plain Java API,可以登陸到 RTC 的服務器端,建立 RTC 連接;
清單 3.建立與 RTC 的連接
public ITeamRepository login(IProgressMonitor monitor)throws TeamRepositoryException { ITeamRepository repository = TeamPlatform.getTeamRepositoryService() .getTeamRepository(Constant.RTC_REPOSITORY_ADDRESS); repository.registerLoginHandler(new ITeamRepository.ILoginHandler() { public ILoginInfo challenge(ITeamRepository repository) { return new ILoginInfo() { public String getUserId() { return Constant.USERID; } public String getPassword() { return Constant.PASSWORD; } }; } }); monitor.subTask("Contacting " + repository.getRepositoryURI() + "..."); repository.login(monitor); monitor.subTask("Connected"); return repository; }
在 login 方法中,將 RTC 的 URI 信息傳給 getTeamRepository 方法來獲取保存 RTC 連接信息的 ITeamRepository 對象,然後通過該對象的 login 方法來建立 RTC 連接。
由於本文事例中的源代碼存放在 CVS 上,這裡需要向 RTC 進行兩次代碼提交,通過比較 CVS 上代碼與本地代碼的差異,借助 RTC Java API 生成在 RTC 中可以識別的代碼變更集。首先要找到存放變更集的項目空間;
清單 4.獲取 RTC 工作空間的連接
//根據保存 RTC 連接信息的 ITeamRepository 對象生成工作空間管理器對象 IWorkspaceManager wm = SCMPlatform.getWorkspaceManager(repo); //將工作空間名稱作為作為參數傳給工作空間管理器的 //findWorkspaces 方法來得到 WorkspaceHandle 對象 IWorkspaceHandle wsHandle = wm.findWorkspaces( IWorkspaceSearchCriteria.FACTORY.newInstance().setExactName( Constant.repositoryWorkspaceName), 1,null).get(0); //將 WorkspaceHandle 對象作為參數傳給工作空間管理器的 getWorkspaceConnection 方法來得到負責 //項目空間連接的 IWorkspaceConnection 實例 IWorkspaceConnection workspace = wm.getWorkspaceConnection(wsHandle, null);
得到當前項目的工作空間對象後,需要將原代碼文件 Check In 並 Deliver 到 RTC 中,然後將更改後的代碼文件 Check In 到代碼評審專用的 Stream 中,從而生成變更集對象。
清單 5.生成代碼變更集
IChangeSetHandle cs2 = workspace.createChangeSet(component, monitor); IConfiguration iconfig = (IConfiguration) workspace.configuration(component); IFileItem file = (IFileItem) IFileItem.ITEM_TYPE.createItem(); file.setContent(storedContent); workspace.commit(cs2, Collections.singletonList(\ workspace.configurationOpFactory().save(file)), monitor);
自動化創建代碼評審工作項;
為了簡化開發人員的操作步驟,代碼評審工具借助於 Eclipse 插件技術和 RTC 提供的 Java API,將代碼評審工作項的創建以表單的形式呈現給開發人員,並根據接收到的相關參數自動生成 WorkItem 對象。
用來生成評審工作項的表單如圖 13 所示。
由上圖可以看出自動生成的 WorkItem 完全根據代碼評審流程中自定義的代碼評審工作項來設定,這些輸入的參數信息會以 ReviewBean 對象的形式傳遞給 WorkItem 的生成方法。
IWorkItemClient service = (IWorkItemClient) repo.getClientLibrary(IWorkItemClient.class); IWorkItemType workItemType = service.findWorkItemType(projectArea,"Review", monitor); IWorkItemHandle handle = service.getWorkItemWorkingCopyManager().connectNew(workItemType, monitor); WorkItemWorkingCopy wc = service.getWorkItemWorkingCopyManager().getWorkingCopy(handle); IWorkItem workItem = wc.getWorkItem(); IContributorHandle contributor = fetchOwner(repo,bean); IWorkItemClient workItemClient = (IWorkItemClient) repo.getClientLibrary(IWorkItemClient.class); IAttribute moderator = workItemClient.findAttribute(projectArea,"Moderator", monitor); workItem.setValue(moderator, contributor);
首先通過 ITeamReposity 對象得到控制 WorkItem 生成過程的 IWorkItemClient 對象,通過調用其 finkWorkItemType 方法找到前面自定義的 Review 類型,並生成該類型的一個 IWorkItem 對象 workItem。同時為該對象設置代碼評審工作項需要的相關屬性,此處以 Moderator 為例,根據開發人員選擇的 Moderator 人員找到其對應的 IContributorHandle 對象,然後將其賦給 workItem 對象。
構建 WorkItem 對象的最後一步,就是將第一階段生成的代碼變更集與代碼評審工作項相關聯,由於代碼變更集並不是 WorkItem 的一個屬性項,所以需要通過調用 IFileSystemWorkItemManager 對象的 createLink 方法將二者關聯起來。
清單 7.建立代碼評審工作項與代碼變更集的關聯
IFileSystemWorkItemManager linkManager = (IFileSystemWorkItemManager) repo .getClientLibrary(IFileSystemWorkItemManager.class); List<ILink> link = linkManager.createLink(wsHandle, cs2,new IWorkItemHandle[] { handle }, monitor);
查詢代碼評審工作項並提交更新的代碼變更集
在代碼評審過程中,如果評審人對代碼修改有問題或建議提出,可以通過創建問題工作項並將其關聯到代碼評審工作項中來實現。開發人員根據相關建議對代碼再進行精練,這樣可以進一步保證代碼的質量。其過程的序列圖如圖 14 所示。
開發人員在問題工作項的基礎上修改代碼後,需要將新的代碼變更集上傳到 RTC 中,這時代碼評審工具可以幫助開發人員快速定位到要更新的代碼評審工作項,代碼工作項查詢客戶端界面如圖 15 所示。
為了提供易用的查找框並展示簡單明了的查詢結果,這裡通過 Dialog 與 Text、TableViewer 結合的方式設計交互界面,下面重點介紹代碼工作項的實時查詢模塊的設計。清單 8 展示了客戶端界面代碼。
txReviewId = new Text(content, SWT.SINGLE | SWT.BORDER); txReviewId.addModifyListener(new ModifyListener() { public void modifyText(ModifyEvent e) { executeFilter(); } }); reviewItemViewer = new TableViewer(content, (multi ? SWT.MULTI : SWT.SINGLE) | SWT.BORDER | SWT.V_SCROLL | SWT.VIRTUAL); reviewItemViewer.setContentProvider(contentProvider); reviewItemViewer.setLabelProvider(itemsListLabelProvider);
這裡為 Review Id 輸入框綁定了監聽器,當用戶敲入想要查找的代碼評審工作項的 ID 時,代碼評審工具會自動觸發查詢動作。而 TableViewer 作為查詢結果的展示區域,擁有兩個關鍵的參數:ContentProvider 和 LabelProvider。ContentProvider 負責管理查找結果的內容,LabelProvider 定義查找結果的顯示效果。
接著,當用戶輸入 Review Id 時,會觸發監聽器從而開始查詢的動作。監聽器的核心方法是 executeFilter,代碼見清單 9。
清單 9.執行查詢的 executeFilter 方法
protected void executeFilter() { ReviewItemsFilter newFilter = new ReviewItemsFilter(txReviewId.getText()); if (filter != null && filter.equalsFilter(newFilter)) { return; } filterJob.cancel(); this.filter = newFilter; if (this.filter != null) { filterJob.schedule(); } }
查看本欄目
實際執行查詢過程的類是 FilterJob,它作為 Job 的子類,通過對 Java 中的 Thread 進行封裝,用於處理多線程的情況。當 FilterJob 的對象調用 schedule 方法時,該 Job 就會被 JobManager 放入等待隊列中並通知線程池有新的 Job 需要處理,線程池就會分配空閒線程執行該 Job。
在 Job 中調用 SearchEngine 對象查詢給定 ID 的代碼評審工作項。
清單 10.借助 RTC API 查詢工作項的 SearchEngine 類
public class ReviewItemSearchEngine { private ReviewItemsFilter filter; private ReviewItemSearchRequestor requestor; private RTCClientUtil rtcClient; public ReviewItemSearchEngine(ReviewItemSearchRequestor requestor, \ RTCClientUtil rtcClient) { this.requestor = requestor; filter = this.requestor.getPluginSearchFilter(); this.rtcClient = rtcClient; } public void search() throws OpenQueryReviewByIdException { if(null == rtcClient.getRepo() || !rtcClient.getRepo().loggedIn()) { rtcClient.LogInRepo(); } IWorkItem workitem = rtcClient.fetchReviewById(filter.getPattern()); if(null != workitem) { requestor.add(new ReviewMatch(workitem.getHTMLSummary().getPlainText(), \ filter.getPattern())); } } }
總結
本文在 Rational Team Concert 的基礎上定制代碼評審流程,包括代碼評審工作項等 Work Item 類型,並借助 Eclipse 插件開發技術對 Rational Team Concert 進行擴展,使得其支持 CVS、SVN 等版本管理工具,形成基於這些代碼庫中的代碼變更並綁定到定制的代碼評審工作項上,從而方便評審人對代碼變更進行評審。這樣不僅大大減少開發人員在代碼評審階段需要花費的時間,而且提高了交付代碼的質量。