代碼這樣寫,誰(shuí)還敢裁你

之前寫過幾篇關(guān)于如何寫出可維護(hù)項(xiàng)目的文章(構(gòu)建大型前端業(yè)務(wù)項(xiàng)目的一點(diǎn)經(jīng)驗(yàn)、編寫可維護(hù)的現(xiàn)代化前端項(xiàng)目、接手前端新項(xiàng)目?這里有些注意點(diǎn)你可能需要留意一下)

近來看到很多公司裁員,忽然驚醒,之前是站在項(xiàng)目角度考慮問題,卻沒站在咱們程序員本身看待問題,險(xiǎn)些釀成大錯(cuò),如果人人都能做到把項(xiàng)目維護(hù)得井井有條,無(wú)論什么人都能看明白都能快速接手,那咱們的競(jìng)爭(zhēng)力在哪里呢?

這個(gè)時(shí)候我再看項(xiàng)目中那些被我天天罵的代碼,頓時(shí)心中就無(wú)限景仰起來,原來屎山才是真能能夠保護(hù)我們的東西,哪有什么歲月靜好,只是有人替你負(fù)屎前行罷了

為了能讓更多人認(rèn)識(shí)到這一點(diǎn),站在前端的角度上,我在仔細(xì)拜讀了項(xiàng)目中的那些暗藏玄機(jī)的代碼后,決定寫下此文,由于本人功力尚淺,且之前一直走在錯(cuò)誤的道路上,所以本文在真正的高手看來可能有些班門弄斧,在此獻(xiàn)丑了??

1用 TypeScript,但不完全用
TypeScript大行其道,在每個(gè)團(tuán)隊(duì)中,總有那么些個(gè)宵小之輩想盡一切辦法在項(xiàng)目里引入 ts,這種行為嚴(yán)重阻礙了屎山的成長(zhǎng)速度,但同是打工人我們也不好阻止,不過就算如此,也無(wú)法阻止我們行使正義

眾所周知,TypeScript 別名 AnyScript,很顯然,這就是TypeScript創(chuàng)始人Anders Hejlsberg給我們留下的暗示,我們有理由相信AnyScript 才是他真正的目的

const list: any = []
const obj: any = {}
const a: any = 1
引入了 ts的項(xiàng)目,由于是在原可運(yùn)行代碼的基礎(chǔ)上額外添加了類型注釋,所以代碼體積毫無(wú)疑問會(huì)增大,有調(diào)查顯示,可能會(huì)增加 30%的代碼量,如果充分發(fā)揮 AnyScript 的宗旨,意味著你很輕松地就讓代碼增加了 30% 毫無(wú)用處但也挑不出啥毛病的代碼,這些代碼甚至還會(huì)增加項(xiàng)目的編譯時(shí)間(畢竟增加了ts校驗(yàn)和移除的成本嘛)

你不僅能讓自己寫的代碼用上 AnyScript,甚至還可以給那些支持 ts 的第三方框架/庫(kù)一個(gè)大嘴巴子

export default defineComponent({
  props: {
    // 現(xiàn)在 data 是 any 類型的啦
    data: {
      type: Number as PropType<any>,
    },
  },
  setup(_, { emit }) {
    // 現(xiàn)在 props 是 any 類型的啦
    const props: any = _
    ...
  }
})
當(dāng)然了,全屏 any可能還是有點(diǎn)明顯了,所以你可以適當(dāng)?shù)亟o部分變量加上具體類型,但是加上類型不意味著必須要正確使用

const obj: number[] = []
// ...
// 雖然 obj 是個(gè) number[],但為了實(shí)現(xiàn)業(yè)務(wù),就得塞入一些不是 number 的類型,我也不想的啊是不是
// 至于編輯器會(huì)劃紅線報(bào)錯(cuò)?那是小問題,不用管它,別人一打開這個(gè)項(xiàng)目就是滿屏的紅線,想想就激動(dòng)
obj.push('2')
obj.push([3])
2命名應(yīng)該更自由
命名一直是個(gè)困擾很多程序員的問題,究其原因,我們總想給變量找個(gè)能夠很好表達(dá)意思的名稱,這樣一來代碼的可閱讀性就高了,但現(xiàn)在我們知道,這并不是件好事,所以我們應(yīng)該放縱自我,既擺脫了命名困難癥,又加速了屎山的堆積進(jìn)度

const a1 = {}
const a2 = {}
const a3 = 2
const p = 1
我必須強(qiáng)調(diào)一點(diǎn),命名不僅是變量命名,還包含文件名、類名、組件名等,這些都是我們可以發(fā)揮的地方,例如類名

<div class="box">
  <div class="box1"></div>
  <div class="box2"></div>
<div>
<div class="box3"></div>
乍一看似乎沒啥毛病,要說有毛病似乎也不值當(dāng)單獨(dú)挑出來說,沒錯(cuò),要的就是這個(gè)效果,讓人單看一段代碼不好說什么,但是如果積少成多,整個(gè)項(xiàng)目都是 box呢?全局搜索都給你廢了!如果你某些組件再一不小心沒用 scoped 呢?稍不留意就不知道把什么組件的樣式給改了,想想就美得很

關(guān)于 css我還想多說一點(diǎn),鑒于其靈活性,我們還可以做得更多,總有人說什么 BEM 不 BEM的,他們敢用我們就敢寫這樣的代碼

&-card {
  &-btn {
    &_link {
      &--right {
      }
    }
    &-nodata {
      &_link {
        &--replay {
          &--create {}
        }
      }
    }
  }
  &-desc {}
}
好了,現(xiàn)在請(qǐng)?jiān)趲装傩校P(guān)于這一點(diǎn)下一節(jié)會(huì)說到)這種格式的代碼里找出類名 .xxx__item_current.mod-xxx__link 對(duì)應(yīng)的樣式吧

3代碼一定要長(zhǎng)
屎山一定是夠高夠深的,這就要求我們的代碼應(yīng)該是夠長(zhǎng)夠多的

大到一個(gè)文件的長(zhǎng)度,小到一個(gè)類、一個(gè)函數(shù),甚至是一個(gè) if 的條件體,都是我們自由發(fā)揮的好地方。

什么單文件最好不超過 400行,什么一個(gè)函數(shù)不超過 100行,簡(jiǎn)直就是毒瘤,

所以這就要求我們要具備將十行代碼就能解決的事情寫成一百行的能力,最好能給人一種多即是少的感覺






data === 1
  ? 'img'
  : data === 2
    ? 'video'
    : data === 3
      ? 'text'
      : data === 4
        ? 'picture'
        : data === 5
          ? 'miniApp'
三元表達(dá)式可以優(yōu)雅地表達(dá)邏輯,像詩(shī)一樣,雖然這段代碼看起來比較多,但邏輯就是這么多,我還專門用了三元表達(dá)式優(yōu)化,不能怪我是不是?什么map映射枚舉優(yōu)化聽都沒聽過

你也可以選擇其他一些比較容易實(shí)現(xiàn)的思路,例如,多寫一些廢話

if (a > 10) {
  // 雖然下面幾個(gè) if 中對(duì)于 a 的判斷毫無(wú)用處,但不仔細(xì)看誰(shuí)能看出來呢?看出來了也不好說什么,畢竟也沒啥錯(cuò)
  // 除此之外,多級(jí) if 嵌套也是堆屎山的一個(gè)小技巧,什么提前 return 不是太明白
  if (a > 5) {
    if (a > 3 && b) {

    }
  }
  if (a > 4) {

  }
}
除此之外,你還可以寫一些中規(guī)中矩的方法,但重點(diǎn)在于這些方法根本就沒用到,這種發(fā)揮的地方就更多了,簡(jiǎn)直就是擴(kuò)充代碼體積的利器,畢竟單看這些方法沒啥毛病,但誰(shuí)能想到根本就用不到呢?就算有人懷疑了,但你猜他敢隨便從運(yùn)行得好好的業(yè)務(wù)項(xiàng)目里刪掉一些沒啥錯(cuò)的代碼嗎?

4組件、方法多多滴耦合
為了避免其他人復(fù)用我的方法或組件,那么在寫方法或組件的時(shí)候,一定要盡可能耦合,提升復(fù)用的門檻

例如明明可以通過 Props傳參解決的事情,我偏要從全局狀態(tài)里取,例如vuex,獨(dú)一份的全局?jǐn)?shù)據(jù),想傳參就得改 store數(shù)據(jù),但你猜你改的時(shí)候會(huì)不會(huì)影響到其他某個(gè)頁(yè)面某個(gè)組件的正常使用呢?如果你用了,那你就可能導(dǎo)致意料之外的問題,如果你不用你就得自己重寫一個(gè)組件



組件不需要傳參?沒關(guān)系,我直接把組件的內(nèi)部變量給掛到全局狀態(tài)上去,雖然這些內(nèi)部變量確實(shí)只有某一個(gè)組件在用,但我掛到全局狀態(tài)也沒啥錯(cuò)啊是不是

嘿,明明一個(gè)組件就能解決的事情,現(xiàn)在有了倆,后面還可能有仨,這代碼量不就上來了嗎?

方法也是如此,明明可以抽取參數(shù),遵循函數(shù)式編程理念,我偏要跟外部變量產(chǎn)生關(guān)聯(lián)

// 首先這個(gè)命名就很契合上面說的自由命名法
function fn1() {
  // ...
  // fn1 的邏輯比較長(zhǎng),且解決的是通用問題,
  // 但 myObj 偏偏是一個(gè)外部變量,這下看你怎么復(fù)用
  window.myObj.name = 'otherName'
  window.myObj.children.push({ id: window.myObj.children.length })
  // ...
}
5魔術(shù)字符串是個(gè)好東西
實(shí)際上,據(jù)我觀察,排除掉某些居心不軌的人之外,大部分人還是比較喜歡寫魔術(shù)字符串的,這讓我很欣慰,看著滿屏的不知道從哪里冒出來也不知道代表著什么的硬編碼字符串,讓人很有安全感

if (a === 'prepare') {
  const data = localStorage.getItem('HOME-show_guide')
  // ...
} else if (a === 'head' && b === 'repeating-error') {
  switch(c) {
    case 'pic':
      // ...
      break
    case 'inDrawer':
      // ...
      break
  }
}
基于此,我們還可以做得更多,比如用變量拼接魔術(shù)字符串,debug的時(shí)候直接廢掉全局搜索

if (a === query.name + '_head') {

}
大家都是中國(guó)人,為什么不試試漢字呢?

if (data === '正常') {

} else if (data === '錯(cuò)誤') {

} else if (data === '通過') {

}
6輪子就得自己造才舒心
眾所周知,造輪子可以顯著提升我們程序員的技術(shù)水平,另外由于輪子我們已經(jīng)自己造了,所以減少了對(duì)社區(qū)的依賴,同時(shí)又增加了項(xiàng)目體積,有力地推動(dòng)了屎山的成長(zhǎng)進(jìn)程,可以說是一魚兩吃了 例如我們可能經(jīng)常在項(xiàng)目中使用到時(shí)間格式化的方法,一般人都是直接引入 dayjs完事,太膚淺了,我們應(yīng)該自己實(shí)現(xiàn),例如,將字符串格式日期格式化為時(shí)間戳






function format(str1: any, str2: any) {
  const num1 = new Date(str1).getTime()
  const num2 = new Date(str2).getTime()
  return (num2 - num1) / 1000
}
多么精簡(jiǎn)多么優(yōu)雅,至于你說的什么格式校驗(yàn)什么 safari下日期字符串的特殊處理,等遇到了再說嘛,就算是dayjs不也是經(jīng)過了多次 fixbug才走到今天的嘛,多一些寬松和耐心好不好啦

如果你覺得僅僅是 dayjs這種小打小鬧難以讓你充分發(fā)揮,你甚至可以造個(gè) vuex,vue官網(wǎng)上寫明了eventBus可以充當(dāng)全局狀態(tài)管理的,所以我們完全可以自己來嘛,這里就不舉例了,這是自由發(fā)揮的地方,就不局限大家的思路了

7借助社區(qū)的力量-輪子還是別人的好
考慮到大家都只是混口飯吃而已,凡事都造輪子未免有些強(qiáng)人所難,所以我們可以嘗試走向另外一個(gè)極端——凡事都用輪子解決 判斷某個(gè)變量是字符串還是對(duì)象,kind-of拿來吧你;獲取某個(gè)對(duì)象的 key,object-keys拿來吧你;獲取屏幕尺寸,vue-screen-size拿來吧你……等等,就不一一列舉了,需要大家自己去發(fā)現(xiàn)

先甭管實(shí)際場(chǎng)景是不是真的需要這些庫(kù),也甭管是不是殺雞用牛刀,要是大家聽都沒聽過的輪子那就更好了,這樣才能彰顯你的見多識(shí)廣,總之能解決問題的輪子就是好問題,

在此我得特別提點(diǎn)一下 lodash,這可是解決很多問題的利器,但是別下載錯(cuò)了,得是 commonjs版本的那個(gè),量大管飽還正宗,es module版本是不行滴,太小家子氣

import _ from 'lodash'
8多嘗試不同的方式來解決相同的問題
世界上的路有很多,很多路都能通往同一個(gè)目的地,但大多數(shù)人庸庸碌碌,只知道沿著前人的腳步,沒有自己的思想,別人說啥就是啥,這種行為對(duì)于我們程序員這種高端的職業(yè)來說,壞處很大,任何一個(gè)有遠(yuǎn)大理想的程序員都應(yīng)該避免 落到實(shí)際上來,就是嘗試使用不同的技術(shù)和方案解決相同的問題

搞個(gè)css模塊化方案,什么BEM、OOCSS、CSS Modules、CSS-in-JS 都在項(xiàng)目里引入,緊跟潮流擴(kuò)展視野 vue項(xiàng)目只用 template?遜啦你,render渲染搞起來

之前看過什么前端依賴注入什么反射的文章,雖然對(duì)于絕大多數(shù)業(yè)務(wù)項(xiàng)目而言都是水土不服,但問題不大,能跑起來就行,引入引入

還有那什么 rxjs,人家都說好,雖然我也不知道好在哪里,但勝在門檻高一般人搞不清楚所以得試試 Pinia 是個(gè)好東西,什么,我們項(xiàng)目里已經(jīng)有 vuex了?out啦,人家官網(wǎng)說了 vue2也可以用,我們一定要試試,緊跟社區(qū)潮流嘛,一個(gè)項(xiàng)目里有兩套狀態(tài)管理有什么值得大驚小怪的!

9做好自己,莫管他人閑事
看過一個(gè)小故事,有人問一個(gè)年紀(jì)很大的老爺爺?shù)拈L(zhǎng)壽秘訣是什么,老爺爺說是從來不管閑事 這個(gè)故事對(duì)我們程序員來說也很有啟發(fā),寫好你自己的代碼,不要去關(guān)心別人能不能看得懂,不要去關(guān)心別人是不是會(huì)掉進(jìn)你寫的坑里

mounted() {
  setTimeout(() => {
    const width = this.$refs.box.offsetWidth
    const itemWidth = 50
    // ...
  }, 200)
}
例如對(duì)于上述代碼,為什么要在 mounted里寫個(gè) setTimeout呢?為什么這個(gè) setTimeout的時(shí)間是 200呢?可能是因?yàn)?box 這個(gè)元素大概會(huì)在 mounted之后的 200ms左右接口返回?cái)?shù)據(jù)就有內(nèi)容了,就可以測(cè)量其寬度進(jìn)行其他一系列的邏輯了,至于有沒有可能因?yàn)榫W(wǎng)絡(luò)等原因超過 200ms還是沒有內(nèi)容呢?這些不需要關(guān)心,你只要保證在你開發(fā)的時(shí)候 200ms這個(gè)時(shí)間是沒問題的就行了;

itemWidth代表另外一個(gè)元素的寬度,在你寫代碼的時(shí)候,這個(gè)元素寬度就是 50,所以沒必要即時(shí)測(cè)量,你直接寫死了,至于后面其他人會(huì)不會(huì)改變這個(gè)元素的寬度導(dǎo)致你這里不準(zhǔn)了,這就不是你要考慮的事情了,你開發(fā)的時(shí)候確實(shí)沒問題,其他人搞出來問題其他人負(fù)責(zé)就行,管你啥事呢?

10代碼自解釋
高端的程序員,往往采用最樸素的編碼方式,高手從來不寫注釋,因?yàn)樗麄儗懙拇a都是自解釋的,什么叫自解釋?就是你看代碼就跟看注釋一樣,所以不需要注釋

我覺得很有道理,代碼都在那里擱著了,邏輯寫得清清楚楚,為啥還要寫注釋呢,直接看代碼不就行了嗎?

乍一看,似乎這一條有點(diǎn)阻礙堆屎山的進(jìn)程,實(shí)則不然

一堆注定要被迭代無(wú)數(shù)版、被無(wú)數(shù)人修改、傳承多年的代碼,其必定是邏輯錯(cuò)綜復(fù)雜,難免存在一些不可名狀的讓人說不清道不明的邏輯,沒有注釋的加成,這些邏輯大概率要永遠(yuǎn)成為黑洞了,所有人看到都得繞著走,相當(dāng)于是圍繞著這些黑洞額外搭起了一套邏輯,這代碼體積和復(fù)雜度不就上來了嗎?

如果你實(shí)在手癢,倒也可以寫點(diǎn)注釋,我這里透露一個(gè)既能讓你寫寫注釋過過癮又能為堆屎山加一把力的方法,那就是:在注釋里撒謊!

沒錯(cuò),誰(shuí)說注釋只能寫對(duì)的?我理解不夠,所以注釋寫得不太對(duì)有什么奇怪的嗎?我又沒保證注釋一定是對(duì)的,也沒逼著你看注釋,所以你看注釋結(jié)果被注釋誤導(dǎo)寫了個(gè)bug,這憑啥怪我啊

// 計(jì)算 data 是否可用
//(實(shí)際上,這個(gè)方法的作用是計(jì)算 data 是否 不可用)
function isDisabledData(data: any) {
  // ...
}
上述這個(gè)例子只能說是小試牛刀,畢竟多調(diào)試一下很容易被發(fā)現(xiàn)的,但就算被發(fā)現(xiàn)了,大家也只會(huì)覺得你只是個(gè)小粗心鬼罷了,怎么好責(zé)怪你呢,這也算是給其他人的一個(gè)小驚喜了,況且,萬(wàn)一真有人不管不顧就信了,那你就賺大了

11編譯問題堅(jiān)決不改
為了阻礙屎山的成長(zhǎng)速度,有些陰險(xiǎn)的家伙總想在各種層面上加以限制,例如加各種lint,在編譯的時(shí)候,命令行中就會(huì)告訴你你哪些地方?jīng)]有按照規(guī)則來,但大部分是 waring 級(jí)別的,即你不改項(xiàng)目也能正常運(yùn)行,這就是我們的突破點(diǎn)了。

盡管按照你的想法去寫代碼,lint的事情不要去管,waring報(bào)錯(cuò)就當(dāng)沒看到,又不是不能用?在這種情況下,如果有人不小心弄了個(gè) error級(jí)別的錯(cuò)誤,他面對(duì)的就是從好幾屏的 warning 中找他的那個(gè) error 的場(chǎng)景了,這就相當(dāng)于是提前跟屎山來了一次面對(duì)面的擁抱

根據(jù)破窗理論,這種行為將會(huì)影響到越來越多的人,大家都將心照不宣地視 warning于無(wú)物(從好幾屏的 warning中找到自己的那個(gè)實(shí)在是太麻煩了),所謂的 lint就成了笑話

12小結(jié)
一座歷久彌香的屎山,必定是需要經(jīng)過時(shí)間的沉淀和無(wú)數(shù)人的操練才能最終成型,這需要我們所有人的努力,多年之后,當(dāng)你看到你曾經(jīng)參與堆砌的屎山中道崩殂轟然倒塌的時(shí)候,你就算是真的領(lǐng)悟了我們程序員所掌控的恐怖實(shí)力!??

作者:清夜 鏈接:https://juejin.cn/post/7107119166989336583

作者:清夜

歡迎關(guān)注微信公眾號(hào) :前端陽(yáng)光