Проста відповідь - коли операція неможлива (через будь-яке застосування АБО, тому що це порушить бізнес-логіку). Якщо метод викликається і неможливо зробити те, про що було написано, киньте виняток. Хороший приклад - конструктори завжди викидають ArgumentExceptions, якщо екземпляр не може бути створений за допомогою наданих параметрів. Інший приклад - InvalidOperationException, який викидається, коли операцію неможливо виконати через стан іншого члена або членів класу.
У вашому випадку, якщо викликається такий спосіб, як Login (ім'я користувача, пароль), якщо ім'я користувача недійсне, це справді правильне закидання UserNameNotValidException або PasswordNotCorrectException, якщо пароль невірний. Користувач не може входити в систему за допомогою наданих параметрів (ів) (тобто це неможливо, оскільки це порушило б автентифікацію), тому киньте виняток. Хоча, можливо, у вас два винятки успадковуються від ArgumentException.
Сказавши, що якщо ви хочете НЕ кидати Виняток, оскільки помилка входу може бути дуже поширеною, одна із стратегій полягає в тому, щоб натомість створити метод, який повертає типи, що представляють різні збої. Ось приклад:
{ // class
...
public LoginResult Login(string user, string password)
{
if (IsInvalidUser(user))
{
return new UserInvalidLoginResult(user);
}
else if (IsInvalidPassword(user, password))
{
return new PasswordInvalidLoginResult(user, password);
}
else
{
return new SuccessfulLoginResult();
}
}
...
}
public abstract class LoginResult
{
public readonly string Message;
protected LoginResult(string message)
{
this.Message = message;
}
}
public class SuccessfulLoginResult : LoginResult
{
public SucccessfulLogin(string user)
: base(string.Format("Login for user '{0}' was successful.", user))
{ }
}
public class UserInvalidLoginResult : LoginResult
{
public UserInvalidLoginResult(string user)
: base(string.Format("The username '{0}' is invalid.", user))
{ }
}
public class PasswordInvalidLoginResult : LoginResult
{
public PasswordInvalidLoginResult(string password, string user)
: base(string.Format("The password '{0}' for username '{0}' is invalid.", password, user))
{ }
}
Більшість розробників навчають уникати винятків через накладні витрати, спричинені кидком. Чудово бути ресурсно усвідомленим, але зазвичай це не за рахунок вашої розробки додатків. Це, мабуть, причина, яку вам сказали не кидати свої два винятки. Використовувати Винятки чи ні, як правило, зводиться до того, як часто Виняток буде відбуватися. Якщо це досить поширений або досить очікуваний результат, саме тоді більшість розробників уникають винятків і замість цього створюють інший метод для позначення невдачі через передбачуване споживання ресурсів.
Ось приклад уникнення використання винятків у сценарії, як щойно описаний, за допомогою шаблону Try ():
public class ValidatedLogin
{
public readonly string User;
public readonly string Password;
public ValidatedLogin(string user, string password)
{
if (IsInvalidUser(user))
{
throw new UserInvalidException(user);
}
else if (IsInvalidPassword(user, password))
{
throw new PasswordInvalidException(password);
}
this.User = user;
this.Password = password;
}
public static bool TryCreate(string user, string password, out ValidatedLogin validatedLogin)
{
if (IsInvalidUser(user) ||
IsInvalidPassword(user, password))
{
return false;
}
validatedLogin = new ValidatedLogin(user, password);
return true;
}
}