好代碼的六誡:編寫經得起時間考驗的代碼

已發表: 2022-03-11

大約半個世紀以來,人類一直在努力研究計算機編程的藝術和科學。 與大多數藝術和科學相比,計算機科學在許多方面仍然只是一個蹣跚學步的孩子,會走進牆壁,絆倒自己的腳,偶爾還會把食物扔到桌子上。

由於其相對年輕,我認為我們尚未就“好代碼”的正確定義達成共識,因為該定義仍在不斷發展。 有人會說“好代碼”是具有 100% 測試覆蓋率的代碼。 其他人會說它速度超快,性能出色,並且可以在使用 10 年的硬件上正常運行。

雖然這些對於軟件開發人員來說都是值得稱讚的目標,但是我冒險將另一個目標加入其中:可維護性。 具體來說,“好代碼”是組織(不僅僅是其作者!)易於維護的代碼,並且比編寫它的 sprint 壽命更長。以下是我在我的文章中發現的一些內容在美國和國外的大公司和小公司擔任工程師的職業生涯似乎與可維護的“好”軟件相關。

永遠不要滿足於“有效”的代碼。 編寫優秀的代碼。
鳴叫

戒律#1:以你希望別人的代碼對待你的方式對待你的代碼

我遠不是第一個寫下代碼的主要受眾不是編譯器/計算機的人,而是下一個必須閱讀、理解、維護和增強代碼的人(從現在起六個月後不一定是你)。 任何值得他們付出的工程師都可以生成“有效”的代碼; 優秀工程師的不同之處在於,他們可以高效地編寫可維護的代碼,以長期支持業務,並具有以簡單、清晰和可維護的方式解決問題的技能。

在任何編程語言中,都可能編寫好代碼或壞代碼。 假設我們根據編程語言編寫好代碼的程度來判斷編程語言(無論如何,它至少應該是最重要的標準之一),任何編程語言都可以是“好”或“壞”,這取決於它的使用方式(或濫用方式) )。

許多人認為“乾淨”且可讀的語言的一個例子是 Python。 該語言本身強制執行某種程度的空白規則,並且內置的 API 豐富且相當一致。 也就是說,有可能創造出無法形容的怪物。 例如,可以定義一個類並在運行時定義/重新定義/取消定義該類上的任何方法(通常稱為猴子補丁)。 這種技術自然會導致最好的 API 不一致,最壞的情況是無法調試怪物。 人們可能會天真地認為,“當然,但沒有人這樣做!” 不幸的是,他們這樣做了,並且在您遇到大量(並且流行!)庫之前瀏覽 pypi 並不需要很長時間,這些庫(ab)廣泛使用猴子補丁作為其 API 的核心。 我最近使用了一個網絡庫,它的整個 API 會根據對象的網絡狀態而變化。 例如,想像一下,調用client.connect()有時會收到MethodDoesNotExist錯誤,而不是HostNotFoundNetworkUnavailable

戒律#2:好的代碼很容易部分和整體地閱讀和理解

好的代碼很容易被其他人部分和全部閱讀和理解(以及未來的作者,試圖避免“我真的寫了那個嗎?”綜合症)。

“部分”我的意思是,如果我在代碼中打開一些模塊或函數,我應該能夠理解它的作用,而不必同時閱讀整個代碼庫的其餘部分。 它應該盡可能直觀和自我記錄。

不斷引用影響代碼庫其他(看似不相關)部分行為的微小細節的代碼就像閱讀一本書,您必須在每個句子的末尾引用腳註或附錄。 你永遠不會通過第一頁!

關於“本地”可讀性的其他一些想法:

  • 封裝良好的代碼往往更具可讀性,在每個級別上分離關注點。

  • 名字很重要。 激活快速思考和慢速思考的系統 2 方式,大腦在這種方式中形成想法,並將一些實際的、仔細的想法放入變量和方法名稱中。 額外的幾秒鐘可以帶來可觀的紅利。 一個命名良好的變量可以使代碼更加直觀,而一個命名不佳的變量可能會導致虛假和混亂。

  • 聰明是敵人。 在使用花哨的技術、範式或操作(例如列表推導或三元運算符)時,請注意以使代碼更具可讀性而不是更短的方式使用它們。

  • 一致性是一件好事。 風格的一致性,無論是在如何放置大括號方面,還是在操作方面,都大大提高了可讀性。

  • 關注點分離。 一個給定的項目在代碼庫的不同點管理著無數的本地重要假設。 盡可能少地將代碼庫的每個部分暴露給這些問題。 假設您有一個人員管理系統,其中人員對像有時可能有一個空姓氏。 對於在顯示人員對象的頁面中編寫代碼的人來說,這可能真的很尷尬! 除非您維護一本“我們的代碼庫具有的尷尬且不明顯的假設”的手冊(我知道我不知道),否則您的顯示頁面程序員不會知道姓氏可以為空,並且可能會使用空指針編寫代碼當最後一個名字為空的情況出現時,它會出現異常。 而是使用經過深思熟慮的 API 和合約來處理這些情況,您的代碼庫的不同部分使用這些 API 和合約來相互交互。

戒律#3:好的代碼有一個深思熟慮的佈局和架構,使管理狀態變得顯而易見

國家是敵人。 為什麼? 因為它是任何應用程序中最複雜的部分,需要非常慎重和深思熟慮地處理。 常見問題包括數據庫不一致、部分 UI 更新(其中新數據未在任何地方反映)、無序操作,或者只是在意複雜的代碼,到處都有 if 語句和分支,導致代碼難以閱讀,甚至更難維護。 將狀態放在一個需要非常小心處理的基礎上,並且在如何訪問和修改狀態方面非常一致和慎重,可以極大地簡化您的代碼庫。 某些語言(例如 Haskell)在程序和句法級別強制執行此操作。 如果您擁有不訪問外部狀態的純函數庫以及引用外部純功能的有狀態代碼的小表面區域,您會驚訝於代碼庫的清晰度可以提高多少。

戒律#4:好的代碼不會重新發明輪子,它站在巨人的肩膀上

在可能重新發明輪子之前,請考慮一下您要解決的問題有多普遍,或者您要執行的功能有多普遍。 有人可能已經實施了您可以利用的解決方案。 如果合適且可用,請花時間考慮和研究任何此類選項。

也就是說,一個完全合理的反駁觀點是,依賴並不是“免費”的,沒有任何缺點。 通過使用添加了一些有趣功能的第 3 方或開源庫,您將致力於並依賴於該庫。 這是一個很大的承諾。 如果它是一個巨大的庫,並且您只需要一點點功能,如果您升級到 Python 3.x,您真的想要更新整個庫的負擔嗎? 此外,如果您遇到錯誤或想要增強功能,您要么依賴作者(或供應商)提供修復或增強功能,要么,如果它是開源的,則發現自己處於探索(潛在的大量)代碼庫您完全不熟悉嘗試修復或修改一些晦澀的功能。

當然,您所依賴的代碼使用得越好,您自己花費時間進行維護的可能性就越小。 最重要的是,您自己進行研究並自行評估是否包含外部技術以及該特定技術將為您的堆棧添加多少維護是值得的。

以下是一些您可能不應該在現代項目中重新發明的更常見的示例(除非這些是您的項目)。

數據庫

找出您的項目需要哪個 CAP,然後選擇具有正確屬性的數據庫。 數據庫不再僅僅意味著 MySQL,您可以選擇:

  • “傳統”架構 SQL: Postgres / MySQL / MariaDB / MemSQL / Amazon RDS 等。
  • 鍵值存儲: Redis / Memcache / Riak
  • NoSQL: MongoDB/Cassandra
  • 託管數據庫: AWS RDS / DynamoDB / AppEngine 數據存儲
  • 繁重的工作: Amazon MR / Hadoop (Hive/Pig) / Cloudera / Google Big Query
  • 瘋狂的東西: Erlang 的 Mnesia,iOS 的 Core Data

數據抽象層

在大多數情況下,您不應該將原始查詢寫入您碰巧選擇使用的任何數據庫。 更有可能的是,在數據庫和應用程序代碼之間存在一個庫,將管理並發數據庫會話和架構細節的關注點與主代碼分開。 至少,您的應用程序代碼中不應該有原始查詢或 SQL 內聯。 相反,將它包裝在一個函數中,並將所有函數集中在一個文件中,該文件稱為非常明顯的文件(例如,“queries.py”)。 例如,像users = load_users()這樣的行比users = db.query(“SELECT username, foo, bar from users LIMIT 10 ORDER BY ID”)更容易閱讀。 這種類型的集中化還使得在查詢中保持一致的樣式變得更加容易,並限制了在架構更改時更改查詢的位置數量。

考慮利用的其他常見庫和工具

  • 排隊或發布/訂閱服務。 選擇 AMQP 提供商、ZeroMQ、RabbitMQ、Amazon SQS
  • 貯存。 亞馬遜 S3、谷歌云存儲
  • 監控: Graphite/Hosted Graphite、AWS Cloud Watch、New Relic
  • 日誌收集/聚合。 日誌,Splunk

自動縮放

  • 自動縮放。 Heroku、AWS Beanstalk、AppEngine、AWS Opsworks、數字海洋

戒律#5:不要越過溪流!

編程設計、發布/訂閱、演員、MVC 等有很多好的模型。選擇你最喜歡的模型,並堅持下去。 處理不同類型數據的不同類型邏輯應該在代碼庫中物理隔離(同樣,這種關注點分離概念並減少未來讀者的認知負擔)。 例如,更新 UI 的代碼在物理上應該不同於計算進入 UI 的代碼。

戒律#6:如果可能,讓計算機完成工作

如果編譯器可以捕獲代碼中的邏輯錯誤並防止不良行為、錯誤或徹底崩潰,我們絕對應該利用這一點。 當然,有些語言的編譯器比其他語言更容易做到這一點。 例如,Haskell 有一個著名的嚴格編譯器,導致程序員將大部分精力都花在編譯代碼上。 但是,一旦它編譯,“它就可以工作”。 對於那些從未用強類型函數式語言編寫過的人來說,這可能看起來很荒謬或不可能,但不要相信我的話。 說真的,點擊其中一些鏈接,絕對有可能生活在一個沒有運行時錯誤的世界中。 它真的是那麼神奇。

誠然,並不是每一種語言都有一個編譯器或語法,可以進行很多(或者在某些情況下!)編譯時檢查。 對於那些不這樣做的人,請花幾分鐘時間研究您可以在項目中啟用哪些可選的嚴格性檢查,並評估它們是否對您有意義。 我最近用於具有寬鬆運行時的語言的一些常見的簡短的、非全面的列表包括:

  • Python:pylint、pyflakes、警告、emacs 中的警告
  • 紅寶石:警告
  • JavaScript: jslint

結論

這絕不是生成“好”(即易於維護)代碼的詳盡或完美的誡命列表。 也就是說,如果將來我必須使用的每個代碼庫都遵循此列表中的一半概念,那麼我的白髮就會少得多,甚至可能在我生命的盡頭再增加五年。 我肯定會發現工作更愉快,壓力更小。