web-dev-qa-db-fra.com

Si vous transmettez toujours le strict minimum nécessaire à une fonction dans des cas comme celui-ci

Disons que j'ai une fonction IsAdmin qui vérifie si un utilisateur est un administrateur. Supposons également que la vérification de l'administrateur se fasse en comparant l'ID utilisateur, le nom et le mot de passe à une sorte de règle (sans importance).

Dans ma tête, il y a alors deux signatures de fonction possibles pour cela:

public bool IsAdmin(User user);
public bool IsAdmin(int id, string name, string password);

Je choisis le plus souvent le deuxième type de signature, en pensant que:

  • La signature de fonction donne au lecteur beaucoup plus d'informations
  • La logique contenue dans la fonction n'a pas besoin de connaître la classe User
  • Il en résulte généralement un peu moins de code à l'intérieur de la fonction

Cependant, je remets parfois en question cette approche et je me rends également compte qu’à un moment donné, elle deviendrait difficile à manier. Si, par exemple, une fonction mappait entre dix champs d'objet différents dans un booléen résultant, j'enverrais évidemment l'objet entier. Mais à part un exemple frappant comme celui-là, je ne vois aucune raison de passer dans l'objet réel.

J'apprécierais tout argument pour l'un ou l'autre style, ainsi que toutes les observations générales que vous pourriez offrir.

Je programme à la fois dans des styles orientés objet et fonctionnels, donc la question doit être considérée comme concernant tous les idiomes.

82
Anders Arpi

Personnellement, je préfère la première méthode de simplement IsAdmin(User user)

Il est beaucoup plus facile à utiliser et si vos critères pour IsAdmin changent à une date ultérieure (peut-être en fonction des rôles ou isActive), vous n'avez pas besoin de réécrire la signature de votre méthode partout.

Il est également probablement plus sûr car vous ne faites pas de publicité sur les propriétés qui déterminent si un utilisateur est un administrateur ou non, ou transmettez la propriété du mot de passe partout. Et syrion souligne que ce qui se passe lorsque votre id ne correspond pas à la name/password?

La longueur du code à l'intérieur d'une fonction ne devrait pas vraiment avoir d'importance, à condition que la méthode fasse son travail, et je préférerais de beaucoup avoir un code d'application plus court et plus simple que le code de la méthode auxiliaire.

123
Rachel

La première signature est supérieure car elle permet l'encapsulation de la logique liée à l'utilisateur dans l'objet User lui-même. Il n'est pas avantageux d'avoir une logique pour la construction du tuple "id, nom, mot de passe" éparpillé sur votre base de code. De plus, cela complique la fonction isAdmin: que se passe-t-il si quelqu'un passe dans un id qui ne correspond pas au name, par exemple? Que faire si vous voulez vérifier si un utilisateur donné est un administrateur dans un contexte où vous ne devriez pas connaître son mot de passe?

En outre, comme une note sur votre troisième point en faveur du style avec des arguments supplémentaires; cela peut entraîner "moins de code à l'intérieur de la fonction", mais où va cette logique? Cela ne peut pas disparaître! Au lieu de cela, il est réparti autour de chaque endroit où la fonction est appelée. Pour enregistrer cinq lignes de code en un seul endroit, vous avez payé cinq lignes par utilisation de la fonction!

82
asthasr

Le premier, mais pas (seulement) pour les raisons avancées par d'autres.

public bool IsAdmin(User user);

Il s'agit d'un type sûr. D'autant plus que l'utilisateur est un type que vous avez défini vous-même, il y a peu ou pas de possibilité de transposer ou de changer d'argument. Il fonctionne clairement sur les utilisateurs et n'est pas une fonction générique acceptant un entier et deux chaînes. C'est une grande partie de l'intérêt d'utiliser un langage de type sûr comme Java ou C # à quoi ressemble votre code. Si vous demandez un langage spécifique, vous voudrez peut-être ajouter une balise pour cette langue à votre question.

public bool IsAdmin(int id, string name, string password);

Ici, n'importe quel int et deux chaînes feront l'affaire. Qu'est-ce qui vous empêche de transposer le nom et le mot de passe? Le second est plus de travail à utiliser et offre plus de possibilités d'erreur.

Vraisemblablement, les deux fonctions nécessitent que user.getId (), user.getName () et user.getPassword () soient appelés, soit à l'intérieur de la fonction (dans le premier cas), soit avant d'appeler la fonction (dans le second), donc le la quantité de couplage est la même dans les deux cas. En fait, puisque cette fonction n'est valide que sur les utilisateurs, en Java j'éliminerais tous les arguments et en ferais une méthode d'instance sur les objets User:

user.isAdmin();

Étant donné que cette fonction est déjà étroitement couplée aux utilisateurs, il est logique de l'intégrer à ce qu'est un utilisateur.

P.S. Je suis sûr que ce n'est qu'un exemple, mais il semble que vous stockiez un mot de passe. À la place, vous devez uniquement stocker un hachage de mot de passe sécurisé sur le plan cryptographique. Lors de la connexion, le mot de passe fourni doit être haché de la même manière et comparé au hachage stocké. L'envoi de mots de passe en texte brut doit être évité. Si vous violez cette règle et que isAdmin (int Str Str) devait enregistrer le nom d'utilisateur, alors si vous avez transposé le nom et le mot de passe dans votre code, le mot de passe pourrait être enregistré à la place, ce qui crée un problème de sécurité (n'écrivez pas de mots de passe dans les journaux ) et est un autre argument en faveur du passage d'un objet au lieu de ses composants (ou en utilisant une méthode de classe).

65
GlenPeterson

Si la langue de votre choix ne transmet pas les structures par valeur, alors (User user) la variante semble meilleure. Et si un jour vous décidiez de supprimer le nom d'utilisateur et d'utiliser simplement l'ID/mot de passe pour identifier l'utilisateur?

En outre, cette approche conduit inévitablement à des appels de méthode longs et exagérés, ou à des incohérences. Passer 3 arguments est-il correct? Que diriez-vous 5? Ou 6? Pourquoi penser à ça, si passer un objet est bon marché en termes de ressources (encore moins cher que de passer 3 arguments, probablement)?

Et je suis (en quelque sorte) en désaccord avec le fait que la deuxième approche donne plus d'informations au lecteur - intuitivement, l'appel de méthode demande "si l'utilisateur a des droits d'administrateur", et non "si la combinaison id/nom/mot de passe donnée a des droits d'administrateur".

Donc, à moins que le passage de l'objet User n'entraîne la copie d'une grande structure, la première approche est plus propre et plus logique pour moi.

6

J'aurais probablement les deux. La première en tant qu'API externe, avec un certain espoir de stabilité dans le temps. Le second en tant qu'implémentation privée appelée par l'API externe.

Si à un moment ultérieur je dois changer la règle de vérification, j'écrirais simplement une nouvelle fonction privée avec une nouvelle signature appelée par la externe.

Cela a l'avantage de faciliter la modification de l'implémentation interne. De plus, il est très courant qu'à un moment donné, vous ayez besoin des deux fonctions disponibles en même temps et appeliez l'une ou l'autre en fonction d'un changement de contexte externe (par exemple, vous pouvez avoir des anciens utilisateurs coexistants et de nouveaux utilisateurs avec des champs différents).

En ce qui concerne le titre de la question, dans les deux cas, vous fournissez le minimum d'informations nécessaires. Un utilisateur semble être la plus petite structure de données liée à l'application contenant les informations. Les trois champs id, mot de passe et nom semblent être les plus petites données d'implémentation réellement nécessaires, mais ce ne sont pas vraiment des objets de niveau application.

En d'autres termes, si vous traitez avec une base de données, un utilisateur serait un enregistrement, tandis que l'identifiant, le mot de passe et la connexion seraient des champs dans cet enregistrement.

3
kriss

Il existe un point négatif pour envoyer des classes (types de référence) aux méthodes, à savoir Vulnérabilité d'affectation de masse . Imaginez qu'au lieu de la méthode IsAdmin(User user), j'ai la méthode UpdateUser(User user).

Si la classe User a une propriété booléenne appelée IsAdmin, et si je ne la vérifie pas pendant l'exécution de ma méthode, alors je suis vulnérable à l'affectation en masse. GitHub a été attaqué par cette même signature en 2012.

3
Saeed Neamati

J'irais avec cette approche:

public bool IsAdmin(this IUser user) { /* ... */ }

public class User: IUser { /* ... */ }

public interface IUser
{
    string Username {get;}
    string Password {get;}
    IID Id {get;}
}
  • L'utilisation du type d'interface comme paramètre pour cette fonction vous permet de passer des objets utilisateur, de définir des objets utilisateur fictifs pour les tests et vous donne plus de flexibilité concernant l'utilisation et la maintenance de cette fonction.

  • J'ai également ajouté le mot clé this pour faire de la fonction une méthode d'extension de toutes les classes IUser; de cette façon, vous pouvez écrire du code de manière plus OO de manière conviviale et utiliser cette fonction dans les requêtes Linq. Il serait plus simple de définir cette fonction dans IUser et User, mais je suppose qu'il y a une raison pour laquelle vous décidé de mettre cette méthode en dehors de cette classe?

  • J'utilise l'interface personnalisée IID au lieu de int car cela vous permet de redéfinir les propriétés de votre ID; par exemple. si vous devez changer de int à long, Guid ou autre chose. C'est probablement exagéré, mais il est toujours bon d'essayer de rendre les choses flexibles de sorte que vous ne soyez pas enfermé dans les premières décisions.

  • NB: Votre objet IUser peut se trouver dans un espace de noms et/ou un assemblage différent de la classe User, vous avez donc la possibilité de garder votre code utilisateur complètement séparé de votre code IsAdmin, avec eux ne partageant qu'une seule bibliothèque référencée. Exactement ce que vous faites, il y a un appel pour savoir comment vous pensez que ces articles doivent être utilisés.

2
JohnLBevan

Avis de non-responsabilité:

Pour ma réponse, je vais me concentrer sur la question du style et oublier si un ID, un identifiant et un mot de passe simple sont un bon ensemble de paramètres à utiliser, du point de vue de la sécurité. Vous devriez être en mesure d'extrapoler ma réponse à n'importe quel ensemble de données de base que vous utilisez pour comprendre les privilèges de l'utilisateur ...

Je considère également que user.isAdmin() est équivalent à IsAdmin(User user). C'est un choix que vous devez faire.

Répondre:

Ma recommandation est à l'un ou l'autre utilisateur uniquement:

public bool IsAdmin(User user);

Ou utilisez les deux, selon:

public bool IsAdmin(User user); // calls the private method below
private bool IsAdmin(int id, string name, string password);

Raisons de la méthode publique:

Lors de l'écriture de méthodes publiques, c'est généralement une bonne idée de réfléchir à la façon dont elles seront utilisées.

Dans ce cas particulier, la raison pour laquelle vous appelez généralement cette méthode est de déterminer si un certain utilisateur est un administrateur. C'est une méthode dont vous avez besoin. Vous ne voulez vraiment pas que chaque appelant doive choisir les bons paramètres à envoyer (et risquez des erreurs dues à la duplication de code).

Raisons de la méthode privée:

La méthode privée ne recevant que l'ensemble minimal de paramètres requis peut parfois être une bonne chose. Parfois pas tellement.

L'une des bonnes choses à propos du fractionnement de ces méthodes est que vous pouvez appeler la version privée à partir de plusieurs méthodes publiques dans la même classe. Par exemple, si vous vouliez (pour une raison quelconque) avoir une logique légèrement différente pour Localuser et RemoteUser (peut-être y a-t-il un paramètre pour activer/désactiver les administrateurs distants?):

public bool IsAdmin(Localuser user); // Just call private method
public bool IsAdmin(RemoteUser user); // Check if setting is on, then call private method
private bool IsAdmin(int id, string name, string password);

De plus, si pour une raison quelconque vous devez rendre la méthode privée publique ... vous pouvez. C'est aussi simple que de simplement changer private en public. Cela peut ne pas sembler être un gros avantage pour ce cas, mais parfois c'est vraiment le cas.

De plus, lors des tests unitaires, vous pourrez faire beaucoup plus de tests atomiques. Il est généralement très agréable de ne pas avoir à créer un objet utilisateur complet pour exécuter des tests sur cette méthode. Et si vous voulez une couverture complète, vous pouvez tester les deux appels.

1
diegoreymendez
public bool IsAdmin(int id, string name, string password);

est mauvais pour les raisons énumérées. Cela ne veut pas dire

public bool IsAdmin(User user);

est automatiquement bon. En particulier si l'utilisateur est un objet ayant des méthodes comme: getOrders(), getFriends(), getAdresses(), ...

Vous pouvez envisager de refactoriser le domaine avec un type UserCredentials contenant uniquement l'identifiant, le nom, le mot de passe et le transmettre à IsAdmin au lieu de toutes les données utilisateur inutiles.

0
tkruse