Mencuci kod anda: bahagikan dan takluki, atau gabung dan berehat

Linda Hamilton
Lepaskan: 2024-11-11 19:30:03
asal
953 orang telah melayarinya

Washing your code: divide and conquer, or merge and relax

Anda sedang membaca petikan daripada buku saya tentang kod bersih, "Mencuci kod anda." Tersedia sebagai PDF, EPUB dan sebagai edisi paperback dan Kindle. Dapatkan salinan anda sekarang.


Mengetahui cara menyusun kod ke dalam modul atau fungsi, dan masa yang sesuai untuk memperkenalkan abstraksi dan bukannya menduplikasi kod, adalah kemahiran penting. Menulis kod generik yang boleh digunakan oleh orang lain dengan berkesan adalah satu lagi kemahiran. Terdapat banyak sebab untuk memisahkan kod seperti yang ada untuk menyimpannya bersama-sama. Dalam bab ini, kita akan membincangkan beberapa sebab ini.

Biarkan abstraksi berkembang

Kami, pembangun, tidak suka melakukan kerja yang sama dua kali. KERING adalah mantra bagi ramai orang. Walau bagaimanapun, apabila kita mempunyai dua atau tiga keping kod yang melakukan perkara yang sama, mungkin masih terlalu awal untuk memperkenalkan abstraksi, tidak kira betapa menggodanya.

Maklumat: Prinsip Jangan ulangi diri sendiri (KERING) menuntut bahawa "setiap pengetahuan mesti mempunyai perwakilan tunggal, tidak jelas, berwibawa dalam sistem", yang sering ditafsirkan sebagai sebarang pertindihan kod adalah verboten sepenuhnya.

Hidup dengan kesakitan pertindihan kod untuk seketika; mungkin ia tidak begitu teruk pada akhirnya, dan kod itu sebenarnya tidak betul-betul sama. Beberapa tahap pertindihan kod adalah sihat dan membolehkan kami mengulang dan mengembangkan kod dengan lebih pantas tanpa rasa takut untuk memecahkan sesuatu.

Sukar juga untuk menghasilkan API yang baik apabila kami hanya mempertimbangkan beberapa kes penggunaan.

Menguruskan kod kongsi dalam projek besar dengan ramai pembangun dan pasukan adalah sukar. Keperluan baharu untuk satu pasukan mungkin tidak berfungsi untuk Pasukan lain dan melanggar kod mereka, atau kita akan mendapat raksasa spageti yang tidak dapat diselenggara dengan berpuluh-puluh syarat.

Bayangkan Pasukan A sedang menambahkan borang ulasan pada halaman mereka: nama, mesej dan butang hantar. Kemudian, Pasukan B memerlukan borang maklum balas, supaya mereka mencari komponen Pasukan A dan cuba menggunakannya semula. Kemudian, Pasukan A juga mahukan medan e-mel, tetapi mereka tidak tahu bahawa Pasukan B menggunakan komponen mereka, jadi mereka menambah medan e-mel yang diperlukan dan memecahkan ciri untuk pengguna Pasukan B. Kemudian, Pasukan B memerlukan medan nombor telefon, tetapi mereka tahu bahawa Pasukan A menggunakan komponen tanpanya, jadi mereka menambah pilihan untuk menunjukkan medan nombor telefon. Setahun kemudian, kedua-dua pasukan membenci satu sama lain kerana melanggar kod satu sama lain, dan komponen itu penuh dengan syarat dan mustahil untuk dikekalkan. Kedua-dua pasukan akan menjimatkan banyak masa dan mempunyai hubungan yang lebih sihat antara satu sama lain jika mereka mengekalkan komponen berasingan yang terdiri daripada komponen kongsi peringkat rendah, seperti medan input atau butang.

Petua: Adalah idea yang baik untuk melarang pasukan lain daripada menggunakan kod kami melainkan ia direka bentuk dan ditandakan sebagai dikongsi. Kapal penjelajah Dependency ialah alat yang boleh membantu menyediakan peraturan sedemikian.

Kadang-kadang, kita perlu melancarkan abstraksi. Apabila kita mula menambah syarat dan pilihan, kita harus bertanya kepada diri sendiri: adakah ia masih merupakan variasi daripada perkara yang sama atau perkara baharu yang harus dipisahkan? Menambah terlalu banyak syarat dan parameter pada modul boleh menjadikan API sukar digunakan dan kod sukar untuk diselenggara dan diuji.

Penduaan adalah lebih murah dan lebih sihat daripada abstraksi yang salah.

Maklumat: Lihat artikel Sandi Metz The Wrong Abstraction untuk penjelasan yang bagus.

Semakin tinggi tahap kod, semakin lama kita perlu menunggu sebelum kita mengabstrakkannya. Abstraksi utiliti peringkat rendah jauh lebih jelas dan stabil daripada logik perniagaan.

Saiz tidak selalu penting

Penggunaan semula kod bukan satu-satunya, malah paling penting, sebab untuk mengekstrak sekeping kod ke dalam fungsi atau modul yang berasingan.

Panjang kod sering digunakan sebagai metrik apabila kita harus memisahkan modul atau fungsi, tetapi saiz sahaja tidak menjadikan kod sukar dibaca atau diselenggara.

Memisahkan algoritma linear, walaupun yang panjang, kepada beberapa fungsi dan kemudian memanggilnya satu demi satu jarang menjadikan kod lebih mudah dibaca. Melompat antara fungsi (dan lebih-lebih lagi — fail) adalah lebih sukar daripada menatal, dan jika kita perlu melihat ke dalam setiap pelaksanaan fungsi untuk memahami kod, maka abstraksi itu bukanlah yang betul.

Maklumat: Egon Elbre menulis artikel bagus tentang psikologi kebolehbacaan kod.

Berikut ialah contoh, disesuaikan daripada Blog Pengujian Google:

function createPizza(order) {
  const pizza = new Pizza({
    base: order.size,
    sauce: order.sauce,
    cheese: 'Mozzarella'
  });

  if (order.kind === 'Veg') {
    pizza.toppings = vegToppings;
  } else if (order.kind === 'Meat') {
    pizza.toppings = meatToppings;
  }

  const oven = new Oven();

  if (oven.temp !== cookingTemp) {
    while (oven.temp < cookingTemp) {
      time.sleep(checkOvenInterval);
      oven.temp = getOvenTemp(oven);
    }
  }

  if (!pizza.baked) {
    oven.insert(pizza);
    time.sleep(cookTime);
    oven.remove(pizza);
    pizza.baked = true;
  }

  const box = new Box();
  pizza.boxed = box.putIn(pizza);
  pizza.sliced = box.slicePizza(order.size);
  pizza.ready = box.close();

  return pizza;
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Saya mempunyai begitu banyak soalan tentang API kelas Pizza, tetapi mari lihat penambahbaikan yang dicadangkan oleh pengarang:

function prepare(order) {
  const pizza = new Pizza({
    base: order.size,
    sauce: order.sauce,
    cheese: 'Mozzarella'
  });
  addToppings(pizza, order.kind);
  return pizza;
}

function addToppings(pizza, kind) {
  if (kind === 'Veg') {
    pizza.toppings = vegToppings;
  } else if (kind === 'Meat') {
    pizza.toppings = meatToppings;
  }
}

function bake(pizza) {
  const oven = new Oven();
  heatOven(oven);
  bakePizza(pizza, oven);
}

function heatOven(oven) {
  if (oven.temp !== cookingTemp) {
    while (oven.temp < cookingTemp) {
      time.sleep(checkOvenInterval);
      oven.temp = getOvenTemp(oven);
    }
  }
}

function bakePizza(pizza, oven) {
  if (!pizza.baked) {
    oven.insert(pizza);
    time.sleep(cookTime);
    oven.remove(pizza);
    pizza.baked = true;
  }
}

function pack(pizza) {
  const box = new Box();
  pizza.boxed = box.putIn(pizza);
  pizza.sliced = box.slicePizza(pizza.size);
  pizza.ready = box.close();
}

function createPizza(order) {
  const pizza = prepare(order);
  bake(pizza);
  pack(pizza);
  return pizza;
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Apa yang dahulunya rumit dan berbelit-belit kini menjadi lebih rumit dan berbelit-belit, dan separuh daripada kod itu hanyalah panggilan fungsi. Ini tidak menjadikan kod lebih mudah untuk difahami, tetapi ia menjadikannya hampir mustahil untuk digunakan. Artikel itu tidak menunjukkan kod lengkap versi pemfaktoran semula, mungkin untuk menjadikan perkara itu lebih menarik.

Pierre “catwell” Chapuis mencadangkan dalam catatan blognya untuk menambah ulasan dan bukannya fungsi baharu:

function createPizza(order) {
  const pizza = new Pizza({
    base: order.size,
    sauce: order.sauce,
    cheese: 'Mozzarella'
  });

  if (order.kind === 'Veg') {
    pizza.toppings = vegToppings;
  } else if (order.kind === 'Meat') {
    pizza.toppings = meatToppings;
  }

  const oven = new Oven();

  if (oven.temp !== cookingTemp) {
    while (oven.temp < cookingTemp) {
      time.sleep(checkOvenInterval);
      oven.temp = getOvenTemp(oven);
    }
  }

  if (!pizza.baked) {
    oven.insert(pizza);
    time.sleep(cookTime);
    oven.remove(pizza);
    pizza.baked = true;
  }

  const box = new Box();
  pizza.boxed = box.putIn(pizza);
  pizza.sliced = box.slicePizza(order.size);
  pizza.ready = box.close();

  return pizza;
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Ini sudah jauh lebih baik daripada versi split. Penyelesaian yang lebih baik ialah menambah baik API dan menjadikan kod lebih jelas. Pierre mencadangkan bahawa memanaskan ketuhar tidak sepatutnya menjadi sebahagian daripada fungsi createPizza() (dan membakar sendiri banyak piza, saya sangat bersetuju!) kerana dalam kehidupan sebenar ketuhar sudah ada dan mungkin sudah panas daripada piza sebelumnya. Pierre juga mencadangkan bahawa fungsi itu harus memulangkan kotak, bukan pizza, kerana dalam kod asal, jenis kotak itu hilang selepas semua penghirisan dan pembungkusan ajaib, dan kami berakhir dengan pizza yang dihiris di tangan kami.

Terdapat banyak cara untuk memasak piza, sama seperti terdapat banyak cara untuk mengekodkan masalah. Hasilnya mungkin kelihatan sama, tetapi sesetengah penyelesaian lebih mudah difahami, diubah suai, digunakan semula dan dipadamkan berbanding yang lain.

Penamaan juga boleh menjadi masalah apabila semua fungsi yang diekstrak adalah sebahagian daripada algoritma yang sama. Kita perlu mencipta nama yang lebih jelas daripada kod dan lebih pendek daripada ulasan — bukan tugas yang mudah.

Maklumat: Kami bercakap tentang mengulas kod dalam bab Elakkan ulasan dan tentang penamaan dalam bab Penamaan adalah sukar.

Anda mungkin tidak akan menemui banyak fungsi kecil dalam kod saya. Dalam pengalaman saya, sebab paling berguna untuk memisahkan kod ialah menukar kekerapan dan menukar sebab.

Asingkan kod yang sering berubah

Mari mulakan dengan tukar kekerapan. Logik perniagaan berubah lebih kerap daripada fungsi utiliti. Adalah wajar untuk memisahkan kod yang sering berubah daripada kod yang sangat stabil.

Borang ulasan yang kita bincangkan sebelum ini dalam bab ini adalah contoh yang pertama; fungsi yang menukar rentetan camelCase kepada kebab-case ialah contoh yang terakhir. Borang ulasan mungkin berubah dan menyimpang dari semasa ke semasa apabila keperluan perniagaan baharu timbul; fungsi penukaran kes tidak mungkin berubah sama sekali dan ia selamat untuk digunakan semula di banyak tempat.

Bayangkan bahawa kami sedang membuat jadual yang kelihatan cantik untuk memaparkan beberapa data. Kami mungkin fikir kami tidak akan memerlukan reka bentuk jadual ini lagi, jadi kami memutuskan untuk menyimpan semua kod untuk jadual dalam satu modul.

Pecut seterusnya, kami mendapat tugas untuk menambah lajur lain pada jadual, jadi kami menyalin kod lajur sedia ada dan menukar beberapa baris di sana. Pecut seterusnya, kita perlu menambah meja lain dengan reka bentuk yang sama. Pecut seterusnya, kita perlu menukar reka bentuk jadual…

Modul jadual kami mempunyai sekurang-kurangnya tiga sebab untuk menukar, atau tanggungjawab:

  • keperluan perniagaan baharu, seperti lajur jadual baharu;
  • Perubahan UI atau tingkah laku, seperti menambah pengisihan atau saiz semula lajur;
  • perubahan reka bentuk, seperti menggantikan sempadan dengan latar belakang baris berjalur.

Ini menjadikan modul lebih sukar untuk difahami dan lebih sukar untuk diubah. Kod pembentangan menambah banyak kata kerja, menjadikannya lebih sukar untuk memahami logik perniagaan. Untuk membuat perubahan dalam mana-mana tanggungjawab, kita perlu membaca dan mengubah suai lebih banyak kod. Ini menjadikannya lebih sukar dan lebih perlahan untuk diulang.

Mempunyai jadual generik sebagai modul berasingan menyelesaikan masalah ini. Sekarang, untuk menambah lajur lain pada jadual, kita hanya perlu memahami dan mengubah suai salah satu daripada dua modul. Kami tidak perlu mengetahui apa-apa tentang modul jadual generik kecuali API awamnya. Untuk menukar reka bentuk semua jadual, kami hanya perlu menukar kod modul jadual generik dan kemungkinan besar tidak perlu menyentuh jadual individu sama sekali.

Walau bagaimanapun, bergantung pada kerumitan masalah, tidak mengapa, dan selalunya lebih baik, bermula dengan pendekatan monolitik dan mengekstrak abstraksi kemudian.

Malah penggunaan semula kod boleh menjadi alasan yang sah untuk memisahkan kod: jika kami menggunakan beberapa komponen pada satu halaman, kami mungkin akan memerlukannya pada halaman lain tidak lama lagi.

Simpan bersama kod yang berubah pada masa yang sama

Mungkin menarik untuk mengekstrak setiap fungsi ke dalam modulnya sendiri. Walau bagaimanapun, ia juga mempunyai kelemahan:

  • Pembangun lain mungkin berpendapat bahawa mereka boleh menggunakan semula fungsi itu di tempat lain, tetapi sebenarnya, fungsi ini berkemungkinan tidak generik atau cukup diuji untuk digunakan semula.
  • Mencipta, mengimport dan bertukar antara berbilang fail menghasilkan overhed yang tidak perlu apabila fungsi hanya digunakan di satu tempat.
  • Fungsi sedemikian selalunya kekal dalam pangkalan kod lama selepas kod yang menggunakannya hilang.

Saya lebih suka menyimpan fungsi kecil yang hanya digunakan dalam satu modul pada permulaan modul. Dengan cara ini, kita tidak perlu mengimportnya untuk digunakan dalam modul yang sama, tetapi menggunakannya semula di tempat lain akan menjadi janggal.

function createPizza(order) {
  const pizza = new Pizza({
    base: order.size,
    sauce: order.sauce,
    cheese: 'Mozzarella'
  });

  if (order.kind === 'Veg') {
    pizza.toppings = vegToppings;
  } else if (order.kind === 'Meat') {
    pizza.toppings = meatToppings;
  }

  const oven = new Oven();

  if (oven.temp !== cookingTemp) {
    while (oven.temp < cookingTemp) {
      time.sleep(checkOvenInterval);
      oven.temp = getOvenTemp(oven);
    }
  }

  if (!pizza.baked) {
    oven.insert(pizza);
    time.sleep(cookTime);
    oven.remove(pizza);
    pizza.baked = true;
  }

  const box = new Box();
  pizza.boxed = box.putIn(pizza);
  pizza.sliced = box.slicePizza(order.size);
  pizza.ready = box.close();

  return pizza;
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Dalam kod di atas, kami mempunyai komponen (FormattedAddress) dan fungsi (getMapLink()) yang hanya digunakan dalam modul ini, jadi ia ditakrifkan di bahagian atas fail.

Jika kita perlu menguji fungsi ini (dan kita harus!), kita boleh mengeksportnya daripada modul dan mengujinya bersama-sama dengan fungsi utama modul.

Perkara yang sama berlaku untuk fungsi yang bertujuan untuk digunakan hanya bersama dengan fungsi atau komponen tertentu. Menyimpannya dalam modul yang sama menjadikannya lebih jelas bahawa semua fungsi adalah bersama dan menjadikan fungsi ini lebih mudah ditemui.

Faedah lain ialah apabila kami memadamkan modul, kami memadamkan kebergantungannya secara automatik. Kod dalam modul kongsi selalunya kekal dalam pangkalan kod selama-lamanya kerana sukar untuk mengetahui sama ada ia masih digunakan (walaupun TypeScript menjadikannya lebih mudah).

Maklumat: Modul sedemikian kadangkala dipanggil modul mendalam: modul yang agak besar yang merangkumi masalah kompleks tetapi mempunyai API yang ringkas. Lawan daripada modul mendalam ialah modul cetek: banyak modul kecil yang perlu berinteraksi antara satu sama lain.

Jika kita sering terpaksa menukar beberapa modul atau fungsi pada masa yang sama, mungkin lebih baik untuk menggabungkannya menjadi satu modul atau fungsi. Pendekatan ini kadangkala dipanggil kolokasi.

Berikut ialah beberapa contoh kolokasi:

  • Komponen bertindak balas: menyimpan semua yang diperlukan oleh komponen dalam fail yang sama, termasuk penanda (JSX), gaya (CSS dalam JS) dan logik, dan bukannya memisahkan setiap satu ke dalam failnya sendiri, mungkin dalam folder yang berasingan.
  • Ujian: menyimpan ujian di sebelah fail modul dan bukannya dalam folder berasingan.
  • Konvensyen Itik untuk Redux: simpan tindakan yang berkaitan, pencipta tindakan dan pengurang dalam fail yang sama daripada menyimpannya dalam tiga fail dalam folder berasingan.

Begini cara pepohon fail berubah dengan colocation:

Berpisah Berisi
Separated Colocated
React components
src/components/Button.tsx src/components/Button.tsx
styles/Button.css
Tests
src/util/formatDate.ts src/util/formatDate.ts
tests/formatDate.ts src/util/formatDate.test.ts
Ducks
src/actions/feature.js src/ducks/feature.js
src/actionCreators/feature.js
src/reducers/feature.js
Komponen bertindak balas<🎜> src/components/Button.tsx src/components/Button.tsx styles/Button.css <🎜>Ujian<🎜> src/util/formatDate.ts src/util/formatDate.ts tests/formatDate.ts src/util/formatDate.test.ts <🎜>Itik<🎜> src/actions/feature.js src/ducks/feature.js src/actionCreators/feature.js src/reducers/feature.js

Maklumat: Untuk mengetahui lebih lanjut tentang kolokasi, baca artikel Kent C. Dodds.

Aduan biasa mengenai kolokasi ialah ia menjadikan komponen terlalu besar. Dalam kes sedemikian, adalah lebih baik untuk mengekstrak beberapa bahagian ke dalam komponennya sendiri, bersama-sama dengan penanda, gaya dan logik.

Idea kolokasi juga bercanggah dengan pemisahan kebimbangan — idea lapuk yang menyebabkan pembangun web menyimpan HTML, CSS dan JavaScript dalam fail berasingan (dan selalunya dalam bahagian berasingan pepohon fail) untuk terlalu lama, memaksa kami mengedit tiga fail pada masa yang sama untuk membuat perubahan yang paling asas pada halaman web.

Maklumat: sebab perubahan juga dikenali sebagai prinsip tanggungjawab tunggal, yang menyatakan bahawa "setiap modul, kelas, atau fungsi harus mempunyai tanggungjawab ke atas satu bahagian fungsi disediakan oleh perisian dan tanggungjawab itu harus dirangkumkan sepenuhnya oleh kelas.”

Sapu kod hodoh itu di bawah permaidani

Kadangkala, kita perlu bekerja dengan API yang amat sukar untuk digunakan atau terdedah kepada ralat. Sebagai contoh, ia memerlukan beberapa langkah dalam susunan tertentu atau memanggil fungsi dengan berbilang parameter yang sentiasa sama. Ini adalah sebab yang baik untuk mencipta fungsi utiliti untuk memastikan kami sentiasa melakukannya dengan betul. Sebagai bonus, kami kini boleh menulis ujian untuk sekeping kod ini.

Manipulasi rentetan — seperti URL, nama fail, penukaran kes atau pemformatan — adalah calon yang baik untuk abstraksi. Kemungkinan besar, sudah ada perpustakaan untuk perkara yang kami cuba lakukan.

Pertimbangkan contoh ini:

function createPizza(order) {
  const pizza = new Pizza({
    base: order.size,
    sauce: order.sauce,
    cheese: 'Mozzarella'
  });

  if (order.kind === 'Veg') {
    pizza.toppings = vegToppings;
  } else if (order.kind === 'Meat') {
    pizza.toppings = meatToppings;
  }

  const oven = new Oven();

  if (oven.temp !== cookingTemp) {
    while (oven.temp < cookingTemp) {
      time.sleep(checkOvenInterval);
      oven.temp = getOvenTemp(oven);
    }
  }

  if (!pizza.baked) {
    oven.insert(pizza);
    time.sleep(cookTime);
    oven.remove(pizza);
    pizza.baked = true;
  }

  const box = new Box();
  pizza.boxed = box.putIn(pizza);
  pizza.sliced = box.slicePizza(order.size);
  pizza.ready = box.close();

  return pizza;
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Ia mengambil sedikit masa untuk menyedari bahawa kod ini mengalih keluar sambungan fail dan mengembalikan nama asas. Ia bukan sahaja tidak perlu dan sukar dibaca, tetapi ia juga menganggap sambungannya sentiasa tiga aksara, yang mungkin tidak berlaku.

Mari kita tulis semula menggunakan perpustakaan, modul laluan Node.js terbina dalam:

function prepare(order) {
  const pizza = new Pizza({
    base: order.size,
    sauce: order.sauce,
    cheese: 'Mozzarella'
  });
  addToppings(pizza, order.kind);
  return pizza;
}

function addToppings(pizza, kind) {
  if (kind === 'Veg') {
    pizza.toppings = vegToppings;
  } else if (kind === 'Meat') {
    pizza.toppings = meatToppings;
  }
}

function bake(pizza) {
  const oven = new Oven();
  heatOven(oven);
  bakePizza(pizza, oven);
}

function heatOven(oven) {
  if (oven.temp !== cookingTemp) {
    while (oven.temp < cookingTemp) {
      time.sleep(checkOvenInterval);
      oven.temp = getOvenTemp(oven);
    }
  }
}

function bakePizza(pizza, oven) {
  if (!pizza.baked) {
    oven.insert(pizza);
    time.sleep(cookTime);
    oven.remove(pizza);
    pizza.baked = true;
  }
}

function pack(pizza) {
  const box = new Box();
  pizza.boxed = box.putIn(pizza);
  pizza.sliced = box.slicePizza(pizza.size);
  pizza.ready = box.close();
}

function createPizza(order) {
  const pizza = prepare(order);
  bake(pizza);
  pack(pizza);
  return pizza;
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Kini, jelas perkara yang berlaku, tiada nombor ajaib dan ia berfungsi dengan sambungan fail dalam sebarang panjang.

Calon lain untuk abstrak termasuk tarikh, keupayaan peranti, borang, pengesahan data, pengantarabangsaan dan banyak lagi. Saya mengesyorkan mencari perpustakaan sedia ada sebelum menulis fungsi utiliti baharu. Kami sering memandang rendah kerumitan fungsi yang kelihatan mudah.

Berikut ialah beberapa contoh perpustakaan tersebut:

  • Lodash: semua jenis fungsi utiliti.
  • Date-fns: berfungsi untuk bekerja dengan tarikh, seperti menghurai, manipulasi dan pemformatan.
  • Zod: pengesahan skema untuk TypeScript.

Berkati pemfaktoran semula sebaris!

Kadangkala, kami terbawa-bawa dan mencipta abstraksi yang tidak memudahkan kod mahupun menjadikannya lebih pendek:

function createPizza(order) {
  // Prepare pizza
  const pizza = new Pizza({
    base: order.size,
    sauce: order.sauce,
    cheese: 'Mozzarella'
  });

  // Add toppings
  if (order.kind == 'Veg') {
    pizza.toppings = vegToppings;
  } else if (order.kind == 'Meat') {
    pizza.toppings = meatToppings;
  }

  const oven = new Oven();

  if (oven.temp !== cookingTemp) {
    // Heat oven
    while (oven.temp < cookingTemp) {
      time.sleep(checkOvenInterval);
      oven.temp = getOvenTemp(oven);
    }
  }

  if (!pizza.baked) {
    // Bake pizza
    oven.insert(pizza);
    time.sleep(cookTime);
    oven.remove(pizza);
    pizza.baked = true;
  }

  // Box and slice
  const box = new Box();
  pizza.boxed = box.putIn(pizza);
  pizza.sliced = box.slicePizza(order.size);
  pizza.ready = box.close();

  return pizza;
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Contoh lain:

function createPizza(order) {
  const pizza = new Pizza({
    base: order.size,
    sauce: order.sauce,
    cheese: 'Mozzarella'
  });

  if (order.kind === 'Veg') {
    pizza.toppings = vegToppings;
  } else if (order.kind === 'Meat') {
    pizza.toppings = meatToppings;
  }

  const oven = new Oven();

  if (oven.temp !== cookingTemp) {
    while (oven.temp < cookingTemp) {
      time.sleep(checkOvenInterval);
      oven.temp = getOvenTemp(oven);
    }
  }

  if (!pizza.baked) {
    oven.insert(pizza);
    time.sleep(cookTime);
    oven.remove(pizza);
    pizza.baked = true;
  }

  const box = new Box();
  pizza.boxed = box.putIn(pizza);
  pizza.sliced = box.slicePizza(order.size);
  pizza.ready = box.close();

  return pizza;
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Perkara terbaik yang boleh kita lakukan dalam kes sedemikian ialah menerapkan pemfaktoran semula dalam talian yang maha kuasa: menggantikan setiap panggilan fungsi dengan badannya. Tiada abstraksi, tiada masalah.

Contoh pertama menjadi:

function prepare(order) {
  const pizza = new Pizza({
    base: order.size,
    sauce: order.sauce,
    cheese: 'Mozzarella'
  });
  addToppings(pizza, order.kind);
  return pizza;
}

function addToppings(pizza, kind) {
  if (kind === 'Veg') {
    pizza.toppings = vegToppings;
  } else if (kind === 'Meat') {
    pizza.toppings = meatToppings;
  }
}

function bake(pizza) {
  const oven = new Oven();
  heatOven(oven);
  bakePizza(pizza, oven);
}

function heatOven(oven) {
  if (oven.temp !== cookingTemp) {
    while (oven.temp < cookingTemp) {
      time.sleep(checkOvenInterval);
      oven.temp = getOvenTemp(oven);
    }
  }
}

function bakePizza(pizza, oven) {
  if (!pizza.baked) {
    oven.insert(pizza);
    time.sleep(cookTime);
    oven.remove(pizza);
    pizza.baked = true;
  }
}

function pack(pizza) {
  const box = new Box();
  pizza.boxed = box.putIn(pizza);
  pizza.sliced = box.slicePizza(pizza.size);
  pizza.ready = box.close();
}

function createPizza(order) {
  const pizza = prepare(order);
  bake(pizza);
  pack(pizza);
  return pizza;
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Dan contoh kedua menjadi:

function createPizza(order) {
  // Prepare pizza
  const pizza = new Pizza({
    base: order.size,
    sauce: order.sauce,
    cheese: 'Mozzarella'
  });

  // Add toppings
  if (order.kind == 'Veg') {
    pizza.toppings = vegToppings;
  } else if (order.kind == 'Meat') {
    pizza.toppings = meatToppings;
  }

  const oven = new Oven();

  if (oven.temp !== cookingTemp) {
    // Heat oven
    while (oven.temp < cookingTemp) {
      time.sleep(checkOvenInterval);
      oven.temp = getOvenTemp(oven);
    }
  }

  if (!pizza.baked) {
    // Bake pizza
    oven.insert(pizza);
    time.sleep(cookTime);
    oven.remove(pizza);
    pizza.baked = true;
  }

  // Box and slice
  const box = new Box();
  pizza.boxed = box.putIn(pizza);
  pizza.sliced = box.slicePizza(order.size);
  pizza.ready = box.close();

  return pizza;
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Hasilnya bukan sahaja lebih pendek dan lebih mudah dibaca; kini pembaca tidak perlu meneka apa yang fungsi ini lakukan, kerana kami kini menggunakan fungsi dan ciri asli JavaScript tanpa abstraksi buatan sendiri.

Dalam banyak kes, sedikit pengulangan adalah baik. Pertimbangkan contoh ini:

function FormattedAddress({ address, city, country, district, zip }) {
  return [address, zip, district, city, country]
    .filter(Boolean)
    .join(', ');
}

function getMapLink({ name, address, city, country, zip }) {
  return `https://www.google.com/maps/?q=${encodeURIComponent(
    [name, address, zip, city, country].filter(Boolean).join(', ')
  )}`;
}

function ShopsPage({ url, title, shops }) {
  return (
    <PageWithTitle url={url} title={title}>
      <Stack as="ul" gap="l">
        {shops.map(shop => (
          <Stack key={shop.name} as="li" gap="m">
            <Heading level={2}>
              <Link href={shop.url}>{shop.name}</Link>
            </Heading>
            {shop.address && (
              <Text variant="small">
                <Link href={getMapLink(shop)}>
                  <FormattedAddress {...shop} />
                </Link>
              </Text>
            )}
          </Stack>
        ))}
      </Stack>
    </PageWithTitle>
  );
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Ia kelihatan baik dan tidak akan menimbulkan sebarang soalan semasa semakan kod. Walau bagaimanapun, apabila kami cuba menggunakan nilai ini, autolengkap hanya menunjukkan nombor dan bukannya nilai sebenar (lihat ilustrasi). Ini menjadikan lebih sukar untuk memilih nilai yang betul.

Washing your code: divide and conquer, or merge and relax

Kita boleh menyelaraskan pemalar baseSpacing:

const file = 'pizza.jpg';
const prefix = file.slice(0, -4);
// → 'pizza'
Salin selepas log masuk
Salin selepas log masuk

Kini, kami mempunyai kurang kod, ia sama mudah difahami, dan autolengkap menunjukkan nilai sebenar (lihat ilustrasi). Dan saya tidak fikir kod ini akan sering berubah — mungkin tidak pernah.

Washing your code: divide and conquer, or merge and relax

Pisahkan "apa" dan "bagaimana"

Pertimbangkan petikan ini daripada fungsi pengesahan borang:

const file = 'pizza.jpg';
const prefix = path.parse(file).name;
// → 'pizza'
Salin selepas log masuk
Salin selepas log masuk

Agak sukar untuk memahami perkara yang berlaku di sini: logik pengesahan bercampur dengan mesej ralat, banyak semakan diulang…

Kita boleh membahagikan fungsi ini kepada beberapa bahagian, masing-masing bertanggungjawab untuk satu perkara sahaja:

  • senarai pengesahan untuk borang tertentu;
  • kumpulan fungsi pengesahan, seperti isEmail();
  • fungsi yang mengesahkan semua nilai borang menggunakan senarai pengesahan.

Kami boleh menerangkan pengesahan secara deklaratif sebagai tatasusunan:

// my_feature_util.js
const noop = () => {};

export const Utility = {
  noop
  // Many more functions…
};

// MyComponent.js
function MyComponent({ onClick }) {
  return <button onClick={onClick}>Hola!</button>;
}

MyComponent.defaultProps = {
  onClick: Utility.noop
};
Salin selepas log masuk
Salin selepas log masuk

Setiap fungsi pengesahan dan fungsi yang menjalankan pengesahan adalah agak generik, jadi kita boleh sama ada mengabstrakkannya atau menggunakan pustaka pihak ketiga.

Kini, kami boleh menambah pengesahan untuk sebarang bentuk dengan menerangkan medan mana yang memerlukan pengesahan dan ralat mana yang perlu ditunjukkan apabila semakan tertentu gagal.

Maklumat: Lihat bab Elakkan syarat untuk kod lengkap dan penjelasan yang lebih terperinci tentang contoh ini.

Saya panggil proses ini pemisahan "apa" dan "bagaimana":

  • "apa" ialah data — senarai pengesahan untuk borang tertentu;
  • “bagaimana” ialah algoritma — fungsi pengesahan dan fungsi pelari pengesahan.

Faedahnya ialah:

  • Kebolehbacaan: selalunya, kita boleh mentakrifkan "apa" secara deklaratif, menggunakan struktur data asas seperti tatasusunan dan objek.
  • Kebolehselenggaraan: kami menukar "apa" lebih kerap daripada "bagaimana", dan kini ia dipisahkan. Kami boleh mengimport "apa" daripada fail, seperti JSON, atau memuatkannya daripada pangkalan data, menjadikan kemas kini boleh dilakukan tanpa perubahan kod atau membenarkan bukan pembangun melakukannya.
  • Kebolehgunaan semula: selalunya, "bagaimana" adalah generik, dan kami boleh menggunakannya semula, atau mengimportnya daripada pustaka pihak ketiga.
  • Kebolehujian: setiap pengesahan dan fungsi pelari pengesahan diasingkan, dan kami boleh mengujinya secara berasingan.

Elakkan fail utiliti raksasa

Banyak projek mempunyai fail yang dipanggil utils.js, helpers.js atau misc.js tempat pembangun memasukkan fungsi utiliti apabila mereka tidak dapat mencari tempat yang lebih baik untuk mereka. Selalunya, fungsi ini tidak pernah digunakan semula di tempat lain dan kekal dalam fail utiliti selama-lamanya, jadi ia terus berkembang. Begitulah cara fail utiliti raksasa dilahirkan.

Fail utiliti raksasa mempunyai beberapa isu:

  • Kebolehtemuan yang lemah: memandangkan semua fungsi berada dalam fail yang sama, kami tidak boleh menggunakan pembuka fail kabur dalam editor kod kami untuk mencarinya.
  • Mereka mungkin hidup lebih lama daripada pemanggil mereka: selalunya fungsi sedemikian tidak pernah digunakan semula dan kekal dalam pangkalan kod, walaupun selepas kod yang menggunakannya dialih keluar.
  • Tidak cukup generik: fungsi sebegini selalunya dibuat untuk kes penggunaan tunggal dan tidak akan meliputi kes penggunaan lain.

Ini adalah peraturan ibu jari saya:

  • Jika fungsi itu kecil dan digunakan sekali sahaja, simpannya dalam modul yang sama di mana ia digunakan.
  • Jika fungsi itu panjang atau digunakan lebih daripada sekali, letakkannya dalam fail berasingan di dalam folder util, kongsi atau helpers.
  • Jika kami mahukan lebih banyak organisasi, daripada membuat fail seperti utils/validators.js, kami boleh mengumpulkan fungsi berkaitan (masing-masing dalam failnya sendiri) ke dalam folder: utils/validators/isEmail.js.

Elakkan eksport lalai

Modul JavaScript mempunyai dua jenis eksport. Yang pertama ialah eksport bernama:

function createPizza(order) {
  const pizza = new Pizza({
    base: order.size,
    sauce: order.sauce,
    cheese: 'Mozzarella'
  });

  if (order.kind === 'Veg') {
    pizza.toppings = vegToppings;
  } else if (order.kind === 'Meat') {
    pizza.toppings = meatToppings;
  }

  const oven = new Oven();

  if (oven.temp !== cookingTemp) {
    while (oven.temp < cookingTemp) {
      time.sleep(checkOvenInterval);
      oven.temp = getOvenTemp(oven);
    }
  }

  if (!pizza.baked) {
    oven.insert(pizza);
    time.sleep(cookTime);
    oven.remove(pizza);
    pizza.baked = true;
  }

  const box = new Box();
  pizza.boxed = box.putIn(pizza);
  pizza.sliced = box.slicePizza(order.size);
  pizza.ready = box.close();

  return pizza;
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Yang boleh diimport seperti ini:

function prepare(order) {
  const pizza = new Pizza({
    base: order.size,
    sauce: order.sauce,
    cheese: 'Mozzarella'
  });
  addToppings(pizza, order.kind);
  return pizza;
}

function addToppings(pizza, kind) {
  if (kind === 'Veg') {
    pizza.toppings = vegToppings;
  } else if (kind === 'Meat') {
    pizza.toppings = meatToppings;
  }
}

function bake(pizza) {
  const oven = new Oven();
  heatOven(oven);
  bakePizza(pizza, oven);
}

function heatOven(oven) {
  if (oven.temp !== cookingTemp) {
    while (oven.temp < cookingTemp) {
      time.sleep(checkOvenInterval);
      oven.temp = getOvenTemp(oven);
    }
  }
}

function bakePizza(pizza, oven) {
  if (!pizza.baked) {
    oven.insert(pizza);
    time.sleep(cookTime);
    oven.remove(pizza);
    pizza.baked = true;
  }
}

function pack(pizza) {
  const box = new Box();
  pizza.boxed = box.putIn(pizza);
  pizza.sliced = box.slicePizza(pizza.size);
  pizza.ready = box.close();
}

function createPizza(order) {
  const pizza = prepare(order);
  bake(pizza);
  pack(pizza);
  return pizza;
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Dan yang kedua ialah eksport lalai:

function createPizza(order) {
  // Prepare pizza
  const pizza = new Pizza({
    base: order.size,
    sauce: order.sauce,
    cheese: 'Mozzarella'
  });

  // Add toppings
  if (order.kind == 'Veg') {
    pizza.toppings = vegToppings;
  } else if (order.kind == 'Meat') {
    pizza.toppings = meatToppings;
  }

  const oven = new Oven();

  if (oven.temp !== cookingTemp) {
    // Heat oven
    while (oven.temp < cookingTemp) {
      time.sleep(checkOvenInterval);
      oven.temp = getOvenTemp(oven);
    }
  }

  if (!pizza.baked) {
    // Bake pizza
    oven.insert(pizza);
    time.sleep(cookTime);
    oven.remove(pizza);
    pizza.baked = true;
  }

  // Box and slice
  const box = new Box();
  pizza.boxed = box.putIn(pizza);
  pizza.sliced = box.slicePizza(order.size);
  pizza.ready = box.close();

  return pizza;
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Yang boleh diimport seperti ini:

function FormattedAddress({ address, city, country, district, zip }) {
  return [address, zip, district, city, country]
    .filter(Boolean)
    .join(', ');
}

function getMapLink({ name, address, city, country, zip }) {
  return `https://www.google.com/maps/?q=${encodeURIComponent(
    [name, address, zip, city, country].filter(Boolean).join(', ')
  )}`;
}

function ShopsPage({ url, title, shops }) {
  return (
    <PageWithTitle url={url} title={title}>
      <Stack as="ul" gap="l">
        {shops.map(shop => (
          <Stack key={shop.name} as="li" gap="m">
            <Heading level={2}>
              <Link href={shop.url}>{shop.name}</Link>
            </Heading>
            {shop.address && (
              <Text variant="small">
                <Link href={getMapLink(shop)}>
                  <FormattedAddress {...shop} />
                </Link>
              </Text>
            )}
          </Stack>
        ))}
      </Stack>
    </PageWithTitle>
  );
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Saya tidak nampak apa-apa kelebihan untuk eksport lalai, tetapi ia mempunyai beberapa isu:

  • Pemfaktoran semula yang lemah: menamakan semula modul dengan eksport lalai selalunya menyebabkan import sedia ada tidak berubah. Ini tidak berlaku dengan eksport bernama, di mana semua import dikemas kini selepas menamakan semula fungsi.
  • Ketidakkonsistenan: modul eksport lalai boleh diimport menggunakan sebarang nama, yang mengurangkan ketekalan dan kebolehcapaian pangkalan kod. Eksport bernama juga boleh diimport menggunakan nama lain menggunakan kata kunci sebagai untuk mengelakkan konflik penamaan, tetapi ia lebih jelas dan jarang dilakukan secara tidak sengaja.

Maklumat: Kami bercakap lebih lanjut tentang kebolehcapaian dalam bahagian Tulis kod boleh dipalsukan dalam bab Teknik lain.

Malangnya, sesetengah API pihak ketiga, seperti React.lazy() memerlukan eksport lalai, tetapi untuk semua kes lain, saya tetap menggunakan eksport bernama.

Elakkan fail tong

Fail tong ialah modul (biasanya dinamakan index.js atau index.ts) yang mengeksport semula sekumpulan modul lain:

function createPizza(order) {
  const pizza = new Pizza({
    base: order.size,
    sauce: order.sauce,
    cheese: 'Mozzarella'
  });

  if (order.kind === 'Veg') {
    pizza.toppings = vegToppings;
  } else if (order.kind === 'Meat') {
    pizza.toppings = meatToppings;
  }

  const oven = new Oven();

  if (oven.temp !== cookingTemp) {
    while (oven.temp < cookingTemp) {
      time.sleep(checkOvenInterval);
      oven.temp = getOvenTemp(oven);
    }
  }

  if (!pizza.baked) {
    oven.insert(pizza);
    time.sleep(cookTime);
    oven.remove(pizza);
    pizza.baked = true;
  }

  const box = new Box();
  pizza.boxed = box.putIn(pizza);
  pizza.sliced = box.slicePizza(order.size);
  pizza.ready = box.close();

  return pizza;
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Kelebihan utama adalah import yang lebih bersih. Daripada mengimport setiap modul secara individu:

function prepare(order) {
  const pizza = new Pizza({
    base: order.size,
    sauce: order.sauce,
    cheese: 'Mozzarella'
  });
  addToppings(pizza, order.kind);
  return pizza;
}

function addToppings(pizza, kind) {
  if (kind === 'Veg') {
    pizza.toppings = vegToppings;
  } else if (kind === 'Meat') {
    pizza.toppings = meatToppings;
  }
}

function bake(pizza) {
  const oven = new Oven();
  heatOven(oven);
  bakePizza(pizza, oven);
}

function heatOven(oven) {
  if (oven.temp !== cookingTemp) {
    while (oven.temp < cookingTemp) {
      time.sleep(checkOvenInterval);
      oven.temp = getOvenTemp(oven);
    }
  }
}

function bakePizza(pizza, oven) {
  if (!pizza.baked) {
    oven.insert(pizza);
    time.sleep(cookTime);
    oven.remove(pizza);
    pizza.baked = true;
  }
}

function pack(pizza) {
  const box = new Box();
  pizza.boxed = box.putIn(pizza);
  pizza.sliced = box.slicePizza(pizza.size);
  pizza.ready = box.close();
}

function createPizza(order) {
  const pizza = prepare(order);
  bake(pizza);
  pack(pizza);
  return pizza;
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Kami boleh mengimport semua komponen daripada fail tong:

function createPizza(order) {
  // Prepare pizza
  const pizza = new Pizza({
    base: order.size,
    sauce: order.sauce,
    cheese: 'Mozzarella'
  });

  // Add toppings
  if (order.kind == 'Veg') {
    pizza.toppings = vegToppings;
  } else if (order.kind == 'Meat') {
    pizza.toppings = meatToppings;
  }

  const oven = new Oven();

  if (oven.temp !== cookingTemp) {
    // Heat oven
    while (oven.temp < cookingTemp) {
      time.sleep(checkOvenInterval);
      oven.temp = getOvenTemp(oven);
    }
  }

  if (!pizza.baked) {
    // Bake pizza
    oven.insert(pizza);
    time.sleep(cookTime);
    oven.remove(pizza);
    pizza.baked = true;
  }

  // Box and slice
  const box = new Box();
  pizza.boxed = box.putIn(pizza);
  pizza.sliced = box.slicePizza(order.size);
  pizza.ready = box.close();

  return pizza;
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Walau bagaimanapun, fail tong mempunyai beberapa isu:

  • Kos penyelenggaraan: kita perlu menambah eksport setiap komponen baharu dalam fail tong, bersama-sama dengan item tambahan seperti jenis fungsi utiliti.
  • Kos prestasi: menyediakan gegaran pokok adalah rumit dan fail tong selalunya membawa kepada peningkatan saiz berkas atau kos masa jalan. Ini juga boleh melambatkan muat semula panas, ujian unit dan linters.
  • Import bulatan: mengimport daripada fail laras boleh menyebabkan import pekeliling apabila kedua-dua modul diimport daripada fail laras yang sama (contohnya, komponen Butang mengimport komponen Kotak).
  • Pengalaman pembangun: navigasi ke definisi fungsi menavigasi ke fail tong dan bukannya kod sumber fungsi; dan autoimport boleh keliru sama ada hendak mengimport daripada fail tong dan bukannya fail sumber.

Maklumat: TkDodo menerangkan kelemahan fail tong dengan terperinci.

Faedah fail tong terlalu kecil untuk membenarkan penggunaannya, jadi saya syorkan untuk mengelakkannya.

Satu jenis fail tong yang saya amat tidak suka ialah fail yang mengeksport satu komponen hanya untuk membenarkan pengimportannya sebagai ./components/button dan bukannya ./components/button/button.

Kekal terhidrat

Untuk troll DRYers (pembangun yang tidak pernah mengulangi kod mereka), seseorang mencipta istilah lain: WET, tulis semuanya dua kali atau kami seronok menaip, mencadangkan kami perlu menduplikasi kod di sekurang-kurangnya dua kali sehingga kita menggantikannya dengan abstraksi. Ia adalah jenaka, dan saya tidak bersetuju sepenuhnya dengan idea itu (kadangkala tidak mengapa untuk menduplikasi beberapa kod lebih daripada dua kali), tetapi ini adalah peringatan yang baik bahawa semua perkara yang baik adalah yang terbaik dalam kesederhanaan.

Pertimbangkan contoh ini:

function createPizza(order) {
  const pizza = new Pizza({
    base: order.size,
    sauce: order.sauce,
    cheese: 'Mozzarella'
  });

  if (order.kind === 'Veg') {
    pizza.toppings = vegToppings;
  } else if (order.kind === 'Meat') {
    pizza.toppings = meatToppings;
  }

  const oven = new Oven();

  if (oven.temp !== cookingTemp) {
    while (oven.temp < cookingTemp) {
      time.sleep(checkOvenInterval);
      oven.temp = getOvenTemp(oven);
    }
  }

  if (!pizza.baked) {
    oven.insert(pizza);
    time.sleep(cookTime);
    oven.remove(pizza);
    pizza.baked = true;
  }

  const box = new Box();
  pizza.boxed = box.putIn(pizza);
  pizza.sliced = box.slicePizza(order.size);
  pizza.ready = box.close();

  return pizza;
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Ini adalah contoh ekstrem Pengeringan kod, yang tidak menjadikan kod lebih mudah dibaca atau diselenggara, terutamanya apabila kebanyakan pemalar ini digunakan sekali sahaja. Melihat nama pembolehubah dan bukannya rentetan sebenar di sini adalah tidak membantu.

Mari sebaris semua pembolehubah tambahan ini. (Malangnya, pemfaktoran semula sebaris dalam Kod Visual Studio tidak menyokong sifat objek sebaris, jadi kami perlu melakukannya secara manual.)

function prepare(order) {
  const pizza = new Pizza({
    base: order.size,
    sauce: order.sauce,
    cheese: 'Mozzarella'
  });
  addToppings(pizza, order.kind);
  return pizza;
}

function addToppings(pizza, kind) {
  if (kind === 'Veg') {
    pizza.toppings = vegToppings;
  } else if (kind === 'Meat') {
    pizza.toppings = meatToppings;
  }
}

function bake(pizza) {
  const oven = new Oven();
  heatOven(oven);
  bakePizza(pizza, oven);
}

function heatOven(oven) {
  if (oven.temp !== cookingTemp) {
    while (oven.temp < cookingTemp) {
      time.sleep(checkOvenInterval);
      oven.temp = getOvenTemp(oven);
    }
  }
}

function bakePizza(pizza, oven) {
  if (!pizza.baked) {
    oven.insert(pizza);
    time.sleep(cookTime);
    oven.remove(pizza);
    pizza.baked = true;
  }
}

function pack(pizza) {
  const box = new Box();
  pizza.boxed = box.putIn(pizza);
  pizza.sliced = box.slicePizza(pizza.size);
  pizza.ready = box.close();
}

function createPizza(order) {
  const pizza = prepare(order);
  bake(pizza);
  pack(pizza);
  return pizza;
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Kini, kami mempunyai kod yang jauh lebih sedikit, dan lebih mudah untuk memahami perkara yang sedang berlaku serta lebih mudah untuk mengemas kini atau memadamkan ujian.

Saya telah menemui begitu banyak abstraksi tanpa harapan dalam ujian. Sebagai contoh, corak ini sangat biasa:

function createPizza(order) {
  // Prepare pizza
  const pizza = new Pizza({
    base: order.size,
    sauce: order.sauce,
    cheese: 'Mozzarella'
  });

  // Add toppings
  if (order.kind == 'Veg') {
    pizza.toppings = vegToppings;
  } else if (order.kind == 'Meat') {
    pizza.toppings = meatToppings;
  }

  const oven = new Oven();

  if (oven.temp !== cookingTemp) {
    // Heat oven
    while (oven.temp < cookingTemp) {
      time.sleep(checkOvenInterval);
      oven.temp = getOvenTemp(oven);
    }
  }

  if (!pizza.baked) {
    // Bake pizza
    oven.insert(pizza);
    time.sleep(cookTime);
    oven.remove(pizza);
    pizza.baked = true;
  }

  // Box and slice
  const box = new Box();
  pizza.boxed = box.putIn(pizza);
  pizza.sliced = box.slicePizza(order.size);
  pizza.ready = box.close();

  return pizza;
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Corak ini cuba mengelakkan panggilan mount(...) berulang dalam setiap kes ujian, tetapi ia menjadikan ujian lebih mengelirukan daripada yang sepatutnya. Mari sebaris mount() panggilan:

function FormattedAddress({ address, city, country, district, zip }) {
  return [address, zip, district, city, country]
    .filter(Boolean)
    .join(', ');
}

function getMapLink({ name, address, city, country, zip }) {
  return `https://www.google.com/maps/?q=${encodeURIComponent(
    [name, address, zip, city, country].filter(Boolean).join(', ')
  )}`;
}

function ShopsPage({ url, title, shops }) {
  return (
    <PageWithTitle url={url} title={title}>
      <Stack as="ul" gap="l">
        {shops.map(shop => (
          <Stack key={shop.name} as="li" gap="m">
            <Heading level={2}>
              <Link href={shop.url}>{shop.name}</Link>
            </Heading>
            {shop.address && (
              <Text variant="small">
                <Link href={getMapLink(shop)}>
                  <FormattedAddress {...shop} />
                </Link>
              </Text>
            )}
          </Stack>
        ))}
      </Stack>
    </PageWithTitle>
  );
}
Salin selepas log masuk
Salin selepas log masuk
Salin selepas log masuk

Selain itu, corak beforeEach hanya berfungsi apabila kita ingin memulakan setiap kes ujian dengan nilai yang sama, yang jarang berlaku:

const file = 'pizza.jpg';
const prefix = file.slice(0, -4);
// → 'pizza'
Salin selepas log masuk
Salin selepas log masuk

Untuk mengelakkan beberapa pertindihan semasa menguji komponen React, saya sering menambah objek Props lalai dan menyebarkannya di dalam setiap kes ujian:

const file = 'pizza.jpg';
const prefix = path.parse(file).name;
// → 'pizza'
Salin selepas log masuk
Salin selepas log masuk

Dengan cara ini, kami tidak mempunyai terlalu banyak pertindihan, tetapi pada masa yang sama, setiap kes ujian diasingkan dan boleh dibaca. Perbezaan antara kes ujian kini lebih jelas kerana lebih mudah untuk melihat prop unik setiap kes ujian.

Berikut ialah variasi yang lebih melampau bagi masalah yang sama:

// my_feature_util.js
const noop = () => {};

export const Utility = {
  noop
  // Many more functions…
};

// MyComponent.js
function MyComponent({ onClick }) {
  return <button onClick={onClick}>Hola!</button>;
}

MyComponent.defaultProps = {
  onClick: Utility.noop
};
Salin selepas log masuk
Salin selepas log masuk

Kita boleh menyelaraskan fungsi beforeEach() seperti yang kita lakukan dalam contoh sebelumnya:

const findByReference = (wrapper, reference) =>
  wrapper.find(reference);

const favoriteTaco = findByReference(
  ['Al pastor', 'Cochinita pibil', 'Barbacoa'],
  x => x === 'Cochinita pibil'
);

// → 'Cochinita pibil'
Salin selepas log masuk

Saya akan pergi lebih jauh dan menggunakan kaedah test.each() kerana kami menjalankan ujian yang sama dengan sekumpulan input yang berbeza:

function MyComponent({ onClick }) {
  return <button onClick={onClick}>Hola!</button>;
}

MyComponent.defaultProps = {
  onClick: () => {}
};
Salin selepas log masuk

Kini, kami telah mengumpulkan semua input ujian dengan keputusan yang dijangkakan di satu tempat, menjadikannya lebih mudah untuk menambah kes ujian baharu.

Maklumat: Lihat helaian tipu Jest and Vitest saya.


Cabaran terbesar dengan abstraksi ialah mencari keseimbangan antara menjadi terlalu tegar dan terlalu fleksibel, serta mengetahui bila untuk mula mengabstrakkan sesuatu dan bila untuk berhenti. Selalunya berbaloi untuk menunggu untuk melihat sama ada kita benar-benar perlu mengabstraksi sesuatu — berkali-kali, lebih baik tidak melakukannya.

Memang bagus untuk mempunyai komponen butang global, tetapi jika ia terlalu fleksibel dan mempunyai sedozen prop boolean untuk bertukar antara variasi yang berbeza, ia akan menjadi sukar untuk digunakan. Walau bagaimanapun, jika ia terlalu tegar, pembangun akan mencipta komponen butang mereka sendiri dan bukannya menggunakan butang yang dikongsi.

Kita harus berwaspada tentang membenarkan orang lain menggunakan semula kod kita. Terlalu kerap, ini mewujudkan gandingan yang ketat antara bahagian pangkalan kod yang sepatutnya bebas, memperlahankan pembangunan dan membawa kepada pepijat.

Mula berfikir tentang:

  • Mengumpul kod berkaitan dalam fail atau folder yang sama untuk memudahkan anda menukar, mengalihkan atau memadam.
  • Sebelum menambah pilihan lain pada abstraksi, fikirkan sama ada kes penggunaan baharu ini benar-benar ada.
  • Sebelum menggabungkan beberapa keping kod yang kelihatan serupa, fikirkan sama ada ia benar-benar menyelesaikan masalah yang sama atau kebetulan kelihatan sama.
  • Sebelum membuat ujian KERING, fikirkan sama ada ia akan menjadikannya lebih mudah dibaca dan diselenggara, atau sedikit pertindihan kod bukanlah satu isu.

Jika anda mempunyai sebarang maklum balas, mastodon saya, tweet saya, buka isu di GitHub, atau e-mel saya di artem@sapegin.ru. Dapatkan salinan anda.

Atas ialah kandungan terperinci Mencuci kod anda: bahagikan dan takluki, atau gabung dan berehat. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

sumber:dev.to
Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn
Artikel terbaru oleh pengarang
Tutorial Popular
Lagi>
Muat turun terkini
Lagi>
kesan web
Kod sumber laman web
Bahan laman web
Templat hujung hadapan