在 9102 年年初,一位室友問我一個問題,如何才能夠提升寫代碼的能力?
可惜的是: 當時僅僅回復了一些自己的想法,如多看開源代碼,多讀書,多學習,多關注業界的動向與實踐,同時也列了一些原則,但是這些并沒有所總結,又或者說沒有例子的語言始終是空泛的,所以在今年年底之際,對應著今年中遇到的形形色色的代碼問題來一一講解一下,
好代碼的用處
實際上本書建立在一個相當不可靠的前提之上:好的代碼是有意義的,我見過太多丑陋的代碼給他們的主人賺著大把鈔票,所以在我看來,軟體要取得商業成功或者廣泛使用,“好的代碼質量”既不必要也不充分,即使如此,我仍然相信,盡管代碼質量不能保證美好的未來,他仍然有其意義:有了質量良好的代碼以后,業務需求能夠被充滿信心的開發和交付,軟體用戶能夠及時調整方向以便應對機遇和競爭,開發團隊能夠再挑戰和挫折面前保持高昂的斗志,總而言之,比起質量低劣,錯誤重重的代碼,好的代碼更有可能幫助用戶取得業務上的成功,
以上文字摘抄于《實作模式》的前言,距離本書翻譯已經時隔 10 年了,但是這本書仍舊有著很大的價值,同時對于上述言論,我并不持否認意見,但是我認為,壞代碼比好代碼更加的費財(嗯,沒打錯,我確定),對于相同的業務需求,壞代碼需要投入的精力,時間更多,產出反而會更少,同時根據破窗理論( 此理論認為環境中的不良現象如果被放任存在,會誘使人們仿效,甚至變本加厲 ),壞代碼會產生更壞的代碼,這是一個惡性回圈,如果不加以控制,完成需求的時間會慢慢失去控制,需要完成需求的人也會失落離開,
也就是說,好代碼可以實作多贏,能夠讓用戶爽,能夠讓老板爽,能夠讓開發者爽,總之,大家爽才是真的爽,
怎么寫出好代碼
少即使多
利用開源出來的設計與代碼來減輕來自于業務線的時間壓力,
The best way to write secure and reliable applications. Write nothing; deploy nowhere.
以上取自 github 上最火的專案之一 nocode,懶惰是程式員的美德之一,所以學習業務,理解業務,拒絕不必要的需求也是一個程式員的必修功課,詳情可以參考如何杜絕一句話需求? 這一篇 blog,當然,在大部分場景下,我們是不具備對需求說不的能力與權力的,但是無論如何,深度的理解業務,對客戶有同理心是對程式員的更高要求,解決問題才是一個程式員需要做的事情,能夠理解好題意才能解決問題,
對于軟體開發而言,時間一定是最寶貴,最有價值的資源,相應的,盡量把時間耗費在解決新的問題,而不是對已經存在確切解決方案的問題老調重彈,所以,盡量不要自己寫代碼,而是借用別人的設計與實作,而在事實上,你也很難在極短的時間壓力下設計并完成比開源更加合適的代碼,
當然,開源作者一定是想讓他的產品有更多的受眾,所以從設計上而言,會采用較為通用的設計,如果你的需求較為特殊并且你覺得不能說服作者幫你“免費打工”(或者作者拒絕了),那么你也只需要在特定之處進行包裝與改寫,但是要比完全重寫要簡單太多了,
當然,調研新的技術方案并且使用到專案中是一種能力,但是千萬不要因為一個小功能添加一個非常大的專案,
筆者在之前就遇到過其他小伙伴因為無法使用數字四舍五入,說 fixed 方法有問題而使用 math.js 的小伙伴,
(11.545).toFixed(2)
// "11.54"
如果想要了解 fixed 方法為何有問題的,可以參考 為什么(2.55).toFixed(1)等于2.5? 作者以 v8 原始碼來解釋為何會有這樣的問題,以及提供了部分修正 fixed 的方案,
事實上如果沒有很大的精度需求,前端完完全全利用一個函式便可以解決的問題,完全不需要復雜的math 這種高精度庫,
function round(number, precision) {
return Math.round(+number + 'e' + precision) / Math.pow(10, precision);
}
當然,也有小伙伴來找我詢問大量資料的表格優化,我第一反應就是 React Infinite 或者 vue-infinite-scroll 此類解決方案,但是對方能夠多提供一些資訊包括背景關系,采用的技術堆疊,當前資料量大小,未來可能需要達到的大小,當前表格是否需要修改等,得到了這些資訊,結合業務來看,相比于增加一個庫,是否如下方式更為便捷與快速,
// 因為 vue 模型的原因,使用 Object.freeze 性能可以有很大增益
this.xxx = Object.freeze(xxx);
隨著堆積業務,代碼的增長,管理復雜度的成本與日俱增,把依賴降低, 利用開源代碼使得任務更容易實作,時間就是成本,關鍵是讓收益可以最大化,
學習更多是為了做的更少,
統一
不同的人由于編碼經驗和編碼偏好不同,專案中同一個功能的實作代碼可能千差萬別,但是如果不加以約束,讓每一個人都按照自己的偏好寫自己的模塊,恐怕就會變成災難,
所以每次在學習一些新技術的時候,我總是想多看看作者的實體代碼,作者是如何理解的,社區又是如何理解的,以求實作起來代碼風格不至于偏離社區太多,這樣的話可以提高溝通與協作的效率,類似于 《阿里巴巴Java開發手冊》 或者 vue 風格指南 這種取自大公司或社區的經驗之談,要多讀幾遍,因為他們所遇到的問題和業務更加復雜,
對于公司內部開發來說,寫一個組件時候,生命周期的代碼放在檔案上面還是放在最下面,如何把代碼的一個功能點集中放置,通用型代碼的修改,代碼行數的限制,能夠列出統一的方案,多利而少害,
化繁為簡(抽象)
抽象是指從具體事物抽出、概括出它們共同的方面、本質屬性與關系等,而將個別的、非本質的方面、屬性與關系舍棄的思維程序,
如果你面對一個較大的系統,你會發現重構并不能解決根本問題,它僅僅只能減少少許的代碼的復雜度以及代碼行數,只有抽象才可以解決實質性問題,
無論是資料庫設計,架構設計,業務設計,代碼設計,但凡設計都離不開抽象,抽象能力強的所面臨的困難會比能力弱的少很多,
或者說抽象能力弱一些的小伙伴遇到一些問題甚至需要重新推翻然后再設計,這個是在時間和業務開發中是不能被接受的,
這里就談談代碼,以下也舉個例子,如 axios 庫中有攔截器與本身業務,在沒有看到原始碼之前,我一直認為他是分 3 階段處理:
-
請求攔截
-
業務處理
-
回應攔截
但如果你去看原始碼,你就會發現其實在作者看來,這 3 個階段其實都是在處理一個 Promise 佇列而已,
// 業務處理
var chain = [dispatchRequest, undefined];
var promise = Promise.resolve(config);
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
// 前置請求攔截
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
// 后置回應攔截
chain.push(interceptor.fulfilled, interceptor.rejected);
});
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
這就是一種代碼抽象能力,讓自己的代碼可以適應更多的場景是程式員需要思考的,代碼不是給機器看的,是給人看的,更高的要求是: 代碼不僅僅是給人看的,更是給人用的,需要考慮到協作的人與事,靈活的配置也是必須要考慮到的,就拿前端的 虛擬 dom 來說,能夠適配更多的平臺,
當然了,抽象能力需要時間,需要經驗,需要學習大量的設計,
注意!:不要過早的抽象業務代碼,甚至不要抽象業務代碼,多寫一點代碼無所謂,千萬別給自己找事做, 在業務上盡量保持簡單和愚蠢,除非你是業務專家,確認當前業務不太會產生變化,
權責對等(拆分與合并)
責任與義務本質上就是對等的,且越對等的就越穩定,這幾年,微服務架構,中臺,微前端理論層出不窮,本質上就是為了權責對等,對于更加基礎的服務,更有產出的業務投入更高的人力與物力以保證更穩定的運行是很正常的一件事,而不是之前的大鍋飯(單體應用),
從代碼上來看,某個模塊是否承擔了它不應該做的事情,或者某個模塊過于簡單,徒增復雜度,
當然,事實上有些東西目前是做不到的讓所有人都覺得滿意,增一分則肥,減一分則瘦,剛剛好很難界定,就像 Dan Abramov 說的那樣:
Flux libraries are like glasses: you’ll know when you need them.
只做一件事
Unix 哲學,這個很好理解,就像我今年想做的事情太多,反而什么都沒有做(或者說都做了,但都不好),
代碼上來看,不要因為一點點性能的原因,把幾件事合在一起去做,例如在一次 for 回圈中解決所有問題,或者將所有代碼寫在一個函式中,例如:
created() {
const {a,b,c,d} = this.data
// ... 三件事情彼此有互動同時需要 a,b,c,d
// 完成之后的邏輯
}
改造后:
created() {
const axx = doA()
doB()
const cxx = doC()
// 完成之后的邏輯
}
// 分離出3個函式
doA() {
const {a,b,c} = this.data
// ... 三件事情彼此有互動同時需要 a,b,c,d
// 完成之后的邏輯
}
// 其他代碼
相比于第一個只需要一次取數,一次setData,第二個性能無疑更低,但是可維護性變高了,3 件事情都被拆分出來,后面修改代碼時候,我可以追加一個 doD 而不是再次把第一份代碼中邏輯整理清楚再小心翼翼的修改代碼,
命名與注釋
There are only two hard things in Computer Science: cache invalidation and naming things.
命名與快取失效是兩大難題,今年講了不少快取問題,同時,命名的確是很困難的一件事情,通過一句話來解釋你們在做什么事情,通過一句話來解釋一件事的意圖,
不說在程式世界中,在現實世界中也是如此,例如: 《震驚!xxx居然xxx》等新聞,雖然說看完后都會想要罵一句,但是,正如這樣的名字才能吸引人家點擊進入,讓人情不自禁的被騙一次又一次,所以在專案沒有發布前,要取一個簡單而又好記的名字,
但在程式內部,我們不需要“騙取”人家的點擊量,反而是要務實點,不要欺騙另外的同伴,比如說寫了一個簡單的名字,結果內部卻封裝了很多的業務代碼,同時我認為這也是函式越寫越短的理由,因為大家難以通過命名來解釋那一大坨代碼的意圖,所以,需要撰寫可以自我解釋的代碼,而這種代碼最佳實踐就是好的命名,
對于開源代碼,你往往會發現,這些檔案開頭都會有一系列注釋,這個注釋告訴我們了這個模塊的意圖與目的,讓你無需看代碼就可以進行開發,
對于業務開發而言,僅在你不能通過代碼清晰解釋其含義的地方,才寫注釋,在多個條件下都無法解釋你的代碼,
-
專案名
-
模塊名
-
檔案名(類名)
-
函式名(方法名)
這并不是讓你不寫注釋,但是我覺得更多的注釋應該放在資料結構而不是代碼邏輯上,聰明的資料結構和笨拙的代碼要比相反的搭配作業的更好,更多的時候,看資料結構我能了解業務是如何運行的,但是僅僅看到代碼并不能實際想象出來,
實際上,隨著時間的推移,代碼做出了許多改動,但注釋并沒有隨之修改,這是一個很大的問題,注釋反而變得更有欺騙性,
這里也提供一篇 export default 有害 的文章,我覺得 export default 匯出一個可以隨意命名的模塊就是一種欺騙性代碼(隨著時間的推移,該模塊的意圖會發生變化),
考慮場景
沒有放眼四海皆準的方案,所以我們必須要考慮到場景的問題,我們總是說可修改性,可讀性是第一位的(往往可讀,可修改的代碼性能都不差),但是如果是急切需求性能的場景下,有些事情是需要再考慮的,
if 是業務處理中最常用的,在每次使用前要考慮以下,哪個更適合作為主體,哪個更適合放在前面進行判斷,如果有兩個維度上的引數,一個是角色,一個是事件,一定是會先判斷角色引數,然后再去判斷事件引數,反之則一定不好,因為前者更符合人的思維模式,在同一維度下,至于哪個放前面,一定是更多被使用的引數放在前面更好,因為更符合機器的執行程序,
就像在 if 中你究竟是使用 else 還是 return,大部分情況下處理業務邏輯互斥使用 else,處理錯誤使用return,因為這樣的代碼最符合人的思維邏輯,
但是在這里我也要舉出來自《代碼之美》的例子,在第五章中,作者 Elliotte Rusty Harold 設計了一個 xml 驗證器,其中有一段在驗證數字字符:
public static boolean isXMLDigit(char c) {
if (c >= 0x0030 && c <= 0x0039) return true;
if (c >= 0x0660 && c <= 0x0669) return true;
if (c >= 0x06F0 && c <= 0x06F9) return true;
// ...
return false
}
這個優化之后如下:
public static boolean isXMLDigit(char c) {
if (c < 0x0030) return false; if (c <= 0x0039) return true;
if (c < 0x0660) return false; if (c <= 0x0669) return true;
if (c < 0x06F0) return false; if (c <= 0x06F0) return true;
// ...
return false
}
全域思考,善于交流
軟體開發已經不是一個人打天下的時代了,你要不停的觸達邊界,在前后端分離的時代,前端可以不知道資料庫如何優化,后端也可以不清楚瀏覽器的渲染機制,但是卻不能不明白對方在做什么,否則等于雞同鴨講,也會浪費時間,在開發時候,把一段邏輯放在那一端取決安全的思考以及簡化邏輯,
善于交流是一種能力,在與別人交流時給與足夠的背景關系,讓你的 leader 溝通,讓她知道你的難處,和小伙伴溝通,說服他人按照你的想法推進,同時,善于聆聽才能不斷進步,
演算法
我不是一個演算法達人( leetcode 中等題目都費勁 ),但這個沒什么可說的,你拿你的 O(n**3) 演算法去對戰人家 O(n * logn) 演算法就是費財,所以,知道自己某方面不夠好去努力就行了,
輔助工具
TypeScript
雖然早就接觸和實踐過,但是以往都是 AnyScript,今年也算重度使用了,才體會到該工具的利好,一個好的開發工具并不是讓你少寫那一點點代碼,而是讓你在交付代碼時候能夠更加自信,
TypeScript 最大的好處就是讓你在寫代碼前先思考,先做設計,就像之前說的,聰明的資料結構和笨拙的代碼要比相反的搭配作業的更好,
TypeScript 同時也可以讓大部分運行時錯誤變為編譯時,并且可以減少使用中的防御性編程(信任但是仍要驗證),你不是一個人在寫代碼,協作優先,
在開發中,如果你接觸過復雜性資料結構,并且還要在模塊中不斷進行資料轉化,你就會不斷的遇到:我的資料呢?到底在那一步丟失了?并且即使是代碼對的,你仍舊害怕,仍舊懷疑,我已經過了那個“寫 bug 是因為想的不夠多,不夠徹底”的年齡,
函式式思維
js 是有函式式的血統的,當年一直聽說,函式是一等公民,只是當時完全不能理解,
純函式,資料不可變以及代碼即資料這三點是我認為是函式式思維對代碼能力提升最大的三點,
這個我不想展開去聊,因為我沒有熟練掌握過任何一門純函式式語言,但是我的代碼一定有函式式的影子,并且它的確讓我的代碼更優美,
其他
單元測驗,代碼審查,安全等等都沒有講到,這個我也需要足夠的學習才能有所輸出,不過這里列出一些資料供大家學習與了解:
谷歌代碼審查指南
SaaS型初創企業安全101
有理有據就是好代碼
作業在別人遺留的糟糕代碼上是常有的事情,同時面對開發需求實際表,為了兼容,我們也不得不寫出一些不那么好的代碼,但是面對他人的疑問,我們需要給與別人這樣做的理由,也就是你的每一行代碼寫下去一定有充分的理由和依據,
結語
明顯不等于簡單,上述都是很明顯的事情,但是要做好都需要很長時間的學習與經驗,
所以如何才能寫好代碼呢?那就是多看開源代碼,多讀書,多學習,多關注業界的動向與實踐,不斷學習,不斷進化的代碼才是好代碼,
最近有一些小伙伴(無中生友)問我的名字為什么要叫 jump_jump,我是為了讓自己以及看到我的小伙伴們牢記多鍛煉,閑下來的時候多跳一跳,對身體有好處,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/96484.html
標籤:其他
上一篇:AT指令之 TCP/IP 命令
