84

Let's say I have a function IsAdmin that checks whether a user is an admin. Let's also say that the admin checking is done by matching user id, name and password against some sort of rule (not important).

In my head there are then two possible function signatures for this:

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

I most often go for the second type of signature, thinking that:

  • The function signature gives the reader a lot more info
  • The logic contained inside the function doesn't have to know about the User class
  • It usually results in slightly less code inside the function

However I sometimes question this approach, and also realize that at some point it would become unwieldy. If for example a function would map between ten different object fields into a resulting bool I would obviously send in the entire object. But apart from a stark example like that I can't see a reason to pass in the actual object.

I would appreciate any arguments for either style, as well as any general observations you might offer.

I program in both object oriented and functional styles, so the question should be seen as regarding any and all idioms.

gnat
  • 21,442
  • 29
  • 112
  • 288
Anders Arpi
  • 959
  • 7
  • 8
  • 41
    Off-topic: Out of curiousity, why does the "is admin" status of a user depend on their password? – stakx Nov 03 '13 at 15:45
  • 43
    @syb0rg Wrong. In C# that's the [naming convention](http://msdn.microsoft.com/en-us/library/ms229043(v=vs.110).aspx). – magnattic Nov 03 '13 at 17:14
  • 2
    @atticae The OP didn't specify the language they're using. Almost every other language uses [camelCase](http://msdn.microsoft.com/en-us/library/x2dbyw72(v=vs.71).aspx). – syb0rg Nov 03 '13 at 17:17
  • 68
    @syb0rg Right, he did not specify the language so I think its rather odd to tell him he is violating the convention of a certain language. – magnattic Nov 03 '13 at 17:22
  • 31
    @syb0rg The general statement that "function names shouldn't start with an uppercase letter" is wrong, because there are languages where they should. Anyway, I did not mean to offend you, I was just trying to clear this up. This is getting pretty much off-topic to the question itself. I dont see the need, but if you want to continue lets move the debate to the [chat](http://chat.stackexchange.com/). – magnattic Nov 03 '13 at 17:41
  • 3
    @atticae I'm not offended, I agree the original statement could have been worded better. – syb0rg Nov 03 '13 at 18:00
  • 6
    Just as a general point, rolling your own security is usually a *bad thing(tm)*. Most languages and frameworks have a security / permission systems available and there are good reasons for using them even when you want something simpler. – James Snell Nov 03 '13 at 22:44
  • 3
    If you store passwords in plaintext, please feel free to tell me more about your system so that I could avoid using it. TL;DR: **[storing passwords in a recoverable form is BAD](http://security.stackexchange.com/questions/5813/am-i-wrong-to-believe-that-passwords-should-never-be-recoverable-one-way-hash)**. – Deer Hunter Nov 04 '13 at 13:40
  • 1
    IsAdmin just be a method of User Entity. – Maykonn Nov 05 '13 at 20:43

9 Answers9

124

I personally prefer the first method of just IsAdmin(User user)

It's much easier to use, and if your criteria for IsAdmin changes at a later date (perhaps based on roles, or isActive), you don't need to rewrite your method signature everywhere.

It's also probably more secure as you aren't advertising what properties determine if a user is an Admin or not, or passing around the password property everywhere. And syrion makes a good point that what happens when your id doesn't match the name/password?

The length of code inside a function shouldn't really matter providing the method does its job, and I'd much rather have shorter and simpler application code than helper method code.

Rachel
  • 23,979
  • 16
  • 91
  • 159
  • 3
    I think the refactoring issue is a very good point - thank you. – Anders Arpi Nov 03 '13 at 14:41
  • 1
    I was going to make the method signature argument if you didn't. This is especially helpful when there's a lot of dependencies on your method that are maintained by other programmers. This helps keep people out of each other's way. +1 – jmort253 Nov 03 '13 at 20:35
  • 1
    I agree with this answer, but let me flag two situations where the other approach ("minimum data") *might* be preferable: (1) You want to cache method results. Better not to use a whole object as the cache key, or have to check an object's properties on every invocation. (2) You want the method/function to execute asynchronously, which might involve serializing the arguments and, say, storing them in a table. Easier and simpler to look up an integer than an object blob when managing pending jobs. – GladstoneKeep Nov 04 '13 at 15:50
  • The primitive obsession anti-pattern could be horrible for unit testing. – Samuel Nov 05 '13 at 23:03
84

The first signature is superior because it allows the encapsulation of user-related logic in the User object itself. It is not beneficial to have logic for the construction of the "id, name, password" tuple strewn about your code base. Furthermore, it complicates the isAdmin function: what happens if someone passes in an id that does not match the name, for example? What if you want to check if a given User is an administrator from a context where you should not know their password?

Furthermore, as a note on your third point in favor of the style with extra arguments; it may result in "less code inside the function," but where does that logic go? It can't just vanish! Instead, it's spread around every single place where the function is called. To save five lines of code in a single place, you've paid five lines per use of the function!

asthasr
  • 3,439
  • 3
  • 17
  • 24
  • 45
    Following your logic, wouldn't `user.IsAdmin()` be a lot better? – Anders Arpi Nov 03 '13 at 14:37
  • 13
    Yes, absolutely. – asthasr Nov 03 '13 at 14:38
  • 6
    @AndersHolmström It depends, are you testing if the user is an admin in general, or just an admin for the current page/form you're on. If in general, then yes that would be better, but if you're only testing `IsAdmin` for a specific form or function, that should be unrelated to the user – Rachel Nov 03 '13 at 14:41
  • 1
    Indeed - the context obviously affects the choices here. – Anders Arpi Nov 03 '13 at 14:42
  • 1
    Or indeed `user.MemberOf(admins)` or 'admins.Contains(user)' be even better still? – James Snell Nov 03 '13 at 15:15
  • 2
    @JamesSnell I think that `admins.Contains(user)` is probably better, since that way a (possibly malicious) instance of `User` can't lie about its admin permissions. – AJMansfield Nov 03 '13 at 19:00
  • 41
    @Anders: no it shouldn't. A user shouldn't decide whether it is an Admin or not. The Privileges or Security system should. Something being tightly coupled to a class doesn't mean it is a good idea to make it part of that class – Marjan Venema Nov 03 '13 at 19:44
  • 1
    @AJMansfield: I prefer that 2nd option myself too it's very easy to implement `admins` as a `List` type then there's less code to write (and test.) As for being able to lie about permissions then if you've got rogue code in play then all bets are off because a malicious group could say all users are admin... – James Snell Nov 03 '13 at 22:41
  • 3
    @MarjanVenema: True, but the User class already knows this. Users don't exist in a vacuum. In particular, in realistic systems users may come from a number of systems. Thus, each user object knows where it came from, and can check _that_ system for admin privileges. If you take that logic outside the user class, you'd have to put the knowledge of user databases everywhere. – MSalters Nov 04 '13 at 12:46
  • 1
    @MSalters: No, why should a user class be aware of permissions? I am pretty sure a user instance can exist very well without ever being aware of being allowed to do something or not. – Marjan Venema Nov 04 '13 at 12:49
  • 1
    @MarjanVenema: It should know the identity of permissions, not necessarily the meaning. I.e. it may know about an "Admin" permission, yet not know what admins are allowed to do. The simple reason is that permissions are almost always a function (designwise) of users. You have a permission P because you are user U, not because you are running application A. – MSalters Nov 04 '13 at 12:58
  • 3
    @MSalters potatoes potaatoes. If it knows about a list of somethings it knows about somethings it doesn't need to know about. And I'd say you have permissions P because you are user U running application A, not just because I am me. My permissions for application B won't do me one bit of good in application A. And because permissions are about the combination of at least two things, they shouldn't be part of either, but implemented in something that has access to whatever is combined. – Marjan Venema Nov 04 '13 at 13:06
  • 11
    @MarjanVenema - the idea that the User class must not "decide" whether an instance is an admin strikes me as being a bit cargo-cultish. You really can't make that generalization so strongly. If your needs are complex, then perhaps it's beneficial to encapsulate them in a separate "system", but citing KISS+YAGNI I'd like to make that decision actually faced with that complexity, and not as a general rule :-). And note that *even if* the logic is not "part" of User it can still be useful as a matter of practicality to have a passthrough on User that just delegates to a more complex system. – Eamon Nerbonne Nov 04 '13 at 21:39
  • @EamonNerbonne: Cargo cultish? Really? Having proper separation of reponsibilities will always aid in keeping things clean and even facilitate the refactorings needed as a result of KISS and YAGNI. Also, there is a major difference between YAGNI and knowing something is in the pipeline but ignoring it because it isn't here yet... – Marjan Venema Nov 05 '13 at 07:31
  • @EamonNerbonne: Oh, you might be interested in the question that Glen posted as a result of my comment (which was repeated under his answer): http://programmers.stackexchange.com/questions/216443/what-is-meant-by-a-user-shouldnt-decide-whether-it-is-an-admin-or-not-the-pr – Marjan Venema Nov 05 '13 at 07:44
  • 2
    @MarjanVenema, I agree with Eamon here. There are times where a an independent Privilege or Security system is called for, but there are definitely times when that's over-engineering. KISS and YAGNI already apply at this level. – Ben Lee Nov 05 '13 at 09:05
  • @MarjanVenema sorry that came across too harsh. I just mean that an authorization system shouldn't be an end in and of itself. Sometimes just distinguishing between admins and others is enough. – Eamon Nerbonne Nov 05 '13 at 15:58
  • @EamonNerbonne: I never said that there should be more than just a distinction between admins and others, just that it shouldn't be in the user class as a user class should not be responsible for permissions. – Marjan Venema Nov 05 '13 at 17:13
  • @BenLee: The system should always be independent, regardless of its complexity. It has nothing to do with over-engineering and everything to do with segregation of responsibilities. No matter how simple the security/permissions system, it is always a combination of user plus context and therefore should not be part of the user class. – Marjan Venema Nov 05 '13 at 17:15
  • 3
    @MarjanVenema, we'll just have to disagree on principle then. You call my approach inappropriate mixing of concerns/responsibilities. I call your approach over-engineering. I don't think we can reconcile these opposing viewpoints. – Ben Lee Nov 05 '13 at 19:30
  • 3
    @BenLee: probably not, at least not without a face-to-face over drinks :-) – Marjan Venema Nov 05 '13 at 19:39
66

The first, but not (only) for the reasons others have given.

public bool IsAdmin(User user);

This is type safe. Especially since User is a type you defined yourself, there is little or no opportunity to transpose or switch up arguments. It clearly operates on Users and is not some generic function accepting any int and two strings. This is a big part of the point of using a type-safe language like Java or C# which your code looks like. If you are asking about a specific language, you might want to add a tag for that language to your question.

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

Here, any int and two strings will do. What prevents you from transposing the name and password? The second is more work to use and allows more opportunity for error.

Presumably, both functions require that user.getId(), user.getName(), and user.getPassword() be called, either inside the function (in the first case) or before calling the function (in the second), so the amount of coupling is the same either way. Actually, since this function is only valid on Users, in Java I would eliminate all arguments and make this an instance method on User objects:

user.isAdmin();

Since this function is already tightly coupled to Users, it makes sense to make it a part of what a user is.

P.S. I'm sure this is just an example, but it looks like you are storing a password. You should instead only store a cryptographically secure password hash. During login, the supplied password should be hashed the same way and compared against the stored hash. Sending passwords around in plain-text should be avoided. If you violate this rule and isAdmin(int Str Str) were to log the user name, then if you transposed the name and password in your code, the password could be logged instead which creates a security issue (don't write passwords to logs) and is another argument in favor of passing an object instead of its component parts (or using a class method).

GlenPeterson
  • 14,890
  • 6
  • 47
  • 75
  • 11
    A user shouldn't decide whether it is an Admin or not. The Privileges or Security system should. Something being tightly coupled to a class doesn't mean it is a good idea to make it part of that class. – Marjan Venema Nov 03 '13 at 19:43
  • 1
    *Sigh*, unfortunately we'll be making the "don't store passwords in plain text" arguments until the end of time, or until Google or someone waters down the languages to the point where the programming language handles it all for you. :D Maybe continually reiterating it eventually gets through to people, and if it gets through to one, then I guess that's worth continuing on! :) – jmort253 Nov 03 '13 at 20:39
  • 6
    @MarjanVenema The user isn't deciding anything. The User object/table stores data about each user. Actual users don't get to change everything about themselves. Even when the user changes something they are allowed to, it may trigger security alerts like an email if they change their email address or their user ID or password. Are you using a system where users are magically allowed to change everything about certain object types? I am not familiar with such a system. – GlenPeterson Nov 03 '13 at 23:57
  • 2
    @GlenPeterson: are you deliberately misunderstanding me? When I said user, I of course meant the user class. – Marjan Venema Nov 04 '13 at 07:32
  • 2
    @GlenPeterson Totally off topic, but multiple rounds of hashing and cryptographic functions will weaken the data. – Gusdor Nov 04 '13 at 09:36
  • 3
    @MarjanVenema: I am not deliberately being difficult. I think we just have very different perspectives and I would like to understand yours better. I have opened a new question where this can be better discussed: http://programmers.stackexchange.com/questions/216443/what-is-meant-by-a-user-shouldnt-decide-whether-it-is-an-admin-or-not-the-pr – GlenPeterson Nov 04 '13 at 12:17
  • Well spotted (the difference in perspective). Upvoted the question you linked (very well written up). Refraining from answering as I see you already got several ones that are expressing my views better than I was in my comment. – Marjan Venema Nov 04 '13 at 12:38
  • @Gusdor: I have never heard that before and have repeatedly heard the opposite. Triple DES makes DES stronger by applying it three times. Do you have a reference or source to back up your comment? – GlenPeterson Nov 04 '13 at 13:26
  • @GlenPeterson Triple DES makes brute force take longer. That is not the same. I do not have a source however. – Gusdor Nov 04 '13 at 13:36
  • 1
    @GlenPeterson, my suggestion: [Use bcrypt](http://codahale.com/how-to-safely-store-a-password/) – Ben Lee Nov 05 '13 at 09:10
  • User.IsAdmin() is a fine choice even if there is a security manager that must be consulted. User.IsAdmin() hides implementation; how much info is needed to validate the user. Sure User may call SecurityManager.IsAdmin(id, name, pw), but at the SecurityManager level that's ok because this is what SecurityManager needs to validate a user. – SeattleCplusplus May 10 '14 at 19:50
  • @GlenPeterson See [this question on hashing multiple times](http://programmers.stackexchange.com/questions/115406/is-it-more-secure-to-hash-a-password-multiple-times). It _can_ be more secure if you're careful about it, but more likely than not it will become less secure due to collisions. Hence, as BenLee suggested, use bcrypt, which is built to do this safely. – Izkata May 11 '14 at 05:43
6

If your language of choice doesn't pass structures by value, then (User user) variant seems better. What if someday you decide to scrap the username and just use the ID/password to identify the user?

Also, this approach inevitably leads to long and overblown method calls, or inconsistencies. Is passing 3 arguments okay? How about 5? Or 6? Why think of that, if passing an object is cheap in terms of resources (even cheaper than passing 3 arguments, probably)?

And I (sort of) disagree that the second approach gives the reader more info - intuitively, the method call is asking "whether the user has admin rights", not "whether the given id/name/password combination has admin rights".

So, unless passing the User object would result in copying a large structure, the first approach is cleaner and more logical to me.

Maciej Stachowski
  • 747
  • 1
  • 5
  • 12
3

I would probably have both. The first one as an external API, with some hope of stability in time. The second one as a private implementation called by the external API.

If at some later time I have to change the checking rule, I would just write a new private function with a new signature called by the external one.

This has the advantage of easyness to change inner implementation. Also it's really usual that at some time you may need both functions available at the same time and call one or the other depending of some external context change (for instance you may have coexisting old Users and new Users with different fields).

Regarding the title of the question, in a way in both cases you are providing the minimum necessary information. A User seems to be the smallest Application Related Data Structure containing the information. The three fields id, password and name seems to be the smallest implementation datas actually needed, but these are not really Application level object.

In other words If you were dealing with a database a User would be a record, while id, password and login would be fields in that record.

kriss
  • 1,040
  • 1
  • 8
  • 11
  • I don't see the reason for private method. Why maintain both? If private method needs different arguments you will still have to modify public one. And you will test it through public one. And no it's not usual that you need both at some time later. You are guessing here - YAGNI. – Piotr Perak Nov 07 '13 at 22:14
  • You are assuming for instance that the User data structure does not have any other field from the beginning. It is usually not true. The second functions makes clear which inner part of the user is checked to see if it's an admin. Typical use cases may be of the kind : if user creation time is before some date, then call old rule, if not call new rule (which may depend on other parts of User, or depends on the same but using another rule). Splitting the code this way you'll get two minimal functions: a minimal conceptual API and a minimal implementation function. – kriss Nov 08 '13 at 17:01
  • in other words this way it will be immediately visible from the internal function signature which parts of User are used or not. In production code that is not always obvious. I'm currently working on a large codebase with several years maintenance history and this kind of splitting is very useful for long terme maintenance. If you don't intend to maintain the code, it's indeed useless (but even providing a stable conceptual API is also probably useless). – kriss Nov 08 '13 at 17:05
  • Another word: I certainly won't unit-test the internal method through external one. That's bad testing practice. – kriss Nov 08 '13 at 17:07
  • You were talking about private not internal method. How do you test private methods? – Piotr Perak Nov 08 '13 at 17:44
  • I wonder how your public method looks then. It is just delegation to private method. So you should inline that private method and remove it. All the code that will be in public method is code for checking whether user is an admin. – Piotr Perak Nov 08 '13 at 17:45
  • this is C++ secific (and the question is not, C++ is just an illustration here, doesn't change API). There are ways to call private methods (have à look at SO) but they are complicated. In this case I would probably use a helper function outside class scope (and provide other fields from current class if the user fields are not the only ones necessary). Better to have a pure helper function. – kriss Nov 09 '13 at 09:51
  • for the delegation point, not really. Unwrapping some parameters is not merely delegation. To have actual delegation the inner called function signature should be the same as the public one. Which is not what we have here. – kriss Nov 09 '13 at 09:53
  • If you test private methods, you are doing it totally wrong. And you add another helper function. Now you have 3 methods instead of one. – Piotr Perak Nov 09 '13 at 11:59
  • I mean using helper function *instead* of private method for something that is basically not a method (useless members beside explicit parameters). – kriss Nov 09 '13 at 14:42
  • The underlying idea is the same that the Facade Design Pattern. It differs merely in the way it mixes functional and OO paradigms in what I'm suggesting here. (User API is OO, and inner subsystems uses classical functions). Making inner subsystem also object would add useless comlexity. – kriss Nov 09 '13 at 14:50
  • 1
    This is a good practice, and there's a name for it. It's called the "single responsibility principle". It can be applied to both classes and methods. Having methods with a single responsibility is a good way to make more modular and maintainable code. In this case, one task is picking basic set of data from the user object, and another task is checking that basic set of data to see if the user is an administrator. By following this principle you get the bonus of being able to compose methods and avoid code duplication. This also means, as stated by @kriss that you get better unit tests. – diegoreymendez May 10 '14 at 18:06
3

There is one negative point to send classes (reference types) to methods, and that is, Mass-Assignment Vulnerability. Imagine that instead of IsAdmin(User user) method, I have UpdateUser(User user) method.

If User class has a Boolean property called IsAdmin, and if I don't check it during the execution of my method, then I'm vulnerable to mass-assignment. GitHub was attacked by this very signature in 2012.

Saeed Neamati
  • 18,142
  • 23
  • 87
  • 125
2

I'd go with this approach:

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

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

public interface IUser
{
    string Username {get;}
    string Password {get;}
    IID Id {get;}
}
  • Using the interface type as a parameter for this function allows you to pass in User objects, to define mock User objects for testing, and gives you more flexibility around both the use and maintenance of this function.

  • I also added the keyword this to make the function an extension method of all IUser classes; this way you can write code in a more OO friendly way and use this function in Linq queries. Simpler still would be to define this function in IUser and User, but I assume there's some reason why you decided to put this method outside of that class?

  • I use the custom interface IID instead of int as this allows you to redefine properties of your ID; e.g. should you ever need to change from int to long, Guid, or something else. That's probably overkill, but always good to try to make things flexible such that you're not locked into early decisions.

  • NB: Your IUser object can be in a different namespace and/or assembly to the User class, so you have the option to keep your User code completely separate from your IsAdmin code, with them only sharing one referenced library. Exactly what you do there is a call for how you suspect these items to be used.

JohnLBevan
  • 425
  • 2
  • 4
  • 13
  • 3
    IUser is useless here and only adds complexity. I don't see why you couldn't use real User in tests. – Piotr Perak Nov 07 '13 at 22:11
  • 1
    It adds future flexibility for very little effort, so whilst not adding value currently it doesn't hurt. Personally I prefer to always do this since it avoids having to think through future possibilities for all scenarios (which is pretty much impossible given the unpredictable nature of requirements, and will take a lot more time to even get a partially good answer than it would to stick in an interface). – JohnLBevan May 11 '14 at 19:27
  • @Peri a real User class might be complex to create, taking an interface allows you to mock it out so you can keep your tests simple – jk. May 12 '14 at 14:09
  • @JohnLBevan: YAGNI!!! – Piotr Perak May 13 '14 at 20:57
  • @jk.: if it's hard to build User then something is wrong – Piotr Perak May 13 '14 at 20:59
1

Disclaimers:

For my reply, I'm going to focus on the style issue and forget about wether an ID, login and plain password is a good set of parameters to use, from the security point of view. You should be able to extrapolate my answer to whatever basic set of data you're using to figure out about the user's privileges...

I also consider user.isAdmin() to be equivalent to IsAdmin(User user). That's a choice for you to make.

Reply:

My recommendation is to either user only:

public bool IsAdmin(User user);

Or use both, along the lines of:

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

Reasons for the public method:

When writing public methods it's usually a good idea to think about how they will be used.

For this particular case, the reason why you'd usually call this method is to figure out if a certain user is an administrator. That's a method you need. You really don't want each caller to have to pick the right parameters to send over (and risk mistakes due to code duplication).

Reasons for the private method:

The private method receiving only the minimum set of required parameters can be a good thing sometimes. Sometimes not so much.

One of the good things about splitting these methods is that you can call the private version from several public methods in the same class. For example if you wanted (for whatever reason) to have slightly different logic for Localuser and RemoteUser (maybe there's a setting to turn on/off remote admins?):

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);

Additionally, if for any reason you need to make the private method public... you can. It's as easy as just changing private to public. It may not seem like that big of an advantage for this case, but sometimes it really is.

Also, when unit-testing you'll be able to make much more atomic tests. It's usually very nice not to have to create a full user object to run tests on this method. And if you want full coverage, you can test both calls.

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

is bad for the reasons listed. That does not mean

public bool IsAdmin(User user);

is automatically good. In particular if User is an object having methods like: getOrders(), getFriends(), getAdresses(), ...

You might consider refactoring the domain with an UserCredentials type containing only id, name, password, and passing that to IsAdmin instead of all the unneeded User data.

tkruse
  • 246
  • 2
  • 11