La réponse simple est, chaque fois qu'une opération est impossible (à cause de l'une ou l'autre application OU parce qu'elle violerait la logique métier). Si une méthode est invoquée et qu'il est impossible de faire ce pour quoi la méthode a été écrite, lancez une exception. Un bon exemple est que les constructeurs lèvent toujours des ArgumentExceptions si une instance ne peut pas être créée à l'aide des paramètres fournis. Un autre exemple est InvalidOperationException, qui est levée lorsqu'une opération ne peut pas être effectuée en raison de l'état d'un autre membre ou de membres de la classe.
Dans votre cas, si une méthode telle que Login (nom d'utilisateur, mot de passe) est invoquée, si le nom d'utilisateur n'est pas valide, il est en effet correct de lever une UserNameNotValidException ou PasswordNotCorrectException si le mot de passe est incorrect. L'utilisateur ne peut pas être connecté en utilisant le (s) paramètre (s) fourni (s) c'est impossible car cela violerait l'authentification), alors lève une exception. Bien que je puisse faire hériter vos deux exceptions de ArgumentException.
Cela dit, si vous ne souhaitez PAS lever une exception car un échec de connexion peut être très courant, une stratégie consiste à créer une méthode qui renvoie des types qui représentent différents échecs. Voici un exemple:
{ // 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))
{ }
}
La plupart des développeurs apprennent à éviter les exceptions en raison des frais généraux causés par leur lancement. C'est génial d'être soucieux des ressources, mais généralement pas au détriment de la conception de votre application. C'est probablement la raison pour laquelle on vous a dit de ne pas lever vos deux exceptions. L'utilisation ou non d'exceptions se résume généralement à la fréquence à laquelle l'exception se produira. Si c'est un résultat assez courant ou assez prévisible, c'est à ce moment-là que la plupart des développeurs éviteront les exceptions et créeront plutôt une autre méthode pour indiquer l'échec, en raison de la consommation supposée de ressources.
Voici un exemple pour éviter d'utiliser des exceptions dans un scénario comme celui qui vient d'être décrit, en utilisant le modèle 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;
}
}