歡迎關注個人公衆號睿Talk
,獲取我最新的文章:
javascript
最近在作一些項目重構的工做,看了很多髒亂差的代碼,身心疲憊。本文將討論如何編寫整潔的代碼,不求高效運行,只求可讀性強,便於維護。前端
做爲一個合格的程序員,寫出簡潔的代碼是基本的職業素養。相信絕大部分的程序員都不會故意寫噁心代碼的,不管是對本身或者對別人都沒有任何好處。那麼,是什麼阻礙咱們寫出優秀代碼呢?有下面這麼幾種可能性:java
出來混早晚要還的,不管是上述哪一種緣由,混亂代碼一旦被寫出來,代碼做者確定是要爲其買單的,只是買單的方式會各有不一樣。多是後期維護的時候邊改邊抽本身,也多是別人改代碼的時候邊改邊罵你傻x。程序員
那麼,代碼寫好了會有什麼好處呢?起碼有如下幾方面:segmentfault
既然有這麼多好處,那到底怎麼評判代碼寫得好很差呢?是本身以爲好就是好嗎?顯然不是。代碼寫得是否整潔是客觀的,是 code review 的人或後期維護的人以爲好纔是真的好。因此增強 code review 也是倒逼寫出優秀代碼的一種方式。設計模式
我的認爲代碼的優秀程度分如下幾個層次:api
層次越高,難度越大,挑戰越大。做爲一個有追求的程序員,咱們應該不斷突破本身的邊界,追求卓越,更上一層樓。前端框架
要想寫出優雅整潔的代碼,就要遵循特定的設計原則。透徹理解這些原則後,還要結合具體的項目落地,不斷的練習和重構。下面總結出的一些通用原則供參考。網絡
業務邏輯要直截了當,不要引入各類依賴,多層次調用。以 React 爲例,常見的錯誤是將props
在state
裏存一份,計算的時候再從state
中取。這樣帶來的問題是要時刻監聽props
的變化,而後再同步到state
中。這徹底是畫蛇添足,直接用props
進行計算便可。架構
// bad componentWillReceiveProps(nextProps) { this.setState({num: nextProps.num}); } render() { return( <div>{this.state.num * 2}</div> ); } /***************************/ // good render() { return( <div>{this.props.num * 2}</div> ); }
不用作機械式的複製粘貼,要鍛鍊本身抽象的能力,儘可能將通用的邏輯抽象出來,方便往後重用。
代碼要對擴展開放,對修改封閉。儘可能作到在不修改原有代碼的基礎上,增長新的功能。React 的容器組件和展現組件分離用的就是這種思想。當數據來源不一樣的時候,只須要修改或新增容器組件,展現組件維持不變。
function Comp() { ... } class ContainerComp extends PureComponent { async componentDidMount() { const data = await fetchData(); this.setState({data}); } render() { return (<Comp data={this.state.data}/>); } }
從架構層面說,微內核架構也是遵循這一設計原則。它能保證核心模塊不變的狀況下,經過插件機制爲系統賦予新的能力。咱們經常使用的 Webpack 就是一個很好的例子,它經過引入 loader 和 plugin 的機制,極大的擴展了其文件處理的能力。
首先說一下類這個概念。本質上來講,定義類就是爲了代碼的複用,對於須要同時建立多個對象實例的狀況下,這種設計模式是很是有效的。好比說鏈接池中就須要同時存在多個鏈接對象,方便資源複用。而對於前端來講,絕大部分的業務場景都是單例,這種狀況下經過定義工具函數,或者直接使用對象字面量會更加高效。工具函數儘可能使用純函數,使代碼更易於理解,不用考慮反作用。這裏說的僅限於業務代碼的範疇,若是是框架型的項目,場景會複雜得多,類會更有用武之地。
既然類都不須要用了,繼承就更無從談起了。繼承的問題是多級繼承以後,定位問題會很是困難,要一級一級往上找才能找到錯誤出處。而組合就沒有這種問題,由於多個功能都是平級的,哪裏出問題一眼就能看出來。比較一下下面 2 種風格的代碼:
繼承的寫法:
class Parent extends PureComponent { componentDidMount() { this.fetchData(this.url); } fetchData(url) { ... } render() { const data = this.calcData(); return ( <div>{data}</data> ); } } class Child extends Parent { constructor(props) { super(props); this.url = 'http://api'; } calcData() { ... } }
組合的寫法:
class Parent extends PureComponent { componentDidMount() { this.fetchData(this.props.url); } fetchData(url) { ... } render() { const data = this.props.calcData(this.state); return ( <div>{data}</data> ); } } class Child extends PureComponent { calcData(state) { ... } render() { <Parent url="http://api" calcData={this.calcData}/> } }
哪一種更易於理解呢?
遵循單一職責的代碼若是設計得好,組合起來的代碼就會很是的清爽。舉一個註冊的場景,能夠劃分爲下面幾個職責:
僞代碼以下:
// UI.js export default function UI() { ... } // api.js export function regist(name, email) { ... } // validate.js export function validateName(name) { ... } export function validateEmail(email) { ... } // Regist.js export default class Regist extends PureComponent { ... onSubmit = async () => { const {name, email} = this.state; if (validateName(name) && validateEmail(email)) { const resp = await regist(name, email); ... } } render() { <UI onSubmit={onSubmit}> } }
能夠看到聚合層的代碼很是簡潔,哪裏出問題了就到相應的地方改就行了,即便是多人協做,也不容易出問題。
關注點分離原則跟單一職責原則有點相似,但更強調的是系統架構層面的設計。典型的例子就是 MVC 模式,Model、View、Control 三層之間都有明確的職責劃分,作到了高內聚低耦合。
React 的源碼設計也是基於這一原則,分爲ReactElement
, ReactCompositeComponent
和 ReactDomComponent
三層。ReactElement
負責描述頁面的 DOM 結構,也就是著名的 Virtual DOM;ReactCompositeComponent
處理的是組件生命週期、組件 Diff 和更新等邏輯;而ReactDomComponent
是真正進行 DOM 操做的類。三層之間分工明確,緊密協做,共同組成了一個強大的前端框架。
提倡簡潔易懂的代碼,而不是晦澀難懂的「聰明」代碼,以下面這種:
let a, b=3, t = (a=2, b<1) ? (console.log('Y'),'yes') : (console.log('N'),'no');
文件一旦超過 200 行,說明邏輯已經有點複雜了,要想辦法抽離出一些純函數工具方法,讓主線邏輯更加清晰。工具方法能夠放在另外的文件裏面,減小讀代碼的心理壓力。有一點須要說明一下,並非全部的文件都不能超過 200 行,像工具方法這種,都是各自獨立的邏輯,寫多少行都無所謂。須要控制的是緊密關聯的業務代碼的行數。
上面提到要合理的拆分代碼,那到底怎麼拆呢?對於前端組件代碼,有下面一些拆分點以供參考:
須要說明的是展示邏輯和業務邏輯是兩回事,最好不要混在一塊兒寫。好比組件的顯示隱藏是展示邏輯,而數據的校驗就是業務邏輯。
本文討論了書寫整潔代碼的必要性和重要性,結合實例列出了一些設計原則,還給出了組件代碼拆分的方式。程序員的職業生涯是一個自我修煉的過程,時刻關注代碼質量,是提升技術水平的重要一環。