طراحی وب

طراحی Error Handling و پیام خطا

تفکیک خطای ورودی و سرویس و نتیجه نامعلوم، خلاصه خطا و Focus، Retry متناسب با اثر عملیات و نمونه مستقل اعتبارسنجی فرم.

1405/01/09 4 دقیقه مطالعه 853 بازدید

وقتی ارسال فرم ناموفق است، کاربر باید بفهمد آیا داده‌اش ثبت شده، کدام بخش نیاز به اصلاح دارد و چگونه ادامه دهد. پیام «مشکلی پیش آمد» فقط وقوع مشکل را می‌گوید. از طرف دیگر، توضیح علت حدسی یا وعده بررسی تیم بدون مسیر واقعی هم قابل اتکا نیست. Error Handling باید اطلاعات درست و رفتار قابل پیش‌بینی ارائه کند، حتی وقتی علت کامل هنوز معلوم نیست.

خطای ورودی، شکست ارتباط، محدودیت دسترسی و نتیجه نامعلوم عملیات یکسان نیستند. هر کدام قدم بعدی متفاوتی دارند. طراحی پیام از طبقه‌بندی همین وضعیت‌ها شروع می‌شود. نام برند و لحن دوستانه می‌توانند حضور داشته باشند، اما نباید جای اطلاعات لازم را بگیرند یا یک مسئله حساس مانند پرداخت را به شوخی تبدیل کنند.

خطا را به کاری که کاربر می‌تواند انجام دهد وصل کنید

در نمونه فرضی ارسال درخواست، عنوان خالی قابل اصلاح است، اختلال سرویس ممکن است با تلاش بعدی رفع شود و نبود اختیار به مسیر دیگری نیاز دارد. این مثال گزارش یک مشتری واقعی نیست. اگر هر سه با پیام «اطلاعات را درست وارد کنید» نشان داده شوند، کاربر برای اصلاح چیزی تلاش می‌کند که مشکل از آن نیست.

راهنمای Error Message در GOV.UK Design System خطای اعتبارسنجی را از مسئله سرویس یا اختیار کاربر جدا می‌کند. پیام کنار فیلد برای داده‌ای مناسب است که کاربر می‌تواند اصلاح کند. در مشکل عمومی سرویس، توضیح در سطح صفحه یا جریان ممکن است مناسب‌تر باشد. محل پیام باید علت و محدوده را منعکس کند.

کد فنی می‌تواند برای پشتیبانی مفید باشد، ولی برای همه کاربران شرح کافی نیست. اگر شناسه پیگیری دارید، آن را همراه توضیح قابل فهم ارائه کنید. متن خام Exception یا مسیر فایل داخلی را در پیام عمومی نگذارید. جزئیات لازم تشخیص در log مناسب ثبت شوند و داده حساس نیز باید در فرایند ثبت کنترل شود.

علت نامعلوم را با داستان مطمئن پر نکنید

Timeout ممکن است به معنی دریافت نشدن پاسخ باشد، نه انجام نشدن عملیات. در پرداخت یا ایجاد سفارش، تکرار فوری می‌تواند اثر دوباره داشته باشد. پیام باید محدوده دانسته‌ها را بیان کند و مسیر بررسی وضعیت معتبر را بدهد. «پرداخت نشد» فقط زمانی درست است که منبع معتبر نتیجه را تأیید کند، نه صرفاً چون مرورگر پاسخ نگرفته است.

عبارت «تیم ما مطلع شده» وقتی درست است که سازوکار ثبت و اطلاع‌رسانی واقعاً این نتیجه را دارد. وجود یک پیام در Console مرورگر، اطلاع تیم پشتیبانی را ثابت نمی‌کند. وعده رفع مشکل در پنج دقیقه نیز به مبنای عملیاتی نیاز دارد. اگر زمان معلوم نیست، آن را قطعی اعلام نکنید و راه ادامه یا پیگیری واقعی را معرفی کنید.

در نبود علت دقیق، می‌توانید بگویید دریافت نتیجه ناموفق بود و چه اطلاعاتی حفظ شده‌اند. این توضیح باید با رفتار واقعی هماهنگ باشد. اگر داده فرم در حافظه صفحه باقی مانده، وعده حفظ آن بعد از بستن مرورگر ندهید. مرز حالت محلی و ذخیره سروری را در پیام مشخص کنید تا کاربر تصمیم خروج را با اطلاعات درست بگیرد.

اعتبارسنجی باید قبل و بعد از ارسال هماهنگ باشد

راهنمای قالب ورودی می‌تواند از خطا پیشگیری کند. قانون را پیش از ارسال قابل یافتن بگذارید، به‌خصوص اگر غیرمعمول است. بررسی کلاینت برای بازخورد سریع مفید است، اما سمت سرور همچنان باید داده را معتبر بداند. اگر دو لایه قوانین متفاوتی دارند، کاربر ممکن است پس از پیام درست بودن با خطای جدیدی روبه‌رو شود.

زمان نمایش خطا را با نوع فیلد هماهنگ کنید. نشان دادن خطا پیش از اینکه فرد فرصت ورود داده داشته باشد، ممکن است مزاحم باشد. بازخورد پس از خروج از فیلد یا ارسال، بسته به کار مناسب است. اصلاح خطا نیز باید state را تازه کند، اما اعلان هر ضربه کلید می‌تواند فناوری کمکی را مشغول کند. این قرارداد را برای component تعریف کنید.

داده واردشده را هنگام خطا بی‌دلیل پاک نکنید. حتی ورودی نامعتبر می‌تواند برای فهم و اصلاح مفید باشد. داده حساس قرارداد جدا دارد و نباید بدون فکر در Storage باقی بماند. حفظ مقدار در همان فرم با ذخیره بلندمدت یکسان نیست. متن پیام و روش نگهداری باید با حساسیت و نیاز محصول هماهنگ باشند.

خلاصه خطا مسیر اصلاح را فراهم کند

راهنمای Error Summary در GOV.UK خلاصه‌ای با پیوند به فیلدهای مربوط را شرح می‌دهد. خلاصه و پیام کنار فیلد باید یک مشکل را با واژه‌های هماهنگ معرفی کنند. اگر خلاصه می‌گوید تاریخ لازم است و پیام فیلد می‌گوید تاریخ در آینده است، کاربر دو دستور متفاوت می‌گیرد.

Focus پس از ارسال ناموفق می‌تواند به خلاصه منتقل شود تا کاربر از وجود خطاها باخبر شود. پیوند هر مورد باید به input مربوط برسد و رابطه برنامه‌ای پیام با input وجود داشته باشد. برای یک خطای کوچک در همان زمینه، انتقال دائمی Focus لازم نیست. فرق ارسال فرم و اعتبارسنجی حین ورود را در رفتار حفظ کنید.

رنگ قرمز تنها نشانه نباشد. متن، حالت invalid و ارتباط توضیح به فیلد لازم‌اند. در Dark Mode، رنگ انتخاب‌شده ممکن است Contrast متفاوتی داشته باشد. مقاله Dark Mode برای جفت رنگ واقعی کمک می‌کند. پیام کم‌تأکید نیز باید خوانا بماند؛ کوچکی خطا، مجوز حذف خوانایی نیست.

نمونه کامل فرم نمایشی با خطای مرتبط

فایل زیر دو ورودی عنوان و ایمیل دارد. آن را با نام index.html ذخیره و باز کنید. هیچ داده‌ای به شبکه ارسال نمی‌شود و پیام موفقیت فقط نتیجه اعتبارسنجی همین نمونه را بیان می‌کند. CSS و JavaScript در فایل هستند و package یا تصویر لازم ندارد. با ارسال مقدار خالی یا قالب ایمیل نامعتبر، خلاصه و پیام کنار فیلد را مشاهده کنید.

<!doctype html>
<html lang="fa" dir="rtl">
<head>
  <meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
  <title>فرم نمایشی با Error Summary</title>
  <style>
    * { box-sizing:border-box; }
    body { margin:0; color:#17212b; background:#f4f6f9; font:1rem/1.9 system-ui,sans-serif; }
    main { max-inline-size:42rem; margin-inline:auto; padding:2rem 1rem; }
    form { background:#fff; padding:1rem; border:1px solid #64748b; }
    label { display:block; font-weight:700; margin-block-start:1rem; }
    input { font:inherit; inline-size:100%; padding:.4rem; border:1px solid #64748b; }
    input[aria-invalid="true"] { border:2px solid #a11212; }
    button { font:inherit; background:#164ca4; color:#fff; border:0; padding:.4rem .8rem; margin-block-start:1rem; }
    a { color:#164ca4; }
    .error { color:#a11212; }
    #summary { border:2px solid #a11212; padding:1rem; margin-block:1rem; }
    :focus-visible { outline:3px solid #164ca4; outline-offset:3px; }
    [hidden] { display:none; }
  </style>
</head>
<body>
  <main>
    <h1>اعتبارسنجی نمونه</h1>
    <p>این فرم فقط محلی است و داده‌ای ارسال نمی‌کند.</p>
    <noscript><p>برای اجرای نمونه، JavaScript لازم است.</p></noscript>
    <section id="summary" aria-labelledby="summary-title" tabindex="-1" hidden>
      <h2 id="summary-title">ورودی‌ها نیاز به اصلاح دارند</h2><ul id="summary-list"></ul>
    </section>
    <form id="form">
      <label for="subject">عنوان درخواست نمونه</label>
      <p id="subject-hint">در این نمونه دست‌کم سه نویسه لازم است.</p>
      <input id="subject" name="subject" required aria-describedby="subject-hint subject-error">
      <p id="subject-error" class="error" hidden></p>
      <label for="email">ایمیل</label>
      <p id="email-hint">قالب ایمیل را وارد کنید؛ مالکیت آن بررسی نمی‌شود.</p>
      <input id="email" name="email" type="email" required autocomplete="email" dir="ltr" aria-describedby="email-hint email-error">
      <p id="email-error" class="error" hidden></p>
      <button id="submit" type="submit" disabled>بررسی ورودی‌های نمونه</button>
      <p id="status" role="status"></p>
    </form>
  </main>
  <script>
    const form = document.getElementById('form');
    const subject = document.getElementById('subject');
    const email = document.getElementById('email');
    const summary = document.getElementById('summary');
    const list = document.getElementById('summary-list');
    const status = document.getElementById('status');
    const fields = [subject,email];
    form.noValidate = true;
    form.addEventListener('submit',event => {
      event.preventDefault();
      status.textContent = '';
      list.replaceChildren();
      for (const field of fields) {
        field.removeAttribute('aria-invalid');
        const message = document.getElementById(`${field.id}-error`);
        message.textContent = '';
        message.hidden = true;
      }
      const errors = [];
      if (Array.from(subject.value.trim()).length < 3) errors.push({ field:subject, message:'عنوان درخواست نمونه را با دست‌کم سه نویسه وارد کنید.' });
      if (email.validity.valueMissing) errors.push({ field:email, message:'ایمیل را وارد کنید.' });
      else if (email.validity.typeMismatch) errors.push({ field:email, message:'قالب ایمیل را بررسی کنید؛ برای نمونه name@example.com.' });
      for (const error of errors) {
        error.field.setAttribute('aria-invalid','true');
        const message = document.getElementById(`${error.field.id}-error`);
        message.textContent = error.message;
        message.hidden = false;
        const item = document.createElement('li');
        const link = document.createElement('a');
        link.href = `#${error.field.id}`;
        link.textContent = error.message;
        link.addEventListener('click',event => { event.preventDefault(); error.field.focus(); });
        item.append(link);
        list.append(item);
      }
      summary.hidden = errors.length === 0;
      if (errors.length) summary.focus();
      else status.textContent = 'ورودی‌های نمونه معتبرند؛ هیچ درخواست یا داده‌ای ثبت نشده است.';
    });
    document.getElementById('submit').disabled = false;
  </script>
</body>
</html>

در نمونه، اعتبارسنجی ایمیل از validity مرورگر استفاده می‌کند و regex حدسی جای آن گذاشته نشده است. این بررسی مالکیت یا امکان دریافت ایمیل را ثابت نمی‌کند. برای تأیید نشانی در محصول، جریان دیگری لازم است. حداقل عنوان هم صرفاً قانون آموزشی نمونه است و نباید به عنوان استاندارد عمومی تمام فرم‌ها معرفی شود.

با آماده شدن script، اعتبارسنجی بومی ارسال خاموش می‌شود تا پیام‌های مثال نمایش داده شوند. در نبود script، دکمه ارسال نمونه غیرفعال است و توضیح noscript وجود دارد؛ فرم مقصد سروری ندارد. داده‌ها هنگام خطا پاک نمی‌شوند و پیام‌ها با textContent ساخته می‌شوند. این محدوده آموزشی مانع می‌شود فرم نمایشی ادعای ثبت واقعی درخواست داشته باشد.

Retry به اثر عملیات وابسته است

RFC 9110 درباره HTTP Semantics مفاهیم safe و idempotent و رفتار روش‌ها را توضیح می‌دهد. برای طراحی تجربه، نتیجه عملی این است که تکرار هر درخواست را یکسان فرض نکنید. دریافت فهرست با ساخت رکورد یا پرداخت فرق دارد. روش HTTP به تنهایی قرارداد کامل جلوگیری از اثر تکراری یک محصول را مشخص نمی‌کند.

برای عملیات دارای اثر، تیم باید روش تشخیص درخواست تکراری و بازیابی نتیجه را تعریف کند. دکمه Retry باید با آن هماهنگ باشد. اگر داده هنوز نامعلوم است، اول وضعیت را بررسی کنید. وعده «دوباره بزنید، چیزی تکرار نمی‌شود» بدون پشتیبانی سرور دقیق نیست. نام و حالت کنترل باید همین محدودیت را نشان دهند.

در retry خودکار، تعداد و فاصله تلاش‌ها باید محدود و قابل توضیح باشند. انتظار بی‌پایان بدون راه خروج، کار را متوقف می‌کند. اگر سرویس زمان مشخصی برای تلاش بعدی اعلام می‌کند، می‌توانید از آن استفاده کنید؛ در غیر این صورت، شمارش معکوس تزئینی نسازید. حالت loading پس از هر تلاش باید به نتیجه، خطا یا لغو معتبر برسد.

خطاهای دسترسی و مسیر نیاز به پیام متناسب دارند

صفحه پیدا نشده، محتوای حذف‌شده و دسترسی ناکافی ممکن است برای کاربر ظاهر نزدیک داشته باشند، اما مسیر ادامه فرق دارد. اگر مقصد تغییر کرده، لینک معتبر بدهید. اگر ورود لازم است، اثر آن بر بازگشت به کار را مشخص کنید. به کاربر اجازه ندهید تصور کند همه داده حذف شده‌اند وقتی فقط اختیار مشاهده ندارد.

در بعضی جریان‌های امنیتی، افشای جزئیات می‌تواند نامناسب باشد. تیم باید محدوده اطلاعات عمومی را با قرارداد امنیت هماهنگ کند. «همیشه علت کامل را بگویید» قانون درستی برای تمام خطاها نیست. پیام همچنان می‌تواند قدم بعدی مفید بدهد بدون اینکه وجود حساب دیگران یا جزئیات داخلی را آشکار کند.

برای رابط تخصصی، اصطلاح فنی لازم را حفظ کنید و در محل مناسب توضیح دهید. کاربر توسعه‌دهنده ممکن است به status code نیاز داشته باشد. رابط عمومی می‌تواند خلاصه قابل فهم و شناسه پیگیری بدهد. حذف اجباری تمام اصطلاح‌های انگلیسی نیز همیشه وضوح ایجاد نمی‌کند؛ مخاطب و هدف پیگیری را در نظر بگیرید.

پیام و رخداد ثبت‌شده باید به هم برسند

برای هر state خطا، پیام عمومی، رخداد فنی و عمل ممکن را ثبت کنید. این فهرست به تیم کمک می‌کند بعد از تغییر API، متن قدیمی باقی نماند. شناسه پیگیری، اگر نمایش داده می‌شود، باید در ابزار پشتیبانی قابل یافتن باشد. جمع‌آوری داده تشخیص را محدود به نیاز کنید و ورودی حساس را بی‌دلیل در پیام یا log تکرار نکنید.

Loading States برای گذار انتظار و UX Writing برای متن نتیجه ادامه‌های مرتبط‌اند. در بررسی، از کاربر بخواهید خطا را توضیح دهد و قدم بعدی را انتخاب کند. اگر برداشت او با state واقعی متفاوت است، علت می‌تواند متن، محل یا رفتار باشد؛ اصلاح یک جمله بدون اصلاح قرارداد عمل کافی نیست.

واکنش به این مقاله

اشتراک‌گذاری این مقاله:
دیدگاه‌ها 0
۰ / ۲۰۰۰

هنوز دیدگاهی ثبت نشده

اولین نفری باشید که نظر می‌دهد!