بررسی عمیقتر Windows 11
طراحی و پیاده سازی زیرساختی برای مدیریت خطاهای حاصل از Business Rule Validationها در ServiceLayer
- Exceptions for flow control: why not?
- Exception handling for flow control is EVIL!
- Replacing Throwing Exceptions with Notification in Validations
- نکات کار با استثناءها در دات نت
- Defensive Programming - بازگشت نتایج قابل پیش بینی توسط متدها
- تفاوت اعتبارسنجی ورودیها با اعتبارسنجی مرتبط با قواعد تجاری
- صدور Exception یا بازگشت Result به عنوان خروجی متد، برای رسیدگی به خطاها
- صدور استثناءها چگونه بر روی کارآیی برنامه تاثیر میگذارند؟
- Fail Fast principle
- نحوهی صحیح استفادهی از Exceptions در برنامههای NET.
- Functional C#: Handling failures, input errors
- بررسی تاثیر صدور استثناءها بر روی کارآیی برنامه
- Code review: User Controller and error handling
- Improving Managed Code Performance (Exception Management)
- پشت صحنهی استثناءها
- Domain Command Patterns - Validation
استفاده از Exception برای نمایش پیغام برای کاربر نهایی
با صدور یک استثناء و مدیریت سراسری آن در بالاترین (خارجی ترین) لایه و نمایش پیغام مرتبط با آن به کاربر نهایی، میتوان از آن به عنوان ابزاری برای ارسال هر نوع پیغامی به کاربر نهایی استفاده کرد. اگر قوانین تجاری با موفقیت برآورده نشدهاند یا لازم است به هر دلیلی یک پیغام مرتبط با یک اعتبارسنجی تجاری را برای کاربر نمایش دهید، این روش بسیار کارساز میباشد و با یکبار وقت گذاشتن برای توسعه زیرساخت برای این موضوع، به عنوان یک Cross Cutting Concern تحت عنوان Exception Management، آزادی عمل زیادی در ادامه توسعه سیستم خود خواهید داشت.
اگر مطالب پیش نیاز را مطالعه کنید، قطعا روش مطرح شده را انتخاب نخواهید کرد؛ به همین دلیل به دنبال راه حل صحیح برخورد با این سناریوها بودم که نتیجه آن را در ادامه خواهیم دید.
راه حل صحیح برای برخورد با این سناریوها بازگشت یک Result میباشد که در مطلب قبلی هم تحت عنوان OperationResult مطرح شد.
public class Result { private static readonly Result SuccessResult = new Result(true, null); protected Result(bool succeeded, string message) { if (succeeded) { if (message != null) throw new ArgumentException("There should be no error message for success.", nameof(message)); } else { if (message == null) throw new ArgumentNullException(nameof(message), "There must be error message for failure."); } Succeeded = succeeded; Error = message; } public bool Succeeded { get; } public string Error { get; } [DebuggerStepThrough] public static Result Success() { return SuccessResult; } [DebuggerStepThrough] public static Result Failed(string message) { return new Result(false, message); } [DebuggerStepThrough] public static Result<T> Failed<T>(string message) { return new Result<T>(default, false, message); } [DebuggerStepThrough] public static Result<T> Success<T>(T value) { return new Result<T>(value, true, string.Empty); } [DebuggerStepThrough] public static Result Combine(string seperator, params Result[] results) { var failedResults = results.Where(x => !x.Succeeded).ToList(); if (!failedResults.Any()) return Success(); var error = string.Join(seperator, failedResults.Select(x => x.Error).ToArray()); return Failed(error); } [DebuggerStepThrough] public static Result Combine(params Result[] results) { return Combine(", ", results); } [DebuggerStepThrough] public static Result Combine<T>(params Result<T>[] results) { return Combine(", ", results); } [DebuggerStepThrough] public static Result Combine<T>(string seperator, params Result<T>[] results) { var untyped = results.Select(result => (Result) result).ToArray(); return Combine(seperator, untyped); } public override string ToString() { return Succeeded ? "Succeeded" : $"Failed : {Error}"; } }
مشابه کلاس بالا، در فریمورک ASP.NET Identity کلاسی تحت عنوان IdentityResult برای همین منظور در نظر گرفته شدهاست.
پراپرتی Succeeded نشان دهنده موفقت آمیز بودن یا عدم موفقیت عملیات (به عنوان مثال یک متد ApplicationService) میباشد. پراپرتی Error دربرگیرنده پیغام خطایی میباشد که قبلا از طریق Message مربوط به یک استثناء صادر شده، در اختیار بالاترین لایه قرار میگرفت. با استفاده از متد Combine، امکان ترکیب چندین Result حاصل از عملیات مختلف را خواهید داشت. متدهای استاتیک Failed و Success هم برای درگیر نشدن برای وهله سازی از کلاس Result در نظر گرفته شدهاند.
متد GetForEdit مربوط به MeetingService را در نظر بگیرید. به عنوان مثال وظیفه این متد بازگشت یک MeetingEditModel میباشد؛ اما با توجه به یکسری قواعد تجاری، بهعنوان مثال «امکان ویرایش جلسهای که پابلیش نهایی شدهاست، وجود ندارد و ...» لازم است خروجی این متد نیز در صورت Fail شدن، دلیل آن را به مصرف کننده ارائه دهد. از این رو کلاس جنریک Result را به شکل زیر خواهیم داشت:
public class Result<T> : Result { private readonly T _value; protected internal Result(T value, bool succeeded, string error) : base(succeeded, error) { _value = value; } public T Value { get { if (!Succeeded) throw new InvalidOperationException("There is no value for failure."); return _value; } } }
public static class ResultExtensions { public static Result<TK> OnSuccess<T, TK>(this Result<T> result, Func<T, TK> func) { return !result.Succeeded ? Result.Failed<TK>(result.Error) : Result.Success(func(result.Value)); } public static Result<T> Ensure<T>(this Result<T> result, Func<T, bool> predicate, string message) { if (!result.Succeeded) return Result.Failed<T>(result.Error); return !predicate(result.Value) ? Result.Failed<T>(message) : Result.Success(result.Value); } public static Result<TK> Map<T, TK>(this Result<T> result, Func<T, TK> func) { return !result.Succeeded ? Result.Failed<TK>(result.Error) : Result.Success(func(result.Value)); } public static Result<T> OnSuccess<T>(this Result<T> result, Action<T> action) { if (result.Succeeded) action(result.Value); return result; } public static T OnBoth<T>(this Result result, Func<Result, T> func) { return func(result); } public static Result OnSuccess(this Result result, Action action) { if (result.Succeeded) action(); return result; } public static Result<T> OnSuccess<T>(this Result result, Func<T> func) { return !result.Succeeded ? Result.Failed<T>(result.Error) : Result.Success(func()); } public static Result<TK> OnSuccess<T, TK>(this Result<T> result, Func<T, Result<TK>> func) { return !result.Succeeded ? Result.Failed<TK>(result.Error) : func(result.Value); } public static Result<T> OnSuccess<T>(this Result result, Func<Result<T>> func) { return !result.Succeeded ? Result.Failed<T>(result.Error) : func(); } public static Result<TK> OnSuccess<T, TK>(this Result<T> result, Func<Result<TK>> func) { return !result.Succeeded ? Result.Failed<TK>(result.Error) : func(); } public static Result OnSuccess<T>(this Result<T> result, Func<T, Result> func) { return !result.Succeeded ? Result.Failed(result.Error) : func(result.Value); } public static Result OnSuccess(this Result result, Func<Result> func) { return !result.Succeeded ? result : func(); } public static Result Ensure(this Result result, Func<bool> predicate, string message) { if (!result.Succeeded) return Result.Failed(result.Error); return !predicate() ? Result.Failed(message) : Result.Success(); } public static Result<T> Map<T>(this Result result, Func<T> func) { return !result.Succeeded ? Result.Failed<T>(result.Error) : Result.Success(func()); } public static TK OnBoth<T, TK>(this Result<T> result, Func<Result<T>, TK> func) { return func(result); } public static Result<T> OnFailure<T>(this Result<T> result, Action action) { if (!result.Succeeded) action(); return result; } public static Result OnFailure(this Result result, Action action) { if (!result.Succeeded) action(); return result; } public static Result<T> OnFailure<T>(this Result<T> result, Action<string> action) { if (!result.Succeeded) action(result.Error); return result; } public static Result OnFailure(this Result result, Action<string> action) { if (!result.Succeeded) action(result.Error); return result; } }
[HttpPost, AjaxOnly, ValidateAntiForgeryToken, ValidateModelState] public virtual async Task<ActionResult> Create([Bind(Prefix = "Model")]MeetingCreateModel model) { var result = await _service.CreateAsync(model); return result.OnSuccess(() => { }) .OnFailure(() => { }) .OnBoth(r => r.Succeeded ? InformationNotification("Messages.Save.Success") : ErrorMessage(r.Error)); }
یا در حالتهای پیچیده تر:
var result = await _service.CreateAsync(new TenantAwareEntityCreateModel()); return Result.Combine(result, Result.Success(), Result.Failed("نتیجه یک متد دیگر به عنوان مثال")) .OnSuccess(() => { }) .OnFailure(() => { }) .OnBoth(r => r.Succeeded ? Json("OK") : Json(r.Error));
ترکیب با الگوی Maybe یا Option
public struct Maybe<T> : IEquatable<Maybe<T>> where T : class { private readonly T _value; private Maybe(T value) { _value = value; } public bool HasValue => _value != null; public T Value => _value ?? throw new InvalidOperationException(); public static Maybe<T> None => new Maybe<T>(); public static implicit operator Maybe<T>(T value) { return new Maybe<T>(value); } public static bool operator ==(Maybe<T> maybe, T value) { return maybe.HasValue && maybe.Value.Equals(value); } public static bool operator !=(Maybe<T> maybe, T value) { return !(maybe == value); } public static bool operator ==(Maybe<T> left, Maybe<T> right) { return left.Equals(right); } public static bool operator !=(Maybe<T> left, Maybe<T> right) { return !(left == right); } /// <inheritdoc /> /// <summary> /// Avoid boxing and Give type safety /// </summary> /// <param name="other"></param> /// <returns></returns> public bool Equals(Maybe<T> other) { if (!HasValue && !other.HasValue) return true; if (!HasValue || !other.HasValue) return false; return _value.Equals(other.Value); } /// <summary> /// Avoid reflection /// </summary> /// <param name="obj"></param> /// <returns></returns> public override bool Equals(object obj) { if (obj is T typed) { obj = new Maybe<T>(typed); } if (!(obj is Maybe<T> other)) return false; return Equals(other); } /// <summary> /// Good practice when overriding Equals method. /// If x.Equals(y) then we must have x.GetHashCode()==y.GetHashCode() /// </summary> /// <returns></returns> public override int GetHashCode() { return HasValue ? _value.GetHashCode() : 0; } public override string ToString() { return HasValue ? _value.ToString() : "NO VALUE"; } }
public static Result<T> ToResult<T>(this Maybe<T> maybe, string message) where T : class { return !maybe.HasValue ? Result.Failed<T>(message) : Result.Success(maybe.Value); }
Result<Customer> customerResult = _customerRepository.GetById(model.Id) .ToResult("Customer with such Id is not found: " + model.Id);
همچنین متدهای الحاقی زیر را نیز برای ساختار داده Maybe میتوان در نظر گرفت:
public static T GetValueOrDefault<T>(this Maybe<T> maybe, T defaultValue = default) where T : class { return maybe.GetValueOrDefault(x => x, defaultValue); } public static TK GetValueOrDefault<T, TK>(this Maybe<T> maybe, Func<T, TK> selector, TK defaultValue = default) where T : class { return maybe.HasValue ? selector(maybe.Value) : defaultValue; } public static Maybe<T> Where<T>(this Maybe<T> maybe, Func<T, bool> predicate) where T : class { if (!maybe.HasValue) return default(T); return predicate(maybe.Value) ? maybe : default(T); } public static Maybe<TK> Select<T, TK>(this Maybe<T> maybe, Func<T, TK> selector) where T : class where TK : class { return !maybe.HasValue ? default : selector(maybe.Value); } public static Maybe<TK> Select<T, TK>(this Maybe<T> maybe, Func<T, Maybe<TK>> selector) where T : class where TK : class { return !maybe.HasValue ? default(TK) : selector(maybe.Value); } public static void Execute<T>(this Maybe<T> maybe, Action<T> action) where T : class { if (!maybe.HasValue) return; action(maybe.Value); } }
- استفاده از الگوی Specification برای زمانیکه منطقی قرار است هم برای اعتبارسنجی درون حافظهای استفاده شود و همچنین برای اعمال فیلتر برای واکشی دادهها؛ در واقع دو Use-case استفاده از این الگو حداقل یکجا وجود داشته باشد. استفاده از این مورد برای Domain Validation در سناریوهای پیچیده بسیار پیشنهاد میشود.
- استفاده از Domain Eventها برای اعمال اعتبارسنجیهای مرتبط با قواعد تجاری تنها در شرایط inter-application communication و در شرایط inner-application communication به صورت صریح، اعتبارسنجیهای مرتبط با قواعد تجاری را در جریان اصلی برنامه پیاده سازی کنید.
Rider EAP 17 منتشر شد .
What’s in this build? Significant performance improvements were made, shared projects are now supported, code inspection severity can be configured, settings for our unit test runner can be edited, various updates to the NuGet tool window UI, and more. Check out the list of fixes in this build if you feel like digging into details.
نتایج نظرسنجی State of JS 2020
23,765 people from 137 countries took part in the recent State of JS survey and while there are some common criticisms of the project, the results are nonetheless interesting and we’ll be digging into some in forthcoming issues. Standouts include:
- Svelte took the top frontend framework crown from React for developer satisfaction.
- Testing Library jumped straight to #1 for testing libraries.
- More developers than ever are producing PWAs or using WebAssembly.
- 86% of respondents are using VS Code to work on their code.
سری eShopSupport
ولی در نهایت همان روش سنتی استفاده از رشتهها، هر چند کمی طولانیتر است، اما بهترین کارآیی و کمترین سربار را هم دارد.
مطلبی هم که من در اینجا عنوان کردم در مورد دیباگ یک سیستم مبتنی بر NHibernate هست و بدیهی است قرار نیست در محیط کاری از آن استفاده شود.