跳轉到內容

如何編寫程式/收集真實經驗

來自華夏公益教科書,開放世界的開放書籍
(重定向自 收集真實經驗)

為什麼是這本書

[編輯 | 編輯原始碼]

良好的實踐和生活經驗從未被記錄在書中。大多數計算機科學書籍都是 API 演練。這本書是不同的!

構建指令碼

[編輯 | 編輯原始碼]

嘗試設定 "一鍵測試"。這使得它更加方便地輸入一些內容,然後按下該按鈕,該按鈕會

  • 儲存您剛剛編輯的檔案,
  • 使用所有適當的選項(如果需要,例如“-fno-emit-frame”)編譯應用程式,以及
  • 執行一些快速測試,以確保您的“改進”不會意外地破壞其他內容。

花費一個小時來編寫一些測試並設定一鍵測試可能*看起來*比它值得的麻煩更大。手動編譯檔案,並手動執行應用程式的各個部分以確保它們正常工作,可能需要不到一個小時的時間。但是相信我,大多數程式您將要編輯、編譯和測試很多很多次。從現在起一年後,當您只做了一個小小的改動時,您難道不希望按下按鈕就完成了嗎?而不是

  • 手動編譯檔案
  • 手動執行應用程式並發現它突然無法工作
  • 拔掉你的頭髮,直到
  • 幾個小時後,您想起您需要包含“-fno-emit-frame”
  • 手動重新編譯檔案,這次使用“-fno-emit-frame”
  • 從頭開始進行測試。

有很多方法可以設定一個 自動構建系統

一鍵測試只是某些程式設計師推薦的持續整合的一部分。

即使律師也能看到自動化構建指令碼的優勢。 [1]

一般指令碼

[編輯 | 編輯原始碼]

這不應該只適用於構建指令碼。如果您發現自己重複了幾行程式碼,請將其放入指令碼或批處理檔案中。

如果您是 Windows 程式設計師,學習使用 Cygwin[2] bash 指令碼通常是值得的。它們比 Windows 批處理檔案靈活得多。還可以研究 AutoHotKey[3]

就此而言,不要害怕構建自己的工具!您是一位程式設計師,如果您正在處理大量元資料,請製作一個元資料編輯器。如果您的構建檔案有太多選項難以記憶,而且這些選項經常發生變化,請為它編寫一個 GUI。您跳過的每一步都是您不會搞砸的每一步——而且一個單一的搞砸可能會花費您與構建指令碼或工具相同的時間。

指令碼和工具的另一個原因是知識保留/交流。您可以告訴同事如何構建 4 次,直到他記住了每個步驟,或者您可以將您使用的指令碼交給他。Ftorkou besaha

資料庫訪問

[編輯 | 編輯原始碼]

併發問題

[編輯 | 編輯原始碼]

不要使用表鎖定!它有缺陷!使用事務,在 JDBC 中設定連線的隔離級別。

錯誤處理

[編輯 | 編輯原始碼]

始終記住使用 finally 塊來關閉資源!否則丟擲異常可能會導致許多開啟的連線

小心空錯誤處理程式......錯誤處理程式永遠不應該為空,但這在 Java 中很難強制執行,因為存在已檢查的異常。例如,Java 的 sleep 方法丟擲一個已檢查的異常,因此您必須捕獲它。很少有人真正使用它並且不在乎,因此他們用一個空處理程式包裝它。正因為如此,已檢查的異常被認為是一種反模式或僅僅是錯誤的。

當堆疊跟蹤被隱藏,因為有人認為它們“不重要”或很亂,或者其他人捕獲了範圍過廣的異常並隱藏了本來可以使您的錯誤修復變得輕而易舉的跟蹤時,跟蹤錯誤非常困難。

錯誤跟蹤

[編輯 | 編輯原始碼]

每當您輸出任何內容作為錯誤訊息時,請在前面加上例如 new Date() 或類似內容,以便您以後能夠跟蹤回溯。將模組名稱新增到錯誤訊息中也很有用。嘗試將錯誤處理構建為測試能力的一部分。不要僅僅依賴偵錯程式來查詢程式碼錯誤。

您的時間花在設定 Log4J 等日誌框架上是值得的。請注意,某些日誌軟體配置不適合生產程式碼,因為日誌功能會導致效率損失。對各種設定組合進行一些迭代測試,以瞭解速度損失,然後決定使用最佳組合。

許多程式設計師在估計向程式新增某個功能需要多長時間方面很糟糕。練習,練習,再練習。即使沒有人問您需要多長時間,也要記錄您開始新增該功能的時間、您估計完成的時間以及您實際完成的時間。即使您完全沒有頭緒,至少也要記下您開始的時間和您停止的時間。每次,您都會學到一些關於完成這類任務需要多長時間的新知識。

註釋!

[編輯 | 編輯原始碼]

令人驚歎的是,有多少程式設計師就是不寫註釋,無論多少種不同的 編碼標準 建議(甚至強制要求)程式碼要新增適當的註釋。

註釋對於軟體專案的維護階段至關重要。任何曾經被要求分析、修改或維護其他人程式碼的人都會證明,如果沒有註釋,這項工作比在已正確註釋的程式碼上完成相同任務需要更多精力、時間和金錢。不要忘記,你可能在未來的某個(更晚的)時間點自己承擔這項任務。

不要過度註釋和不要註釋不足同樣重要。在每一行程式碼的末尾添加註釋是完全沒有必要的。相反,假設讀者對該語言有一定的熟悉程度,並將註釋限制在描述程式碼塊的功能。

無論何時你在檢視程式碼(即使是自己的程式碼),如果你對程式碼有一點不理解,就需要添加註釋。如果你對程式碼很不理解,則需要重構。

你可能會發現,在完成方法的編碼後添加註釋會更舒服——回到開頭,逐個步驟瀏覽。這也會強制對程式碼進行一些審查。在編碼之前添加註釋也是一個好主意——首先在註釋中建立一個基本流程,然後在註釋之間插入程式碼。只是不要忘記在你完成後檢查一下你的註釋,我經常發現當我完成時,我最初的註釋中只剩下很少一部分了。

另一個重要的概念是“自文件程式碼”。這意味著你使用函式、變數、類和方法的名稱來傳達程式碼的思想,這樣你就不用寫太多註釋。如果你用它執行的操作來命名一個方法,那麼讀者就會很清楚它將要做什麼;例如,使用名為 determineIfStackIsEmpty() 的方法,而不是隻做像 if( size == 0 ) 這樣的比較。

不要忽視程式碼模組中的檔案頭。這些註釋對於提供程式碼操作的摘要以及作者和許可資訊非常重要。大多數編輯器程式在螢幕頂部以檔案的第一行開啟程式碼模組;使檔案的頂部部分包含一個塊註釋,該註釋指定該模組的功能,這在維護期間非常有用。

註釋不是一項難以掌握的學科;實際上,建立一個包含多種語言標準註釋形式的模板集非常容易,然後在開始一個新模組時複製模板,新增程式碼和相應的註釋細節。一些整合開發環境工具,如 EclipseX-Code,可以在建立新原始檔時自動配置為執行此操作。

最後,在中型到大型的專案中,使用自動文件工具(如 DoxygenJavaDocsAutodoc)為專案中的模組建立超連結文件可能會有所幫助。這為專案的所有成員提供了相同的標準文件,以便每個人都能瞭解專案中所有程式碼模組的可用性和操作細節。

要獲得一些可用程式碼文件的良好示例,請檢視庫中的 Java 原始碼。每個類都有一個註釋塊,顯示一般的類使用情況,每個外部方法都有一個註釋塊,描述使用情況和注意事項,每個非平凡方法都有內聯(非 javadoc)註釋,描述程式碼塊(通常是每一行)正在發生的事情。總體而言,每行程式碼可能會有 5-10 行註釋,但方法註釋比需要更詳細,除非你正在為公共使用構建庫。

(當你閱讀 Sun 的原始碼時,你還會注意到大多數方法只有 1-3 行。良好的 OO 設計看起來像“魔術”——你認為一定有一個巨大的例程在某個地方進行所有“真正的工作”,但你從未見過它,它只是這些只有 1-3 行的例程一直到中心。)

如果你因為輸入註釋而減慢速度(即使是每行程式碼 3 行註釋),那麼你要麼急需參加打字課,要麼就是打字速度比思考速度快! 請花時間處理你的程式碼——不要只是草草地寫完,然後檢查,再繼續下一個。在你重構程式碼的第一遍時,審查並重寫註釋。你可能認為你透過儘快輸入程式碼更改和錯誤修復來完成老闆的要求,但實際上你是在(根據我的經驗)讓整個團隊因極高的錯誤數量和可維護性問題而被解僱。

配置管理

[編輯 | 編輯原始碼]

配置管理,也稱為“CM”(有時也稱為“SCM”代表“軟體 CM”),是一個被嚴重誤解的話題。儘管最近態度發生了一些變化,但普遍的看法似乎是,CM 是一種“必要的邪惡”,應該容忍但不要積極參與,至少不應超過絕對必要。

當構建計算機軟體時,變化是不可避免的,並且變化會增加正在該專案上工作的開發人員之間的混亂程度。當修改在執行之前沒有經過分析、在進行之前沒有寫下來、沒有以提高質量和減少錯誤的方式進行控制或沒有適當地報告給應該瞭解它們的人員時,就會產生混亂。

需要更改的原因有很多。首先,客戶可能會提出一個或多個新的要求,或者可能提出更改現有要求的請求。這可能是由設計審查、應用程式的重新設計,甚至時間安排和預算限制驅動的。其次,應用程式的業務條件或市場變化可能會導致需要更改。第三,業務環境可能會增長或發生變化,這可能會改變專案優先順序或客戶或供應商工程團隊的結構。需要記住的是,隨著時間的推移,所有利益相關者都會更深入地瞭解系統;這種知識的增長是驅動大多數對軟體請求/要求的修改的根本原因。因此,這是一個事實,許多軟體工程師和專案經理難以接受,即大多數對軟體的更改都是合理的

配置管理處理軟體工程專案的多分支功能。CM 是一組活動,這些活動已被開發出來以幫助管理在軟體專案生命週期中發生的更改。在一個典型的專案中,可交付系統包含許多不同的檔案和目錄,並且它們之間可能會發展出許多複雜的關係。由於在開發過程中需要頻繁修改,這個問題變得更加嚴重。CM 不善的結果包括

  • 多次查詢/修復同一個錯誤(版本之間不一致)
  • 文件和程式碼之間不一致
  • 文件丟失
  • 程式碼丟失

在軟體系統中,配置包括與系統相關的所有“工作產品”。這意味著不僅是實際交付給客戶的系統,還包括開發人員維護的文件和支援程式碼。配置還可能包括針對不同平臺的不同版本的可執行檔案,並且可能具有與分配模組給程式設計團隊成員相關的缺陷和任務。

為了理解與 CM 相關的問題,必須掌握作為典型軟體專案一部分的龐大物件數量。即使是一個小型系統也可能包含超過一百個檔案。對於規模合理的系統,這個數字可能會增長到數千甚至數萬。與 CM 相關的問題可能與現代圖書館面臨的挑戰相似。如果沒有仔細的庫存控制,圖書館中的許多書籍就會被放錯位置、放錯架子、被盜,甚至丟失。

另一件需要記住的事情是,一個有效的 CM 計劃是任何聲稱堅持能力成熟度模型® 整合 (CMMI) 原則的軟體開發工作的一部分。這個系統由卡內基梅隆大學的軟體工程研究所 (SEI) 建立,為軟體開發提供了一種流程改進方法。它有 5 個級別,前三個級別都包含某種形式的已記錄的 CM 流程。

CVS/Subversion

[編輯 | 編輯原始碼]

Subversion (SVN) 在功能上似乎已經超過了 CVS(參見 TortoiseSVN)。但是,CVS 擁有一個相當不錯的介面 WinCVS。

最佳化

[編輯 | 編輯原始碼]

你可能聽說過“過早最佳化是萬惡之源”。這是真的。我建議你遵循以下準則。

  • 始終先編寫未最佳化的程式碼。
  • 如果您的應用程式執行緩慢,請執行分析器以找出具體位置。
  • 重寫指示的程式碼以實現更最佳化的效能,但將舊程式碼保留在註釋中。
  • 重新測試,如果應用程式沒有明顯變快,則刪除最佳化。

注意,這不是對無知的藉口!作為一名程式設計師,您必須理解基本概念,例如,如果您正在執行插入排序,請選擇連結串列而不是陣列!這不是最佳化,而是程式設計!

請參閱 最佳化程式碼以提高速度,瞭解許多使程式執行更快的技巧。

避免程式碼重複

[編輯 | 編輯原始碼]

我始終堅持的第一條規則是永遠不要重複程式碼。即使只有一兩行相似程式碼也會導致問題。永遠不要複製貼上。完全沒有冗餘的程式碼集稱為“完全分解”。完全分解的程式碼比過度使用複製貼上的程式碼更容易處理得多。

這也稱為“DRY”原則:不要重複自己。

我甚至會說,您可以根據避免重複程式碼的難易程度來區分語言。

識別重複程式碼

[編輯 | 編輯原始碼]

複製貼上者通常會複製一個程式碼塊並多次貼上它,然後逐一更改幾個常量以適應新程式碼塊的需要。這些很容易識別,只需掃描程式碼中重複的行長模式即可。

從重複的程式碼塊中提取唯一資料

[編輯 | 編輯原始碼]

解決方案通常是將常量提取到陣列中,刪除除一個之外的所有副本,然後遍歷剩餘的程式碼副本。

這通常會導致程式碼縮減的其他機會。例如,當代碼中變化的“常量”是函式/方法呼叫(在每組重複程式碼的相同位置使用不同的方法名稱)時,您可能會發現被呼叫方法中存在大量冗餘。這也常常會導致建立新的物件和以前沒有的可重用資料結構。好的最佳化通常會啟動一系列重構,您可能會發現自己刪除了如此多的程式碼以至於讓人感到尷尬。

某些語言結構似乎只是吸引程式設計師進行程式碼複製。例如,設定 GUI 元件可能會非常重複。這是尋找程式碼縮減機會的好地方。Java 要求每個 MenuItem 物件都有一個類定義。您通常會看到它被實現為一屏程式碼,除了建立 MenuItem 物件之外什麼也不做。透過定義一組資料並編寫一個小型例程來建立所有 MenuItem 類,可以更容易地做到這一點(實際上更容易得多)。在這一點上,您會發現,像為每個選單項新增按鈕這樣的操作,在重構之前可能很可怕,但現在變得微不足道了。

完全分解程式碼通常需要設定一些資料並遍歷它。在 Java 中,在程式碼中設定一個數據陣列非常簡潔。稍後,陣列資料可以非常容易地外部化,比最初的 C&P 程式碼更容易。要在 Java 中設定陣列,請習慣使用這種簡單的語法(其他語言應該有類似的結構)

String[] data = new String[] {"Opt1", "Opt2", "Opt3"};

字串很常見,但您也可以使用 int 或 long 陣列 - 語法相同。我經常使用 Object 陣列將整數與字串配對:new Object[] {"One", 1, "Two", 2}...(這將在 Java 5 中有效,在 Java 5 之前,您可能更適合使用兩個陣列。這些陣列的技巧是保持語法簡單而簡潔。

在您的資料中通常也需要控制訊號。不要害怕在程式碼中包含所需的訊號。例如,如果您要自動生成一組選單,您需要識別哪些選單是頂級選單,哪些是子項。這樣的列表應該包含您需要的所有資料

"^", "File", "Load", "Save", "^", "Edit", "Copy", "Paste"

這些資料可以被輸入一個簡單的迴圈(最多幾行)以建立您的整個選單結構。

下一步,建立一個新的類!

[編輯 | 編輯原始碼]

從重構中建立的這些資料結構通常最終會包含資料對或三元組——或者更糟糕的情況。只要您有想要組合在一起的資料對或集合,您就應該定義一個自定義物件來儲存它們。我知道這聽起來很極端,但試試看。您會突然發現自己一直都需要那個物件。您應該很快發現自己將曾經是靜態實用程式方法(程式碼異味)的程式碼轉移到您的新類中,並且它將具有完美的程式碼異味。

您可以建立一個字串或物件陣列並遍歷它來建立您的自定義物件,但更好的方法是在陣列中實際建立您的物件

以下是一個我在過去使用過的有點棘手的例子

  MyClass[] primaryData=new MyClass[] {
    new MyClass("File", top),
    new MyClass("Save", "Save the file", "saveFunc"),
    new MyClass("Load", "Load the file", "loadFunc"),
    new MyClass("Edit", top),
    new MyClass("Copy", "Copy the selection", "copyFunc", "isTextSelectedFunc"),
  };

這使用了一些技巧。首先,有多個建構函式。最上面的是 (string, int),因此它與眾不同,系統知道由此建立一個新的頂級選單項。之後新增的所有 (沒有 int “top”) 都將成為該選單的成員。其餘的建構函式接受字串——一個字串建構函式接受 3 個字串,另一個接受 4 個。

第一個字串顯然是選單名稱。第二個是工具提示,第三個是呼叫函式的名稱(在原始物件中)(這是透過反射完成的)。第四個,如果存在,則假定為返回 T/F 的布林方法名稱,可以根據返回值停用該選單項。

這段程式碼並不完全精確,它只是一個例子。要真正實現它,MyClass 需要訪問一些其他東西(例如呼叫物件),但這可以給您一個想法。

使用這種結構來實現一個具有 20 或 30 個條目的典型選單可以替換許多 C&P 程式碼螢幕。此外,您不必處理難看的“內部類”語法。您確實需要處理難看的反射語法,但這埋藏在 MyClass 物件中,只需要處理一次(永遠)。

您可能還會注意到,現在所有字串都在一個地方。這意味著用資料驅動的例程替換這幾行程式碼將非常容易。這可能是下一步,也可能不是。我不建議嘗試直接跳到資料驅動。

關於程式碼示例的說明

[編輯 | 編輯原始碼]

我以這種方式編碼是為了說明將類儲存在陣列中的要點。如果您真的要這樣做,我建議建立一個 MyClassHolder 例項並將呼叫類傳遞給它,以便 MyClassHolder 可以對其進行反射呼叫——然後,對於每個後續 MyClass 行,不要使用 “new”,而是呼叫 MyClassHolder 中的一個方法,該方法為您建立 MyClass 並將例項儲存在自身內部。如果您想看到它真正實現,請在討論標籤中留言。此外,我可以根據它們只有一個引數的事實來區分“頂級”項,但我試圖展示一種方法,如果您不能僅透過引數來區分每個方法,就可以使用這種方法。

警告:不要依賴可見字串

[編輯 | 編輯原始碼]

請注意,上面的示例中資料存在一些冗餘。

new MyClass("Save", "Save the file", "saveFunc"),

可以從 Save 計算 saveFunc。例如,您可以簡單地說儲存函式必須命名為 “doXFunc”。因此,建立一個“Save”選項會自動呼叫 doSaveFunc。

這在開始時通常有效,並且似乎是一個不錯的技巧,但在我的經驗中——我一直後悔將我的內部名稱與外部顯示的文字繫結,或者使用外部顯示的文字作為任何形式的“鍵”。最終,有人會想用另一種語言或其他東西來實現它。我贊成簡潔,但在這種情況下,它並不值得。

警告:謹慎使用資料

[編輯 | 編輯原始碼]

當你切換到資料驅動程式碼時,你生活中的大部分都會變得容易得多,但是某些型別的除錯會變得困難得多——記住現在是你檢查資料的工作。

  • 明確定義資料,以便可以更改它。
  • 對資料要極其挑剔,只允許你定義的結構和值。
  • 檢查資料中的引用(如果存在)。
  • 以清晰的錯誤說明大聲失敗。
  • 當用戶忽略你的解釋,並說這是你的程式碼的錯誤,而沒有閱讀它時,問問他們你可以做些什麼來讓他們閱讀資訊並解決他們的問題。把所有學到的東西放回程式碼中。
  • 如果資料足夠複雜,考慮實現一個編輯器。
  • 資料可以很容易地儲存在 .properties 檔案(煩人)、XML 或資料庫中——或者其他任何方式,選擇最適合你的方式——.properties 檔案使關聯資料變得困難,而資料庫幾乎需要一個編輯器。XML 是一個很好的平衡。

確保驗證資料與程式其餘部分之間的界限。例如,在上面的例子中,在載入時檢查每個反射字串。事實上,你應該在載入 MyClass 物件時例項化 Method 物件,並存儲方法物件,而不是每次都反射。

請記住,如果編寫了像這樣的錯誤檢查程式碼,在資料中發現拼寫錯誤將是微不足道的——否則幾乎不可能。

匿名內部類

[編輯 | 編輯原始碼]

匿名內部類是另一個導致 C&P 的情況。例如,每當控制元件的值發生變化時,你可能希望驗證表單。最糟糕的情況:整個驗證方法被 C&P 到每個匿名監聽器中。更好的情況:每個監聽器呼叫一個“驗證方法”。

所有匿名監聽器都應該相似,如果不是完全相同的話。

建立一個內部類(不是匿名的),它擴充套件你的基本監聽器型別(ActionListener,...)。將一個匿名內部類中的程式碼放在這裡。如果所有內部類都完全相同,你可以使該類無狀態(沒有內部變數)。

如果它是無狀態的,建立一個例項,並將其傳遞到建立監聽器的每個位置。你完成了。你可能將檔案大小減少了 1/3 到 2/3,具體取決於其他正在進行的操作。你可能還在此過程中消除了 2 到 3 個與拼寫錯誤相關的錯誤。

如果你的匿名內部類略有不同,你應該能夠在例項化內部類時傳入一個變數,並使用該變數來控制差異。如果你這樣做,你將不得不建立多個類的例項——每個不同的型別一個。我仍然建議你使它們不可變,否則你需要為每個監聽器建立一個例項。

如果它們有一些差異,你可以建立一個基類監聽器並繼承到一些子類中——就像你在任何 OO 程式碼中一樣。沒有理由僅僅因為你總是將它們視為匿名來對監聽器進行不同的處理。

最後,如果它們非常不同,只需建立 2 或 3 個不同的類。你可以將任何唯一的匿名內部類保留下來,但是如果監聽器中的程式碼是相同或非常相似的,請嘗試將它們合併。

函式/方法大小

[編輯 | 編輯原始碼]

嘗試將函式/方法限制在一個螢幕內。你會經常失敗,但這是一個很好的目標。如果你看到一個函式長度達到 2 或 3 個螢幕,你應該開始感到很不舒服。

處理一堆小函式比處理一個大函式容易得多。

請不要透過串聯來做。僅僅因為函式太長而將其打斷在中間,甚至更糟。

正如我在註釋部分所說,好的 OO 程式碼看起來什麼也沒做——任何地方。我認為我從未見過 Java 類中比一個螢幕更長的方法(刪除註釋後),絕大多數都是 1-3 行程式碼。我剛掃描了 Hashtable。沒有一個方法超過一個螢幕(比如 25 行左右),大多數更小——所有都很小,專注於解決一個問題。

每個方法或函式都應該只做一件事。這種功能細分使管理更加容易,並且可以提供在計劃過程中可能沒有意識到的抽象級別。通常,如果程式碼執行超過 40 行,你可能在一個步驟中做得太多。如果你覺得該功能不應該公開給其他開發人員,請將該方法設定為私有。

公共變數/變數範圍

[編輯 | 編輯原始碼]

我得出的結論是,每個變數都應該是私有的。總是。有時我會實現一個簡單的類——矩形,甚至“Pair”,並將變數設為公有,這樣我就不必編寫 setter/getter。我最終總會後悔的。

即使是受保護的變數也很煩人。如果你必須這樣做,編寫一個簡單的受保護的 getter(getter 和 setter 也可能是個壞主意——在谷歌上搜索“Getters and Setters are evil”以獲取一篇很棒的文章)。

傳遞一個可變物件與全域性變數一樣糟糕。要非常小心可變物件(大多數 Java 物件,如 String,是不可變的,因此更安全)。

進一步閱讀

[編輯 | 編輯原始碼]
華夏公益教科書