ما کیفیت کد خود را با ممنوع کردن اگر 😎 بهبود دادیم

🙄 چرا «اگر» را ممنوع کنم؟
شما باید پوشش آزمایشی برنامه خود را بهبود ببخشید، که در حال حاضر 0٪ است (حداقل یکی از آن ها را دارید، همه یکی دارند).
بنابراین، برای شروع سریع، یک فایل بزرگ را باز می کنید. در داخل، یک روش عظیم وجود دارد … ناخالص (تغییر استدلال ها) … با تو در تو if
بیانیه.
چه کار میکنی؟
- 🤓 همه رو پاک میکنی آفرین. اما این پاسخی نیست که ما در اینجا به آن علاقه مندیم.
- 🤭 تو باهوشی، فایل را می بندی و یک فایل زیباتر باز می کنی.
- 👉 شما درست به داخل شیرجه میروید. خطر داشتن یک تست با کیفیت پایین: شاخههای بدون پوشش، فراخوانهای API اجرا شده، و مهمتر از همه، مشکلات در تعیین دقیق علت شکست تست.
و در نهایت، چرا در چنین شرایطی قرار می گیریم؟ چرا حتی با وجود تمام سخت گیری های موجود در دنیا، تست نمی تواند کیفیت خوبی داشته باشد؟ یکی از پاسخ ها نقض اصل مسئولیت واحد (SRP) است که می توان آن را به عنوان «روش های کوچک، ساده، خالص، نام گذاری مناسب بدون هیچ گونه غافلگیری ناخوشایند» خلاصه کرد. من حتی فراتر می روم و می گویم که پیروی از SRP نیازی به اظهار نظر در مورد بخش الگوریتمی کد شما را از بین می برد.
و چه در مورد ممنوعیت از if
بیانیه؟
در واقع، تمام این تفکرات بیشتر در مورد عبارات بلوکی است که می توانیم بعد از آن پیدا کنیم if
/while
/for
/switch
. اجتناب از آنها باعث ارتقای SRP “با طراحی” می شود.
🤨 کلمات خیلی زیاد است، کد را به من نشان دهید
علاوه بر دستاوردهای فنی، صحبت کردن در مورد آن با توسعه دهندگان دیگر نیز بسیار سرگرم کننده است: اول آنها فکر می کنند که این یک شوخی (یا مزخرف**) است، سپس می گویند امکان پذیر نیست یا نامربوط است. آنها فقط از لحظه ای که نمونه های عینی از نحوه انجام آن را نشان می دهیم شروع به بررسی این عمل می کنند.
مثال 1 – من یک if را در یک سوئیچ/مورد lmao قرار دادم
const getSheet = (color, glaze) => {
switch (color) {
case "white":
if (glaze) {
return "glaze-white-sheet";
}
return "white-sheet";
case "blue":
return "blue-sheet";
case "green":
return "green-sheet";
default:
throw new Error("Provided color is not available");
}
};
// Tests.
test("White and glaze", () => {
expect(getSheet("white", true)).toBe("glaze-white-sheet");
});
test("Simple white", () => {
expect(getSheet("white", false)).toBe("white-sheet");
});
test("Simple green", () => {
expect(getSheet("green", false)).toBe("green-sheet");
});
// …
👎 چه اشکالی دارد:
- خوانایی: باید حداکثر 5 تورفتگی را بخوانید.
-
مقیاس پذیری: باید خطوطی را در وسط اضافه کنید
getSheet
روش. -
تست ها: همه آزمون ها یکسان هستند
getSheet()
تابع. منطق مربوط به یک رنگ با منطق رنگ های دیگر مخلوط می شود. اگرswitch/case
مشکل دارد، هر آزمایش ممکن است خراب شود، اشکال را نمی توان دقیقاً پیدا کرد.
قدم اول: سوئیچ را با مقداری اگر تعویض کنید.
const getSheet = (color, glaze) => {
if (color === "white") {
if (glaze) return "glaze-white-sheet";
return "white-sheet";
}
if (color === "blue") return "blue-sheet";
if (color === "green") return "green-sheet";
throw new Error("Provided color is not available");
};
// Tests.
test("White & glaze", () => {
expect(getSheet("white", true)).toBe("glaze-white-sheet");
});
test("Simple white", () => {
expect(getSheet("white", false)).toBe("white-sheet");
});
test("Simple green", () => {
expect(getSheet("green", false)).toBe("green-sheet");
});
// …
👎 چه اشکالی دارد:
- خوانایی: باید حداکثر 3 تورفتگی را بخوانید.
-
مقیاس پذیری: باید خطوطی را در وسط اضافه کنید
getSheet
روش. - تست ها: مانند بالا.
👉 بسیار خوب، مرحله دوم: استفاده از یک شی برای جداسازی منطق ها.
const sheetsColorStrategies = {
white: (glaze) => glaze ? "glaze-white-sheet" : "white-sheet",
blue: (_glaze) => "blue-sheet",
green: (_glaze) => "green-sheet"
};
const defaultColorStrategy = (_glaze) => { throw new Error("Provided color is not available"); }
const getSheet = (color, glaze) =>
(sheetsColorStrategies[color] ?? defaultColorStrategy)(glaze);
// Tests.
test("getSheet dispatch with existing color", () => {
expect(getSheet("white", true)).toBe(sheetsColorStrategies["white"](true));
});
test("getSheet dispatch with unknown color", () => {
expect(() => getSheet("aaa", true)).toThrow("Provided color is not available");
});
test("white glaze color strategy", () => {
expect(sheetsColorStrategies["white"](true)).toBe("glaze-white-sheet");
});
test("white color strategy", () => {
expect(sheetsColorStrategies["white"](false)).toBe("white-sheet");
});
test("green color strategy", () => {
expect(sheetsColorStrategies["green"](true)).toBe("green-sheet");
});
💁 وقتی کد را می خوانید، با چه سرعتی می دانید چیست getSheet("green", false)
در حال بازگشت است؟
👌 آنچه “بدون بیانیه” در اینجا آورده است:
-
خوانایی:
getSheet
فقط تماس را به منطق رنگی مورد نظر (استراتژی) ارسال کنید. -
مقیاس پذیری: فقط باید یک رنگ جدید به داخل اضافه کنید
sheetsColorStrategies
. شما هیچ چیز دیگری را تحت تأثیر قرار نمی دهید. -
تست ها: همه چیز در اینجا به طور مستقل قابل آزمایش است (
getSheet
ارسال، رنگ).
مثال 2 – یک رشته بسازید
در این مثال، بیشتر در مورد خوانایی است.
let something = "a";
if (oneBoolean) something += "b";
something += "c";
if (screamingD) something += "D";
else something += "d";
something += "e";
بیایید سعی کنیم کد را بهبود ببخشیم:
[
"a",
oneBoolean && "b",
"c",
screamingD ? "D" : "d",
"e"
].filter(_ => _).join("");
👌 “نه اگر” اینجا می آورد:
- خوانایی: با رشته/آرایه/… پیچیده، می توانید خیلی سریعتر بفهمید که چگونه یک حرف/کلمه/مورد/… اضافه می شود.
مثال 3 – همان اگر دوباره و دوباره
const orderBook = (bookName, quantity) => {
let order;
if (quantity === 1) {
order = bookClient.orderOne(bookName);
if (isLogEnabled()) {
console.log("Ordered one book");
}
} else {
order = bookClient.orderMany(bookName, quantity);
if (isLogEnabled()) {
console.log("Ordered many books");
}
}
return order;
};
👎 چه اشکالی دارد:
- خوانایی: هر لاگ 3 خط میگیره روش رو آلوده میکنه. تورفتگی بیش از حد.
- کارایی: هر زمان که بخواهید چیزی را ثبت کنید، CPU یک شرط را دستور می دهد.
- تست ها: شاخه های بیش از حد برای پوشاندن در این روش.
بیایید سعی کنیم کد را بهبود ببخشیم:
const log = isLogEnabled() ? console.log : () => {};
const orderOneBook = (bookName) => {
order = bookClient.orderOne(bookName);
console.log("Ordered one book");
};
const orderManyBook = (bookName) => {
order = bookClient.orderMany(bookName, quantity);
log("Ordered many books");
};
const orderBook = (bookName, quantity) =>
quantity === 1 ? orderOneBook(bookName) : orderManyBook(bookName, quantity);
👌 آنچه “بدون بیانیه” در اینجا آورده است:
- خوانایی: گزارش یک خطی، بدون تورفتگی.
-
کارایی:
isLogEnabled()
یک بار نامیده می شود. - تست ها: همه چیز در اینجا به طور مستقل قابل آزمایش است.
مثال 4 – آزاردهنده برای بیانیه
const locations = [
{ city: "Lorient" },
{ city: "Brest" },
{ city: "Paris" }
];
const toCelsius = (degree) => (degree - 32) * 5/9;
const getWeathers = (locations, celsius) => {
if(!weatherService.isAuthenticated()) {
weatherService.authenticate();
}
for(const location of locations) {
if(location.city) {
let temp = weatherService.getTemperatureFeinr(location.city);
if(celsius) temp = toCelsius(temp);
location.temperature = temp;
}
}
return locations;
};
👎 چه اشکالی دارد:
-
خوانایی: 3 بیانیه بلوکی (
if
، پس یکif
تو در تو aif
تو در تو afor
). 😵💫 - کارایی: شرایط سلسیوس در هر حلقه ارزیابی می شود.
-
نگهداری: روش جهش اشیاء از
cities
آرایه به عنوان آرگومان ارسال شد.
👉 بیایید سعی کنیم کد را بهبود ببخشیم:
const locations = [
{ city: "Lorient" },
{ city: "Brest" },
{ city: "Paris" }
];
const toCelsius = (degree) => (degree - 32) * 5 / 9;
const getWeathers = (locations, celsius) => {
weatherService.isAuthenticated() || weatherService.authenticate();
const mayConvertUnit = celsius ? toCelsius : (degree) => degree;
return locations?.filter(location => location.city)
.map((location) => ({
...location,
temperature: mayConvertUnit(weatherService.getTemperatureFeinr(location.city))
}))
.filter(_ => _);
};
👌 آنچه “بدون بیانیه” در اینجا آورده است:
- خوانایی: احراز هویت یک خطی، بدون تورفتگی.
-
کارایی:
celsius
شرایط یک بار ارزیابی می شود. -
نگهداری:
getWeatchers
جهش نمی یابدcities
دیگر، روش خالص است.
🫵 اکنون آن را امتحان کنید!
اگر پروژه جاوا اسکریپت خود را با ESLint پر می کنید، می توانید با فعال کردن @jaouan/eslint-config-no-if خود را به چالش بکشید:
npm install -D eslint @jaouan/eslint-config-no-if
آن را فعال کنید .eslintrc
.
{
"extends": [
"@jaouan/eslint-config-no-if"
]
}
🤓 یک نتیجه گیری کوچک با نقاط گلوله
- ممکن است! 😉
- خوانایی اغلب یک موضوع عادت است. همه ما عادت هایی داریم.
- جایگزین کردن
if
با یک شرط دیگر (سه تایی، زنجیره ای اختیاری) عملکرد را افزایش نمی دهد. - تقسیم کد ما به روش های کوچک، پیروی از اصل مسئولیت پذیری واحد، آزمایش پذیری را افزایش می دهد و خطر رگرسیون را کاهش می دهد.
- استفاده نکن
if
داخل aif
داخل دیگریif
داخل … کمتر خسته می شود. - گاهی اوقات (به ندرت)، با استفاده از یک ساده
if
شاید انتخاب بهتری باشد - استفاده از
if
را می توان در هر زبانی کاهش داد. بله، برخی از زبان ها با ارائه ویژگی هایی مانند زنجیره اختیاری یا سه تایی (جاوا اسکریپت، تایپ اسکریپت، کاتلین) به ما کمک بیشتری می کنند. 👍
هنگامی که یک زبان جدید یاد می گیرید، اولین چیزی که یاد می گیرید چاپ «سلام جهان» است، سپس یاد می گیرید که چگونه این کار را انجام دهید if
. امیدوارم این مقاله نکاتی را به شما ارائه کرده باشد تا فراموش کنید که چگونه این کار را انجام دهید if
. 😁