مجموعه بلوک های ساختمان: بلوک های کد را از یکدیگر جدا کنید؟

آیا همه ما به طور مداوم همان بیت های کد را دوباره ایجاد نمی کنیم؟
این فراتر از کد دیگ بخار است. ما در حال اضافه کردن بیت های کد یکسان به هر پروژه هستیم
میانبرهای git، همان فرمتکنندههای لاگ، همان دکوراتورهای مجوز، …
در اینجا من شروع به جمع آوری یک مجموعه شخصی از بلوک های ساختمانی کرده ام.
و من با این شروع می کنم: کدی که بلوک های کد را ایزوله (عایق) می کند.
اشاره: عملکرد کامل در پست وبلاگ من است
مثال: من برای همه اعضای تیم ایمیل می فرستم.
اگر ایمیل 1 نفر باعث ایجاد اشکال شود، میخواهم کد از این شخص رد شود و به ارسال ایمیل برای دیگران ادامه دهد.
ساده لوح – بدون رسیدگی به خطا:
def send_all_emails(users: list[User]):
for user in users:
send_report_email_to_user(user)
اگر لیستی از 10 کاربر داشته باشیم، اما با یک خطای غیرمنتظره مواجه شویم
با گزارش کاربر شماره 3، فقط 2 کاربر اول ایمیل را دریافت می کنند، دیگران دریافت نمی کنند.
با رسیدگی به خطا:
def send_all_emails(users: list[User]):
for user in users:
with suppress_and_log_exc():
# ↑ Will catch any Exception, log it correctly
# and then continue with the next user
send_report_email_to_user(user)
چی suppress_and_log_exc
انجام می دهد
- بعضی ها را می گیرد
Exception
کلاس - آن را به درستی ثبت می کند
- اجازه می دهد تا کد ادامه یابد
بنابراین، چیزی شبیه به این:
@contextmanager
def suppress_and_log_exc(
*,
action_desc: str,
# ↑ Let's require an identifier. The error msg will be more helpful this way.
):
try:
yield
except Exception as exc:
logger.error(
f"Error `{exc.__class__.__name__}` occurred while {action_desc}",
exc_info=exc,
)
موارد استفاده احتمالی
این کد هر زمان که لیستی از اقدامات مستقل از یکدیگر داشته باشید به کارتان می آید.
به عنوان مثال: ما بعد از یک رویداد عوارض جانبی مختلفی را ایجاد می کنیم.
شاید کاربر جدیدی ثبت نام کرده باشد، بنابراین، ما می خواهیم:
- یک Slack high-five را به تیم توسعه دهنده بفرستید و همچنین
- یک بلیط برای تیم موفقیت مشتری ایجاد کنید تا با آنها تماس بگیرد و همچنین
- ….
اگر هر یک از این عوارض جانبی شکست بخورد، بقیه هنوز باید تحریک شوند.
مثال دیگر: ما یک سیستم چند مستاجر داریم و میخواهیم برای هر مستأجر یک کار کرفس را راهاندازی کنیم.
اگر کد ایجاد تسک Celery برای مشتری شماره 5 مشکل دارد، باز هم می خواهیم وظایف را برای مشتریان 6 تا 1.000.000 ایجاد کنیم.
احمقانه است، اگر کد ما در مثلاً … ارسال صورتحساب ماهانه با شماره مشتری 5 و حتی سعی نکنید آن را به مشتری شماره 6 و 7 و غیره ارسال کنید، احمقانه است.
افزودن تنظیمات بیشتر به contextmanager
ما میتوانیم با افزودن تنظیماتی برای این تابع، آن را قابل تنظیمتر کنیم:
- سطح گزارش – برخی چیزها در واقعیت فقط یک هشدار یا یک اطلاعات هستند
- نقشه سطوح گزارش – یک سطح گزارش خاص در هر کلاس استثنایی
- کلاس استثنایی که می خواهیم بگیریم – شاید ما فقط به EmailSendingException اهمیت می دهیم
- دادههای گزارش بیشتر – بنابراین بهتر میتوانیم متوجه شویم که با دیدن این پیام در Sentry چه اشتباهی رخ داده است
- یک پاسخ به خطا – تابعی که هنگام وقوع خطا فراخوانی می شود و می تواند برای پاکسازی خطای سفارشی استفاده شود
- .. هر چی دلت بخواد .. 💖
بنابراین، در اینجا کد کامل است.
فین: الان همه با هم
کد مثال ما اکنون می تواند به شکل زیر باشد:
def send_all_emails(users: list[User]):
for user in users:
with suppress_and_log_exc(
action_desc="Sending my very special report email",
extra={"user": user.id}
):
# ↑ Will catch any Exception, log it correctly and then.
send_report_email_to_user(user)
یا ممکن است این چنین پیچیده باشد:
import logging
def send_all_emails(users: list[User]):
for user in users:
with suppress_and_log_exc(
action_desc="Sending my very special report email",
log_level=logging.WARNING, # <- default log level
log_level_maps={EmailIsInvalidException: logging.INFO},
exc_types_to_catch=(ReportException, EmailException,),
extra={"user": user.id}
):
# ↑ Will catch any Exception, log it correctly and then.
send_report_email_to_user(user)